var width = 960, height = 500; var svg = d3.select('html') .append('svg') .attr('width', width) .attr('height', height) var shapes = d3.range(30).map(function(i){ var rv = { offset: [Math.random()*width, Math.random()*height], rotate: Math.random(), rotateSpeed: (Math.random() - .5)/50, type: ['cross', 'square', 'triangle-up'][i % 3], scale: Math.random()*10, centroid: [0, 0], } rv.path = d3.svg.symbol().type(rv.type)() if (rv.type == 'triangle-up') rv.centroid = [0, 1.754] return rv }) var isShift = false var initalRotate = 0 var initalScale = 1 var drag = d3.behavior.drag() .on('dragstart', function(d){ isShift = d3.event.sourceEvent.shiftKey initalRotate = d.rotate initalScale = d.scale }) .on('drag', function(d){ var x = d3.event.x var y = d3.event.y if (isShift){ d.rotate = initalRotate + x/500 d.scale = initalScale*Math.pow(1.3, (y - d.offset[1])/50) d.rotateSpeed = 0 } else{ d.offset = [x, y] } d3.select(this).attr('transform', transform) }) .origin(function(d){ return {x: d.offset[0], y: d.offset[1]} }) var shapeSel = svg.selectAll('path') .data(shapes).enter() .append('path') .attr('d', function(d){ return d.path }) .attr('transform', transform) shapeSel.call(drag) d3.timer(function(t){ shapeSel .each(function(d){ d.rotate += d.rotateSpeed }) .attr('transform', transform) }) function transform(d){ var x = d.centroid[0] var y = d.centroid[1] var s = d.scale var a = d.rotate var matrix = multAffine( [1, 0, 0, 1, d.offset[0], d.offset[1]], [1, 0, 0, 1, x, y], [s, 0, 0, s, 0, 0], [Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0], [1, 0, 0, 1, -x, -y] ) return 'matrix(' + matrix + ')' return [ 'translate(', d.offset, ')', 'translate(', d.centroid, ')', 'scale(', d.scale, ')', 'rotate(', 180 * d.rotate, ')', 'translate(', negPos(d.centroid), ')' ].join(' ') } function multAffine(a, b){ if (arguments.length < 2) return a var m = [ a[0]*b[0] + a[2]*b[1], a[0]*b[2] + a[2]*b[3], a[0]*b[4] + a[2]*b[5] + a[4], a[1]*b[0] + a[3]*b[1], a[1]*b[2] + a[3]*b[3], a[1]*b[4] + a[3]*b[5] + a[5], ] m = [m[0], m[3], m[1], m[4], m[2], m[5]] if (arguments.length < 3) return m return multAffine.apply(null, [m].concat([].slice.call(arguments).slice(2))) } function addPos(a, b){ return [a[0] + b[0], a[1] + b[1]] } function negPos(a){ return [a[0]*-1, a[1]*-1] } function rotPos(d, a){ return [Math.cos(a)*d[0], Math.sin(a)*d[1]] }