Color by value
A series normally takes one color from the palette. Point colorProperty at a data property and each bar is colored per category instead, mapped through the series' colorScale ramp — a second measure encoded on the same bars.
ts
// colorProperty reads a color value per data row and maps it through the
// series colorScale ramp — here bar height is revenue while fill encodes
// margin, a second measure on the same bars. The row without a margin value
// falls back to colorScale.missing.
import type { MochartInputConfig } from '@mochart/core';
export const config: MochartInputConfig = {
version: '1.0.0',
title: { text: 'Revenue Shaded by Margin' },
legend: { visible: true },
categoryAxis: { property: 'product', type: 'string', scale: 'ordinal' },
valueAxes: [{ id: 'VA0', title: { text: 'Revenue ($k)' } }],
series: [
{
property: 'revenue',
title: 'Revenue (shaded by margin %)',
renderer: 'bar',
// Bar fills and strokes default to 0.8 opacity; full opacity keeps the ramp true.
shapeStyle: { normal: { strokeOpacity: 1, fillOpacity: 1 } },
colorProperty: 'margin',
colorScale: { interpolation: 'lab', min: '#cde2fb', max: '#0d366b' }
}
]
};
export const data = [
{ product: 'Laptops', revenue: 840, margin: 9 },
{ product: 'Phones', revenue: 720, margin: 14 },
{ product: 'Tablets', revenue: 310, margin: 12 },
{ product: 'Monitors', revenue: 260, margin: 21 },
{ product: 'Audio', revenue: 190, margin: 28 },
{ product: 'Accessories', revenue: 130, margin: 34 },
// No margin reported — colorScale.missing (default gray) colors this bar.
{ product: 'Services', revenue: 110 }
];How it works
- Each category's
colorPropertyvalue maps linearly fromcolorScale.mintocolorScale.maxacross the property's extent in that series — the palest bar is always the smallest color value and the darkest the largest, whatever the numbers are. PointcolorPropertyat the series' ownpropertyto shade each bar by its own value instead of a second measure. colorScale.interpolationpicks the d3 color space to interpolate in (rgb,hsl,lab,hcl). SettingcolorPropertyis the switch: once it's set, the scale defaults tohclthrough#8f8fff→#0000ff— the example above overrides the ramp, everything else is defaults.- Per-category color applies to
barseries (including floating bars viarangeProperty) — line and area shapes and markers keep their single series color. - Bar fills and strokes default to
0.8opacity, which dilutes the ramp against the background; the example setsshapeStyle.normal's two opacities to1so the colors read true. - A category with no color value gets
colorScale.missing(default#cccccc, the Services bar above); set it tonullto fall back to the series' own style colors instead. - The series' legend and tooltip color chips become a min→max ramp swatch, so the legend doubles as a compact color key (
showColorInLegend/showColorInTooltipturn the chips off). - The tooltip shows the series value, not the color value; point
tooltipPropertyat the color property to surface it, as the heatmap does. colorPropertycannot be combined with a gradient. A pattern replaces the per-category fill; the per-category stroke color still applies.
Diverging around a base
Set colorScale.base.value and the ramp splits in two: one color pair above the threshold, another below — growth in blue, decline in red.
ts
// colorScale.base.value splits the ramp in two: values above the base
// interpolate through the above pair, values below through the below pair.
// Each min/max anchors to its half's data domain — belowMin sits at the most
// negative value, so the saturated color goes there for the classic
// palest-at-the-base diverging look.
import type { MochartInputConfig } from '@mochart/core';
export const config: MochartInputConfig = {
version: '1.0.0',
title: { text: 'Revenue Shaded by Growth' },
legend: { visible: true },
categoryAxis: { property: 'region', type: 'string', scale: 'ordinal' },
valueAxes: [{ id: 'VA0', title: { text: 'Revenue ($k)' } }],
series: [
{
property: 'revenue',
title: 'Revenue (shaded by YoY growth)',
renderer: 'bar',
shapeStyle: { normal: { strokeOpacity: 1, fillOpacity: 1 } },
colorProperty: 'growth',
colorScale: {
interpolation: 'hcl',
base: {
value: 0,
aboveMin: '#8f8fff',
aboveMax: '#0000ff',
belowMin: '#ff0000',
belowMax: '#ff8f8f'
}
}
}
]
};
export const data = [
{ region: 'North', revenue: 620, growth: 12 },
{ region: 'South', revenue: 540, growth: -4 },
{ region: 'East', revenue: 480, growth: 22 },
{ region: 'West', revenue: 450, growth: -11 },
{ region: 'Central', revenue: 300, growth: 3 },
{ region: 'Export', revenue: 210, growth: 35 }
];- With
base.valueset,min/maxmust benull(their default in that case — setting them alongside a base is a validation error) and the fourbasecolors take over. Each anchors to its half's data extent:aboveMinsits at the base andaboveMaxat the highest value;belowMinsits at the most negative value andbelowMaxat the base. The defaults give the classic diverging look — palest at the base, saturated at both extremes. - Each half fits its own side of the color property's extent, so the deepest red and deepest blue always mark the current extremes.
- The base splits only the colors. Here the bars measure revenue (all positive) while the color diverges on growth; when the color property is the series' own values, pin the value axis with
baseso the bars grow out of the same divide the colors split on.
For value-colored grids — rows of full-width bars sharing one global ramp — see the heatmap recipe.