Skip to content

Commit c7fed98

Browse files
committed
document the custom render option (#1711)
1 parent 0726989 commit c7fed98

File tree

3 files changed

+138
-1
lines changed

3 files changed

+138
-1
lines changed

docs/.vitepress/config.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ export default defineConfig({
6767
{text: "Formats", link: "/features/formats"},
6868
{text: "Markers", link: "/features/markers"},
6969
{text: "Shorthand", link: "/features/shorthand"},
70-
{text: "Accessibility", link: "/features/accessibility"}
70+
{text: "Accessibility", link: "/features/accessibility"},
71+
{text: "Extensions", link: "/features/extensions"}
7172
]
7273
},
7374
{

docs/features/extensions.md

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Extending Plot
2+
3+
## The render method {#render}
4+
5+
Each [mark](./marks.md) has a render method that is called once for each facet (unless its data for that facet is empty). This method is responsible for drawing the mark. You can extend (or replace) this method by specifying the **render** mark option as a custom function.
6+
7+
:::warning
8+
Note that, in general, we do not recommend using this low-level interface when a more high-level option exists. It is meant for use by extensions developers more than by users.
9+
:::
10+
11+
The render function is called with the following six arguments:
12+
13+
* *index*: the index of the facet
14+
* *scales*: the scale functions and descriptors
15+
* *values*: the scaled and raw channels
16+
* *dimensions*: the dimensions of the facet
17+
* *context*: the context
18+
* *next*: the next render method in the chain
19+
20+
The function is expected to return a single SVG node, or null or undefined if the facet should be skipped. Typically, it returns a [<g>](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g) container, with a child node (say, a [<circle>](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle)) for each valid data point.
21+
22+
The *index* is an array of indices in the channels, that represent the points to be drawn in the current facet. The *scales* object contains the scale functions, indexed by name, and an additional scales property with the scales descriptors, also indexed by name.
23+
24+
For example, the following code will log the color associated with the Torgersen category ("#e15759") and the [instantiated color scale object](./plots.md#plot_scale), and will not render anything to the chart.
25+
```js
26+
Plot.dot(penguins, {
27+
x: "culmen_length_mm",
28+
y: "culmen_depth_mm",
29+
fill: "island",
30+
render(index, scales, values, dimensions, context, next) {
31+
console.log(scales.color("Torgersen"));
32+
console.log(scales.scales.color);
33+
}
34+
}).plot()
35+
```
36+
37+
The *values* object contains the scaled channels, indexed by name, and an additional channels property with the unscaled channels, also indexed by name. For example:
38+
39+
```js
40+
Plot.dot(penguins, {
41+
x: "culmen_length_mm",
42+
y: "culmen_depth_mm",
43+
fx: "species",
44+
fill: "island",
45+
render(index, scales, values, dimensions, context, next) {
46+
const i = index[0];
47+
console.log(i, values.fill[i], values.channels.fill.value[i]);
48+
}
49+
}).plot()
50+
```
51+
52+
will output the following three lines to the console, with each line containing the index of the first penguin of the current facet, its fill color, and the underlying (unscaled) category:
53+
54+
```js
55+
0 '#e15759' 'Torgersen'
56+
152 '#f28e2c' 'Dream'
57+
220 '#4e79a7' 'Biscoe'
58+
```
59+
60+
The *dimensions* object contains the width, height, marginLeft, marginTop, marginRight, and marginBottom of the frame. For example, to draw an ellipse that extends to the edges:
61+
62+
```js
63+
Plot.plot({
64+
marks: [
65+
function (index, scales, values, dimensions, context, next) {
66+
const e = context.document.createElementNS("http://www.w3.org/2000/svg", "ellipse");
67+
e.setAttribute("rx", (dimensions.width - dimensions.marginLeft - dimensions.marginRight) / 2);
68+
e.setAttribute("ry", (dimensions.height - dimensions.marginTop - dimensions.marginBottom) / 2);
69+
e.setAttribute("cx", (dimensions.width + dimensions.marginLeft - dimensions.marginRight) / 2);
70+
e.setAttribute("cy", (dimensions.height + dimensions.marginTop - dimensions.marginBottom) / 2);
71+
e.setAttribute("fill", "red");
72+
return e;
73+
}
74+
]
75+
})
76+
```
77+
78+
The *context* contains several useful globals:
79+
* document - the [document object](https://developer.mozilla.org/en-US/docs/Web/API/Document)
80+
* ownerSVGElement - the chart’s bare svg element
81+
* className - the [class name](./plots.md#other-options) of the chart (*e.g.*, "plot-d6a7b5")
82+
* projection - the [projection](./projections.md) stream, if any
83+
84+
:::tip
85+
When writing a plugin, prefer *context*.document to the global document; this will allow your code to run in different contexts such as a server-side rendering environment.
86+
:::
87+
88+
The last argument, *next*, is a function that can be called to continue the render chain. For example, if you wish to animate a mark to fade in, you can render it as usual, immediately set its opacity to 0, then bring it to life with D3:
89+
90+
```js
91+
Plot.dot(penguins, {
92+
x: "culmen_length_mm",
93+
y: "culmen_depth_mm",
94+
fill: "island",
95+
render(index, scales, values, dimensions, context, next) {
96+
const g = next(index, scales, values, dimensions, context);
97+
d3.select(g)
98+
.selectAll("circle")
99+
.style("opacity", 0)
100+
.transition()
101+
.delay(() => Math.random() * 5000)
102+
.style("opacity", 1);
103+
return g;
104+
}
105+
}).plot()
106+
```
107+
108+
:::info
109+
Note that Plot’s marks usually set the attributes of the nodes than their styles. This allows you to play with the styles — which have a higher priority than the attributes — without conflicting.
110+
:::
111+
112+
Here is another example, where we render the dots one by one:
113+
```js
114+
Plot.dot(penguins, {
115+
x: "culmen_length_mm",
116+
y: "culmen_depth_mm",
117+
fill: "island",
118+
render(index, scales, values, dimensions, context, next) {
119+
let node = next(index, scales, values, dimensions, context);
120+
let k = 0;
121+
requestAnimationFrame(function draw() {
122+
const newNode = next(index.slice(0, ++k), scales, values, dimensions, context);
123+
node.replaceWith(newNode);
124+
node = newNode;
125+
if (node.isConnected && k < index.length) requestAnimationFrame(draw);
126+
});
127+
return node;
128+
}
129+
}).plot()
130+
```
131+
132+
:::info
133+
A similar technique is used by Plot’s [pointer](../interactions/pointer.md) transform to render the point closest to the pointer.
134+
:::

docs/features/marks.md

+2
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,8 @@ All marks support the following [transform](./transforms.md) options:
553553

554554
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).
555555

556+
The **render** option allows to override or extend the default mark’s rendering method. See [extengin Plot](./extensions.md#render) for details.
557+
556558
## marks(...*marks*) <VersionBadge version="0.2.0" /> {#marks}
557559

558560
```js

0 commit comments

Comments
 (0)