|  | <!DOCTYPE html> | 
        
          |  | <meta charset="utf-8"> | 
        
          |  | <canvas width="960" height="500"></canvas> | 
        
          |  | <script src="https://d3js.org/d3.v4.min.js"></script> | 
        
          |  | <script> | 
        
          |  |  | 
        
          |  | var canvas = d3.select("canvas"), | 
        
          |  | context = canvas.node().getContext("2d"), | 
        
          |  | width = canvas.property("width"), | 
        
          |  | height = canvas.property("height"), | 
        
          |  | radius = 2.5, | 
        
          |  | transform = d3.zoomIdentity; | 
        
          |  |  | 
        
          |  | var points = d3.range(2000).map(phyllotaxis(10)); | 
        
          |  |  | 
        
          |  | canvas | 
        
          |  | .call(d3.drag().subject(dragsubject).on("drag", dragged)) | 
        
          |  | .call(d3.zoom().scaleExtent([1 / 2, 8]).on("zoom", zoomed)) | 
        
          |  | .call(render); | 
        
          |  |  | 
        
          |  | function zoomed() { | 
        
          |  | transform = d3.event.transform; | 
        
          |  | render(); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function dragsubject() { | 
        
          |  | var i, | 
        
          |  | x = transform.invertX(d3.event.x), | 
        
          |  | y = transform.invertY(d3.event.y), | 
        
          |  | dx, | 
        
          |  | dy; | 
        
          |  |  | 
        
          |  | for (i = points.length - 1; i >= 0; --i) { | 
        
          |  | point = points[i]; | 
        
          |  | dx = x - point[0]; | 
        
          |  | dy = y - point[1]; | 
        
          |  | if (dx * dx + dy * dy < radius * radius) { | 
        
          |  | point.x = transform.applyX(point[0]); | 
        
          |  | point.y = transform.applyY(point[1]); | 
        
          |  | return point; | 
        
          |  | } | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function dragged() { | 
        
          |  | d3.event.subject[0] = transform.invertX(d3.event.x); | 
        
          |  | d3.event.subject[1] = transform.invertY(d3.event.y); | 
        
          |  | render(); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function render() { | 
        
          |  | context.save(); | 
        
          |  | context.clearRect(0, 0, width, height); | 
        
          |  | context.beginPath(); | 
        
          |  | context.translate(transform.x, transform.y); | 
        
          |  | context.scale(transform.k, transform.k); | 
        
          |  | points.forEach(drawPoint); | 
        
          |  | context.fill(); | 
        
          |  | context.restore(); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function drawPoint(point) { | 
        
          |  | context.moveTo(point[0] + radius, point[1]); | 
        
          |  | context.arc(point[0], point[1], radius, 0, 2 * Math.PI); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function phyllotaxis(radius) { | 
        
          |  | var theta = Math.PI * (3 - Math.sqrt(5)); | 
        
          |  | return function(i) { | 
        
          |  | var r = radius * Math.sqrt(i), a = theta * i; | 
        
          |  | return [ | 
        
          |  | width / 2 + r * Math.cos(a), | 
        
          |  | height / 2 + r * Math.sin(a) | 
        
          |  | ]; | 
        
          |  | }; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | </script> |