Skip to content

Commit 5c5b868

Browse files
Filmbostock
andauthored
empty facets (#516)
* empty facets (subset of #332) * test plots * filter facetKeys; pull domain up * where via range * update snapshots; remove unused import * call domain once * remove unused marksIndex Co-authored-by: Mike Bostock <[email protected]>
1 parent 1c56369 commit 5c5b868

File tree

6 files changed

+50
-395
lines changed

6 files changed

+50
-395
lines changed

src/axis.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {axisTop, axisBottom, axisRight, axisLeft, create, format, utcFormat} from "d3";
22
import {formatIsoDate} from "./format.js";
3-
import {boolean, number, string, keyword, maybeKeyword, constant, isTemporal} from "./mark.js";
3+
import {boolean, take, number, string, keyword, maybeKeyword, constant, isTemporal} from "./mark.js";
44

55
export class AxisX {
66
constructor({
@@ -67,7 +67,7 @@ export class AxisX {
6767
.attr("font-family", null)
6868
.call(!line ? g => g.select(".domain").remove() : () => {})
6969
.call(!grid ? () => {}
70-
: fy ? gridFacetX(fy, -ty)
70+
: fy ? gridFacetX(index, fy, -ty)
7171
: gridX(offsetSign * (marginBottom + marginTop - height)))
7272
.call(!label ? () => {} : g => g.append("text")
7373
.attr("fill", "currentColor")
@@ -148,7 +148,7 @@ export class AxisY {
148148
.attr("font-family", null)
149149
.call(!line ? g => g.select(".domain").remove() : () => {})
150150
.call(!grid ? () => {}
151-
: fx ? gridFacetY(fx, -tx)
151+
: fx ? gridFacetY(index, fx, -tx)
152152
: gridY(offsetSign * (marginLeft + marginRight - width)))
153153
.call(!label ? () => {} : g => g.append("text")
154154
.attr("fill", "currentColor")
@@ -182,22 +182,24 @@ function gridY(x2) {
182182
.attr("x2", x2);
183183
}
184184

185-
function gridFacetX(fy, ty) {
185+
function gridFacetX(index, fy, ty) {
186186
const dy = fy.bandwidth();
187+
const domain = fy.domain();
187188
return g => g.selectAll(".tick")
188189
.append("path")
189190
.attr("stroke", "currentColor")
190191
.attr("stroke-opacity", 0.1)
191-
.attr("d", fy.domain().map(v => `M0,${fy(v) + ty}v${dy}`).join(""));
192+
.attr("d", (index ? take(domain, index) : domain).map(v => `M0,${fy(v) + ty}v${dy}`).join(""));
192193
}
193194

194-
function gridFacetY(fx, tx) {
195+
function gridFacetY(index, fx, tx) {
195196
const dx = fx.bandwidth();
197+
const domain = fx.domain();
196198
return g => g.selectAll(".tick")
197199
.append("path")
198200
.attr("stroke", "currentColor")
199201
.attr("stroke-opacity", 0.1)
200-
.attr("d", fx.domain().map(v => `M${fx(v) + tx},0h${dx}`).join(""));
202+
.attr("d", (index ? take(domain, index) : domain).map(v => `M${fx(v) + tx},0h${dx}`).join(""));
201203
}
202204

203205
function createAxis(axis, scale, {ticks, tickSize, tickPadding, tickFormat}) {

src/facet.js

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {cross, difference, groups, InternMap} from "d3";
22
import {create} from "d3";
3-
import {Mark, first, second, markify} from "./mark.js";
3+
import {Mark, first, second, markify, where} from "./mark.js";
44
import {applyScales} from "./scales.js";
55
import {filterStyles} from "./style.js";
66

@@ -24,7 +24,6 @@ class Facet extends Mark {
2424
this.marks = marks.flat(Infinity).map(markify);
2525
// The following fields are set by initialize:
2626
this.marksChannels = undefined; // array of mark channels
27-
this.marksIndex = undefined; // array of mark indexes (for non-faceted marks)
2827
this.marksIndexByFacet = undefined; // map from facet key to array of mark indexes
2928
}
3029
initialize() {
@@ -34,7 +33,6 @@ class Facet extends Mark {
3433
const facetsIndex = Array.from(facets, second);
3534
const subchannels = [];
3635
const marksChannels = this.marksChannels = [];
37-
const marksIndex = this.marksIndex = new Array(this.marks.length);
3836
const marksIndexByFacet = this.marksIndexByFacet = facetMap(channels);
3937
for (const facetKey of facetsKeys) {
4038
marksIndexByFacet.set(facetKey, new Array(this.marks.length));
@@ -57,12 +55,10 @@ class Facet extends Mark {
5755
for (let j = 0; j < facetsKeys.length; ++j) {
5856
marksIndexByFacet.get(facetsKeys[j])[i] = I[j];
5957
}
60-
marksIndex[i] = []; // implicit empty index for sparse facets
6158
} else {
6259
for (let j = 0; j < facetsKeys.length; ++j) {
6360
marksIndexByFacet.get(facetsKeys[j])[i] = I;
6461
}
65-
marksIndex[i] = I;
6662
}
6763
}
6864
for (const [, channel] of markChannels) {
@@ -73,44 +69,54 @@ class Facet extends Mark {
7369
return {index, channels: [...channels, ...subchannels]};
7470
}
7571
render(I, scales, channels, dimensions, axes) {
76-
const {marks, marksChannels, marksIndex, marksIndexByFacet} = this;
72+
const {marks, marksChannels, marksIndexByFacet} = this;
7773
const {fx, fy} = scales;
74+
const fyDomain = fy && fy.domain();
75+
const fxDomain = fx && fx.domain();
7876
const fyMargins = fy && {marginTop: 0, marginBottom: 0, height: fy.bandwidth()};
7977
const fxMargins = fx && {marginRight: 0, marginLeft: 0, width: fx.bandwidth()};
8078
const subdimensions = {...dimensions, ...fxMargins, ...fyMargins};
8179
const marksValues = marksChannels.map(channels => applyScales(channels, scales));
8280
return create("svg:g")
8381
.call(g => {
8482
if (fy && axes.y) {
85-
const domain = fy.domain();
8683
const axis1 = axes.y, axis2 = nolabel(axis1);
87-
const j = axis1.labelAnchor === "bottom" ? domain.length - 1 : axis1.labelAnchor === "center" ? domain.length >> 1 : 0;
84+
const j = axis1.labelAnchor === "bottom" ? fyDomain.length - 1 : axis1.labelAnchor === "center" ? fyDomain.length >> 1 : 0;
8885
const fyDimensions = {...dimensions, ...fyMargins};
8986
g.selectAll()
90-
.data(domain)
87+
.data(fyDomain)
9188
.join("g")
9289
.attr("transform", ky => `translate(0,${fy(ky)})`)
93-
.append((_, i) => (i === j ? axis1 : axis2).render(null, scales, null, fyDimensions));
90+
.append((ky, i) => (i === j ? axis1 : axis2).render(
91+
fx && where(fxDomain, kx => marksIndexByFacet.has([kx, ky])),
92+
scales,
93+
null,
94+
fyDimensions
95+
));
9496
}
9597
if (fx && axes.x) {
96-
const domain = fx.domain();
9798
const axis1 = axes.x, axis2 = nolabel(axis1);
98-
const j = axis1.labelAnchor === "right" ? domain.length - 1 : axis1.labelAnchor === "center" ? domain.length >> 1 : 0;
99+
const j = axis1.labelAnchor === "right" ? fxDomain.length - 1 : axis1.labelAnchor === "center" ? fxDomain.length >> 1 : 0;
99100
const {marginLeft, marginRight} = dimensions;
100101
const fxDimensions = {...dimensions, ...fxMargins, labelMarginLeft: marginLeft, labelMarginRight: marginRight};
101102
g.selectAll()
102-
.data(domain)
103+
.data(fxDomain)
103104
.join("g")
104105
.attr("transform", kx => `translate(${fx(kx)},0)`)
105-
.append((_, i) => (i === j ? axis1 : axis2).render(null, scales, null, fxDimensions));
106+
.append((kx, i) => (i === j ? axis1 : axis2).render(
107+
fy && where(fyDomain, ky => marksIndexByFacet.has([kx, ky])),
108+
scales,
109+
null,
110+
fxDimensions
111+
));
106112
}
107113
})
108114
.call(g => g.selectAll()
109-
.data(facetKeys(scales))
115+
.data(facetKeys(scales).filter(marksIndexByFacet.has, marksIndexByFacet))
110116
.join("g")
111117
.attr("transform", facetTranslate(fx, fy))
112118
.each(function(key) {
113-
const marksFacetIndex = marksIndexByFacet.get(key) || marksIndex;
119+
const marksFacetIndex = marksIndexByFacet.get(key);
114120
for (let i = 0; i < marks.length; ++i) {
115121
const values = marksValues[i];
116122
const index = filterStyles(marksFacetIndex[i], values);

src/mark.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,11 @@ export function range(data) {
230230
return Uint32Array.from(data, indexOf);
231231
}
232232

233+
// Returns a filtered range of data given the test function.
234+
export function where(data, test) {
235+
return range(data).filter(i => test(data[i], i, data));
236+
}
237+
233238
// Returns an array [values[index[0]], values[index[1]], …].
234239
export function take(values, index) {
235240
return Array.from(index, i => values[i]);

test/output/penguinCulmen.svg

Lines changed: 7 additions & 12 deletions
Loading

0 commit comments

Comments
 (0)