$(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 = 8; 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(), attr; lines.___update = updatePosition; stage.addChild(lines); function updatePosition() { if(!attr) return; lines.clear(); lines.__data__.forEach(function(d, i) { 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); }); lines.endFill(); } return { exit: function exit($selection) { lines.clear(); return this; }, enter: function($selection) { lines.clear(); return this; }, update: function($selection) { }, merge: function b($selection) { var line = $selection.eq(0); lines.__data__ = $selection.data("values"); attr = lines.__data__ ? line.attr.bind(line) : null; 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; }, onDragStart: function onDragStart(event) { var r = this.__data__.radius; offset = event.data.getLocalPosition(this); offset.x *= r/r0; offset.y *= r/r0; 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.__data__.fixed &= ~6; // set the interaction data to null this.eventData = null; }, onDragMove: function onDragMove(event) { var d; if((d = this.__data__).fixed & 2) { console.log([myName(arguments), this.index].join(": ")); var newPosition = this.eventData.getLocalPosition(this.parent); d.x = d.px = this.position.x = newPosition.x - offset.x; d.y = d.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 }, { radius: r0/2, color: 0 }, { radius: r0/2, color: 0 } ].map(function(d, i){ return Object.defineProperties(d, { v: { get: function v() { return { x: (Math.random() - 0.5) * r0 * 2, y: (Math.random() - 0.5) * r0 * 2 } } }, s: { get: function s() { var v = this.v; return Math.sqrt(v.x * v.x + v.y * v.y) } }, x: { value: width / 2 * (Math.random() + 0.5), writable: true }, y: { value: height / 2 * (Math.random() + 0.5), writable: true } , i: { value: i, writable: true } }) }), $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) .attr("opacity", 0.5) .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(); });