Skip to content

Rect mark

The rect mark draws axis-aligned rectangles defined by x1, y1, x2, and y2. For example, here we display geographic bounding boxes of U.S. counties represented as [x1, y1, x2, y2] tuples, where x1 & x2 are degrees longitude and y1 & y2 are degrees latitude.

Fork
js
Plot.plot({
  projection: "albers-usa",
  marks: [
    Plot.rect(countyboxes, {
      x1: "0", // or ([x1]) => x1
      y1: "1", // or ([, y1]) => y1
      x2: "2", // or ([,, x2]) => x2
      y2: "3", // or ([,,, y2]) => y2
      stroke: "currentColor"
    })
  ]
})

More commonly, the rect mark is used to produce histograms or heatmaps of quantitative data. For example, given some binned observations computed by d3.bin, we can produce a basic histogram with rectY as follows:

020406080100120140160180200↑ length−4−3−2−10123Fork
js
Plot.rectY(bins, {x1: "x0", x2: "x1", y: "length"}).plot({round: true})
js
bins = d3.bin()(d3.range(1000).map(d3.randomNormal()))

INFO

d3.bin uses x0 and x1 to represent the lower and upper bound of each bin, whereas the rect mark uses x1 and x2. The length field is the count of values in each bin, which is encoded as y.

Most often, the rect mark is paired with the bin transform to bin quantitative values as part of the plot itself. As an added bonus, this sets default inset options for a 1px gap separating adjacent rects, improving readability.

020406080100120140160180200↑ Frequency−4−3−2−10123Fork
js
Plot.rectY(d3.range(1000).map(d3.randomNormal()), Plot.binX()).plot()

Like the bar mark, the rect mark has two convenience constructors for common orientations: rectX is for horizontal→ rects and applies an implicit stackX transform, while rectY is for vertical↑ rects and applies an implicit stackY transform.

Fork
js
Plot.plot({
  color: {legend: true},
  marks: [
    Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight", fill: "sex"})),
    Plot.ruleY([0])
  ]
})

For overlapping rects, you can opt-out of the implicit stack transform by specifying either x1 or x2 for rectX, and likewise either y1 or y2 for rectY.

Fork
js
Plot.plot({
  round: true,
  color: {legend: true},
  marks: [
    Plot.rectY(olympians, Plot.binX({y2: "count"}, {x: "weight", fill: "sex", mixBlendMode: "multiply"})),
    Plot.ruleY([0])
  ]
})

CAUTION

While the mixBlendMode option is useful for mitigating occlusion, it can be slow to render if there are many elements. More than two overlapping histograms may also be hard to read.

The rect mark and bin transform naturally support faceting, too.

Fork
js
Plot.plot({
  marks: [
    Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight", fy: "sex"})),
    Plot.ruleY([0])
  ]
})

The rect constructor, again with the bin transform, can produce two-dimensional histograms (heatmaps) where density is represented by the fill color encoding.

Fork
js
Plot.plot({
  height: 640,
  marginLeft: 60,
  color: {
    scheme: "YlGnBu",
    type: "symlog"
  },
  marks: [
    Plot.rect(diamonds, Plot.bin({fill: "count"}, {x: "carat", y: "price", thresholds: 100}))
  ]
})

TIP

A similar plot can be made with the dot mark, if you’d prefer a size encoding.

Below we recreate an uncommon chart by Max Roser that visualizes global poverty. Each rect represents a country: x encodes the country’s population, while y encodes the proportion of that population living in poverty; hence area represents the number of people living in poverty. Rects are stacked along x in order of descending y.

Fork
js
Plot.plot({
  x: {label: "Population (millions)"},
  y: {percent: true, label: "Proportion living on less than $30 per day (%)"},
  marks: [
    Plot.rectY(povcalnet, Plot.stackX({
      filter: (d) => ["N", "A"].includes(d.CoverageType),
      x: "ReqYearPopulation",
      order: "HeadCount",
      reverse: true,
      y2: "HeadCount", // y2 to avoid stacking by y
      title: (d) => `${d.CountryName}\n${(d.HeadCount * 100).toFixed(1)}%`,
      insetLeft: 0.2,
      insetRight: 0.2
    })),
    Plot.ruleY([0])
  ]
})

The interval transform may be used to convert a single value in x or y (or both) into an extent. For example, the chart below shows the observed daily maximum temperature in Seattle for the year 2015. The day-in-month and month-in-year numbers are expanded to unit intervals by setting the interval option to 1.

Fork
js
Plot.plot({
  aspectRatio: 1,
  y: {ticks: 12, tickFormat: Plot.formatMonth("en", "narrow")},
  marks: [
    Plot.rect(seattle, {
      filter: (d) => d.date.getUTCFullYear() === 2015,
      x: (d) => d.date.getUTCDate(),
      y: (d) => d.date.getUTCMonth(),
      interval: 1,
      fill: "temp_max",
      inset: 0.5
    })
  ]
})

TIP

A similar chart could be made with the cell mark using ordinal x and y scales instead, or with the dot mark as a scatterplot.

Rect options

The following channels are optional:

  • x1 - the starting horizontal position; bound to the x scale
  • y1 - the starting vertical position; bound to the y scale
  • x2 - the ending horizontal position; bound to the x scale
  • y2 - the ending vertical position; bound to the y scale

If x1 is specified but x2 is not specified, then x must be a band scale; if y1 is specified but y2 is not specified, then y must be a band scale.

If an interval is specified, such as d3.utcDay, x1 and x2 can be derived from x: interval.floor(x) is invoked for each x to produce x1, and interval.offset(x1) is invoked for each x1 to produce x2. The same is true for y, y1, and y2, respectively. If the interval is specified as a number n, x1 and x2 are taken as the two consecutive multiples of n that bracket x. Named UTC intervals such as day are also supported; see scale options.

The rect mark supports the standard mark options, including insets and rounded corners. The stroke defaults to none. The fill defaults to currentColor if the stroke is none, and to none otherwise.

rect(data, options)

js
Plot.rect(olympians, Plot.bin({fill: "count"}, {x: "weight", y: "height"}))

Returns a new rect with the given data and options.

rectX(data, options)

js
Plot.rectX(olympians, Plot.binY({x: "count"}, {y: "weight"}))

Equivalent to rect, except that if neither the x1 nor x2 option is specified, the x option may be specified as shorthand to apply an implicit stackX transform; this is the typical configuration for a histogram with horizontal→ rects aligned at x = 0. If the x option is not specified, it defaults to the identity function.

rectY(data, options)

js
Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight"}))

Equivalent to rect, except that if neither the y1 nor y2 option is specified, the y option may be specified as shorthand to apply an implicit stackY transform; this is the typical configuration for a histogram with vertical↑ rects aligned at y = 0. If the y option is not specified, it defaults to the identity function.