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.
ForkPlot.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"
})
]
})
The rect mark is often 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:
ForkPlot.rectY(bins, {x1: "x0", x2: "x1", y: "length"}).plot({round: true})
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.
More commonly, the rect mark is paired with the bin transform to bin quantitative values automatically. As an added bonus, this sets default inset options for a 1px gap separating adjacent rects, improving readability.
ForkPlot.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 with an implicit stackX transform, while rectY is for vertical↑ rects with an implicit stackY transform.
ForkPlot.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.
ForkPlot.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.
ForkPlot.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.
ForkPlot.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.
ForkPlot.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. (Unlike the bin transform, the interval transform will produce overlapping rects if multiple points have the same position.) 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.
ForkPlot.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.
To round corners, use the r option. If the combined corner radii exceed the width or height of the rect, the radii are proportionally reduced to produce a pill shape with circular caps. Try increasing the radii below.
Plot.plot({
marks: [
Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight", r: 4, thresholds: 10})),
Plot.ruleY([0])
]
})
To round corners on a specific side, use the rx1, ry1, rx2, or ry2 options. When stacking rounded rects vertically, use a positive ry2 and a corresponding negative ry1; likewise for stacking rounded rects horizontally, use a positive rx2 and a negative rx1. Use the clip option to hide the “wings” below zero.
ForkPlot.plot({
color: {legend: true},
marks: [
Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight", fill: "sex", ry2: 4, ry1: -4, clip: "frame"})),
Plot.ruleY([0])
]
})
You can even round specific corners using the rx1y1, rx2y1, rx2y2, and rx1y2 options.
ForkPlot.plot({
color: {legend: true},
marks: [
Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight", fill: "sex", rx1y2: 10, rx1y1: -10, clip: "frame"})),
Plot.ruleY([0])
]
})
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)
Plot.rect(olympians, Plot.bin({fill: "count"}, {x: "weight", y: "height"}))
Returns a new rect with the given data and options.
rectX(data, options)
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)
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.