forked from observablehq/plot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathline.js
133 lines (125 loc) · 3.87 KB
/
line.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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import {curveLinear, geoPath, line as shapeLine} from "d3";
import {create} from "../context.js";
import {Curve} from "../curve.js";
import {indexOf, identity, maybeTuple, maybeZ} from "../options.js";
import {Mark} from "../mark.js";
import {coerceNumbers} from "../scales.js";
import {
applyDirectStyles,
applyIndirectStyles,
applyTransform,
applyGroupedChannelStyles,
groupIndex
} from "../style.js";
import {maybeDenseIntervalX, maybeDenseIntervalY} from "../transforms/bin.js";
import {applyGroupedMarkers, markers} from "./marker.js";
const defaults = {
ariaLabel: "line",
fill: "none",
stroke: "currentColor",
strokeWidth: 1.5,
strokeLinecap: "round",
strokeLinejoin: "round",
strokeMiterlimit: 1
};
// This is a special built-in curve that will use d3.geoPath when there is a
// projection, and the linear curve when there is not. You can explicitly
// opt-out of d3.geoPath and instead use d3.line with the "linear" curve.
function curveAuto(context) {
return curveLinear(context);
}
// For the “auto” curve, return a symbol instead of a curve implementation;
// we’ll use d3.geoPath instead of d3.line to render if there’s a projection.
function LineCurve({curve = curveAuto, tension}) {
return typeof curve !== "function" && `${curve}`.toLowerCase() === "auto" ? curveAuto : Curve(curve, tension);
}
export class Line extends Mark {
constructor(data, options = {}) {
const {x, y, z} = options;
super(
data,
{
x: {value: x, scale: "x"},
y: {value: y, scale: "y"},
z: {value: maybeZ(options), optional: true}
},
options,
defaults
);
this.z = z;
this.curve = LineCurve(options);
markers(this, options);
}
filter(index) {
return index;
}
project(channels, values, context) {
// For the auto curve, projection is handled at render.
if (this.curve !== curveAuto) {
super.project(channels, values, context);
}
}
render(index, scales, channels, dimensions, context) {
const {x: X, y: Y} = channels;
const {curve} = this;
return create("svg:g", context)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, scales)
.call((g) =>
g
.selectAll()
.data(groupIndex(index, [X, Y], this, channels))
.enter()
.append("path")
.call(applyDirectStyles, this)
.call(applyGroupedChannelStyles, this, channels)
.call(applyGroupedMarkers, this, channels)
.attr(
"d",
curve === curveAuto && context.projection
? sphereLine(context.projection, X, Y)
: shapeLine()
.curve(curve)
.defined((i) => i >= 0)
.x((i) => X[i])
.y((i) => Y[i])
)
)
.node();
}
}
function sphereLine(projection, X, Y) {
const path = geoPath(projection);
X = coerceNumbers(X);
Y = coerceNumbers(Y);
return (I) => {
let line = [];
const lines = [line];
for (const i of I) {
// Check for undefined value; see groupIndex.
if (i === -1) {
line = [];
lines.push(line);
} else {
line.push([X[i], Y[i]]);
}
}
return path({type: "MultiLineString", coordinates: lines});
};
}
/** @jsdoc line */
export function line(data, options = {}) {
let {x, y, ...remainingOptions} = options;
[x, y] = maybeTuple(x, y);
return new Line(data, {...remainingOptions, x, y});
}
/** @jsdoc lineX */
export function lineX(data, options = {}) {
const {x = identity, y = indexOf, ...remainingOptions} = options;
return new Line(data, maybeDenseIntervalY({...remainingOptions, x, y}));
}
/** @jsdoc lineY */
export function lineY(data, options = {}) {
const {x = indexOf, y = identity, ...remainingOptions} = options;
return new Line(data, maybeDenseIntervalX({...remainingOptions, x, y}));
}