I have a d3 graph that I implemented in Canvas. The graphs performance is great in Chrome, but when I deploy it with Ionic(webview), zoom and pan redrawing are really staggering on android and even slower on iOS.
I originally developed the graph with SVG, but switched to canvas after being convinced that canvas would run smoother.
Setup Code
HTML
<ion-header>
<ion-navbar>
<ion-title>
Ionic Blank
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<canvas width="300" height="300"></canvas>
</ion-content>
Canvas Initializing
mode = 'monthly';
canvas = document.querySelector("canvas");
context = canvas.getContext("2d");
margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = canvas.width - margin.left - margin.right,
height = canvas.height - margin.top - margin.bottom;
var parseTime = d3.timeParse("%d-%b-%y");
// setup scales
x = d3.scaleTime()
.range([0, width]);
x2 = d3.scaleTime().range([0, width]);
y = d3.scaleLinear()
.range([height, 0]);
// setup domain
x.domain(d3.extent(data, function (d) { return moment(d.usageTime); }));
y.domain(d3.extent(data, function (d) { return d.kWh; }));
x2.domain(x.domain());
// get day range
dayDiff = daydiff(x.domain()[0], x.domain()[1]);
// line generator
line = d3.line()
.x(function (d) { return x(moment(d.usageTime)); })
.y(function (d) { return y(d.kWh); })
.curve(d3.curveMonotoneX)
.context(context);
area = d3.area()
.curve(d3.curveMonotoneX)
.x(function (d) { return x(moment(d.usageTime)); })
.y0(height)
.y1(function (d) { return y(d.kWh); })
.context(context);
// zoom
zoom = d3.zoom()
.scaleExtent([1, dayDiff * 12])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
d3.select("canvas").call(zoom)
context.translate(margin.left, margin.top);
draw();
Draw Code
The canvas draw code looks like this:
function draw() {
// remove everything:
context.clearRect(-margin.left, -margin.top, canvas.width, canvas.height);
// draw axes:
xAxis();
yAxis();
// save the context without a clip path
context.save();
// create a clip path:
context.beginPath()
context.rect(0, 0, width, height);
context.lineWidth = 0;
context.strokeStyle = "none";
context.stroke();
context.clip();
// draw line in clip path
line(data);
context.lineWidth = 1.5;
context.strokeStyle = "steelblue";
context.stroke();
context.beginPath();
area(data);
context.fillStyle = 'steelblue';
context.strokeStyle = 'steelblue';
context.fill();
// restore without a clip path
context.restore();
}
Zoom Code
My zoom code looks like this:
function zoomed() {
var t = d3.event.transform;
x = t.rescaleX(x2);
draw();
var diff = daydiff(x.domain()[0], x.domain()[1]);
if (diff < 366 && diff > 120 && mode !== 'monthly') {
mode = 'monthly';
data = monthlyData;
y.domain(d3.extent(data, function (d) { return d.kWh; }));
return;
}
if (diff <= 120 && diff > 2 && mode !== 'daily') {
mode = 'daily';
data = dailyData;
y.domain(d3.extent(data, function (d) { return d.kWh; }));
return;
}
}
The performance seems to increase when excluding the area path(not drawing it at all) from the canvas.
The code
I'm attaching a link to the repository
. To make it run do the following:
- git clone https://github.com/airasheed/canvas-d3-test.git
- npm install
- ionic serve <--for browser to see graph
- ionic cordova build ios/android <--choose your testing platform
- OR - ionic cordova emulate android/ios
I'd like to know if its a code performance issue or if the amount of data i'm using is what's causing the issue.
For the first zoom level there are only 21 points, to plot which is surprising. It seems its staggering when redrawing.
Benchmarking
In chrome the line(data)
method takes .5ms, but in the iOS webview it can take anywhere from 15ms - 40ms. It looks like its lagging and not responsive.