Skip to content

Blocks

A slat is made of blocks. You give a block numbers from your data, and rhp works out where on the chart those numbers fall. You can put as many blocks in a slat as you like, and any elements of your own around or inside them. For many scatter points, ManyDots draws a collection directly inside a Chart.

Bar

A bar from from to to. from is 0 if you leave it out. thick sets how thick the bar is: a length like "10px", or a fraction of the slat’s thickness, like 0.5 for half of it.

Bakery
Library
Gym
import { Chart, Plot, Bar, Label } from "@bezda/rhp";

// Opening hours: each bar runs from opening to closing time.
export default function Hours() {
  return (
    <Chart
      scale={[6, 24]}
      ticks={[6, 9, 12, 15, 18, 21, 24]}
      format={(h) => h + ":00"}
    >
      <Plot
        shop={["Bakery", "Library", "Gym"]}
        opens={[7, 10, 6]}
        closes={[14, 19, 23]}
      >
        {(d) => (
          <div>
            <Label edge="start">{d.shop}</Label>
            <Bar from={d.opens} to={d.closes} thick="10px" />
          </div>
        )}
      </Plot>
    </Chart>
  );
}

A Bar whose to is below its from runs backward, like a negative value.

Dot

A round point at at. size sets its diameter (10px by default).

Ana
Ben
Chloe
Dev
import { Chart, Plot, Dot, Label } from "@bezda/rhp";

// Race times: a dot for each runner's best lap.
export default function Laps() {
  return (
    <Chart scale={[60, 80]} format={(s) => s + " s"}>
      <Plot
        runner={["Ana", "Ben", "Chloe", "Dev"]}
        best={[71.2, 66.8, 64.5, 69.9]}
      >
        {(d) => (
          <div>
            <Label edge="start">{d.runner}</Label>
            <Dot at={d.best} size="14px" />
          </div>
        )}
      </Plot>
    </Chart>
  );
}

ManyDots

A bulk collection of plain HTML points, positioned by at and cross on the Chart’s two scales. Use ManyDots directly inside the Chart, without a Plot or a slat per point. Choose it explicitly for a large scatter collection; Dot remains available at any point count for individual blocks and custom slat content.

Points can share styling, use category classes, or have individual colors, sizes, shapes, and styles. The collection uses light DOM, so document queries and native event delegation can reach its points.

See the ManyDots page for the live example, complete props, interaction patterns, and current source-build availability.

Tick

A short line across the slat at at: a goal, a median, a threshold.

Mon
Tue
Wed
import { Chart, Plot, Bar, Tick, Label } from "@bezda/rhp";

// Steps against a daily goal: the bar is the steps, the tick the goal.
export default function Steps() {
  return (
    <Chart scale={[0, 12]} format={(k) => k + "k"}>
      <Plot
        day={["Mon", "Tue", "Wed"]}
        steps={[8.2, 11.5, 5.1]}
        goal={[10, 10, 8]}
      >
        {(d) => (
          <div>
            <Label edge="start">{d.day}</Label>
            <Bar to={d.steps} />
            <Tick at={d.goal} color="ink" />
          </div>
        )}
      </Plot>
    </Chart>
  );
}

Label

Text on the chart.

  • at={value} puts it just past that value. Add side="before" to put it just before instead, which suits negative bars.
  • edge="start" puts it before the chart, where names go. edge="end" puts it after.
Jan
25
Feb
-12
Mar
8
Apr
-30
import { Chart, Plot, Bar, Label } from "@bezda/rhp";

// Profit and loss: the number sits just past the bar's end.
export default function Profit() {
  return (
    <Chart scale={[-40, 40]} ticks={[-40, -20, 0, 20, 40]}>
      <Plot
        month={["Jan", "Feb", "Mar", "Apr"]}
        profit={[25, -12, 8, -30]}
      >
        {(d) => (
          <div>
            <Label edge="start">{d.month}</Label>
            <Bar
              to={d.profit}
              color={d.profit < 0 ? "negative" : "positive"}
            />
            <Label
              at={d.profit}
              side={d.profit < 0 ? "before" : undefined}
            >
              {d.profit}
            </Label>
          </div>
        )}
      </Plot>
    </Chart>
  );
}

Cell

A cell that fills the space it is given, colored by its value: the chart’s scale runs from the theme’s low color to its high one. Cells make heatmaps. Here each day’s slat holds a small Plot of its own, one cell per hour (more about this in charts inside slats).

Fri
Sat
Sun
import { Chart, Plot, Cell, Label } from "@bezda/rhp";

// Visitors per hour: a slat per day, a cell per hour.
const visitors = [
  [2, 8, 21, 35, 30, 12],
  [4, 11, 26, 40, 34, 15],
  [9, 24, 38, 29, 18, 6],
];

export default function Visitors() {
  return (
    <Chart scale={[0, 40]} ticks={false}>
      <Plot day={["Fri", "Sat", "Sun"]} hours={visitors}>
        {(d) => (
          <div>
            <Label edge="start">{d.day}</Label>
            <Plot orientation="across" count={d.hours}>
              {(h) => (
                <div>
                  <Cell value={h.count} title={h.count + " visitors"} />
                </div>
              )}
            </Plot>
          </div>
        )}
      </Plot>
    </Chart>
  );
}

Place

A Place draws nothing at all: no size, no colour, nothing on the screen. It only marks a spot, so whatever you put inside it sits at at on the scale. Use it for the pieces that are yours to draw, a badge, a bubble, a photo, a needle, where rhp only has to say where they go.

Everest
8849 m
K2
8611 m
Denali
6190 m
Mont Blanc
4808 m
import { Chart, Plot, Bar, Label, Place, slat } from "@bezda/rhp";

// A Place draws nothing: the badge inside it sits at the value.
const Climb = slat(
  {
    thickness: 44,
    room: { start: 70, end: 30 },
    css: `
      .badge {
        position: absolute;
        translate: -50% -50%;
        padding: 2px 7px;
        border-radius: 99px;
        background: var(--rhp-ink);
        color: var(--rhp-surface);
        font-size: 11px;
        font-weight: 700;
        white-space: nowrap;
      }
    `,
  },
  (d) => (
    <div>
      <Label edge="start">{d.peak}</Label>
      <Bar to={d.metres} />
      <Place at={d.metres}>
        <span class="badge">{d.metres} m</span>
      </Place>
    </div>
  ),
);

export default function Peaks() {
  return (
    <Chart scale={[0, 9000]} ticks={[0, 3000, 6000, 9000]}>
      <Plot
        peak={["Everest", "K2", "Denali", "Mont Blanc"]}
        metres={[8849, 8611, 6190, 4808]}
      >
        {Climb}
      </Plot>
    </Chart>
  );
}
  • across puts it across the band, from 0 to 1, and cross puts it on a second axis instead.
  • Everything inside it is yours: rhp adds no padding, no background and no text rules to undo.
  • Without it you would borrow another block and switch its appearance off, which is CSS you would have to keep in step with rhp.

Area

A smooth shape over the scale, from a list of points, each [value, height]. The tallest point reaches the full thickness of the slat. Add mirror to draw it both ways from the middle, for violin plots. Add smooth to join the points with curves instead of straight lines.

Weekday
Weekend
import { Chart, Plot, Area, Label } from "@bezda/rhp";

// When people arrive at a café: one smooth shape per day.
export default function Arrivals() {
  return (
    <Chart scale={[7, 19]} format={(h) => h + ":00"}>
      <Plot
        day={["Weekday", "Weekend"]}
        shape={[
          [
            [7, 1],
            [8, 6],
            [9, 4],
            [12, 7],
            [13, 5],
            [17, 3],
            [19, 1],
          ],
          [
            [7, 0],
            [9, 2],
            [11, 6],
            [13, 8],
            [15, 5],
            [17, 2],
            [19, 1],
          ],
        ]}
      >
        {(d) => (
          <div>
            <Label edge="start">{d.day}</Label>
            <Area points={d.shape} />
          </div>
        )}
      </Plot>
    </Chart>
  );
}

For a shape made from raw samples, the density helper computes the points for you.

Line

A line through a list of points, each [value, y]. In a slat it is a sparkline. The largest y reaches the top of the slat, so each slat is scaled on its own. Set peak to the y that should reach the top, and then the slats can be compared with each other. Add fill to fill under the line, and smooth to join the points with curves.

Home
Pricing
Docs
import { Chart, Plot, Line, Label } from "@bezda/rhp";

// Visits to three pages: a sparkline per slat, with one peak for all so they compare.
export default function Visits() {
  return (
    <Chart
      scale={[1, 7]}
      ticks={[1, 2, 3, 4, 5, 6, 7]}
      format={(day) => "MTWTFSS"[day - 1]}
    >
      <Plot
        page={["Home", "Pricing", "Docs"]}
        visits={[
          [320, 280, 350, 300, 390, 180, 150],
          [90, 120, 110, 160, 150, 60, 40],
          [210, 240, 260, 230, 250, 120, 140],
        ]}
      >
        {(d) => (
          <div>
            <Label edge="start">{d.page}</Label>
            <Line points={d.visits.map((v, i) => [i + 1, v])} peak={400} fill />
          </div>
        )}
      </Plot>
    </Chart>
  );
}

On a chart with a second axis, y is on that axis instead: a line chart.

What every block takes

Every block also takes what an HTML element takes:

  • class and style to style it,
  • color: a theme color ("series-2", "positive") or any CSS color,
  • event handlers such as onClick, and attributes such as title, data-* and aria-*,
  • children: text, an image, or any other element, drawn inside the block.

Bar, Dot, Tick, Label and Cell also take shape, an outline to draw instead of their rectangle. shape() makes one from SVG’s path commands, in the block’s own box:

import { shape } from "@bezda/rhp";

// a bar with a pointed end: 26px back from its end, it narrows to a point at the end's middle
const arrow = shape(["M", 0, 0], ["L", "-26px", 0], ["L", 1, 0.5], ["L", "-26px", 1], ["L", 0, 1], ["Z"]);

<Bar to={d.value} shape={arrow} />

The first number of each point runs along the scale, from the block’s start (0) to its end (1), and the second across the band, from 0 to 1. A length such as "26px" is measured from the start, and "-26px" from the end. rhp turns the shape for a vertical chart and for a bar that runs backward, so you write it once.

The full list of props is in the blocks reference.