Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 7 additions & 7 deletions src/marks/hexgrid.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {create} from "../context.js";
import {Mark} from "../mark.js";
import {number, singleton} from "../options.js";
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, offset} from "../style.js";
import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform} from "../style.js";
import {sqrt4_3} from "../symbol.js";
import {ox, oy} from "../transforms/hexbin.js";
import {ox} from "../transforms/hexbin.js";

const defaults = {
ariaLabel: "hexgrid",
Expand All @@ -26,15 +26,15 @@ export class Hexgrid extends Mark {
const {marginTop, marginRight, marginBottom, marginLeft, width, height} = dimensions;
const x0 = marginLeft - ox,
x1 = width - marginRight - ox,
y0 = marginTop - oy,
y1 = height - marginBottom - oy,
y0 = marginTop,
y1 = height - marginBottom,
rx = binWidth / 2,
ry = rx * sqrt4_3,
hy = ry / 2,
wx = rx * 2,
wy = ry * 1.5,
i0 = Math.floor(x0 / wx),
i1 = Math.ceil(x1 / wx),
i0 = Math.floor((x0 - rx) / wx),
i1 = Math.ceil((x1 + rx) / wx),
j0 = Math.floor((y0 + hy) / wy),
j1 = Math.ceil((y1 - hy) / wy) + 1,
path = `m0,${round(-ry)}l${round(rx)},${round(hy)}v${round(ry)}l${round(-rx)},${round(hy)}`;
Expand All @@ -47,7 +47,7 @@ export class Hexgrid extends Mark {
return create("svg:g", context)
.datum(0)
.call(applyIndirectStyles, this, dimensions, context)
.call(applyTransform, this, {}, offset + ox, offset + oy)
.call(applyTransform, this, {}, ox, 0)
.call((g) => g.append("path").call(applyDirectStyles, this).call(applyChannelStyles, this, channels).attr("d", d))
.node();
}
Expand Down
2 changes: 1 addition & 1 deletion src/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {isNone, isNoneish, isRound, maybeColorChannel, maybeNumberChannel} from
import {keyof, 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
export const offset = typeof window !== "undefined" && !(window.devicePixelRatio > 1) ? 0.5 : 0;

let nextClipId = 0;
let nextPatternId = 0;
Expand Down
37 changes: 24 additions & 13 deletions src/transforms/hexbin.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import {map, number, valueof} from "../options.js";
import {applyPosition} from "../projection.js";
import {offset} from "../style.js";
import {sqrt3} from "../symbol.js";
import {initializer} from "./basic.js";
import {hasOutput, maybeGroup, maybeGroupOutputs, maybeSubgroup} from "./group.js";

// We don’t want the hexagons to align with the edges of the plot frame, as that
// would cause extreme x-values (the upper bound of the default x-scale domain)
// to be rounded up into a floating bin to the right of the plot. Therefore,
// rather than centering the origin hexagon around ⟨0,0⟩ in screen coordinates,
// we offset slightly to ⟨0.5,0⟩. The hexgrid mark uses the same origin.
export const ox = 0.5,
oy = 0;
// When a data value lands exactly on a hexbin grid boundary (i.e., the scaled
// x-coordinate is a half-integer due to the odd-row offset), Math.round would
// round up into a floating bin outside the plot. We use a custom rounding
// function that breaks such ties toward the center of the plot, preventing
// exterior bins on left and right edges.
export const ox = -offset;

// Rounds x to the nearest integer, breaking .5 ties toward center.
function round(x, center) {
return Math.round(center + (x - center) * (1 - 1e-12));
}

export function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) {
const {z} = options;
Expand All @@ -30,14 +35,19 @@ export function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) {
if (options.symbol === undefined) options.symbol = "hexagon";
if (options.r === undefined && !hasOutput(outputs, "r")) options.r = binWidth / 2;

return initializer(options, (data, facets, channels, scales, _, context) => {
return initializer(options, (data, facets, channels, scales, dimensions, context) => {
let {x: X, y: Y, z: Z, fill: F, stroke: S, symbol: Q} = channels;
if (X === undefined) throw new Error("missing channel: x");
if (Y === undefined) throw new Error("missing channel: y");

// Get the (either scaled or projected) xy channels.
({x: X, y: Y} = applyPosition(channels, scales, context));

// Compute the horizontal midpoint of the frame in pixel space; used by
// hbin to break rounding ties toward the center, preventing exterior bins.
const {marginRight, marginLeft, width} = dimensions;
const mx = (marginLeft + width - marginRight) / 2;

// Extract the values for channels that are eligible for grouping; not all
// marks define a z channel, so compute one if it not already computed. If z
// was explicitly set to null, ensure that we don’t subdivide bins.
Expand All @@ -64,7 +74,7 @@ export function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) {
const binFacet = [];
for (const o of outputs) o.scope("facet", facet);
for (const [f, I] of maybeGroup(facet, G)) {
for (const {index: b, extent} of hbin(data, I, X, Y, binWidth)) {
for (const {index: b, extent} of hbin(data, I, X, Y, binWidth, mx)) {
binFacet.push(++i);
BX.push(extent.x);
BY.push(extent.y);
Expand Down Expand Up @@ -105,15 +115,16 @@ export function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) {
});
}

function hbin(data, I, X, Y, dx) {
function hbin(data, I, X, Y, dx, mx) {
const dy = dx * (1.5 / sqrt3);
const cx = (mx - ox) / dx;
const bins = new Map();
for (const i of I) {
let px = X[i],
py = Y[i];
if (isNaN(px) || isNaN(py)) continue;
let pj = Math.round((py = (py - oy) / dy)),
pi = Math.round((px = (px - ox) / dx - (pj & 1) / 2)),
let pj = Math.round((py = py / dy)),
pi = round((px = (px - ox) / dx - (pj & 1) / 2), cx - (pj & 1) / 2),
py1 = py - pj;
if (Math.abs(py1) * 3 > 1) {
let px1 = px - pi,
Expand All @@ -126,7 +137,7 @@ function hbin(data, I, X, Y, dx) {
const key = `${pi},${pj}`;
let bin = bins.get(key);
if (bin === undefined) {
bin = {index: [], extent: {data, x: (pi + (pj & 1) / 2) * dx + ox, y: pj * dy + oy}};
bin = {index: [], extent: {data, x: (pi + (pj & 1) / 2) * dx + ox, y: pj * dy}};
bins.set(key, bin);
}
bin.index.push(i);
Expand Down
120 changes: 60 additions & 60 deletions test/output/aaplBollinger.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading