Add images or icons in neo4j with d3js - javascript

I would like to add icons or images to nodes. That is, the nodes people with an icon or image, the nodes, cell phones with an icon image and so on.
So far my code is as follows. It should be noted that the data is obtained directly from my base in NE4OJ
Cada color representa una propiedad, Quisera añadir un icono o imagen a cada nodo
var graphWidth = 1024;
var graphHeight = 450;
var neo4jAPIURL = 'http://localhost:7474';
var neo4jLogin = 'USER';
var neo4jPassword = 'PASSWORD';
var circleSize = 30;
var textPosOffsetY = 5;
var arrowWidth = 5;
var arrowHeight = 5;
var collideForceSize = circleSize * 1.5;
var linkForceSize = 150;
var iconPosOffset = {'lock': [-40, -50], 'cross': [18, -50]};
var linkTypeMapping = {'OUT_ADD': '+', 'OUT_SUB': '-', 'IN_AND': 'AND', 'IN_OR': 'OR'};
var lockIconSVG = 'm18,8l-1,0l0,-2c0,-2.76 -3.28865,-5.03754 -5,-5c-1.71135,0.03754 -5.12064,0.07507 -5,4l1.9,0c0,-1.71 1.39,-2.1 3.1,-2.1c1.71,0 3.1,1.39 3.1,3.1l0,2l-9.1,0c-1.1,0 -2,0.9 -2,2l0,10c0,1.1 0.9,2 2,2l12,0c1.1,0 2,-0.9 2,-2l0,-10c0,-1.1 -0.9,-2 -2,-2zm0,12l-12,0l0,-10l12,0l0,10z';
var crossIconSVG = 'M14.59 8L12 10.59 9.41 8 8 9.41 10.59 12 8 14.59 9.41 16 12 13.41 14.59 16 16 14.59 13.41 12 16 9.41 14.59 8zM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z';
var boxSVG = 'M0 0 L23 0 L23 23 L0 23 Z';
//######################### variable #########################
var nodeItemMap = {};
var linkItemMap = {};
var d3Simulation = null;
var circles;
var circleText;
var lines;
var lineText;
var iconLock;
var iconCross;
var itemColorMap = {};
var colorScale = d3.scaleOrdinal(d3.schemeSet2);
var drag_handler = d3.drag()
.on('start', drag_start)
.on('drag', drag_move)
.on('end', drag_end);
var zoom_handler = d3.zoom()
.filter(function() {
//Only enable wheel zoom and mousedown to pan
return (d3.event.type == 'wheel' | d3.event.type == 'mousedown');
})
.on('zoom', zoom_actions);
function unfreezeItms() {
var nodeItmArray = d3Simulation.nodes();
if (nodeItmArray != null) {
nodeItmArray.forEach(function(nodeItm) {
if (nodeItm.fx != null) {
nodeItm.fx = null;
nodeItm.fy = null;
}
});
}
}
function drag_start(d) {
//if (!d3.event.active && d3Simulation != null)
d3Simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function drag_move(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function drag_end(d) {
if (!d3.event.active && d3Simulation != null)
d3Simulation.alphaTarget(0);
//d.fx = null;
//d.fy = null;
}
function zoom_actions(){
d3.select('#resultSvg').select('g').attr('transform', d3.event.transform);
}
function initGraph() {
var svg = d3.select('#resultSvg');
var zoomGLayer = svg.append('g');
var centerX = graphWidth / 2;
var centerY = graphHeight / 2;
svg.attr('width', graphWidth)
.attr('height', graphHeight);
/*
var defs = svg.append('defs');
Not use marker as IE does not support it and so embed the arrow in the path directly
// define arrow markers for graph links
defs.append('marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 10)
.attr('markerWidth', 5)
.attr('markerHeight', 5)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,-5L10,0L0,5');
*/
zoomGLayer.append('g').attr('id', 'circle-group').attr('transform', 'translate(' + centerX + ',' + centerY + ')');
zoomGLayer.append('g').attr('id', 'text-group').attr('transform', 'translate(' + centerX + ',' + centerY + ')');
zoomGLayer.append('g').attr('id', 'path-group').attr('transform', 'translate(' + centerX + ',' + centerY + ')');
zoomGLayer.append('g').attr('id', 'path-label-group').attr('transform', 'translate(' + centerX + ',' + centerY + ')');
zoomGLayer.append('g').attr('id', 'control-icon-group').attr('transform', 'translate(' + centerX + ',' + centerY + ')');
zoom_handler(svg);
}
function stopSimulation() {
if (d3Simulation != null) {
d3Simulation.stop()
.on('tick', null);
d3Simulation = null;
}
}
function tick() {
lines.attr('d', drawLine);
lineText.attr('transform', transformPathLabel);
circles.attr('transform', transform);
circleText.attr('transform', transform);
iconLock.attr('transform', function(d) {return transformIcon(d, 'lock');});
iconCross.attr('transform', function(d) {return transformIcon(d, 'cross');});
}
function transformIcon(d, type) {
var sourceX = d.x + iconPosOffset[type][0];
var sourceY = d.y + iconPosOffset[type][1];
return 'translate(' + sourceX + ',' + sourceY + ')';
}
function transformPathLabel(d) {
var sourceX = d.source.x + ((d.target.x - d.source.x) / 2);
var sourceY = d.source.y + ((d.target.y - d.source.y) / 2);
return 'translate(' + sourceX + ',' + sourceY + ')';
}
function transform(d) {
return 'translate(' + d.x + ',' + d.y + ')';
}
function drawLine(d) {
var deltaX, deltaY, dist, cosTheta, sinTheta, sourceX, sourceY, targetX, targetY;
deltaX = d.target.x - d.source.x;
deltaY = d.target.y - d.source.y;
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
cosTheta = deltaX / dist;
sinTheta = deltaY / dist;
sourceX = d.source.x + (circleSize * cosTheta);
sourceY = d.source.y + (circleSize * sinTheta);
targetX = d.target.x - (circleSize * cosTheta);
targetY = d.target.y - (circleSize * sinTheta);
//Not use marker as IE does not support it and so embed the arrow in the path directly
var arrowLeftX, arrowLeftY, arrowRightX, arrowRightY;
arrowLeftX = targetX - (arrowHeight * sinTheta) - (arrowWidth * cosTheta);
arrowLeftY = targetY + (arrowHeight * cosTheta) - (arrowWidth * sinTheta);
arrowRightX = targetX + (arrowHeight * sinTheta) - (arrowWidth * cosTheta);
arrowRightY = targetY - (arrowHeight * cosTheta) - (arrowWidth * sinTheta);
return 'M' + sourceX + ' ' + sourceY + ' L' + targetX + ' ' + targetY
+ ' M' + targetX + ' ' + targetY + ' L' + arrowLeftX + ' ' + arrowLeftY
+ ' L' + arrowRightX + ' ' + arrowRightY + ' Z';
}
function clearProperties() {
$('#propertiesBox').empty();
}
function showProperties(d) {
clearProperties();
var propertiesText = 'id: ' + d.id;
//For nodes
//if (d.labels != null)
// propertiesText += ', labels: ' + d.labels.join(', ');
//For links
//if (d.type != null)
// propertiesText += ', type: ' + d.type;
$.map(d.properties, function(value, key) {
propertiesText += ', ' + key + ': ' + value;
});
$('#propertiesBox').append($('<p></p>').text(propertiesText));
}
function replaceLinkTypeName(d) {
var linkTypeName = linkTypeMapping[d.type];
if (linkTypeName == null)
return d.type;
return linkTypeName;
}
function generateCircleClasses(d) {
if (d.properties != null && d.properties.cyclic == '1')
return 'Cyclic ' + d.labels.join(' ');
return d.labels.join(' ');
}
function removeNode(d) {
delete nodeItemMap[d.id];
$.map(linkItemMap, function(value, key) {
if (value.startNode == d.id || value.endNode == d.id)
delete linkItemMap[key];
});
}
function updateGraph() {
var d3LinkForce = d3.forceLink()
.distance(linkForceSize)
.links(mapToArray(linkItemMap))
.id(function(d) {return d.id;});
d3Simulation = d3.forceSimulation()
//.force('chargeForce', d3.forceManyBody())//.strength(-300)
.force('collideForce', d3.forceCollide(collideForceSize))
.nodes(mapToArray(nodeItemMap))
.force('linkForce', d3LinkForce);
circles = d3.select('#circle-group').selectAll('circle')
.data(d3Simulation.nodes(), function(d) {return d.id;});
circleText = d3.select('#text-group').selectAll('text')
.data(d3Simulation.nodes(), function(d) {return d.id;});
lines = d3.select('#path-group').selectAll('path')
.data(d3LinkForce.links(), function(d) {return d.id;});
lineText = d3.select('#path-label-group').selectAll('text')
.data(d3LinkForce.links(), function(d) {return d.id;});
iconLock = d3.select('#control-icon-group').selectAll('g.lockIcon')
.data([], function(d) {return d.id;});
iconCross = d3.select('#control-icon-group').selectAll('g.crossIcon')
.data([], function(d) {return d.id;});
iconLock.exit().remove();
iconCross.exit().remove();
circles.exit().remove();
circles = circles.enter().append('circle')
.attr('r', circleSize)
.attr('fill', getItemColor)
.attr('title', function(d) {return d.labels.join('-');})
.attr('class', function(d) {return generateCircleClasses(d);})
.call(drag_handler)
.on('mouseover', function(d) {
d3.select(this)
showProperties(d);
})
.on('dblclick', function(d) {
d.fx = d.x;
d.fy = d.y;
submitQuery(d.id);
})
.on('click', function(d) {
iconLock = d3.select('#control-icon-group').selectAll('g.lockIcon')
.data([d], function(d) {return d.id;});
iconCross = d3.select('#control-icon-group').selectAll('g.crossIcon')
.data([d], function(d) {return d.id;});
iconLock.exit().remove();
iconLock.remove();
iconCross.exit().remove();
iconCross.remove();
var iconLockEnter = iconLock.enter().append('g')
.attr('class', 'lockIcon')
.attr('transform', function(d) {
return transformIcon(d, 'lock');
})
.on('click', function(d) {
d.fx = null;
d.fy = null;
iconLock.remove();
iconCross.remove();
});
iconLockEnter.append('path').attr('class', 'overlay').attr('d', boxSVG);
iconLockEnter.append('path').attr('d', lockIconSVG);
var iconCrossEnter = iconCross.enter().append('g')
.attr('class', 'crossIcon')
.attr('transform', function(d) {
return transformIcon(d, 'cross');
})
.on('click', function(d) {
removeNode(d);
updateGraph();
});
iconCrossEnter.append('path').attr('class', 'overlay').attr('d', boxSVG);
iconCrossEnter.append('path').attr('d', crossIconSVG);
iconLock = iconLockEnter
.merge(iconLock);
iconCross = iconCrossEnter
.merge(iconCross);
})
.merge(circles);
circleText.exit().remove();
circleText = circleText.enter().append('text')
.attr('y', textPosOffsetY)
.attr('text-anchor', 'middle')
.text(function(d) {
return [
d.properties.name
];})
.merge(circleText);
lines.exit().remove();
lines = lines.enter().append('path')
//.attr('marker-end', 'url(#end-arrow)')
.attr('title', function(d) {return d.type;})
.attr('class', function(d) {return d.type;})
.on('mouseover', function(d) {
showProperties(d);
})
.merge(lines);
lineText.exit().remove();
lineText = lineText.enter().append('text')
.attr('y', textPosOffsetY)
.attr('text-anchor', 'middle')
.text(function(d) {return replaceLinkTypeName(d);})
.merge(lineText);
d3Simulation
.on('tick', tick);
}
function submitQuery(nodeID) {
removeAlert();
var queryStr = null;
if (nodeID == null || !nodeID) {
queryStr = $.trim($('#queryText').val());
if (queryStr == '') {
promptAlert($('#graphContainer'), 'Error: el texto de consulta no puede estar vacío !', true);
return;
}
if ($('#chkboxCypherQry:checked').val() != 1)
queryStr = 'match (n) where n.Cedula =~ \'(?i).*' + queryStr + '.*\' return n';
} else
queryStr = 'match (n)-[j]-(k) where id(n) = ' + nodeID + ' return n,j,k';
stopSimulation();
if (nodeID == null || !nodeID) {
nodeItemMap = {};
linkItemMap = {};
}
var jqxhr = $.post(neo4jAPIURL, '{"statements":[{"statement":"' + queryStr + '", "resultDataContents":["graph"]}]}',
function(data) {
//console.log(JSON.stringify(data));
if (data.errors != null && data.errors.length > 0) {
promptAlert($('#graphContainer'), 'Error: ' + data.errors[0].message + '(' + data.errors[0].code + ')', true);
return;
}
if (data.results != null && data.results.length > 0 && data.results[0].data != null && data.results[0].data.length > 0) {
var neo4jDataItmArray = data.results[0].data;
neo4jDataItmArray.forEach(function(dataItem) {
//Node
if (dataItem.graph.nodes != null && dataItem.graph.nodes.length > 0) {
var neo4jNodeItmArray = dataItem.graph.nodes;
neo4jNodeItmArray.forEach(function(nodeItm) {
if (!(nodeItm.id in nodeItemMap))
nodeItemMap[nodeItm.id] = nodeItm;
});
}
//Link
if (dataItem.graph.relationships != null && dataItem.graph.relationships.length > 0) {
var neo4jLinkItmArray = dataItem.graph.relationships;
neo4jLinkItmArray.forEach(function(linkItm) {
if (!(linkItm.id in linkItemMap)) {
linkItm.source = linkItm.startNode;
linkItm.target = linkItm.endNode;
linkItemMap[linkItm.id] = linkItm;
}
});
}
});
console.log('nodeItemMap.size:' + Object.keys(nodeItemMap).length);
console.log('linkItemMap.size:' + Object.keys(linkItemMap).length);
updateGraph();
return;
}
//also update graph when empty
updateGraph();
promptAlert($('#graphContainer'), 'No encontrado!', false);
}, 'json');
jqxhr.fail(function(data) {
promptAlert($('#graphContainer'), 'Error: se envió el texto de la consulta pero se recibió un mensaje de error (' + data + ')', true);
});
}
//Page Init
$(function() {
setupNeo4jLoginForAjax(neo4jLogin, neo4jPassword);
initGraph();
$('#queryText').keyup(function(e) {
if(e.which == 13) {
submitQuery();
}
});
$('#btnSend').click(function() {submitQuery()});
$('#chkboxCypherQry').change(function() {
if (this.checked)
$('#queryText').prop('placeholder', 'Cypher');
else
$('#queryText').prop('placeholder', 'Node Name');
});
});
</script>
</body>
</html>

Related

Transform label in reverse order d3 radial chart

I have a d3 radial chart created using some community sample and stack-overflow posts.
Here the two bottom labels and numbers are in mirrored form (A13 and A14). Looking for some snippets to transform only this two in counter-clockwise with numbers top (next to the chart) then label so that it will be in better readable form.
JSFiddle
var data = [
{"name":"A11","value":217,"color":"#fad64b"},
{"name":"A12","value":86,"color":"#f15d5d"},
{"name":"A13","value":79,"color":"#f15d5d"},
{"name":"A14","value":82,"color":"#f15d5d"},
{"name":"A15","value":101,"color":"#fad64b"},
{"name":"A16","value":91,"color":"#fad64b"}
];
var width = 500;
var height = 300;
var barHeight = height / 2 - 15;
var formatNumber = d3.format('s');
var color = d3.scale.ordinal()
.range(['#F15D5D', '#FAD64B']);
var svg = d3.select('#chart').append('svg')
.attr('width', width)
.attr('height', height)
.attr('class', 'radial')
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
var extent = [0, d3.max(data, function(d) { return d.value; })];
var lastNum = extent[1];
var percentageOne = (lastNum*25)/100;
var percentageTwo = (lastNum*50)/100;
var percentageThree = (lastNum*75)/100;
var tickValues = [percentageOne, percentageTwo, percentageThree, lastNum];
var barScale = d3.scale.linear()
.domain(extent)
.range([0, barHeight]);
var keys = data.map(function(d, i) {
return d.name;
});
var numBars = keys.length;
// X scale
var x = d3.scale.linear()
.domain(extent)
.range([0, -barHeight]);
// X axis
var xAxis = d3.svg.axis()
.scale(x).orient('left')
.tickFormat(formatNumber)
.tickValues(tickValues);
// Inner circles
var circles = svg.selectAll('circle')
.data(tickValues)
.enter().append('circle')
.attr('r', function(d) {
return barScale(d);
})
.style('fill', 'none')
.style('stroke-width', '0.5px');
// Create arcs
var arc = d3.svg.arc()
.startAngle(function(d, i) {
var a = (i * 2 * Math.PI) / numBars;
var b = ((i + 1) * 2 * Math.PI) / numBars;
var d = (b - a) / 4;
var x = a + d;
var y = b - d;
return x; //(i * 2 * Math.PI) / numBars;
})
.endAngle(function(d, i) {
var a = (i * 2 * Math.PI) / numBars;
var b = ((i + 1) * 2 * Math.PI) / numBars;
var d = (b - a) / 4;
var x = a + d;
var y = b - d;
return y; //((i + 1) * 2 * Math.PI) / numBars;
})
.innerRadius(0);
// Render colored arcs
var segments = svg.selectAll('path')
.data(data)
.enter().append('path')
.each(function(d) {
d.outerRadius = 0;
})
.attr('class', 'bar')
.style('fill', function(d) {
return d.color;
})
.attr('d', arc);
// Animate segments
segments.transition().ease('elastic').duration(1000).delay(function(d, i) {
return (25 - i) * 10;
})
.attrTween('d', function(d, index) {
var i = d3.interpolate(d.outerRadius, barScale(+d.value));
return function(t) {
d.outerRadius = i(t);
return arc(d, index);
};
});
// Outer circle
svg.append('circle')
.attr('r', barHeight)
.classed('outer', true)
.style('fill', 'none')
.style('stroke-width', '.5px');
// Apply x axis
svg.append('g')
.attr('class', 'x axis')
.call(xAxis);
// Labels
var labelRadius = barHeight * 1.025;
var labels = svg.selectAll('foo')
.data(data)
.enter()
.append('g')
.classed('labels', true);
labels.append('def')
.append('path')
.attr('id', function(d, i) { return 'label-path' + i; })
.attr('d', function(d) {
return 'm0 ' + -(barScale(d.value) + 4) + ' a' + (barScale(d.value) + 4) + ' ' + (barScale(d.value) + 4) + ' 0 1,1 -0.01 0';
});
labels.append('def')
.append('path')
.attr('id', function(d, i) { return 'label-pathnum' + i; })
.attr('d', function(d){
return 'm0 ' + -(barScale(d.value) + 14) + ' a' + (barScale(d.value) + 14) + ' ' + (barScale(d.value) + 14) + ' 0 1,1 -0.01 0';
});
labels.append('text')
.style('text-anchor', 'middle')
.style('fill', function(d, i) {
return d.color;
})
.append('textPath')
.attr('xlink:href', function(d, i) { return '#label-path' + i; })
.attr('startOffset', function(d, i) {
return i * 100 / numBars + 50 / numBars + '%';
})
.text(function(d) {
return d.name.toUpperCase();
});
labels.append('text')
.style('text-anchor', 'middle')
.style('fill', function(d, i) {
return d.color;
})
.append('textPath')
.attr('xlink:href', function(d, i) { return '#label-pathnum' + i; })
.attr('startOffset', function(d, i) {
return i * 100 / numBars + 50 / numBars + '%';
})
.text(function(d) {
return d.value;
});
You need to modify the path for the specific elements that need to be flipped. To do this, I start by storing the angle in your data object:
.each(function(d,i) {
d.outerRadius = 0;
var angleStart = (i/numBars) * 360;
var angleEnd = ((i+1)/numBars) * 360;
d.angle = (angleStart + angleEnd) / 2;
})
Then I tested the angle when creating the paths for the text and I reverse the path for the flipped text case:
var len = barScale(d.value) + 4;
if(d.angle > 91 && d.angle < 269) {
len += 8; // the flipped text is on the inside of the path so we need to draw it further out
return 'M -0.01 ' + (-len) + ' A ' + len + ' ' + len + ' 0 1,0 0 ' + (-len);
}
else {
return 'M 0 ' + (-len) + ' A' + len + ' ' + len + ' 0 1,1 -0.01 ' + (-len);
}
Then, you need to flip your '% around the path' for the placement of the text along the reversed path:
.attr('startOffset', function(d, i) {
if(d.angle > 91 && d.angle < 269)
return (100 - (i * 100 / numBars + 50 / numBars)) + '%';
else
return i * 100 / numBars + 50 / numBars + '%';
})
Working fiddle can be found here: https://jsfiddle.net/FrancisMacDougall/mnrqokqL/
With this result:

How to stop text transitioning off the screen

I have a visual of a two ringed pie chart.
What I'm building is the following:
- if a sector's radians are too small for the specified text then the text is hidden. This is done and seems to work ok
- if text is hidden then a label should appear outside the pie. This is also done when the visual is initially rendered.
When the radio button is pressed the labels outside the pie should transition accordingly. I've attempted the transition and slowed it to 3500 and these labels are just slowly transitioning off the screen.
How do I fix this transition?
The snippet I've added to try to create the transition starts at line 241:
var arcs2 = svg.data([json]).selectAll(".arcG");
arcs2.data(partition.nodes)
.transition()
.duration(3500)
.attr("transform", function(d) {
var c = arc.centroid(d),
x = c[0],
y = c[1],
// pythagorean theorem for hypotenuse
h = Math.sqrt(x * x + y * y);
return "translate(" + (x / h * labelr) + ',' +
(y / h * labelr) + ")";
})
.attr("text-anchor", "middle");
svg.selectAll(".theTxtsOuter")
.text(function(d, i) {
if (d.name === 'root') {
return;
} else if ((d.depth === 1) && (d.dx < (d.name.length * 0.15))) {
return d.name;
} else if ((d.depth === 2) && (d.dx < (d.name.length * 0.1))) {
return d.name;
} else {
return;
}
});
This is a plunk of the working(!) visual:
https://plnkr.co/edit/jYVPCL?p=preview
Here is the complete javascript used by the pie:
function pieChart(dataFile) {
var plot;
var vis;
var width = 400,
height = 400,
radius = Math.min(width, height) / 2.1,
color = d3.scale.ordinal()
.range(["#338ABA", "#016da9", "#4c96d5"])
.domain([0, 2]);
var labelr = radius + 5 // radius for label anchor
var div = d3.select("body")
.append("div")
.attr("class", "toolTip");
var arc = d3.svg.arc()
.startAngle(function(d) {
return d.x;
})
.endAngle(function(d) {
return d.x + d.dx;
})
.outerRadius(function(d) {
return (d.y + d.dy) / (radius);
})
.innerRadius(function(d) {
return d.y / (radius);
});
//check if the svg already exists
plot = d3.select("#svgPIEChart");
if (plot.empty()) {
vis = d3.select("#pieChart")
.append("svg")
.attr({
id: "svgPIEChart"
});
} else {
vis = d3.select("#svgPIEChart");
vis.selectAll("*").remove();
}
//group of the svg element
var svg = vis
.append("g")
.attr({
'transform': "translate(" + width / 2 + "," + height * .52 + ")"
});
//svg element
vis.attr({
//set the width and height of our visualization (these will be attributes of the <svg> tag
width: width,
height: height
});
d3.text(dataFile, function(text) {
var csv = d3.csv.parseRows(text);
var json = buildHierarchy(csv);
// it seems d3.layout.partition() can be either squares or arcs
var partition = d3.layout.partition()
.sort(null)
.size([2 * Math.PI, radius * radius])
.value(function(d) {
return d.SalesRev;
});
var path = svg.data([json]).selectAll(".theArc")
.data(partition.nodes)
.enter()
.append("path")
.attr("class", "theArc")
.attr("id", function(d, i) {
return "theArc_" + i;
}) //Give each slice a unique ID
.attr("display", function(d) {
return d.depth ? null : "none";
})
.attr("d", arc)
.style("stroke", "#fff")
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.attr("fill-rule", "evenodd")
.style("opacity", 0.01)
.style("stroke-opacity", 0.01)
.each(stash);
path.transition()
.duration(PIEOBJ.transTime)
.style("opacity", 1)
.style("stroke-opacity", 1)
path
.on("mouseout", mouseout)
.on("mousemove", function(d) {
div.style("left", d3.event.pageX + 10 + "px");
div.style("top", d3.event.pageY - 25 + "px");
div.style("display", "inline-block");
div.html(d.name + "<br>" + PIEOBJ.formatShrtInt(d.SalesRev));
})
var txts = svg.data([json]).selectAll(".theTxts")
.data(partition.nodes)
.enter()
.append("text");
txts
.attr("class", "theTxts")
.attr("dx", 10) //Move the text from the start angle of the arc
.attr("dy", 15) //Move the text down
.style("opacity", 0)
txts
.transition()
.duration(PIEOBJ.transTime)
.style("opacity", 1);
var txtPths = txts.append("textPath")
// .attr("xlink:href", function(d, i) {
.attr("href", function(d, i) {
return "#theArc_" + i;
})
.text(function(d) {
if (d.name === 'root') {
return;
} else if ((d.depth === 1) && (d.dx < (d.name.length * 0.15))) {
return;
} else if ((d.depth === 2) && (d.dx < (d.name.length * 0.1))) {
return;
} else {
return d.name;
}
});
/* ------- TEXT LABELS OUTSIDE THE PIE-------*/
//var arcs = svg.selectAll(".theArc");
var arcs = svg.data([json]).selectAll(".arcG")
.data(partition.nodes)
.enter()
.append("g")
.attr("class", "arcG");
arcs.append("text")
.attr("transform", function(d) {
var c = arc.centroid(d),
x = c[0],
y = c[1],
// pythagorean theorem for hypotenuse
h = Math.sqrt(x * x + y * y);
console.log(c, h);
return "translate(" + (x / h * labelr) + ',' +
(y / h * labelr) + ")";
})
.attr("text-anchor", "middle")
.text(function(d, i) {
if (d.name === 'root') {
return;
} else if ((d.depth === 1) && (d.dx < (d.name.length * 0.15))) {
return d.name;
} else if ((d.depth === 2) && (d.dx < (d.name.length * 0.1))) {
return d.name;
} else {
return;
}
})
.attr("class", "theTxtsOuter");
/* ----------------------------------------*/
d3.selectAll("input").on("change", function change() {
function createValueFunc(val) {
// currentMeasure = val;
return function(d) {
return d[val];
};
}
value = createValueFunc(this.value);
PIEOBJ.currentMeasure = this.value;
var path2 = svg.data([json]).selectAll(".theArc");
path2
.data(partition.value(value).nodes)
.transition()
.duration(1500)
.attrTween("d", arcTween)
.each("start", function() {
d3.select(this)
.on("mouseout", null) //CLEARING the listeners
.on("mousemove", null);
})
.each("end", function() {
d3.select(this)
.on("mouseout", mouseout) //attaching the listeners
.on("mousemove", function(d) {
div.style("left", d3.event.pageX + 10 + "px");
div.style("top", d3.event.pageY - 25 + "px");
div.style("display", "inline-block");
div.html(d.name + "<br>" + PIEOBJ.formatShrtInt(value(d)));
});
});
svg.selectAll("textPath")
.text(function(d) {
if (d.name === 'root') {
return;
} else if ((d.depth === 1) && (d.dx < (d.name.length * 0.15))) {
return;
} else if ((d.depth === 2) && (d.dx < (d.name.length * 0.1))) {
return;
} else {
return d.name;
}
});
var arcs2 = svg.data([json]).selectAll(".arcG");
arcs2.data(partition.nodes)
.transition()
.duration(3500)
.attr("transform", function(d) {
var c = arc.centroid(d),
x = c[0],
y = c[1],
// pythagorean theorem for hypotenuse
h = Math.sqrt(x * x + y * y);
return "translate(" + (x / h * labelr) + ',' +
(y / h * labelr) + ")";
})
.attr("text-anchor", "middle");
svg.selectAll(".theTxtsOuter")
.text(function(d, i) {
if (d.name === 'root') {
return;
} else if ((d.depth === 1) && (d.dx < (d.name.length * 0.15))) {
return d.name;
} else if ((d.depth === 2) && (d.dx < (d.name.length * 0.1))) {
return d.name;
} else {
return;
}
});
// the following deletes what was originally created and then recreates the text
// svg.selectAll("#titleX").remove();
});
function mouseout() {
div.style("display", "none"); //<< gets rid of the tooltip <<
}
// Stash the old values for transition.
function stash(d) {
d.x0 = d.x;
d.dx0 = d.dx;
}
// Interpolate the arcs in data space.
function arcTween(a) {
var i = d3.interpolate({
x: a.x0,
dx: a.dx0
}, a);
return function(t) {
var b = i(t);
a.x0 = b.x;
a.dx0 = b.dx;
return arc(b);
};
}
});
}
// // Take a 2-column CSV and transform it into a hierarchical structure suitable
// // for a partition layout.
function buildHierarchy(csv) {
var root = {
"name": "root",
"children": []
};
for (var i = 0; i < csv.length; i++) {
var sequence = csv[i][0];
// var APD = +csv[i][1];
var SalesRev = +csv[i][1];
var Amount = +csv[i][2];
if (isNaN(SalesRev)) { // e.g. if this is a header row
continue;
}
var parts = sequence.split("-");
var currentNode = root;
for (var j = 0; j < parts.length; j++) {
var children = currentNode.children;
var nodeName = parts[j];
var childNode;
if (j + 1 < parts.length) {
// Not yet at the end of the sequence; move down the tree.
var foundChild = false;
for (var k = 0; k < children.length; k++) {
if (children[k].name == nodeName) {
childNode = children[k];
foundChild = true;
break;
}
}
// If we don't already have a child node for this branch, create it.
if (!foundChild) {
childNode = {
"name": nodeName,
"children": []
};
children.push(childNode);
}
currentNode = childNode;
} else {
// Reached the end of the sequence; create a leaf node.
childNode = {
"name": nodeName,
// "APD": APD,
"SalesRev": SalesRev,
"Amount": Amount
};
children.push(childNode);
}
}
}
root.children.forEach(function(v) {
v.SalesRev = 0;
v.Amount = 0;
v.children.forEach(function(a) {
v.SalesRev += a.SalesRev;
v.Amount += a.Amount;
});
});
return root;
}
When you initially position them you are transforming the text elements. When you transition them you are positioning the outer g elements. These causes conflicting transforms. Use:
arcs2.data(partition.nodes)
.select('text') //<-- apply on child text
.transition()
.duration(3500)
.attr("transform", function(d) {
...
});
Updated plunker.

D3 merge colors to gradients

I have been trying to merge a set of paths in d3, So that one color blends into the other so it appears as though forms a gradient i tried to use to create a gradient but its always in a single direction which does not work out.
Fiddle here https://jsfiddle.net/roug3/jnpe5v3p/
var mapGroup = d3.select("svg");
function renderARC() {
var txData = {x: 200 , y : 200 , angle : 30};
var etxD = {etxSN : "TX500"};
if(d3.select(".arc"+etxD.etxSN).node()){
return;
}
var arcLevel = 5;
var arcSpan = 20;
var arcAngle = 2.0944;
var txAngle = txData.angle + 0;
var startAngle = txAngle - (arcAngle / 2);
var endAngle = txAngle + (arcAngle / 2);
var x = txData.x;
var y = txData.y;
var cwidth = 20;
var dataset = {};
for(var i = 1;i<= arcLevel;i++){
dataset[i] = [i];
}
var color = ["#ee4035","#f37736","#fdf498","#7bc043","#0392cf"]
// var color = ["#009933" , "#33cc33" ,"#ff3300" , "#ffcc66" ,"#ff6699" ,"#4dffff"];
var pie = d3.layout.pie()
.sort(null)
.startAngle(startAngle)
.endAngle(endAngle);
var arc = d3.svg.arc();
var gs = mapGroup.append("g").classed("arc"+etxD.etxSN , true).classed("arcSegment" , true);
console.log(gs);
var ggs = gs.selectAll("g").data(d3.values(dataset)).enter().append("g");
var arcP = ggs.selectAll("path").data(function (d) {
return pie(d);
})
.enter();
arcP.append("path").
attr("class" , function (d, i) {
return "arcID"+etxD.etxSN+i;
})
.attr("fill", function (d, i, j) {
// var cspan = Math.floor(Math.random() * arcLevel);
return color[ j ];
})
.attr("d", function (d, i, j) {
return arc.innerRadius(cwidth * j + arcSpan).outerRadius(cwidth * (j + 1) + arcSpan)(d);
}).
attr("transform" , "translate("+x+","+y+")");
}
renderARC();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width=500 height=500></svg>
Any Suggestions
Thanks
This is as close as I could get it : https://jsfiddle.net/thatoneguy/jnpe5v3p/2/
With the help of these :
http://jsfiddle.net/Qh9X5/1110/
http://www.w3schools.com/svg/svg_grad_radial.asp
Basically you have to create a radial blur using the dataset :
var grads = mapGroup.append("defs").selectAll("radialGradient").data(pie(d3.values(dataset)))
.enter().append("radialGradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", function(d, i) {
return cwidth * (i + 1) + arcSpan
})
.attr("id", function(d, i) {
return "grad" + i;
}).attr("transform", "translate(" + x + "," + y + ")");;
grads.append("stop")
.attr("offset", "80%")
.style("stop-color", function(d, i) {return color[i];});
grads.append("stop")
.attr("offset", "100%")
.style("stop-color", function(d, i) {
if (color[i + 1]) {
console.log(color[i + 1])
return color[i + 1];
} else {
return color[i];
}
})
Then select this to fill your paths :
arcP.append("path").
attr("class", function(d, i) {
return "arcID" + etxD.etxSN + i;
})
.attr("fill", function(d, i) {
console.log(count)
count++;
return "url(#grad" + count + ")";
})
.attr("d", function(d, i, j) {
return arc.innerRadius(cwidth * j + arcSpan).outerRadius(cwidth * (j + 1) + arcSpan)(d);
}).
attr("transform", "translate(" + x + "," + y + ")");
var mapGroup = d3.select("svg");
function renderARC() {
var txData = {
x: 200,
y: 200,
angle: 30
};
var etxD = {
etxSN: "TX500"
};
if (d3.select(".arc" + etxD.etxSN).node()) {
return;
}
var arcLevel = 5;
var arcSpan = 20;
var arcAngle = 2.0944;
var txAngle = txData.angle + 0;
var startAngle = txAngle - (arcAngle / 2);
var endAngle = txAngle + (arcAngle / 2);
var x = txData.x;
var y = txData.y;
var cwidth = 20;
var dataset = {};
for (var i = 1; i <= arcLevel; i++) {
dataset[i] = [i];
}
var color = ["#ee4035", "#f37736", "#fdf498", "#7bc043", "#0392cf"]
// var color = ["#009933" , "#33cc33" ,"#ff3300" , "#ffcc66" ,"#ff6699" ,"#4dffff"];
var pie = d3.layout.pie()
.sort(null)
.startAngle(startAngle)
.endAngle(endAngle);
var arc = d3.svg.arc();
var gs = mapGroup.append("g").classed("arc" + etxD.etxSN, true).classed("arcSegment", true);
console.log(gs);
var ggs = gs.selectAll("g").data(d3.values(dataset)).enter().append("g");
var arcP = ggs.selectAll("path").data(function(d) {
return pie(d);
})
.enter();
var grads = mapGroup.append("defs").selectAll("radialGradient").data(pie(d3.values(dataset)))
.enter().append("radialGradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", function(d, i) {
return cwidth * (i + 1) + arcSpan
})
.attr("id", function(d, i) {
return "grad" + i;
}).attr("transform", "translate(" + x + "," + y + ")");;
grads.append("stop")
.attr("offset", "80%")
.style("stop-color", function(d, i) {return color[i];});
grads.append("stop")
.attr("offset", "100%")
.style("stop-color", function(d, i) {
if (color[i + 1]) {
console.log(color[i + 1])
return color[i + 1];
} else {
return color[i];
}
})
var count = -1;
arcP.append("path").
attr("class", function(d, i) {
return "arcID" + etxD.etxSN + i;
})
.attr("fill", function(d, i) {
console.log(count)
count++;
return "url(#grad" + count + ")";
})
.attr("d", function(d, i, j) {
return arc.innerRadius(cwidth * j + arcSpan).outerRadius(cwidth * (j + 1) + arcSpan)(d);
}).
attr("transform", "translate(" + x + "," + y + ")");
}
renderARC();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width=500 height=500></svg>
It isn't perfect but will put you on the right track :) Hope this helps

Slow performance in Firefox for D3 force layout

I've created a force-layout using D3 (see image below). However, it runs very slowly in Firefox, whereas it works perfectly fine in Chrome. I'm debugging it using a local server and browsing at http://localhost:8888/. It's might be due to the following message in the Firefox console, but accordingly to the comments that's unlikely. Can someone pinpoint the performance issue and give me a hint on how to resolve it?
mutating the [[Prototype]] of an object will cause your code to run very slowly; instead create the object with the correct initial [[Prototype]] value using Object.create
Data and code in zip: https://www.dropbox.com/s/ksh2qk1b5s9lfq5/Network%20View.zip?dl=0
Visualization:
Index.html
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.legend {
font-size: 10px;
}
rect {
stroke-width: 2;
}
.node circle {
stroke: white;
stroke-width: 2px;
opacity: 1.0;
}
line {
stroke-width: 4px;
stroke-opacity: 1.0;
//stroke: "black";
}
body {
/* Scaling for different browsers */
-ms-transform: scale(1,1);
-webkit-transform: scale(1,1);
transform: scale(1,1);
}
svg{
position:absolute;
top:50%;
left:0px;
}
</style>
<body>
<script type="text/javascript" src="d3.js"></script>
<script type="text/javascript" src="papaparse.js"></script>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="networkview.js"></script>
</body>
networkview.js
var line_diff = 0.5; // increase from zero if you want space between the call/text lines
var mark_offset = 10; // how many percent of the mark lines in each end are not used for the relationship between incoming/outgoing?
var mark_size = 5; // size of the mark on the line
var legendRectSize = 9; // 18
var legendSpacing = 4; // 4
var recordTypes = [];
var legend;
var text_links_data, call_links_data;
// colors for the different parts of the visualization
recordTypes.push({
text : "call",
color : "#438DCA"
});
recordTypes.push({
text : "text",
color : "#70C05A"
});
recordTypes.push({
text : "balance",
color : "#245A76"
});
// Function for grabbing a specific property from an array
pluck = function (ary, prop) {
return ary.map(function (x) {
return x[prop]
});
}
// Sums an array
sum = function (ary) {
return ary.reduce(function (a, b) {
return a + b
}, 0);
}
maxArray = function (ary) {
return ary.reduce(function (a, b) {
return Math.max(a, b)
}, -Infinity);
}
minArray = function (ary) {
return ary.reduce(function (a, b) {
return Math.min(a, b)
}, Infinity);
}
var data_links;
var data_nodes;
var results = Papa.parse("links.csv", {
header : true,
download : true,
dynamicTyping : true,
delimiter : ",",
skipEmptyLines : true,
complete : function (results) {
data_links = results.data;
dataLoaded();
}
});
var results = Papa.parse("nodes.csv", {
header : true,
download : true,
dynamicTyping : true,
delimiter : ",",
skipEmptyLines : true,
complete : function (results) {
data_nodes = results.data;
data_nodes.forEach(function (d, i) {
d.size = (i == 0)? 200 : 30
d.fill = (d.no_network_info == 1)? "#dfdfdf": "#a8a8a8"
});
dataLoaded();
}
});
function node_radius(d) {
return Math.pow(40.0 * ((d.index == 0) ? 200 : 30), 1 / 3);
}
function node_radius_data(d) {
return Math.pow(40.0 * d.size, 1 / 3);
}
function dataLoaded() {
if (typeof data_nodes === "undefined" || typeof data_links === "undefined") {
//console.log("Still loading")
} else {
CreateVisualizationFromData();
}
}
function isConnectedToOtherThanMain(a) {
var connected = false;
for (i = 1; i < data_nodes.length; i++) {
if (isConnected(a, data_nodes[i]) && a.index != i) {
connected = true;
}
}
return connected;
}
function isConnected(a, b) {
return isConnectedAsTarget(a, b) || isConnectedAsSource(a, b) || a.index == b.index;
}
function isConnectedAsSource(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
function isConnectedAsTarget(a, b) {
return linkedByIndex[b.index + "," + a.index];
}
function isEqual(a, b) {
return a.index == b.index;
}
function tick() {
if (call_links_data.length > 0) {
callLink
.attr("x1", function (d) {
return d.source.x - line_perpendicular_shift(d, 1)[0] + line_radius_shift_to_edge(d, 0)[0];
})
.attr("y1", function (d) {
return d.source.y - line_perpendicular_shift(d, 1)[1] + line_radius_shift_to_edge(d, 0)[1];
})
.attr("x2", function (d) {
return d.target.x - line_perpendicular_shift(d, 1)[0] + line_radius_shift_to_edge(d, 1)[0];
})
.attr("y2", function (d) {
return d.target.y - line_perpendicular_shift(d, 1)[1] + line_radius_shift_to_edge(d, 1)[1];
});
callLink.each(function (d) {
applyGradient(this, "call", d)
});
}
if (text_links_data.length > 0) {
textLink
.attr("x1", function (d) {
return d.source.x - line_perpendicular_shift(d, -1)[0] + line_radius_shift_to_edge(d, 0)[0];
})
.attr("y1", function (d) {
return d.source.y - line_perpendicular_shift(d, -1)[1] + line_radius_shift_to_edge(d, 0)[1];
})
.attr("x2", function (d) {
return d.target.x - line_perpendicular_shift(d, -1)[0] + line_radius_shift_to_edge(d, 1)[0];
})
.attr("y2", function (d) {
return d.target.y - line_perpendicular_shift(d, -1)[1] + line_radius_shift_to_edge(d, 1)[1];
});
textLink.each(function (d) {
applyGradient(this, "text", d)
});
node
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
if (force.alpha() < 0.05)
drawLegend();
}
function getRandomInt() {
return Math.floor(Math.random() * (100000 - 0));
}
function applyGradient(line, interaction_type, d) {
var self = d3.select(line);
var current_gradient = self.style("stroke")
//current_gradient = current_gradient.substring(4, current_gradient.length - 1);
if (current_gradient.match("http")) {
var parts = current_gradient.split("/");
current_gradient = parts[-1];
} else {
current_gradient = current_gradient.substring(4, current_gradient.length - 1);
}
var new_gradient_id = "line-gradient" + getRandomInt();
var from = d.source.size < d.target.size ? d.source : d.target;
var to = d.source.size < d.target.size ? d.target : d.source;
var mid_offset = 0;
var standardColor = "";
if (interaction_type == "call") {
mid_offset = d.inc_calls / (d.inc_calls + d.out_calls);
standardColor = "#438DCA";
} else {
mid_offset = d.inc_texts / (d.inc_texts + d.out_texts);
standardColor = "#70C05A";
}
/* recordTypes_ID = pluck(recordTypes, 'text');
whichRecordType = recordTypes_ID.indexOf(interaction_type);
standardColor = recordTypes[whichRecordType].color;
*/
mid_offset = mid_offset * 100;
mid_offset = mid_offset * 0.6 + 20; // scale so it doesn't hit the ends
lineLengthCalculation = function (x, y, x0, y0) {
return Math.sqrt((x -= x0) * x + (y -= y0) * y);
};
lineLength = lineLengthCalculation(from.px, from.py, to.px, to.py);
if (lineLength >= 0.1) {
mark_size_percent = (mark_size / lineLength) * 100;
defs.append("linearGradient")
.attr("id", new_gradient_id)
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", from.px)
.attr("y1", from.py)
.attr("x2", to.px)
.attr("y2", to.py)
.selectAll("stop")
.data([{
offset : "0%",
color : standardColor,
opacity : "1"
}, {
offset : Math.round(mid_offset - mark_size_percent / 2) + "%",
color : standardColor,
opacity : "1"
}, {
offset : Math.round(mid_offset - mark_size_percent / 2) + "%",
color : standardColor,
opacity : "1"
}, {
offset : Math.round(mid_offset - mark_size_percent / 2) + "%",
color : "#245A76",
opacity : "1"
}, {
offset : Math.round(mid_offset + mark_size_percent / 2) + "%",
color : "#245A76",
opacity : "1"
}, {
offset : Math.round(mid_offset + mark_size_percent / 2) + "%",
color : standardColor,
opacity : "1"
}, {
offset : Math.round(mid_offset + mark_size_percent / 2) + "%",
color : standardColor,
opacity : "1"
}, {
offset : "100%",
color : standardColor,
opacity : "1"
}
])
.enter().append("stop")
.attr("offset", function (d) {
return d.offset;
})
.attr("stop-color", function (d) {
return d.color;
})
.attr("stop-opacity", function (d) {
return d.opacity;
});
self.style("stroke", "url(#" + new_gradient_id + ")")
defs.select(current_gradient).remove();
}
}
var linkedByIndex;
var width = $(window).width();
var height = $(window).height();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force;
var callLink;
var textLink;
var link;
var node;
var defs;
var total_interactions = 0;
var max_interactions = 0;
function CreateVisualizationFromData() {
for (i = 0; i < data_links.length; i++) {
total_interactions += data_links[i].inc_calls + data_links[i].out_calls + data_links[i].inc_texts + data_links[i].out_texts;
max_interactions = Math.max(max_interactions, data_links[i].inc_calls + data_links[i].out_calls + data_links[i].inc_texts + data_links[i].out_texts)
}
linkedByIndex = {};
data_links.forEach(function (d) {
linkedByIndex[d.source + "," + d.target] = true;
//linkedByIndex[d.source.index + "," + d.target.index] = true;
});
//console.log(total_interactions);
//console.log(max_interactions);
function chargeForNode(d, i) {
// main node
if (i == 0) {
return -25000;
}
// contains other links
else if (isConnectedToOtherThanMain(d)) {
return -2000;
} else {
return -1200;
}
}
// initial placement of nodes prevents overlaps
central_x = width / 2
central_y = height / 2
data_nodes.forEach(function(d, i) {
if (i != 0) {
connected = isConnectedToOtherThanMain(d);
data_nodes[i].x = connected? central_x + 10000: central_x -10000;
data_nodes[i].y = connected? central_y: central_y;
}
else {data_nodes[i].x = central_x; data_nodes[i].y = central_y;}})
force = d3.layout.force()
.nodes(data_nodes)
.links(data_links)
.charge(function (d, i) {
return chargeForNode(d, i)
})
.friction(0.6) // 0.6
.gravity(0.4) // 0.6
.size([width, height])
.start();
call_links_data = data_links.filter(function(d) {
return (d.inc_calls + d.out_calls > 0)});
text_links_data = data_links.filter(function(d) {
return (d.inc_texts + d.out_texts > 0)});
callLink = svg.selectAll(".call-line")
.data(call_links_data)
.enter().append("line");
textLink = svg.selectAll(".text-line")
.data(text_links_data)
.enter().append("line");
link = svg.selectAll("line");
node = svg.selectAll(".node")
.data(data_nodes)
.enter().append("g")
.attr("class", "node");
defs = svg.append("defs");
node
.append("circle")
.attr("r", node_radius)
.style("fill", function (d) {
return (d.index == 0)? "#ffffff" : d.fill;
})
.style("stroke", function (d) {
return (d.index == 0)? "#8C8C8C" : "#ffffff";
})
svg
.append("marker")
.attr("id", "arrowhead")
.attr("refX", 6 + 7)
.attr("refY", 2)
.attr("markerWidth", 6)
.attr("markerHeight", 4)
.attr("orient", "auto")
.append("path")
.attr("d", "M 0,0 V 4 L6,2 Z");
if (text_links_data.length > 0) {
textLink
.style("stroke-width", function stroke(d) {
return text_width(d)
})
.each(function (d) {
applyGradient(this, "text", d)
});
}
if (call_links_data.length > 0) {
callLink
.style("stroke-width", function stroke(d) {
return call_width(d)
})
.each(function (d) {
applyGradient(this, "call", d)
});
}
force
.on("tick", tick);
}
function drawLegend() {
var node_px = pluck(data_nodes, 'px');
var node_py = pluck(data_nodes, 'py');
var nodeLayoutRight = Math.max(maxArray(node_px));
var nodeLayoutBottom = Math.max(maxArray(node_py));
legend = svg.selectAll('.legend')
.data(recordTypes)
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function (d, i) {
var rect_height = legendRectSize + legendSpacing;
var offset = rect_height * (recordTypes.length-1);
var horz = nodeLayoutRight + 15; /* - 2*legendRectSize; */
var vert = nodeLayoutBottom + (i * rect_height) - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', function (d) {
return d.color
})
.style('stroke', function (d) {
return d.color
});
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing + 3)
.text(function (d) {
return d.text;
})
.style('fill', '#757575');
}
var line_width_factor = 10.0 // width for the widest line
function call_width(d) {
return (d.inc_calls + d.out_calls) / max_interactions * line_width_factor;
}
function text_width(d) {
return (d.inc_texts + d.out_texts) / max_interactions * line_width_factor;
}
function total_width(d) {
return (d.inc_calls + d.out_calls + d.inc_texts + d.out_texts) / max_interactions * line_width_factor + line_diff;
}
function line_perpendicular_shift(d, direction) {
theta = getAngle(d);
theta_perpendicular = theta + (Math.PI / 2) * direction;
lineWidthOfOppositeLine = direction == 1 ? text_width(d) : call_width(d);
shift = lineWidthOfOppositeLine / 2;
delta_x = (shift + line_diff) * Math.cos(theta_perpendicular)
delta_y = (shift + line_diff) * Math.sin(theta_perpendicular)
return [delta_x, delta_y]
}
function line_radius_shift_to_edge(d, which_node) { // which_node = 0 if source, = 1 if target
theta = getAngle(d);
theta = (which_node == 0) ? theta : theta + Math.PI; // reverse angle if target node
radius = (which_node == 0) ? node_radius(d.source) : node_radius(d.target) // d.source and d.target refer directly to the nodes (not indices)
radius -= 2; // add stroke width
delta_x = radius * Math.cos(theta)
delta_y = radius * Math.sin(theta)
return [delta_x, delta_y]
}
function getAngle(d) {
rel_x = d.target.x - d.source.x;
rel_y = d.target.y - d.source.y;
return theta = Math.atan2(rel_y, rel_x);
}
Links.csv
source,target,inc_calls,out_calls,inc_texts,out_texts
0,1,1.0,0.0,1.0,0.0
0,2,0.0,0.0,1.0,3.0
0,3,3.0,9.0,5.0,7.0
0,4,2.0,12.0,9.0,14.0
0,5,5.0,9.0,9.0,13.0
0,6,5.0,17.0,2.0,25.0
0,7,6.0,13.0,7.0,16.0
0,8,7.0,7.0,8.0,8.0
0,9,3.0,10.0,8.0,20.0
0,10,5.0,10.0,6.0,23.0
0,11,8.0,10.0,13.0,15.0
0,12,9.0,18.0,9.0,22.0
0,13,1.0,2.0,2.0,2.0
0,14,11.0,13.0,7.0,15.0
0,15,5.0,18.0,9.0,22.0
0,16,8.0,15.0,13.0,20.0
0,17,4.0,10.0,9.0,26.0
0,18,9.0,18.0,8.0,33.0
0,19,12.0,11.0,4.0,15.0
0,20,4.0,15.0,9.0,25.0
0,21,4.0,17.0,10.0,19.0
0,22,4.0,16.0,12.0,29.0
0,23,6.0,9.0,12.0,20.0
0,24,2.0,2.0,1.0,3.0
0,25,3.0,8.0,10.0,16.0
0,26,3.0,10.0,11.0,22.0
0,27,6.0,14.0,9.0,11.0
0,28,2.0,7.0,8.0,15.0
0,29,2.0,11.0,8.0,15.0
0,30,1.0,8.0,9.0,6.0
0,31,3.0,6.0,7.0,7.0
0,32,4.0,9.0,3.0,12.0
0,33,4.0,4.0,7.0,12.0
0,34,4.0,4.0,5.0,9.0
0,35,2.0,3.0,0.0,7.0
0,36,3.0,7.0,5.0,9.0
0,37,1.0,7.0,5.0,3.0
0,38,1.0,13.0,1.0,2.0
0,39,2.0,7.0,3.0,4.0
0,40,1.0,3.0,2.0,6.0
0,41,0.0,1.0,2.0,1.0
0,42,0.0,0.0,2.0,0.0
0,43,0.0,3.0,1.0,5.0
0,44,0.0,1.0,0.0,2.0
0,45,4.0,1.0,1.0,10.0
0,46,2.0,7.0,3.0,5.0
0,47,5.0,7.0,3.0,5.0
0,48,2.0,5.0,4.0,10.0
0,49,3.0,3.0,5.0,13.0
1,15,10.0,30.0,13.0,37.0
2,8,16.0,9.0,24.0,15.0
2,43,4.0,10.0,9.0,16.0
5,48,3.0,5.0,0.0,4.0
6,37,11.0,25.0,15.0,34.0
8,48,12.0,4.0,7.0,2.0
9,42,25.0,9.0,29.0,15.0
9,45,11.0,3.0,16.0,5.0
12,24,4.0,15.0,13.0,16.0
14,31,18.0,9.0,29.0,12.0
14,33,5.0,10.0,4.0,9.0
15,28,8.0,5.0,16.0,5.0
16,36,14.0,11.0,10.0,19.0
23,38,3.0,11.0,6.0,10.0
26,42,9.0,23.0,17.0,21.0
27,46,12.0,12.0,15.0,21.0
29,39,8.0,15.0,9.0,20.0
29,47,8.0,27.0,19.0,24.0
33,46,6.0,4.0,13.0,13.0
37,43,10.0,12.0,6.0,21.0
Nodes.csv
no_network_info
0
0
0
1
1
0
0
0
0
0
0
1
0
1
0
0
0
1
0
1
1
0
0
0
0
1
0
0
0
0
1
0
1
0
1
1
0
0
0
0
1
1
0
0
1
0
0
0
0
0
EDIT
The root cause of the problem was document bloat caused by failing to remove outdated linearGradient tags in the defs section of the
HTML. This was only happening in Firefox because of what it returns
in response to getPropertyValue in it's CSSStyleDeclaration
interface (which is called by d3 in selection.style()). The value
returned is of the form
"url("http://localhost:88888/index.html#line-gradientXXXXXX")
transparent", compared to "url(#line-gradientXXXXXX)" in the other
browsers. Since the id was not properly extracted by the OP,
linearGradient tags ear-marked for deletion were not found and not
deleted, causing them to grow in number. The problem is avoided by
using unique indexing, already available in the data, to label the
linearGradient tags.
As per my comments above, I managed to solve the Firefox problem by making the following changes:
Eliminate redundant calculations in the forEach sections in tick and applyGradient.
Using well-formed d3 to manage the defs. It was probably fine how it was, it just took me a while to realise how it was done but, I changed it to standard d3 patterns which will manage updating and changing data properly. This line is particularly sensitive...
var new_gradient_id = "line-gradient" + getRandomInt();
this works better...
var new_gradient_id = "lg" + interaction_type + d.source.index + d.target.index;
Applied standard d3 patterns to managing the callLink and textLink sections in CreateVisualizationFromData. Using these patterns it updates properly and manages changing data.
After making these changes, the speed problems in Firefox disapeared and it is now the same in all three major browsers in terms of speed. It looks better in Chrome though. Some experimenting would be in order to determine exactly which changes are critical, but there was definitely a problem with deleting the linearGradient tags. These were not being properly deleted in FF and massively bloating the DOM. I think this is probably what was causing the problem.
The other changes I made were just stylistic to make it easier for me to understand.
Amended code:
HTML
<!DOCTYPE html>
<meta charset="utf-8">
<style>
/*div {
outline: 1px solid black;*/
}
.legend {
font-size: 10px;
}
rect {
stroke-width: 2;
}
.node circle {
stroke: white;
stroke-width: 2px;
opacity: 1.0;
}
line {
stroke-width: 4px;
stroke-opacity: 1.0;
//stroke: "black";
}
body {
/* Scaling for different browsers */
-ms-transform: scale(1,1);
-webkit-transform: scale(1,1);
transform: scale(1,1);
}
svg{
position:absolute;
top:50%;
left:0px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<div style="margin: 50px 0 10px 50px; display: inline-block">click to start/stop</div>
<!--<script src="d3/d3 CB.js"></script>-->
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="papaparse.js"></script>
<script type="text/javascript" src="networkview CB.js"></script>
</body>
JS
var line_diff = 0.5; // increase from zero if you want space between the call/text lines
var mark_offset = 10; // how many percent of the mark lines in each end are not used for the relationship between incoming/outgoing?
var mark_size = 5; // size of the mark on the line
var legendRectSize = 9; // 18
var legendSpacing = 4; // 4
var recordTypes = [];
var legend;
var text_links_data, call_links_data;
// colors for the different parts of the visualization
recordTypes.push({
text : "call",
color : "#438DCA"
});
recordTypes.push({
text : "text",
color : "#70C05A"
});
recordTypes.push({
text : "balance",
color : "#245A76"
});
// Function for grabbing a specific property from an array
pluck = function (ary, prop) {
return ary.map(function (x) {
return x[prop]
});
}
// Sums an array
sum = function (ary) {
return ary.reduce(function (a, b) {
return a + b
}, 0);
}
maxArray = function (ary) {
return ary.reduce(function (a, b) {
return Math.max(a, b)
}, -Infinity);
}
minArray = function (ary) {
return ary.reduce(function (a, b) {
return Math.min(a, b)
}, Infinity);
}
var data_links;
var data_nodes;
var results = Papa.parse("links.csv", {
header : true,
download : true,
dynamicTyping : true,
delimiter : ",",
skipEmptyLines : true,
complete : function (results) {
data_links = results.data;
for (i = 0; i < data_links.length; i++) {
total_interactions += data_links[i].inc_calls
+ data_links[i].out_calls
+ data_links[i].inc_texts
+ data_links[i].out_texts;
max_interactions = Math.max(max_interactions,
data_links[i].inc_calls
+ data_links[i].out_calls
+ data_links[i].inc_texts
+ data_links[i].out_texts)
}
//console.log(total_interactions);
//console.log(max_interactions);
linkedByIndex = {};
data_links.forEach(function (d) {
linkedByIndex[d.source + "," + d.target] = true;
//linkedByIndex[d.source.index + "," + d.target.index] = true;
});
dataLoaded();
}
});
var results = Papa.parse("nodes.csv", {
header : true,
download : true,
dynamicTyping : true,
delimiter : ",",
skipEmptyLines : true,
complete : function (results) {
data_nodes = results.data;
data_nodes.forEach(function (d, i) {
d.size = (i == 0)? 200 : 30
d.fill = (d.no_network_info == 1)? "#dfdfdf": "#a8a8a8"
});
dataLoaded();
}
});
function node_radius(d) {
return Math.pow(40.0 * ((d.index == 0) ? 200 : 30), 1 / 3);
}
function node_radius_data(d) {
return Math.pow(40.0 * d.size, 1 / 3);
}
function dataLoaded() {
if (typeof data_nodes === "undefined" || typeof data_links === "undefined") {
console.log("Still loading " + (typeof data_nodes === "undefined" ? 'data_links' : 'data_nodes'))
} else {
CreateVisualizationFromData();
}
}
function isConnectedToOtherThanMain(a) {
var connected = false;
for (i = 1; i < data_nodes.length; i++) {
if (isConnected(a, data_nodes[i]) && a.index != i) {
connected = true;
}
}
return connected;
}
function isConnected(a, b) {
return isConnectedAsTarget(a, b) || isConnectedAsSource(a, b) || a.index == b.index;
}
function isConnectedAsSource(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
function isConnectedAsTarget(a, b) {
return linkedByIndex[b.index + "," + a.index];
}
function isEqual(a, b) {
return a.index == b.index;
}
var log = d3.select('body').append('div').attr('id', 'log').style({margin: '50px 0 10px 3px', display: 'inline-block'});
log.update = function (alpha) {
this.text('alpha: ' + d3.format(".3f")(alpha))
}
function tick(e) {
log.update(e.alpha)
if (call_links_data.length > 0) {
callLink
//CB eliminate redundant calculations
.each(function (d) {
d.lpf1 = line_perpendicular_shift(d, 1)
d.lrste = []
d.lrste.push(line_radius_shift_to_edge(d, 0))
d.lrste.push(line_radius_shift_to_edge(d, 1))
})
//CB
.attr("x1", function (d) {
return d.source.x - d.lpf1[0] + d.lrste[0][0];
})
.attr("y1", function (d) {
return d.source.y - d.lpf1[1] + d.lrste[0][1];
})
.attr("x2", function (d) {
return d.target.x - d.lpf1[0] + d.lrste[1][0];
})
.attr("y2", function (d) {
return d.target.y - d.lpf1[1] + d.lrste[1][1];
});
callLink.each(function (d, i) {
applyGradient(this, "call", d, i)
});
}
if (text_links_data.length > 0) {
textLink
//CB
.each(function (d) {
d.lpfNeg1 = line_perpendicular_shift(d, -1);
d.lrste = [];
d.lrste.push(line_radius_shift_to_edge(d, 0));
d.lrste.push(line_radius_shift_to_edge(d, 1));
})
//CB
.attr("x1", function (d) {
return d.source.x - d.lpfNeg1[0] + d.lrste[0][0];
})
.attr("y1", function (d) {
return d.source.y - d.lpfNeg1[1] + d.lrste[0][1];
})
.attr("x2", function (d) {
return d.target.x - d.lpfNeg1[0] + d.lrste[1][0];
})
.attr("y2", function (d) {
return d.target.y - d.lpfNeg1[1] + d.lrste[1][1];
});
textLink.each(function (d, i) {
applyGradient(this, "text", d, i)
});
node
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
if (force.alpha() < 0.05)
drawLegend();
}
function getRandomInt() {
return Math.floor(Math.random() * (100000 - 0));
}
function applyGradient(line, interaction_type, d, i) {
var self = d3.select(line);
var current_gradient = self.style("stroke");
//current_gradient = current_gradient.substring(4, current_gradient.length - 1);
if (current_gradient.match("http")) {
var parts = current_gradient.split("/");
current_gradient = parts[-1];
} else {
current_gradient = current_gradient.substring(4, current_gradient.length - 1);
}
var new_gradient_id = "lg" + interaction_type + d.source.index + d.target.index; // + getRandomInt();
var from = d.source.size < d.target.size ? d.source : d.target;
var to = d.source.size < d.target.size ? d.target : d.source;
var mid_offset = 0;
var standardColor = "";
if (interaction_type == "call") {
mid_offset = d.inc_calls / (d.inc_calls + d.out_calls);
standardColor = "#438DCA";
} else {
mid_offset = d.inc_texts / (d.inc_texts + d.out_texts);
standardColor = "#70C05A";
}
/* recordTypes_ID = pluck(recordTypes, 'text');
whichRecordType = recordTypes_ID.indexOf(interaction_type);
standardColor = recordTypes[whichRecordType].color;
*/
mid_offset = mid_offset * 100;
mid_offset = mid_offset * 0.6 + 20; // scale so it doesn't hit the ends
lineLengthCalculation = function (x, y, x0, y0) {
return Math.sqrt((x -= x0) * x + (y -= y0) * y);
};
lineLength = lineLengthCalculation(from.px, from.py, to.px, to.py);
if (lineLength >= 0.1) {
var mark_size_percent = (mark_size / lineLength) * 100,
_offsetDiff = Math.round(mid_offset - mark_size_percent / 2) + "%",
_offsetSum = Math.round(mid_offset + mark_size_percent / 2) + "%",
defsUpdate = defs.selectAll("#" + new_gradient_id)
.data([{
x1: from.px,
y1: from.py,
x2: to.px,
y2: to.py
}]),
defsEnter = defsUpdate.enter().append("linearGradient")
.attr("id", new_gradient_id)
.attr("gradientUnits", "userSpaceOnUse"),
defsUpdateEnter = defsUpdate
.attr("x1", function (d) { return d.x1 })
.attr("y1", function (d) { return d.y1 })
.attr("x2", function (d) { return d.x2 })
.attr("y2", function (d) { return d.y2 }),
stopsUpdate = defsUpdateEnter.selectAll("stop")
.data([{
offset: "0%",
color: standardColor,
opacity: "1"
}, {
offset: _offsetDiff,
color: standardColor,
opacity: "1"
}, {
offset: _offsetDiff,
color: standardColor,
opacity: "1"
}, {
offset: _offsetDiff,
color: "#245A76",
opacity: "1"
}, {
offset: _offsetSum,
color: "#245A76",
opacity: "1"
}, {
offset: _offsetSum,
color: standardColor,
opacity: "1"
}, {
offset: _offsetSum,
color: standardColor,
opacity: "1"
}, {
offset: "100%",
color: standardColor,
opacity: "1"
}
]),
stopsEnter = stopsUpdate.enter().append("stop")
stopsUpdateEnter = stopsUpdate
.attr("offset", function (d) {
return d.offset;
})
.attr("stop-color", function (d) {
return d.color;
})
.attr("stop-opacity", function (d) {
return d.opacity;
})
self.style("stroke", "url(#" + new_gradient_id + ")")
//current_gradient && defs.select(current_gradient).remove(); /*CB Edit*/
}
} /*applyGradient*/
var linkedByIndex;
var width = $(window).width();
var height = $(window).height();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force;
var callLink;
var textLink;
var link;
var node;
var defs;
var marker;
var total_interactions = 0;
var max_interactions = 0;
function CreateVisualizationFromData() {
function chargeForNode(d, i) {
// main node
if (i == 0) {
return -25000;
}
// contains other links
else if (isConnectedToOtherThanMain(d)) {
return -2000;
} else {
return -1200;
}
}
// initial placement of nodes prevents overlaps
var xOffset = 10000,
yOffset = -10000,
central_x = width / 2,
central_y = height / 2;
data_nodes.forEach(function(d, i) {
if (i != 0) {
connected = isConnectedToOtherThanMain(d);
data_nodes[i].x = connected ? central_x + xOffset : central_x - xOffset;
data_nodes[i].y = connected ? central_y + yOffset : central_y - yOffset;
}
else {data_nodes[i].x = central_x; data_nodes[i].y = central_y;}})
force = d3.layout.force()
.nodes(data_nodes)
.links(data_links)
.charge(function (d, i) {
return chargeForNode(d, i)
})
.friction(0.6) // 0.6
.gravity(0.4) // 0.6
.size([width, height])
.start() //initialise alpha
.stop();
log.update(force.alpha());
call_links_data = data_links.filter(function(d) {
return (d.inc_calls + d.out_calls > 0)});
text_links_data = data_links.filter(function(d) {
return (d.inc_texts + d.out_texts > 0)});
//UPDATE
callLink = svg.selectAll(".call-line")
.data(call_links_data)
//ENTER
callLink.enter().append("line")
.attr('class', 'call-line');
//EXIT
callLink.exit().remove;
//UPDATE
textLink = svg.selectAll(".text-line")
.data(text_links_data)
//ENTER
textLink.enter().append("line")
.attr('class', 'text-line');
//EXIT
textLink.exit().remove;
//UPDATE
node = svg.selectAll(".node")
.data(data_nodes)
//CB the g elements are not needed because there is only one element
//in each node...
//ENTER
node.enter().append("g")
.attr("class", "node")
.append("circle")
.attr("r", node_radius)
.style("fill", function (d) {
return (d.index == 0) ? "#ffffff" : d.fill;
})
.style("stroke", function (d) {
return (d.index == 0) ? "#8C8C8C" : "#ffffff";
});
//EXIT
node.exit().remove;
defs = !(defs && defs.length) ? svg.append("defs") : defs;
marker = svg.selectAll('marker')
.data([{refX: 6+7, refY: 2, markerWidth: 6, markerHeight: 4}])
.enter().append("marker")
.attr("id", "arrowhead")
.attr("refX", function (d) { return d.refX })
.attr("refY", function (d) { return d.refY })
.attr("markerWidth", function (d) { return d.markerWidth })
.attr("markerHeight", function (d) { return d.markerHeight })
.attr("orient", "auto")
.append("path")
.attr("d", "M 0,0 V 4 L6,2 Z");
if (text_links_data.length > 0) {
//UPDATE + ENTER
textLink
.style("stroke-width", function stroke(d) {
return text_width(d)
})
.each(function (d, i) {
applyGradient(this, "text", d, i)
});
}
if (call_links_data.length > 0) {
//UPDATE + ENTER
callLink
.style("stroke-width", function stroke(d) {
return call_width(d)
})
.each(function (d, i) {
applyGradient(this, "call", d, i)
});
}
force
.on("tick", tick);
}
d3.select(document).on('click', (function () {
var _disp = d3.dispatch('stop_start')
return function (e) {
if (!_disp.on('stop_start') || _disp.on('stop_start') === force.stop) {
if (!_disp.on('stop_start')) {
_disp.on('stop_start', force.start)
} else {
_disp.on('stop_start', function () {
CreateVisualizationFromData();
force.start()
//force.alpha(0.5)
})
}
} else {
_disp.on('stop_start', force.stop)
}
_disp.stop_start()
}
})())
function drawLegend() {
var node_px = pluck(data_nodes, 'px');
var node_py = pluck(data_nodes, 'py');
var nodeLayoutRight = Math.max(maxArray(node_px));
var nodeLayoutBottom = Math.max(maxArray(node_py));
legend = svg.selectAll('.legend')
.data(recordTypes)
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function (d, i) {
var rect_height = legendRectSize + legendSpacing;
var offset = rect_height * (recordTypes.length-1);
var horz = nodeLayoutRight + 15; /* - 2*legendRectSize; */
var vert = nodeLayoutBottom + (i * rect_height) - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', function (d) {
return d.color
})
.style('stroke', function (d) {
return d.color
});
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing + 3)
.text(function (d) {
return d.text;
})
.style('fill', '#757575');
}
var line_width_factor = 10.0 // width for the widest line
function call_width(d) {
return (d.inc_calls + d.out_calls) / max_interactions * line_width_factor;
}
function text_width(d) {
return (d.inc_texts + d.out_texts) / max_interactions * line_width_factor;
}
function total_width(d) {
return (d.inc_calls + d.out_calls + d.inc_texts + d.out_texts) / max_interactions * line_width_factor + line_diff;
}
function line_perpendicular_shift(d, direction) {
theta = getAngle(d);
theta_perpendicular = theta + (Math.PI / 2) * direction;
lineWidthOfOppositeLine = direction == 1 ? text_width(d) : call_width(d);
shift = lineWidthOfOppositeLine / 2;
delta_x = (shift + line_diff) * Math.cos(theta_perpendicular)
delta_y = (shift + line_diff) * Math.sin(theta_perpendicular)
return [delta_x, delta_y]
}
function line_radius_shift_to_edge(d, which_node) { // which_node = 0 if source, = 1 if target
theta = getAngle(d);
theta = (which_node == 0) ? theta : theta + Math.PI; // reverse angle if target node
radius = (which_node == 0) ? node_radius(d.source) : node_radius(d.target) // d.source and d.target refer directly to the nodes (not indices)
radius -= 2; // add stroke width
delta_x = radius * Math.cos(theta)
delta_y = radius * Math.sin(theta)
return [delta_x, delta_y]
}
function getAngle(d) {
rel_x = d.target.x - d.source.x;
rel_y = d.target.y - d.source.y;
return theta = Math.atan2(rel_y, rel_x);
}

How to hide outer ring in zoomable sunburst

I wanted to hide the outer ring in Sunburst, and I wanted to show it when the user drills down the sunburst. So that I can clearly show the data in outer ring clearly.... Thanks in advance
Here is my code
var width = 700,
height = width,
radius = width / 2,
x = d3.scale.linear().range([0, 2 * Math.PI]),
y = d3.scale.pow().exponent(1.3).domain([0, 1]).range([0, radius]),
padding = 5,
duration = 1000;
color = d3.scale.category20c();
var div = d3.select("#vis");
div.select("svg").remove();
div.select("div").remove();
div.select("p").remove();
div.select("img").remove();
var vis = div.append("svg")
.attr("width", width + padding * 2)
.attr("height", height + (padding * 2) + 2000)
.append("g")
.attr("transform", "translate(" + [radius + padding, radius + padding] + ")");
/*
div.append("p")
.attr("id", "intro")
.text("Click to zoom!");
*/
var partition = d3.layout.partition()
.sort(null)
.value(function(d) { return 5.8 - d.depth; });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, d.y ? y(d.y) : d.y); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
var json = JSON.parse(jsonStr);
var nodes = partition.nodes({children: json});
var path = vis.selectAll("path").data(nodes);
path.enter().append("path")
.attr("id", function(d, i) { return "path-" + i; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", colour)
.attr("title",function(d)
{
return d.depth ? d.desc: "";
})
.on("click", nodeClicked);
var text = vis.selectAll("text").data(nodes);
var textEnter = text.enter().append("text")
.style("fill-opacity", 1)
.style("fill", function(d)
{
return brightness(d3.rgb(colour(d))) < 125 ? "#eee" : "#000";
})
.attr("text-anchor", function(d)
{
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
})
.attr("dy", ".2em")
.attr("transform", function(d)
{
var multiline = (d.name || "").split(" ").length > 1,
angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -0.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
})
.attr("title",function(d)
{
return d.depth ? d.desc: "";
})
.on("click", showModelDetailsForCriteria);
textEnter.append("tspan")
.attr("x", 0)
.text(function(d) { return d.depth ? d.name.split(" ")[0] : ""; });
textEnter.append("tspan")
.attr("x", 0)
.attr("dy", "1em")
.text(function(d) { return d.depth ? d.name.split(" ")[1] || "" : ""; });
function nodeClicked(d)
{
path.transition()
.duration(duration)
.attrTween("d", arcTween(d));
// Somewhat of a hack as we rely on arcTween updating the scales.
text.style("visibility", function(e)
{
return isParentOf(d, e) ? null : d3.select(this).style("visibility");
})
.transition()
.duration(duration)
.attrTween("text-anchor", function(d)
{
return function()
{
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
};
})
.attrTween("transform", function(d)
{
var multiline = (d.name || "").split(" ").length > 1;
return function()
{
var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -0.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) +
")rotate(" + (angle > 90 ? -180 : 0) + ")";
};
})
.style("fill-opacity", function(e)
{
return isParentOf(d, e) ? 1 : 1e-6;
})
.each("end", function(e)
{
d3.select(this).style("visibility", isParentOf(d, e) ? null : "hidden");
});
}
function isParentOf(p, c)
{
if (p === c) return true;
if (p.children)
{
return p.children.some(function(d)
{
return isParentOf(d, c);
});
}
return false;
}
function colour(d)
{
var nameStr = d.name;
if (d.name == 'Danger')
{
return "red";
}
else if (d.name == 'Stable')
{
return "green";
}
else if ((d.name !== undefined) && (nameStr.indexOf("Need Ana") >= 0))
{
return "orange";
}
else if ((d.name !== undefined) && (nameStr.indexOf("Not Mon") >= 0))
{
return "grey";
}
else
{
if (!d.parent)
{
return "#fff";
}
if (!useRAG)
{
if ((d.data.color !== null) && (d.data.color !== undefined) &&
((d.data.color == "red")||(d.data.color == "green")||(d.data.color == "amber")||(d.data.color == "grey")))
{
return d.data.color;
}
return color((d.children ? d : d.parent).name);
}
if (d.children)
{
var colours = d.children.map(colour);
var childCount = d.children.length;
var hueAddition = 10;
for (var index = 0; index < childCount; index++ )
{
hueAddition += (d3.hsl(colours[index])).h;
}
return d3.hsl((hueAddition) / childCount, (d3.hsl(colours[0])).s * 1.2,
(d3.hsl(colours[0])).l * 1.2);
}
return d.colour || "#fff";
}
}
// Interpolate the scales!
function arcTween(d)
{
var my = maxY(d),
xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, my]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d)
{
return function(t)
{
x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d);
};
};
}
function maxY(d)
{
return d.children ? Math.max.apply(Math, d.children.map(maxY)) : d.y + d.dy;
}
// http://www.w3.org/WAI/ER/WD-AERT/#color-contrast
function brightness(rgb)
{
return rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114;
}
if (top != self) top.location.replace(location);
function showModelDetailsForCriteria(d)
{
var criterions = d.filter.split(",");
var confirmationMessage = "Do you want to view the model list for - ";
var startindex = 0;
if (criterions[startindex] !== null && criterions[startindex] !== undefined)
{
var statusInt = parseInt(criterions[startindex]);
var statusString = "Not Monitered";
if (statusInt === 0)
{
statusString = "Stable";
}
else if (statusInt == 1)
{
statusString = "Need Analysis";
}
else if (statusInt == 2)
{
statusString = "Danger";
}
confirmationMessage += "<BR>Health Status = " + statusString;
startindex++;
}
if (criterions[startindex] !== null && criterions[startindex] !== undefined)
{
confirmationMessage += "<BR>Model Owner = " + criterions[startindex];
startindex++;
}
if (criterions[startindex] !== null && criterions[startindex] !== undefined)
{
confirmationMessage += "<BR>Biz Application = " + criterions[startindex];
startindex++;
}
if (criterions[startindex] !== null && criterions[startindex] !== undefined)
{
confirmationMessage += "<BR>Class of Model = " + criterions[startindex];
startindex++;
}
if (criterions[startindex] !== null && criterions[startindex] !== undefined)
{
confirmationMessage += "<BR>Model Name = " + criterions[startindex];
startindex++;
}
if (criterions[startindex] !== null && criterions[startindex] !== undefined)
{
confirmationMessage += "<BR>Product Type = " + criterions[startindex];
startindex++;
}
app.confirm(confirmationMessage, false,
function()
{
nodeClicked(d);
},
function ()
{
//nodeClicked(d);
});
}
Look at this piece of code, you should be able to add the depth for each ring. You should know the max depth of the layout and that gives you your outer ring.
var path = vis.selectAll("path").data(nodes);
path.enter().append("path")
.attr("id", function(d, i) { return "path-" + i; })
.attr("class", function(d) { return "ring_" + d.depth; }) <--- add this line
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", colour)
.attr("title",function(d)
{
return d.depth ? d.desc: "";
})
.on("click", nodeClicked);
Just in case you find some useful ideas here: http://protembla.com/wheel.html some unfinished viz I played with when I was just learning d3, so quality is not great!

Categories

Resources