function Force(graph) { Transform.prototype.init.call(this, graph); this._prev = null; this._interactive = false; this._setup = true; this._nodes = []; this._links = []; this._layout = d3.layout.force(); Transform.addParameters(this, { size: {type: 'array<value>', default: [500, 500]}, bound: {type: 'value', default: true}, links: {type: 'data'}, // TODO: for now force these to be value params only (pun-intended) // Can update to include fields after Parameter refactoring. linkStrength: {type: 'value', default: 1}, linkDistance: {type: 'value', default: 20}, charge: {type: 'value', default: -30}, chargeDistance: {type: 'value', default: Infinity}, friction: {type: 'value', default: 0.9}, theta: {type: 'value', default: 0.8}, gravity: {type: 'value', default: 0.1}, alpha: {type: 'value', default: 0.1}, iterations: {type: 'value', default: 500}, interactive: {type: 'value', default: this._interactive}, active: {type: 'value', default: this._prev}, fixed: {type: 'data'} }); this._output = { 'x': 'layout_x', 'y': 'layout_y' }; return this.mutates(true); }
function render(selectedNodeId) { var width = window.innerWidth, height = window.innerHeight; if (force) destroy(); d3.select(window).on('resize', resize); force = d3.layout.force() .linkDistance(150) .charge(-500) .size([width, height]) .on("tick", tick); svg = d3.select(container).append("svg") .attr("id", "twoDGraphSVG") .attr("width", width) .attr("height", height); link = svg.selectAll(".link"); node = svg.selectAll(".node"); var rootInfo = scene.getNodeInfo(selectedNodeId); root = { name: rootInfo.name, id: rootInfo.id }; updateMessage(selectedNodeId); // root structure expandFromNode(selectedNodeId); update(); }
init: function(){ var that = this; this.nodes = []; this.links = []; this.colour = d3.scale.ordinal() .range( help.colours ); this.svg = this.ele.append("svg") .attr("width", this.width + this.margin.left + this.margin.right ) .attr("height", this.height + this.margin.top + this.margin.bottom ) .append("g") .attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")"); this.svg.append("rect") .attr("x", 0) .attr("y", 0) .attr("width", this.width + this.margin.left + this.margin.right ) .attr("height", this.height + this.margin.top + this.margin.bottom ) .attr("fill", "#000000" ); this.link = this.svg.selectAll(".link"); this.node = this.svg.selectAll(".node"); this.force = d3.layout.force() .size([ this.width, this.height ]) .nodes( this.nodes ) .links( this.links ) .charge(-100) .linkStrength( 0.2 ) .linkDistance(function( l, i ){ return ((Math.sin(i) + (Math.random()*0.1)) * 150) + 100; }) .on("tick", function(){ that.tick() }); },
export default function createGraph(props) { const { container, nodes, onClickNode, onClickOutside, } = props let selected = null d3.selection.prototype.dblTap = function(callback) { var last = 0 return this.each(function() { d3.select(this).on('touchstart', function(e) { if ((d3.event.timeStamp - last) < 500) { return callback(e) } last = d3.event.timeStamp }) }) } const force = d3.layout.force() .size([width, height]) .on('tick', tick) const zoom = d3.behavior.zoom() .scaleExtent([1, 10]) .on('zoom', resize) .scale(2) .translate([ -1 * width / 2, -1 * height / 2 ]) const svg = d3.select(container).append('svg') .attr('id', 'containerSVG') .attr('width', width) .attr('height', height) .style('cursor,', 'move') .on('click', clickOutside) .call(zoom) zoom.event(svg.transition().duration(50)) const vis = svg.append('g') .attr('width', width) .attr('height', height) let link = vis.selectAll('.link') let node = vis.selectAll('.node') update() function update() { const links = d3.layout.tree().links(nodes) // Restart the force layout. force .nodes(nodes) .links(links) .start() // Update the links… link = link.data(links, (d) => d.target.id ) // Exit any old links. link.exit().remove() // Enter any new links. link.enter().insert('line', '.node') .attr('class', 'link') .attr('x1', (d) => d.source.x) .attr('y1', (d) => d.source.y) .attr('x2', (d) => d.target.x) .attr('y2', (d) => d.target.y) // Update the nodes… node = node.data(nodes, (d) => d.id ).style('fill', color) // Exit any old nodes. node.exit().remove() const drag = force.drag() .on('dragstart', () => d3.event.sourceEvent.stopPropagation()) // Enter any new nodes. node.enter().append('circle') .attr('class', 'node') .attr('cx', (d) => d.x ) .attr('cy', (d) => d.y ) .attr('r', (d) => d.size) .style('fill', color) .on('click', clickNode) .call(drag) svg.on('dblclick.zoom', (d) => { d3.event.stopPropagation() var dcx = (window.innerWidth / 2 - d.x * zoom.scale()) var dcy = (window.innerHeight / 2 - d.y * zoom.scale()) zoom.translate([dcx, dcy]) vis.attr('transform', `translate(${dcx},${dcy})scale(${zoom.scale()})`) }) } function tick() { link.attr('x1', (d) => d.source.x ) .attr('y1', (d) => d.source.y ) .attr('x2', (d) => d.target.x ) .attr('y2', (d) => d.target.y ) node.attr('cx', (d) => d.x ) .attr('cy', (d) => d.y ) } // Color leaf nodes orange, and packages white or blue. function color(d) { if (d.isOwnUser) { return '#FFF' } if (d._children) { return '#3182bd' } else { return d.children ? '#c6dbef' : '#fd8d3c' } } function clickNode(d) { if (selected) { selected.attr('class', 'node') } selected = d3.select(this) selected.attr('class', 'node selected') if (!d3.event.defaultPrevented) { d3.event.stopPropagation() d3.event.preventDefault() update() onClickNode(d.id) } } function clickOutside() { if (selected) { selected.attr('class', 'node') } selected = null if (!d3.event.defaultPrevented) { d3.event.stopPropagation() d3.event.preventDefault() onClickOutside() } } function resize() { const trans = d3.event.translate const scale = d3.event.scale vis.attr('transform', `translate(${trans}) scale(${scale})`) } }
function bubblesVis({data, container, depth=0, gravity=0, width, height, radius=300, maxAmount, fillColor}) { let nodesIndex = {}; let force = d3.layout.force() .size([width,height]); // let center = { // x: radius, // y: radius, // }; let centerDamper = 0.5, collideDamper = 0; let handlers = { }; function update (nodes, center) { let income = d => d.income; function moveTowardsCenter (alpha) { return (d) => { let c = center(d); d.x = d.x + (c.x - d.x) * (centerDamper) * alpha; d.y = d.y + (c.y - d.y) * (centerDamper) * alpha; }; } force.nodes(nodes); force.gravity(gravity) .charge(d => -Math.pow(radiusScale(d.income), 2.0)) // .charge(d => -radiusScale(d.income) * 15) .friction(0.9) let s = radius let radiusScale = d3.scale.linear().domain([0, maxAmount]).range([s/50, s/3]) nodes.forEach(node => { if (node.x == null || node.y == null) { let r = Math.random() * (radius - radiusScale(node.income)); let a = Math.random() * Math.PI * 2; [node.x,node.y] = pol2cart(r, a, radius, radius); } }); // data.forEach(d => { // // let bubbleRadius= = (((d.income / avgPowAmount) * radius) / data.length) * 2e9 // let bubbleRadius = radiusScale(income); // //radiusScale(d.income); // if (!(d.id in nodesIndex)) { // let r = Math.random() * (radius - bubbleRadius); // let a = Math.random() * Math.PI * 2; // let [x,y] = pol2cart(r, a, radius, radius); // nodesIndex[d.id] = {id: d.id, x, y}; // nodes.push(nodesIndex[d.id]); // } // let node = nodesIndex[d.id]; // _.extend( // node, // { // radius: bubbleRadius, // subData: d.subData, // data: d, // }); // }); let nodeEls = container.selectAll('.node') .data(nodes, d => d.id); let enter = nodeEls.enter().append('g') .attr('class', 'node') // .attr('opacity', 0); // nodeEls.exit() // .transition().duration(2000).attr('transform', 'scale(0)').attr('opacity', 0).remove(); nodeEls.exit().remove(); enter.append('circle') .attr('r', d => radiusScale(d.income)) .attr('stroke-width', 2) .attr('fill', fillColor) .attr('aria-label', (d,i) => { let inSubSectorView = !!d.strata; if (inSubSectorView) { return `Sub-sector ${d.subSector} and income band ${d.strata} with total income ${formatMoney(d.income)} and total spending ${formatMoney(d.expend)} (${formatCount(d.count)} organisations)`; } else { return `Sub-sector ${d.subSector} with total income ${formatMoney(d.income)} and total spending ${formatMoney(d.expend)} (${formatCount(d.count)} organisations)`; } }) .on('click', function (d,i) { handlers['click'] && handlers['click'].call(this, d,i) }) .on('mouseover', function (d,i) { handlers['mouseover'] && handlers['mouseover'].call(this, d,i) }) .on('mousemove', function (d,i) { handlers['mousemove'] && handlers['mousemove'].call(this, d,i) }) .on('mouseout', function (d,i) { handlers['mouseout'] && handlers['mouseout'].call(this, d,i) }); // .on('click', (d,i) => handlers.select(d.data, i, depth)); // nodeEls.transition().duration(2000).attr('opacity', 1); // Resolves collisions between d and all other circles. let padding = 20; function collide(alpha) { var quadtree = d3.geom.quadtree(nodes); return function(d) { var radius = radiusScale(d.income); var r = radius + padding, nx1 = d.x - r, nx2 = d.x + r, ny1 = d.y - r, ny2 = d.y + r; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = radius + radiusScale(quad.point.income) + padding; if (l < r) { l = (l - r) / l * alpha * collideDamper; d.x -= x *= l; d.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; } force.on("tick", (e) => { nodeEls .each(collide(e.alpha)) .each(moveTowardsCenter(e.alpha)) .attr('transform', d => { return `translate(${d.x}, ${d.y})` }); }) nodeEls .attr('transform', d => { return `translate(${d.x}, ${d.y})` }); // // nodeEls // .attr('transform', d => { // let c = center(d); // return `translate(${c.x}, ${c.y})` // }); force.start(); return self; }; function on(event, handler) { handlers[event] = handler; return self; } let self = {update, on}; return self; }
var width = $(window).width(), height = $(window).height() // TODO: Create pagewalker var repos = $.ajax({ url: 'https://api.github.com/users/QuantumPhi/repos?type=all&per_page=100', cache: true, async: false }) var color = function(language) { if(!language || !colors[language]) return '#717171' return colors[language].color }, force = d3.layout.force() .charge(-250) .linkDistance(100) .size([width, height]), svg = d3.select('#content') .append('svg') .attr('width', width) .attr('height', height) var groups = {} var nodes = function() { var array = [{ 'index': 0, 'color': '#000', 'group': -1,
draw: function() { this.set_ranges(); var x_scale = this.scales.x, y_scale = this.scales.y, color_scale = this.scales.color, link_color_scale = this.scales.link_color; // clean up the old graph this.d3el.selectAll(".node").remove(); this.d3el.selectAll(".link").remove(); this.force_layout = d3.layout.force() .size([this.parent.width, this.parent.height]) .linkDistance(this.model.get("link_distance")); if (x_scale && y_scale) { //set x and y on mark data manually this.model.mark_data.forEach(function(d) { d.x = x_scale.scale(d.xval) + x_scale.offset; d.y = y_scale.scale(d.yval) + y_scale.offset; }); } this.force_layout .nodes(this.model.mark_data) .links(this.model.link_data); if (!x_scale && !y_scale) { this.force_layout .charge(this.model.get("charge")) .on("tick", _.bind(this.tick, this)) .start(); } var directed = this.model.get("directed"); this.links = this.d3el.selectAll(".link") .data(this.force_layout.links()) .enter().append("path") .attr("class", "link") .style("stroke", function(d) { return link_color_scale ? link_color_scale.scale(d.value) : null; }) .style("stroke-width", function(d) { return d.link_width; }) .attr("marker-mid", directed ? "url(#arrow)" : null); var that = this; this.nodes = this.d3el.selectAll(".node") .data(this.force_layout.nodes()) .enter().append("g") .attr("class", "node") .call(this.force_layout.drag); this.nodes .append(function(d) { return document.createElementNS(d3.ns.prefix.svg, d.shape); }) .attr("class", "element") .each(function(d) { var node = d3.select(this); for(var key in d.shape_attrs) { node.attr(key, d.shape_attrs[key]); } }) .style("fill", function(d, i) { return that.get_node_color(d, i); }); this.nodes.append("text") .attr("class", "label") .attr("text-anchor", function(d) { return d.label_display === "center" ? "middle": "start"; }) .attr("x", function(d) { var xloc = 0; if (d.label_display === "outside") { switch (d.shape) { case "rect": xloc = d.shape_attrs.width / 2 + 5; break; case "circle": xloc = d.shape_attrs.r + 5; break; case "ellipse": xloc = d.shape_attrs.rx + 5; break; default: xloc = 0; } } return xloc; }) .attr("y", ".31em") .text(function(d) { return d.label; }) .style("display", function(d) { return d.label_display === "none" ? "none" : "inline"; }); this.nodes.on("click", _.bind(function(d, i) { this.event_dispatcher("element_clicked", {"data": d, "index": i}); }, this)); this.nodes.on("mouseover", _.bind(function(d, i) { this.hover_handler({"data": d, "index": i}); }, this)); this.nodes.on("mouseout", _.bind(function() { this.reset_hover(); }, this)); },
var allCountries = function (container_selector, service) { var model = this; model.service = service; var data = model.service.getActiveDataset("cityPmData"); var xVar = "pm2.5Mean"; data = data.filter(function (d) { return !(isNaN(d[xVar]) || isNaN(d[xVar])); }); model.iconTip = d3.tip() .attr('class', 'd3-tip') .offset([-10, 0]) .html(function (d) { return d.city + ", " + d.country + "<br>PM2.5: " + d[xVar]; }); model.legendtip = d3.tip() .attr('class', 'd3-tip') .offset([-10, 0]) .html(function () { return "Click to pin"; }); var margin = {top: 20, right: 60, bottom: 100, left: 5}; var width = 1100 - margin.left - margin.right, height = 700 - margin.top - margin.bottom; var radius = 3; var padding = 7; var radius2 = 9; // init svg model.svgPad = d3.select(container_selector).append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom); model.svg = model.svgPad.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); model.svg.call(model.iconTip); model.svg.call(model.legendtip); var x = d3.scale.linear() .range([0, width]); var y = d3.scale.linear() .range([height, 0]); var color = d3.scale.category10(); var xAxis = d3.svg.axis() .scale(x) .orient("top"); var svg = model.svg; var who = svg.append("g"); svg = svg.append("g"); var force = d3.layout.force() .nodes(data) .size([width, height]) .on("tick", tick) .charge(-0.001) .gravity(0) .chargeDistance(30); x.domain(d3.extent(data, function (d) { return d[xVar]; })).nice(); y.domain([0, height]).nice(); // set colors color("African Region"); color("Region of the Americas"); color("Eastern Mediterranean Region"); color("European Region"); color("South-East Asian Region"); color("Western Pacific Region"); // Set initial positions data.forEach(function (d) { d.x = d.px; d.y = d.py; d.color = color(d.region); d.radius = radius; }); service.regionScale = color; svg.append("g") .attr("class", "x-axis") .attr("transform", "translate(0," + 0 + ")") .call(xAxis) .append("text") .attr("class", "label") .attr("x", width) .attr("y", 12) .style("text-anchor", "end") .text("Annual PM 2.5 (μg/m3)"); // Interaction icon model.hand = model.svg.append("svg:image") .attr("x", width) .attr("y", 50) .attr("width", 30) .attr("height", 30) .attr("xlink:href", "img/hand.png"); var node = svg.selectAll(".dot") .data(data) .enter().append("circle") .attr("class", "dot") .attr("r", radius) .attr("cx", function (d) { return d.x; }) .attr("cy", function (d) { return d.y; }) .style("fill", function (d) { return d.color; }) .style("opacity", 0.3) .attr("stroke", "gray") .attr("stroke-width", 1) .on('mouseover', model.iconTip.show) .on('mouseout', model.iconTip.hide); var legend = svg.selectAll(".legend") .data(color.domain()) .enter().append("g") .attr("class", "legend") .attr("transform", function (d, i) { return "translate(0," + ((i * 20) + 50) + ")"; }); legend.append("rect") .attr("x", width - 18) .attr("width", 18) .attr("height", 18) .style("fill", "transparent") .style('cursor', 'pointer') .style("stroke-width", 3) .style("stroke", color) .on("mouseover", function (d) { model.legendtip.show(d); model.show(d); model.removeCountryLegend(); }) .on("mouseout", function (d) { model.legendtip.hide(d); model.update(); }) .on("click", function (d) { model.removeInteractivityIcon(); var index = model.pinned.indexOf(d); if (index > -1) { model.pinned.splice(index, 1); d3.select(this).style("fill", "transparent"); } else { model.pinned.push(d); d3.select(this).style("fill", color); } }); model.pinned = []; legend.append("text") .attr("x", width - 24) .attr("y", 9) .attr("dy", ".35em") .style("text-anchor", "end") .text(function (d) { return d; }); svg.append("line") .attr("x1", width - 150) .attr("y1", 175) .attr("x2", width) .attr("y2", 175) .style("stroke", "gray") .style("stroke-width", 1); // Selected city legend model.legend_city_text = svg.append("text") .attr("x", width - 24) .attr("y", 190) .attr("dy", ".35em") .style("text-anchor", "end") .text("Selected City"); model.legend_city_circle = svg.append("circle") .attr("r", radius) .attr("cx", width - 8) .attr("cy", 190) .style("fill", 'yellow') .attr("stroke", "gray") .attr("stroke-width", 1); force.start(); model.stop = null; function tick(e) { //if (model.stop !== null) { // force.stop(); //} // // //node.each(moveTowardDataPosition(e.alpha)); // //node.each(collide(e.alpha)); // //node.attr("cx", function (d) { // return d.x; // }) // .attr("cy", function (d) { // if (model.city && model.city.city === d.city && model.city.country === d.country) { // var x = d.x; // var y = d.y; // model.citydot.attr("cx", x).attr("cy", y); // // } // return d.y; // }); } function moveTowardDataPosition(alpha) { return function (d) { d.y += ( y(height / 2) - d.y) * 0.1 * alpha; }; } // Resolve collisions between nodes. function collide(alpha) { var quadtree = d3.geom.quadtree(data); return function (d) { var r = d.radius + radius + padding, nx1 = d.x - r, nx2 = d.x + r, ny1 = d.y - r, ny2 = d.y + r; quadtree.visit(function (quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = d.radius + quad.point.radius + radius2 + (d.color !== quad.point.color) * padding; if (l < r) { l = (l - r) / l * alpha; d.y -= y *= l; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; } who.append("line") .attr("x1", x(10)) .attr("y1", 0) .attr("x2", x(10)) .attr("y2", height) .style("stroke", "black") .style("stroke-width", 2); who.append("text") .attr("class", "label") .attr("x", x(10)) .attr("y", -10) .style("text-anchor", "middle") .text("WHO SAFE VALUE"); // Function to remove the interactivity icon later model.removeInteractivityIcon = function () { model.hand.remove(); return true; }; model.legend_country_text = null; // Function to remove the country legend text for later model.removeCountryLegend = function () { model.legend_country_text.remove(); model.legend_country_text = null; model.legend_country_circle.remove(); return true; }; model.show = function (group) { node.data(data) .each(function (d) { d3.select(this).style("opacity", 0.1) .style("fill", function () { return 'gray'; }); if (d.region === group) { d3.select(this).style("opacity", 0.7) .style("fill", function (d) { return d.color; }); } }); }; model.update = function () { // Selected country legend if (model.legend_country_text === null && model.pinned.indexOf(service.getSelectedCityData().region) <= -1) { model.legend_country_text = svg.append("text") .attr("x", width - 24) .attr("y", 210) .attr("dy", ".35em") .style("text-anchor", "end") .text("Selected Country"); model.legend_country_circle = svg.append("circle") .attr("r", radius) .attr("cx", width - 8) .attr("cy", 210) .style("fill", function () { var colorVal = color(service.getSelectedCityData().region); //console.log(service.getSelectedCityData()); return colorVal; }) .attr("stroke", "grey") .attr("stroke-width", 1); } model.legend_country_circle .style("fill", function () { var colorVal = color(service.getSelectedCityData().region); //console.log(service.getSelectedCityData()); return colorVal; }); if (model.cityline) { model.cityline.remove(); model.others.remove(); if (model.citydot) { model.citydot.remove(); } } model.others = svg.append("g"); model.cityline = svg.append("g"); model.city = model.service.getSelectedCityData(); //var y = 0; var values = []; var total = 0; var better = 0; node.data(data) .each(function (d) { //console.log(d); d3.select(this).style("opacity", 0.3) .style("fill", function (d) { if (model.pinned.indexOf(d.region) > -1) { d3.select(this).style("opacity", 0.7); return d.color; } return 'gray'; }); if (d[xVar] > model.city[xVar]) { better++; } total++; if (d.country === model.city.country) { d.lineY = d3.select(this).attr("cy"); d.lineX = d3.select(this).attr("cx"); if (d.lineY > 0) { values.push(d); } d3.select(this).style("opacity", 0.7) .style("fill", d.color); if (d.city === model.city.city) { d3.select(this).style("opacity", 0.3) .style("fill", 'gray'); model.citydot = svg.append("circle") .attr("r", radius) .attr("cx", d.lineX) .attr("cy", d.lineY) .style("fill", 'yellow') .attr("stroke", "gray") .attr("stroke-width", 1) .on('mouseover', function () { model.iconTip.show(d); }) .on('mouseout', function () { model.iconTip.hide(d); }); } } } ); // set cities in country lines var lowest = 9001; var highest = 0; var highest_val, lowest_val; values.forEach(function (d) { if (x(d[xVar]) > highest) { highest = x(d[xVar]); highest_val = d[xVar]; } if (x(d[xVar]) < lowest) { lowest = x(d[xVar]); lowest_val = d[xVar]; } }); model.others.append("line") .attr("x1", lowest) .attr("y1", height + 55) .attr("x2", highest) .attr("y2", height + 55) .style("stroke", "black") .style("stroke-width", 1); model.cityline.append("text") .attr("class", "label") .attr("x", (highest + lowest) / 2) .attr("y", height + 70) .style("text-anchor", "middle") .style("fill", "black") .text(model.city.country); // Lowest model.cityline.append("text") .attr("class", "label") .attr("x", lowest - 10) .attr("y", height + 60) .style("text-anchor", "middle") .style("fill", "black") .text(lowest_val); // Highest model.cityline.append("text") .attr("class", "label") .attr("x", highest + 10) .attr("y", height + 60) .style("text-anchor", "middle") .style("fill", "black") .text(highest_val); var c = model.city.city + ", " + model.city.country; var perc = (better / total * 100); model.textLine = ""; if (perc >= 50) { model.textLine = "The air in " + c + " is better than the air in " + perc.toFixed(0) + "% of all other recorded cities."; } else { model.textLine = "The air in " + c + " is worse than the air in " + (100 - perc).toFixed(0) + "% of all other recorded cities."; } $("#percentCompared").text(model.textLine); model.setSocial(); window.history.pushState('string', 'The Air we Breathe', 'http://www.theairwebreathe.org/index.html?city=' + model.city.city + '&country=' + model.city.country); }; model.setSocial = function () { $('meta[property="og:title"]').remove(); $('head').append('<meta property="og:title" content="' + model.text + '">'); var simpleLink = 'http://www.theairwebreathe.org'; var Linkedinlink = simpleLink + '/index.html?city=' + model.city.city + '%26country=' + model.city.country; var link = simpleLink + '/index.html?city=' + encodeURIComponent(model.city.city) + '&country=' + encodeURIComponent(model.city.country); $("#fb_share").unbind(); $("#fb_share").click(function () { FB.ui({ method: 'feed', link: link, caption: 'The Air We Breathe - Free Radicals', name: model.textLine, }, function (response) { }); }); $("#linkedInShare").unbind() $("#linkedInShare").click(function () { var llink = 'https://www.linkedin.com/shareArticle?url=' + Linkedinlink + '&title="' + encodeURIComponent(model.textLine) + '"&summary=Air%20pollution%20is%20now%20the%20single%20biggest%20environmental%20health%20risk%20to%20humans.%20Outdoor%20air%20pollution%20in%20both%20cities%20and%20rural%20areas%20was%20estimated%20to%20cause%203.7%20million%20premature%20deaths%20worldwide%20in%202012.&source=LinkedIn'; window.open(llink, "popupWindow", "width=600, height=400, scrollbars=yes"); }); $("#twitterShare").unbind() $("#twitterShare").click(function () { var tlink = 'https://twitter.com/intent/tweet?hashtags=air%2Cpollution&original_referer=' + simpleLink + '&ref_src=twsrc%5Etfw&text=' + encodeURIComponent('"' + model.textLine + '" - ') + '&tw_p=tweetbutton&url=' + simpleLink; window.open(tlink, "popupWindow", "width=600, height=400, scrollbars=yes"); }); }; model.updateExternal = function () { model.update(); } //setTimeout(function () { // console.log(JSON.stringify(data)); //}, 5 * 60000); };
Template.logincounts.rendered = function () { container=document.getElementById('logins-wrapper') container.style.cursor='wait' var margin = {top: 0, right: 0, bottom: 0, left: 0}, width = 960 - margin.left - margin.right, height = 500 - margin.top - margin.bottom, minRadius=3, maxRadius=40, clipPadding=4; var fill = d3.scale.category10(); var nodes = [], links = [], foci = [{x: 50, y: 50}, {x: 350, y: 250}]; var svg = d3.select(".logins-wrapper").append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var force = d3.layout.force() .nodes(nodes) .links([]) .gravity(.1) .charge(-300) .size([width, height]) .on("tick", tick); var node = svg.selectAll(".node"), link = svg.selectAll(".link"); var r = d3.scale.sqrt() .range([0, maxRadius]); Meteor.apply('logincounts', [], onResultReceived = function(err,result){ //debugLog(err,result); if (typeof err == 'undefined') { logincountsResult.status='completed'; logincountsResult.result = result; logincountsResult.content=result.content; logincountsResult.data=result.data; jsondata=result.data; container.style.cursor='auto'; r.domain([0, d3.max(jsondata, function(d) { return d.success+ d.failures; })]) jsondata.forEach(function(d){ d.id=d.username; d.count=(d.success +d.failures) d.k = fraction(d.success, d.failures); d.r = r(d.count); d.cr = Math.max(minRadius, d.r); nodes.push(d) }); start(); } else { //debugLog(err,result); logincountsResult.status='error'; logincountsResult.error=err; container.style.cursor='auto'; } }); function start() { container.style.cursor='auto' node = node.data(force.nodes(), function(d) { return d.id;}); //console.log(node) //make a node for each entry node.enter() .append("a") .attr("class", function(d) { return "node " + d.id; }) .attr("class", "node") .call(force.drag); // delineate between success/failures: var successEnter = node.append("g") .attr("class", "g-success"); successEnter.append("clipPath") .attr("id", function(d) { return "g-clip-success-" + d.username; }) .append("rect"); successEnter.append("circle") .attr("r", function(d) {return d.cr;}) .attr("class","successcircle"); var failureEnter = node.append("g") .attr("class", "g-failures"); failureEnter.append("clipPath") .attr("id", function(d) { return "g-clip-failure-" + d.username; }) .append("rect"); failureEnter.append("circle") .attr("r", function(d) {return d.cr;}) .attr("class","failurecircle"); node.append("line") .attr("class", "g-split") .attr("x1", function(d) { return -d.cr + 2 * d.r * d.k; }) .attr("y1", function(d) { return -Math.sqrt(d.cr * d.cr - Math.pow(-d.cr + 2 * d.cr * d.k, 2)); }) .attr("x2", function(d) { return -d.cr + 2 * d.cr * d.k; }) .attr("y2", function(d) { return Math.sqrt(d.cr * d.cr - Math.pow(-d.cr + 2 * d.cr * d.k, 2)); }) .attr("stroke","white") .attr("stroke-width",1); node.append("svg:text") .attr("x", 1) .attr("y", ".3em") .attr("class","textlabel") .text(function(d) { return d.username; }); // make a mouse over for the success/failure counts node.append("title") .text(function(d) { return d.username + ": " + d.success + " / " + d.failures }); // size circle clips node.selectAll("rect") .attr("y", function(d) { return -d.r - clipPadding; }) .attr("height", function(d) { return 2 * d.r + 2 * clipPadding; }); node.select(".g-success rect") .style("display", function(d) { return d.k > 0 ? null : "none" }) .attr("x", function(d) { return -d.r - clipPadding; }) .attr("width", function(d) { return 2 * d.r * d.k + clipPadding; }); node.select(".g-success circle") .attr("clip-path", function(d) { return d.k < 1 ? "url(#g-clip-success-" + d.username + ")" : null; }); node.select(".g-failures rect") .style("display", function(d) { return d.k < 1 ? null : "none" }) .attr("x", function(d) { return -d.cr + 2 * d.cr * d.k; }) .attr("width", function(d) { return 2 * d.cr; }); node.select(".g-failures circle") .attr("clip-path", function(d) { return d.k > 0 ? "url(#g-clip-failure-" + d.username + ")" : null; }); node.exit().remove(); force.start(); } function tick(e) { var k = .1 * e.alpha; // Push nodes toward their designated focus // based on ratio of success to failure nodes.forEach(function(o, i) { if (o.success > o.failures ){ o.y += (foci[0].y - o.y) * k; o.x += (foci[0].x - o.x) * k; }else{ o.y += (foci[1].y - o.y) * k; o.x += (foci[1].x - o.x) * k; } }); svg.selectAll(".node") .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); } // Given two quantities a and b, returns the fraction to split the circle a + b. function fraction(a, b) { var k = a / (a + b); if (k > 0 && k < 1) { var t0, t1 = Math.pow(12 * k * Math.PI, 1 / 3); for (var i = 0; i < 10; ++i) { // Solve for theta numerically. t0 = t1; t1 = (Math.sin(t0) - t0 * Math.cos(t0) + 2 * k * Math.PI) / (1 - Math.cos(t0)); } k = (1 - Math.cos(t1 / 2)) / 2; } return k; } } //end Template rendered
function visualizeTypeSolver(typeSolver, types) { // Taken from d3 example of force-directed graphs var nodes = getNodes(typeSolver.constraints); var links = getLinks(typeSolver.constraints); var width = window.innerWidth - 400; var height = window.innerHeight; var color = d3.scale.category20(); var colorTypes = []; for (var i = 0; i < nodes.length; i++) { colorTypes.push(nodes[i].type); } for (var i = 0; i < links.length; i++) { colorTypes.push(links[i].type); } colorTypes = Utils.set(colorTypes); function color(type) { colorTypes.push(type); return actualColor(type); } var force = d3.layout.force() .charge(-300) .linkDistance(100) .size([width / 2, height / 2]); var prevSvg = document.getElementById('d3-container'); if (prevSvg) { prevSvg.parentNode.removeChild(prevSvg); } var svg = d3.select('body').append('svg') .attr('width', width) .attr('height', height) .attr('id', 'd3-container'); force.nodes(nodes) .links(links) .start(); var link = svg.selectAll('.link') .data(links) .enter().append('line') .attr('class', 'link') .style('stroke-width', '5px') .style('stroke', function(d) { return color(d.type); }); var node = svg.selectAll('.node') .data(nodes) .enter().append('g') .attr('class', 'node'); node.append('circle') .attr('r', function(d) { return 10 + (getNodeText(d) + ': ' + getNodeTypeText(d)).length * 3.5; }) .style('fill', function(d) { return color(d.type); }) .call(force.drag); function getNodeTypeText(node) { var finalTypes = types.find(function(candidateType) { return node === candidateType.node; }); if (!finalTypes) { return '??'; } return getTypesText(finalTypes.types); } function getTypesText(types) { if (types.length === 0) { return 'UNSAT'; } var text = ''; for (var i = 0; i < types.length; i++) { text += types[i].name; if (i < types.length - 1) { text += '|'; } } return text; } node.append('text') .attr('dy', '.31em') .attr('text-anchor', 'middle') .text(function(d) { return getNodeText(d) + ': ' + getNodeTypeText(d); }); node.append('title') .text(function(d) { return getNodeText(d) + ': ' + getNodeTypeText(d); }); force.on('tick', function() { link.attr('x1', function(d) { return d.source.x * 2; }) .attr('y1', function(d) { return d.source.y * 2; }) .attr('x2', function(d) { return d.target.x * 2; }) .attr('y2', function(d) { return d.target.y * 2; }); node.attr('transform', function(d) { return 'translate(' + (d.x * 2) + ',' + (d.y * 2) + ')'; }); }); makeLegend(colorTypes, color); }
initialize: function(options) { var self = this; self.width = options.width; self.height = options.height; self.base.classed('sillyforce', true); self.bases = { nodes: self.base.append('g') .classed('nodes', true), links: self.base.append('g') .classed('links', true) }; function onEnd() { if (self.running) { generateLinks(); self.draw(); self.start(); } } function tick() { self.base.selectAll('.node').attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); } self.force = d3.layout.force() .charge(-400) .linkDistance(320) .size([self.width, self.height]) .on("tick", tick) .on("end", onEnd); self.layer('nodes', self.bases.nodes, { dataBind: function(data) { return this.selectAll(".node") .data(self.force.nodes(), function(d) { return d.id; }); }, insert: function() { return this.append("circle"); }, events: { enter: function() { this.attr("class", function(d) { return "node " + d.id; }) .attr("r", 8) .attr("fill", colors.regular); }, "merge:transition": function() { this.attr("opacity", function(d) { return Math.random(); }); }, exit: function() { this.remove(); } } }); },
RDFSChema.visualize = function(data){ var nodes = data.nodes; var links = data.links; var width = window.innerWidth; var height = window.innerHeight; var edges = []; links.forEach(function(e) { var sourceNode = nodes.filter(function(n) { return n.id === e.source; })[0], targetNode = nodes.filter(function(n) { return n.id === e.target; })[0]; edges.push({source: sourceNode, target: targetNode, label:e.label, uri: e.uri}); }); // set up data var graph = {}; graph.nodes = nodes; graph.links = edges; //Set up the colour scale var color = d3.scale.category20(); //Set up the force layout var force = d3.layout.force() .charge(-500) .theta(0.1) .gravity(0.7) .linkDistance(200) .size([width, height]); //Append a SVG to the body of the html page. Assign this SVG as an object to svg var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) .attr('viewBox','0 0 '+Math.min(width,height)+' '+Math.min(width,height)) //Creates the graph data structure out of the json data force.nodes(graph.nodes) .links(graph.links) .start(); //Create all the line svgs but without locations yet var edges = svg.selectAll("line") .data(graph.links) .enter() .append("line") .attr('id', function(d, i){return 'edge' + i;}) .style("marker-end", "url(#arrowhead)") //Added .style('stroke-width', function(d){ return d.count; }) .style("pointer-events", "none"); //Set up tooltip var tip = d3.tip() .attr('class', 'd3-tip') .offset([-10, 0]) .html(function(d) { return "<strong>Uri:</strong> <span style='color:red'>" + d.uri + "</span>"; }) svg.call(tip); //Do the same with the circles for the nodes - no var node = svg.selectAll(".rect") .data(graph.nodes) .enter().append("g") .attr("class", "node") .call(force.drag) .on('click', tip.show) .on('mouseout', function() { d3.select(".d3-tip") .transition() .delay(100) .duration(1000) .style("opacity",0) .style('pointer-events', 'none') }) node.append('rect') .attr('height', font + rect_padding) .attr('width', function(d){ if(d.label){ return (d.label.length/2 * font) + 'px'; }else{ return font + 'px'; } }) .style('stroke-width', 1) .style('stroke', 'black') .style("fill", function (d) { if(d.label){ return 'green'; }else{ return 'red'; } }); node.append("text") //.attr('text-anchor', 'middle') .text(function(d){ //console.log(d) return d.label; }) .style('font-size',font + 'px') .style('color', 'black') .attr("dy", font + 'px'); var edgepaths = svg.selectAll(".edgepath") .data(graph.links) .enter() .append('path') .attr({'d': function(d) {return 'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y}, 'class':'edgepath', 'fill-opacity':1, 'stroke-opacity':1, 'fill':'blue', 'stroke':'red', 'id':function(d,i) {return 'edgepath'+i}}) .style("pointer-events", "none"); var edgelabels = svg.selectAll(".edgelabel") .data(graph.links) .enter() .append('text') .style("pointer-events", "none") .attr({'class':'edgelabel', 'id':function(d,i){return 'edgelabel'+i}, 'dx':80, 'dy':0, 'font-size':10, 'fill':'#aaa'}); edgelabels.append('textPath') .attr('xlink:href',function(d,i) {return '#edgepath'+i}) .style("pointer-events", "none") .text(function(d,i){ return d.label }); svg.append('defs').append('marker') .attr({'id':'arrowhead', 'viewBox':'-0 -5 10 10', 'refX':25, 'refY':0, //'markerUnits':'strokeWidth', 'orient':'auto', 'markerWidth':10, 'markerHeight':10, 'xoverflow':'visible'}) .append('svg:path') .attr('d', 'M 0,-5 L 10 ,0 L 0,5') .attr('fill', '#ccc') .attr('stroke','#ccc'); /* //fisheye var fisheye = FE.fisheye.circular() .radius(120); svg.on("mousemove", function() { force.stop(); fisheye.focus(d3.mouse(this)); d3.selectAll("rect").each(function(d) { d.fisheye = fisheye(d); }) .attr("x", function(d) { return d.fisheye.x; }) .attr("y", function(d) { return d.fisheye.y; }) edges.attr("x1", function(d) { return d.source.fisheye.x; }) .attr("y1", function(d) { return d.source.fisheye.y; }) .attr("x2", function(d) { return d.target.fisheye.x; }) .attr("y2", function(d) { return d.target.fisheye.y; }); }); */ //Now we are giving the SVGs co-ordinates - the force layout is generating the co-ordinates which this code is using to update the attributes of the SVG elements force.on("tick", function () { edges.attr("x1", function (d) { return d.source.x; }) .attr("y1", function (d) { return d.source.y; }) .attr("x2", function (d) { return d.target.x; }) .attr("y2", function (d) { return d.target.y; }); d3.selectAll("rect").attr("x", function (d) { return d.x; }) .attr("y", function (d) { return d.y; }); d3.selectAll("text").attr("x", function (d) { return d.x; }) .attr("y", function (d) { return d.y; }); edgepaths.attr('d', function(d){ var path='M'+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y; return path; }); edgelabels.attr('transform',function(d,i){ if (d.target.x<d.source.x){ var bbox = this.getBBox(); var rx = bbox.x+bbox.width/2; var ry = bbox.y+bbox.height/2; return 'rotate(180 '+rx+' '+ry+')'; } else { return 'rotate(0)'; } }); node.each(collide(1)); //Added }); // collision detection var padding = 1, // separation between circles radius=8; function collide(alpha) { var quadtree = d3.geom.quadtree(graph.nodes); return function(d) { var rb = 2*radius + padding, nx1 = d.x - rb, nx2 = d.x + rb, ny1 = d.y - rb, ny2 = d.y + rb; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y); if (l < rb) { l = (l - rb) / l * alpha; d.x -= x *= l; d.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; } //Arrows svg.append("defs").selectAll("marker") .data(["suit", "licensing", "resolved"]) .enter().append("marker") .attr("id", function(d) { return d; }) .attr("viewBox", "0 -5 10 10") .attr("refX", 25) .attr("refY", 0) .attr("markerWidth", 6) .attr("markerHeight", 6) .attr("orient", "auto") .append("path") .attr("d", "M0,-5L10,0L0,5 L10,0 L0, -5") .style("stroke", "#4679BD") .style("opacity", "0.6"); //Toggle stores whether the highlighting is on var toggle = 0; //Create an array logging what is connected to what var linkedByIndex = {}; for (var i = 0; i < graph.nodes.length; i++) { linkedByIndex[i + "," + i] = 1; }; /*// search var optArray = []; for (var i = 0; i < graph.nodes.length - 1; i++) { optArray.push(graph.nodes[i].label); } optArray = optArray.sort(); $(function () { $("#search").autocomplete({ source: optArray }); }); function searchNode() { //find the node var selectedVal = document.getElementById('search').value; var node = svg.selectAll(".node"); if (selectedVal == "none") { node.style("stroke", "white").style("stroke-width", "1"); } else { var selected = node.filter(function (d, i) { return d.label != selectedVal; }); selected.style("opacity", "0"); var link = svg.selectAll(".link") link.style("opacity", "0"); d3.selectAll(".node, .link").transition() .duration(5000) .style("opacity", 1); } }*/ }
var createVisualization = function(containerElement, data){ // set up SVG for D3 var width = 495, height = 495, colors = d3.scale.category10(); var svg = d3.select(containerElement) .append('svg') .attr('oncontextmenu', 'return false;') .attr('width', width) .attr('height', height); // set up initial nodes and links // - nodes are known by 'id', not by index in array. // - reflexive edges are indicated on the node (as a bold black circle). // - links are always source < target; edge directions are set by 'left' and 'right'. var nodes = [ {id: 0, reflexive: false}, {id: 1, reflexive: true }, {id: 2, reflexive: false} ], lastNodeId = 2, links = [ {source: nodes[0], target: nodes[1], left: true, right: true }, {source: nodes[1], target: nodes[2], left: true, right: true } ]; // init D3 force layout var force = d3.layout.force() .nodes(nodes) .links(links) .size([width, height]) .linkDistance(150) .charge(-500) .on('tick', tick) // define arrow markers for graph links svg.append('svg:defs').append('svg:marker') .attr('id', 'end-arrow') .attr('viewBox', '0 -5 10 10') .attr('refX', 6) .attr('markerWidth', 3) .attr('markerHeight', 3) .attr('orient', 'auto') .append('svg:path') .attr('d', 'M0,-5L10,0L0,5') .attr('fill', '#000'); svg.append('svg:defs').append('svg:marker') .attr('id', 'start-arrow') .attr('viewBox', '0 -5 10 10') .attr('refX', 4) .attr('markerWidth', 3) .attr('markerHeight', 3) .attr('orient', 'auto') .append('svg:path') .attr('d', 'M10,-5L0,0L10,5') .attr('fill', '#000'); // line displayed when dragging new nodes var drag_line = svg.append('svg:path') .attr('class', 'link dragline hidden') .attr('d', 'M0,0L0,0'); // handles to link and node element groups var path = svg.append('svg:g').selectAll('path'), circle = svg.append('svg:g').selectAll('g'); // mouse event vars var selected_node = null, selected_link = null, mousedown_link = null, mousedown_node = null, mouseup_node = null; function resetMouseVars() { mousedown_node = null; mouseup_node = null; mousedown_link = null; } // update force layout (called automatically each iteration) function tick() { // draw directed edges with proper padding from node centers path.attr('d', function(d) { var deltaX = d.target.x - d.source.x, deltaY = d.target.y - d.source.y, dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY), normX = deltaX / dist, normY = deltaY / dist, sourcePadding = d.left ? 17 : 12, targetPadding = d.right ? 17 : 12, sourceX = d.source.x + (sourcePadding * normX), sourceY = d.source.y + (sourcePadding * normY), targetX = d.target.x - (targetPadding * normX), targetY = d.target.y - (targetPadding * normY); return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY; }); circle.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; }); } // update graph (called when needed) function restart() { // path (link) group path = path.data(links); // update existing links path.classed('selected', function(d) { return d === selected_link; }) .style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; }) .style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; }); // add new links path.enter().append('svg:path') .attr('class', 'link') .classed('selected', function(d) { return d === selected_link; }) .style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; }) .style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; }) .on('mousedown', function(d) { if(d3.event.ctrlKey) return; // select link mousedown_link = d; if(mousedown_link === selected_link) selected_link = null; else selected_link = mousedown_link; selected_node = null; restart(); }); // remove old links path.exit().remove(); // circle (node) group // NB: the function arg is crucial here! nodes are known by id, not by index! circle = circle.data(nodes, function(d) { return d.id; }); // update existing nodes (reflexive & selected visual states) circle.selectAll('circle') .style('fill', function(d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); }) .classed('reflexive', function(d) { return d.reflexive; }); // add new nodes var g = circle.enter().append('svg:g'); g.append('svg:circle') .attr('class', 'node') .attr('r', 12) .style('fill', function(d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); }) .style('stroke', function(d) { return d3.rgb(colors(d.id)).darker().toString(); }) .classed('reflexive', function(d) { return d.reflexive; }) .on('mouseover', function(d) { if(!mousedown_node || d === mousedown_node) return; // enlarge target node d3.select(this).attr('transform', 'scale(1.1)'); }) .on('mouseout', function(d) { if(!mousedown_node || d === mousedown_node) return; // unenlarge target node d3.select(this).attr('transform', ''); }) .on('mousedown', function(d) { if(d3.event.ctrlKey) return; // select node mousedown_node = d; if(mousedown_node === selected_node) selected_node = null; else selected_node = mousedown_node; selected_link = null; // reposition drag line drag_line .style('marker-end', 'url(#end-arrow)') .classed('hidden', false) .attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + mousedown_node.x + ',' + mousedown_node.y); restart(); }) .on('mouseup', function(d) { if(!mousedown_node) return; // needed by FF drag_line .classed('hidden', true) .style('marker-end', ''); // check for drag-to-self mouseup_node = d; if(mouseup_node === mousedown_node) { resetMouseVars(); return; } // unenlarge target node d3.select(this).attr('transform', ''); // add link to graph (update if exists) // NB: links are strictly source < target; arrows separately specified by booleans var source, target, direction; if(mousedown_node.id < mouseup_node.id) { source = mousedown_node; target = mouseup_node; direction = 'right'; } else { source = mouseup_node; target = mousedown_node; direction = 'left'; } var link; link = links.filter(function(l) { return (l.source === source && l.target === target); })[0]; if(link) { link[direction] = true; } else { link = {source: source, target: target, left: false, right: false}; link[direction] = true; links.push(link); } // select new link selected_link = link; selected_node = null; restart(); }); // show node IDs g.append('svg:text') .attr('x', 0) .attr('y', 4) .attr('class', 'id') .text(function(d) { return d.id; }); // remove old nodes circle.exit().remove(); // set the graph in motion force.start(); } function mousedown() { // prevent I-bar on drag //d3.event.preventDefault(); // because :active only works in WebKit? svg.classed('active', true); if(d3.event.ctrlKey || mousedown_node || mousedown_link) return; // insert new node at point var point = d3.mouse(this), node = {id: ++lastNodeId, reflexive: false}; node.x = point[0]; node.y = point[1]; nodes.push(node); restart(); } function mousemove() { if(!mousedown_node) return; // update drag line drag_line.attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + d3.mouse(this)[0] + ',' + d3.mouse(this)[1]); restart(); } function mouseup() { if(mousedown_node) { // hide drag line drag_line .classed('hidden', true) .style('marker-end', ''); } // because :active only works in WebKit? svg.classed('active', false); // clear mouse event vars resetMouseVars(); } function spliceLinksForNode(node) { var toSplice = links.filter(function(l) { return (l.source === node || l.target === node); }); toSplice.map(function(l) { links.splice(links.indexOf(l), 1); }); } // only respond once per keydown var lastKeyDown = -1; function keydown() { d3.event.preventDefault(); if(lastKeyDown !== -1) return; lastKeyDown = d3.event.keyCode; // ctrl if(d3.event.keyCode === 17) { circle.call(force.drag); svg.classed('ctrl', true); } if(!selected_node && !selected_link) return; switch(d3.event.keyCode) { case 8: // backspace case 46: // delete if(selected_node) { nodes.splice(nodes.indexOf(selected_node), 1); spliceLinksForNode(selected_node); } else if(selected_link) { links.splice(links.indexOf(selected_link), 1); } selected_link = null; selected_node = null; restart(); break; case 66: // B if(selected_link) { // set link direction to both left and right selected_link.left = true; selected_link.right = true; } restart(); break; case 76: // L if(selected_link) { // set link direction to left only selected_link.left = true; selected_link.right = false; } restart(); break; case 82: // R if(selected_node) { // toggle node reflexivity selected_node.reflexive = !selected_node.reflexive; } else if(selected_link) { // set link direction to right only selected_link.left = false; selected_link.right = true; } restart(); break; } } function keyup() { lastKeyDown = -1; // ctrl if(d3.event.keyCode === 17) { circle .on('mousedown.drag', null) .on('touchstart.drag', null); svg.classed('ctrl', false); } } // app starts here svg.on('mousedown', mousedown) .on('mousemove', mousemove) .on('mouseup', mouseup); d3.select(window) .on('keydown', keydown) .on('keyup', keyup); restart(); return containerElement; }
componentDidMount: function() { var width = window.innerWidth, height = window.innerHeight; var foci = []; for (var i = 0; i <= 10; i++) { foci[i] = { x: (i+1) * width / 13 + width / 26, y: height / 2 }; } var svg = d3.select('#main-svg') .attr('xmlns', 'http://www.w3.org/2000/svg') .attr('width', width) .attr('height', height); var force = d3.layout.force() .gravity(0.1) .charge(-300) .chargeDistance(Infinity) .friction(0.9) .linkDistance(2) .linkStrength(function(d) { return d.weight / 5; }) .size([width, height]); var component = this; var nodes = Array.from(this.props.animes.animeIds).map(function(id) { return { name: component.props.animes.animeTitles[id], url: component.props.animes.animeUrls[id], myScore: component.props.animes.animePersonalScores[id], group: 1 }; }); var links = this.props.edges; force.nodes(nodes) .links(links) .start(); var link = svg.selectAll('.link') .data(links) .enter().append('svg:line') .attr('class', 'link'); var node = svg.selectAll('.node') .data(nodes) .enter().append('g') .attr('class', 'node') .attr('x', function(d) { return d.myScore; }) .attr('y', 0) .attr('px', function(d) { return d.myScore; }) .attr('py', 0) .on('mouseover', mouseover) .on('mouseout', mouseout) .call(force.drag); node.append('circle') .attr('r', 30.1) .attr('cx', 0) .attr('cy', 0) .attr('stroke', 'black') .attr('stroke-width', 1); node.append('svg:image') .attr('xlink:href', function(d) { return d.url; }) .attr('x', -35) .attr('y', -35) .attr('width', 70) .attr('height', 70) .attr('preserveAspectRatio', 'xMidYMid slice') .attr('clip-path', 'url(#clipCircle)'); /*node.append('text') .attr('dx', 12) .attr('dy', '.35em') .text(function(d) { return d.myScore; });*/ function mouseover() { d3.select(this).selectAll('image,circle').transition() .duration(400) .attr('transform', 'scale(3, 3)'); }; function mouseout() { d3.select(this).selectAll('image,circle').transition() .duration(400) .attr('transform', 'scale(1, 1)'); }; force.on('tick', function(e) { link.attr('x1', function(d) { return d.source.x; }) .attr('y1', function(d) { return d.source.y; }) .attr('x2', function(d) { return d.target.x; }) .attr('y2', function(d) { return d.target.y; }); var k = .1 * e.alpha; nodes.forEach(function(o) { o.y += (foci[o.myScore].y - o.y) * k; o.x += (foci[o.myScore].x - o.x) * k; }); node.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; }); }); },
buildForceLayout: function() { let width = 800; let height = 800; let color = d3.scale.category20(); this.refs.forceLayout.innerHTML = ''; this.zoom = d3.behavior.zoom() .scaleExtent([-10, 10]) .on('zoom', this.zoomed); this.force = d3.layout.force() .size([width, height]) .nodes(this.props.nodes) .charge(function(d){ let charge = -4000; if (d.index === 0) charge = 10 * charge; return charge; }) .links(this.props.links) .linkDistance(15); this.svg = d3.select(this.refs.forceLayout).append('svg') .attr('width', '100%') .attr('height', height) .append('g'); let rect = this.svg.append('rect') .attr('width', width) .attr('height', height) .style('fill', 'none') .style('pointer-events', 'all'); this.container = this.svg.append('g'); let link = this.container.append('g') .attr('class', 'links') .selectAll('.link') .data(this.props.links) .enter() .append('line') .attr('class', 'link'); let node = this.container.append('g') .attr('class', 'nodes') .selectAll('.node') .data(this.props.nodes) .enter(); let circles = node.append('circle') .style('fill', function(node) { return color(node.type); }) .attr('class', 'node'); this.force.on('end', () => { this.setState({ loading: false }); let minWeight = this.props.nodes.reduce((prev, current) => { return Math.min(prev, current.weight); }, 100); let maxWeight = this.props.nodes.reduce((prev, current) => { return Math.max(prev, current.weight); }, 0); this.svg.call(this.zoom); circles.attr('r', function(d) { return Math.max(5, (d.weight-minWeight+1)/(maxWeight+1)*15); }) .attr('cx', function(d) { return d.x; }) .attr('cy', function(d) { return d.y; }); node.append('text') .attr('x', function(d) { return d.x; }) .attr('y', function(d) { return d.y; }) .attr('text-anchor', 'middle') .attr('dy', '1.25em') .text(function(d) { return d.name; }) .style('fill', '#555').style('font-family', 'Arial').style('font-size', 12); link.attr('x1', function(d) { return d.source.x; }) .attr('y1', function(d) { return d.source.y; }) .attr('x2', function(d) { return d.target.x; }) .attr('y2', function(d) { return d.target.y; }); this.zoomFit(); }); this.setState({ loading: true }); this.force.start(); },
Force.prototype._init = function() { var opts = this.opts var height = this.height var width = this.width var selector = this.selector var links = this.data.links var nodes = this.data.nodes var self = this this.$el = $(selector).first(); // if points are colored use gray, otherwise use our default var linkStrokeColor = nodes[0].c ? '#999' : '#A38EF3'; // set opacity inversely proportional to number of links var linkStrokeOpacity = Math.max(1 - 0.0005 * links.length, 0.5) // set circle stroke thickness based on number of nodes var strokeWidth = nodes.length > 500 ? 1 : 1.1 function nearestPoint(points, target, xscale, yscale) { // find point in points nearest to target // using scales x and y // point must have attrs x, y, and s var i = 0, count = 0; var found, dist, n, p; while (count == 0 & i < points.length) { p = points[i] dist = Math.sqrt(Math.pow(xscale(p.x) - target[0], 2) + Math.pow(yscale(p.y) - target[1], 2)) if (dist <= p.s) { found = p count = 1 } i++; } return found } this.x = d3.scale.linear() .domain([0, width + margin.left + margin.right]) .range([0, width + margin.left + margin.right]); this.y = d3.scale.linear() .domain([height + margin.top + margin.bottom, 0]) .range([height + margin.top + margin.bottom, 0]); var zoom = d3.behavior.zoom() .x(self.x) .y(self.y) .scaleExtent([0.2, 7]) .on('zoom', zoomed) var container = d3.select(selector) .append('div') .style('width', width + margin.left + margin.right + "px") .style('height', height + margin.top + margin.bottom + "px") var canvas = container .append('canvas') .attr('class', 'force-plot canvas') .attr('width', width + margin.left + margin.right) .attr('height', height + margin.top + margin.bottom) .call(zoom) .on("click", mouseHandler) .on("dblclick.zoom", null) .node().getContext("2d") var loading = container .append('svg:svg') .attr('class', 'force-plot svg') .attr('width', width + margin.left + margin.right) .attr('height', height + margin.top + margin.bottom) .append("text") .attr("x", width / 2) .attr("y", height / 2) .attr("dy", ".35em") .style("text-anchor", "middle") .text("loading..."); function mouseHandler() { if (d3.event.defaultPrevented) return; var pos = d3.mouse(this) var found = nearestPoint(nodes, pos, self.x, self.y) if (found) { highlighted = [] highlighted.push(found.i) self.emit('hover', found); } else { highlighted = [] selected = [] }; redraw(); } var selected = []; var highlighted = []; var shiftKey; var brush = d3.svg.brush() .x(self.x) .y(self.y) .on("brushstart", function() { // remove any highlighting highlighted = [] // select a point if we click without extent var pos = d3.mouse(this) var found = nearestPoint(nodes, pos, self.x, self.y) if (found) { if (_.indexOf(selected, found.i) == -1) { selected.push(found.i) } else { _.remove(selected, function(d) {return d == found.i}) } redraw(); } }) .on("brush", function() { var extent = d3.event.target.extent(); if (Math.abs(extent[0][0] - extent[1][0]) > 0 & Math.abs(extent[0][1] - extent[1][1]) > 0) { selected = [] var x = self.x var y = self.y _.forEach(nodes, function(n) { var cond1 = (x(n.x) > x(extent[0][0]) & x(n.x) < x(extent[1][0])) var cond2 = (y(n.y) > y(extent[0][1]) & y(n.y) < y(extent[1][1])) if (cond1 & cond2) { selected.push(n.i) } }) redraw(); } }) .on("brushend", function() { getUserData() d3.event.target.clear(); d3.select(this).call(d3.event.target); }) function zoomed() { redraw(); } _.map(nodes, function(d) { d.s = d.s ? d.s : self.defaultSize d.cfill = d.c ? d.c : self.defaultFill d.cstroke = d.c ? d.c.darker(0.75) : self.defaultStroke return d }) // array indicating links var linkedByIndex = {}; var i for (i = 0; i < nodes.length; i++) { linkedByIndex[i + ',' + i] = 1; }; links.forEach(function (d) { linkedByIndex[d.source + ',' + d.target] = 1; }); // look up neighbor pairs function neighboring(a, b) { return linkedByIndex[a.index + ',' + b.index]; } var force = d3.layout.force() .size([width, height]) .charge(-120) .linkDistance(30) .nodes(nodes) .links(links) var brushrect = container .append('svg:svg') .attr('class', 'force-plot brush-container') .attr('width', width + margin.left + margin.right) .attr('height', height + margin.top + margin.bottom) .append("g") .attr('class', 'brush') .call(brush) d3.selectAll('.brush .background') .style('cursor', 'default') d3.selectAll('.brush') .style('pointer-events', 'none') d3.select(selector).attr("tabindex", -1) d3.select(selector).on("keydown", function() { shiftKey = d3.event.shiftKey; if (shiftKey) { d3.selectAll('.brush').style('pointer-events', 'all') d3.selectAll('.brush .background').style('cursor', 'crosshair') } }); d3.select(selector).on("keyup", function() { if (shiftKey) { d3.selectAll('.brush').style('pointer-events', 'none') d3.selectAll('.brush .background').style('cursor', 'default') } shiftKey = false }); function getUserData() { utils.sendCommMessage(self, 'selection', selected); utils.updateSettings(self, { selected: selected }, function(err) { if(err) { console.log('err saving user data'); } }); } function redraw() { canvas.clearRect(0, 0, width + margin.left + margin.right, height + margin.top + margin.bottom); draw() } function draw() { _.forEach(links, function(l) { var alpha if (selected.length > 0) { if (_.indexOf(selected, l.source.index) > -1 & _.indexOf(selected, l.target.index) > -1) { alpha = 0.9 } else { alpha = 0.05 } } if (highlighted.length > 0) { if (_.indexOf(highlighted, l.source.index) > -1 | _.indexOf(highlighted, l.target.index) > -1) { alpha = 0.9 } else { alpha = 0.05 } } if (selected.length == 0 & highlighted.length == 0) { alpha = linkStrokeOpacity } canvas.strokeStyle = utils.buildRGBA(linkStrokeColor, alpha); canvas.lineWidth = 1 * Math.sqrt(l.value); canvas.lineJoin = 'round'; canvas.beginPath(); canvas.moveTo(self.x(l.source.x), self.y(l.source.y)) canvas.lineTo(self.x(l.target.x), self.y(l.target.y)); canvas.stroke() }) _.forEach(nodes, function(n) { var alpha, stroke; if (selected.length > 0) { if (_.indexOf(selected, n.i) >= 0) { alpha = 0.9 } else { alpha = 0.1 } } else { alpha = 0.9 } if (highlighted.length > 0) { if (neighboring(nodes[highlighted[0]], n) | neighboring(n, nodes[highlighted[0]])) { alpha = 0.9 } else { alpha = 0.1 } } if (_.indexOf(highlighted, n.i) >= 0) { stroke = "black" } else { stroke = n.cstroke } canvas.beginPath(); canvas.arc(self.x(n.x), self.y(n.y), n.s, 0, 2 * Math.PI, false); canvas.fillStyle = utils.buildRGBA(n.cfill, alpha) canvas.lineWidth = strokeWidth canvas.strokeStyle = utils.buildRGBA(stroke, alpha) canvas.fill() canvas.stroke() }) } setTimeout(function() { force.start(); for (var i = nodes.length * nodes.length; i > 0; --i) force.tick(); force.stop(); draw(); loading.style('fill', 'white'); }, 10); };
const render = (json) => { var width = 960, height = 600; d3.select("#host").selectAll("*").remove(); var fill = d3.scale.category10(); var nodes = json.map(function (d, i) { var status; if (d.OfferStatus) { status = d.OfferStatus.SystemName } else { status = d.RoleStatus.SystemName; } return { index: i, status: status, prop: d }; }); var force = d3.layout.force() .nodes(nodes) .size([width, height]) .on("tick", tick) .start(); var svg = d3.select("#host").append("svg") .attr("width", width) .attr("height", height); var node = svg.selectAll(".node") .data(nodes) .enter().append("circle") .attr("class", "node") .attr("cx", function (d) { return d.x; }) .attr("cy", function (d) { return d.y; }) .attr("r", function (d, i) { return d.prop.AskingPrice.PriceValue / 100000 + 6; }) .style("fill", function (d, i) { switch (d.status) { case "Valued": return "#f2bd72"; case "InstructionToSell": return "#33cc99" case "OfferAccepted": return "#da2c01" default: return "#385595"; } }) .style("stroke", function (d, i) { return d3.rgb(fill(i & 3)).darker(2); }) .call(force.drag) .on("mousedown", function () { d3.event.stopPropagation(); }); svg.style("opacity", 1e-6) .transition() .duration(1000) .style("opacity", 1); function tick(e) { if (grouped) { var k = 6 * e.alpha; nodes.forEach(function (o, i) { o.y += o.status === "Valued" ? k : -k; o.x += o.status === "InstructionToSell" ? k : -k; o.x += o.status === "OfferAccepted" ? k : -k; }); } node.attr("cx", function (d) { return d.x; }) .attr("cy", function (d) { return d.y; }); } }
var d3 = require('d3'); var width = window.innerWidth, height = window.innerHeight, c = {y:height/2, x: width/2}; var rootEl, svg, node, nodes, inited; var nodesOut = []; var force = d3.layout.force() // .nodes(nodes) .links([]) .charge(-0.5) .gravity(0) .theta(0.99) .size([width, height]) .on("tick", tick); var forceOut = d3.layout.force() .nodes(nodesOut) .links([]) .charge(-1) .gravity(-0.1) .size([width,height]) .on('tick', tickOut); function visualisation(el, maxValue) { rootEl = el; init(maxValue); // ? return update;
render(users) { const width = 1000; const height = 700; const radius = 15; const vis = d3 .select(this.containerElement) .append('svg') .attr('width', width) .attr('height', height); const links = []; users.forEach((user, userIndex) => { if (user.manager) { const manager = users.find(x => x.id === user.manager.id); if (manager) { const managerIndex = users.findIndex(x => x.id === manager.id); links.push({ source: userIndex, target: managerIndex }); } else { // eslint-disable-next-line no-console console.log(`Missing manager for ${user.displayName} (${user.id}) in data.`); } } }); const link = vis.selectAll('line') .data(links) .enter().append('line'); const node = vis.selectAll('.node') .data(users) .enter().append('g') .attr('class', 'node') .on('mouseover', d => this.onNodeMouseOver(d)); node.append('circle') .attr('r', d => { d.radiusMultiplier = 1; if (!d.manager) { d.radiusMultiplier = 1.6; } else { const atLeastOneDirectReport = users.some(x => x.manager && x.manager.id === d.id); const manager = users.find(x => x.id === d.manager.id); if (!manager.manager && atLeastOneDirectReport) { d.radiusMultiplier = 1.5; } else if (atLeastOneDirectReport) { d.radiusMultiplier = 1.2; } } return radius * d.radiusMultiplier; }); this.updateGrouping(); Array.from(document.querySelectorAll('#js-group-by-container input')) .forEach(x => (x.onclick = () => this.updateGrouping())); node.append('text') .attr('text-anchor', 'middle') .attr('class', 'circle-text') .text(x => this.getNameAbbreviation(x.displayName)); const circleImageStrokeBorderPx = 4; node.append('image') .attr('class', 'circle-image') .attr('xlink:href', d => this.imageRetriever.getImageUrl(d.email)) .attr('x', d => radius * d.radiusMultiplier / 2 + circleImageStrokeBorderPx) .attr('y', d => radius * d.radiusMultiplier / 2 + circleImageStrokeBorderPx) .attr('width', d => radius * 2 * d.radiusMultiplier - circleImageStrokeBorderPx * 2) .attr('height', d => radius * 2 * d.radiusMultiplier - circleImageStrokeBorderPx * 2); const force = d3.layout.force() .nodes(users) .links(links) .size([width, height]) .linkDistance(30) .charge(-400) .gravity(0.3) .start(); node.call(force.drag); force.on('tick', () => { link.attr('x1', d => d.source.x) .attr('y1', d => d.source.y) .attr('x2', d => d.target.x) .attr('y2', d => d.target.y); d3.selectAll('circle') .attr('cx', d => Math.max(radius, Math.min(width - radius, d.x))) .attr('cy', d => Math.max(radius, Math.min(height - radius, d.y))); d3.selectAll('.circle-text') .attr('x', d => Math.max(radius, Math.min(width - radius, d.x))) .attr('y', d => Math.max(radius, Math.min(height - radius, d.y)) + 5); d3.selectAll('.circle-image') .attr('x', d => Math.max(radius, Math.min(width - radius, d.x)) - radius * d.radiusMultiplier + circleImageStrokeBorderPx) .attr('y', d => Math.max(radius, Math.min(height - radius, d.y)) - radius * d.radiusMultiplier + circleImageStrokeBorderPx); }); }
d3.json(slice.jsonEndpoint(), function (error, json) { const linkLength = json.form_data.link_length || 200; const charge = json.form_data.charge || -500; if (error !== null) { slice.error(error.responseText, error); return; } const links = json.data; const nodes = {}; // Compute the distinct nodes from the links. links.forEach(function (link) { link.source = nodes[link.source] || (nodes[link.source] = { name: link.source, }); link.target = nodes[link.target] || (nodes[link.target] = { name: link.target, }); link.value = Number(link.value); const targetName = link.target.name; const sourceName = link.source.name; if (nodes[targetName].total === undefined) { nodes[targetName].total = link.value; } if (nodes[sourceName].total === undefined) { nodes[sourceName].total = 0; } if (nodes[targetName].max === undefined) { nodes[targetName].max = 0; } if (link.value > nodes[targetName].max) { nodes[targetName].max = link.value; } if (nodes[targetName].min === undefined) { nodes[targetName].min = 0; } if (link.value > nodes[targetName].min) { nodes[targetName].min = link.value; } nodes[targetName].total += link.value; }); /* eslint-disable no-use-before-define */ // add the curvy lines function tick() { path.attr('d', function (d) { const dx = d.target.x - d.source.x; const dy = d.target.y - d.source.y; const dr = Math.sqrt(dx * dx + dy * dy); return ( 'M' + d.source.x + ',' + d.source.y + 'A' + dr + ',' + dr + ' 0 0,1 ' + d.target.x + ',' + d.target.y ); }); node.attr('transform', function (d) { return 'translate(' + d.x + ',' + d.y + ')'; }); } /* eslint-enable no-use-before-define */ const force = d3.layout.force() .nodes(d3.values(nodes)) .links(links) .size([width, height]) .linkDistance(linkLength) .charge(charge) .on('tick', tick) .start(); const svg = div.append('svg') .attr('width', width) .attr('height', height); // build the arrow. svg.append('svg:defs').selectAll('marker') .data(['end']) // Different link/path types can be defined here .enter() .append('svg:marker') // This section adds in the arrows .attr('id', String) .attr('viewBox', '0 -5 10 10') .attr('refX', 15) .attr('refY', -1.5) .attr('markerWidth', 6) .attr('markerHeight', 6) .attr('orient', 'auto') .append('svg:path') .attr('d', 'M0,-5L10,0L0,5'); const edgeScale = d3.scale.linear() .range([0.1, 0.5]); // add the links and the arrows const path = svg.append('svg:g').selectAll('path') .data(force.links()) .enter() .append('svg:path') .attr('class', 'link') .style('opacity', function (d) { return edgeScale(d.value / d.target.max); }) .attr('marker-end', 'url(#end)'); // define the nodes const node = svg.selectAll('.node') .data(force.nodes()) .enter() .append('g') .attr('class', 'node') .on('mouseenter', function () { d3.select(this) .select('circle') .transition() .style('stroke-width', 5); d3.select(this) .select('text') .transition() .style('font-size', 25); }) .on('mouseleave', function () { d3.select(this) .select('circle') .transition() .style('stroke-width', 1.5); d3.select(this) .select('text') .transition() .style('font-size', 12); }) .call(force.drag); // add the nodes const ext = d3.extent(d3.values(nodes), function (d) { return Math.sqrt(d.total); }); const circleScale = d3.scale.linear() .domain(ext) .range([3, 30]); node.append('circle') .attr('r', function (d) { return circleScale(Math.sqrt(d.total)); }); // add the text node.append('text') .attr('x', 6) .attr('dy', '.35em') .text(function (d) { return d.name; }); slice.done(json); });
function collision(){ var width = 960, height = 500; var nodes = d3.range(200).map(function() { return {radius: Math.random() * 12 + 4}; }), root = nodes[0], color = d3.scale.category10(); root.radius = 0; root.fixed = true; var force = d3.layout.force() .gravity(0.05) .charge(function(d, i) { return i ? 0 : -2000; }) .nodes(nodes) .size([width, height]); force.start(); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); svg.selectAll("circle") .data(nodes.slice(1)) .enter().append("circle") .attr("r", function(d) { return d.radius; }) .style("fill", function(d, i) { return color(i % 3); }); force.on("tick", function(e) { var q = d3.geom.quadtree(nodes), i = 0, n = nodes.length; while (++i < n) q.visit(collide(nodes[i])); svg.selectAll("circle") .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); }); svg.on("mousemove", function() { var p1 = d3.mouse(this); root.px = p1[0]; root.py = p1[1]; force.resume(); }); function collide(node) { var r = node.radius + 16, nx1 = node.x - r, nx2 = node.x + r, ny1 = node.y - r, ny2 = node.y + r; return function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== node)) { var x = node.x - quad.point.x, y = node.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = node.radius + quad.point.radius; if (l < r) { l = (l - r) / l * .5; node.x -= x *= l; node.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }; } }
module.exports = () => { const svg = d3.select('#d3container').append('svg') .attr('width', width) .attr('height', height); let chartData = { nodes: [{ name: 'bot', r: 5, color: '#999', alpha: 1, }], links: [], }; let force = d3.layout.force() .charge(-250) .linkDistance(40) .nodes(chartData.nodes) .links(chartData.links) .size([width, height]) .start(); const update = () => { const nodes = svg.selectAll('.bubble') .data(chartData.nodes); const links = svg.selectAll('.link') .data(chartData.links); const text = svg.selectAll('.label') .data(chartData.nodes); links.enter().append('line') .attr('class', 'link') .style('stroke-width', '10'); nodes.enter().append('circle') .attr('class', 'bubble') .style('position', 'absolute') .attr('r', d => Math.sqrt(d.r * 100)) .style('fill', d => { if (d.color) { return d.color; } else { return 'red'; } }) .style('fill-opacity', d => d.alpha) .call(force.drag); text.enter().append('text') .attr('class', 'label') .attr('x', d => d.x) .attr('y', d => d.y) .text(d => d.name) .call(force.drag); links.exit().remove(); nodes.exit().remove(); text.exit().remove(); links.attr('x1', d => d.source.x) .attr('y1', d => d.source.y) .attr('x2', d => d.target.x) .attr('y2', d => d.target.y) .style('stroke-width', '5') .style('stroke', '#999') .style('stroke-opacity', '.6'); nodes.attr('cx', d => d.x) .attr('cy', d => d.y) .attr('r', d => Math.sqrt(d.r * 100)); text.attr('x', d => d.x) .attr('y', d => d.y); }; d3.timer(update); return tax => { force.stop(); chartData = taxUpdate(tax, chartData); force.start(); }; };
ns.update = function (el, state, props, dispatcher) { if (_.isEmpty(state.reactions)) { return } // TODO 2nd update (componentDidUpdate) don't work var nodes = getReactionsWithRatio(state.reactions) var node = d3.select(el).select('svg').selectAll('.node') .data(nodes) .enter().append('g') .attr('class', 'node') var sizeByCount = (d) => ( Math.sqrt(Math.max(d.ratio / d.ratio_total, 0.05)) * 150 ) function tick (e) { // Push different nodes in different directions for clustering. images.attr('x', function (d) { return d.x }) .attr('y', function (d) { return d.y }) labels.attr('x', function (d) { return d.x }) .attr('y', function (d) { return d.y }) } // TODO tune the charge // http://stackoverflow.com/questions/9901565/charge-based-on-size-d3-force-layout let k = this.k(nodes.length, props.width, props.height) var force = d3.layout.force() .nodes(nodes) .gravity(0.4) .charge(-1700) .size([props.width, props.height]) .on('tick', tick) .start() // labels before images for svg z-index let labels = node .append('text') .attr('dx', 1) .attr('dy', 1) .text(function (d) { return (d.ratio * 100).toFixed(0) + '%' }) var images = node.append('image') .attr('xlink:href', (d) => getReactionImageUrl(d.type)) .attr('x', function (d) { return d.x }) .attr('y', function (d) { return d.y }) .attr('width', sizeByCount) .attr('height', sizeByCount) // .style("fill", function(d, i) { return fill(i & 3); }) // .style("stroke", function(d, i) { return d3.rgb(fill(i & 3)).darker(2); }) .call(force.drag) .on('mousedown', function () { d3.event.stopPropagation() }) var like = d3.select(el).select('svg') .append('image') .attr('xlink:href', (d) => getReactionImageUrl('LIKE')) .attr('x', 360) .attr('y', 370) .attr('width', 30) .attr('height', 30) .call(force.drag) .on('mousedown', function () { d3.event.stopPropagation() }) var likeRatio = (state.reactions['LIKE'] / _.sum(_.values(state.reactions)) * 100).toFixed(0) d3.select(el).select('svg') .append('text') .attr('x', 400) .attr('y', 390) .text(likeRatio + '%') // state.reactions['LIKE'] + ' - ' + // namespace otherwise as will replace as the only listener // http://stackoverflow.com/questions/14749182/how-to-register-multiple-external-listeners-to-the-same-selection-in-d3 if (!isMobile()) { d3.select(window).on('resize.' + props.id, ns._resize.bind(null, el, nodes, props, force)) } d3.select(el) .on('mousedown', this._mousedown(nodes, force)) // var scales = this._scales(el, state.domain) // var prevScales = this._scales(el, state.prevDomain) // this._drawPoints(el, scales, state.data, prevScales, dispatcher) // this._drawTooltips(el, scales, state.tooltips, prevScales) }
var SvgGraph = function (nodes, links, container) { /** * The external api (hoisted) * * @type {Object} */ var _this = null; /** * The svg container * * @type {[Element]} */ container = d3.select(container) .append("svg:svg") .attr({ "width": "100%", "height": "100%" }); /** * The svg group ( zoomable capable ) * * @type {[Element]} */ var svg = container .append("svg:g") .attr({ "width": "100%", "height": "100%" }); // disable doube click zoom svg.on("dblclick.zoom", null); /** * The viwport * * @type {Viewport} */ var viewport = Viewport(container); /** * Currently dragging over * */ var draggingOverTimeout = null, draggingOver = null; /** * The D3 layout * * @type {D3.Layout} */ var layout = d3.layout.force() .size([500, 500]) .nodes(nodes) .links(links) .linkDistance(50); /** * The node/link elements * */ var node = svg.selectAll(".node"), link = svg.selectAll(".link"), text = svg.selectAll(".text"); /** * Updates the rendered node/links elements * */ var render = function(){ link = link.data(links, function(d) { return d.source.id + "-" + d.target.id; }); link.enter() .insert("line", ".node") .attr("class", "link") .attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }) .attr("data-from", function(d) { return d.source.id; }) .attr("data-to", function(d) { return d.target.id; }); /// remove not existing items link.exit().remove(); node = node.data(nodes, function(d) { return d.id;}); node.enter() .append("circle") .attr("class", function(d) { return "node " + d.id; }) .attr("r", 20) .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }) // click event .on("click", function (d) { d3.event.preventDefault(); d3.event.stopPropagation(); _this.emitEvent("selected", [this, d]); }) // hover events .on("mouseover", function(d){ /// manage the current dragging item /* jshint -W041 */ if(draggingOverTimeout != null) clearTimeout(draggingOverTimeout); draggingOver = d; _this.emitEvent("mouseover", [this, d]); }) .on("mouseout", function (d) { // reset the debounced timeout to set dragging over to null draggingOverTimeout = setTimeout(function(){ draggingOver = null; }, 300); _this.emitEvent("mouseout", [this, d]); }); /// remove not existing nodes node.exit().remove(); text = text.data(nodes, function(d) { return d.id;}); text.enter() .append("text") .attr("class", "text") .attr("x", function(d) { return d.x; }) .attr("y", function(d) { return d.y; }) .attr("text-anchor", "left"); text.exit().remove(); // execute animation to re-position elements layout.start(); layout.tick(); layout.stop(); }; /** * On simulation tick update the nodes positions * */ layout.on("tick", function(e) { link .transition() .duration(50) .ease(d3.ease("linear")) .attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); /// Update the node .transition() .duration(50) .ease(d3.ease("linear")) .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }) .attr("class", function(d) { return $(this).attr("class") + " " + d.class; }); text .transition() .duration(50) .ease(d3.ease("linear")) .attr("x", function(d) { var x = d.x - 40; if(!d.children.length){ x = d.x + 35; } return x; }) .attr("y", function(d) { var y = d.y - 35; if(!d.children.length){ y = d.y + 5; } return y; }) .text(function(d){ return d.text || ""; }); }); /** * External API * * @type {Object} * */ _this = { /** * Renders the graph * * @type {Function} */ render: render, /** * Dragging over * * @type {Object} * */ draggingOver: function(){ return draggingOver; }, /** * Reset * * @return {[type]} [description] */ reset: function(){ /*link.remove(); node.remove(); text.remove();*/ _.remove(nodes); _.remove(links); } }; // initialize this instance as an EventEmitter OOP.super(_this, EventEmitter); OOP.inherit(_this, EventEmitter.prototype); // extend from behaviour _.assign(_this, viewport); return _this; };
{ url: require("data/NetworkGraph/nodes_company.csv"), rowParser: ({ id, name, logo, logoAspectRatio, relations }) => ({ id, name, logo, logoAspectRatio: +logoAspectRatio, relations: +relations, type: "company" }) } ], links: [ { url: require("data/NetworkGraph/links_person_company.csv"), rowParser: ({ person, company, title, type }) => ({ source: person, target: company, title, type }) } ] }; const force = d3.layout.force(); const rawData = { nodes: [], links: [] }; function createPromise(src) { if (typeof src.nodes === "undefined" || !Array.isArray(src.nodes) || typeof src.links === "undefined" || !Array.isArray(src.links)) { throw new TypeError("loadCSV() called with wrong argument type"); } const nodeRequests = src.nodes.map(obj => new Promise((resolve, reject) => { d3.csv(obj.url).row(obj.rowParser).get((error, rows) => { if (error) reject(error); else resolve({ type: "node", data: rows }); }); })); const linkRequests = src.links.map(obj => new Promise((resolve, reject) => {
setupD3(function(d3, parent) { var svg = d3.select(parent).append("svg:svg"), width = 1200, height = 800; var force = d3.layout.force() .charge(-40) .linkDistance(20) .gravity(0.3) .size([width/2, height/2]); // just to be sure it fits svg.attr("width", width) .attr("height", height); var nodes = [], links = [], nodes_set = {}, nodes_idx = {}; // deduplicate nodes _.each(data, function(movies, user) { nodes_set[user] = -1; _.each(movies, function(movie) { nodes_set[movie] = -1; }); }); _.each(nodes_set, function(_, id) { var l = nodes.push({ id: id, name: id, is_movie: id[0] == "m", }); // store the index nodes_set[id] = l-1; }); _.each(data, function(movies, user) { _.each(movies, function(movie) { var idx1 = nodes_set[user], idx2 = nodes_set[movie]; links.push({source: idx1, target: idx2, weight: 1}); }); }); force.nodes(nodes) .links(links); force.start() var maxTicks = 600; for (var i=0; i<maxTicks; i++) { // console.log("tick", i, "out of", maxTicks); force.tick(); } force.stop() /* svg.selectAll(".link") .data(links) .enter().append("line") .attr("class", "link") .style("stroke", "#AAA") .attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); // */ svg.selectAll(".node") .data(nodes) .enter().append("circle") .attr("class", "node") .attr("r", function(d) { return d.is_movie ? 3 : 3 }) .style("fill", function(d) { return d.is_movie ? "blue" : "green"; // experimental }) .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); res.writeHead(200, {"Content-Type": "image/svg+xml"}); res.end(parent.innerHTML); });
render: function() { var self = this; this.init(); function circleRadius(d) { return self.radiusScale(self.getSize(d) || 1); } var force = this.force = d3.layout.force() .nodes(this.nodes) .links(this.links) .size([this.opts.width, this.opts.height]) .linkDistance(defaults.linkDistance) .charge(defaults.graphCharge) .gravity(defaults.gravity) .on("tick", tick) .start(); this.d3Path = this.parent.append("svg:g").selectAll("path") .data(force.links()) .enter().append("svg:path") .attr("stroke", bind(this, this.pathStroke)) .attr("stroke-width", PATH_STROKE_WIDTH) .attr("fill", "none"); var node = this.d3Nodes = this.parent.selectAll(".node") .data(force.nodes()) .enter().append("g") .attr("class", "node") .on("mouseover", bind(this, this.onMouseOver)) .on("mouseout", bind(this, this.onMouseOut)) .on("click", bind(this, this.onCircleClick)); this.d3Circles = node.append("circle") .style("fill", bind(this, this.getClusterColor)) .attr("r", circleRadius) this.d3TitleNodes = node.append("text") .attr("text-anchor", "middle") .attr("dy", ".35em") .style("display", "none") .text(function(d) { return self.getText(d); }); function tick(e) { //var progress = Math.round((1 - (e.alpha * 10 - 0.1)) * 100); if (force.alpha() < defaults.forceAlphaLimit) { self.handleCollisions(); // to prevent the chart from moving after force.stop(); self.updateNodesPosition(); self.updateLinksPosition(); // center the graph self._center(); // showing canvas after finished rendering self.show(); self.refreshZoom(); self.emit("rendered"); } } if (this.hasUnappliedFilters() || this.hasUnappliedFocus()) { this.update(); } return this; },
/** * Draw network view **/ function draw(selectedEdgeId, sourceIpNodeId, targetIpNodeId, data) { //Get the nodes from the data var data = this.state.data; var nodes = data.map(function (d) { return { ip: d.srcIP, isInternal: +d.srcIpInternal }; }) var nodes_2 = data.map(function (d) { return { ip: d.dstIP, isInternal: +d.destIpInternal }; }); nodes = getUniqueNodes(nodes.concat(nodes_2)); // Get the edges from the data var edges = data.map(function (d) { var fSrc = -1, fDst = -1, i, n; // Find indexes for srcIP and dstIP in nodes array for (i=0, n=nodes.length ; i<n ; i++) { // Is this srcIP? if (nodes[i].ip == d.srcIP) fSrc = i; // Is this dstIP? if (nodes[i].ip == d.dstIP) fDst = i; // Stop looking as soon as we find both indexes if (fSrc>=0 && fDst>=0) break; } return { source: fSrc, target: fDst, weight: -Math.log(d.lda_score), id: "k" + d.srcIP.replace(/\./g, "_") + "-" + d.dstIP.replace(/\./g, "_") }; }); // Update the degree in the edges if the edge is a suspect edges.forEach(function (d) { nodes[d.source].degree = nodes[d.source].degree + 1 || 1; nodes[d.target].degree = nodes[d.target].degree + 1 || 1; }); // define an opacity function var opacity = d3.scale.threshold() .domain([13]) .range([0.1, 1]); // Color for edges var color = d3.scale.linear() .domain([16, 13, 12, 2]) .range([d3.hsl(214, 0.04, 0.34), d3.hsl(216, 0.02, 0.59), d3.hsl(216, 0.69, 0.84), d3.hsl(201, 0.1, 0.72)]) .interpolate(d3Interpolate.interpolateCubehelix); // Graph dimensions var w = $(this.getDOMNode()).width(), h = $(this.getDOMNode()).height(), r = Math.round(w * 0.005), // 0.005 magic number for nodes styling purposes when expanding graph, radious is 0.5% of the #grap div rightMargin = w - r, bottomMargin = h - r, size = [w, h]; //Main SVG container d3.select(this.getDOMNode()).select('svg').remove(); var svg = d3.select(this.getDOMNode()) .append('svg') .attr('width', w) .attr('height', h) .append('g'); // Graph force var force = d3.layout.force() .linkDistance(70) .charge(-120) .chargeDistance(-Math.round(h * 0.55)) // 0.55 is a magic number for graph styling purposes charge is 55% of the grap height .size(size) .nodes(nodes) .links(edges); // Group and append the edges to the main SVG svg.append('g') .selectAll('.edge') .data(edges) .enter() .append('line') .classed('edge', true) .attr("id", function (d) { return d.id; }) .style('stroke', function (d) { return color(d.weight); }) .style('stroke-opacity', function (d) { return opacity(d.weight); }); var edge = svg.selectAll('.edge'); // Tooltip generator var tooltip = d3.select(this.getDOMNode()) .append("div") .classed('node-label', true); // GROUP and append the nodes to the main SVG var node = svg.append('g') .selectAll('.node') .data(nodes.filter(function (d) { return d.degree > 0; })) .data(nodes) .enter() .append('path') .classed('node', true) .attr("id", function (d) { return "n" + d.ip.replace(/\./g, "_"); }) .attr("d", d3.svg.symbol() .size(function (d) { return (d.degree + r) * 20; }) .type(function (d) { if (d.isInternal == 1) return "diamond"; else return "circle"; })) .attr('fill', function (d) { if (d.isInternal == 1) return "#0071C5"; else return "#fdb813"; }) .call(force.drag) .on('mouseover', function (d) { tooltip.html(d.ip + '<br/> <small class="text-muted">Right click to apply IP filter</small>') .style('visibility', 'visible'); }) .on('mousemove', function (d) { tooltip.style('top', d3.event.layerY + 'px'); if ((w*.5) > d3.event.layerX) { // Show tooltip to the right tooltip.style('left', (d3.event.layerX + 20) + 'px'); } else { // Show tooltip to the left tooltip.style('left', (d3.event.layerX - 200) + 'px'); } }) .on("click", nodeClick) .on("contextmenu", nodeContextualClick) .on('mouseout', function () { tooltip.style('visibility', 'hidden'); }); // set the tick event listener for the force force.on('tick', function () { edge.attr("x1", function (d) { return d.source.x; }) .attr("y1", function (d) { return d.source.y; }) .attr("x2", function (d) { return d.target.x; }) .attr("y2", function (d) { return d.target.y; }); node.attr('transform', function (d) { d.x = Math.max(r, Math.min(rightMargin, d.x)); d.y = Math.max(r, Math.min(bottomMargin, d.y)); return 'translate(' + d.x + ',' + d.y + ')'; }); }); force.start(); // if the function params are not null then that means we have a selected edge and nodes and we need to add the blink animation to them if (this.state.selectedEdgeId && this.state.selectedSrcNodeId && this.state.selectedDstNodeId) { var selectedEdge = d3.select("#" + this.state.selectedEdgeId) .style("stroke", "#FDB813") .style("stroke-opacity", "1") .classed("blink_me", true) .classed("active", true); var parent = $("#" + selectedEdge.attr("id")).parent() selectedEdge.remove(); parent.append(selectedEdge[0]); d3.select("#" + this.state.selectedSrcNodeId) .classed("blink_me", true); d3.select("#" + this.state.selectedDstNodeId) .classed("blink_me", true); } }
module.exports = () => { let width = window.innerWidth; const height = window.innerHeight - 100; const svg = d3.select('#d3container').append('svg') .attr('width', width) .attr('height', height); let chartData = []; let force = d3.layout.force() .gravity(0.1) .distance(100) .charge(-150) .nodes(chartData) .size([width * 0.8, height * 0.8]); function collide(node) { var r = node.r + 8, nx1 = (node.x + node.r) - r, nx2 = (node.x + node.r) + r, ny1 = (node.y + node.r) - r, ny2 = (node.y + node.r) + r; return function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== node)) { var x = (node.x + node.r) - (quad.point.x + quad.point.r), y = (node.y + node.r) - (quad.point.y + quad.point.r), l = Math.sqrt(x * x + y * y), r = node.r + quad.point.r; if (l < r) { l = (l - r) / l * 0.5; node.x -= x *= l; node.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }; } force.start(); const update = () => { const q = d3.geom.quadtree(chartData); let i = 0; const n = chartData.length; while (++i < n) { q.visit(collide(chartData[i])); } const charts = svg.selectAll('.bubble') .data(chartData) .call(force.drag); const text = svg.selectAll('.label') .data(chartData); charts.transition().duration(30) .attr('x', d => d.x) .attr('y', d => d.y); charts.enter().append('image') .attr('class', 'bubble') .style('position', 'absolute') .attr('x', d => d.x) .attr('y', d => d.y) .attr('width', d => d.r * 2) .attr('height', d => d.r * 2) .attr('xlink:href', '../assets/bubble.png'); text.enter().append('text') .attr('class', 'label') .attr('x', d => d.x + d.r) .attr('y', d => d.y + d.r) .style('font-family', 'Arial') .style('text-anchor', 'middle') .style('fill', 'white') .style('font-weight', 'bold') .style('fill-opacity', 1) .style('stroke', '#000000') .style('stroke-width', '1px') .style('stroke-linecap', 'butt') .style('stroke-linejoin', 'miter') .style('stroke-opacity', 1) .text(d => d.text) .call(force.drag); charts.exit().remove(); text.exit().remove(); text.attr('x', d => d.x + d.r) .attr('y', d => d.y + d.r); }; d3.timer(update); return { update: (keywords) => { force.stop(); for (let i in keywords) { let found = false; for (let n in chartData) { if (keywords[i].keyword_text === chartData[n].text) { chartData[n].val = keywords[i]['SUM(relevance)']; chartData[n].r = Math.ceil(Math.sqrt(keywords[i]['SUM(relevance)'] * 1000)); found = true; break; } } if (!found) { const newVal = { text: keywords[i].keyword_text, val: keywords[i]['SUM(relevance)'], x: Math.ceil(Math.random() * width), y: Math.ceil(Math.random() * height), r: Math.ceil(Math.sqrt(keywords[i]['SUM(relevance)'] * 1000)), }; chartData.push(newVal); } } force.start(); }, resize: () => { force.stop(); width = window.innerWidth; force.size([width * 0.8, height * 0.8]).start(); }, }; };
componentDidMount(){ const container = ReactDOM.findDOMNode(this); const canvas = container.querySelector('.filter-viz__canvas'); const svg = d3.select(canvas); const width = window.innerWidth / 2; const height = window.innerHeight; this.radius = d3.scale.linear().domain([1,10]).range([30, 80]); this.radius2 = d3.scale.sqrt().domain([5,15]).range([40, 80]); this.flexScale = d3.scale.linear().domain([1,10]).range([0, window.innerHeight]) this.personScale = d3.scale.linear().domain([5,15]).range([50, window.innerHeight]) this.mode = 'none'; //prepare data this.nodes = data.map((row) => { row.radius = this.radius(row.size); row.x = 0; row.y = 0; return row; }); this.data = [ ...this.nodes ]; //add mouse this.mouse = { name: 'mouse', radius: 10 }; this.data.push(this.mouse); this.force = d3.layout.force() .nodes(this.data) // .links(this.links) // .friction(0.9) .size([width, height]) .on('tick', this.tick.bind(this)) .start(); this.circles = svg.selectAll('circle') .data(this.nodes) .enter() .append('circle') .attr('r', function(d){ return d.radius; }) .style('fill', function(d){ return '#fff'; }) .call(this.force.drag) // .on('mouseover', main.tooltip) // .on('mouseout', main.hideTooltip); .on('click', (d) => { this.props.history.push('/' + d.slug); this.force.resume(); }) this.texts = svg.selectAll('text') .data(this.nodes) .enter() .append('text') .text((d) => { return d.name }); window.addEventListener('mousemove', (evt) => { this.mouse.x = evt.clientX; this.mouse.y = evt.clientY; // this.force.alpha(0.1); }); window.addEventListener('resize', (evt) => { this.force.size([window.innerWidth/2, window.innerHeight]).resume(); }); window.__force = this.force; }