Chart families

Gauge and Solid Gauge charts

Show one operational measurement against a known range, target, and zones.

Gauge answers one operational question: where is one measurement inside a known numeric range? It combines an explicit domain with optional zones, target, thresholds, status, and a direct center summary.

Use Gauge for one current measurement. Use Radial Bar when several independent categories share a scale, and Donut when values contribute to one total.

Needle Gauge

BravenChartPlus(
  series: [
    GaugeChartSeries.needle(
      id: 'cpu',
      metric: 'CPU utilization',
      unit: '%',
      value: 72,
      minimum: 0,
      maximum: 100,
      target: const GaugeTarget(value: 70, label: 'Target'),
      zones: const [
        GaugeZone(from: 0, to: 60, status: 'Healthy'),
        GaugeZone(from: 60, to: 85, status: 'Elevated'),
        GaugeZone(from: 85, to: 100, status: 'Critical'),
      ],
    ),
  ],
  gaugeChartConfig: const GaugeChartConfig(),
)

The value remains an absolute source measurement. normalizedProgress is derived from minimum, maximum, and value; it is not stored as the data. Zones are ordered, non-overlapping, and half-open. The last zone also owns the exact maximum endpoint.

Solid Gauge

Needle and Solid Gauge share the same measurement contract. Change only the indicator style:

GaugeChartSeries.solid(
  id: 'availability',
  metric: 'Service availability',
  unit: '%',
  value: 99.94,
  minimum: 99,
  maximum: 100,
  target: const GaugeTarget(value: 99.9, label: 'SLO'),
  zones: const [
    GaugeZone(from: 99, to: 99.9, status: 'At risk'),
    GaugeZone(from: 99.9, to: 100, status: 'Healthy'),
  ],
  style: const SolidGaugeStyle(
    cornerRadius: 10,
    trackOpacity: 0.16,
  ),
)

This makes precise non-zero domains portable without converting the source value to a percentage.

Portable solid-arc gradients

Solid Gauge arcs can use a sweep or radial gradient without introducing a runtime-only painter:

style: const SolidGaugeStyle(
  cornerRadius: 12,
  gradient: GaugeGradientStyle(
    type: GaugeGradientType.sweep,
    startColor: Color(0xFF22D3EE),
    endColor: Color(0xFF4F46E5),
  ),
),

Omit startColor and endColor to derive both stops from the active indicator colour. startLightnessShift and endLightnessShift control that derivation. The gradient is validated, serialized in chart artifacts, hydrated, and included in generated Dart source.

Pane, ticks, and center content

GaugeChartConfig controls the shared radial pane and direct-display layers:

const GaugeChartConfig(
  pane: PolarPaneConfig(
    startAngleDegrees: -135,
    sweepAngleDegrees: 270,
    clockwise: true,
    innerRadiusFactor: 0.52,
    outerRadiusFactor: 0.9,
  ),
  tickCount: 6,
  showAxis: true,
  showTicks: true,
  showTickLabels: true,
  showZones: true,
  colorIndicatorByActiveZone: true,
  scale: GaugeScaleStyle(
    tickColor: Color(0xFF64748B),
    tickWidth: 2,
    tickLength: 14,
    labelStyle: PolarLabelStyle(
      color: Color(0xFF334155),
      fontSize: 11,
    ),
    labelOffset: 12,
  ),
  references: GaugeReferenceStyle(
    innerLineOffset: 6,
    outerLineOffset: 10,
    labelStyle: PolarLabelStyle(fontSize: 11),
    labelOffset: 12,
    showLabelPanel: true,
  ),
  center: GaugeCenterConfig(
    showMetric: true,
    showValue: true,
    showTarget: true,
    showStatus: true,
    verticalOffset: 4,
    lineSpacing: 5,
  ),
)

GaugeScaleStyle independently controls tick colour, width and length plus numeric-label colour, size, weight, radial offset, and maximum width. Null tick and text colours inherit the active axis theme. labelOffset is the edge-to-edge gap between the tick endpoint and the nearest label edge, so 0 keeps the two touching without drawing the text over the tick or arc.

Dense instrument scales and segmented zones

Major ticks remain the labelled scale contract. Add unlabeled subdivisions with minorTicksPerInterval, then place ticks and labels independently:

GaugeChartSeries.needle(
  id: 'revenue',
  metric: 'Revenue this month',
  unit: 'k',
  value: 80,
  minimum: 0,
  maximum: 200,
  zones: const [
    GaugeZone(from: 0, to: 100, status: 'Baseline'),
    GaugeZone(from: 100, to: 150, status: 'On plan'),
    GaugeZone(from: 150, to: 200, status: 'Ahead'),
  ],
  style: const NeedleGaugeStyle(
    needleWidth: 28,
    needleTipWidth: 2,
    pivotRadius: 14,
    pivotColor: Color(0xFFFFFFFF),
    pivotBorderColor: Color(0xFF111111),
    pivotBorderWidth: 6,
    axisThickness: 42,
  ),
)

const GaugeChartConfig(
  pane: PolarPaneConfig(
    startAngleDegrees: 180,
    sweepAngleDegrees: 180,
    clockwise: false,
  ),
  tickCount: 9,
  minorTicksPerInterval: 4,
  scale: GaugeScaleStyle(
    tickPosition: GaugeTickPosition.inside,
    tickGap: 12,
    minorTickWidth: 1,
    minorTickLength: 5,
    labelPosition: GaugeScaleLabelPosition.outside,
  ),
  zones: GaugeZoneStyle(
    gap: 5,
    cornerRadius: 2,
    opacity: 1,
  ),
)

tickCount always counts labelled major ticks. Each major interval receives the requested minor ticks, so the example above paints 9 major and 32 minor marks. inside, centered, and outside are semantic placements relative to the rendered axis band. Inside and outside ticks can use tickGap for a clear edge-to-edge rail separation; centered ticks retain the established outer-edge split and ignore that gap.

GaugeZoneStyle changes only the visual presentation of operational zones. Its gap, corner radius, opacity, border colour, and border width are portable; the source GaugeZone.from and to values remain unchanged. Gaps are inserted only between contiguous zones, not across genuine uncovered domain ranges.

needleTipWidth: 0 preserves the classic pointed needle. A positive tip width creates a tapered instrument pointer, while pivot fill and border remain independently styleable.

GaugeReferenceStyle controls the shared target/threshold callout reach and outside-label typography, offset, width, and optional panel. The target and threshold still own their individual stroke colour, width, and dash pattern; a null reference-label colour inherits the corresponding reference colour. Its labelOffset uses the same edge-to-edge contract from the callout endpoint to the nearest label or panel edge.

Center content supports independent typography for metric, value, target, and status plus horizontal/vertical offsets and line spacing.

Compact and large-text layouts reserve enough of the pane to keep the value readable. If every optional center line still cannot fit, the portable fallback removes target, status, and metric copy in that order before reducing the value below its readability threshold. The single semantic summary and tooltip retain the complete metric, range, target, and active status.

For runtime-only center content, pass gaugeCenterBuilder. The builder receives an immutable GaugeCenterContext with the metric, source value, formatted value, domain, target, active status, and available center bounds. Runtime widgets are intentionally not serialized; portable artifacts retain GaugeCenterConfig.

Legend

Set showLegend: true to display the native radial legend. A Gauge legend is informational: it reports the metric, formatted value, and active status but does not pretend that the single current reading is selectable.

The shared ChartTheme.legendStyle controls position, orientation, marker shape and size, text size, opacity, background, border, and corner radius. Because the Gauge contains exactly one measurement, both horizontal and vertical orientations remain compact while the position still determines which side of the pane owns the legend.

Targets and thresholds

GaugeTarget is the preferred reading. GaugeThreshold is an additional absolute guide and may use a dash pattern. Neither reference invents status: status always comes from the active GaugeZone.

thresholds: const [
  GaugeThreshold(
    value: 90,
    label: 'Limit',
    width: 2,
    dashPattern: [6, 4],
  ),
],

Interaction, accessibility, and motion

Gauge uses shared hover, focus, tracking, tooltip, Workbench, table, artifact, hydration, and generated-source infrastructure. V1 does not expose durable Gauge selection because the current reading is state, not a selected record.

Needle and Solid variants expose equivalent semantics: metric, formatted value, range, active status, and target. Decorative ticks and zones do not become duplicate focus nodes. High-contrast media mode adds explicit zone boundaries, and reduced-motion media mode applies value revisions immediately.

Stable-ID value changes interpolate from the previous reading. Switching between authored Gauge presentations starts a fresh chart instead of morphing unrelated geometry.

The showcase also exposes scale ticks and labels, reference callouts and panels, center offsets, popup trigger and position, cursor following, delay, surface, text, border, shadow, spacing, and font controls. Motion controls cover entrance, data/theme revision, and interaction durations and curves.

The native Workbench table contains Metric, Value, Minimum, Maximum, Progress, Target, and Status. Chart, Data, Split, and Source modes use the same portable document.

See the runnable desktop showcase at ?page=gauge-charts. Its authored examples cover Needle, Solid, Gradient, Zones, Target, Legend, Popup, partial sweeps, accessibility, dense references, and a semicircular Instrument gauge. They use varied 150–360 degree panes and start angles to demonstrate compact, semicircular, broad, and complete-dial compositions. The Playground stays last and randomizes portable data, major/minor scale geometry, zone segmentation, needle shape, colours, gradients, labels, references, legend, popup, theme, and motion while leaving every generated property editable in Options.

The main Gallery includes operational Needle, gradient Solid, and high-contrast partial-sweep compositions. The mobile showcase contains four phone-sized Needle, Solid, partial-sweep, and dense-reference examples.

V1 boundaries

  • A Gauge chart contains exactly one GaugeChartSeries.
  • The measurement is an absolute source value inside an explicit domain.
  • Durable selection is intentionally absent: the current reading is state, not a selected record.
  • Runtime builders remain host-owned and are not serialized.