The symptom: a Perspective XY Chart with one series rendered as columns (or horizontal bars) paints every bar the same color. Values below zero look exactly like values above zero, and the operator has to read the axis to tell a loss from a gain. You want red for negative and a second color for positive, without splitting the data into multiple series.
The fix is one binding transform and two property entries. Before you get there, skip the fixes below.
Stop Chasing the amCharts Adapter Tutorial
The Perspective XY Chart is built on amCharts v4. amCharts publishes a tutorial for different column fill colors on positive and negative values, and it is the first search result most engineers land on.
- That tutorial works through amCharts adapters: JavaScript functions that run in the browser on each column as it renders.
- Perspective does not expose adapters or any hook for custom JavaScript on the chart. You configure the chart only through the component's property tree.
- You cannot paste the tutorial code into a Perspective view. Nothing in
propswill accept it.
Read the tutorial for the concept, then move on. That is not the path.
Don't Set a Static Fill on the Column Series
The next thing people try is series[n].column.appearance.fill.color and series[n].column.appearance.stroke.color.
- Those properties apply one color to every column in the series.
- Binding them to an expression doesn't help either. The expression evaluates once for the property, not once per data row, so every bar still gets the same result.
Leave the static fill.color and stroke.color empty ("") when you apply the real fix. They act as the default for any row that doesn't supply its own color.
Skip the Two-Series Split and Heat Rules
You can make two color groups by splitting the data into a positive series and a negative series. On a category axis this causes new problems:
- amCharts clusters columns from multiple series side by side within each category. Your bars shift off-center and leave empty slots where the other series has no value.
- You now maintain two datasets, two series definitions, and two tooltips for what is logically one signal.
- The goal was a single series. This abandons it.
Heat rules (column.appearance.heatRules with enabled, dataField, min, max) look tempting but interpolate a gradient between two colors across the value range. You get shades that shift gradually through zero, not a hard switch at the sign boundary. Use heat rules for magnitude shading, not for sign.
Understand Why Per-Row Color Needs a Data Field
Start here. amCharts can color each column individually only if each data item carries its own color value. Perspective exposes this through deriveFieldsFromData in the series appearance:
-
column.appearance.deriveFieldsFromData.fill.colortakes the name of a key in the data rows, not a color. - At render time, the chart reads that key from each row and uses the value as that column's fill.
-
stroke.color,fill.opacity,stroke.opacity, andstroke.widthwork the same way.
Your raw data has only an x value and a category. There is no color key yet, so you have to compute one from the value. Do that in a script transform on the dataSources binding. The chart then sees rows like {"x": -33, "y": 0, "color": "#fc0303"}.
Add the Color Key With a Binding Transform
The working configuration below uses horizontal bars: series render is column, xAxis is a value axis, and yAxis is a category axis. The numeric value lives in x, so that is the field you test for sign.
- Store or bind your source data. In the reference setup it lives in
view.custom.dataas a list of objects withx(value) andy(category index). - On the XY Chart, create a property binding on
props.dataSources.datapointing atview.custom.data. - Add a Script transform to that binding:
data = []
for point in value:
x = point['x']
color = "#fc0303" if x < 0 else "#fccf03"
data.append(
{
"x": x,
"y": point['y'],
"color": color
}
)
return data
- Confirm the series
datablock matches:source=data,x=x,y=y. Thesourcename must match the key underdataSourcesexactly.
Adjust for your layout:
- Vertical columns (category on X, value on Y): test the Y field for sign instead of X.
-
Dataset sources (tag history, named query returning a dataset): convert with
system.dataset.toPyDataSet(value)and iterate rows, or add a color column to the dataset. The chart only needs a column or key with the name you map. - Three-way coloring (negative, zero, positive, or alarm bands): extend the conditional. The chart doesn't care how the string was derived.
Map the Key in deriveFieldsFromData
With the color key flowing into the data source, point the series at it.
- Expand
props.series[0].column.appearance.deriveFieldsFromData. - Set
fill.colortocolor. - Set
stroke.colortocolor. If you skip this, each bar gets a derived fill with the default series outline around it. - Leave
column.appearance.fill.colorandstroke.coloras"". Keepfill.opacityat1andstroke.widthat1unless you want a different look. - Leave
heatRules.enabledatfalse. A heat rule on the same series competes with the derived fill.
Edit the right block. Each series carries separate column, line, stepLine, and candlestick appearance sections. Only the one matching the series render value takes effect. Setting deriveFieldsFromData under line on a column-rendered series does nothing.
Verify Colors on Edge Values
Check the data before you check the chart.
- Select the XY Chart and open
props.dataSources.datain the property editor. Every row must show acolorkey with a valid hex string. - If the key is missing, the transform isn't running or is erroring. Open the binding and read the transform error overlay.
- Feed test values: a large negative, a large positive, exactly
0, and a null. Watch where each lands. - Hover the bars in a running session. The tooltip value and bar color must agree on sign.
- Change a source value from positive to negative live and confirm the bar recolors on the next update.
| Symptom | Cause | Fix |
|---|---|---|
| All bars still default color |
deriveFieldsFromData.fill.color empty, or set under the wrong render block |
Set it to color under column.appearance
|
| All bars default color, property set correctly | Rows in dataSources have no color key |
Check transform output in the property editor |
| Bars colored but outline wrong |
stroke.color not derived |
Map deriveFieldsFromData.stroke.color to color
|
| Zero-value bars show positive color |
x < 0 puts zero in the else branch |
Add an explicit == 0 branch if zero needs its own color |
| Null or missing values show negative color | Ignition scripting is Jython (Python 2.7 semantics), where None < 0 evaluates true |
Test x is None first and assign a neutral color |
| Colors flip the wrong way on vertical columns | Transform tests the category field, not the value field | Test the field mapped to the value axis |
| Bars offset from category centers | Data split across two series | Merge into one series with the derived color |
| Bars rendered as gradient near zero | Heat rules enabled | Set heatRules.enabled to false
|
Watch transform cost on large datasets. The script runs on every source update, so a chart fed by a fast-polling tag history binding re-colors every row every poll. For thousands of points, compute the color column once in the query or the dataset-producing script instead.
FAQ
Can I use the amCharts adapter code for negative column colors in Ignition Perspective?
No. Perspective configures the XY Chart only through its property tree and doesn't expose amCharts adapters or custom JavaScript. Add a computed color key to each data row and map it through column.appearance.deriveFieldsFromData.fill.color.
Does deriveFieldsFromData take a color value or a field name?
A field name. Enter the key that exists in your data rows, for example color, and the chart reads each row's value from that key as the bar color. Entering a hex string there does nothing useful.
Can I color bars by threshold instead of sign on the Perspective XY Chart?
Yes. The chart only reads the resulting color string, so change the transform condition to any rule: alarm limits, target bands, or quality flags. Use heat rules only when you want a continuous gradient across a min/max range.
If the transform output shows a valid color key on every row, the mapping sits under the correct render block, and bars still ignore it, stop tweaking properties. Export the view JSON, note your exact Ignition version, and open a case with Inductive Automation support. Rendering that ignores a correctly populated derived field points to a component defect, not a configuration error.