$(function(){ var r0 = 50, width = 600, height = 300, data = [ { radius: r0, color: 0 }, { radius: r0/2, color: 0 }, { radius: r0/2, color: 0 } , { radius: r0/2, color: 0 }, { radius: r0/2, color: 0 }, { radius: r0/2, color: 0 }, { radius: r0/2, 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"), sceneGL = $container.webGL().appendChild("scene") .attr({"id": "scene", "width": width, "height": height }) .webGL(), $circles = sceneGL .bind(data, "sprite") .attr({opacity: 0.8, anchor: 0.5}), $lines = sceneGL .bind(data, "line") .attr({ "stroke": "red", "stroke-width": function(d, i, val){return 12 * d.radius/r0;}, "stroke-linecap": "round", opacity: 0.5 }), //renderer = Renderer$($container).shadow("distance", r0); renderer = $container.webGL().Renderer().shadow("distance", r0); renderer.sprites.init(r0); renderer.sprites.enter($circles).merge($circles); renderer.graphics.enter($lines).merge($lines); function update() { renderer.draw(); window.requestAnimationFrame(update) } update(); }); !(function($){ function Renderer$($container) { var $view = $("").appendTo($container), $pfScene = $container.children("scene"), renderer = new PIXI. autoDetectRenderer($pfScene.attr("width"), $pfScene.attr("height"), {view: $view.get(0)}), dropShadow = new PIXI.filters.DropShadowFilter(), stage = new PIXI.Container(), elements = { line:{}, circle: {}, rect: {}, arc: {}, sprite: {} }; renderer.backgroundColor = +("0x" + tinycolor("rgba(255,255,255,0)").toHex()); var sprites = (function() { var spriteSheet, rMax, container = new PIXI.Container(), 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/rMax; offset.y *= r/rMax; 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; } } } })(); stage.addChild(container); function updatePosition() { container.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.each(function(i) { container.removeChildAt(i - s++); }); return this; }, enter: function enter($selection) { // create sprites based on the structure and attributes of the // selected nodes and the data bound to them $selection.each(function(i) { var $e = $selection.eq(i), d = $e.data().values, attr = $e.attr.bind($e), sprite = new PIXI.Sprite(spriteSheet(d.color)); sprite.anchor.set(attr("anchor")); sprite.alpha = attr("opacity"); sprite.interactive = true; sprite.bringToFront = bringToFront; sprite .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); container.addChild(sprite) } ); return this; }, update: function() {}, merge: function b($selection) { // UPDATE+ENTER $selection.each(function(i) { var $e = $selection.eq(i), d = $e.data().values, circle = container.children[i]; circle.texture = spriteSheet(d.color); circle.scale.set(d.radius / rMax); circle.__data__ = d; }); container.___update = container.children.length ? updatePosition : null; return this; } } })(); var graphics = (function() { var container = new PIXI.Graphics(); stage.addChild(container); function updatePosition() { if(!container.graphicsData.length) return; container.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]; }); }); container.clearDirty = container.dirty = /*container.boundsDirty = container.webGLDirty =*/ true; //container.updateLocalBounds(); /* log.textContent = [ container.graphicsData.map(property("points")), container.graphicsData.map(property("shape")).map(property("points")) ].map(precision(4)).reduce(function(s, d){ return (s += (d ? d.join("\n") : "") + "\n", s)}, "") */ } return { exit: function exit($selection) { // EXIT var s = 0; $selection.each(function(i) { container.graphicsData.splice(i - s++, 1); }); return this; }, enter: function($selection) { // create the GraphicsData objects with the drawing style based on // the node attributes $selection.each(function(i) { var $e = $selection.eq(i), d = $e.data().values, attr = $e.attr.bind($e); container.lineStyle(+attr("stroke-width"), +("0x" + tinycolor(attr("stroke")).toHex()) || 0, +attr("opacity") || 1); container.moveTo(d.x, d.y); container.lineTo(d.x - d.v.x, d.y - d.v.y); }); return this; }, update: function($selection) { }, merge: function b($selection) { // UPDATE+ENTER $selection.each(function(i) { container.graphicsData[i].__data__ = $selection.eq(i).data().values; }); container.___update = container.graphicsData.length ? updatePosition : null; return this; } } })(); 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 { shadow: (function() { function update(opt, value) { var config = (!opt || typeof opt == "object") ? $.extend({}, { color: "0x" + tinycolor("steelblue").toHex(), angle: Math.PI / 4, blur: 4, distance: 6 }, opt) : Object.defineProperty({}, opt, {value: value, enumerable: true}); Object.keys(config).forEach(function(k) { dropShadow[k] = config[k]; }); stage.filters = [dropShadow]; return this; } update(); return update; })(), draw: function draw() { stage.children.forEach(function(c) { if(!c.___update) return; c.___update(); }); renderer.render(stage) }, sprites: sprites, graphics: graphics, stage: stage }; } // extend jQuery object // https://learn.jquery.com/plugins/basic-plugin-creation/ $.fn.webGL = function(){ var that = this, ns = "CB:webGL/dummy/nodes"; return { bind: function(data, element) { // Create new DOM elements in the webGL namespace to realise the data structure var $s = $(); data.forEach(function(d, i) { $s = $s.add($(document.createElementNS(ns, element)).appendTo(that) .data("values", d) ) } ); return $s.webGL(); }, attr: function(attr, value) { return typeof attr == "object" ? that.attr(Object.keys(attr).reduce(function(o, k) { // translate any calculated attributes to match jq callback // The callback signatures are different because webGL callbacks // are about data and jq callbacks are about DOM elements return (o[k] = typeof attr[k] == "function" ? function(i, value) { // jq index is ignored return attr[k]($(this).data().values, i, value); } : attr[k], o) }, {})) : that.attr(attr, value) }, ns: ns, appendChild: function(element){ return $(document.createElementNS(ns, element)).appendTo(that) }, Renderer: function(){ return Renderer$(that); } } } function myName(args) { return /function\s+(\w*)\(/.exec(args.callee)[1]; } function property(p){ return function(x){ return x[p] } } function precision(n) { return function f(x) { return typeof x == "undefined" ? x : (Array.isArray(x) ? x.map(precision(n)) : x.toPrecision(n)); } } function f(_fmt, x) { return Array.isArray(x) ? x.map(f.bind(null, _fmt)) : d3.format(_fmt)(x); } })(jQuery);