Skip to content

make facet indexes rectangular if they overlap #1068

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function channelDomain(channels, facetChannels, data, options) {
let domain = rollup(
range(XV),
(I) => reducer.reduce(I, YV),
(i) => XV[i]
(i) => XV[i % XV.length]
);
domain = sort(domain, reverse ? descendingGroup : ascendingGroup);
if (lo !== 0 || hi !== Infinity) domain = domain.slice(lo, hi);
Expand All @@ -88,7 +88,7 @@ function findScaleChannel(channels, scale) {
function difference(channels, k1, k2) {
const X1 = values(channels, k1);
const X2 = values(channels, k2);
return map(X2, (x2, i) => Math.abs(x2 - X1[i]), Float64Array);
return map(X2, (x2, i) => Math.abs(x2 - X1[i % X1.length]), Float64Array);
}

function values(channels, name, alias) {
Expand Down
17 changes: 17 additions & 0 deletions src/facet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Make sure the facets are exclusive, possibly by creating a rectangular index
// of size m * n, where m is the number of facets, and n the data’s length
export function facetExclusive(facets, n) {
const m = facets.length;
if (m === 1) return {facets, n};
const overlap = new Uint8Array(n);
let max = -Infinity;
for (const facet of facets) {
for (const i of facet) {
if (overlap[i]) return {facets: facets.map((f, i) => f.map((d) => d + i * n)), n: m * n};
overlap[i] = 1;
if (i > max) max = i;
}
}
// If the facets were already expanded, return a sufficient multiple of n
return {facets, n: n * (1 + Math.floor(max / n))};
}
8 changes: 4 additions & 4 deletions src/marks/area.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ export class Area extends Mark {
shapeArea()
.curve(this.curve)
.defined((i) => i >= 0)
.x0((i) => X1[i])
.y0((i) => Y1[i])
.x1((i) => X2[i])
.y1((i) => Y2[i])
.x0((i) => X1[i % X1.length])
.y0((i) => Y1[i % Y1.length])
.x1((i) => X2[i % X2.length])
.y1((i) => Y2[i % Y2.length])
)
)
.node();
Expand Down
10 changes: 5 additions & 5 deletions src/marks/arrow.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class Arrow extends Mark {
render(index, scales, channels, dimensions, context) {
const {x1: X1, y1: Y1, x2: X2 = X1, y2: Y2 = Y1, SW} = channels;
const {strokeWidth, bend, headAngle, headLength, insetStart, insetEnd} = this;
const sw = SW ? (i) => SW[i] : constant(strokeWidth === undefined ? 1 : strokeWidth);
const sw = SW ? (i) => SW[i % SW.length] : constant(strokeWidth === undefined ? 1 : strokeWidth);

// When bending, the offset between the straight line between the two points
// and the outgoing tangent from the start point. (Also the negative
Expand Down Expand Up @@ -78,10 +78,10 @@ export class Arrow extends Mark {
.attr("d", (i) => {
// The start ⟨x1,y1⟩ and end ⟨x2,y2⟩ points may be inset, and the
// ending line angle may be altered for inset swoopy arrows.
let x1 = X1[i],
y1 = Y1[i],
x2 = X2[i],
y2 = Y2[i];
let x1 = X1[i % X1.length],
y1 = Y1[i % Y1.length],
x2 = X2[i % X2.length],
y2 = Y2[i % Y2.length];
const lineLength = Math.hypot(x2 - x1, y2 - y1);
if (lineLength <= insetStart + insetEnd) return null;
let lineAngle = Math.atan2(y2 - y1, x2 - x1);
Expand Down
12 changes: 6 additions & 6 deletions src/marks/bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ export class AbstractBar extends Mark {
}
_x(scales, {x: X}, {marginLeft}) {
const {insetLeft} = this;
return X ? (i) => X[i] + insetLeft : marginLeft + insetLeft;
return X ? (i) => X[i % X.length] + insetLeft : marginLeft + insetLeft;
}
_y(scales, {y: Y}, {marginTop}) {
const {insetTop} = this;
return Y ? (i) => Y[i] + insetTop : marginTop + insetTop;
return Y ? (i) => Y[i % Y.length] + insetTop : marginTop + insetTop;
}
_width({x}, {x: X}, {marginRight, marginLeft, width}) {
const {insetLeft, insetRight} = this;
Expand Down Expand Up @@ -90,13 +90,13 @@ export class BarX extends AbstractBar {
}
_x({x}, {x1: X1, x2: X2}, {marginLeft}) {
const {insetLeft} = this;
return isCollapsed(x) ? marginLeft + insetLeft : (i) => Math.min(X1[i], X2[i]) + insetLeft;
return isCollapsed(x) ? marginLeft + insetLeft : (i) => Math.min(X1[i % X1.length], X2[i % X2.length]) + insetLeft;
}
_width({x}, {x1: X1, x2: X2}, {marginRight, marginLeft, width}) {
const {insetLeft, insetRight} = this;
return isCollapsed(x)
? width - marginRight - marginLeft - insetLeft - insetRight
: (i) => Math.max(0, Math.abs(X2[i] - X1[i]) - insetLeft - insetRight);
: (i) => Math.max(0, Math.abs(X2[i % X2.length] - X1[i % X1.length]) - insetLeft - insetRight);
}
}

Expand All @@ -119,13 +119,13 @@ export class BarY extends AbstractBar {
}
_y({y}, {y1: Y1, y2: Y2}, {marginTop}) {
const {insetTop} = this;
return isCollapsed(y) ? marginTop + insetTop : (i) => Math.min(Y1[i], Y2[i]) + insetTop;
return isCollapsed(y) ? marginTop + insetTop : (i) => Math.min(Y1[i % Y1.length], Y2[i % Y2.length]) + insetTop;
}
_height({y}, {y1: Y1, y2: Y2}, {marginTop, marginBottom, height}) {
const {insetTop, insetBottom} = this;
return isCollapsed(y)
? height - marginTop - marginBottom - insetTop - insetBottom
: (i) => Math.max(0, Math.abs(Y2[i] - Y1[i]) - insetTop - insetBottom);
: (i) => Math.max(0, Math.abs(Y2[i % Y2.length] - Y1[i % Y1.length]) - insetTop - insetBottom);
}
}

Expand Down
22 changes: 11 additions & 11 deletions src/marks/delaunay.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ class DelaunayLink extends Mark {
const {x: X, y: Y, z: Z} = channels;
const {curve} = this;
const [cx, cy] = applyFrameAnchor(this, dimensions);
const xi = X ? (i) => X[i] : constant(cx);
const yi = Y ? (i) => Y[i] : constant(cy);
const xi = X ? (i) => X[i % X.length] : constant(cx);
const yi = Y ? (i) => Y[i % Y.length] : constant(cy);
const mark = this;

function links(index) {
Expand Down Expand Up @@ -113,8 +113,8 @@ class DelaunayLink extends Mark {
const p = path();
const c = curve(p);
c.lineStart();
c.point(X1[i], Y1[i]);
c.point(X2[i], Y2[i]);
c.point(X1[i % X1.length], Y1[i % Y1.length]);
c.point(X2[i % X2.length], Y2[i % Y2.length]);
c.lineEnd();
return p;
})
Expand All @@ -130,7 +130,7 @@ class DelaunayLink extends Mark {
? (g) =>
g
.selectAll()
.data(group(index, (i) => Z[i]).values())
.data(group(index, (i) => Z[i % Z.length]).values())
.enter()
.append("g")
.each(links)
Expand All @@ -157,8 +157,8 @@ class AbstractDelaunayMark extends Mark {
render(index, scales, channels, dimensions, context) {
const {x: X, y: Y, z: Z} = channels;
const [cx, cy] = applyFrameAnchor(this, dimensions);
const xi = X ? (i) => X[i] : constant(cx);
const yi = Y ? (i) => Y[i] : constant(cy);
const xi = X ? (i) => X[i % X.length] : constant(cx);
const yi = Y ? (i) => Y[i % Y.length] : constant(cy);
const mark = this;

function mesh(index) {
Expand All @@ -179,7 +179,7 @@ class AbstractDelaunayMark extends Mark {
? (g) =>
g
.selectAll()
.data(group(index, (i) => Z[i]).values())
.data(group(index, (i) => Z[i % Z.length]).values())
.enter()
.append("g")
.each(mesh)
Expand Down Expand Up @@ -225,8 +225,8 @@ class Voronoi extends Mark {
render(index, scales, channels, dimensions, context) {
const {x: X, y: Y, z: Z} = channels;
const [cx, cy] = applyFrameAnchor(this, dimensions);
const xi = X ? (i) => X[i] : constant(cx);
const yi = Y ? (i) => Y[i] : constant(cy);
const xi = X ? (i) => X[i % X.length] : constant(cx);
const yi = Y ? (i) => Y[i % Y.length] : constant(cy);

function cells(index) {
const delaunay = Delaunay.from(index, xi, yi);
Expand All @@ -249,7 +249,7 @@ class Voronoi extends Mark {
? (g) =>
g
.selectAll()
.data(group(index, (i) => Z[i]).values())
.data(group(index, (i) => Z[i % Z.length]).values())
.enter()
.append("g")
.each(cells)
Expand Down
6 changes: 3 additions & 3 deletions src/marks/density.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ function densityInitializer(options, fillDensity, strokeDensity) {
const SD = strokeDensity && [];

const density = contourDensity()
.x(X ? (i) => X[i] : cx)
.y(Y ? (i) => Y[i] : cy)
.weight(W ? (i) => W[i] : 1)
.x(X ? (i) => X[i % X.length] : cx)
.y(Y ? (i) => Y[i % Y.length] : cy)
.weight(W ? (i) => W[i % W.length] : 1)
.size([width, height])
.bandwidth(bandwidth);

Expand Down
18 changes: 9 additions & 9 deletions src/marks/dot.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,32 +77,32 @@ export class Dot extends Mark {
circle
? (selection) => {
selection
.attr("cx", X ? (i) => X[i] : cx)
.attr("cy", Y ? (i) => Y[i] : cy)
.attr("r", R ? (i) => R[i] : this.r);
.attr("cx", X ? (i) => X[i % X.length] : cx)
.attr("cy", Y ? (i) => Y[i % Y.length] : cy)
.attr("r", R ? (i) => R[i % R.length] : this.r);
}
: (selection) => {
const translate =
X && Y
? (i) => `translate(${X[i]},${Y[i]})`
? (i) => `translate(${X[i % X.length]},${Y[i % Y.length]})`
: X
? (i) => `translate(${X[i]},${cy})`
? (i) => `translate(${X[i % X.length]},${cy})`
: Y
? (i) => `translate(${cx},${Y[i]})`
? (i) => `translate(${cx},${Y[i % Y.length]})`
: () => `translate(${cx},${cy})`;
selection
.attr(
"transform",
A
? (i) => `${translate(i)} rotate(${A[i]})`
? (i) => `${translate(i)} rotate(${A[i % A.length]})`
: this.rotate
? (i) => `${translate(i)} rotate(${this.rotate})`
: translate
)
.attr("d", (i) => {
const p = path(),
r = R ? R[i] : this.r;
(S ? S[i] : this.symbol).draw(p, r * r * Math.PI);
r = R ? R[i % R.length] : this.r;
(S ? S[i % S.length] : this.symbol).draw(p, r * r * Math.PI);
return p;
});
}
Expand Down
18 changes: 9 additions & 9 deletions src/marks/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,26 +81,26 @@ export class Image extends Mark {
.attr(
"x",
W && X
? (i) => X[i] - W[i] / 2
? (i) => X[i % X.length] - W[i % W.length] / 2
: W
? (i) => cx - W[i] / 2
? (i) => cx - W[i % W.length] / 2
: X
? (i) => X[i] - this.width / 2
? (i) => X[i % X.length] - this.width / 2
: cx - this.width / 2
)
.attr(
"y",
H && Y
? (i) => Y[i] - H[i] / 2
? (i) => Y[i % Y.length] - H[i % H.length] / 2
: H
? (i) => cy - H[i] / 2
? (i) => cy - H[i % H.length] / 2
: Y
? (i) => Y[i] - this.height / 2
? (i) => Y[i % Y.length] - this.height / 2
: cy - this.height / 2
)
.attr("width", W ? (i) => W[i] : this.width)
.attr("height", H ? (i) => H[i] : this.height)
.call(applyAttr, "href", S ? (i) => S[i] : this.src)
.attr("width", W ? (i) => W[i % W.length] : this.width)
.attr("height", H ? (i) => H[i % H.length] : this.height)
.call(applyAttr, "href", S ? (i) => S[i % S.length] : this.src)
.call(applyAttr, "preserveAspectRatio", this.preserveAspectRatio)
.call(applyAttr, "crossorigin", this.crossOrigin)
.call(applyChannelStyles, this, channels)
Expand Down
4 changes: 2 additions & 2 deletions src/marks/line.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ export class Line extends Mark {
shapeLine()
.curve(this.curve)
.defined((i) => i >= 0)
.x((i) => X[i])
.y((i) => Y[i])
.x((i) => X[i % X.length])
.y((i) => Y[i % Y.length])
)
)
.node();
Expand Down
18 changes: 9 additions & 9 deletions src/marks/linearRegression.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class LinearRegressionX extends LinearRegression {
}
_renderBand(I, X, Y) {
const {ci, precision} = this;
const [y1, y2] = extent(I, (i) => Y[i]);
const [y1, y2] = extent(I, (i) => Y[i % Y.length]);
const f = linearRegressionF(I, Y, X);
const g = confidenceIntervalF(I, Y, X, (1 - ci) / 2, f);
return shapeArea()
Expand All @@ -95,7 +95,7 @@ class LinearRegressionX extends LinearRegression {
.x1((y) => g(y, +1))(range(y1, y2 - precision / 2, precision).concat(y2));
}
_renderLine(I, X, Y) {
const [y1, y2] = extent(I, (i) => Y[i]);
const [y1, y2] = extent(I, (i) => Y[i % Y.length]);
const f = linearRegressionF(I, Y, X);
return `M${f(y1)},${y1}L${f(y2)},${y2}`;
}
Expand All @@ -107,7 +107,7 @@ class LinearRegressionY extends LinearRegression {
}
_renderBand(I, X, Y) {
const {ci, precision} = this;
const [x1, x2] = extent(I, (i) => X[i]);
const [x1, x2] = extent(I, (i) => X[i % X.length]);
const f = linearRegressionF(I, X, Y);
const g = confidenceIntervalF(I, X, Y, (1 - ci) / 2, f);
return shapeArea()
Expand All @@ -116,7 +116,7 @@ class LinearRegressionY extends LinearRegression {
.y1((x) => g(x, +1))(range(x1, x2 - precision / 2, precision).concat(x2));
}
_renderLine(I, X, Y) {
const [x1, x2] = extent(I, (i) => X[i]);
const [x1, x2] = extent(I, (i) => X[i % X.length]);
const f = linearRegressionF(I, X, Y);
return `M${x1},${f(x1)}L${x2},${f(x2)}`;
}
Expand Down Expand Up @@ -166,8 +166,8 @@ function linearRegressionF(I, X, Y) {
sumXY = 0,
sumX2 = 0;
for (const i of I) {
const xi = X[i];
const yi = Y[i];
const xi = X[i % X.length];
const yi = Y[i % Y.length];
sumX += xi;
sumY += yi;
sumXY += xi * yi;
Expand All @@ -180,12 +180,12 @@ function linearRegressionF(I, X, Y) {
}

function confidenceIntervalF(I, X, Y, p, f) {
const mean = sum(I, (i) => X[i]) / I.length;
const mean = sum(I, (i) => X[i % X.length]) / I.length;
let a = 0,
b = 0;
for (const i of I) {
a += (X[i] - mean) ** 2;
b += (Y[i] - f(X[i])) ** 2;
a += (X[i % X.length] - mean) ** 2;
b += (Y[i % Y.length] - f(X[i % X.length])) ** 2;
}
const sy = Math.sqrt(b / (I.length - 2));
const t = qt(p, I.length - 2);
Expand Down
4 changes: 2 additions & 2 deletions src/marks/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export class Link extends Mark {
const p = path();
const c = curve(p);
c.lineStart();
c.point(X1[i], Y1[i]);
c.point(X2[i], Y2[i]);
c.point(X1[i % X1.length], Y1[i % Y1.length]);
c.point(X2[i % X2.length], Y2[i % Y2.length]);
c.lineEnd();
return p;
})
Expand Down
4 changes: 2 additions & 2 deletions src/marks/marker.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ function markerCircleStroke(color, context) {
let nextMarkerId = 0;

export function applyMarkers(path, mark, {stroke: S} = {}) {
return applyMarkersColor(path, mark, S && ((i) => S[i]));
return applyMarkersColor(path, mark, S && ((i) => S[i % S.length]));
}

export function applyGroupedMarkers(path, mark, {stroke: S} = {}) {
return applyMarkersColor(path, mark, S && (([i]) => S[i]));
return applyMarkersColor(path, mark, S && (([i]) => S[i % S.length]));
}

function applyMarkersColor(path, {markerStart, markerMid, markerEnd, stroke}, strokeof = () => stroke) {
Expand Down
Loading