Skip to content

Commit 0f07e6b

Browse files
committed
paint
1 parent e44dd3d commit 0f07e6b

File tree

9 files changed

+121
-15
lines changed

9 files changed

+121
-15
lines changed

src/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export * from "./marks/tip.js";
3737
export * from "./marks/tree.js";
3838
export * from "./marks/vector.js";
3939
export * from "./options.js";
40+
export * from "./paint.js";
4041
export * from "./plot.js";
4142
export * from "./projection.js";
4243
export * from "./reducer.js";

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,4 @@ export {pointer, pointerX, pointerY} from "./interactions/pointer.js";
4545
export {formatIsoDate, formatWeekday, formatMonth} from "./format.js";
4646
export {scale} from "./scales.js";
4747
export {legend} from "./legends.js";
48+
export {linearGradient} from "./paint.js";

src/mark.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {Channel, ChannelDomainSort, ChannelValue, ChannelValues, ChannelValueSpec} from "./channel.js";
22
import type {Context} from "./context.js";
33
import type {Dimensions} from "./dimensions.js";
4+
import type {Paint} from "./paint.js";
45
import type {plot} from "./plot.js";
56
import type {ScaleFunctions} from "./scales.js";
67
import type {InitializerFunction, SortOrder, TransformFunction} from "./transforms/basic.js";
@@ -309,7 +310,7 @@ export interface MarkOptions {
309310
*
310311
* [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill
311312
*/
312-
fill?: ChannelValueSpec;
313+
fill?: ChannelValueSpec | Paint;
313314

314315
/**
315316
* The [fill-opacity][1]; a constant number between 0 and 1, or a channel
@@ -329,7 +330,7 @@ export interface MarkOptions {
329330
*
330331
* [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke
331332
*/
332-
stroke?: ChannelValueSpec;
333+
stroke?: ChannelValueSpec | Paint;
333334

334335
/**
335336
* The [stroke-dasharray][1]; a constant number indicating the length in

src/options.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,12 +442,19 @@ export function isEvery(values, is) {
442442
return every;
443443
}
444444

445+
// TODO Make isPaint the generic one (either a color or a custom paint
446+
// implementation), rather than making isColor the generic one?
447+
export function isPaint(value) {
448+
return typeof value?.paint === "function";
449+
}
450+
445451
// Mostly relies on d3-color, with a few extra color keywords. Currently this
446452
// strictly requires that the value be a string; we might want to apply string
447453
// coercion here, though note that d3-color instances would need to support
448454
// valueOf to work correctly with InternMap.
449455
// https://www.w3.org/TR/SVG11/painting.html#SpecifyingPaint
450456
export function isColor(value) {
457+
if (isPaint(value)) return true;
451458
if (typeof value !== "string") return false;
452459
value = value.toLowerCase().trim();
453460
return (

src/paint.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type {Context} from "./context.js";
2+
3+
/** TODO */
4+
export interface Paint {
5+
/** TODO */
6+
paint(context: Context): void;
7+
}
8+
9+
/** TODO */
10+
export function linearGradient(): Paint;

src/paint.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {create} from "./context.js";
2+
3+
export function linearGradient() {
4+
return {
5+
paint(context) {
6+
const gradient = create("svg:linearGradient", context).attr("gradientTransform", "rotate(90)");
7+
gradient.append("stop").attr("offset", "5%").attr("stop-color", "purple");
8+
gradient.append("stop").attr("offset", "75%").attr("stop-color", "red");
9+
gradient.append("stop").attr("offset", "100%").attr("stop-color", "gold");
10+
return gradient.node();
11+
}
12+
};
13+
}

src/style.js

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,8 @@ import {geoPath, group, namespaces} from "d3";
22
import {create} from "./context.js";
33
import {defined, nonempty} from "./defined.js";
44
import {formatDefault} from "./format.js";
5-
import {
6-
string,
7-
number,
8-
maybeColorChannel,
9-
maybeNumberChannel,
10-
maybeKeyword,
11-
isNoneish,
12-
isNone,
13-
isRound,
14-
keyof
15-
} from "./options.js";
5+
import {isNone, isNoneish, isPaint, isRound} from "./options.js";
6+
import {keyof, maybeColorChannel, maybeKeyword, maybeNumberChannel, number, string} from "./options.js";
167
import {warn} from "./warnings.js";
178

189
export const offset = (typeof window !== "undefined" ? window.devicePixelRatio > 1 : typeof it === "undefined") ? 0 : 0.5; // prettier-ignore
@@ -115,7 +106,7 @@ export function styles(
115106

116107
// Some marks don’t support fill (e.g., tick and rule).
117108
if (defaultFill !== null) {
118-
mark.fill = impliedString(cfill, "currentColor");
109+
mark.fill = isPaint(cfill) ? cfill : impliedString(cfill, "currentColor");
119110
mark.fillOpacity = impliedNumber(cfillOpacity, 1);
120111
}
121112

@@ -364,7 +355,14 @@ function applyClip(selection, mark, dimensions, context) {
364355
// Note: may mutate selection.node!
365356
export function applyIndirectStyles(selection, mark, dimensions, context) {
366357
applyClip(selection, mark, dimensions, context);
367-
applyAttr(selection, "fill", mark.fill);
358+
if (isPaint(mark.fill)) {
359+
const paint = mark.fill.paint(context);
360+
paint.setAttribute("id", "test-paint");
361+
context.ownerSVGElement.append(paint);
362+
selection.attr("fill", "url(#test-paint)");
363+
} else {
364+
applyAttr(selection, "fill", mark.fill);
365+
}
368366
applyAttr(selection, "fill-opacity", mark.fillOpacity);
369367
applyAttr(selection, "stroke", mark.stroke);
370368
applyAttr(selection, "stroke-width", mark.strokeWidth);

test/output/penguinSpeciesPaint.svg

Lines changed: 65 additions & 0 deletions
Loading

test/plots/penguin-species.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ export async function penguinSpeciesCheysson() {
2929
});
3030
}
3131

32+
export async function penguinSpeciesPaint() {
33+
const penguins = await d3.csv<any>("data/penguins.csv", d3.autoType);
34+
return Plot.plot({
35+
marks: [
36+
Plot.barY(penguins, Plot.groupX({y: "count"}, {x: "species", fill: Plot.linearGradient()})),
37+
Plot.ruleY([0])
38+
]
39+
});
40+
}
41+
3242
export async function penguinSpeciesGradient() {
3343
const penguins = await d3.csv<any>("data/penguins.csv", d3.autoType);
3444
return Plot.plot({

0 commit comments

Comments
 (0)