From d5359e805fe42e249b723f6e4a541206662d7ba8 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Wed, 28 Jun 2023 15:39:54 -0500 Subject: [PATCH] paint --- src/index.js | 1 + src/options.js | 5 +++++ src/paint.js | 13 +++++++++++++ src/style.js | 24 +++++++++++------------- test/plots/penguin-species.ts | 10 ++++++++++ 5 files changed, 40 insertions(+), 13 deletions(-) create mode 100644 src/paint.js diff --git a/src/index.js b/src/index.js index fec7e243ec..b6bb57cc63 100644 --- a/src/index.js +++ b/src/index.js @@ -45,3 +45,4 @@ export {pointer, pointerX, pointerY} from "./interactions/pointer.js"; export {formatIsoDate, formatWeekday, formatMonth} from "./format.js"; export {scale} from "./scales.js"; export {legend} from "./legends.js"; +export {linearGradient} from "./paint.js"; diff --git a/src/options.js b/src/options.js index f70a118ef8..ff6a23189d 100644 --- a/src/options.js +++ b/src/options.js @@ -442,12 +442,17 @@ export function isEvery(values, is) { return every; } +export function isPaint(value) { + return typeof value?.paint === "function"; +} + // Mostly relies on d3-color, with a few extra color keywords. Currently this // strictly requires that the value be a string; we might want to apply string // coercion here, though note that d3-color instances would need to support // valueOf to work correctly with InternMap. // https://www.w3.org/TR/SVG11/painting.html#SpecifyingPaint export function isColor(value) { + if (isPaint(value)) return true; if (typeof value !== "string") return false; value = value.toLowerCase().trim(); return ( diff --git a/src/paint.js b/src/paint.js new file mode 100644 index 0000000000..b23faf9abe --- /dev/null +++ b/src/paint.js @@ -0,0 +1,13 @@ +import {create} from "./context.js"; + +export function linearGradient() { + return { + paint(context) { + const gradient = create("svg:linearGradient", context).attr("gradientTransform", "rotate(90)"); + gradient.append("stop").attr("offset", "5%").attr("stop-color", "purple"); + gradient.append("stop").attr("offset", "75%").attr("stop-color", "red"); + gradient.append("stop").attr("offset", "100%").attr("stop-color", "gold"); + return gradient.node(); + } + }; +} diff --git a/src/style.js b/src/style.js index b3e046cc91..cda28d2742 100644 --- a/src/style.js +++ b/src/style.js @@ -2,17 +2,8 @@ import {geoPath, group, namespaces} from "d3"; import {create} from "./context.js"; import {defined, nonempty} from "./defined.js"; import {formatDefault} from "./format.js"; -import { - string, - number, - maybeColorChannel, - maybeNumberChannel, - maybeKeyword, - isNoneish, - isNone, - isRound, - keyof -} from "./options.js"; +import {isNone, isNoneish, isPaint, isRound} from "./options.js"; +import {keyof, maybeColorChannel, maybeKeyword, maybeNumberChannel, number, string} from "./options.js"; import {warn} from "./warnings.js"; export const offset = (typeof window !== "undefined" ? window.devicePixelRatio > 1 : typeof it === "undefined") ? 0 : 0.5; // prettier-ignore @@ -115,7 +106,7 @@ export function styles( // Some marks don’t support fill (e.g., tick and rule). if (defaultFill !== null) { - mark.fill = impliedString(cfill, "currentColor"); + mark.fill = isPaint(cfill) ? cfill : impliedString(cfill, "currentColor"); mark.fillOpacity = impliedNumber(cfillOpacity, 1); } @@ -364,7 +355,14 @@ function applyClip(selection, mark, dimensions, context) { // Note: may mutate selection.node! export function applyIndirectStyles(selection, mark, dimensions, context) { applyClip(selection, mark, dimensions, context); - applyAttr(selection, "fill", mark.fill); + if (isPaint(mark.fill)) { + const paint = mark.fill.paint(context); + paint.setAttribute("id", "test-paint"); + context.ownerSVGElement.append(paint); + selection.attr("fill", "url(#test-paint)"); + } else { + applyAttr(selection, "fill", mark.fill); + } applyAttr(selection, "fill-opacity", mark.fillOpacity); applyAttr(selection, "stroke", mark.stroke); applyAttr(selection, "stroke-width", mark.strokeWidth); diff --git a/test/plots/penguin-species.ts b/test/plots/penguin-species.ts index c7eacdde31..993a5ece59 100644 --- a/test/plots/penguin-species.ts +++ b/test/plots/penguin-species.ts @@ -29,6 +29,16 @@ export async function penguinSpeciesCheysson() { }); } +export async function penguinSpeciesPaint() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + return Plot.plot({ + marks: [ + Plot.barY(penguins, Plot.groupX({y: "count"}, {x: "species", fill: Plot.linearGradient()})), + Plot.ruleY([0]) + ] + }); +} + export async function penguinSpeciesGradient() { const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.plot({