From 40e1374c84374667f15d3ddada043a481d88c9dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Wed, 13 Jan 2021 18:51:34 +0100 Subject: [PATCH] complete revision of the facets' grids TODO: make the grid the *first* layer of the facet (rather than the last). --- src/axes.js | 14 ++++--- src/axis.js | 109 +++++++++++++++++++++++++++++++-------------------- src/facet.js | 18 +++++---- src/plot.js | 5 +++ 4 files changed, 89 insertions(+), 57 deletions(-) diff --git a/src/axes.js b/src/axes.js index dd38f2dfa2..e1a5f7907a 100644 --- a/src/axes.js +++ b/src/axes.js @@ -1,8 +1,8 @@ -import {AxisX, AxisY} from "./axis.js"; +import {AxisX, AxisY, GridX, GridY} from "./axis.js"; export function Axes( {x: xScale, y: yScale, fx: fxScale, fy: fyScale}, - {x = {}, y = {}, fx = {}, fy = {}, grid, facet: {grid: facetGrid} = {}} = {} + {x = {}, y = {}, fx = {}, fy = {}, grid = false} = {} ) { let {axis: xAxis = true} = x; let {axis: yAxis = true} = y; @@ -13,10 +13,12 @@ export function Axes( if (fxAxis === true) fxAxis = xAxis === "bottom" ? "top" : "bottom"; if (fyAxis === true) fyAxis = yAxis === "left" ? "right" : "left"; return { - ...xScale && xAxis && {x: new AxisX({grid, ...x, axis: xAxis})}, - ...yScale && yAxis && {y: new AxisY({grid, ...y, axis: yAxis})}, - ...fxScale && fxAxis && {fx: new AxisX({name: "fx", grid: facetGrid, ...fx, axis: fxAxis})}, - ...fyScale && fyAxis && {fy: new AxisY({name: "fy", grid: facetGrid, ...fy, axis: fyAxis})} + ...xScale && xAxis && {x: new AxisX({...x, axis: xAxis})}, + ...yScale && yAxis && {y: new AxisY({...y, axis: yAxis})}, + ...fxScale && fxAxis && {fx: new AxisX({name: "fx", ...fx, axis: fxAxis})}, + ...fyScale && fyAxis && {fy: new AxisY({name: "fy", ...fy, axis: fyAxis})}, + ...(grid || x.grid) && xScale && xAxis && {gridx: new GridX(x)}, + ...(grid || y.grid) && yScale && yAxis && {gridy: new GridY(y)} }; } diff --git a/src/axis.js b/src/axis.js index 9940eb85f3..e4ace9acc6 100644 --- a/src/axis.js +++ b/src/axis.js @@ -10,7 +10,6 @@ export class AxisX { tickSize = name === "fx" ? 0 : 6, tickPadding = tickSize === 0 ? 9 : 3, tickFormat, - grid, label, labelAnchor, labelOffset @@ -22,14 +21,13 @@ export class AxisX { this.tickSize = tickSize; this.tickPadding = tickPadding; this.tickFormat = tickFormat; - this.grid = grid; this.label = label; this.labelAnchor = labelAnchor; this.labelOffset = labelOffset; } render( index, - {[this.name]: x, fy}, + {[this.name]: x}, channels, { width, @@ -50,7 +48,6 @@ export class AxisX { tickSize, tickPadding, tickFormat, - grid, label, labelAnchor, labelOffset @@ -70,9 +67,6 @@ export class AxisX { .attr("font-size", null) .attr("font-family", null) .call(g => g.select(".domain").remove()) - .call(!grid ? () => {} - : fy ? gridFacetX(fy, -ty) - : gridX(offsetSign * (marginBottom + marginTop - height))) .call(label == null ? () => {} : g => g.append("text") .attr("fill", "currentColor") .attr("transform", `translate(${ @@ -116,7 +110,7 @@ export class AxisY { } render( index, - {[this.name]: y, fx}, + {[this.name]: y}, channels, { width, @@ -135,7 +129,6 @@ export class AxisY { tickSize, tickPadding, tickFormat, - grid, label, labelAnchor, labelOffset @@ -155,9 +148,6 @@ export class AxisY { .attr("font-size", null) .attr("font-family", null) .call(g => g.select(".domain").remove()) - .call(!grid ? () => {} - : fx ? gridFacetY(fx, -tx) - : gridY(offsetSign * (marginLeft + marginRight - width))) .call(label == null ? () => {} : g => g.append("text") .attr("fill", "currentColor") .attr("transform", `translate(${labelOffset * offsetSign},${ @@ -176,40 +166,73 @@ export class AxisY { } } -function round(scale) { - return scale.interpolate // TODO round band and point scales? - ? scale.copy().interpolate(interpolateRound) - : scale; -} - -function gridX(y2) { - return g => g.selectAll(".tick line") - .clone(true) - .attr("stroke-opacity", 0.1) - .attr("y2", y2); +export class GridX { + constructor({ + name = "x", + ticks + } = {}) { + this.name = name; + this.ticks = ticks; + } + render( + index, + {[this.name]: x}, + channels, + { + height, + marginTop, + marginBottom + } + ) { + const { ticks } = this; + return create("svg:g") + .attr("transform", `translate(0,${marginTop})`) + .attr("stroke-opacity", 0.1) + .call(axisTop(round(x)) + .ticks(Array.isArray(ticks) ? null : ticks) + .tickValues(Array.isArray(ticks) ? ticks : null) + .tickSize(marginTop + marginBottom - height)) + .call(g => g.select(".domain").remove()) + .call(g => g.selectAll(".tick text").remove()) + .node(); + } } -function gridY(x2) { - return g => g.selectAll(".tick line") - .clone(true) - .attr("stroke-opacity", 0.1) - .attr("x2", x2); +export class GridY { + constructor({ + name = "y", + ticks + } = {}) { + this.name = name; + this.ticks = ticks; + } + render( + index, + {[this.name]: y}, + channels, + { + width, + marginLeft, + marginRight + } + ) { + const { ticks } = this; + return create("svg:g") + .attr("transform", `translate(${marginLeft},0)`) + .attr("stroke-opacity", 0.1) + .call(axisLeft(round(y)) + .ticks(Array.isArray(ticks) ? null : ticks) + .tickValues(Array.isArray(ticks) ? ticks : null) + .tickSize(marginLeft + marginRight - width)) + .call(g => g.select(".domain").remove()) + .call(g => g.selectAll(".tick text").remove()) + .node(); + } } -function gridFacetX(fy, ty) { - const dy = fy.bandwidth(); - return g => g.selectAll(".tick") - .append("path") - .attr("stroke", "currentColor") - .attr("stroke-opacity", 0.1) - .attr("d", fy.domain().map(v => `M0,${fy(v) + ty}v${dy}`).join("")); +function round(scale) { + return scale.interpolate // TODO round band and point scales? + ? scale.copy().interpolate(interpolateRound) + : scale; } -function gridFacetY(fx, tx) { - const dx = fx.bandwidth(); - return g => g.selectAll(".tick") - .append("path") - .attr("stroke", "currentColor") - .attr("stroke-opacity", 0.1) - .attr("d", fx.domain().map(v => `M${fx(v) + tx},0h${dx}`).join("")); -} diff --git a/src/facet.js b/src/facet.js index fdccb60f5e..7f120f1268 100644 --- a/src/facet.js +++ b/src/facet.js @@ -105,14 +105,16 @@ class Facet extends Mark { .attr("transform", facetTranslate(fx, fy)) .each(function(key) { const marksFacetIndex = marksIndexByFacet.get(key) || marksIndex; - for (let i = 0; i < marks.length; ++i) { - const node = marks[i].render( - marksFacetIndex[i], - scales, - marksChannels[i], - subdimensions - ); - if (node != null) this.appendChild(node); + if (marksIndexByFacet.has(key)) { + for (let i = 0; i < marks.length; ++i) { + const node = marks[i].render( + marksFacetIndex[i], + scales, + marksChannels[i], + subdimensions + ); + if (node != null) this.appendChild(node); + } } })) .node(); diff --git a/src/plot.js b/src/plot.js index 276807d095..0d6d244ad6 100644 --- a/src/plot.js +++ b/src/plot.js @@ -67,6 +67,11 @@ export function plot(options = {}) { // When faceting, render axes for fx and fy instead of x and y. const x = facet !== undefined && scales.fx ? "fx" : "x"; const y = facet !== undefined && scales.fy ? "fy" : "y"; + + const f = facet !== undefined ? marks[0].marks : marks; + if (axes.gridx) (x === "fx" ? f : marks).push(axes.gridx); // should be unshift + if (axes.gridy) (y === "fy" ? f : marks).push(axes.gridy); + if (axes[x]) marks.unshift(axes[x]); if (axes[y]) marks.unshift(axes[y]);