Skip to content

Commit

Permalink
extend interval to dot, text, image marks
Browse files Browse the repository at this point in the history
  • Loading branch information
Fil authored and mbostock committed May 29, 2022
1 parent ddbe39b commit b1b2d67
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 12 deletions.
7 changes: 4 additions & 3 deletions src/marks/dot.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {identity, maybeFrameAnchor, maybeNumberChannel, maybeTuple} from "../opt
import {Mark} from "../plot.js";
import {applyChannelStyles, applyDirectStyles, applyFrameAnchor, applyIndirectStyles, applyTransform, offset} from "../style.js";
import {maybeSymbolChannel} from "../symbols.js";
import {maybeIntervalMidX, maybeIntervalMidY} from "../transforms/interval.js";

const defaults = {
ariaLabel: "dot",
Expand Down Expand Up @@ -91,15 +92,15 @@ export class Dot extends Mark {

export function dot(data, {x, y, ...options} = {}) {
if (options.frameAnchor === undefined) ([x, y] = maybeTuple(x, y));
return new Dot(data, {...options, x, y});
return new Dot(data, maybeIntervalMidY(maybeIntervalMidX({...options, x, y})));
}

export function dotX(data, {x = identity, ...options} = {}) {
return new Dot(data, {...options, x});
return new Dot(data, maybeIntervalMidY({...options, x}));
}

export function dotY(data, {y = identity, ...options} = {}) {
return new Dot(data, {...options, y});
return new Dot(data, maybeIntervalMidX({...options, y}));
}

export function circle(data, options) {
Expand Down
3 changes: 2 additions & 1 deletion src/marks/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {positive} from "../defined.js";
import {maybeFrameAnchor, maybeNumberChannel, maybeTuple, string} from "../options.js";
import {Mark} from "../plot.js";
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, applyAttr, offset, impliedString, applyFrameAnchor} from "../style.js";
import {maybeIntervalMidX, maybeIntervalMidY} from "../transforms/interval.js";

const defaults = {
ariaLabel: "image",
Expand Down Expand Up @@ -85,5 +86,5 @@ export class Image extends Mark {

export function image(data, {x, y, ...options} = {}) {
if (options.frameAnchor === undefined) ([x, y] = maybeTuple(x, y));
return new Image(data, {...options, x, y});
return new Image(data, maybeIntervalMidY(maybeIntervalMidX({...options, x, y})));
}
7 changes: 4 additions & 3 deletions src/marks/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {formatDefault} from "../format.js";
import {indexOf, identity, string, maybeNumberChannel, maybeTuple, numberChannel, isNumeric, isTemporal, keyword, maybeFrameAnchor, isTextual} from "../options.js";
import {Mark} from "../plot.js";
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyAttr, applyTransform, offset, impliedString, applyFrameAnchor} from "../style.js";
import {maybeIntervalMidX, maybeIntervalMidY} from "../transforms/interval.js";

const defaults = {
ariaLabel: "text",
Expand Down Expand Up @@ -118,15 +119,15 @@ function applyMultilineText(selection, {monospace, lineAnchor, lineHeight, lineW

export function text(data, {x, y, ...options} = {}) {
if (options.frameAnchor === undefined) ([x, y] = maybeTuple(x, y));
return new Text(data, {...options, x, y});
return new Text(data, maybeIntervalMidY(maybeIntervalMidX({...options, x, y})));
}

export function textX(data, {x = identity, ...options} = {}) {
return new Text(data, {...options, x});
return new Text(data, maybeIntervalMidY({...options, x}));
}

export function textY(data, {y = identity, ...options} = {}) {
return new Text(data, {...options, y});
return new Text(data, maybeIntervalMidX({...options, y}));
}

function applyIndirectTextStyles(selection, mark, T) {
Expand Down
40 changes: 35 additions & 5 deletions src/transforms/interval.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {range} from "d3";
import {labelof, maybeValue, valueof} from "../options.js";
import {isTemporal, labelof, maybeValue, valueof} from "../options.js";
import {maybeInsetX, maybeInsetY} from "./inset.js";

// TODO Allow the interval to be specified as a string, e.g. “day” or “hour”?
Expand Down Expand Up @@ -43,13 +43,35 @@ function maybeIntervalK(k, maybeInsetK, options, trivial) {
[`${k}2`]: v2 === undefined ? kv : v2
};
}
let V1;
const tv1 = data => V1 || (V1 = valueof(data, value).map(v => interval.floor(v)));
let D1, V1;
function transform(data) {
if (V1 !== undefined && data === D1) return V1; // memoize
return V1 = Array.from(valueof(D1 = data, value), v => interval.floor(v));
}
return maybeInsetK({
...options,
[k]: undefined,
[`${k}1`]: v1 === undefined ? {transform: tv1, label} : v1,
[`${k}2`]: v2 === undefined ? {transform: data => tv1(data).map(v => interval.offset(v)), label} : v2
[`${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]: {
label: labelof(v),
transform: data => {
const V1 = Array.from(valueof(data, value), v => interval.floor(v));
const V2 = V1.map(v => interval.offset(v));
return V1.map(isTemporal(V1)
? (v1, v2) => v1 == null || isNaN(v1 = +v1) || (v2 = V2[v2], v2 == null) || isNaN(v2 = +v2) ? undefined : new Date((v1 + v2) / 2)
: (v1, v2) => v1 == null || (v2 = V2[v2], v2 == null) ? NaN : (+v1 + +v2) / 2);
}
}
});
}

Expand All @@ -68,3 +90,11 @@ export function maybeIntervalX(options = {}) {
export function maybeIntervalY(options = {}) {
return maybeIntervalK("y", maybeInsetY, options);
}

export function maybeIntervalMidX(options = {}) {
return maybeIntervalMidK("x", maybeInsetX, options);
}

export function maybeIntervalMidY(options = {}) {
return maybeIntervalMidK("y", maybeInsetY, options);
}
32 changes: 32 additions & 0 deletions test/output/athletesBirthdays.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions test/plots/athletes-birthdays.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as Plot from "@observablehq/plot";
import * as d3 from "d3";

export default async function() {
const athletes = await d3.csv("data/athletes.csv", d3.autoType)
.then(data => data.filter(d => d.date_of_birth.getUTCFullYear() === 1990));
return Plot.plot({
marginRight: 20,
marks: [
Plot.rectX(
athletes,
Plot.groupY(
{ x: "count" },
{
y: (d) => d3.utcMonth.floor(d["date_of_birth"]),
interval: d3.utcMonth,
insetTop: 4,
insetBottom: 4,
stroke: "black",
rx: 8
}
)
),
Plot.textX(
athletes,
Plot.groupY(
{ x: "count", text: "count" },
{
y: d => d3.utcMonth.floor(d["date_of_birth"]),
interval: d3.utcMonth,
dx: 14
}
)
),
Plot.textX(
athletes,
Plot.groupY(
{ x: data => data.length / 2, text: ([d]) => Plot.formatMonth()(d.getUTCMonth()) },
{
y: d => d3.utcMonth.floor(d["date_of_birth"]),
interval: d3.utcMonth,
text: d => d["date_of_birth"]
}
)
)
],
x: { axis: null },
y: { reverse: true, axis: null }
});
}
1 change: 1 addition & 0 deletions test/plots/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export {default as aaplVolume} from "./aapl-volume.js";
export {default as aaplVolumeRect} from "./aapl-volume-rect.js";
export {default as anscombeQuartet} from "./anscombe-quartet.js";
export {default as athletesBinsColors} from "./athletes-bins-colors.js";
export {default as athletesBirthdays} from "./athletes-birthdays.js";
export {default as athletesHeightWeight} from "./athletes-height-weight.js";
export {default as athletesHeightWeightBin} from "./athletes-height-weight-bin.js";
export {default as athletesHeightWeightBinStroke} from "./athletes-height-weight-bin-stroke.js";
Expand Down

0 comments on commit b1b2d67

Please sign in to comment.