billboard.js icon indicating copy to clipboard operation
billboard.js copied to clipboard

Very bad performance on point creation

Open dpraul opened this issue 5 years ago • 4 comments

In data-dense charts with data-points enabled, most of the time spent rendering the chart is spent on creating the data points. This can be observed in the screenshots in #1089 - see that updateCircle and redrawCircle combine to take the largest time in redraw. I believe this is because d3-selection is used to generate the points, and the functional approach that d3-selection causes a lot of bloat.

Related to #757

dpraul avatar Oct 17 '19 21:10 dpraul

Potential Solutions

  1. Disable data points (using point.show = false)
    • This totally mitigates the issue, but the current click-handling logic requires them to exist for data.onclick to work
    • User loses the ability to accurately see where they're clicking
    • This is only a viable solution in presentation-only graphs unless the click-handling logic is adjusted - then it's only viable for data-sparse graphs
  2. Add an option to filter what points are created
    • I imagine most use-cases don't need presentation for certain points (e.g. a timeseries graph where y=0)
    • Doesn't totally solve the issue, but gives users the opportunity to mitigate it
  3. Re-work point-creation / point-update logic
    • Using d3-selection gives a lot of flexibility, but in the end a lot of the slowness is because d3-selection requires creating each element ahead-of-time for each point. There are faster ways to create lots of DOM - particularly since all of the point DOM can be assembled first, and then created all at once.
    • Right now the points are created in updateCircle, inserted, and then modified in redrawCircle - doing everything before the DOM is inserted would reduce modifications to the virtual DOM

dpraul avatar Oct 17 '19 21:10 dpraul

@dpraul, I did an rough test replacing the appending to be done by each dataset at once by assembling HTML strings. I took a test for about 200 data points(<circle> elements) scenario and got some improvement, but not dramatic one.

Before the full implementation it should considering the harmony with other interactions(like flow, data toggle transition, etc.), which the test wasn't really successful.

I'll trying to dig more on this. Thanks for the report and the possible solution comment.

// line.js
updateCircle() {
	const $$ = this;

	if (!$$.config.point_show) {
		return;
	}

	const fn = $$.point("create", this, $$.pointR.bind($$), $$.color);
	const data = {};

	$$.mainCircle = $$.main
		.selectAll(`.${CLASS.circles}`)
		.each(function(d) {
			const datum = !$$.isBarType(d) &&
				(!$$.isLineType(d) || $$.shouldDrawPointsForLine(d)) &&
					$$.labelishData(d);

			this.innerHTML = datum.map(d => fn(d)).join("");
			data[d.id] = datum;

		})
		.selectAll(`.${CLASS.circle}`)
		.data(d => data[d.id])
		.style("stroke", $$.color)
		.style("opacity", $$.initialOpacityForCircle.bind($$));
},

// poiint.js
circle: {
	create(d, sizeFn, fillStyleFn) {
		return `<circle class="${this.updatePointClass.bind(this)(d)}" r="${sizeFn(d)}" style="display:none;fill:${fillStyleFn(d)}"></circle>`;

netil avatar Nov 21 '19 09:11 netil

Would perhaps offloading this to a WebWorker(s) help?

patrickcate avatar Jun 02 '20 02:06 patrickcate

@patrickcate, that can be one of the alternatives need to look for. To mitigate on this, v2 added new "point.focus.only" option, where this can be useful for massive data viz letting generate/manipulate one single <circle> node per dataset.

  • https://github.com/naver/billboard.js/blob/v2/CHANGELOG-v2.md#new-options

netil avatar Jul 09 '20 07:07 netil