Skip to content

Instantly share code, notes, and snippets.

@mthh
Last active December 4, 2020 18:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mthh/71ffab65cd1ac902365c8237d2ac51cc to your computer and use it in GitHub Desktop.
Save mthh/71ffab65cd1ac902365c8237d2ac51cc to your computer and use it in GitHub Desktop.
Canvas native API / Pixi canvas renderer
license: gpl-3.0
border: no

Tests with canvas (native API or with Pixi.js) and d3 generated path

Render about 35K polygons using d3.geoPath and the native canvas API or Pixi.js canvas renderer
(in order to compare the resulting images and the time required to draw them).

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<p id="#progress"></p>
<p style="margin:2px 10px;display: inline-block;" id="timing"></p>
<p style="margin: 0 0 0 25px;display: inline-block;background:#000;border-radius:4px;font-size:14px;color:#FFF;padding: 6px 10px;cursor:pointer;" id="switch" value="a"></p>
<div id="a"></div>
<div id="b"></div>
<p style="margin-top:75px;display:none;" id="waiting">Drawing in progress...</p>
<script src="http://d3js.org/d3.v4.js" charset="utf-8"></script>
<script src="https://unpkg.com/topojson@3"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.5.1/pixi.min.js"></script>
<script type="text/javascript">
let width = 800,
height = 800;
const progress = d3.select('#progress');
const waiting = d3.select('#waiting');
const devicePixelRatio = window.devicePixelRatio || 1;
const canvas = d3.select("#a").append("canvas")
.attr("width", width * devicePixelRatio)
.attr("height", height * devicePixelRatio)
.style("width", width + "px")
.style("height", height + "px");
let context = canvas.node().getContext("2d");
context.scale(devicePixelRatio, devicePixelRatio);
let proj = d3.geoAzimuthalEqualArea()
.rotate([-6, -48, 0])
.scale(3600)
.translate([width / 1.5, height / 3])
.precision(0.1);
let communes, path, dep_colors, dep_borders;
const req = new XMLHttpRequest();
req.open('GET', 'voronoi_communes_2016.topojson', true);
req.onload = function(e) {
prepare(JSON.parse(req.responseText));
}
req.onprogress = function(e) {
if (e.lengthComputable) {
progress.html(['Chargement en cours : ', Math.round(e.loaded / e.total), ' % effectué'].join(''));
} else {
progress.html('Chargement en cours');
}
};
req.send();
d3.select('#switch')
.on('click', function() {
if (this.getAttribute('value') === 'a') {
this.setAttribute('value', 'b');
d3.select('#a').style('display', 'none');
d3.select('#b').style('display', null).html('');
waiting.style('display', null);
drawTimePixi();
waiting.style('display', 'none');
// setTimeout(() => { drawTimePixi(); waiting.style('display', 'none'); }, 10);
} else {
this.setAttribute('value', 'a');
d3.select('#a').style('display', null);
d3.select('#b').style('display', 'none');
waiting.style('display', null);
drawTime();
waiting.style('display', 'none');
// setTimeout(() => { drawTime(); waiting.style('display', 'none'); }, 10);
}
})
function prepare(topo) {
progress.remove();
communes = topojson.feature(topo, topo.objects['voronoi_communes_2016_2-2_id']).features;
dep_borders = [];
path = d3.geoPath()
.projection(proj)
.context(context);
dep_colors = new Map();
communes.forEach((ft) => {
const id_dep = ft.properties.INSEE_COM.slice(0, 2);
ft.properties.CODE_DEP = id_dep;
if (!dep_colors.has(id_dep)) {
dep_colors.set(id_dep, randomColor());
}
});
Array.from(dep_colors.keys()).forEach((code_dep) => {
dep_borders.push(
topojson.merge(
topo,
topo.objects['voronoi_communes_2016_2-2_id'].geometries.filter((d) => d.properties.CODE_DEP === code_dep )));
});
proj.fitExtent([[20, 20], [800, 800]], { type: 'FeatureCollection', features: communes });
drawTime();// drawPixi();
}
function drawTime() {
context.save();
var t0 = performance.now();
draw();
var t1 = performance.now();
context.restore();
d3.select('#timing').html([
'Rendering with canvas native API took ', Math.round(t1 - t0), 'ms',
].join(''));
d3.select('#switch').html('Switch to Pixi canvas renderer');
}
function draw () {
context.strokeStyle = "#fff";
context.lineWidth = 0.3;
communes.forEach((d) => {
context.fillStyle = dep_colors.get(d.properties.CODE_DEP);
context.beginPath();
path(d);
context.fill();
context.stroke();
});
context.strokeStyle = "#800080";
context.lineWidth = 2;
dep_borders.forEach((d) => {
context.beginPath();
path(d);
context.stroke();
});
// context.restore();
}
function drawTimePixi() {
var t0 = performance.now();
drawPixi(true);
var t1 = performance.now();
d3.select('#timing').html([
'Rendering with Pixi canvas renderer took ', Math.round(t1 - t0), 'ms',
].join(''));
d3.select('#switch').html('Switch to canvas native API');
}
function drawPixi(forceCanvas=false, resolution=1) {
const app = new PIXI.Application({
width,
height,
resolution,
forceCanvas,
backgroundColor: 0xffffff,
});
document.getElementById('b').appendChild(app.view);
const graphics = new PIXI.Graphics();
path = d3.geoPath()
.projection(proj)
.context(graphics);
communes.forEach((d) => {
graphics.beginFill(HEXToVBColor(dep_colors.get(d.properties.CODE_DEP)), 1);
graphics.lineStyle(0.3, 0xffffff, 1);
path(d);
graphics.endFill();
});
dep_borders.forEach((d) => {
graphics.lineStyle(2, 0x800080, 1);
path(d);
});
app.stage.addChild(graphics);
}
function HEXToVBColor(rrggbb) {
if (rrggbb.startsWith('#')) rrggbb = rrggbb.slice(1);
return parseInt(rrggbb.substr(4, 2) + rrggbb.substr(2, 2) + rrggbb.substr(0, 2), 16);
}
// From https://gist.github.com/jdarling/06019d16cb5fd6795edf
// itself adapted from http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
const randomColor = (function () {
const golden_ratio_conjugate = 0.618033988749895;
let _h = Math.random();
const hslToRgb = function (h, s, l) {
let r,
g,
b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
}
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return `#${Math.round(r * 255).toString(16)}${Math.round(g * 255).toString(16)}${Math.round(b * 255).toString(16)}`;
};
return function () {
_h += golden_ratio_conjugate;
_h %= 1;
return hslToRgb(_h, 0.5, 0.60);
};
}());
</script>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment