Skip to content
Domphy

chartTooltipAdvanced

A Charts block/component from shadcn/ui — clean-room reimplemented for Domphy (see methodology). Call chartTooltipAdvanced() with no arguments for a working demo, or edit the code below live.

Props

PropTypeDescription
dataActivityDayPoint[]
seriesActivitySeriesEntry[]
showCursorboolean
defaultOpenIndexnumber | null
unitstring
panelMinWidthPxnumber
showTotalboolean
totalLabelstring
titlestring
descriptionstring
Implementation notes

Square swatch, hidden header, monospace+unit value rendering, and the conditional divider + bold Total row on the configured column index are all fully ported. The one unmet part of the spec is 'a fixed pixel width for the panel': @domphy/chart's tooltip container hardcodes its own box model (border/radius/shadow/background/max-width) inside createTooltip(), and TooltipOption exposes no width field the runtime actually reads (verified in packages/chart/src/overlay/tooltip.ts — only option.show/formatter/valueFormatter are consumed). Approximated with a min-width wrapper div inside the formatter's own returned HTML, which reserves the requested width visually (the outer panel uses white-space with no overflow, so wider content overflows the box rather than wrapping) but cannot truly widen the outer panel past the engine's hardcoded max-width — a real, source-verified engine limitation, not a guess. Direct-source-diff fix (2026-07-05): The Total row only rendered for a single hardcoded column — upstream's formatter appends a Total row after every hover, on every column. Replaced the one-off totalAtIndex prop with a showTotal boolean (default true).

Status: partial · Reference: shadcn/ui original

// shadcn/ui "charts/tooltip" (advanced recipe) — clean-room reimplementation.
//
// Enhanced tooltip: no default date header, a small square swatch per row, a
// monospace value with a muted unit suffix, and an extra bold "Total" row
// appended beneath a divider line — on every column — summing both series'
// values (matching the upstream recipe, whose formatter appends the total
// after its last series row on every hover).
//
// FIDELITY NOTE: the spec calls for "a fixed pixel width for the panel".
// @domphy/chart's tooltip container hardcodes its own box model
// (border/radius/shadow/background/max-width:260px) inside `createTooltip()`
// — `TooltipOption` has no width-override hook the engine actually reads
// (verified in chart-tooltip-shared.ts's header comment). This is
// approximated by wrapping the formatter's inner content in a
// `min-width`-styled div, which reserves the requested width visually but
// cannot force the outer panel wider than the engine's own hardcoded
// max-width — a real, source-verified constraint of the current chart
// engine, not a guess.
//
// VALUE/UNIT HIERARCHY: upstream renders the value with `font-mono
// font-medium` (weight 500) and the unit in `text-muted-foreground` at
// `font-normal` (weight 400), giving a clear value/unit hierarchy. The shared
// default renderer emits no unit suffix, so this recipe supplies its own
// `renderValue` (`advancedValueRenderer` below) — a per-recipe use of the
// formatter's designed `renderValue` hook.
//
// KNOWN RESIDUAL GAP (engine limitation, cannot be fixed here):
//   - Stacked-bar corners: upstream rounds only the outer corners 4px
//     ([0,0,4,4]/[4,4,0,0]) with a square seam; @domphy/chart's BarRenderer
//     hardcodes `barRadius = 2` on all four corners of every instance and
//     ignores series/itemStyle radius entirely.

import type { DomphyElement } from "@domphy/core";
import { themeColor } from "@domphy/theme";
import {
  ACTIVITY_ENERGY_UNIT,
  ACTIVITY_SERIES_CONFIG,
  ACTIVITY_TOOLTIP_DATA,
  type ActivityDayPoint,
  type ActivitySeriesEntry,
  activityBarOption,
  activityTooltipCard,
  activityTooltipFormatter,
  activityTooltipPlot,
  formatWeekdayShort,
  type TooltipValueContext,
} from "./chart-tooltip-shared.js";

/**
 * Value renderer matching upstream's value/unit hierarchy: a mono/tabular
 * value at font-medium (500) followed by the unit at font-normal (400) in a
 * muted foreground. The neutral shift-9 var(--…) reference resolves against
 * the live theme at paint time and is lighter than the engine tooltip's own
 * text tone (shift-10), so it reads as de-emphasized, yet still clears WCAG
 * AA (~4.95:1) on the tooltip's light background — unlike the reduced-opacity
 * approach the shared renderer avoided.
 * The value is always numeric (safe to interpolate); the unit is a caller
 * prop, so its text is escaped before it reaches innerHTML.
 */
function advancedValueRenderer(
  unit: string,
): (context: TooltipValueContext) => string {
  const mutedColor = themeColor(null, "shift-9", "neutral");
  const safeUnit = unit
    .replace(/&/g, "&")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;");
  return (context) =>
    `<span style="font-variant-numeric:tabular-nums;font-family:ui-monospace,monospace;font-weight:500;">${String(context.value)}</span>` +
    `<span style="margin-left:2px;font-weight:400;color:${mutedColor};">${safeUnit}</span>`;
}

export interface ChartTooltipAdvancedProps {
  data?: ActivityDayPoint[];
  series?: ActivitySeriesEntry[];
  showCursor?: boolean;
  defaultOpenIndex?: number | null;
  unit?: string;
  panelMinWidthPx?: number;
  showTotal?: boolean;
  totalLabel?: string;
  title?: string;
  description?: string;
}

/**
 * shadcn/ui "charts/tooltip" advanced recipe — square swatch, mono+unit
 * values, no header, and a computed grand-total row on every column. Call
 * with no arguments for a working demo.
 */
function chartTooltipAdvanced(
  props: ChartTooltipAdvancedProps = {},
): DomphyElement<"div"> {
  const {
    data = ACTIVITY_TOOLTIP_DATA,
    series = ACTIVITY_SERIES_CONFIG,
    showCursor = false,
    defaultOpenIndex = 1,
    unit = ACTIVITY_ENERGY_UNIT,
    panelMinWidthPx = 180,
    showTotal = true,
    totalLabel = "Total",
    title = "Bar Chart - Tooltip Advanced",
    description = "Custom rows plus a computed total per column",
  } = props;

  const categories = data.map((point) => formatWeekdayShort(point.date));
  const formatter = activityTooltipFormatter(data, series, {
    indicator: "square",
    showLabel: false,
    // Upstream's advanced custom formatter renders the series label bare in the
    // full foreground (not muted like the default/formatter recipes).
    mutedLabel: false,
    renderValue: advancedValueRenderer(unit),
    panelMinWidthPx,
    showTotal,
    totalLabel,
  });
  const option = activityBarOption({
    data,
    categories,
    series,
    showCursor,
    formatter,
  });

  return activityTooltipCard({
    title,
    description,
    plot: activityTooltipPlot({ option, categories, defaultOpenIndex }),
  });
}

export { chartTooltipAdvanced };

← Back to shadcn/ui catalog