Perspective XY Chart: How Do I Bind Two Data Sources?

Ryan Tanaka8 min read
HMI / SCADAOther ManufacturerTroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Here is what you see. You bound two custom properties, props.custom.processTempData and props.custom.outputTempData, into props.series[0].data and props.series[1].data. The bindings evaluate without a quality error. The chart is blank or draws one trace. The component also throws a schema error saying it expected an object and got an array.

Start here: the data is in the wrong property. The query and the custom properties are fine. The one-source version of this chart already works, so the problem is how the chart is wired.

Skip the Fixes That Waste Time

These are the usual first attempts. Each one fails for a specific reason.

  • Binding the dataset straight into series[n].data. That property is a small mapping object that tells the series which data source to read and which fields are X and Y. An array there overwrites the mapping. The series loses its pointer to the data, and the schema check rejects the type.
  • Wrapping the array in an object to clear the error. The red text goes away, but the chart still draws nothing. The keys you invented are not the keys the series reads.
  • Re-running the query or rebuilding the custom properties. That is not the fault. The data arrives correctly, and it lands in a property the renderer does not treat as data.
  • Plotting two years on a date axis with raw timestamps. Both traces render, but side by side, with one year on the left and the next year on the right. They do not overlay.
  • Stripping the year from the timestamp and hoping the points line up. Both series share one axis, but weekly buckets from different years rarely fall on the same calendar date. You get interleaved categories with gaps, not an overlay.

Learn How the XY Chart Separates Data from Rendering

The XY Chart splits the job across three property groups:

  • props.dataSources is an object. Each key is a named dataset, either a dataset or a list of row objects. Bind your query or transform output here.
  • props.series is an array of render definitions. Each series names a data source key, the field used for X, the field used for Y, the axis it plots against, the render type, and its appearance.
  • props.xAxes and props.yAxes define axis type (date, category, value) and names that series reference.

Data flows one way: data source key, then series[n].data.source, then the x and y field names inside each row. One source can feed several series. Several sources can each feed their own series. In every case, the bindings go on dataSources, never on series.

Match the Symptom to the Cause

What the chart shows Cause Fix
Schema error: object expected, array found Dataset bound into series[n].data Move the binding to a key under props.dataSources
Blank chart, no error data.source does not match a data source key, or the x/y field names do not match row keys (case-sensitive) Copy the key and field names exactly
One trace draws, the second does not Second series points at the first source, or its y field does not exist in that source Point series[1].data.source at the second key
Two years render side by side Date axis with full timestamps; the X domains do not overlap Normalize X to a year-independent key, or give each series its own X axis
Overlay has gaps and zig-zags Year-stripped MM-dd keys differ between years, so the category axis takes the union Key on week number, not calendar date
X labels like 02-26 do not appear or collapse String X value on a date axis Switch that X axis to category
Weeks out of order Unpadded week strings sort as text (W10 before W2) Zero-pad (W02) or use an integer on a value axis

Decide Between One Merged Source and Two Separate Sources

Make this call before you touch the property tree.

  • Merge into one source when both measurements share the same X domain row for row. Build one row per t_stamp carrying both values, for example [{"t_stamp": "2025-02-26 00:00:00", "output_temp": 123, "process_temp": 456}, ...]. Then create two series on the same source with different y fields. This is the cleanest option: one binding, one query, guaranteed X alignment.
  • Keep two sources when X values do not coincide, such as different sample rates, different tables, or different years. Create two keys under dataSources and two series, each pointing at its own key.
  • Add a second X axis when the domains differ and you want each series on its own time scale. Define a second entry in xAxes and assign the second series to it by name.
  • Normalize X when you want different years overlaid on one scale. Neither raw timestamps nor a second date axis gives a true overlay. Convert X to a key that means the same thing every year.

Wire Two Data Sources Into the Chart

  1. Delete the bindings on props.series[0].data and props.series[1].data. Restore each to an object with source, x, and y keys.
  2. Under props.dataSources, add two keys, for example processTemp and outputTemp. Bind each one to its query, either directly or through props.custom.
  3. Set series[0].data.source to processTemp and series[1].data.source to outputTemp.
  4. Set x on both series to the same field name, such as t_stamp. Set y to the value field in each source.
  5. Check the X axis type. Use a date axis for real Date values. Use a category axis for string keys like MM-dd or week labels.
  6. Point both series at the same X axis name for an overlay, or at different X axes for separate time scales.

Resulting structure:

dataSources:
  processTemp: [ {"t_stamp": ..., "process_temp": ...}, ... ]
  outputTemp:  [ {"t_stamp": ..., "output_temp": ...}, ... ]

series[0].data: {"source": "processTemp", "x": "t_stamp", "y": "process_temp"}
series[1].data: {"source": "outputTemp",  "x": "t_stamp", "y": "output_temp"}

Overlay Two Years on One X Axis

Comparing weekly values year over year is the case that trips people up. A working approach is a transform that filters the query by a code from a sibling text field, drops the year from WEEK_START, formats it as MM-dd, and returns a list of dicts. Bind one of these to each data source key, one per year. Both series then share a category X axis, and they overlay.

The weakness is the key itself. A weekly bucket starts on a fixed weekday, and that weekday falls on a different calendar date each year. A week starting on 02-24 one year starts on 02-23 or 02-22 the next. MM-dd keys from two years almost never match, so the category axis builds the union of both key sets. The result is two traces that alternate categories and leave gaps. Key on week-of-year instead.

Transform body for a dataSources binding. Here value is the query dataset:


Why each change matters:

  • Week key, not date key. W09 means the same slot every year, so both series land on the same category.
  • Zero-padded label. The string sort and the category order stay numeric.
  • getWeekYear() instead of YEAR. A late-December week can belong to week 1 of the following year. YEAR would tag it with the old year and put a stray point at the start of the wrong series.
  • Explicit week rules. follows the gateway locale unless you set first day and minimal days. If you leave them unset, a locale change shifts every week by one.
  • Imports and Calendar created once. This avoids re-importing and re-allocating on every row.

For a single merged source, pivot instead. Return one row per week with a field per year, for example {"t_stamp": "W09", "y2024": ..., "y2025": ...}. Then point both series at that source with y set to each year field. Use this when the year pair is fixed. Use two sources when the user picks the years at runtime.

Add a Legend Keyed to the Year

  1. Enable the chart legend under props.legend.
  2. Set each series name or label text to the year it plots. Legend entries take their text from the series, not from the data source key.
  3. Bind that text to the same year parameter that drives the data source query. The legend then follows the user's selection instead of showing a stale hard-coded year.
  4. Set each series stroke and fill color explicitly. Default palette colors are assigned by series index, so reordering series swaps colors between years.

Verify the Chart Before You Hand It Over

  1. In the property editor, expand each dataSources key. Confirm it holds rows, and that the row keys match the x and y names exactly, including case.
  2. Confirm each series[n].data is still an object with source, x, y, and that no binding icon remains on it.
  3. Count X categories. A full year of weekly data gives 52 or 53 labels with no duplicates and no MM-dd near-misses.
  4. Hover one week. The tooltip or cursor shows values from both years at the same category.
  5. Change the TextField_3 value. Both traces and both legend entries refresh.
  6. Check the Designer output console and the gateway logs for transform exceptions. A script error leaves the last good value in place and hides the failure.

FAQ

How do I bind two datasets to one Perspective XY Chart?

Add two keys under props.dataSources and bind each to its query. Then set series[0].data.source and series[1].data.source to those keys, with matching x and y field names. Never bind the data into series[n].data itself.

How do I fix the XY Chart error that says it expected an object instead of an array?

Remove the binding from series[n].data and restore it to an object with source, x, and y. Move the array to a key under props.dataSources. Wrapping the array in an object clears the error but still draws nothing.

How do I overlay two years of weekly data on the same X axis?

Transform each year's timestamps into a year-independent key, such as a zero-padded week number (W01 to W53), and plot both series on one category X axis. Avoid MM-dd keys, because weekly buckets start on different calendar dates each year and will not line up.

When should I stop and escalate to Inductive Automation support?

Escalate if the data source keys hold valid rows, the series mappings match the field names exactly, no transform errors are logged, and the chart still renders blank or throws a schema error. Send support the exported view JSON, your Ignition version, and the exact error text from the Designer console.

Back to blog