forked from observablehq/plot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdot.js
113 lines (105 loc) · 4.52 KB
/
dot.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import {path, symbolCircle} from "d3";
import {create} from "../context.js";
import {positive} from "../defined.js";
import {identity, maybeFrameAnchor, maybeNumberChannel, maybeTuple} from "../options.js";
import {Mark} from "../plot.js";
import {applyChannelStyles, applyDirectStyles, applyFrameAnchor, applyIndirectStyles, applyTransform} from "../style.js";
import {maybeSymbolChannel} from "../symbols.js";
import {sort} from "../transforms/basic.js";
import {maybeIntervalMidX, maybeIntervalMidY} from "../transforms/interval.js";
const defaults = {
ariaLabel: "dot",
fill: "none",
stroke: "currentColor",
strokeWidth: 1.5
};
export class Dot extends Mark {
constructor(data, options = {}) {
const {x, y, r, rotate, symbol = symbolCircle, frameAnchor} = options;
const [vrotate, crotate] = maybeNumberChannel(rotate, 0);
const [vsymbol, csymbol] = maybeSymbolChannel(symbol);
const [vr, cr] = maybeNumberChannel(r, vsymbol == null ? 3 : 4.5);
super(
data,
[
{name: "x", value: x, scale: "x", optional: true},
{name: "y", value: y, scale: "y", optional: true},
{name: "r", value: vr, scale: "r", filter: positive, optional: true},
{name: "rotate", value: vrotate, optional: true},
{name: "symbol", value: vsymbol, scale: "symbol", optional: true}
],
options.sort === undefined && options.reverse === undefined ? sort({channel: "r", order: "descending"}, options) : options,
defaults
);
this.r = cr;
this.rotate = crotate;
this.symbol = csymbol;
this.frameAnchor = maybeFrameAnchor(frameAnchor);
// Give a hint to the symbol scale; this allows the symbol scale to chose
// appropriate default symbols based on whether the dots are filled or
// stroked, and for the symbol legend to match the appearance of the dots.
const {channels} = this;
const symbolChannel = channels.find(({scale}) => scale === "symbol");
if (symbolChannel) {
const fillChannel = channels.find(({name}) => name === "fill");
const strokeChannel = channels.find(({name}) => name === "stroke");
symbolChannel.hint = {
fill: fillChannel ? (fillChannel.value === symbolChannel.value ? "color" : "currentColor") : this.fill,
stroke: strokeChannel ? (strokeChannel.value === symbolChannel.value ? "color" : "currentColor") : this.stroke
};
}
}
render(index, scales, channels, dimensions, context) {
const {x: X, y: Y, r: R, rotate: A, symbol: S} = channels;
const [cx, cy] = applyFrameAnchor(this, dimensions);
const circle = this.symbol === symbolCircle;
return create("svg:g", context)
.call(applyIndirectStyles, this, scales, dimensions)
.call(applyTransform, this, scales)
.call(g => g.selectAll()
.data(index)
.enter()
.append(circle ? "circle" : "path")
.call(applyDirectStyles, this)
.call(circle
? selection => {
selection
.attr("cx", X ? i => X[i] : cx)
.attr("cy", Y ? i => Y[i] : cy)
.attr("r", R ? i => R[i] : this.r);
}
: selection => {
const translate = X && Y ? i => `translate(${X[i]},${Y[i]})`
: X ? i => `translate(${X[i]},${cy})`
: Y ? i => `translate(${cx},${Y[i]})`
: () => `translate(${cx},${cy})`;
selection
.attr("transform", A ? i => `${translate(i)} rotate(${A[i]})`
: this.rotate ? i => `${translate(i)} rotate(${this.rotate})`
: translate)
.attr("d", i => {
const p = path(), r = R ? R[i] : this.r;
(S ? S[i] : this.symbol).draw(p, r * r * Math.PI);
return p;
});
})
.call(applyChannelStyles, this, channels))
.node();
}
}
export function dot(data, {x, y, ...options} = {}) {
if (options.frameAnchor === undefined) ([x, y] = maybeTuple(x, y));
return new Dot(data, {...options, x, y});
}
export function dotX(data, {x = identity, ...options} = {}) {
return new Dot(data, maybeIntervalMidY({...options, x}));
}
export function dotY(data, {y = identity, ...options} = {}) {
return new Dot(data, maybeIntervalMidX({...options, y}));
}
export function circle(data, options) {
return dot(data, {...options, symbol: "circle"});
}
export function hexagon(data, options) {
return dot(data, {...options, symbol: "hexagon"});
}