From 3dc9b012a7536a7d555742352a9936af6b508bfe Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 31 Dec 2021 09:46:27 -0800 Subject: [PATCH 1/2] =?UTF-8?q?=E2=80=9Cmid=E2=80=9D=20interval=20transfor?= =?UTF-8?q?m?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/marks/dot.js | 8 ++++---- src/marks/image.js | 4 ++-- src/marks/text.js | 8 ++++---- src/transforms/interval.js | 38 +++++++++++++++++++++++++++++++------- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/marks/dot.js b/src/marks/dot.js index 78c969e7e2..ab4d0c1932 100644 --- a/src/marks/dot.js +++ b/src/marks/dot.js @@ -1,6 +1,6 @@ import {create} from "d3"; import {filter, positive} from "../defined.js"; -import {maybeIntervalX, maybeIntervalY} from "../transforms/interval.js"; +import {maybeIntervalMidX, maybeIntervalMidY} from "../transforms/interval.js"; import {Mark, identity, maybeNumber, maybeTuple} from "../mark.js"; import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, offset} from "../style.js"; @@ -53,13 +53,13 @@ export class Dot extends Mark { export function dot(data, {x, y, ...options} = {}) { ([x, y] = maybeTuple(x, y)); - return new Dot(data, maybeIntervalY(maybeIntervalX({...options, x, y}))); + return new Dot(data, maybeIntervalMidY(maybeIntervalMidX({...options, x, y}))); } export function dotX(data, {x = identity, ...options} = {}) { - return new Dot(data, maybeIntervalY({...options, x})); + return new Dot(data, maybeIntervalMidY({...options, x})); } export function dotY(data, {y = identity, ...options} = {}) { - return new Dot(data, maybeIntervalX({...options, y})); + return new Dot(data, maybeIntervalMidX({...options, y})); } diff --git a/src/marks/image.js b/src/marks/image.js index 0f1ad6eb7d..def9c2a212 100644 --- a/src/marks/image.js +++ b/src/marks/image.js @@ -1,6 +1,6 @@ import {create} from "d3"; import {filter, positive} from "../defined.js"; -import {maybeIntervalX, maybeIntervalY} from "../transforms/interval.js"; +import {maybeIntervalMidX, maybeIntervalMidY} from "../transforms/interval.js"; import {Mark, maybeNumber, maybeTuple, string} from "../mark.js"; import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, applyAttr, offset, impliedString} from "../style.js"; @@ -90,5 +90,5 @@ export class Image extends Mark { export function image(data, {x, y, ...options} = {}) { ([x, y] = maybeTuple(x, y)); - return new Image(data, maybeIntervalY(maybeIntervalX({...options, x, y}))); + return new Image(data, maybeIntervalMidY(maybeIntervalMidX({...options, x, y}))); } diff --git a/src/marks/text.js b/src/marks/text.js index dca286479f..d143845c22 100644 --- a/src/marks/text.js +++ b/src/marks/text.js @@ -2,7 +2,7 @@ import {create} from "d3"; import {filter, nonempty} from "../defined.js"; import {Mark, indexOf, identity, string, maybeNumber, maybeTuple, numberChannel} from "../mark.js"; import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyAttr, applyTransform, offset} from "../style.js"; -import {maybeIntervalX, maybeIntervalY} from "../transforms/interval.js"; +import {maybeIntervalMidX, maybeIntervalMidY} from "../transforms/interval.js"; const defaults = { strokeLinejoin: "round" @@ -80,15 +80,15 @@ export class Text extends Mark { export function text(data, {x, y, ...options} = {}) { ([x, y] = maybeTuple(x, y)); - return new Text(data, maybeIntervalY(maybeIntervalX({...options, x, y}))); + return new Text(data, maybeIntervalMidY(maybeIntervalMidX({...options, x, y}))); } export function textX(data, {x = identity, ...options} = {}) { - return new Text(data, maybeIntervalY({...options, x})); + return new Text(data, maybeIntervalMidY({...options, x})); } export function textY(data, {y = identity, ...options} = {}) { - return new Text(data, maybeIntervalX({...options, y})); + return new Text(data, maybeIntervalMidX({...options, y})); } function applyIndirectTextStyles(selection, mark) { diff --git a/src/transforms/interval.js b/src/transforms/interval.js index 0674ea4bb7..915c545ab6 100644 --- a/src/transforms/interval.js +++ b/src/transforms/interval.js @@ -27,14 +27,34 @@ function maybeIntervalK(k, maybeInsetK, options) { const {[k]: v, [`${k}1`]: v1, [`${k}2`]: v2} = options; const {value, interval} = maybeIntervalValue(v, options); if (value == null || interval == null) return options; - let V1; - const tv1 = data => V1 || (V1 = valueof(data, value).map(v => interval.floor(v))); + let D1, V1; const label = labelof(v); + function transform(data) { + if (V1 !== undefined && data === D1) return V1; // memoize + return V1 = valueof(D1 = data, value).map(v => interval.floor(v)); + } + return maybeInsetK({ + ...options, + [k]: undefined, + [`${k}1`]: v1 === undefined ? {transform, label} : v1, + [`${k}2`]: v2 === undefined ? {transform: data => transform(data).map(v => interval.offset(v)), label} : v2 + }); +} + +function maybeIntervalMidK(k, maybeInsetK, options) { + const {[k]: v} = options; + const {value, interval} = maybeIntervalValue(v, options); + if (value == null || interval == null) return options; return maybeInsetK({ ...options, - [k]: {transform: (data) => tv1(data).map(v => mid(interval.offset(v), v)), label}, - [`${k}1`]: v1 === undefined ? {transform: tv1, label} : v1, - [`${k}2`]: v2 === undefined ? {transform: (data) => tv1(data).map(v => interval.offset(v)), label} : v2 + [k]: { + label: labelof(v), + transform: data => valueof(data, value).map(v => { + const a = interval.floor(v); + const b = interval.offset(a); + return a instanceof Date ? new Date((+a + +b) / 2) : (a + b) / 2; + }) + } }); } @@ -46,6 +66,10 @@ export function maybeIntervalY(options = {}) { return maybeIntervalK("y", maybeInsetY, options); } -function mid(a, b) { - return a instanceof Date ? new Date((+a + +b) / 2) : (a + b) / 2; +export function maybeIntervalMidX(options = {}) { + return maybeIntervalMidK("x", maybeInsetX, options); +} + +export function maybeIntervalMidY(options = {}) { + return maybeIntervalMidK("y", maybeInsetY, options); } From ed2610807ef808ebcf20668274c05d2cea5eb808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Sat, 1 Jan 2022 16:35:38 +0100 Subject: [PATCH 2/2] optimize mid computation --- src/transforms/interval.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/transforms/interval.js b/src/transforms/interval.js index 915c545ab6..17c5f0ef07 100644 --- a/src/transforms/interval.js +++ b/src/transforms/interval.js @@ -9,9 +9,19 @@ function maybeInterval(interval) { if (typeof interval === "number") { const n = interval; // Note: this offset doesn’t support the optional step argument for simplicity. - interval = {floor: d => n * Math.floor(d / n), offset: d => d + n}; + interval = { + floor: d => n * Math.floor(d / n), + mid: d => n * (Math.floor(d / n) + 0.5), + offset: d => d + n + }; } if (typeof interval.floor !== "function" || typeof interval.offset !== "function") throw new Error("invalid interval"); + if (typeof interval.mid !== "function") { + interval = { + ...interval, + mid: x => (x = interval.floor(x), new Date((+x + +interval.offset(x)) / 2)) + }; + } return interval; } @@ -49,11 +59,7 @@ function maybeIntervalMidK(k, maybeInsetK, options) { ...options, [k]: { label: labelof(v), - transform: data => valueof(data, value).map(v => { - const a = interval.floor(v); - const b = interval.offset(a); - return a instanceof Date ? new Date((+a + +b) / 2) : (a + b) / 2; - }) + transform: data => valueof(data, value).map(interval.mid) } }); }