Skip to content

rect support for band scales #1909

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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