Skip to content

Commit 51d85c4

Browse files
mbostockFil
andauthored
rect support for band scales (#1909)
* 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 <fil@rezo.net> --------- Co-authored-by: Philippe Rivière <fil@rezo.net>
1 parent 99c8995 commit 51d85c4

File tree

11 files changed

+506
-51
lines changed

11 files changed

+506
-51
lines changed

docs/marks/bar.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const timeseries = [
2626
# Bar mark
2727

2828
:::tip
29-
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).
29+
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).
3030
:::
3131

3232
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.

docs/marks/cell.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ onMounted(() => {
2121
# Cell mark
2222

2323
:::tip
24-
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).
24+
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).
2525
:::
2626

2727
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.

docs/marks/rect.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,6 @@ onMounted(() => {
2626

2727
# Rect mark
2828

29-
:::tip
30-
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).
31-
:::
32-
3329
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.
3430

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

202-
Typically either **x1** and **x2** are specified, or **y1** and **y2**, or both.
198+
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.
203199

204200
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).
205201

src/marks/rect.js

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ export class Rect extends Mark {
3030
super(
3131
data,
3232
{
33-
x1: {value: x1, scale: "x", optional: true},
34-
y1: {value: y1, scale: "y", optional: true},
33+
x1: {value: x1, scale: "x", type: x1 != null && x2 == null ? "band" : undefined, optional: true},
34+
y1: {value: y1, scale: "y", type: y1 != null && y2 == null ? "band" : undefined, optional: true},
3535
x2: {value: x2, scale: "x", optional: true},
3636
y2: {value: y2, scale: "y", optional: true}
3737
},
@@ -51,9 +51,11 @@ export class Rect extends Mark {
5151
const {marginTop, marginRight, marginBottom, marginLeft, width, height} = dimensions;
5252
const {projection} = context;
5353
const {insetTop, insetRight, insetBottom, insetLeft, rx, ry} = this;
54+
const bx = (x?.bandwidth ? x.bandwidth() : 0) - insetLeft - insetRight;
55+
const by = (y?.bandwidth ? y.bandwidth() : 0) - insetTop - insetBottom;
5456
return create("svg:g", context)
5557
.call(applyIndirectStyles, this, dimensions, context)
56-
.call(applyTransform, this, {x: X1 && X2 && x, y: Y1 && Y2 && y}, 0, 0)
58+
.call(applyTransform, this, {}, 0, 0)
5759
.call((g) =>
5860
g
5961
.selectAll()
@@ -63,26 +65,34 @@ export class Rect extends Mark {
6365
.call(applyDirectStyles, this)
6466
.attr(
6567
"x",
66-
X1 && X2 && (projection || !isCollapsed(x))
67-
? (i) => Math.min(X1[i], X2[i]) + insetLeft
68+
X1 && (projection || !isCollapsed(x))
69+
? X2
70+
? (i) => Math.min(X1[i], X2[i]) + insetLeft
71+
: (i) => X1[i] + insetLeft
6872
: marginLeft + insetLeft
6973
)
7074
.attr(
7175
"y",
72-
Y1 && Y2 && (projection || !isCollapsed(y))
73-
? (i) => Math.min(Y1[i], Y2[i]) + insetTop
76+
Y1 && (projection || !isCollapsed(y))
77+
? Y2
78+
? (i) => Math.min(Y1[i], Y2[i]) + insetTop
79+
: (i) => Y1[i] + insetTop
7480
: marginTop + insetTop
7581
)
7682
.attr(
7783
"width",
78-
X1 && X2 && (projection || !isCollapsed(x))
79-
? (i) => Math.max(0, Math.abs(X2[i] - X1[i]) - insetLeft - insetRight)
84+
X1 && (projection || !isCollapsed(x))
85+
? X2
86+
? (i) => Math.max(0, Math.abs(X2[i] - X1[i]) + bx)
87+
: bx
8088
: width - marginRight - marginLeft - insetRight - insetLeft
8189
)
8290
.attr(
8391
"height",
84-
Y1 && Y2 && (projection || !isCollapsed(y))
85-
? (i) => Math.max(0, Math.abs(Y1[i] - Y2[i]) - insetTop - insetBottom)
92+
Y1 && (projection || !isCollapsed(y))
93+
? Y2
94+
? (i) => Math.max(0, Math.abs(Y1[i] - Y2[i]) + by)
95+
: by
8696
: height - marginTop - marginBottom - insetTop - insetBottom
8797
)
8898
.call(applyAttr, "rx", rx)

src/transforms/interval.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function maybeIntervalK(k, maybeInsetK, options, trivial) {
2222
...options,
2323
[k]: undefined,
2424
[`${k}1`]: v1 === undefined ? kv : v1,
25-
[`${k}2`]: v2 === undefined ? kv : v2
25+
[`${k}2`]: v2 === undefined && !(v1 === v2 && trivial) ? kv : v2
2626
};
2727
}
2828
let D1, V1;

test/output/groupedRects.svg

Lines changed: 32 additions & 32 deletions
Loading

test/output/rectBandX.svg

Lines changed: 136 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)