Skip to content

Commit b1885ad

Browse files
mbostockFil
andauthored
Common style channels (#490)
* common style channels * common styles via mark constructor * common styles for lines * common styles for bars and cells * common styles for rects * common styles for ticks * common styles for rules * common styles for links * test frame * common styles for frames * common styles for dots * common styles for texts * remove obsolete Style function * filter common styles * values ↦ applyScales * prettier * filter facets (and centralize the filtering of common styles in style.js) * normalize fill and stroke defaults * move applyScales * test frame stroke and fill Co-authored-by: Philippe Rivière <[email protected]>
1 parent e7879d7 commit b1885ad

File tree

16 files changed

+344
-473
lines changed

16 files changed

+344
-473
lines changed

src/facet.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {cross, difference, groups, InternMap} from "d3";
22
import {create} from "d3";
3-
import {Mark, values, first, second} from "./mark.js";
3+
import {Mark, first, second} from "./mark.js";
4+
import {applyScales} from "./scales.js";
5+
import {filterStyles} from "./style.js";
46

57
export function facets(data, {x, y, ...options}, marks) {
68
return x === undefined && y === undefined
@@ -76,7 +78,7 @@ class Facet extends Mark {
7678
const fyMargins = fy && {marginTop: 0, marginBottom: 0, height: fy.bandwidth()};
7779
const fxMargins = fx && {marginRight: 0, marginLeft: 0, width: fx.bandwidth()};
7880
const subdimensions = {...dimensions, ...fxMargins, ...fyMargins};
79-
const marksValues = marksChannels.map(channels => values(channels, scales));
81+
const marksValues = marksChannels.map(channels => applyScales(channels, scales));
8082
return create("svg:g")
8183
.call(g => {
8284
if (fy && axes.y) {
@@ -110,10 +112,12 @@ class Facet extends Mark {
110112
.each(function(key) {
111113
const marksFacetIndex = marksIndexByFacet.get(key) || marksIndex;
112114
for (let i = 0; i < marks.length; ++i) {
115+
const values = marksValues[i];
116+
const index = filterStyles(marksFacetIndex[i], values);
113117
const node = marks[i].render(
114-
marksFacetIndex[i],
118+
index,
115119
scales,
116-
marksValues[i],
120+
values,
117121
subdimensions
118122
);
119123
if (node != null) this.appendChild(node);

src/mark.js

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import {color} from "d3";
22
import {ascendingDefined, nonempty} from "./defined.js";
33
import {plot} from "./plot.js";
4+
import {styles} from "./style.js";
45

56
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
67
const TypedArray = Object.getPrototypeOf(Uint8Array);
78
const objectToString = Object.prototype.toString;
89

910
export class Mark {
10-
constructor(data, channels = [], {facet = "auto", ...options} = {}) {
11+
constructor(data, channels = [], options = {}, defaults) {
12+
const {facet = "auto"} = options;
1113
const names = new Set();
1214
this.data = data;
1315
this.facet = facet ? keyword(facet === true ? "include" : facet, "facet", ["auto", "include", "exclude"]) : null;
1416
const {transform} = maybeTransform(options);
1517
this.transform = transform;
18+
if (defaults !== undefined) channels = styles(this, options, channels, defaults);
1619
this.channels = channels.filter(channel => {
1720
const {name, value, optional} = channel;
1821
if (value == null) {
@@ -309,23 +312,6 @@ export function numberChannel(source) {
309312
};
310313
}
311314

312-
// TODO use Float64Array.from for position and radius scales?
313-
export function values(channels = [], scales) {
314-
const values = Object.create(null);
315-
for (let [name, {value, scale}] of channels) {
316-
if (name !== undefined) {
317-
if (scale !== undefined) {
318-
scale = scales[scale];
319-
if (scale !== undefined) {
320-
value = Array.from(value, scale);
321-
}
322-
}
323-
values[name] = value;
324-
}
325-
}
326-
return values;
327-
}
328-
329315
export function isOrdinal(values) {
330316
for (const value of values) {
331317
if (value == null) continue;

src/marks/area.js

Lines changed: 17 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,49 @@
1-
import {group} from "d3";
2-
import {create} from "d3";
3-
import {area as shapeArea} from "d3";
1+
import {area as shapeArea, create, group} from "d3";
42
import {Curve} from "../curve.js";
53
import {defined} from "../defined.js";
6-
import {Mark, indexOf, maybeColor, titleGroup, maybeNumber} from "../mark.js";
7-
import {Style, applyDirectStyles, applyIndirectStyles, applyTransform, applyAttr} from "../style.js";
4+
import {Mark, indexOf, maybeZ} from "../mark.js";
5+
import {applyDirectStyles, applyIndirectStyles, applyTransform, applyGroupedChannelStyles} from "../style.js";
86
import {maybeStackX, maybeStackY} from "../transforms/stack.js";
97

8+
const defaults = {
9+
strokeWidth: 1,
10+
strokeMiterlimit: 1
11+
};
12+
1013
export class Area extends Mark {
11-
constructor(
12-
data,
13-
{
14-
x1,
15-
y1,
16-
x2,
17-
y2,
18-
z, // optional grouping for multiple series
19-
title,
20-
fill,
21-
fillOpacity,
22-
stroke,
23-
strokeOpacity,
24-
curve,
25-
tension,
26-
...options
27-
} = {}
28-
) {
29-
const [vstroke, cstroke] = maybeColor(stroke, "none");
30-
const [vstrokeOpacity, cstrokeOpacity] = maybeNumber(strokeOpacity);
31-
const [vfill, cfill] = maybeColor(fill, cstroke === "none" ? "currentColor" : "none");
32-
const [vfillOpacity, cfillOpacity] = maybeNumber(fillOpacity);
33-
if (z === undefined && vfill != null) z = vfill;
34-
if (z === undefined && vstroke != null) z = vstroke;
14+
constructor(data, options = {}) {
15+
const {x1, y1, x2, y2, curve, tension} = options;
3516
super(
3617
data,
3718
[
3819
{name: "x1", value: x1, scale: "x"},
3920
{name: "y1", value: y1, scale: "y"},
4021
{name: "x2", value: x2, scale: "x", optional: true},
4122
{name: "y2", value: y2, scale: "y", optional: true},
42-
{name: "z", value: z, optional: true},
43-
{name: "title", value: title, optional: true},
44-
{name: "fill", value: vfill, scale: "color", optional: true},
45-
{name: "fillOpacity", value: vfillOpacity, scale: "opacity", optional: true},
46-
{name: "stroke", value: vstroke, scale: "color", optional: true},
47-
{name: "strokeOpacity", value: vstrokeOpacity, scale: "opacity", optional: true}
23+
{name: "z", value: maybeZ(options), optional: true}
4824
],
49-
options
25+
options,
26+
defaults
5027
);
5128
this.curve = Curve(curve, tension);
52-
Style(this, {
53-
fill: cfill,
54-
fillOpacity: cfillOpacity,
55-
stroke: cstroke,
56-
strokeMiterlimit: cstroke === "none" ? undefined : 1,
57-
strokeOpacity: cstrokeOpacity,
58-
...options
59-
});
6029
}
61-
render(I, {x, y}, {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1, z: Z, title: L, fill: F, fillOpacity: FO, stroke: S, strokeOpacity: SO}) {
30+
render(I, {x, y}, channels) {
31+
const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1, z: Z} = channels;
6232
return create("svg:g")
6333
.call(applyIndirectStyles, this)
6434
.call(applyTransform, x, y)
6535
.call(g => g.selectAll()
6636
.data(Z ? group(I, i => Z[i]).values() : [I])
6737
.join("path")
6838
.call(applyDirectStyles, this)
69-
.call(applyAttr, "fill", F && (([i]) => F[i]))
70-
.call(applyAttr, "fill-opacity", FO && (([i]) => FO[i]))
71-
.call(applyAttr, "stroke", S && (([i]) => S[i]))
72-
.call(applyAttr, "stroke-opacity", SO && (([i]) => SO[i]))
39+
.call(applyGroupedChannelStyles, channels)
7340
.attr("d", shapeArea()
7441
.curve(this.curve)
7542
.defined(i => defined(X1[i]) && defined(Y1[i]) && defined(X2[i]) && defined(Y2[i]))
7643
.x0(i => X1[i])
7744
.y0(i => Y1[i])
7845
.x1(i => X2[i])
79-
.y1(i => Y2[i]))
80-
.call(titleGroup(L)))
46+
.y1(i => Y2[i])))
8147
.node();
8248
}
8349
}

src/marks/bar.js

Lines changed: 13 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,15 @@
11
import {create} from "d3";
22
import {filter} from "../defined.js";
3-
import {Mark, number, maybeColor, title, maybeNumber} from "../mark.js";
4-
import {Style, applyDirectStyles, applyIndirectStyles, applyTransform, impliedString, applyAttr} from "../style.js";
3+
import {Mark, number} from "../mark.js";
4+
import {applyDirectStyles, applyIndirectStyles, applyTransform, impliedString, applyAttr, applyChannelStyles} from "../style.js";
55
import {maybeStackX, maybeStackY} from "../transforms/stack.js";
66

7+
const defaults = {};
8+
79
export class AbstractBar extends Mark {
8-
constructor(
9-
data,
10-
channels,
11-
{
12-
title,
13-
fill,
14-
fillOpacity,
15-
stroke,
16-
strokeOpacity,
17-
inset = 0,
18-
insetTop = inset,
19-
insetRight = inset,
20-
insetBottom = inset,
21-
insetLeft = inset,
22-
rx,
23-
ry,
24-
...options
25-
} = {}
26-
) {
27-
const [vstroke, cstroke] = maybeColor(stroke, "none");
28-
const [vstrokeOpacity, cstrokeOpacity] = maybeNumber(strokeOpacity);
29-
const [vfill, cfill] = maybeColor(fill, cstroke === "none" ? "currentColor" : "none");
30-
const [vfillOpacity, cfillOpacity] = maybeNumber(fillOpacity);
31-
super(
32-
data,
33-
[
34-
...channels,
35-
{name: "title", value: title, optional: true},
36-
{name: "fill", value: vfill, scale: "color", optional: true},
37-
{name: "fillOpacity", value: vfillOpacity, scale: "opacity", optional: true},
38-
{name: "stroke", value: vstroke, scale: "color", optional: true},
39-
{name: "strokeOpacity", value: vstrokeOpacity, scale: "opacity", optional: true}
40-
],
41-
options
42-
);
43-
Style(this, {
44-
fill: cfill,
45-
fillOpacity: cfillOpacity,
46-
stroke: cstroke,
47-
strokeOpacity: cstrokeOpacity,
48-
...options
49-
});
10+
constructor(data, channels, options = {}) {
11+
super(data, channels, options, defaults);
12+
const {inset = 0, insetTop = inset, insetRight = inset, insetBottom = inset, insetLeft = inset, rx, ry} = options;
5013
this.insetTop = number(insetTop);
5114
this.insetRight = number(insetRight);
5215
this.insetBottom = number(insetBottom);
@@ -56,8 +19,7 @@ export class AbstractBar extends Mark {
5619
}
5720
render(I, scales, channels, dimensions) {
5821
const {rx, ry} = this;
59-
const {title: L, fill: F, fillOpacity: FO, stroke: S, strokeOpacity: SO} = channels;
60-
const index = filter(I, ...this._positions(channels), F, FO, S, SO);
22+
const index = filter(I, ...this._positions(channels));
6123
return create("svg:g")
6224
.call(applyIndirectStyles, this)
6325
.call(this._transform, scales)
@@ -69,13 +31,9 @@ export class AbstractBar extends Mark {
6931
.attr("width", this._width(scales, channels, dimensions))
7032
.attr("y", this._y(scales, channels, dimensions))
7133
.attr("height", this._height(scales, channels, dimensions))
72-
.call(applyAttr, "fill", F && (i => F[i]))
73-
.call(applyAttr, "fill-opacity", FO && (i => FO[i]))
74-
.call(applyAttr, "stroke", S && (i => S[i]))
75-
.call(applyAttr, "stroke-opacity", SO && (i => SO[i]))
7634
.call(applyAttr, "rx", rx)
7735
.call(applyAttr, "ry", ry)
78-
.call(title(L)))
36+
.call(applyChannelStyles, channels))
7937
.node();
8038
}
8139
_x(scales, {x: X}, {marginLeft}) {
@@ -99,7 +57,8 @@ export class AbstractBar extends Mark {
9957
}
10058

10159
export class BarX extends AbstractBar {
102-
constructor(data, {x1, x2, y, ...options} = {}) {
60+
constructor(data, options = {}) {
61+
const {x1, x2, y} = options;
10362
super(
10463
data,
10564
[
@@ -127,7 +86,8 @@ export class BarX extends AbstractBar {
12786
}
12887

12988
export class BarY extends AbstractBar {
130-
constructor(data, {x, y1, y2, ...options} = {}) {
89+
constructor(data, options = {}) {
90+
const {x, y1, y2} = options;
13191
super(
13292
data,
13393
[

src/marks/dot.js

Lines changed: 17 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,38 @@
11
import {create} from "d3";
22
import {filter, positive} from "../defined.js";
3-
import {Mark, identity, maybeColor, maybeNumber, maybeTuple, title} from "../mark.js";
4-
import {Style, applyDirectStyles, applyIndirectStyles, applyTransform, applyAttr} from "../style.js";
3+
import {Mark, identity, maybeNumber, maybeTuple} from "../mark.js";
4+
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform} from "../style.js";
5+
6+
const defaults = {
7+
fill: "none",
8+
stroke: "currentColor",
9+
strokeWidth: 1.5
10+
};
511

612
export class Dot extends Mark {
7-
constructor(
8-
data,
9-
{
10-
x,
11-
y,
12-
r,
13-
title,
14-
fill,
15-
fillOpacity,
16-
stroke,
17-
strokeOpacity,
18-
...options
19-
} = {}
20-
) {
13+
constructor(data, options = {}) {
14+
const {x, y, r} = options;
2115
const [vr, cr] = maybeNumber(r, 3);
22-
const [vfill, cfill] = maybeColor(fill, "none");
23-
const [vfillOpacity, cfillOpacity] = maybeNumber(fillOpacity);
24-
const [vstroke, cstroke] = maybeColor(stroke, cfill === "none" ? "currentColor" : "none");
25-
const [vstrokeOpacity, cstrokeOpacity] = maybeNumber(strokeOpacity);
2616
super(
2717
data,
2818
[
2919
{name: "x", value: x, scale: "x", optional: true},
3020
{name: "y", value: y, scale: "y", optional: true},
31-
{name: "r", value: vr, scale: "r", optional: true},
32-
{name: "title", value: title, optional: true},
33-
{name: "fill", value: vfill, scale: "color", optional: true},
34-
{name: "fillOpacity", value: vfillOpacity, scale: "opacity", optional: true},
35-
{name: "stroke", value: vstroke, scale: "color", optional: true},
36-
{name: "strokeOpacity", value: vstrokeOpacity, scale: "opacity", optional: true}
21+
{name: "r", value: vr, scale: "r", optional: true}
3722
],
38-
options
23+
options,
24+
defaults
3925
);
4026
this.r = cr;
41-
Style(this, {
42-
fill: cfill,
43-
fillOpacity: cfillOpacity,
44-
stroke: cstroke,
45-
strokeOpacity: cstrokeOpacity,
46-
strokeWidth: cstroke === "none" ? undefined : 1.5,
47-
...options
48-
});
4927
}
5028
render(
5129
I,
5230
{x, y},
53-
{x: X, y: Y, r: R, title: L, fill: F, fillOpacity: FO, stroke: S, strokeOpacity: SO},
31+
channels,
5432
{width, height, marginTop, marginRight, marginBottom, marginLeft}
5533
) {
56-
let index = filter(I, X, Y, F, FO, S, SO);
34+
const {x: X, y: Y, r: R} = channels;
35+
let index = filter(I, X, Y);
5736
if (R) index = index.filter(i => positive(R[i]));
5837
return create("svg:g")
5938
.call(applyIndirectStyles, this)
@@ -65,11 +44,7 @@ export class Dot extends Mark {
6544
.attr("cx", X ? i => X[i] : (marginLeft + width - marginRight) / 2)
6645
.attr("cy", Y ? i => Y[i] : (marginTop + height - marginBottom) / 2)
6746
.attr("r", R ? i => R[i] : this.r)
68-
.call(applyAttr, "fill", F && (i => F[i]))
69-
.call(applyAttr, "fill-opacity", FO && (i => FO[i]))
70-
.call(applyAttr, "stroke", S && (i => S[i]))
71-
.call(applyAttr, "stroke-opacity", SO && (i => SO[i]))
72-
.call(title(L)))
47+
.call(applyChannelStyles, channels))
7348
.node();
7449
}
7550
}

0 commit comments

Comments
 (0)