Built-in transforms handle the everyday jobs — a derivative here, a moving average there. The moment your question becomes “…and now combine these signals in a way the toolbox does not offer”, you need a custom function. PlotJuggler’s Custom Function Editor provides exactly that: you write a small function in Lua that takes multiple recorded series as inputs and produces one new series as output. It is a defining PJ3 feature that PJ4 keeps (adding Python as an alternative — the Lua craft in this tutorial remains fully relevant). This guide explains the model, works a real pattern, and stays honest about what is concept versus API. Set-up context: Getting Started and the Tutorials hub.
First, exhaust the built-ins
Custom code is the second resort, not the first — a built-in transform is maintained, tested and instant. PlotJuggler’s built-in editor offers transforms including derivative (rate of change), moving average (smoothing), integral (accumulated quantity) and scale (unit conversions and calibration factors), among others; PJ4’s analysis set also names filters and FFT. Reach for Lua when the computation genuinely combines signals — a ratio, a magnitude from components, an angle from a quaternion — or applies logic a fixed transform cannot express. A useful rule: if the formula fits in one arithmetic expression over several series, it is custom-function territory; if it is one operation on one series, a built-in almost certainly already does it.
The model: multiple inputs, one output
Everything about the Custom Function Editor follows from one sentence in the project’s own description: custom functions are multi-input, single-output functions written in Lua. Concretely:
- Inputs: you select the recorded series the computation reads — one, two, or several. At each timestamp, the function sees the inputs’ values at that moment.
- The function: your Lua code combines those values into a single result for that timestamp — arithmetic, trigonometry, conditionals, whatever the language offers.
- Output: the results form one new series. It is a normal PlotJuggler series in every respect: plot it, overlay it, feed it into further transforms, save it in a layout.
- One output per function: need three derived quantities? Define three functions. That constraint keeps each definition small, nameable and reusable — embrace it rather than fighting it.
A real-world pattern: quaternion to Euler angles (PX4)
The best-documented real use of this feature comes from the PX4 ecosystem: the official PX4 documentation carries a full “Log Analysis using PlotJuggler” guide whose workflow uses Lua custom series to convert a logged quaternion attitude into roll, pitch and yaw. Described conceptually — which is how you should design any custom function before writing it — the pattern is:
- Inputs: the four quaternion component series from the ULog (the vehicle’s attitude estimate, as recorded).
- Computation: the standard quaternion-to-Euler conversion, applied sample by sample — trigonometry over the four inputs, nothing more exotic.
- Outputs: three separate custom functions, one each for roll, pitch and yaw, each emitting a single angle series — the multi-input/single-output model, applied three times.
- Payoff: angles a human can read, plotted against time and against each other, from a log that only ever stored quaternions.
We deliberately describe the inputs and outputs rather than printing “the API”: exact editor mechanics belong to the application in front of you and the PX4 guide itself, and copied signatures rot quickly. The transferable skill is the decomposition — inputs, per-sample computation, one output — which fits every custom function you will ever write here, from a two-input power calculation (voltage × current, straight from a CSV) to a four-input attitude conversion.
Your first custom function, step by step
- Pick a computation you can verify by hand. A scale-and-combine you can check at one timestamp with a calculator — power from voltage and current is perfect. First functions are for learning the workflow, not for solving the hard problem.
- Load data containing the inputs and plot the inputs themselves first. If an input is wrong, the function will be wrong more confusingly.
- Create the custom function in the Custom Function Editor, select the input series, and write the Lua expression of your formula: read the inputs, compute, return the single output value.
- Plot the output against its inputs. Check the hand-verifiable timestamp. Check a second one. Check behaviour at the interesting event (the step, the peak) — not just at rest.
- Name it like you mean it. The output series joins your signal list and your layouts; a name that states quantity and unit (“power_watts”) saves every future reader a trip back into the editor.
- Save the layout. Custom functions are part of the view you built; saving the layout is how the derived series survives to the next session and the next recording (see layouts).
-- Illustrative pattern only — pseudocode showing the shape of a custom function,
-- NOT PlotJuggler editor syntax. Real mechanics: the Custom Function Editor
-- and, for the quaternion case, the PX4 documentation guide.
inputs: voltage, current -- recorded series, selected in the editor
output: power = voltage * current -- one derived value per timestampDesign habits that pay off
- Keep functions small and single-purpose. Chain two clear functions instead of writing one cryptic one; the intermediate series is often worth plotting in its own right.
- Mind the edges. Decide what your function should do where inputs are missing or zero — a division that is elegant mid-flight can be a spike factory at the boundary. A conditional in Lua is cheap insurance.
- Prefer built-ins inside functions sparingly. Smooth an input with a moving average first if the raw signal is too noisy for stable derivatives or ratios; transforms compose, in the editor as elsewhere.
- Re-verify after changing inputs. Pointing an existing function at a different recording assumes the same series names and units. The two-timestamp hand check takes a minute and catches both assumptions.
And in PlotJuggler 4: Lua, or Python
PJ4 keeps this entire model and adds Python scripting as an alternative to Lua for custom transforms — the subject of our PJ4 Python scripting guide. Nothing about the decomposition changes: inputs in, one derived series out. If your team lives in Python, PJ4 lets the transform live there too; if your Lua functions already work, they remain the right tool. Either way, the derived series you build become candidates for the two views that exploit them best — XY plots and the multi-recording overlays in comparing bag files. The full series is indexed on the Tutorials hub, and the program itself is on the Download page.
