Skip to content

Color by value

A series normally takes one color from the palette. Point colorProperty at a data property and each bar is colored per row 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: 'Revenue ($k)' }],
  series: [
    {
      property: 'revenue',
      title: 'Revenue (shaded by margin %)',
      renderer: 'bar',
      // Bar fills default to half 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 row's colorProperty value maps linearly from colorScale.min to colorScale.max across 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. Point colorProperty at the series' own property to shade each bar by its own value instead of a second measure.
  • colorScale.interpolation picks the d3 color space to interpolate in (rgb, hsl, lab, hcl). Setting colorProperty is the switch: once it's set, the scale defaults to hcl through #8f8fff#0000ff — the example above overrides the ramp, everything else is defaults.
  • Per-row color applies to bar series (including floating bars via rangeProperty) — line and area shapes and markers keep their single series color.
  • Bar fills default to half opacity, which dilutes the ramp against the background; the example sets shapeStyle.normal's two opacities to 1 so the colors read true.
  • A row without a color value gets colorScale.missing (default #cccccc, the Services bar above); set it to null to 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 / showColorInTooltip turn the chips off).
  • The tooltip shows the series value, not the color value; point tooltipProperty at the color property to surface it, as the heatmap does.

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' },
  categoryAxis: { property: 'region', type: 'string', scale: 'ordinal' },
  valueAxes: [{ id: 'VA0', title: '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.value set, min/max are ignored and the four base colors take over. Each anchors to its half's data domain: aboveMin sits at the base and aboveMax at the highest value, while belowMin sits at the most negative value and belowMax at the base. For the classic diverging look — palest at the base, saturated at the extremes — order the below pair saturated → pale, as above (the defaults run the below pair the other way).
  • 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 base so 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.

Released under the BSD-3-Clause License.