mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-11-04 09:35:07 +01:00 
			
		
		
		
	Remove in-dev uplot scatter from this branch
This commit is contained in:
		@@ -1,627 +0,0 @@
 | 
				
			|||||||
<script>
 | 
					 | 
				
			||||||
    import Quadtree from '@timohausmann/quadtree-js' // https://github.com/timohausmann/quadtree-js
 | 
					 | 
				
			||||||
    import uPlot from 'uplot'
 | 
					 | 
				
			||||||
    import { formatNumber } from '../units.js'
 | 
					 | 
				
			||||||
    import { onMount, onDestroy } from 'svelte'
 | 
					 | 
				
			||||||
    import { Card } from 'sveltestrap'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let plotWrapper = null
 | 
					 | 
				
			||||||
    let uplot = null
 | 
					 | 
				
			||||||
    let timeoutId = null
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function randInt(min, max) {
 | 
					 | 
				
			||||||
        return Math.floor(Math.random() * (max - min + 1)) + min;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function filledArr(len, val) {
 | 
					 | 
				
			||||||
        let arr = Array(len);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (typeof val == "function") {
 | 
					 | 
				
			||||||
            for (let i = 0; i < len; ++i)
 | 
					 | 
				
			||||||
                arr[i] = val(i);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else {
 | 
					 | 
				
			||||||
            for (let i = 0; i < len; ++i)
 | 
					 | 
				
			||||||
                arr[i] = val;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return arr;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let points = 10000;
 | 
					 | 
				
			||||||
    let series = 5;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    console.time("prep");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let data = filledArr(series, v => [
 | 
					 | 
				
			||||||
        filledArr(points, i => randInt(0,500)),
 | 
					 | 
				
			||||||
        filledArr(points, i => randInt(0,500)),
 | 
					 | 
				
			||||||
    ]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    data[0] = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    console.timeEnd("prep");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    console.log(data);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const drawPoints = (u, seriesIdx, idx0, idx1) => {
 | 
					 | 
				
			||||||
        const size = 5 * devicePixelRatio;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        uPlot.orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim, moveTo, lineTo, rect, arc) => {
 | 
					 | 
				
			||||||
            let d = u.data[seriesIdx];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            u.ctx.fillStyle = series.stroke();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let deg360 = 2 * Math.PI;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            console.time("points");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //	let cir = new Path2D();
 | 
					 | 
				
			||||||
        //	cir.moveTo(0, 0);
 | 
					 | 
				
			||||||
        //	arc(cir, 0, 0, 3, 0, deg360);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Create transformation matrix that moves 200 points to the right
 | 
					 | 
				
			||||||
        //	let m = document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGMatrix();
 | 
					 | 
				
			||||||
        //	m.a = 1;   m.b = 0;
 | 
					 | 
				
			||||||
        //	m.c = 0;   m.d = 1;
 | 
					 | 
				
			||||||
        //	m.e = 200; m.f = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let p = new Path2D();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for (let i = 0; i < d[0].length; i++) {
 | 
					 | 
				
			||||||
                let xVal = d[0][i];
 | 
					 | 
				
			||||||
                let yVal = d[1][i];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (xVal >= scaleX.min && xVal <= scaleX.max && yVal >= scaleY.min && yVal <= scaleY.max) {
 | 
					 | 
				
			||||||
                    let cx = valToPosX(xVal, scaleX, xDim, xOff);
 | 
					 | 
				
			||||||
                    let cy = valToPosY(yVal, scaleY, yDim, yOff);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    p.moveTo(cx + size/2, cy);
 | 
					 | 
				
			||||||
                //	arc(p, cx, cy, 3, 0, deg360);
 | 
					 | 
				
			||||||
                    arc(p, cx, cy, size/2, 0, deg360);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                //	m.e = cx;
 | 
					 | 
				
			||||||
                //	m.f = cy;
 | 
					 | 
				
			||||||
                //	p.addPath(cir, m);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                //	qt.add({x: cx - 1.5, y: cy - 1.5, w: 3, h: 3, sidx: seriesIdx, didx: i});
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            console.timeEnd("points");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            u.ctx.fill(p);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return null;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const drawPoints2 = (u, seriesIdx, idx0, idx1) => {
 | 
					 | 
				
			||||||
        uPlot.orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim, moveTo, lineTo, rect, arc) => {
 | 
					 | 
				
			||||||
            let d = u.data[seriesIdx];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            u.ctx.fillStyle = series.fill();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let deg360 = 2 * Math.PI;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            console.time("points");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //	let cir = new Path2D();
 | 
					 | 
				
			||||||
        //	cir.moveTo(0, 0);
 | 
					 | 
				
			||||||
        //	arc(cir, 0, 0, 3, 0, deg360);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Create transformation matrix that moves 200 points to the right
 | 
					 | 
				
			||||||
        //	let m = document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGMatrix();
 | 
					 | 
				
			||||||
        //	m.a = 1;   m.b = 0;
 | 
					 | 
				
			||||||
        //	m.c = 0;   m.d = 1;
 | 
					 | 
				
			||||||
        //	m.e = 200; m.f = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let p = new Path2D();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let strokeWidth = 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for (let i = 0; i < d[0].length; i++) {
 | 
					 | 
				
			||||||
                let cx = valToPosX(d[0][i], scaleX, xDim, xOff);
 | 
					 | 
				
			||||||
                let cy = valToPosY(d[1][i], scaleY, yDim, yOff);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let size = d[2][i] * devicePixelRatio;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                p.moveTo(cx + size, cy);
 | 
					 | 
				
			||||||
                arc(p, cx, cy, size, 0, deg360);
 | 
					 | 
				
			||||||
                qt.add({
 | 
					 | 
				
			||||||
                    x: cx - size - strokeWidth/2 - u.bbox.left,
 | 
					 | 
				
			||||||
                    y: cy - size - strokeWidth/2 - u.bbox.top,
 | 
					 | 
				
			||||||
                    w: size * 2 + strokeWidth,
 | 
					 | 
				
			||||||
                    h: size * 2 + strokeWidth,
 | 
					 | 
				
			||||||
                    sidx: seriesIdx,
 | 
					 | 
				
			||||||
                    didx: i
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            console.timeEnd("points");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            u.ctx.fill(p);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            u.ctx.lineWidth = strokeWidth;
 | 
					 | 
				
			||||||
            u.ctx.strokeStyle = series.stroke();
 | 
					 | 
				
			||||||
            u.ctx.stroke(p);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return null;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // shape?
 | 
					 | 
				
			||||||
    const makeDrawPoints3 = (opts) => {
 | 
					 | 
				
			||||||
        let {/*size,*/ disp, each = () => {}} = opts;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return (u, seriesIdx, idx0, idx1) => {
 | 
					 | 
				
			||||||
            uPlot.orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim, moveTo, lineTo, rect, arc) => {
 | 
					 | 
				
			||||||
                let d = u.data[seriesIdx];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let strokeWidth = 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                u.ctx.save();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                u.ctx.rect(u.bbox.left, u.bbox.top, u.bbox.width, u.bbox.height);
 | 
					 | 
				
			||||||
                u.ctx.clip();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                u.ctx.fillStyle = series.fill();
 | 
					 | 
				
			||||||
                u.ctx.strokeStyle = series.stroke();
 | 
					 | 
				
			||||||
                u.ctx.lineWidth = strokeWidth;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let deg360 = 2 * Math.PI;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                console.time("points");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //	let cir = new Path2D();
 | 
					 | 
				
			||||||
            //	cir.moveTo(0, 0);
 | 
					 | 
				
			||||||
            //	arc(cir, 0, 0, 3, 0, deg360);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Create transformation matrix that moves 200 points to the right
 | 
					 | 
				
			||||||
            //	let m = document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGMatrix();
 | 
					 | 
				
			||||||
            //	m.a = 1;   m.b = 0;
 | 
					 | 
				
			||||||
            //	m.c = 0;   m.d = 1;
 | 
					 | 
				
			||||||
            //	m.e = 200; m.f = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // compute bubble dims
 | 
					 | 
				
			||||||
                let sizes = disp.size.values(u, seriesIdx, idx0, idx1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // todo: this depends on direction & orientation
 | 
					 | 
				
			||||||
                // todo: calc once per redraw, not per path
 | 
					 | 
				
			||||||
                let filtLft = u.posToVal(-maxSize / 2, scaleX.key);
 | 
					 | 
				
			||||||
                let filtRgt = u.posToVal(u.bbox.width / devicePixelRatio + maxSize / 2, scaleX.key);
 | 
					 | 
				
			||||||
                let filtBtm = u.posToVal(u.bbox.height / devicePixelRatio + maxSize / 2, scaleY.key);
 | 
					 | 
				
			||||||
                let filtTop = u.posToVal(-maxSize / 2, scaleY.key);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                for (let i = 0; i < d[0].length; i++) {
 | 
					 | 
				
			||||||
                    let xVal = d[0][i];
 | 
					 | 
				
			||||||
                    let yVal = d[1][i];
 | 
					 | 
				
			||||||
                    let size = sizes[i] * devicePixelRatio;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (xVal >= filtLft && xVal <= filtRgt && yVal >= filtBtm && yVal <= filtTop) {
 | 
					 | 
				
			||||||
                        let cx = valToPosX(xVal, scaleX, xDim, xOff);
 | 
					 | 
				
			||||||
                        let cy = valToPosY(yVal, scaleY, yDim, yOff);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        u.ctx.moveTo(cx + size/2, cy);
 | 
					 | 
				
			||||||
                        u.ctx.beginPath();
 | 
					 | 
				
			||||||
                        u.ctx.arc(cx, cy, size/2, 0, deg360);
 | 
					 | 
				
			||||||
                        u.ctx.fill();
 | 
					 | 
				
			||||||
                        u.ctx.stroke();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        each(u, seriesIdx, i,
 | 
					 | 
				
			||||||
                            cx - size/2 - strokeWidth/2,
 | 
					 | 
				
			||||||
                            cy - size/2 - strokeWidth/2,
 | 
					 | 
				
			||||||
                            size + strokeWidth,
 | 
					 | 
				
			||||||
                            size + strokeWidth
 | 
					 | 
				
			||||||
                        );
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                console.timeEnd("points");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                u.ctx.restore();
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return null;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInPath
 | 
					 | 
				
			||||||
    let qt;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let pxRatio;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function setPxRatio() {
 | 
					 | 
				
			||||||
        pxRatio = devicePixelRatio;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function guardedRange(u, min, max) {
 | 
					 | 
				
			||||||
        if (max == min) {
 | 
					 | 
				
			||||||
            if (min == null) {
 | 
					 | 
				
			||||||
                min = 0;
 | 
					 | 
				
			||||||
                max = 100;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else {
 | 
					 | 
				
			||||||
                let delta = Math.abs(max) || 100;
 | 
					 | 
				
			||||||
                max += delta;
 | 
					 | 
				
			||||||
                min -= delta;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return [min, max];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    setPxRatio();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    window.addEventListener('dppxchange', setPxRatio);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const opts = {
 | 
					 | 
				
			||||||
        title: "Scatter Plot",
 | 
					 | 
				
			||||||
        mode: 2,
 | 
					 | 
				
			||||||
        width: 1920,
 | 
					 | 
				
			||||||
        height: 600,
 | 
					 | 
				
			||||||
        legend: {
 | 
					 | 
				
			||||||
            live: false,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        hooks: {
 | 
					 | 
				
			||||||
            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, i) => {
 | 
					 | 
				
			||||||
                        if (i > 0)
 | 
					 | 
				
			||||||
                            s._paths = null;
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        scales: {
 | 
					 | 
				
			||||||
            x: {
 | 
					 | 
				
			||||||
                time: false,
 | 
					 | 
				
			||||||
            //	auto: false,
 | 
					 | 
				
			||||||
            //	range: [0, 500],
 | 
					 | 
				
			||||||
                // remove any scale padding, use raw data limits
 | 
					 | 
				
			||||||
                range: guardedRange,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            y: {
 | 
					 | 
				
			||||||
            //	auto: false,
 | 
					 | 
				
			||||||
            //	range: [0, 500],
 | 
					 | 
				
			||||||
                // remove any scale padding, use raw data limits
 | 
					 | 
				
			||||||
                range: guardedRange,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        series: [
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                /*
 | 
					 | 
				
			||||||
                stroke: "red",
 | 
					 | 
				
			||||||
                fill: "rgba(255,0,0,0.1)",
 | 
					 | 
				
			||||||
                paths: (u, seriesIdx, idx0, idx1) => {
 | 
					 | 
				
			||||||
                    uPlot.orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
 | 
					 | 
				
			||||||
                        let d = u.data[seriesIdx];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        console.log(d);
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    return null;
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                */
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                stroke: "red",
 | 
					 | 
				
			||||||
                fill: "rgba(255,0,0,0.1)",
 | 
					 | 
				
			||||||
                paths: drawPoints,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                stroke: "green",
 | 
					 | 
				
			||||||
                fill: "rgba(0,255,0,0.1)",
 | 
					 | 
				
			||||||
                paths: drawPoints,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                stroke: "blue",
 | 
					 | 
				
			||||||
                fill: "rgba(0,0,255,0.1)",
 | 
					 | 
				
			||||||
                paths: drawPoints,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                stroke: "magenta",
 | 
					 | 
				
			||||||
                fill: "rgba(0,0,255,0.1)",
 | 
					 | 
				
			||||||
                paths: drawPoints,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let u = new uPlot(opts, data, document.body);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    points = 50;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // size range in pixels (diameter)
 | 
					 | 
				
			||||||
    let minSize = 6;
 | 
					 | 
				
			||||||
    let maxSize = 60;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let maxArea = Math.PI * (maxSize / 2) ** 2;
 | 
					 | 
				
			||||||
    let minArea = Math.PI * (minSize / 2) ** 2;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // quadratic scaling (px area)
 | 
					 | 
				
			||||||
    function getSize(value, minValue, maxValue) {
 | 
					 | 
				
			||||||
        let pct = value / maxValue;
 | 
					 | 
				
			||||||
        // clamp to min area
 | 
					 | 
				
			||||||
        //let area = Math.max(maxArea * pct, minArea);
 | 
					 | 
				
			||||||
        let area = maxArea * pct;
 | 
					 | 
				
			||||||
        return Math.sqrt(area / Math.PI) * 2;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function getSizeMinMax(u) {
 | 
					 | 
				
			||||||
        let minValue = Infinity;
 | 
					 | 
				
			||||||
        let maxValue = -Infinity;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (let i = 1; i < u.series.length; i++) {
 | 
					 | 
				
			||||||
            let sizeData = u.data[i][2];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for (let j = 0; j < sizeData.length; j++) {
 | 
					 | 
				
			||||||
                minValue = Math.min(minValue, sizeData[j]);
 | 
					 | 
				
			||||||
                maxValue = Math.max(maxValue, sizeData[j]);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return [minValue, maxValue];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let drawPoints3 = makeDrawPoints3({
 | 
					 | 
				
			||||||
        disp: {
 | 
					 | 
				
			||||||
            size: {
 | 
					 | 
				
			||||||
                unit: 3, // raw CSS pixels
 | 
					 | 
				
			||||||
            //	discr: true,
 | 
					 | 
				
			||||||
                values: (u, seriesIdx, idx0, idx1) => {
 | 
					 | 
				
			||||||
                    // TODO: only run once per setData() call
 | 
					 | 
				
			||||||
                    let [minValue, maxValue] = getSizeMinMax(u);
 | 
					 | 
				
			||||||
                    return u.data[seriesIdx][2].map(v => getSize(v, minValue, maxValue));
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        each: (u, seriesIdx, dataIdx, lft, top, wid, hgt) => {
 | 
					 | 
				
			||||||
            // 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});
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let data2 = filledArr(series, v => [
 | 
					 | 
				
			||||||
        filledArr(points, i => randInt(0,500)),
 | 
					 | 
				
			||||||
        filledArr(points, i => randInt(0,500)),
 | 
					 | 
				
			||||||
        filledArr(points, i => randInt(1,10000)),  // bubble size, population
 | 
					 | 
				
			||||||
        filledArr(points, i => (Math.random() + 1).toString(36).substring(7)), // label / country name
 | 
					 | 
				
			||||||
    ]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    data2[0] = null;
 | 
					 | 
				
			||||||
    data2[1][1] = data2[1][1].map(v => v == 0 ? v : v * -1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /*
 | 
					 | 
				
			||||||
    const legendValue = (u, val, seriesIdx, idx) => {
 | 
					 | 
				
			||||||
        let [x, y, size] = u.data[seriesIdx];
 | 
					 | 
				
			||||||
        return `${x[idx]}, ${y[idx]}, ${size[idx]}`;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const legendValues = (u, seriesIdx, dataIdx) => {
 | 
					 | 
				
			||||||
        // when data null, it's initial schema probe (also u.status == 0)
 | 
					 | 
				
			||||||
        if (u.data == null || dataIdx == null || hRect == null || hRect.sidx != seriesIdx) {
 | 
					 | 
				
			||||||
            return {
 | 
					 | 
				
			||||||
                "Country":    '<NUM COUNTRIES>',
 | 
					 | 
				
			||||||
                "Population": '<TOTAL POP>',
 | 
					 | 
				
			||||||
                "GDP":        '<TOTAL GDP>',
 | 
					 | 
				
			||||||
                "Income":     '<AVG INCOME>'
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let [x, y, size, label] = u.data[seriesIdx];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            "Country":    label[hRect.didx],
 | 
					 | 
				
			||||||
            "Population": size[hRect.didx],
 | 
					 | 
				
			||||||
            "GDP":        '$' + x[hRect.didx],
 | 
					 | 
				
			||||||
            "Income":     '$' + y[hRect.didx],
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // hovered
 | 
					 | 
				
			||||||
    let hRect;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const opts2 = {
 | 
					 | 
				
			||||||
        title: "Bubble Plot",
 | 
					 | 
				
			||||||
        mode: 2,
 | 
					 | 
				
			||||||
        width: 1920,
 | 
					 | 
				
			||||||
        height: 600,
 | 
					 | 
				
			||||||
        legend: {
 | 
					 | 
				
			||||||
        //	live: false,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        cursor: {
 | 
					 | 
				
			||||||
            dataIdx: (u, seriesIdx) => {
 | 
					 | 
				
			||||||
                if (seriesIdx == 1) {
 | 
					 | 
				
			||||||
                    hRect = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let dist = Infinity;
 | 
					 | 
				
			||||||
                    let cx = u.cursor.left * pxRatio;
 | 
					 | 
				
			||||||
                    let cy = u.cursor.top * pxRatio;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // !!! https://github.com/timohausmann/quadtree-js/blob/master/docs/simple.html
 | 
					 | 
				
			||||||
                    // Rewrite mouseOver based on this example
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // let overlaps =  qt.retrieve({x:cx, y:cy, width:1, height:1})
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    // for(var i=0;i<overlaps.length;i=i+1) {
 | 
					 | 
				
			||||||
                    //     overlaps[i].check = true;
 | 
					 | 
				
			||||||
                    // }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // OLD!
 | 
					 | 
				
			||||||
                    qt.get(cx, cy, 1, 1, o => {
 | 
					 | 
				
			||||||
                        if (pointWithin(cx, cy, o.x, o.y, o.x + o.w, o.y + o.h)) {
 | 
					 | 
				
			||||||
                            let ocx = o.x + o.w / 2;
 | 
					 | 
				
			||||||
                            let ocy = o.y + o.h / 2;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            let dx = ocx - cx;
 | 
					 | 
				
			||||||
                            let dy = ocy - cy;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            let d = Math.sqrt(dx ** 2 + dy ** 2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            // test against radius for actual hover
 | 
					 | 
				
			||||||
                            if (d <= o.w / 2) {
 | 
					 | 
				
			||||||
                                // only hover bbox with closest distance
 | 
					 | 
				
			||||||
                                if (d <= dist) {
 | 
					 | 
				
			||||||
                                    dist = d;
 | 
					 | 
				
			||||||
                                    hRect = o;
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return hRect && seriesIdx == hRect.sidx ? hRect.didx : null;
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            points: {
 | 
					 | 
				
			||||||
                size: (u, seriesIdx) => {
 | 
					 | 
				
			||||||
                    return hRect && seriesIdx == hRect.sidx ? hRect.w / devicePixelRatio : 0;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        hooks: {
 | 
					 | 
				
			||||||
            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, i) => {
 | 
					 | 
				
			||||||
                        if (i > 0)
 | 
					 | 
				
			||||||
                            s._paths = null;
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        axes: [
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                label: "GDP",
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                label: "Income 1",
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                side: 1,
 | 
					 | 
				
			||||||
                scale: 'y2',
 | 
					 | 
				
			||||||
                stroke: "red",
 | 
					 | 
				
			||||||
                label: "Income 2",
 | 
					 | 
				
			||||||
                grid: {
 | 
					 | 
				
			||||||
                    show: false,
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        scales: {
 | 
					 | 
				
			||||||
            x: {
 | 
					 | 
				
			||||||
                time: false,
 | 
					 | 
				
			||||||
            //	auto: false,
 | 
					 | 
				
			||||||
            //	range: [0, 500],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // remove any scale padding, use raw data limits
 | 
					 | 
				
			||||||
                range: guardedRange,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            y: {
 | 
					 | 
				
			||||||
            //	auto: false,
 | 
					 | 
				
			||||||
            //	range: [0, 500],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // remove any scale padding, use raw data limits
 | 
					 | 
				
			||||||
                range: guardedRange,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            y2: {
 | 
					 | 
				
			||||||
                dir: 1,
 | 
					 | 
				
			||||||
                ori: 1,
 | 
					 | 
				
			||||||
                range: guardedRange,
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        series: [
 | 
					 | 
				
			||||||
            null,
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                facets: [
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        scale: 'x',
 | 
					 | 
				
			||||||
                        auto: true,
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        scale: 'y2',
 | 
					 | 
				
			||||||
                        auto: true,
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
                label: "Region A",
 | 
					 | 
				
			||||||
                stroke: "red",
 | 
					 | 
				
			||||||
                fill: "rgba(255,0,0,0.3)",
 | 
					 | 
				
			||||||
                paths: drawPoints3,
 | 
					 | 
				
			||||||
                values: legendValues,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                facets: [
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        scale: 'x',
 | 
					 | 
				
			||||||
                        auto: true,
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        scale: 'y',
 | 
					 | 
				
			||||||
                        auto: true,
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
                label: "Region B",
 | 
					 | 
				
			||||||
                stroke: "green",
 | 
					 | 
				
			||||||
                fill: "rgba(0,255,0,0.3)",
 | 
					 | 
				
			||||||
                paths: drawPoints3,
 | 
					 | 
				
			||||||
                values: legendValues,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                facets: [
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        scale: 'x',
 | 
					 | 
				
			||||||
                        auto: true,
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        scale: 'y',
 | 
					 | 
				
			||||||
                        auto: true,
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
                label: "Region C",
 | 
					 | 
				
			||||||
                stroke: "blue",
 | 
					 | 
				
			||||||
                fill: "rgba(0,0,255,0.3)",
 | 
					 | 
				
			||||||
                paths: drawPoints3,
 | 
					 | 
				
			||||||
                values: legendValues,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                facets: [
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        scale: 'x',
 | 
					 | 
				
			||||||
                        auto: true,
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        scale: 'y',
 | 
					 | 
				
			||||||
                        auto: true,
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
                label: "Region E",
 | 
					 | 
				
			||||||
                stroke: "orange",
 | 
					 | 
				
			||||||
                fill: "rgba(255,128,0,0.3)",
 | 
					 | 
				
			||||||
                paths: drawPoints3,
 | 
					 | 
				
			||||||
                values: legendValues,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let u2 = new uPlot(opts2, data2, document.body);
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
{#if data != null}
 | 
					 | 
				
			||||||
    <div bind:this={plotWrapper}/>
 | 
					 | 
				
			||||||
{:else}
 | 
					 | 
				
			||||||
    <Card class="mx-4" body color="warning">Cannot render scatter: No data!</Card>
 | 
					 | 
				
			||||||
{/if}
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user