Skip to content

Commit dd72d5b

Browse files
committed
rewrite bars path builder to not rely on band clipping
1 parent a3df179 commit dd72d5b

File tree

6 files changed

+95
-215
lines changed

6 files changed

+95
-215
lines changed

Diff for: dist/uPlot.cjs.js

+23-53
Original file line numberDiff line numberDiff line change
@@ -2346,17 +2346,19 @@ function bars(opts) {
23462346
[baseRadius, valRadius] = radiusFn(u, seriesIdx);
23472347

23482348
const _dirX = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
2349-
const _dirY = scaleY.dir * (scaleY.ori == 1 ? 1 : -1);
2349+
// const _dirY = scaleY.dir * (scaleY.ori == 1 ? 1 : -1);
23502350

23512351
let rect = scaleX.ori == 0 ? rectH : rectV;
23522352

23532353
let each = scaleX.ori == 0 ? _each : (u, seriesIdx, i, top, lft, hgt, wid) => {
23542354
_each(u, seriesIdx, i, lft, top, wid, hgt);
23552355
};
23562356

2357-
let [ bandFillDir, bandClipDir ] = bandFillClipDirs(u, seriesIdx);
2357+
// band where this series is the "from" edge
2358+
let band = ifNull(u.bands, EMPTY_ARR).find(b => b.series[0] == seriesIdx);
23582359

2359-
let fillTo = series.fillTo(u, seriesIdx, series.min, series.max, bandFillDir);
2360+
let fillDir = band != null ? band.dir : 0;
2361+
let fillTo = series.fillTo(u, seriesIdx, series.min, series.max, fillDir);
23602362
let fillToY = pxRound(valToPosY(fillTo, scaleY, yDim, yOff));
23612363

23622364
// barWid is to center of stroke
@@ -2393,8 +2395,6 @@ function bars(opts) {
23932395

23942396
let { x0, size } = disp;
23952397

2396-
let bandClipNulls = true;
2397-
23982398
if (x0 != null && size != null) {
23992399
dataX = x0.values(u, seriesIdx, idx0, idx1);
24002400

@@ -2459,33 +2459,23 @@ function bars(opts) {
24592459
barWid = pxRound(clamp(colWid - gapWid, minWidth, maxWidth) - strokeWidth - extraGap);
24602460

24612461
xShift = (align == 0 ? barWid / 2 : align == _dirX ? 0 : barWid) - align * _dirX * extraGap / 2;
2462-
2463-
// when colWidth is smaller than [min-clamped] bar width (e.g. aligned data values are non-uniform)
2464-
// disable clipping of null-valued band bars to avoid clip overlap / bleed into adjacent bars
2465-
// (this could still bleed clips of adjacent band/stacked bars into each other, so is far from perfect)
2466-
if (barWid + strokeWidth > colWid)
2467-
bandClipNulls = false;
24682462
}
24692463

2470-
const _paths = {stroke: null, fill: null, clip: null, band: null, gaps: null, flags: BAND_CLIP_FILL | BAND_CLIP_STROKE}; // disp, geom
2471-
2472-
let yLimit;
2473-
2474-
if (bandClipDir != 0) {
2475-
_paths.band = new Path2D();
2476-
yLimit = pxRound(valToPosY(bandClipDir == 1 ? scaleY.max : scaleY.min, scaleY, yDim, yOff));
2477-
}
2464+
const _paths = {stroke: null, fill: null, clip: null, band: null, gaps: null, flags: 0}; // disp, geom
24782465

24792466
const stroke = multiPath ? null : new Path2D();
2480-
const band = _paths.band;
2481-
2482-
let { y0, y1 } = disp;
24832467

24842468
let dataY0 = null;
24852469

2486-
if (y0 != null && y1 != null) {
2487-
dataY = y1.values(u, seriesIdx, idx0, idx1);
2488-
dataY0 = y0.values(u, seriesIdx, idx0, idx1);
2470+
if (band != null)
2471+
dataY0 = u.data[band.series[1]];
2472+
else {
2473+
let { y0, y1 } = disp;
2474+
2475+
if (y0 != null && y1 != null) {
2476+
dataY = y1.values(u, seriesIdx, idx0, idx1);
2477+
dataY0 = y0.values(u, seriesIdx, idx0, idx1);
2478+
}
24892479
}
24902480

24912481
let radVal = valRadius * barWid;
@@ -2494,29 +2484,24 @@ function bars(opts) {
24942484
for (let i = _dirX == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += _dirX) {
24952485
let yVal = dataY[i];
24962486

2497-
// we can skip both, drawing and band clipping for alignment artifacts
2498-
if (yVal === undefined)
2487+
if (yVal == null)
24992488
continue;
25002489

2501-
/*
2502-
// interpolate upwards band clips
2503-
if (yVal == null) {
2504-
// if (hasBands)
2505-
// yVal = costlyLerp(i, idx0, idx1, _dirX, dataY);
2506-
// else
2490+
if (dataY0 != null) {
2491+
let yVal0 = dataY0[i] ?? 0;
2492+
2493+
if (yVal - yVal0 == 0)
25072494
continue;
2495+
2496+
fillToY = valToPosY(yVal0, scaleY, yDim, yOff);
25082497
}
2509-
*/
25102498

25112499
let xVal = scaleX.distr != 2 || disp != null ? dataX[i] : i;
25122500

25132501
// TODO: all xPos can be pre-computed once for all series in aligned set
25142502
let xPos = valToPosX(xVal, scaleX, xDim, xOff);
25152503
let yPos = valToPosY(ifNull(yVal, fillTo), scaleY, yDim, yOff);
25162504

2517-
if (dataY0 != null && yVal != null)
2518-
fillToY = valToPosY(dataY0[i], scaleY, yDim, yOff);
2519-
25202505
let lft = pxRound(xPos - xShift);
25212506
let btm = pxRound(max(yPos, fillToY));
25222507
let top = pxRound(min(yPos, fillToY));
@@ -2544,21 +2529,6 @@ function bars(opts) {
25442529
barHgt,
25452530
);
25462531
}
2547-
2548-
if (bandClipDir != 0 && (yVal != null || bandClipNulls)) {
2549-
if (_dirY * bandClipDir == 1) {
2550-
btm = top;
2551-
top = yLimit;
2552-
}
2553-
else {
2554-
top = btm;
2555-
btm = yLimit;
2556-
}
2557-
2558-
barHgt = btm - top;
2559-
2560-
rect(band, lft - strokeWidth / 2, top, barWid + strokeWidth, max(0, barHgt), 0, 0); // radius here?
2561-
}
25622532
}
25632533

25642534
if (strokeWidth > 0)
@@ -4074,7 +4044,7 @@ function uPlot(opts, data, then) {
40744044

40754045
// for all bands where this series is the top edge, create upwards clips using the bottom edges
40764046
// and apply clips + fill with band fill or dfltFill
4077-
bands.forEach((b, bi) => {
4047+
flags != 0 && bands.forEach((b, bi) => {
40784048
// isUpperEdge?
40794049
if (b.series[0] == si) {
40804050
let lowerEdge = series[b.series[1]];

Diff for: dist/uPlot.esm.js

+23-53
Original file line numberDiff line numberDiff line change
@@ -2344,17 +2344,19 @@ function bars(opts) {
23442344
[baseRadius, valRadius] = radiusFn(u, seriesIdx);
23452345

23462346
const _dirX = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
2347-
const _dirY = scaleY.dir * (scaleY.ori == 1 ? 1 : -1);
2347+
// const _dirY = scaleY.dir * (scaleY.ori == 1 ? 1 : -1);
23482348

23492349
let rect = scaleX.ori == 0 ? rectH : rectV;
23502350

23512351
let each = scaleX.ori == 0 ? _each : (u, seriesIdx, i, top, lft, hgt, wid) => {
23522352
_each(u, seriesIdx, i, lft, top, wid, hgt);
23532353
};
23542354

2355-
let [ bandFillDir, bandClipDir ] = bandFillClipDirs(u, seriesIdx);
2355+
// band where this series is the "from" edge
2356+
let band = ifNull(u.bands, EMPTY_ARR).find(b => b.series[0] == seriesIdx);
23562357

2357-
let fillTo = series.fillTo(u, seriesIdx, series.min, series.max, bandFillDir);
2358+
let fillDir = band != null ? band.dir : 0;
2359+
let fillTo = series.fillTo(u, seriesIdx, series.min, series.max, fillDir);
23582360
let fillToY = pxRound(valToPosY(fillTo, scaleY, yDim, yOff));
23592361

23602362
// barWid is to center of stroke
@@ -2391,8 +2393,6 @@ function bars(opts) {
23912393

23922394
let { x0, size } = disp;
23932395

2394-
let bandClipNulls = true;
2395-
23962396
if (x0 != null && size != null) {
23972397
dataX = x0.values(u, seriesIdx, idx0, idx1);
23982398

@@ -2457,33 +2457,23 @@ function bars(opts) {
24572457
barWid = pxRound(clamp(colWid - gapWid, minWidth, maxWidth) - strokeWidth - extraGap);
24582458

24592459
xShift = (align == 0 ? barWid / 2 : align == _dirX ? 0 : barWid) - align * _dirX * extraGap / 2;
2460-
2461-
// when colWidth is smaller than [min-clamped] bar width (e.g. aligned data values are non-uniform)
2462-
// disable clipping of null-valued band bars to avoid clip overlap / bleed into adjacent bars
2463-
// (this could still bleed clips of adjacent band/stacked bars into each other, so is far from perfect)
2464-
if (barWid + strokeWidth > colWid)
2465-
bandClipNulls = false;
24662460
}
24672461

2468-
const _paths = {stroke: null, fill: null, clip: null, band: null, gaps: null, flags: BAND_CLIP_FILL | BAND_CLIP_STROKE}; // disp, geom
2469-
2470-
let yLimit;
2471-
2472-
if (bandClipDir != 0) {
2473-
_paths.band = new Path2D();
2474-
yLimit = pxRound(valToPosY(bandClipDir == 1 ? scaleY.max : scaleY.min, scaleY, yDim, yOff));
2475-
}
2462+
const _paths = {stroke: null, fill: null, clip: null, band: null, gaps: null, flags: 0}; // disp, geom
24762463

24772464
const stroke = multiPath ? null : new Path2D();
2478-
const band = _paths.band;
2479-
2480-
let { y0, y1 } = disp;
24812465

24822466
let dataY0 = null;
24832467

2484-
if (y0 != null && y1 != null) {
2485-
dataY = y1.values(u, seriesIdx, idx0, idx1);
2486-
dataY0 = y0.values(u, seriesIdx, idx0, idx1);
2468+
if (band != null)
2469+
dataY0 = u.data[band.series[1]];
2470+
else {
2471+
let { y0, y1 } = disp;
2472+
2473+
if (y0 != null && y1 != null) {
2474+
dataY = y1.values(u, seriesIdx, idx0, idx1);
2475+
dataY0 = y0.values(u, seriesIdx, idx0, idx1);
2476+
}
24872477
}
24882478

24892479
let radVal = valRadius * barWid;
@@ -2492,29 +2482,24 @@ function bars(opts) {
24922482
for (let i = _dirX == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += _dirX) {
24932483
let yVal = dataY[i];
24942484

2495-
// we can skip both, drawing and band clipping for alignment artifacts
2496-
if (yVal === undefined)
2485+
if (yVal == null)
24972486
continue;
24982487

2499-
/*
2500-
// interpolate upwards band clips
2501-
if (yVal == null) {
2502-
// if (hasBands)
2503-
// yVal = costlyLerp(i, idx0, idx1, _dirX, dataY);
2504-
// else
2488+
if (dataY0 != null) {
2489+
let yVal0 = dataY0[i] ?? 0;
2490+
2491+
if (yVal - yVal0 == 0)
25052492
continue;
2493+
2494+
fillToY = valToPosY(yVal0, scaleY, yDim, yOff);
25062495
}
2507-
*/
25082496

25092497
let xVal = scaleX.distr != 2 || disp != null ? dataX[i] : i;
25102498

25112499
// TODO: all xPos can be pre-computed once for all series in aligned set
25122500
let xPos = valToPosX(xVal, scaleX, xDim, xOff);
25132501
let yPos = valToPosY(ifNull(yVal, fillTo), scaleY, yDim, yOff);
25142502

2515-
if (dataY0 != null && yVal != null)
2516-
fillToY = valToPosY(dataY0[i], scaleY, yDim, yOff);
2517-
25182503
let lft = pxRound(xPos - xShift);
25192504
let btm = pxRound(max(yPos, fillToY));
25202505
let top = pxRound(min(yPos, fillToY));
@@ -2542,21 +2527,6 @@ function bars(opts) {
25422527
barHgt,
25432528
);
25442529
}
2545-
2546-
if (bandClipDir != 0 && (yVal != null || bandClipNulls)) {
2547-
if (_dirY * bandClipDir == 1) {
2548-
btm = top;
2549-
top = yLimit;
2550-
}
2551-
else {
2552-
top = btm;
2553-
btm = yLimit;
2554-
}
2555-
2556-
barHgt = btm - top;
2557-
2558-
rect(band, lft - strokeWidth / 2, top, barWid + strokeWidth, max(0, barHgt), 0, 0); // radius here?
2559-
}
25602530
}
25612531

25622532
if (strokeWidth > 0)
@@ -4072,7 +4042,7 @@ function uPlot(opts, data, then) {
40724042

40734043
// for all bands where this series is the top edge, create upwards clips using the bottom edges
40744044
// and apply clips + fill with band fill or dfltFill
4075-
bands.forEach((b, bi) => {
4045+
flags != 0 && bands.forEach((b, bi) => {
40764046
// isUpperEdge?
40774047
if (b.series[0] == si) {
40784048
let lowerEdge = series[b.series[1]];

0 commit comments

Comments
 (0)