Skip to content

Commit

Permalink
rect support for band scales (#1909)
Browse files Browse the repository at this point in the history
* rect support for band scales

* band hint if only one value

* more band scale support

* band rect docs

* Update docs/marks/rect.md

Co-authored-by: Philippe Rivière <[email protected]>

---------

Co-authored-by: Philippe Rivière <[email protected]>
  • Loading branch information
mbostock and Fil authored Nov 2, 2023
1 parent 99c8995 commit 51d85c4
Show file tree
Hide file tree
Showing 11 changed files with 506 additions and 51 deletions.
2 changes: 1 addition & 1 deletion docs/marks/bar.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const timeseries = [
# Bar mark

:::tip
The bar mark is one of several marks in Plot for drawing rectangles; it should be used when one dimension is ordinal and the other is quantitative. See also [rect](./rect.md) and [cell](./cell.md).
The bar mark is a variant of the [rect mark](./rect.md) for use when one dimension is ordinal and the other is quantitative. See also the [cell mark](./cell.md).
:::

The **bar mark** comes in two orientations: [barY](#barY) extends vertically↑ as in a vertical bar chart or column chart, while [barX](#barX) extends horizontally→. For example, the bar chart below shows the frequency of letters in the English language.
Expand Down
2 changes: 1 addition & 1 deletion docs/marks/cell.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ onMounted(() => {
# Cell mark

:::tip
The cell mark is one of several marks in Plot for drawing rectangles; it should be used when both dimensions are ordinal. See also [bar](./bar.md) and [rect](./rect.md).
The cell mark is a variant of the [rect mark](./rect.md) for use when both dimensions are ordinal. See also the [bar mark](./bar.md).
:::

The **cell mark** draws rectangles positioned in two ordinal dimensions. Hence, the plot’s *x* and *y* scales are [band scales](../features/scales.md). Cells typically also have a **fill** color encoding.
Expand Down
6 changes: 1 addition & 5 deletions docs/marks/rect.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ onMounted(() => {

# Rect mark

:::tip
The rect mark is one of several marks in Plot for drawing rectangles; it should be used when both dimensions are quantitative. See also [bar](./bar.md) and [cell](./cell.md).
:::

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.

:::plot defer https://observablehq.com/@observablehq/plot-county-boxes
Expand Down Expand Up @@ -199,7 +195,7 @@ The following channels are optional:
* **x2** - the ending horizontal position; bound to the *x* scale
* **y2** - the ending vertical position; bound to the *y* scale

Typically either **x1** and **x2** are specified, or **y1** and **y2**, or both.
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](../features/scales.md#scale-options).

Expand Down
32 changes: 21 additions & 11 deletions src/marks/rect.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export class Rect extends Mark {
super(
data,
{
x1: {value: x1, scale: "x", optional: true},
y1: {value: y1, scale: "y", optional: true},
x1: {value: x1, scale: "x", type: x1 != null && x2 == null ? "band" : undefined, optional: true},
y1: {value: y1, scale: "y", type: y1 != null && y2 == null ? "band" : undefined, optional: true},
x2: {value: x2, scale: "x", optional: true},
y2: {value: y2, scale: "y", optional: true}
},
Expand All @@ -51,9 +51,11 @@ export class Rect extends Mark {
const {marginTop, marginRight, marginBottom, marginLeft, width, height} = dimensions;
const {projection} = context;
const {insetTop, insetRight, insetBottom, insetLeft, rx, ry} = this;
const bx = (x?.bandwidth ? x.bandwidth() : 0) - insetLeft - insetRight;
const by = (y?.bandwidth ? y.bandwidth() : 0) - insetTop - insetBottom;
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, {x: X1 && X2 && x, y: Y1 && Y2 && y}, 0, 0)
.call(applyTransform, this, {}, 0, 0)
.call((g) =>
g
.selectAll()
Expand All @@ -63,26 +65,34 @@ export class Rect extends Mark {
.call(applyDirectStyles, this)
.attr(
"x",
X1 && X2 && (projection || !isCollapsed(x))
? (i) => Math.min(X1[i], X2[i]) + insetLeft
X1 && (projection || !isCollapsed(x))
? X2
? (i) => Math.min(X1[i], X2[i]) + insetLeft
: (i) => X1[i] + insetLeft
: marginLeft + insetLeft
)
.attr(
"y",
Y1 && Y2 && (projection || !isCollapsed(y))
? (i) => Math.min(Y1[i], Y2[i]) + insetTop
Y1 && (projection || !isCollapsed(y))
? Y2
? (i) => Math.min(Y1[i], Y2[i]) + insetTop
: (i) => Y1[i] + insetTop
: marginTop + insetTop
)
.attr(
"width",
X1 && X2 && (projection || !isCollapsed(x))
? (i) => Math.max(0, Math.abs(X2[i] - X1[i]) - insetLeft - insetRight)
X1 && (projection || !isCollapsed(x))
? X2
? (i) => Math.max(0, Math.abs(X2[i] - X1[i]) + bx)
: bx
: width - marginRight - marginLeft - insetRight - insetLeft
)
.attr(
"height",
Y1 && Y2 && (projection || !isCollapsed(y))
? (i) => Math.max(0, Math.abs(Y1[i] - Y2[i]) - insetTop - insetBottom)
Y1 && (projection || !isCollapsed(y))
? Y2
? (i) => Math.max(0, Math.abs(Y1[i] - Y2[i]) + by)
: by
: height - marginTop - marginBottom - insetTop - insetBottom
)
.call(applyAttr, "rx", rx)
Expand Down
2 changes: 1 addition & 1 deletion src/transforms/interval.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function maybeIntervalK(k, maybeInsetK, options, trivial) {
...options,
[k]: undefined,
[`${k}1`]: v1 === undefined ? kv : v1,
[`${k}2`]: v2 === undefined ? kv : v2
[`${k}2`]: v2 === undefined && !(v1 === v2 && trivial) ? kv : v2
};
}
let D1, V1;
Expand Down
64 changes: 32 additions & 32 deletions test/output/groupedRects.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
136 changes: 136 additions & 0 deletions test/output/rectBandX.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 51d85c4

Please sign in to comment.