Skip to content

Commit

Permalink
rounded rect (observablehq#2099)
Browse files Browse the repository at this point in the history
* rounded rect

* handle collapsed & 1d rects

* remove unused test snapshot

* ratio-preserving radius reduction

* r shorthand; more docs

* rounded frame

* remove unused applyRoundedRect

* rounded bar & cell

* cleaner

* remove unused import

* slightly tidier

* docs

* negative radii

* inset api index

* more docs

* Update docs/marks/rect.md

Co-authored-by: Philippe Rivière <fil@rezo.net>

* test corner orientation exhaustively

* tiny optimization

* more docs

* move docs around

---------

Co-authored-by: Philippe Rivière <fil@rezo.net>
  • Loading branch information
mbostock and Fil authored Jul 21, 2024
1 parent 78cff33 commit 203bd4c
Show file tree
Hide file tree
Showing 35 changed files with 2,386 additions and 137 deletions.
21 changes: 13 additions & 8 deletions docs/data/api.data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ function getHref(name: string, path: string): string {
case "features/plot":
case "features/projection":
return `${path}s`;
case "features/inset":
return "features/scales";
case "features/options":
return "features/transforms";
case "marks/axis": {
Expand Down Expand Up @@ -85,8 +83,8 @@ function getInterfaceName(name: string, path: string): string {
name = name.replace(/([a-z0-9])([A-Z])/, (_, a, b) => `${a} ${b}`); // camel case conversion
name = name.toLowerCase();
if (name === "curve auto") name = "curve";
if (name === "plot facet") name = "plot";
if (name === "bollinger window") name = "bollinger map method";
else if (name === "plot facet") name = "plot";
else if (name === "bollinger window") name = "bollinger map method";
else if (path.startsWith("marks/")) name += " mark";
else if (path.startsWith("transforms/")) name += " transform";
return name;
Expand All @@ -105,10 +103,15 @@ export default {
if (Node.isInterfaceDeclaration(declaration)) {
if (isInternalInterface(name)) continue;
for (const property of declaration.getProperties()) {
const path = index.getRelativePathTo(declaration.getSourceFile());
const href = getHref(name, path);
if (property.getJsDocs().some((d) => d.getTags().some((d) => Node.isJSDocDeprecatedTag(d)))) continue;
allOptions.push({name: property.getName(), context: {name: getInterfaceName(name, path), href}});
if (name === "InsetOptions") {
allOptions.push({name: property.getName(), context: {name: "mark", href: "features/marks"}});
allOptions.push({name: property.getName(), context: {name: "scale", href: "features/scales"}});
} else {
const path = index.getRelativePathTo(declaration.getSourceFile());
const href = getHref(name, path);
allOptions.push({name: property.getName(), context: {name: getInterfaceName(name, path), href}});
}
}
} else if (Node.isFunctionDeclaration(declaration)) {
const comment = getDescription(declaration);
Expand Down Expand Up @@ -141,7 +144,9 @@ export default {
throw new Error(`anchor not found: ${href}#${name}`);
}
}
for (const {context: {href}} of allOptions) {
for (const {
context: {href}
} of allOptions) {
if (!anchors.has(`/${href}.md`)) {
throw new Error(`file not found: ${href}`);
}
Expand Down
2 changes: 1 addition & 1 deletion docs/features/facets.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ Faceting can be explicitly enabled or disabled on a mark with the **facet** opti

When mark-level faceting is used, the default *auto* setting is equivalent to *include*: the mark will be faceted if either the **fx** or **fy** channel option (or both) is specified. The null or false option will disable faceting, while *exclude* draws the subset of the mark’s data *not* in the current facet. When a mark uses *super* faceting, it is not allowed to use position scales (*x*, *y*, *fx*, or *fy*); *super* faceting is intended for decorations, such as labels and legends.

The **facetAnchor** option<a id="facetAnchor" class="header-anchor" href="#facetAnchor" aria-label="Permalink to &quot;facetAnchor&quot;"></a> <VersionBadge version="0.6.3" /> controls the placement of the mark with respect to the facets. Based on the value, the mark will be displayed on:
The **facetAnchor** option<a id="facetAnchor" href="#facetAnchor" aria-label="Permalink to &quot;facetAnchor&quot;"></a> <VersionBadge version="0.6.3" /> controls the placement of the mark with respect to the facets. Based on the value, the mark will be displayed on:

* null - non-empty facets
* *top*, *right*, *bottom*, or *left* - the given side
Expand Down
41 changes: 30 additions & 11 deletions docs/features/marks.md
Original file line number Diff line number Diff line change
Expand Up @@ -531,17 +531,6 @@ Plot.dot(numbers, {x: {transform: (data) => data}})

The **title**, **href**, and **ariaLabel** options can *only* be specified as channels. When these options are specified as a string, the string refers to the name of a column in the mark’s associated data. If you’d like every instance of a particular mark to have the same value, specify the option as a function that returns the desired value, *e.g.* `() => "Hello, world!"`.

The rectangular marks ([bar](../marks/bar.md), [cell](../marks/cell.md), [frame](../marks/frame.md), and [rect](../marks/rect.md)) support insets and rounded corner constant options:

* **insetTop** - inset the top edge
* **insetRight** - inset the right edge
* **insetBottom** - inset the bottom edge
* **insetLeft** - inset the left edge
* **rx** - the [*x* radius](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/rx) for rounded corners
* **ry** - the [*y* radius](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/ry) for rounded corners

Insets are specified in pixels. Corner radii are specified in either pixels or percentages (strings). Both default to zero. Insets are typically used to ensure a one-pixel gap between adjacent bars; note that the [bin transform](../transforms/bin.md) provides default insets, and that the [band scale padding](./scales.md#position-scale-options) defaults to 0.1, which also provides separation.

For marks that support the **frameAnchor** option, it may be specified as one of the four sides (*top*, *right*, *bottom*, *left*), one of the four corners (*top-left*, *top-right*, *bottom-right*, *bottom-left*), or the *middle* of the frame.

All marks support the following [transform](./transforms.md) options:
Expand All @@ -554,6 +543,36 @@ All marks support the following [transform](./transforms.md) options:

The **sort** option, when not specified as a channel value (such as a field name or an accessor function), can also be used to [impute ordinal scale domains](./scales.md#sort-mark-option).

### Insets

Rect-like marks support insets: a positive inset moves the respective side in (towards the opposing side), whereas a negative inset moves the respective side out (away from the opposing side). Insets are specified in pixels using the following options:

* **inset** - shorthand for all four insets
* **insetTop** - inset the top edge
* **insetRight** - inset the right edge
* **insetBottom** - inset the bottom edge
* **insetLeft** - inset the left edge

Insets default to zero. Insets are commonly used to create a one-pixel gap between adjacent bars in histograms; the [bin transform](../transforms/bin.md) provides default insets. (Note that the [band scale padding](./scales.md#position-scale-options) defaults to 0.1 as an alternative to insets.)

### Rounded corners

Rect-like marks support rounded corners. Each corner (or side) is individually addressable <VersionBadge pr="2099" /> using the following options:

* **r** - the radius for all four corners
* **rx1** - the radius for the **x1**-**y1** and **x1**-**y2** corners
* **rx2** - the radius for the **x2**-**y1** and **x2**-**y2** corners
* **ry1** - the radius for the **x1**-**y1** and **x2**-**y1** corners
* **ry2** - the radius for the **x1**-**y2** and **x2**-**y2** corners
* **rx1y1** - the radius for the **x1**-**y1** corner
* **rx1y2** - the radius for the **x1**-**y2** corner
* **rx2y1** - the radius for the **x2**-**y1** corner
* **rx2y2** - the radius for the **x2**-**y2** corner
* **rx** - the [*x*-radius](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/rx) for elliptical corners
* **ry** - the [*y*-radius](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/ry) for elliptical corners

Corner radii are specified in either pixels or, for **rx** and **ry**, as percentages (strings) or the keyword *auto*. If the corner radii are too big, they are reduced proportionally.

## marks(...*marks*) <VersionBadge version="0.2.0" /> {#marks}

```js
Expand Down
2 changes: 1 addition & 1 deletion docs/features/plots.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ The default **width** is 640. On Observable, the width can be set to the [standa
Plot does not adjust margins automatically to make room for long tick labels. If your *y* axis labels are too long, you can increase the **marginLeft** to make more room. Also consider using a different **tickFormat** for short labels (*e.g.*, `s` for SI prefix notation), or a scale **transform** (say to convert units to millions or billions).
:::

The **aspectRatio** option<a id="aspectRatio" class="header-anchor" href="#aspectRatio" aria-label="Permalink to &quot;aspectRatio&quot;"></a> <VersionBadge version="0.6.4" />, if not null, computes a default **height** such that a variation of one unit in the *x* dimension is represented by the corresponding number of pixels as a variation in the *y* dimension of one unit.
The **aspectRatio** option<a id="aspectRatio" href="#aspectRatio" aria-label="Permalink to &quot;aspectRatio&quot;"></a> <VersionBadge version="0.6.4" />, if not null, computes a default **height** such that a variation of one unit in the *x* dimension is represented by the corresponding number of pixels as a variation in the *y* dimension of one unit.

<p>
<label class="label-input">
Expand Down
2 changes: 1 addition & 1 deletion docs/features/scales.md
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ Plot.plot({
[Mark transforms](./transforms.md) typically consume values *before* they are passed through scales (_e.g._, when binning). In this case the mark transforms will see the values prior to the scale transform as input, and the scale transform will apply to the *output* of the mark transform.
:::

The **interval** scale option<a id="interval" class="header-anchor" href="#interval" aria-label="Permalink to &quot;interval&quot;"></a> <VersionBadge version="0.5.1" /> sets an ordinal scale’s **domain** to the start of every interval within the extent of the data. In addition, it implicitly sets the **transform** of the scale to *interval*.floor, rounding values down to the start of each interval. For example, below we generate a time-series bar chart; when an **interval** is specified, missing days are visible.
The **interval** scale option<a id="interval" href="#interval" aria-label="Permalink to &quot;interval&quot;"></a> <VersionBadge version="0.5.1" /> sets an ordinal scale’s **domain** to the start of every interval within the extent of the data. In addition, it implicitly sets the **transform** of the scale to *interval*.floor, rounding values down to the start of each interval. For example, below we generate a time-series bar chart; when an **interval** is specified, missing days are visible.

<p>
<label class="label-input">
Expand Down
2 changes: 1 addition & 1 deletion docs/marks/bar.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ Plot.plot({

## Bar options

For required channels, see [barX](#barX) and [barY](#barY). The bar mark supports the [standard mark options](../features/marks.md), including insets and rounded corners. The **stroke** defaults to *none*. The **fill** defaults to *currentColor* if the stroke is *none*, and to *none* otherwise.
For required channels, see [barX](#barX) and [barY](#barY). The bar mark supports the [standard mark options](../features/marks.md), including [insets](../features/marks.md#insets) and [rounded corners](../features/marks.md#rounded-corners). The **stroke** defaults to *none*. The **fill** defaults to *currentColor* if the stroke is *none*, and to *none* otherwise.

## barX(*data*, *options*) {#barX}

Expand Down
1 change: 1 addition & 0 deletions docs/marks/box.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ The given *options* are passed through to these underlying marks, with the excep
* **stroke** - the stroke color of the rule, tick, and dot; defaults to *currentColor*
* **strokeOpacity** - the stroke opacity of the rule, tick, and dot; defaults to 1
* **strokeWidth** - the stroke width of the tick; defaults to 1
* **r** - the radius of the dot; defaults to 3

## boxX(*data*, *options*) {#boxX}

Expand Down
2 changes: 1 addition & 1 deletion docs/marks/cell.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ When an ordinal scale domain has high cardinality, the **ticks** scale option ca

## Cell options

In addition to the [standard mark options](../features/marks.md#mark-options), including insets and rounded corners, the following optional channels are supported:
In addition to the [standard mark options](../features/marks.md#mark-options), including [insets](../features/marks.md#insets) and [rounded corners](../features/marks.md#rounded-corners), the following optional channels are supported:

* **x** - the horizontal position; bound to the *x* scale, which must be *band*
* **y** - the vertical position; bound to the *y* scale, which must be *band*
Expand Down
2 changes: 1 addition & 1 deletion docs/marks/frame.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ Plot.plot({

## Frame options

The frame mark supports the [standard mark options](../features/marks.md#mark-options), and the **rx** and **ry** options to set the [*x* radius](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/rx) and [*y* radius](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/ry) for rounded corners. It does not accept any data. The default **stroke** is *currentColor*, and the default **fill** is *none*.
The frame mark supports the [standard mark options](../features/marks.md#mark-options), including [insets](../features/marks.md#insets) and [rounded corners](../features/marks.md#rounded-corners). It does not accept any data. The default **stroke** is *currentColor*, and the default **fill** is *none*.

If the **anchor** option is specified as one of *left*, *right*, *top*, or *bottom*, that side is rendered as a single line (and the **fill**, **fillOpacity**, **rx**, and **ry** options are ignored).

Expand Down
Loading

0 comments on commit 203bd4c

Please sign in to comment.