PlotJuggler Lua Custom Functions: Build Your Own Transforms

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

  1. 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.
  2. Load data containing the inputs and plot the inputs themselves first. If an input is wrong, the function will be wrong more confusingly.
  3. 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.
  4. 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.
  5. 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.
  6. 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 timestamp

Design 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.

Similar Posts