Skip to content
Ndop UI

Chart

Chart changelog

Every version of Chart, newest first. Update with npx @ndop/cli update chart; it keeps your edits.

Preview an update

Paste your copy of the file to see what updating it to 1.0.0 would do.

Runs in your browser; your file isn't uploaded. If your ndop.json aliases differ from the registry's @/, import paths can show up as changes.

1.0.0 Latest

Stable release; no code changes since 0.2.0.

0.2.0

Axis labels keep their size at every width (ADR-0029). They were SVG text, which scaled with the plot: about 6px on a phone, larger than the text around them on a wide screen. ChartAxis is now an HTML band under the plot, and the new ChartLabel places each label in it at its tick, [style.left.%]="(x / width) * 100"; it also places labels over the plot, such as a donut's total, and its align input keeps a label at the plot's edge within it. Move your <g ndChartAxis> out of the svg into a <div ndChartAxis> after it, turn each <text> into a <span ndChartLabel>, and drop the room you kept under the plot for them from its viewBox and scales.

The plot spans its width at its viewBox's aspect ratio (h-auto instead of h-full), so its positions are the same percentages of its box. The grid and cursor lines stay a pixel wide whatever the plot's size, so a plot can also take a fixed height with preserveAspectRatio="none".

What ndop update does from 0.1.0

  • Updates {ui}/chart/chart.ts, keeping your edits.
{ui}/chart/chart.ts
 * ```html
 * <figure ndChartContainer [config]="config">
 *   <figcaption>Visitors</figcaption>
 *   <svg ndChartPlot viewBox="0 0 500 250">…</svg>
 *   <svg ndChartPlot viewBox="0 0 500 225">…</svg>
 *   <div ndChartAxis>…</div>
 * </figure>
 * ```
 */
// ⋯
 * it `xs`, each point's horizontal position in `viewBox` units, and name
 * it with `aria-label` or `aria-labelledby`.
 *
 * It spans the chart's width at its `viewBox`'s aspect ratio, so a
 * position in `viewBox` units is the same percentage of the plot's box,
 * where the HTML labels and the tooltip go. For a fixed height instead,
 * set one with a class and `preserveAspectRatio="none"`, and give your
 * lines and outlines `vector-effect="non-scaling-stroke"` so they keep
 * their width, as the grid and cursor do.
 *
 * @example
 * ```html
 * <svg ndChartPlot viewBox="0 0 500 250" [xs]="xs()" [(active)]="active" aria-label="Visitors by month">…</svg>
 * <svg ndChartPlot viewBox="0 0 500 225" [xs]="xs()" [(active)]="active" aria-label="Visitors by month">…</svg>
 * ```
 */
@Directive({
// ⋯

  protected readonly classes = computed(() =>
    cn(
      'h-full w-full rounded-md outline-hidden focus-visible:ring-[3px] focus-visible:ring-ring/50',
      'block h-auto w-full rounded-md outline-hidden focus-visible:ring-[3px] focus-visible:ring-ring/50',
      this.class(),
    ),
  );
// ⋯
}

/**
 * The grid lines behind the data, on a `g`.
 * The grid lines behind the data, on a `g`. They stay a pixel wide
 * whatever the plot's size.
 *
 * @example
 * ```html
// ⋯
  readonly class = input('');

  protected readonly classes = computed(() =>
    cn('[&_line]:stroke-border/50', this.class()),
    cn(
      '[&_line]:stroke-border/50 [&_line]:[vector-effect:non-scaling-stroke]',
      this.class(),
    ),
  );
}

/**
 * An axis' labels, on a `g`.
 * The x axis' labels, in a band under the plot. They're HTML rather than
 * SVG text, so they keep their size at every width instead of scaling
 * with the plot. Put a `ChartLabel` in it for each tick, at the tick's
 * position across the plot in percent. Screen readers skip it: the
 * chart's table holds the same labels.
 *
 * @example
 * ```html
 * <g ndChartAxis>…<text>Jan</text></g>
 * <div ndChartAxis>
 *   <span ndChartLabel [style.left.%]="(x / width) * 100">Jan</span>
 * </div>
 * ```
 */
@Directive({
  selector: '[ndChartAxis]',
  host: { 'data-slot': 'chart-axis', '[class]': 'classes()' },
  host: {
    'data-slot': 'chart-axis',
    'aria-hidden': 'true',
    '[class]': 'classes()',
  },
})
export class ChartAxis {
  /** Extra classes, merged last so they win. */
  readonly class = input('');

  protected readonly classes = computed(() =>
    cn('[&_text]:fill-muted-foreground', this.class()),
    cn('relative h-4 text-muted-foreground', this.class()),
  );
}

/**
 * The marker of the active data point: a line, or a band behind bars.
 * A label at a point of the chart, such as an axis tick or a value, in
 * HTML so it keeps its size at every width. It sits at its `left`, a
 * percentage of the plot's width, centered on it by default; a label at
 * the plot's edge aligns `left` or `right` instead, so it stays within
 * the plot. In a `ChartAxis` it sits at the top of the band. Over the
 * plot, put the plot and the label in a `relative` box and set its `top`
 * too, centering it with `-translate-y-1/2`.
 *
 * Like the plot, it isn't mirrored in right-to-left layouts, so it's
 * placed with `left` and aligns to physical sides, not `start` and `end`.
 *
 * @example
 * ```html
 * <span ndChartLabel [style.left.%]="0" align="left">Jan</span>
 * <span ndChartLabel [style.left.%]="50">Apr</span>
 * <span ndChartLabel [style.left.%]="100" align="right">Jun</span>
 * ```
 */
@Directive({
  selector: '[ndChartLabel]',
  host: {
    'data-slot': 'chart-label',
    '[attr.data-align]': 'align()',
    '[class]': 'classes()',
  },
})
export class ChartLabel {
  /**
   * Which of its sides is at its `left`: `center` for its middle, `left`
   * for its left edge (it extends right), `right` for its right edge.
   */
  readonly align = input<'center' | 'left' | 'right'>('center');

  /** Extra classes, merged last so they win. */
  readonly class = input('');

  protected readonly classes = computed(() =>
    cn(
      'pointer-events-none absolute top-0 whitespace-nowrap',
      this.align() === 'center' && '-translate-x-1/2',
      this.align() === 'right' && '-translate-x-full',
      this.class(),
    ),
  );
}

/**
 * The marker of the active data point: a line, which stays a pixel
 * wide whatever the plot's size, or a band behind bars.
 *
 * @example
 * ```html
 * <rect ndChartCursor kind="band" … />
 * ```
 */
// ⋯

  protected readonly classes = computed(() =>
    cn(
      'pointer-events-none data-[kind=band]:fill-muted data-[kind=line]:stroke-border',
      'pointer-events-none data-[kind=band]:fill-muted data-[kind=line]:stroke-border data-[kind=line]:[vector-effect:non-scaling-stroke]',
      this.class(),
    ),
  );

0.1.0

First release.

What ndop update does

  • Adds {ui}/chart/chart.ts.
  • Adds {lib}/chart.ts.
  • Installs the utils item.