Skip to content

Instantly share code, notes, and snippets.

@1Cr18Ni9
Last active November 13, 2019 06:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 1Cr18Ni9/cb7c2c490577dad2f4238d49b7c60376 to your computer and use it in GitHub Desktop.
Save 1Cr18Ni9/cb7c2c490577dad2f4238d49b7c60376 to your computer and use it in GitHub Desktop.
Canvas Animation Using D3.js II
license: mit

Inspired by:

This Demo is based on Previous One and add interactive feature on canvas.

  • Moving mouse on canvas hovered circle will be highlighed. I create an offscreen canvas that has the same dimmension as the presentation canvas. Offscreen canvas will be rendered ahead of the presentation one so that we can detect which circle the mouse hovered by ctx.getImageData(mouseX, mouseY, 1, 1).

  • Click on canvasImg will reveal the current offscreen canvas in an <img> node.

  • Once count is changed that will generate new circles and update canvas. The performance monitor will show the rendering frequency.

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.12.0/d3.min.js" integrity="sha256-+9Mf3cAVmxxudDsr1XwXUeRZFtvdWVYdq5/vcgiYyNU=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js" integrity="sha256-HJ7j+71YYw6Kcs8THwQV9lXmPOcR0eXlg7n8KRTZsyA=" crossorigin="anonymous"></script>
<script>
// stats.js - http://github.com/mrdoob/stats.js
(function(f,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"===typeof define&&define.amd?define(e):f.Stats=e()})(this,function(){var f=function(){function e(a){c.appendChild(a.dom);return a}function u(a){for(var d=0;d<c.children.length;d++)c.children[d].style.display=d===a?"block":"none";l=a}var l=0,c=document.createElement("div");c.style.cssText="position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000";c.addEventListener("click",function(a){a.preventDefault();
u(++l%c.children.length)},!1);var k=(performance||Date).now(),g=k,a=0,r=e(new f.Panel("FPS","#0ff","#002")),h=e(new f.Panel("MS","#0f0","#020"));if(self.performance&&self.performance.memory)var t=e(new f.Panel("MB","#f08","#201"));u(0);return{REVISION:16,dom:c,addPanel:e,showPanel:u,begin:function(){k=(performance||Date).now()},end:function(){a++;var c=(performance||Date).now();h.update(c-k,200);if(c>g+1E3&&(r.update(1E3*a/(c-g),100),g=c,a=0,t)){var d=performance.memory;t.update(d.usedJSHeapSize/
1048576,d.jsHeapSizeLimit/1048576)}return c},update:function(){k=this.end()},domElement:c,setMode:u}};f.Panel=function(e,f,l){var c=Infinity,k=0,g=Math.round,a=g(window.devicePixelRatio||1),r=80*a,h=48*a,t=3*a,v=2*a,d=3*a,m=15*a,n=74*a,p=30*a,q=document.createElement("canvas");q.width=r;q.height=h;q.style.cssText="width:80px;height:48px";var b=q.getContext("2d");b.font="bold "+9*a+"px Helvetica,Arial,sans-serif";b.textBaseline="top";b.fillStyle=l;b.fillRect(0,0,r,h);b.fillStyle=f;b.fillText(e,t,v);
b.fillRect(d,m,n,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d,m,n,p);return{dom:q,update:function(h,w){c=Math.min(c,h);k=Math.max(k,h);b.fillStyle=l;b.globalAlpha=1;b.fillRect(0,0,r,m);b.fillStyle=f;b.fillText(g(h)+" "+e+" ("+g(c)+"-"+g(k)+")",t,v);b.drawImage(q,d+a,m,n-a,p,d,m,n-a,p);b.fillRect(d+n-a,m,a,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d+n-a,m,a,g((1-h/w)*p))}}};return f});
</script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
canvas, img {
outline: 1px solid gray;
display: block;
margin: 0.5em auto;
}
</style>
</head>
<body>
<canvas></canvas>
<img>
<script>
var w = 950, h = 300,
pos = [-100, -100], // mouse position
canvas = document.querySelector("canvas"),
ctx = canvas.getContext("2d");
d3.select(canvas).attr("width", w).attr("height", h)
.on("mouseleave", function () { pos = [-100, -100]; })
.on("mousemove", function () { pos = d3.mouse(this); });
var genColor = (function () {
var nextCol = 1, _step = 50;
var fn = function () {
var ret = [];
// via http://stackoverflow.com/a/15804183
if (nextCol < 16777215) {
ret.push(nextCol & 0xff); // R
ret.push((nextCol & 0xff00) >> 8); // G
ret.push((nextCol & 0xff0000) >> 16); // B
nextCol += _step;
}
var col = "rgb(" + ret.join(",") + ")";
return col;
};
fn.step = function (v) {
if (!v || v < 0) { return _step; }
_step = v;
return fn;
};
fn.clear = function () {
nextCol = 1;
return fn;
};
return fn;
})();
var customNode = document.createElement("custom:cc"),
dataContainer = d3.select(customNode),
// A shallow copy of offscreen canvas that has the same dimmension
hCanvas = canvas.cloneNode(),
hCtx = hCanvas.getContext("2d"),
img = d3.select("img").attr("width", w).attr("height", h),
circleMap = {},
p = {
count: 200,
canvasImg: function () {
img.attr("src", hCanvas.toDataURL());
},
data: genCircles(this.num)
};
function genCircles (n) {
if (!n) { n = 100; }
genColor.clear();
return d3.range(n)
.map(function (d, i) {
var color = genColor(),
node = {
x: Math.random() * w,
y: Math.random() * h,
r: Math.random() * 10 + 3,
_fill: color
};
circleMap[color] = node;
return node;
});
}
var gui = new dat.GUI();
gui.add(p, "count", 100, 10000)
.step(100)
.onChange(function (n) {
p.data = genCircles(n);
drawCustom();
});
gui.add(p, "canvasImg");
function drawCustom () {
var u = dataContainer.selectAll("c").data(p.data);
u.enter()
.append("c")
.attr("r", d => d.r)
.attr("x", Math.random() * w)
.attr("y", Math.random() * h)
.merge(u)
.transition()
.duration(2000)
.attr("x", d => d.x)
.attr("y", d => d.y)
.attr("r", d => d.r);
u.exit().transition()
.duration(2000)
.attr("r", 0)
.remove();
}
function getCircleByColor (pos) {
var rgb = hCtx.getImageData(pos[0], pos[1], 1, 1).data;
rgb = "rgb(" + rgb.slice(0, 3).join(",") + ")"; // avoid alpha charnnel
return circleMap[rgb];
}
function drawCanvas (ctx) {
ctx.save();
ctx.clearRect(0, 0, w, h);
ctx.lineWidth = 1;
ctx.strokeStyle = "#335";
// pick a color spot and detect which circle under this spot.
var t = getCircleByColor(pos);
ctx.fillStyle = "rgba(70, 130, 180, 0.5)";
dataContainer.selectAll("c").each(function (d) {
var x = +this.getAttribute("x"),
y = +this.getAttribute("y"),
r = +this.getAttribute("r");
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2, false);
ctx.fill();
ctx.stroke();
});
if (t) {
ctx.beginPath();
ctx.arc(t.x, t.y, t.r, 0, Math.PI * 2, false);
ctx.fillStyle = "red";
ctx.fill();
}
ctx.restore();
}
function drawHiddenCanvas (ctx) {
ctx.save();
ctx.clearRect(0, 0, w, h);
dataContainer.selectAll("c").each(function (d) {
var x = +this.getAttribute("x"),
y = +this.getAttribute("y"),
r = +this.getAttribute("r");
ctx.fillStyle = d._fill;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2, false);
ctx.fill();
});
ctx.restore();
}
function imageText (ctx) {
ctx.save();
ctx.fillStyle = "gray";
ctx.font = "50px fantasy";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Click \"canvasImg\" to see", w / 2, 120);
ctx.fillText("what the offscreen canvas looks like", w / 2, 170);
ctx.restore();
}
// Performance Monitor
var stats = new Stats();
stats.showPanel( 0 ); // 0: fps, 1: ms, 2: mb, 3+: custom
document.body.appendChild( stats.dom );
var cb = function () {
stats.begin();
drawHiddenCanvas(hCtx);
drawCanvas(ctx);
stats.end();
};
imageText(hCtx);
p.canvasImg();
drawCustom();
d3.timer(cb);
d3.select(window.parent.document.querySelector("iframe"))
.style("height", "620px");
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment