diff --git a/src/style.js b/src/style.js index 903c3fdf60..d77bd0f1b8 100644 --- a/src/style.js +++ b/src/style.js @@ -47,7 +47,8 @@ export function styles( imageFilter, paintOrder, pointerEvents, - shapeRendering + shapeRendering, + channels }, { ariaLabel: cariaLabel, @@ -81,9 +82,9 @@ export function styles( // default fill becomes none. Similarly for marks that stroke by stroke, the // default stroke only applies if the fill is (constant) none. if (isNoneish(defaultFill)) { - if (!isNoneish(defaultStroke) && !isNoneish(fill)) defaultStroke = "none"; + if (!isNoneish(defaultStroke) && (!isNoneish(fill) || channels?.fill)) defaultStroke = "none"; } else { - if (isNoneish(defaultStroke) && !isNoneish(stroke)) defaultFill = "none"; + if (isNoneish(defaultStroke) && (!isNoneish(stroke) || channels?.stroke)) defaultFill = "none"; } const [vfill, cfill] = maybeColorChannel(fill, defaultFill); diff --git a/src/transforms/hexbin.js b/src/transforms/hexbin.js index d2ff05e4e1..5952951352 100644 --- a/src/transforms/hexbin.js +++ b/src/transforms/hexbin.js @@ -1,4 +1,4 @@ -import {isNoneish, map, number, valueof} from "../options.js"; +import {map, number, valueof} from "../options.js"; import {applyPosition} from "../projection.js"; import {sqrt3} from "../symbol.js"; import {initializer} from "./basic.js"; @@ -13,17 +13,19 @@ export const ox = 0.5, oy = 0; export function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) { + const {z} = options; + // TODO filter e.g. to show empty hexbins? // TODO disallow x, x1, x2, y, y1, y2 reducers? binWidth = binWidth === undefined ? 20 : number(binWidth); outputs = maybeOutputs(outputs, options); - // A fill output means a fill channel, and hence the stroke should default to - // none (assuming a mark that defaults to fill and no stroke, such as dot). - // Note that it’s safe to mutate options here because we just created it with - // the rest operator above. - const {z, fill, stroke} = options; - if (stroke === undefined && isNoneish(fill) && hasOutput(outputs, "fill")) options.stroke = "none"; + // A fill output means a fill channel; declaring the channel here instead of + // waiting for the initializer allows the mark constructor to determine that + // the stroke should default to none (assuming a mark that defaults to fill + // and no stroke, such as dot). Note that it’s safe to mutate options here + // because we just created it with the rest operator above. + if (hasOutput(outputs, "fill")) options.channels = {...options.channels, fill: {value: []}}; // Populate default values for the r and symbol options, as appropriate. if (options.symbol === undefined) options.symbol = "hexagon"; diff --git a/test/output/tipHexbinExplicit.svg b/test/output/tipHexbinExplicit.svg new file mode 100644 index 0000000000..f4ca6b4cc8 --- /dev/null +++ b/test/output/tipHexbinExplicit.svg @@ -0,0 +1,306 @@ + + + + + + + + + + + + + + + + 1.3 + 1.4 + 1.5 + 1.6 + 1.7 + 1.8 + 1.9 + 2.0 + 2.1 + 2.2 + + + ↑ height + + + + + + + + + + + + 40 + 60 + 80 + 100 + 120 + 140 + 160 + + + weight → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/tip.ts b/test/plots/tip.ts index 35f2410da9..417a740df8 100644 --- a/test/plots/tip.ts +++ b/test/plots/tip.ts @@ -123,6 +123,19 @@ export async function tipHexbin() { return Plot.hexagon(olympians, Plot.hexbin({r: "count"}, {x: "weight", y: "height", tip: true})).plot(); } +// Normally you would slap a tip: true on the hexagon, as above, but here we +// want to test that the hexbin transform isn’t applying an erroneous stroke: +// none to the tip options (which would change the tip appearance). +export async function tipHexbinExplicit() { + const olympians = await d3.csv("data/athletes.csv", d3.autoType); + return Plot.plot({ + marks: [ + Plot.hexagon(olympians, Plot.hexbin({fill: "count"}, {x: "weight", y: "height"})), + Plot.tip(olympians, Plot.pointer(Plot.hexbin({fill: "count"}, {x: "weight", y: "height"}))) + ] + }); +} + export async function tipLine() { const aapl = await d3.csv("data/aapl.csv", d3.autoType); return Plot.lineY(aapl, {x: "Date", y: "Close", tip: true}).plot();