Skip to content

Commit

Permalink
implement disp.y0 & disp.y1 in bars pathBuilder opts (#634)
Browse files Browse the repository at this point in the history
  • Loading branch information
leeoniya committed Jan 2, 2022
1 parent 4936199 commit 6d26659
Show file tree
Hide file tree
Showing 7 changed files with 317 additions and 36 deletions.
215 changes: 215 additions & 0 deletions demos/sparklines-bars.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Sparkline + Floating Bars + Gradients</title>
<meta name="viewport" content="width=device-width, initial-scale=1">

<link rel="stylesheet" href="../dist/uPlot.min.css">
</head>
<body>
<script src="../dist/uPlot.iife.js"></script>
<script>
let can = document.createElement("canvas");
let ctx = can.getContext("2d");

function scaleGradient(u, scaleKey, ori, scaleStops, discrete = false) {
let scale = u.scales[scaleKey];

// we want the stop below or at the scaleMax
// and the stop below or at the scaleMin, else the stop above scaleMin
let minStopIdx;
let maxStopIdx;

for (let i = 0; i < scaleStops.length; i++) {
let stopVal = scaleStops[i][0];

if (stopVal <= scale.min || minStopIdx == null)
minStopIdx = i;

maxStopIdx = i;

if (stopVal >= scale.max)
break;
}

if (minStopIdx == maxStopIdx)
return scaleStops[minStopIdx][1];

let minStopVal = scaleStops[minStopIdx][0];
let maxStopVal = scaleStops[maxStopIdx][0];

if (minStopVal == -Infinity)
minStopVal = scale.min;

if (maxStopVal == Infinity)
maxStopVal = scale.max;

let minStopPos = u.valToPos(minStopVal, scaleKey, true);
let maxStopPos = u.valToPos(maxStopVal, scaleKey, true);

let range = minStopPos - maxStopPos;

let x0, y0, x1, y1;

if (ori == 1) {
x0 = x1 = 0;
y0 = minStopPos;
y1 = maxStopPos;
}
else {
y0 = y1 = 0;
x0 = minStopPos;
x1 = maxStopPos;
}

let grd = ctx.createLinearGradient(x0, y0, x1, y1);

let prevColor;

for (let i = minStopIdx; i <= maxStopIdx; i++) {
let s = scaleStops[i];

let stopPos = i == minStopIdx ? minStopPos : i == maxStopIdx ? maxStopPos : u.valToPos(s[0], scaleKey, true);
let pct = (minStopPos - stopPos) / range;

if (discrete && i > minStopIdx)
grd.addColorStop(pct, prevColor);

grd.addColorStop(pct, prevColor = s[1]);
}

return grd;
}


let data = [
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
[5, -5, 0, 1, 5, 9, 10, 15, 5, -10, -15, -20, -20, -5, 0, 5],
];

// bar lows & highs
data.push(data[1].map(v => v - 5));
data.push(data[1].map(v => v + 5));


let opts = {
width: 800,
height: 400,
drawOrder: ["series", "axes"],
cursor: {show: false},
select: {show: false},
legend: {show: false},
scales: {
x: {
time: false
},
y: {
range: (u, dataMin, dataMax) => [dataMin, dataMax],
}
},
axes: [
{
show: false,
},
{
size: 0,
splits: [0],
ticks: {
show: false,
},
grid: {
width: 1,
stroke: "gray",
dash: [3, 3],
}
}
],
series: [
{},
{
points: {
show: false,
},
fill: (u, seriesIdx) => {
let s = u.series[seriesIdx];
let sc = u.scales[s.scale];

return scaleGradient(u, s.scale, 1, [
[sc.min, "red"],
[ 0, "white"],
[sc.max, "green"],
]);
},
stroke: (u, seriesIdx) => {
let s = u.series[seriesIdx];
let sc = u.scales[s.scale];

return scaleGradient(u, s.scale, 1, [
[sc.min, "red"],
[ 0, "green"],
], 1, true);
},
},
{
width: 0,
points: {
show: false,
},
fill: (u, seriesIdx) => {
let s = u.series[seriesIdx];
let sc = u.scales[s.scale];

return scaleGradient(u, s.scale, 1, [
[sc.min, "red"],
[ 0, "green"],
], 1, true);
},
paths: uPlot.paths.bars({
disp: {
// bar lows
y0: {
unit: 1,
values: (u, seriesIdx) => u.data[2],
},
// bar highs
y1: {
unit: 1,
values: (u, seriesIdx) => u.data[3],
},
}
})
}
],
};

let u = new uPlot(opts, data, document.body);

// with explicit bar colors
let opts2 = uPlot.assign({}, opts);
opts2.series[2].fill = null;
opts2.series[2].paths = uPlot.paths.bars({
disp: {
// bar lows
y0: {
unit: 1,
values: (u, seriesIdx) => u.data[2],
},
// bar highs
y1: {
unit: 1,
values: (u, seriesIdx) => u.data[3],
},
fill: {
unit: 3,
values: (u, seriesIdx) => u.data[2].map((v, i) => {
return v < 0 || u.data[3][i] < 0 ? "red" : "green";
})
}
}
});

let u2 = new uPlot(opts2, data, document.body);
</script>
</body>
</html>
34 changes: 25 additions & 9 deletions dist/uPlot.cjs.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021, Leon Sorokin
* Copyright (c) 2022, Leon Sorokin
* All rights reserved. (MIT Licensed)
*
* uPlot.js (渭Plot)
Expand Down Expand Up @@ -2046,7 +2046,7 @@ function bars(opts) {
let strokeColors = null;
let strokePaths = null;

if (dispFills != null && dispStrokes != null) {
if (dispFills != null && (strokeWidth == 0 || dispStrokes != null)) {
multiPath = true;

fillColors = dispFills.values(u, seriesIdx, idx0, idx1);
Expand All @@ -2056,12 +2056,14 @@ function bars(opts) {
fillPaths.set(color, new Path2D());
});

strokeColors = dispStrokes.values(u, seriesIdx, idx0, idx1);
strokePaths = new Map();
(new Set(strokeColors)).forEach(color => {
if (color != null)
strokePaths.set(color, new Path2D());
});
if (strokeWidth > 0) {
strokeColors = dispStrokes.values(u, seriesIdx, idx0, idx1);
strokePaths = new Map();
(new Set(strokeColors)).forEach(color => {
if (color != null)
strokePaths.set(color, new Path2D());
});
}
}

let { x0, size } = disp;
Expand Down Expand Up @@ -2131,6 +2133,15 @@ function bars(opts) {
const stroke = multiPath ? null : new Path2D();
const band = _paths.band;

let { y0, y1 } = disp;

let dataY0 = null;

if (y0 != null && y1 != null) {
dataY = y1.values(u, seriesIdx, idx0, idx1);
dataY0 = y0.values(u, seriesIdx, idx0, idx1);
}

for (let i = _dirX == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += _dirX) {
let yVal = dataY[i];

Expand All @@ -2148,7 +2159,10 @@ function bars(opts) {

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

if (dataY0 != null && yVal != null)
y0Pos = valToPosY(dataY0[i], scaleY, yDim, yOff);

let lft = pxRound(xPos - xShift);
let btm = pxRound(max(yPos, y0Pos));
Expand All @@ -2158,6 +2172,8 @@ function bars(opts) {

let r = radius * barWid;

console.log(lft, btm, top, barHgt);

if (yVal != null) { // && yVal != fillToY (0 height bar)
if (multiPath) {
if (strokeWidth > 0 && strokeColors[i] != null)
Expand Down
2 changes: 2 additions & 0 deletions dist/uPlot.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,8 @@ declare namespace uPlot {
export interface BarsPathBuilderDisplay {
x0?: BarsPathBuilderFacet;
// x1?: BarsPathBuilderFacet;
y0?: BarsPathBuilderFacet;
y1?: BarsPathBuilderFacet;
size?: BarsPathBuilderFacet;
fill?: BarsPathBuilderFacet;
stroke?: BarsPathBuilderFacet;
Expand Down
34 changes: 25 additions & 9 deletions dist/uPlot.esm.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021, Leon Sorokin
* Copyright (c) 2022, Leon Sorokin
* All rights reserved. (MIT Licensed)
*
* uPlot.js (渭Plot)
Expand Down Expand Up @@ -2044,7 +2044,7 @@ function bars(opts) {
let strokeColors = null;
let strokePaths = null;

if (dispFills != null && dispStrokes != null) {
if (dispFills != null && (strokeWidth == 0 || dispStrokes != null)) {
multiPath = true;

fillColors = dispFills.values(u, seriesIdx, idx0, idx1);
Expand All @@ -2054,12 +2054,14 @@ function bars(opts) {
fillPaths.set(color, new Path2D());
});

strokeColors = dispStrokes.values(u, seriesIdx, idx0, idx1);
strokePaths = new Map();
(new Set(strokeColors)).forEach(color => {
if (color != null)
strokePaths.set(color, new Path2D());
});
if (strokeWidth > 0) {
strokeColors = dispStrokes.values(u, seriesIdx, idx0, idx1);
strokePaths = new Map();
(new Set(strokeColors)).forEach(color => {
if (color != null)
strokePaths.set(color, new Path2D());
});
}
}

let { x0, size } = disp;
Expand Down Expand Up @@ -2129,6 +2131,15 @@ function bars(opts) {
const stroke = multiPath ? null : new Path2D();
const band = _paths.band;

let { y0, y1 } = disp;

let dataY0 = null;

if (y0 != null && y1 != null) {
dataY = y1.values(u, seriesIdx, idx0, idx1);
dataY0 = y0.values(u, seriesIdx, idx0, idx1);
}

for (let i = _dirX == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += _dirX) {
let yVal = dataY[i];

Expand All @@ -2146,7 +2157,10 @@ function bars(opts) {

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

if (dataY0 != null && yVal != null)
y0Pos = valToPosY(dataY0[i], scaleY, yDim, yOff);

let lft = pxRound(xPos - xShift);
let btm = pxRound(max(yPos, y0Pos));
Expand All @@ -2156,6 +2170,8 @@ function bars(opts) {

let r = radius * barWid;

console.log(lft, btm, top, barHgt);

if (yVal != null) { // && yVal != fillToY (0 height bar)
if (multiPath) {
if (strokeWidth > 0 && strokeColors[i] != null)
Expand Down

0 comments on commit 6d26659

Please sign in to comment.