$(function(){ function Renderer$($container) { var $view = $("").appendTo($container), $pfScene = $container.children("scene"); var renderer = new PIXI. autoDetectRenderer($pfScene.attr("width"), $pfScene.attr("height"), {view: $view.get(0)}), dropShadow = new PIXI.filters.DropShadowFilter(), stage = new PIXI.Container(); renderer.backgroundColor = +("0x" + tinycolor("rgba(255,255,255,0)").toHex()); dropShadow.color = "0x" + tinycolor("steelblue").toHex(); dropShadow.angle = Math.PI / 4; dropShadow.blur = 4; dropShadow.distance = r0; stage.filters = [dropShadow]; var bubbles = (function() { var spriteSheet, rMax, circles = new PIXI.Container(); //circles.___update = updatePosition; stage.addChild(circles); function updatePosition() { circles.children.forEach(function(c) { var d = c.__data__; if(d.fixed & 2) { // update the data to reflect the moved to position d.x = c.position.x; d.y = c.position.y; } else { c.position.x = d.x + dither(); c.position.y = d.y + dither(); } }) } return { init: function(_rMax) { rMax = _rMax spriteSheet = filters.makeSpriteSheet((rMax).toFixed(), ["steelblue"]) }, exit: function exit($selection) { // EXIT var s = 0; $selection.data("values").forEach(function(d, i) { circles.removeChildAt(i - s++); }); return this; }, update: function() {}, enter: function enter($selection) { // ENTER $selection.data("values").forEach( function(d) { var circle = new PIXI.Sprite(spriteSheet(d.color)); circle.anchor.set(0.5); circle.interactive = true; circle.bringToFront = bringToFront; circle .on("mouseover", drag.onMouseOver) .on("mouseout", drag.onMouseOut) // events for drag start .on('mousedown', drag.onDragStart) .on('touchstart', drag.onDragStart) // events for drag end .on('mouseup', drag.onDragEnd) .on('mouseupoutside', drag.onDragEnd) .on('touchend', drag.onDragEnd) .on('touchendoutside', drag.onDragEnd) // events for drag move .on('mousemove', drag.onDragMove) .on('touchmove', drag.onDragMove); circles.addChild(circle) } ); return this; }, merge: function b($selection) { // UPDATE+ENTER $selection.data("values").forEach(function(d, i) { var circle = circles.children[i]; circle.texture = spriteSheet(d.color); circle.scale.set(d.radius / rMax); circle.__data__ = d; }); circles.___update = circles.children.length ? updatePosition : null; return this; } } })(); var momenta = (function() { var lines = new PIXI.Graphics(); lines.___update = updatePosition; stage.addChild(lines); function updatePosition() { if(!lines.graphicsData.length) return; lines.graphicsData.forEach(function(g) { var d = g.__data__; g.shape.points.forEach(function(p, i, a){ a[i] = [ d.x, d.y, d.x - d.v.x, d.y - d.v.y ][i]; }); //g.points = g.shape.points.concat(g.shape.points.slice(2)); // had no effect }); lines.dirty = lines.boundsDirty = lines.glDirty = true; lines.updateLocalBounds(); //lines.renderWebGL(renderer); // also had no effect } return { exit: function exit($selection) { // EXIT var s = 0; $selection.each(function(i, d) { lines.graphicsData.splice(i - s++, 1); }); return this; }, enter: function($selection) { $selection.data("values").forEach(function(d, i) { var line = $selection.eq(i), attr = line.attr.bind(line); lines.lineStyle(attr("stroke-width"), +("0x" + tinycolor(attr("stroke")).toHex()) || 0, +attr("opacity") || 1); lines.moveTo(d.x, d.y); lines.lineTo(d.x - d.v.x, d.y - d.v.y); }) return this; }, update: function($selection) { }, merge: function b($selection) { // UPDATE+ENTER $selection.data("values").forEach(function(d, i) { lines.graphicsData[i].__data__ = d; }); updatePosition(); return this; } } })(); var drag = (function(){ var offset; return { onMouseOver: function onMouseOver(event) { var d = this.__data__; //toolTip.target = d; d.fixed |= 4; console.log([myName(arguments), d.index, "children", stage.children.length].join(": ")); // d.x = this.position.x -= d.v.x; // d.y = this.position.y -= d.v.y; this.bringToFront(); }, onMouseOut: function onMouseOut(event) { //toolTip.target = null; console.log(["\t", myName(arguments), this.index].join(": ")) this.__data__.fixed &= ~4; this.alpha = 1; }, onDragStart: function onDragStart(event) { offset = event.data.getLocalPosition(this); console.log([myName(arguments), this.index].join(": ")) // store a reference to the __data__ // the reason for this is because of multitouch // we want to track the movement of this particular touch this.eventData = event.data; // this.alpha = 0.5; this.__data__.fixed |= 2; }, onDragEnd: function onDragEnd() { console.log(["\t", myName(arguments), this.index].join(": ")) this.alpha = 1; this.__data__.fixed &= ~6; // set the interaction data to null this.eventData = null; }, onDragMove: function onDragMove(event) { if(this.__data__.fixed & 2) { console.log([myName(arguments), this.index].join(": ")); var newPosition = this.eventData.getLocalPosition(this.parent); this.__data__.x = this.__data__.px = this.position.x = newPosition.x - offset.x; this.__data__.y = this.__data__.py = this.position.y = newPosition.y - offset.y; } } } })(); function bringToFront() { var container = this.parent; container.swapChildren(this, container.children[container.children.length - 1]); } function dither(m) { return (Math.random() - 0.5) * (m || 1); } return { draw: function draw() { stage.children.forEach(function(c) { if(!c.___update) return; c.___update(); }); renderer.render(stage) }, bubbles: bubbles, momenta: momenta, stage: stage }; } function myName(args) { return /function\s+(\w*)\(/.exec(args.callee)[1]; } var r0 = 50, width = 600, height = 300, data = [{ radius: r0, color: 0, x: width/2, y: height/2, get v() {return {x: (Math.random() - 0.5)*r0, y: (Math.random() - 0.5)*r0}}, get s() {var v = this.v; return Math.sqrt(v.x * v.x + v.y * v.y)} }], $container = $("
").appendTo("body").attr("id", "viz"), $scene = $(document.createElementNS("webGL", "scene")).appendTo($container) .attr({"id": "scene", "width": width, "height": height }), $circles = $(document.createElementNS("webGL", "circle")).appendTo($scene) .data("values", data), $lines = $(document.createElementNS("webGL", "line")).appendTo($scene) .attr({ "stroke": "red", "stroke-width": 12, "stroke-linecap": "round", opacity: 1 }) .data("values", data), renderer = Renderer$($container); renderer.bubbles.init(r0); renderer.bubbles.enter($circles).merge($circles); renderer.momenta.enter($lines).merge($lines); function update() { renderer.draw(); window.requestAnimationFrame(update) } update(); });