Skip to content

Commit 820144e

Browse files
Filmbostock
andauthored
color scale override for Plot.raster (#1318)
* closes #1317 * Update src/marks/raster.js Co-authored-by: Mike Bostock <[email protected]> * don't hardcode that fill is scaled with {color} * expose channel states to render --------- Co-authored-by: Mike Bostock <[email protected]>
1 parent 7e689b1 commit 820144e

File tree

6 files changed

+132
-9
lines changed

6 files changed

+132
-9
lines changed

src/channel.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,9 @@ export type ChannelDomainSort = {
8282
} & ChannelDomainOptions;
8383

8484
export type ChannelReducers = {[key in ChannelName]?: ChannelReducerSpec | null};
85+
86+
/** The abstract (unscaled) values, and associated scale, per channel. */
87+
export type ChannelStates = {[key in ChannelName]?: {value: any[]; scale: ScaleName | null}};
88+
89+
/** The possibly-scaled values for each channel. */
90+
export type ChannelValues = {[key in ChannelName]?: any[]} & {channels: ChannelStates};

src/channel.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ export function createChannels(channels, data) {
2424

2525
// TODO Use Float64Array for scales with numeric ranges, e.g. position?
2626
export function valueObject(channels, scales) {
27-
return Object.fromEntries(
27+
const values = Object.fromEntries(
2828
Object.entries(channels).map(([name, {scale: scaleName, value}]) => {
2929
const scale = scaleName == null ? null : scales[scaleName];
3030
return [name, scale == null ? value : map(value, scale)];
3131
})
3232
);
33+
values.channels = channels; // expose channel state for advanced usage
34+
return values;
3335
}
3436

3537
// If the channel uses the "auto" scale (or equivalently true), infer the scale

src/mark.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import type {Channels, ChannelDomainSort, ChannelName, ChannelValue, ChannelValueSpec} from "./channel.js";
1+
import type {ChannelDomainSort, Channels, ChannelValue, ChannelValues, ChannelValueSpec} from "./channel.js";
22
import type {Context} from "./context.js";
33
import type {Dimensions} from "./dimensions.js";
44
import type {Facet, FacetAnchor} from "./facet.js";
55
import type {plot} from "./plot.js";
66
import type {ScaleFunctions} from "./scales.js";
7-
import type {InitializerFunction, TransformFunction, SortOrder} from "./transforms/basic.js";
7+
import type {InitializerFunction, SortOrder, TransformFunction} from "./transforms/basic.js";
88

99
export type FrameAnchor =
1010
| "middle"
@@ -22,7 +22,7 @@ export type Data = Iterable<any> | ArrayLike<any>;
2222
export type RenderFunction = (
2323
index: number[],
2424
scales: ScaleFunctions,
25-
values: {[key in ChannelName]?: any[]},
25+
values: ChannelValues,
2626
dimensions: Dimensions,
2727
context: Context
2828
) => SVGElement | null;

src/marks/raster.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,11 @@ export class Raster extends AbstractRaster {
9797
scale(channels, {color, ...scales}, context) {
9898
return super.scale(channels, scales, context);
9999
}
100-
render(index, scales, channels, dimensions, context) {
101-
const color = scales.color ?? ((x) => x);
102-
const {x: X, y: Y} = channels;
100+
render(index, scales, values, dimensions, context) {
101+
const color = scales[values.channels.fill?.scale] ?? ((x) => x);
102+
const {x: X, y: Y} = values;
103103
const {document} = context;
104-
const [x1, y1, x2, y2] = renderBounds(channels, dimensions, context);
104+
const [x1, y1, x2, y2] = renderBounds(values, dimensions, context);
105105
const dx = x2 - x1;
106106
const dy = y2 - y1;
107107
const {pixelSize: k, width: w = Math.round(Math.abs(dx) / k), height: h = Math.round(Math.abs(dy) / k)} = this;
@@ -110,7 +110,7 @@ export class Raster extends AbstractRaster {
110110
// Interpolate the samples to fill the raster grid. If interpolate is null,
111111
// then a continuous function is being sampled, and the raster grid is
112112
// already aligned with the canvas.
113-
let {fill: F, fillOpacity: FO} = channels;
113+
let {fill: F, fillOpacity: FO} = values;
114114
let offset = 0;
115115
if (this.interpolate) {
116116
const kx = w / dx;

test/output/rasterVapor2.html

Lines changed: 96 additions & 0 deletions
Large diffs are not rendered by default.

test/plots/raster-vapor.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,25 @@ export async function rasterVapor() {
1717
});
1818
}
1919

20+
export async function rasterVapor2() {
21+
return Plot.plot({
22+
color: {scheme: "blues", legend: true},
23+
x: {transform: (x) => x - 180},
24+
y: {transform: (y) => 90 - y},
25+
marks: [
26+
Plot.raster(await vapor(), {
27+
width: 360,
28+
height: 180
29+
}),
30+
Plot.raster(await vapor(), {
31+
width: 360,
32+
height: 180,
33+
fill: {value: (d) => (d > 4 ? "red" : null), scale: null}
34+
})
35+
]
36+
});
37+
}
38+
2039
export async function contourVapor() {
2140
return Plot.plot({
2241
width: 960,

0 commit comments

Comments
 (0)