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).
Last active
December 4, 2020 18:17
-
-
Save mthh/71ffab65cd1ac902365c8237d2ac51cc to your computer and use it in GitHub Desktop.
Canvas native API / Pixi canvas renderer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: gpl-3.0 | |
border: no |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment