billboard.js
billboard.js copied to clipboard
Very bad performance on point creation
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
Potential Solutions
- 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
- This totally mitigates the issue, but the current click-handling logic requires them to exist for
- 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
- I imagine most use-cases don't need presentation for certain points (e.g. a timeseries graph where
- 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 becaused3-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 inredrawCircle
- doing everything before the DOM is inserted would reduce modifications to the virtual DOM
- Using
@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>`;
Would perhaps offloading this to a WebWorker(s) help?
@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