Skip to content
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

Bubble Charts #758

Open
cloudlena opened this issue Oct 7, 2022 · 3 comments
Open

Bubble Charts #758

cloudlena opened this issue Oct 7, 2022 · 3 comments

Comments

@cloudlena
Copy link

I would like to create a bubble chart, but somehow cannot follow the complex example at https://leeoniya.github.io/uPlot/demos/scatter.html. The example seems overly complicated and not well documented. Is there a simpler example, or would it even make sense to expose the point size as a simple function for Series.Points.size? That would make bubble charts much simpler to create.

For reference, my example uses the following data structure:

const data = [
  [1,2,3,4,5], // x-Axis
  [3,4,5,6,7], // y-Axis
  [2,3,2,6,4], // Bubble radius
];
@leeoniya
Copy link
Owner

leeoniya commented Oct 7, 2022

if you don't need hover support, or multiple bubble series, or optimizations that most efficiently handle thousands of bubbles, i can make a much simplified static renderer. each of those requirements complicates the existing demo.

@cloudlena
Copy link
Author

Thanks for the quick reply, @leeoniya!
I do need hover-support, have one bubble series and need to be able to handle hundreds but not thousands of bubbles.

@cloudlena
Copy link
Author

cloudlena commented Oct 16, 2022

@leeoniya, I created a custom paths function:

interface BubblePathBuilderOptions {
  sizeSeriesIndex: number;
  maxSize: number;
}

function bubbles(opts: BubblePathBuilderOptions): Series.PathBuilder {
        const deg360 = 2 * Math.PI;

	return (u, seriesIdx) => {
		uPlot.orient(
			u,
			seriesIdx,
			(series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
				const dataS = u.data[opts.sizeSeriesIndex] as number[];
				const maxS = dataS.reduce((p, d) => (d ? Math.max(d, p) : p), 0);

				u.ctx.save();

				if (typeof series.stroke === 'function') {
					u.ctx.strokeStyle = series.stroke(u, seriesIdx);
				}
				if (typeof series.fill === 'function') {
					u.ctx.fillStyle = series.fill(u, seriesIdx);
				}
				u.ctx.lineWidth = series.width || 0;

				dataX.forEach((d, i) => {
					const xVal = d;
					const yVal = dataY[i];
					const sVal = dataS[i];

					if (
						yVal !== undefined &&
						yVal !== null &&
						(scaleX.min === undefined || xVal >= scaleX.min) &&
						(scaleX.max === undefined || xVal <= scaleX.max) &&
						(scaleY.min === undefined || yVal >= scaleY.min) &&
						(scaleY.max === undefined || yVal <= scaleY.max)
					) {
						const xPos = valToPosX(xVal, scaleX, xDim, xOff);
						const yPos = valToPosY(yVal, scaleY, yDim, yOff);
						const sizeRatio = sVal ? sVal / maxS : 0;
						const size = opts.maxSize * sizeRatio * devicePixelRatio + 9;

						u.ctx.moveTo(xPos + size / 2, yPos);
						u.ctx.beginPath();
						u.ctx.arc(xPos, yPos, size / 2, 0, deg360);
						u.ctx.fill();
						u.ctx.stroke();
					}
				});

				u.ctx.restore();
			}
		);

		return null;
	};
}

Would it make sense to include something like that in the library as a new member of PathBuilderFactories? Or do you see any major flaws or ways to improve it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants