From 7d9eb8576650691f586766953e04feb42d89af46 Mon Sep 17 00:00:00 2001 From: "DESKTOP-H6RLJ9U\\fadid" Date: Wed, 2 Aug 2023 08:52:07 +0300 Subject: [PATCH 1/3] Apply zooming with grouped bars --- demos/bars-grouped-stacked.html | 75 ++++---- demos/grouped-bars.js | 318 +++++++++++++++++++++++--------- demos/multi-bars.html | 268 ++++++++++++++++----------- 3 files changed, 431 insertions(+), 230 deletions(-) diff --git a/demos/bars-grouped-stacked.html b/demos/bars-grouped-stacked.html index 54bc29e1..1a3961f8 100644 --- a/demos/bars-grouped-stacked.html +++ b/demos/bars-grouped-stacked.html @@ -1,11 +1,11 @@ - + - + Bars (Grouped or Stacked) - + - + @@ -43,22 +43,22 @@ let series = o.series; const opts = { - width: ori == 0 ? 800 : 400, + width: ori == 0 ? 800 : 400, height: ori == 0 ? 400 : 800, scales: { y: { range: [0, null], ori: ori == 0 ? 1 : 0, - // dir: ori == 0 ? 1 : -1, - } + // dir: ori == 0 ? 1 : -1, + }, }, bands, axes: [ { - // rotate: -45, + // rotate: -45, }, { - // show: false, + // show: false, side: ori == 0 ? 3 : 0, }, ], @@ -66,16 +66,19 @@ live: false, markers: { width: 0, - } + }, }, padding: [null, 0, null, 0], series, plugins: [ - seriesBarsPlugin({ - ori, - dir, - stacked, - }), + seriesBarsPlugin( + { + ori, + dir, + stacked, + }, + data + ), ], }; @@ -83,7 +86,7 @@ } function makeChart2(opts, data) { - let { bands, data: _data } = stack(data, i => false); + let { bands, data: _data } = stack(data, (i) => false); makeChart(opts, _data, bands); } @@ -109,19 +112,19 @@ width: 0, }, { - label: "Metric 3", + label: "Metric 3", fill: "#BB1133", width: 0, }, ]; - makeChart({series, ori: 0, dir: 1}, data); - makeChart2({series, ori: 0, dir: 1, stacked: true}, data); + makeChart({ series, ori: 0, dir: 1 }, data); + makeChart2({ series, ori: 0, dir: 1, stacked: true }, data); document.body.appendChild(document.createElement("div")); - makeChart({series, ori: 1, dir: -1}, data); - makeChart2({series, ori: 1, dir: -1, stacked: true}, data); + makeChart({ series, ori: 1, dir: -1 }, data); + makeChart2({ series, ori: 1, dir: -1, stacked: true }, data); document.body.appendChild(document.createElement("div")); } @@ -142,20 +145,15 @@ }, ]; - makeChart({series, ori: 0, dir: 1}, data); - makeChart2({series, ori: 0, dir: 1, stacked: true}, data); + makeChart({ series, ori: 0, dir: 1 }, data); + makeChart2({ series, ori: 0, dir: 1, stacked: true }, data); document.body.appendChild(document.createElement("div")); } { // one group, multi bar - let data = [ - ["Group A"], - [1], - [3], - [5], - ]; + let data = [["Group A"], [1], [3], [5]]; let series = [ {}, @@ -170,24 +168,21 @@ width: 0, }, { - label: "Metric 3", + label: "Metric 3", fill: "#BB1133", width: 0, }, ]; - makeChart({series, ori: 0, dir: 1}, data); - makeChart2({series, ori: 0, dir: 1, stacked: true}, data); + makeChart({ series, ori: 0, dir: 1 }, data); + makeChart2({ series, ori: 0, dir: 1, stacked: true }, data); document.body.appendChild(document.createElement("div")); } { // one group, one bar - let data = [ - ["Group A"], - [1], - ]; + let data = [["Group A"], [1]]; let series = [ {}, @@ -198,11 +193,11 @@ }, ]; - makeChart({series, ori: 0, dir: 1}, data); - makeChart2({series, ori: 0, dir: 1, stacked: true}, data); + makeChart({ series, ori: 0, dir: 1 }, data); + makeChart2({ series, ori: 0, dir: 1, stacked: true }, data); document.body.appendChild(document.createElement("div")); } - \ No newline at end of file + diff --git a/demos/grouped-bars.js b/demos/grouped-bars.js index 9b1cd512..e3f3a78c 100644 --- a/demos/grouped-bars.js +++ b/demos/grouped-bars.js @@ -1,4 +1,4 @@ -function seriesBarsPlugin(opts) { +function seriesBarsPlugin(opts, data) { let pxRatio; let font; @@ -13,30 +13,50 @@ function seriesBarsPlugin(opts) { setPxRatio(); - window.addEventListener('dppxchange', setPxRatio); + window.addEventListener("dppxchange", setPxRatio); - const ori = opts.ori; - const dir = opts.dir; - const stacked = opts.stacked; + const ori = opts.ori; + const dir = opts.dir; + const stacked = opts.stacked; const groupWidth = 0.9; const groupDistr = SPACE_BETWEEN; - const barWidth = 1; - const barDistr = SPACE_BETWEEN; + const barWidth = 1; + const barDistr = SPACE_BETWEEN; - function distrTwo(groupCount, barCount, barSpread = true, _groupWidth = groupWidth) { - let out = Array.from({length: barCount}, () => ({ + function distrTwo( + groupCount, + barCount, + barSpread = true, + _groupWidth = groupWidth + ) { + let out = Array.from({ length: barCount }, () => ({ offs: Array(groupCount).fill(0), size: Array(groupCount).fill(0), })); - distr(groupCount, _groupWidth, groupDistr, null, (groupIdx, groupOffPct, groupDimPct) => { - distr(barCount, barWidth, barDistr, null, (barIdx, barOffPct, barDimPct) => { - out[barIdx].offs[groupIdx] = groupOffPct + (barSpread ? (groupDimPct * barOffPct) : 0); - out[barIdx].size[groupIdx] = groupDimPct * (barSpread ? barDimPct : 1); - }); - }); + distr( + groupCount, + _groupWidth, + groupDistr, + null, + (groupIdx, groupOffPct, groupDimPct) => { + distr( + barCount, + barWidth, + barDistr, + null, + (barIdx, barOffPct, barDimPct) => { + out[barIdx].offs[groupIdx] = + groupOffPct + + (barSpread ? groupDimPct * barOffPct : 0); + out[barIdx].size[groupIdx] = + groupDimPct * (barSpread ? barDimPct : 1); + } + ); + } + ); return out; } @@ -49,16 +69,18 @@ function seriesBarsPlugin(opts) { disp: { x0: { unit: 2, - // discr: false, (unary, discrete, continuous) - values: (u, seriesIdx, idx0, idx1) => barsPctLayout[seriesIdx].offs, + // discr: false, (unary, discrete, continuous) + values: (u, seriesIdx, idx0, idx1) => + barsPctLayout[seriesIdx].offs, }, size: { unit: 2, - // discr: true, - values: (u, seriesIdx, idx0, idx1) => barsPctLayout[seriesIdx].size, + // discr: true, + values: (u, seriesIdx, idx0, idx1) => + barsPctLayout[seriesIdx].size, }, ...opts.disp, - /* + /* // e.g. variable size via scale (will compute offsets from known values) x1: { units: 1, @@ -70,39 +92,83 @@ function seriesBarsPlugin(opts) { // we get back raw canvas coords (included axes & padding). translate to the plotting area origin lft -= u.bbox.left; top -= u.bbox.top; - qt.add({x: lft, y: top, w: wid, h: hgt, sidx: seriesIdx, didx: dataIdx}); + qt.add({ + x: lft, + y: top, + w: wid, + h: hgt, + sidx: seriesIdx, + didx: dataIdx, + }); }, }); function drawPoints(u, sidx, i0, i1) { u.ctx.save(); - u.ctx.font = font; - u.ctx.fillStyle = "black"; - - uPlot.orient(u, sidx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim, moveTo, lineTo, rect) => { - const _dir = dir * (ori == 0 ? 1 : -1); - - const wid = Math.round(barsPctLayout[sidx].size[0] * xDim); - - barsPctLayout[sidx].offs.forEach((offs, ix) => { - if (dataY[ix] != null) { - let x0 = xDim * offs; - let lft = Math.round(xOff + (_dir == 1 ? x0 : xDim - x0 - wid)); - let barWid = Math.round(wid); - - let yPos = valToPosY(dataY[ix], scaleY, yDim, yOff); - - let x = ori == 0 ? Math.round(lft + barWid/2) : Math.round(yPos); - let y = ori == 0 ? Math.round(yPos) : Math.round(lft + barWid/2); - - u.ctx.textAlign = ori == 0 ? "center" : dataY[ix] >= 0 ? "left" : "right"; - u.ctx.textBaseline = ori == 1 ? "middle" : dataY[ix] >= 0 ? "bottom" : "top"; - - u.ctx.fillText(dataY[ix], x, y); - } - }); - }); + u.ctx.font = font; + u.ctx.fillStyle = "black"; + + uPlot.orient( + u, + sidx, + ( + series, + dataX, + dataY, + scaleX, + scaleY, + valToPosX, + valToPosY, + xOff, + yOff, + xDim, + yDim, + moveTo, + lineTo, + rect + ) => { + const _dir = dir * (ori == 0 ? 1 : -1); + + const wid = Math.round(barsPctLayout[sidx].size[0] * xDim); + + barsPctLayout[sidx].offs.forEach((offs, ix) => { + if (dataY[ix] != null) { + let x0 = xDim * offs; + let lft = Math.round( + xOff + (_dir == 1 ? x0 : xDim - x0 - wid) + ); + let barWid = Math.round(wid); + + let yPos = valToPosY(dataY[ix], scaleY, yDim, yOff); + + let x = + ori == 0 + ? Math.round(lft + barWid / 2) + : Math.round(yPos); + let y = + ori == 0 + ? Math.round(yPos) + : Math.round(lft + barWid / 2); + + u.ctx.textAlign = + ori == 0 + ? "center" + : dataY[ix] >= 0 + ? "left" + : "right"; + u.ctx.textBaseline = + ori == 1 + ? "middle" + : dataY[ix] >= 0 + ? "bottom" + : "top"; + + u.ctx.fillText(dataY[ix], x, y); + } + }); + } + ); u.ctx.restore(); } @@ -115,18 +181,81 @@ function seriesBarsPlugin(opts) { let qt; return { + init: [ + (u) => { + u.over.ondblclick = () => { + u.setData(data); + }; + }, + ], + setSelect: [ + (u) => { + if (u.select.width > 0) { + const min = u.posToVal(u.select.left, "x"); + const max = u.posToVal(u.select.left + u.select.width, "x"); + const roundedMin = Object.is(Math.ceil(min), -0) + ? 0 + : Math.ceil(min); + + const roundedMax = Math.ceil(max); + + const newData = []; + + data.forEach((singleData) => { + newData.push( + singleData?.slice(roundedMin, roundedMax + 1) + ); + }); + let minM = newData[0][0]; + let maxM = newData[0][newData[0].length - 1]; + + u.setData(newData, false); + let pctOffset = 0; + + distr( + u.data[0].length, + groupWidth, + groupDistr, + 0, + (di, lftPct, widPct) => { + pctOffset = lftPct + widPct / 2; + } + ); + + const rn = maxM - minM; + + if (pctOffset === 0.5) minM -= rn; + else { + const upScale = 1 / (1 - pctOffset * 2); + const offset = (upScale * rn - rn) / 2; + + minM -= offset; + maxM += offset; + } + u.setScale("x", { minM, maxM }); + u.setSelect({ width: 0, height: 0 }, false); + } + }, + ], hooks: { - drawClear: u => { + drawClear: (u) => { qt = qt || new Quadtree(0, 0, u.bbox.width, u.bbox.height); qt.clear(); // force-clear the path cache to cause drawBars() to rebuild new quadtree - u.series.forEach(s => { + u.series.forEach((s) => { s._paths = null; }); - barsPctLayout = [null].concat(distrTwo(u.data[0].length, u.series.length - 1 - ignore.length, !stacked, groupWidth)); + barsPctLayout = [null].concat( + distrTwo( + u.data[0].length, + u.series.length - 1 - ignore.length, + !stacked, + groupWidth + ) + ); // TODOL only do on setData, not every redraw if (opts.disp?.fill != null) { @@ -151,7 +280,6 @@ function seriesBarsPlugin(opts) { let hRect; uPlot.assign(opts, { - select: {show: false}, cursor: { x: false, y: false, @@ -162,13 +290,24 @@ function seriesBarsPlugin(opts) { let cx = u.cursor.left * pxRatio; let cy = u.cursor.top * pxRatio; - qt.get(cx, cy, 1, 1, o => { - if (pointWithin(cx, cy, o.x, o.y, o.x + o.w, o.y + o.h)) + qt.get(cx, cy, 1, 1, (o) => { + if ( + pointWithin( + cx, + cy, + o.x, + o.y, + o.x + o.w, + o.y + o.h + ) + ) hRect = o; }); } - return hRect && seriesIdx == hRect.sidx ? hRect.didx : null; + return hRect && seriesIdx == hRect.sidx + ? hRect.didx + : null; }, points: { fill: "rgba(255,255,255, 0.3)", @@ -176,13 +315,13 @@ function seriesBarsPlugin(opts) { let isHovered = hRect && seriesIdx == hRect.sidx; return { - left: isHovered ? hRect.x / pxRatio : -10, - top: isHovered ? hRect.y / pxRatio : -10, - width: isHovered ? hRect.w / pxRatio : 0, + left: isHovered ? hRect.x / pxRatio : -10, + top: isHovered ? hRect.y / pxRatio : -10, + width: isHovered ? hRect.w / pxRatio : 0, height: isHovered ? hRect.h / pxRatio : 0, }; - } - } + }, + }, }, scales: { x: { @@ -190,21 +329,26 @@ function seriesBarsPlugin(opts) { distr: 2, ori, dir, - // auto: true, + // auto: true, range: (u, min, max) => { - min = 0; - max = Math.max(1, u.data[0].length - 1); + min = u.data[0][0]; + max = u.data[0][u.data[0].length - 1]; let pctOffset = 0; - distr(u.data[0].length, groupWidth, groupDistr, 0, (di, lftPct, widPct) => { - pctOffset = lftPct + widPct / 2; - }); + distr( + u.data[0].length, + groupWidth, + groupDistr, + 0, + (di, lftPct, widPct) => { + pctOffset = lftPct + widPct / 2; + } + ); let rn = max - min; - if (pctOffset == 0.5) - min -= rn; + if (pctOffset == 0.5) min -= rn; else { let upScale = 1 / (1 - pctOffset * 2); let offset = (upScale * rn - rn) / 2; @@ -214,14 +358,14 @@ function seriesBarsPlugin(opts) { } return [min, max]; - } + }, }, - rend: yScaleOpts, - size: yScaleOpts, - mem: yScaleOpts, - inter: yScaleOpts, + rend: yScaleOpts, + size: yScaleOpts, + mem: yScaleOpts, + inter: yScaleOpts, toggle: yScaleOpts, - } + }, }); if (ori == 1) { @@ -234,28 +378,28 @@ function seriesBarsPlugin(opts) { splits = u._data[0].slice(); return _dir == 1 ? splits : splits.reverse(); }, - values: u => u.data[0], - gap: 15, - size: ori == 0 ? 40 : 150, - labelSize: 20, - grid: {show: false}, - ticks: {show: false}, - - side: ori == 0 ? 2 : 3, + values: (u) => u.data[0], + gap: 15, + size: ori == 0 ? 40 : 150, + labelSize: 20, + grid: { show: false }, + ticks: { show: false }, + + side: ori == 0 ? 2 : 3, }); opts.series.forEach((s, i) => { if (i > 0 && !ignore.includes(i)) { uPlot.assign(s, { - // pxAlign: false, - // stroke: "rgba(255,0,0,0.5)", + // pxAlign: false, + // stroke: "rgba(255,0,0,0.5)", paths: barsBuilder, points: { - show: drawPoints - } + show: drawPoints, + }, }); } }); - } + }, }; -} \ No newline at end of file +} diff --git a/demos/multi-bars.html b/demos/multi-bars.html index df8dd308..c24b2e36 100644 --- a/demos/multi-bars.html +++ b/demos/multi-bars.html @@ -1,11 +1,11 @@ - + - + Multi-Bars - + - + @@ -43,22 +43,22 @@ let series = o.series; const opts = { - width: ori == 0 ? 800 : 400, + width: ori == 0 ? 800 : 400, height: ori == 0 ? 400 : 800, scales: { y: { range: [0, null], ori: ori == 0 ? 1 : 0, - // dir: ori == 0 ? 1 : -1, - }, + // dir: ori == 0 ? 1 : -1, + } }, bands, axes: [ { - // rotate: -45, + // rotate: -45, }, { - // show: false, + // show: false, side: ori == 0 ? 3 : 0, }, ], @@ -66,19 +66,16 @@ live: false, markers: { width: 0, - }, + } }, padding: [null, 0, null, 0], series, plugins: [ - seriesBarsPlugin( - { - ori, - dir, - stacked, - }, - data - ), + seriesBarsPlugin({ + ori, + dir, + stacked, + }), ], }; @@ -86,7 +83,7 @@ } function makeChart2(opts, data) { - let { bands, data: _data } = stack(data, (i) => false); + let { bands, data: _data } = stack(data, i => false); makeChart(opts, _data, bands); } @@ -112,19 +109,19 @@ width: 0, }, { - label: "Metric 3", + label: "Metric 3", fill: "#BB1133", width: 0, }, ]; - makeChart({ series, ori: 0, dir: 1 }, data); - makeChart2({ series, ori: 0, dir: 1, stacked: true }, data); + makeChart({series, ori: 0, dir: 1}, data); + makeChart2({series, ori: 0, dir: 1, stacked: true}, data); document.body.appendChild(document.createElement("div")); - makeChart({ series, ori: 1, dir: -1 }, data); - makeChart2({ series, ori: 1, dir: -1, stacked: true }, data); + makeChart({series, ori: 1, dir: -1}, data); + makeChart2({series, ori: 1, dir: -1, stacked: true}, data); document.body.appendChild(document.createElement("div")); } @@ -145,15 +142,20 @@ }, ]; - makeChart({ series, ori: 0, dir: 1 }, data); - makeChart2({ series, ori: 0, dir: 1, stacked: true }, data); + makeChart({series, ori: 0, dir: 1}, data); + makeChart2({series, ori: 0, dir: 1, stacked: true}, data); document.body.appendChild(document.createElement("div")); } { // one group, multi bar - let data = [["Group A"], [1], [3], [5]]; + let data = [ + ["Group A"], + [1], + [3], + [5], + ]; let series = [ {}, @@ -168,21 +170,24 @@ width: 0, }, { - label: "Metric 3", + label: "Metric 3", fill: "#BB1133", width: 0, }, ]; - makeChart({ series, ori: 0, dir: 1 }, data); - makeChart2({ series, ori: 0, dir: 1, stacked: true }, data); + makeChart({series, ori: 0, dir: 1}, data); + makeChart2({series, ori: 0, dir: 1, stacked: true}, data); document.body.appendChild(document.createElement("div")); } { // one group, one bar - let data = [["Group A"], [1]]; + let data = [ + ["Group A"], + [1], + ]; let series = [ {}, @@ -193,11 +198,11 @@ }, ]; - makeChart({ series, ori: 0, dir: 1 }, data); - makeChart2({ series, ori: 0, dir: 1, stacked: true }, data); + makeChart({series, ori: 0, dir: 1}, data); + makeChart2({series, ori: 0, dir: 1, stacked: true}, data); document.body.appendChild(document.createElement("div")); } - + \ No newline at end of file diff --git a/demos/grouped-bars.js b/demos/grouped-bars.js index e3f3a78c..9b1cd512 100644 --- a/demos/grouped-bars.js +++ b/demos/grouped-bars.js @@ -1,4 +1,4 @@ -function seriesBarsPlugin(opts, data) { +function seriesBarsPlugin(opts) { let pxRatio; let font; @@ -13,50 +13,30 @@ function seriesBarsPlugin(opts, data) { setPxRatio(); - window.addEventListener("dppxchange", setPxRatio); + window.addEventListener('dppxchange', setPxRatio); - const ori = opts.ori; - const dir = opts.dir; - const stacked = opts.stacked; + const ori = opts.ori; + const dir = opts.dir; + const stacked = opts.stacked; const groupWidth = 0.9; const groupDistr = SPACE_BETWEEN; - const barWidth = 1; - const barDistr = SPACE_BETWEEN; + const barWidth = 1; + const barDistr = SPACE_BETWEEN; - function distrTwo( - groupCount, - barCount, - barSpread = true, - _groupWidth = groupWidth - ) { - let out = Array.from({ length: barCount }, () => ({ + function distrTwo(groupCount, barCount, barSpread = true, _groupWidth = groupWidth) { + let out = Array.from({length: barCount}, () => ({ offs: Array(groupCount).fill(0), size: Array(groupCount).fill(0), })); - distr( - groupCount, - _groupWidth, - groupDistr, - null, - (groupIdx, groupOffPct, groupDimPct) => { - distr( - barCount, - barWidth, - barDistr, - null, - (barIdx, barOffPct, barDimPct) => { - out[barIdx].offs[groupIdx] = - groupOffPct + - (barSpread ? groupDimPct * barOffPct : 0); - out[barIdx].size[groupIdx] = - groupDimPct * (barSpread ? barDimPct : 1); - } - ); - } - ); + distr(groupCount, _groupWidth, groupDistr, null, (groupIdx, groupOffPct, groupDimPct) => { + distr(barCount, barWidth, barDistr, null, (barIdx, barOffPct, barDimPct) => { + out[barIdx].offs[groupIdx] = groupOffPct + (barSpread ? (groupDimPct * barOffPct) : 0); + out[barIdx].size[groupIdx] = groupDimPct * (barSpread ? barDimPct : 1); + }); + }); return out; } @@ -69,18 +49,16 @@ function seriesBarsPlugin(opts, data) { disp: { x0: { unit: 2, - // discr: false, (unary, discrete, continuous) - values: (u, seriesIdx, idx0, idx1) => - barsPctLayout[seriesIdx].offs, + // discr: false, (unary, discrete, continuous) + values: (u, seriesIdx, idx0, idx1) => barsPctLayout[seriesIdx].offs, }, size: { unit: 2, - // discr: true, - values: (u, seriesIdx, idx0, idx1) => - barsPctLayout[seriesIdx].size, + // discr: true, + values: (u, seriesIdx, idx0, idx1) => barsPctLayout[seriesIdx].size, }, ...opts.disp, - /* + /* // e.g. variable size via scale (will compute offsets from known values) x1: { units: 1, @@ -92,83 +70,39 @@ function seriesBarsPlugin(opts, data) { // we get back raw canvas coords (included axes & padding). translate to the plotting area origin lft -= u.bbox.left; top -= u.bbox.top; - qt.add({ - x: lft, - y: top, - w: wid, - h: hgt, - sidx: seriesIdx, - didx: dataIdx, - }); + qt.add({x: lft, y: top, w: wid, h: hgt, sidx: seriesIdx, didx: dataIdx}); }, }); function drawPoints(u, sidx, i0, i1) { u.ctx.save(); - u.ctx.font = font; - u.ctx.fillStyle = "black"; - - uPlot.orient( - u, - sidx, - ( - series, - dataX, - dataY, - scaleX, - scaleY, - valToPosX, - valToPosY, - xOff, - yOff, - xDim, - yDim, - moveTo, - lineTo, - rect - ) => { - const _dir = dir * (ori == 0 ? 1 : -1); - - const wid = Math.round(barsPctLayout[sidx].size[0] * xDim); - - barsPctLayout[sidx].offs.forEach((offs, ix) => { - if (dataY[ix] != null) { - let x0 = xDim * offs; - let lft = Math.round( - xOff + (_dir == 1 ? x0 : xDim - x0 - wid) - ); - let barWid = Math.round(wid); - - let yPos = valToPosY(dataY[ix], scaleY, yDim, yOff); - - let x = - ori == 0 - ? Math.round(lft + barWid / 2) - : Math.round(yPos); - let y = - ori == 0 - ? Math.round(yPos) - : Math.round(lft + barWid / 2); - - u.ctx.textAlign = - ori == 0 - ? "center" - : dataY[ix] >= 0 - ? "left" - : "right"; - u.ctx.textBaseline = - ori == 1 - ? "middle" - : dataY[ix] >= 0 - ? "bottom" - : "top"; - - u.ctx.fillText(dataY[ix], x, y); - } - }); - } - ); + u.ctx.font = font; + u.ctx.fillStyle = "black"; + + uPlot.orient(u, sidx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim, moveTo, lineTo, rect) => { + const _dir = dir * (ori == 0 ? 1 : -1); + + const wid = Math.round(barsPctLayout[sidx].size[0] * xDim); + + barsPctLayout[sidx].offs.forEach((offs, ix) => { + if (dataY[ix] != null) { + let x0 = xDim * offs; + let lft = Math.round(xOff + (_dir == 1 ? x0 : xDim - x0 - wid)); + let barWid = Math.round(wid); + + let yPos = valToPosY(dataY[ix], scaleY, yDim, yOff); + + let x = ori == 0 ? Math.round(lft + barWid/2) : Math.round(yPos); + let y = ori == 0 ? Math.round(yPos) : Math.round(lft + barWid/2); + + u.ctx.textAlign = ori == 0 ? "center" : dataY[ix] >= 0 ? "left" : "right"; + u.ctx.textBaseline = ori == 1 ? "middle" : dataY[ix] >= 0 ? "bottom" : "top"; + + u.ctx.fillText(dataY[ix], x, y); + } + }); + }); u.ctx.restore(); } @@ -181,81 +115,18 @@ function seriesBarsPlugin(opts, data) { let qt; return { - init: [ - (u) => { - u.over.ondblclick = () => { - u.setData(data); - }; - }, - ], - setSelect: [ - (u) => { - if (u.select.width > 0) { - const min = u.posToVal(u.select.left, "x"); - const max = u.posToVal(u.select.left + u.select.width, "x"); - const roundedMin = Object.is(Math.ceil(min), -0) - ? 0 - : Math.ceil(min); - - const roundedMax = Math.ceil(max); - - const newData = []; - - data.forEach((singleData) => { - newData.push( - singleData?.slice(roundedMin, roundedMax + 1) - ); - }); - let minM = newData[0][0]; - let maxM = newData[0][newData[0].length - 1]; - - u.setData(newData, false); - let pctOffset = 0; - - distr( - u.data[0].length, - groupWidth, - groupDistr, - 0, - (di, lftPct, widPct) => { - pctOffset = lftPct + widPct / 2; - } - ); - - const rn = maxM - minM; - - if (pctOffset === 0.5) minM -= rn; - else { - const upScale = 1 / (1 - pctOffset * 2); - const offset = (upScale * rn - rn) / 2; - - minM -= offset; - maxM += offset; - } - u.setScale("x", { minM, maxM }); - u.setSelect({ width: 0, height: 0 }, false); - } - }, - ], hooks: { - drawClear: (u) => { + drawClear: u => { qt = qt || new Quadtree(0, 0, u.bbox.width, u.bbox.height); qt.clear(); // force-clear the path cache to cause drawBars() to rebuild new quadtree - u.series.forEach((s) => { + u.series.forEach(s => { s._paths = null; }); - barsPctLayout = [null].concat( - distrTwo( - u.data[0].length, - u.series.length - 1 - ignore.length, - !stacked, - groupWidth - ) - ); + barsPctLayout = [null].concat(distrTwo(u.data[0].length, u.series.length - 1 - ignore.length, !stacked, groupWidth)); // TODOL only do on setData, not every redraw if (opts.disp?.fill != null) { @@ -280,6 +151,7 @@ function seriesBarsPlugin(opts, data) { let hRect; uPlot.assign(opts, { + select: {show: false}, cursor: { x: false, y: false, @@ -290,24 +162,13 @@ function seriesBarsPlugin(opts, data) { let cx = u.cursor.left * pxRatio; let cy = u.cursor.top * pxRatio; - qt.get(cx, cy, 1, 1, (o) => { - if ( - pointWithin( - cx, - cy, - o.x, - o.y, - o.x + o.w, - o.y + o.h - ) - ) + qt.get(cx, cy, 1, 1, o => { + if (pointWithin(cx, cy, o.x, o.y, o.x + o.w, o.y + o.h)) hRect = o; }); } - return hRect && seriesIdx == hRect.sidx - ? hRect.didx - : null; + return hRect && seriesIdx == hRect.sidx ? hRect.didx : null; }, points: { fill: "rgba(255,255,255, 0.3)", @@ -315,13 +176,13 @@ function seriesBarsPlugin(opts, data) { let isHovered = hRect && seriesIdx == hRect.sidx; return { - left: isHovered ? hRect.x / pxRatio : -10, - top: isHovered ? hRect.y / pxRatio : -10, - width: isHovered ? hRect.w / pxRatio : 0, + left: isHovered ? hRect.x / pxRatio : -10, + top: isHovered ? hRect.y / pxRatio : -10, + width: isHovered ? hRect.w / pxRatio : 0, height: isHovered ? hRect.h / pxRatio : 0, }; - }, - }, + } + } }, scales: { x: { @@ -329,26 +190,21 @@ function seriesBarsPlugin(opts, data) { distr: 2, ori, dir, - // auto: true, + // auto: true, range: (u, min, max) => { - min = u.data[0][0]; - max = u.data[0][u.data[0].length - 1]; + min = 0; + max = Math.max(1, u.data[0].length - 1); let pctOffset = 0; - distr( - u.data[0].length, - groupWidth, - groupDistr, - 0, - (di, lftPct, widPct) => { - pctOffset = lftPct + widPct / 2; - } - ); + distr(u.data[0].length, groupWidth, groupDistr, 0, (di, lftPct, widPct) => { + pctOffset = lftPct + widPct / 2; + }); let rn = max - min; - if (pctOffset == 0.5) min -= rn; + if (pctOffset == 0.5) + min -= rn; else { let upScale = 1 / (1 - pctOffset * 2); let offset = (upScale * rn - rn) / 2; @@ -358,14 +214,14 @@ function seriesBarsPlugin(opts, data) { } return [min, max]; - }, + } }, - rend: yScaleOpts, - size: yScaleOpts, - mem: yScaleOpts, - inter: yScaleOpts, + rend: yScaleOpts, + size: yScaleOpts, + mem: yScaleOpts, + inter: yScaleOpts, toggle: yScaleOpts, - }, + } }); if (ori == 1) { @@ -378,28 +234,28 @@ function seriesBarsPlugin(opts, data) { splits = u._data[0].slice(); return _dir == 1 ? splits : splits.reverse(); }, - values: (u) => u.data[0], - gap: 15, - size: ori == 0 ? 40 : 150, - labelSize: 20, - grid: { show: false }, - ticks: { show: false }, - - side: ori == 0 ? 2 : 3, + values: u => u.data[0], + gap: 15, + size: ori == 0 ? 40 : 150, + labelSize: 20, + grid: {show: false}, + ticks: {show: false}, + + side: ori == 0 ? 2 : 3, }); opts.series.forEach((s, i) => { if (i > 0 && !ignore.includes(i)) { uPlot.assign(s, { - // pxAlign: false, - // stroke: "rgba(255,0,0,0.5)", + // pxAlign: false, + // stroke: "rgba(255,0,0,0.5)", paths: barsBuilder, points: { - show: drawPoints, - }, + show: drawPoints + } }); } }); - }, + } }; -} +} \ No newline at end of file diff --git a/demos/multi-bars.html b/demos/multi-bars.html index c24b2e36..df8dd308 100644 --- a/demos/multi-bars.html +++ b/demos/multi-bars.html @@ -1,11 +1,11 @@ - + - + Multi-Bars - + - +