I am fairly new to using d3, but what I am trying to do is make a chord diagram of some site traffic, and I am trying to make it interactive by changing the color of the paths when a user clicks on the group for a certain site.here is the style and script section of my code:
<style type="text/css">
.group text {
font: 11px sans-serif;
pointer-events: none;
}
#circle circle {
fill: none;
pointer-events: all;
}
.group path {
stroke: #000;
fill-opacity: .5;
}
path.chord {
stroke-width: .75;
fill-opacity: .75;
}
#circle:hover path.fade {
display: none;
}
</style>
</head>
<body>
<script type="text/javascript">
// Chart dimensions.
var w = 600,
h = 700,
r1 = Math.min(w, h) / 2 - 4,
r0 = r1 - 20,
format = d3.format(",.3r");
// Square matrices, asynchronously loaded; credits is the transpose of sitename.
var sitename = [];
// The chord layout, for computing the angles of chords and groups.
var layout = d3.layout.chord()
.sortGroups(d3.descending)
.sortSubgroups(d3.descending)
.sortChords(d3.descending)
.padding(.04);
// The color scale, for different categories of "worrisome" risk.
var fill = d3.scale.ordinal();
// The arc generator, for the groups.
var arc = d3.svg.arc()
.innerRadius(r0)
.outerRadius(r1);
// The chord generator (quadratic Bézier), for the chords.
var chord = d3.svg.chord()
.radius(r0);
// Add an SVG element for each diagram, and translate the origin to the center.
var svg = d3.select("body").selectAll("div")
.data([sitename])
.enter().append("div")
.style("display", "inline-block")
.style("width", w + "px")
.style("height", h + "px")
.append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
// Load our data file…
d3.csv("data2.csv", function(data) {
var uniqueids = {},
array = [],
n = 0;
// Compute a unique id for each site.
data.forEach(function(d) {
d.siteid1 = uniqueIDMaker(d.siteid1);
d.siteid2 = uniqueIDMaker(d.siteid2);
d.valueOf = value; // convert object to number implicitly
});
// Initialize a square matrix of sitename and users
for (var i = 0; i < n; i++) {
sitename[i] = [];
for (var j = 0; j < n; j++) {
sitename[i][j] = 0;
}
}
// Populate the matrices, and stash a map from id to site.
data.forEach(function(d) {
sitename[d.siteid1.id][d.siteid2.id] = d;
array[d.siteid1.id] = d.siteid1;
array[d.siteid2.id] = d.siteid2;
});
// For each diagram…
svg.each(function(matrix, j) {
var svg = d3.select(this);
// Compute the chord layout.
layout.matrix(matrix);
// Add chords.
svg.selectAll(".chord")
.data(layout.chords)
.enter().append("svg:path")
.attr("class", "chord")
.style("fill", function(d) { return fill(d.source.value); })
.style("stroke", function(d) { return d3.rgb(fill(d.source.value)).darker(); })
.attr("d", chord)
.on("dblclick",function(){
d3.select(this)
.style("fill","red")
.style("stroke","yellow")
})
.append("svg:title")
.text(function(d) { return "site " + d.source.value.siteid2.name + " and site " + d.source.value.siteid1.name + " have " + format(d.source.value) + " common users"; })
;
// Add groups.
var g = svg.selectAll("g.group")
.data(layout.groups)
.enter().append("svg:g")
.attr("class", "group");
// Add the group arc.
g.append("svg:path")
.style("fill", function(d) { return fill(array[d.index]); })
.attr("id", function(d, i) { return "group" + d.index + "-" + j; })
.attr("d", arc)
.append("svg:title")
.text(function(d) { return "site " + array[d.index].name + " has " + format(d.value) + "users"; });
g.append("svg:text")
.attr("x", 6)
.attr("dy", 15)
.filter(function(d) { return d.value > 110; })
.append("svg:textPath")
.attr("xlink:href", function(d) { return "#group" + d.index + "-" + j; })
.text(function(d) { return array[d.index].name; });
});
function uniqueIDMaker(d) {
return uniqueids[d] || (uniqueids[d] = {
name: d,
id: n++
});
}
function value() {
return +this.count;
}});
</script>
any help would be greatly appreciated
http://jsfiddle.net/Rw3aK/2/ is a jsFiddle of the script, not sure how to make it read from a file, so here is the contents of data2.csv:
siteid1,siteid2,count,pubid1,pubid2
8,94,11132,57141,57141
201,94,10035,57141,57141
201,8,9873,57141,57141
0,94,8488,45020,57141
0,8,8258,45020,57141
0,201,7644,45020,57141
0,1,6973,45020,45020
94,1,5719,57141,45020
8,1,5670,57141,45020
1,201,5410,57141,45020
I forked your jsfiddle and converted your CSV data to JSON, now in a variable data: http://jsfiddle.net/mdml/K6FHW/.
I also modified your code slightly so that when you click on a group, all outgoing chords are highlighted red. When you click on a group again, the chords change back to their original color. Here're the relevant snippets:
When adding the chords, label each chord with a class according to the chord's source
svg.selectAll(".chord")
.data(layout.chords)
.enter().append("svg:path")
.attr("class", function(d){ return "chord chord-" + d.source.index; })
...
When clicking a group, check if that group's chords are highlighted.
If so, fill the chords with their default color
If not, fill the chords red
Then record whether or not the group's chords are highlighted in a variable d.chordHighlighted
g.append("svg:path")
...
.attr("id", function (d, i) {
return "group" + d.index + "-" + j;
})
...
.on("click", function(d){
if (d.chordHighlighted)
d3.selectAll(".chord-" + d.index)
.style("fill", fill(d.value));
else{
d3.selectAll(".chord-" + d.index)
.style("fill", "red");
}
d.chordHighlighted = d.chordHighlighted ? false : true;
})
Related
I have generated a Sankey diagram as shown above using d3 code (.js file) mentioned below [the .html and .css files are not quoted here].
Now I want the Sankey diagram to look like below with node "Technology" and "Strategy" appearing apart as a fourth level:
What are the necessary changes to be done in the D3 code?
var svg = d3.select("svg").attr("style", "outline: thin solid grey;"),
width = +svg.attr("width"),
height = +svg.attr("height");
var formatNumber = d3.format(",.0f"),
format = function(d) { return formatNumber(d) + " TWh"; },
color = d3.scaleOrdinal(d3.schemeCategory10);
var school = {"nodes": [
{"name":"High School"}, // 0
{"name":"Community College"}, // 1
{"name":"Finance"}, // 2
{"name":"Accounting"}, // 3
{"name":"ITS"}, // 4
{"name":"Marketing"}, // 5
{"name":"Analytics"}, // 6
{"name":"Security"}, // 7
{"name":"Consulting"}, // 8
{"name":"Banking"}, // 9
{"name":"Internal"}, // 10
{"name":"Securities"}, // 11
{"name":"Public"}, // 12
{"name":"Audting"}, // 13
{"name":"Internal"}, // 14
{"name":"Retail"}, // 15
{"name":"Technology"}, // 16
{"name":"Strategy"} // 17
],
"links":[
// FirstYear
{"source":0,"target":2,"value":33},
{"source":0,"target":3,"value":42},
{"source":0,"target":4,"value":74},
{"source":0,"target":5,"value":60},
// Community College
{"source":1,"target":2,"value":7},
{"source":1,"target":3,"value":13},
{"source":1,"target":4,"value":11},
{"source":1,"target":5,"value":9},
// Finance
{"source":2,"target":9,"value":16},
{"source":2,"target":10,"value":14},
{"source":2,"target":11,"value":10},
// Accounting
{"source":3,"target":12,"value":20},
{"source":3,"target":13,"value":12},
{"source":3,"target":7,"value":8},
{"source":3,"target":14,"value":15},
// Marketing
{"source":5,"target":6,"value":30},
{"source":5,"target":15,"value":39},
// ITS
{"source":4,"target":6,"value":40},
{"source":4,"target":7,"value":20},
{"source":4,"target":12,"value":6},
{"source":4,"target":8,"value":19},
// ITS Consulting
{"source":8,"target":16,"value":10},
{"source":8,"target":17,"value":9},
]};
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.extent([[1, 1], [width - 1, height - 6]]);
var link = svg.append("g")
.attr("class", "links")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-opacity", 0.2)
.selectAll("path");
var node = svg.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.selectAll("g");
sankey(school);
link = link
.data(school.links)
.enter().append("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function(d) { return Math.max(1, d.width); });
// link hover values
link.append("title")
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
node = node
.data(school.nodes)
.enter().append("g");
node.append("rect")
.attr("x", function(d) { return d.x0; })
.attr("y", function(d) { return d.y0; })
.attr("height", function(d) { return d.y1 - d.y0; })
.attr("width", function(d) { return d.x1 - d.x0; })
.attr("fill", function(d) { return color(d.name.replace(/ .*/, "")); })
.attr("stroke", "#000");
node.append("text")
.attr("x", function(d) { return d.x0 - 6; })
.attr("y", function(d) { return (d.y1 + d.y0) / 2; })
.attr("dy", "0.35em")
.attr("text-anchor", "end")
.text(function(d) { return d.name; })
.filter(function(d) { return d.x0 < width / 2; })
.attr("x", function(d) { return d.x1 + 6; })
.attr("text-anchor", "start");
svg.append("text")
.attr("x", 10)
.attr("y", 30)
.attr("class", "graphTitle")
.text("STUDENT CHOICES");
svg.append("text")
.attr("x", width - 80)
.attr("y", height - 10)
.attr("class", "footnote")
.text("data is fictitious");
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-sankey#0"></script>
<svg width="600" height="500"></svg>
The alignment of d3.sankey can be configured using .nodeAlign(), and for your requirement, you will need .nodeAlign(d3.sankeyLeft)
If it is not specified, the alignment defaults to d3.sankeyJustify, which is what you are currently seeing.
https://github.com/d3/d3-sankey#alignments
For those who are looking for a quick ans. Have a nice day!
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.nodeAlign(function (node) {
// you may specify the horizatonal location here
// i.e. if your data structure contain node.horizontalPosition (an integer)
// you can return node.horizontalPosition
return node.depth; //align left
})
.extent([[1, 1], [width - 1, height - 6]]);
I have seen lots of great demos for live graphing of data using D3.
http://bl.ocks.org/simenbrekken/6634070 is one I like. However, all of the examples I have seen use random generated values. I want to graph live data, and display the most recent values as an updating numeric display. I use a python script which writes data from sensor readings to csv files. The csv is 3 values on each line: unixtime,sensor1_value,sensor2_value. Every 5 seconds there is a new line of data added to a ring buffer file which has 720 lines of data. When the web page is displayed I want to read the 720 lines in the buffer file then update the graph with each new value which is written onto the end of the file. I could also create a file with just the new line of csv every 5 seconds so that the update was performed by always reading a file with just 1 line of csv rather than manipulating the entire buffer.
Does anyone know of an example, or the right code to achieve this?
The code for the above cited example is:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.graph .axis {
stroke-width: 1;
}
.graph .axis .tick line {
stroke: black;
}
.graph .axis .tick text {
fill: black;
font-size: 0.7em;
}
.graph .axis .domain {
fill: none;
stroke: black;
}
.graph .group {
fill: none;
stroke: black;
stroke-width: 1.5;
}
</style>
</head>
<body>
<div class="graph"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var limit = 60 * 1,
duration = 750,
now = new Date(Date.now() - duration)
var width = 500,
height = 200
var groups = {
current: {
value: 0,
color: 'orange',
data: d3.range(limit).map(function() {
return 0
})
},
target: {
value: 0,
color: 'green',
data: d3.range(limit).map(function() {
return 0
})
},
output: {
value: 0,
color: 'grey',
data: d3.range(limit).map(function() {
return 0
})
}
}
var x = d3.time.scale()
.domain([now - (limit - 2), now - duration])
.range([0, width])
var y = d3.scale.linear()
.domain([0, 100])
.range([height, 0])
var line = d3.svg.line()
.interpolate('basis')
.x(function(d, i) {
return x(now - (limit - 1 - i) * duration)
})
.y(function(d) {
return y(d)
})
var svg = d3.select('.graph').append('svg')
.attr('class', 'chart')
.attr('width', width)
.attr('height', height + 50)
var axis = svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(x.axis = d3.svg.axis().scale(x).orient('bottom'))
var paths = svg.append('g')
for (var name in groups) {
var group = groups[name]
group.path = paths.append('path')
.data([group.data])
.attr('class', name + ' group')
.style('stroke', group.color)
}
function tick() {
now = new Date()
// Add new values
for (var name in groups) {
var group = groups[name]
//group.data.push(group.value) // Real values arrive at irregular intervals
group.data.push(20 + Math.random() * 100)
group.path.attr('d', line)
}
// Shift domain
x.domain([now - (limit - 2) * duration, now - duration])
// Slide x-axis left
axis.transition()
.duration(duration)
.ease('linear')
.call(x.axis)
// Slide paths left
paths.attr('transform', null)
.transition()
.duration(duration)
.ease('linear')
.attr('transform', 'translate(' + x(now - (limit - 1) * duration) + ')')
.each('end', tick)
// Remove oldest data point from each group
for (var name in groups) {
var group = groups[name]
group.data.shift()
}
}
tick()
</script>
</body>
</html>
Whereas the code I use for creating a static graph of one of the values (o2) from my csv versus a line:
<!DOCTYPE html>
<meta charset="utf-8">
<style> /* set the CSS */
body { font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 900 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Set the ranges
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(10);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(10);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.time); })
.y(function(d) { return y(d.o2); });
// Adds the svg canvas
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Get the data
d3.csv("./data/buffer.txt", function(error, data) {
data.forEach(function(d) {
d.time = +d.time;
d.o2 = +d.o2;
console.log(d.time);
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.time; }));
y.domain([0, d3.max(data, function(d) { return d.o2; })]);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
</script>
</body>
From the blog: http://bl.ocks.org/lgersman/5370827
I want to understand about how connection line between circles are implemented. I tried to go through it but it just went over my head. There's not much documentation about the example I found on blog. I guess other new users like me would be facing the same challenge.
If any one can explain the below sample code, that would be great!
Here's the code I minified for my requirement:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>d3.js selection frame example</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.js"></script>
<link rel="stylesheet" href="<%=request.getContextPath()%>/app.css" />
<script>
window.onload = function ()
{
var radius = 40;
window.states = [
{x: 43, y: 67, label: "first", transitions: []},
{x: 340, y: 150, label: "second", transitions: []},
{x: 200, y: 250, label: "third", transitions: []}
];
window.svg = d3.select('body')
.append("svg")
.attr("width", "960px")
.attr("height", "500px");
// 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', 4)
.attr('markerWidth', 8)
.attr('markerHeight', 8)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('class', 'end-arrow')
;
// line displayed when dragging new nodes
var drag_line = svg.append('svg:path')
.attr({
'class': 'dragline hidden',
'd': 'M0,0L0,0'
})
;
**// NEED EXPLANATION FROM HERE**
var gTransitions = svg.append('g').selectAll("path.transition");
var gStates = svg.append("g").selectAll("g.state");
var transitions = function () {
return states.reduce(function (initial, state) {
return initial.concat(
state.transitions.map(function (transition) {
return {source: state, transition: transition};
})
);
}, []);
};
var transformTransitionEndpoints = function (d, i) {
var endPoints = d.endPoints();
var point = [
d.type == 'start' ? endPoints[0].x : endPoints[1].x,
d.type == 'start' ? endPoints[0].y : endPoints[1].y
];
return "translate(" + point + ")";
}
var transformTransitionPoints = function (d, i) {
return "translate(" + [d.x, d.y] + ")";
}
var computeTransitionPath = (function () {
var line = d3.svg.line()
.x(function (d, i) {
return d.x;
})
.y(function (d, i) {
return d.y;
})
.interpolate("cardinal");
return function (d) {
var source = d.source,
target = d.transition.points.length && d.transition.points[0] || d.transition.target,
deltaX = target.x - source.x,
deltaY = target.y - source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist,
sourcePadding = radius + 4, //d.left ? 17 : 12,
sourceX = source.x + (sourcePadding * normX),
sourceY = source.y + (sourcePadding * normY);
source = d.transition.points.length && d.transition.points[ d.transition.points.length - 1] || d.source;
target = d.transition.target;
deltaX = target.x - source.x;
deltaY = target.y - source.y;
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
normX = deltaX / dist;
normY = deltaY / dist;
targetPadding = radius + 8;//d.right ? 17 : 12,
targetX = target.x - (targetPadding * normX);
targetY = target.y - (targetPadding * normY);
var points =
[{x: sourceX, y: sourceY}].concat(
d.transition.points,
[{x: targetX, y: targetY}]
)
;
var l = line(points);
return l;
};
})();
var dragPoint = d3.behavior.drag()
.on("drag", function (d, i) {
console.log("transitionmidpoint drag");
var gTransitionPoint = d3.select(this);
gTransitionPoint.attr("transform", function (d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
return "translate(" + [d.x, d.y] + ")"
});
// refresh transition path
gTransitions.selectAll("path").attr('d', computeTransitionPath);
// refresh transition endpoints
gTransitions.selectAll("circle.endpoint").attr({
transform: transformTransitionEndpoints
});
// refresh transition points
gTransitions.selectAll("circle.point").attr({
transform: transformTransitionPoints
});
d3.event.sourceEvent.stopPropagation();
});
var renderTransitionMidPoints = function (gTransition) {
gTransition.each(function (transition) {
var transitionPoints = d3.select(this).selectAll('circle.point').data(transition.transition.points, function (d) {
return transition.transition.points.indexOf(d);
});
transitionPoints.enter().append("circle")
.attr({
'class': 'point',
r: 4,
transform: transformTransitionPoints
})
.call(dragPoint);
transitionPoints.exit().remove();
});
};
var renderTransitionPoints = function (gTransition) {
gTransition.each(function (d) {
var endPoints = function () {
var source = d.source,
target = d.transition.points.length && d.transition.points[0] || d.transition.target,
deltaX = target.x - source.x,
deltaY = target.y - source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist,
sourceX = source.x + (radius * normX),
sourceY = source.y + (radius * normY);
source = d.transition.points.length && d.transition.points[ d.transition.points.length - 1] || d.source;
target = d.transition.target;
deltaX = target.x - source.x;
deltaY = target.y - source.y;
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
normX = deltaX / dist;
normY = deltaY / dist;
targetPadding = radius + 8;//d.right ? 17 : 12,
targetX = target.x - (radius * normX);
targetY = target.y - (radius * normY);
return [{x: sourceX, y: sourceY}, {x: targetX, y: targetY}];
};
var transitionEndpoints = d3.select(this).selectAll('circle.endpoint').data([
{endPoints: endPoints, type: 'start'},
{endPoints: endPoints, type: 'end'}
]);
transitionEndpoints.enter().append("circle")
.attr({
'class': function (d) {
return 'endpoint ' + d.type;
},
r: 4,
transform: transformTransitionEndpoints
})
;
transitionEndpoints.exit().remove();
});
};
var renderTransitions = function () {
gTransition = gTransitions.enter().append('g')
.attr({
'class': 'transition'
})
gTransition.append('path')
.attr({
d: computeTransitionPath,
class: 'background'
})
.on({
dblclick: function (d, i) {
gTransition = d3.select(d3.event.target.parentElement);
if (d3.event.ctrlKey) {
var p = d3.mouse(this);
gTransition.classed('selected', true);
d.transition.points.push({x: p[0], y: p[1]});
renderTransitionMidPoints(gTransition, d);
gTransition.selectAll('path').attr({
d: computeTransitionPath
});
} else {
var gTransition = d3.select(d3.event.target.parentElement),
transition = gTransition.datum(),
index = transition.source.transitions.indexOf(transition.transition);
transition.source.transitions.splice(index, 1)
gTransition.remove();
d3.event.stopPropagation();
}
}
});
gTransition.append('path')
.attr({
d: computeTransitionPath,
class: 'foreground'
});
renderTransitionPoints(gTransition);
renderTransitionMidPoints(gTransition);
gTransitions.exit().remove();
};
var renderStates = function () {
var gState = gStates.enter()
.append("g")
.attr({
"transform": function (d) {
return "translate(" + [d.x, d.y] + ")";
},
'class': 'state'
})
.call(drag);
gState.append("circle")
.attr({
r: radius + 4,
class: 'outer'
})
.on({
mousedown: function (d) {
console.log("state circle outer mousedown");
startState = d, endState = undefined;
// reposition drag line
drag_line
.style('marker-end', 'url(#end-arrow)')
.classed('hidden', false)
.attr('d', 'M' + d.x + ',' + d.y + 'L' + d.x + ',' + d.y);
// force element to be an top
this.parentNode.parentNode.appendChild(this.parentNode);
//d3.event.stopPropagation();
},
mouseover: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", true);
},
mouseout: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", false);
//$( this).popover( "hide");
}
});
gState.append("circle")
.attr({
r: radius,
class: 'inner'
})
.on({
mouseover: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", true);
},
mouseout: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", false);
},
});
};
var startState, endState;
var drag = d3.behavior.drag()
.on("drag", function (d, i) {
console.log("drag");
if (startState) {
return;
}
var selection = d3.selectAll('.selected');
// if dragged state is not in current selection
// mark it selected and deselect all others
if (selection[0].indexOf(this) == -1) {
selection.classed("selected", false);
selection = d3.select(this);
selection.classed("selected", true);
}
// move states
selection.attr("transform", function (d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
return "translate(" + [d.x, d.y] + ")"
});
// move transistion points of each transition
// where transition target is also in selection
var selectedStates = d3.selectAll('g.state.selected').data();
var affectedTransitions = selectedStates.reduce(function (array, state) {
return array.concat(state.transitions);
}, [])
.filter(function (transition) {
return selectedStates.indexOf(transition.target) != -1;
});
affectedTransitions.forEach(function (transition) {
for (var i = transition.points.length - 1; i >= 0; i--) {
var point = transition.points[i];
point.x += d3.event.dx;
point.y += d3.event.dy;
}
});
// reappend dragged element as last
// so that its stays on top
selection.each(function () {
this.parentNode.appendChild(this);
});
// refresh transition path
gTransitions.selectAll("path").attr('d', computeTransitionPath);
// refresh transition endpoints
gTransitions.selectAll("circle.endpoint").attr({
transform: transformTransitionEndpoints
});
// refresh transition points
gTransitions.selectAll("circle.point").attr({
transform: transformTransitionPoints
});
d3.event.sourceEvent.stopPropagation();
})
.on("dragend", function (d) {
console.log("dragend");
// needed by FF
drag_line.classed('hidden', true)
.style('marker-end', '');
if (startState && endState) {
startState.transitions.push({label: "transition label 1", points: [], target: endState});
update();
}
startState = undefined;
d3.event.sourceEvent.stopPropagation();
});
svg.on({
mousedown: function () {
console.log("mousedown", d3.event.target);
if (d3.event.target.tagName == 'svg') {
if (!d3.event.ctrlKey) {
d3.selectAll('g.selected').classed("selected", false);
}
var p = d3.mouse(this);
}
},
mousemove: function () {
var p = d3.mouse(this);
// update drag line
drag_line.attr('d', 'M' + startState.x + ',' + startState.y + 'L' + p[0] + ',' + p[1]);
var state = d3.select('g.state .inner.hover');
endState = (!state.empty() && state.data()[0]) || undefined;
},
mouseup: function () {
console.log("mouseup");
// remove temporary selection marker class
d3.selectAll('g.state.selection').classed("selection", false);
},
mouseout: function ()
{
if (!d3.event.relatedTarget || d3.event.relatedTarget.tagName == 'HTML') {
// remove temporary selection marker class
d3.selectAll('g.state.selection').classed("selection", false);
}
}
});
update();
function update() {
gStates = gStates.data(states, function (d) {
return states.indexOf(d);
});
renderStates();
var _transitions = transitions();
gTransitions = gTransitions.data(_transitions, function (d) {
return _transitions.indexOf(d);
});
renderTransitions();
}
;
};
</script>
</head>
<body>
</body>
</html>
Css file:
rect.selection {
stroke : gray;
stroke-dasharray: 2px;
stroke-opacity : 0.5;
fill : transparent;
}
g.state circle {
stroke : gray;
}
g.state circle.inner {
fill : white;
transition : fill 0.5s;
cursor : move;
}
g.state circle.inner.hover,
g.state circle.outer.hover {
fill : aliceblue;
}
g.state circle.outer.hover {
stroke-width : 1px;
}
g.state circle.outer {
stroke-width : 0px;
stroke-dasharray: 2px;
stroke-color : gray;
fill : transparent;
transition : all 0.5s;
cursor : pointer;
}
g.state.selected circle.outer {
stroke-width : 1px;
}
g.state text {
font : 12px sans-serif;
font-weight : bold;
pointer-events : none;
}
g.transition path,
path.dragline {
fill : none;
stroke : gray;
stroke-width: 1px;
}
g.transition path.foreground {
marker-end : url(#end-arrow);
}
g.transition.hover path.background {
stroke-dasharray: none;
stroke : aliceblue;
stroke-opacity : 1.0;
transition : all 0.5s;
}
g.transition path.background {
stroke-dasharray: none;
stroke-width: 8px;
stroke : transparent;
}
g.transition.selected path.foreground {
stroke-dasharray: 2px;
stroke-color : gray;
}
g.transition path {
cursor : default;
}
.end-arrow {
fill : gray;
stroke-width : 1px;
}
g.transition circle.endpoint {
display : none;
fill : none;
cursor : pointer;
stroke : gray;
stroke-dasharray: 2px;
}
g.transition circle.point {
display : none;
fill : aliceblue;
cursor : move;
stroke : gray;
}
g.transition.selected circle.endpoint,
g.transition.selected circle.point {
display : inline;
transition : all 0.5s;
}
g.transition:not( .selected).hover *,
path.dragline {
display : inline;
}
g.transition:not( .selected).hover {
transition : all 0.5s;
}
path.dragline {
pointer-events: none;
stroke-opacity : 0.5;
stroke-dasharray: 2px;
}
path.dragline.hidden {
stroke-width: 0;
}
/* disable text selection */
svg *::selection {
background : transparent;
}
svg *::-moz-selection {
background:transparent;
}
svg *::-webkit-selection {
background:transparent;
}
I understand the basics about d3 such as appending new circle and drag behaviors, but mainly the transitions part used to draw and connect lines to circle is holding me back.
There is quite a lot in there and actually, and looking at it it's not actually using typical css transitions as you might expect. I'll summarize the interesting parts and expand if you need. The interesting section is the following:
var dragPoint = d3.behavior.drag()
.on("drag", function( d, i) {
console.log( "transitionmidpoint drag");
var gTransitionPoint = d3.select( this);
gTransitionPoint.attr( "transform", function( d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
return "translate(" + [ d.x,d.y ] + ")"
});
// refresh transition path
gTransitions.selectAll( "path").attr( 'd', computeTransitionPath);
// refresh transition endpoints
gTransitions.selectAll( "circle.endpoint").attr({
transform : transformTransitionEndpoints
});
// refresh transition points
gTransitions.selectAll( "circle.point").attr({
transform : transformTransitionPoints
});
d3.event.sourceEvent.stopPropagation();
});
This is where all the hard work is taking place. This code basically says, whenever the drag event occurs (e.g. you move a circle) execute the code within this function.
You can see this splits into sections:
Move the point that was clicked by a d.x and d.y which is the amount dragged from the previous event. This is done using the translate transform.
Change the path, this is done by updating the d parameter, which is how you specify the shape of an SVG path. The code that calculates the path is within the comuteTransitionPath function.
Update the circle.endpoint - This is a hidden point on the line
Update the circle.point - These are points on the line that control the curve, they are hidden by default.
I've seen this question and many other examples, but it isn't helping. I'm trying the last example on this page and after 5 seconds, I want the curved path that is being drawn, to completely disappear and 5 more seconds later, I want a new path to be created.
I've tried the below code, but although the entire svg element itself is removed, when I use appendGraph() to created the svg and the path again, the same old path re-appears. How can I ensure that the old path is completely removed and that the tick function also does not get called when the graph is removed?
The fiddle is here: http://jsfiddle.net/nav9/5uygqj9v/
And the code is:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<style>
svg {
font: 10px sans-serif;
}
.noselect {
/* these are to disable text selection */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
opacity: 0.5;
shape-rendering: crispEdges;
}
rect.zoom {
stroke: steelblue;
fill-opacity: 0.3;
}
#placeholder {margin: 10px 5px 15px 70px;}
</style>
<script type="text/javascript" src="http://d3js.org/d3.v3.js"></script>
</head>
<body>
<div id="placeholder" ></div>
<script>
//---------globals
var timer = null, interval = 500, value = 0;
var value1 = 0;
var n = 143, duration = interval, now = new Date(Date.now() - duration), count = 0, data = d3.range(n).map(function() { return 0; });
var margin = {top: 20, right: 40, bottom: 50, left: 60}, width = 580 - margin.right, height = 420 - margin.top - margin.bottom;
var x = d3.time.scale().domain([now - (n - 2) * duration, now - duration]).range([0, width]);
var y = d3.scale.linear().domain([-1, 1]).range([height, 0]);
var line = d3.svg.line().interpolate("basis")
.x(function(d, i) { return x(now - (n - 1 - i) * duration); })
.y(function(d, i) { return y(d); });
var svg, path, yaxis, axis;
//--------program starts
appendGraph();
tick();
value1 = 0;
setTimeout(function() {removeGraph();}, 5000);
setTimeout(function() {addGraphAgain();}, 10000);
//-------------------------------functions -------------------------------
function appendGraph()
{
svg = d3.select("body").select("#placeholder").append("p").append("svg:svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("id", "mainSVG")
.style("margin-left", -margin.left + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
axis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(x.axis = d3.svg.axis().scale(x).orient("bottom"));
yaxis = svg.append("g")
.attr("class", "y axis")
.call(y.axis = d3.svg.axis().scale(y).orient("left"));
path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.data([data])
.attr("id", "line1")
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", "1.5px")
.style("visibility","visible");
}//appendGraph
//TODO: These tick functions could be simplified to handle more lines on the graph
function tick()
{
// push the accumulated count onto the back, and reset the count
value1 = Math.random() * 100;
if (value1 >= 0) {data.push(value1);} else {data.push(0);}//ensure that no NaN or undefined values corrupt the range
// update the domains
now = new Date();
x.domain([now - (n - 2) * duration, now - duration]);
count = 0;
// redraw the lines
svg.select("#line1").attr("d", line).attr("transform", null);
// slide the line left
path.transition().duration(duration).ease("linear").attr("transform", "translate(" + x(now - (n - 1) * duration) + ")").each("end", tick);
y.domain([0, 100]);
y = d3.scale.linear().domain([0, 100]).range([height, 0]);
yaxis.call(y.axis = d3.svg.axis().scale(y).orient("left"));
// pop the old data point off the front
data.shift();
console.log("tick being called");
}
function removeGraph()
{
path.transition().duration(0).each(function() { this.__transition__.active = 0; });//at least this is stopping tick from being called
svg.selectAll("*").remove();
//-------tried these too
// d3.select("#mainSVG").remove("svg");
// d3.select("#line1").remove("path");
// path.remove();
//d3.selectAll("path").attr("d", "Z");
console.log("REMOVED");
}//removeGraph
function addGraphAgain()
{
appendGraph();
tick();
value1 = 0;
console.log("ADDED AGAIN");
}//addGraphAgain
</script>
</body>
</html>
Not an exact answer to this question, but since the reason I asked this was because I wanted to have phases where I wanted to send null inputs to the graph and there seemed no other way to do it other than to remove the line and replace it with a new line.
The trick to handle null or NaN data or missing data in d3.js or to simply not display data for a while is to use defined.
A working example of it here and in the line transition, it's like this:
I supply a random number at if (counter%5==0) ran = null;data.push(ran); and .defined(function(d) { return d != null; }) takes care of the null, by not drawing a line there.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.line {
fill: none;
stroke: #000;
stroke-width: 1.5px;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var n = 40,
random = d3.random.normal(0, .2),
data = d3.range(n).map(random);
var margin = {top: 20, right: 20, bottom: 20, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, n - 1])
.range([0, width]);
var y = d3.scale.linear()
.domain([-1, 1])
.range([height, 0]);
var line = d3.svg.line()
.defined(function(d) { return d != null; })
.x(function(d, i) { return x(i); })
.y(function(d, i) { return y(d); });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + y(0) + ")")
.call(d3.svg.axis().scale(x).orient("bottom"));
svg.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var counter = 0;
tick();
function tick()
{
// push a new data point onto the back
var ran = random();
counter++;
if (counter%5==0) ran = null;
data.push(ran);
// redraw the line, and slide it to the left
path
.attr("d", line)
.attr("transform", null)
.transition()
.duration(500)
.ease("linear")
.attr("transform", "translate(" + x(-1) + ",0)")
.each("end", tick);
// pop the old data point off the front
data.shift();
}
</script>
Unable to add images to bubble layout in D3.js . I am trying to append images to the circles in bubble layout but it doesnt works out . the image is not getting transformed.
I want to have look and feel of this:-
http://www.cloudshapes.co.uk/labs/attention-hungry-cabinet-ministers/
here is the fiddle link for what I have been trying to do:
http://jsfiddle.net/Ankitb/eYGCY/4/
var force = d3.layout.force()
.charge(-300)
.size([w, h])
.nodes(nodes)
.on("tick", tick)
.start();
function tick() {
svg.selectAll("circle")
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
}
var interval = setInterval(function () {
var d = {
x: w / 4 + 2 *( Math.random() - 1),
y: h / 4 + 2 *( Math.random() - 1)
};
var personDot = svg.append("g")
.attr("class", "g-person-dots")
.selectAll("g")
.data([d])
.enter().append("g")
.attr("transform", function (d) { return "translate(" + d.x+ "," + d.y + ")"; });
personDot.append("circle")
.data([d]).attr("r", 40)
//.attr("r", 1e-6)
.attr("cx",0).attr("cy",0)
.transition().style("stroke", "gray").style("fill","white")
.ease(Math.sqrt);
personDot.append("image").data([d])
.attr("xlink:href", "PeopleProfilePicture.jpg")
// .attr("x", function (d, i) { return -mugDiameter / 2 - mugDiameter * (i % 9); })
//.attr("y", function (d, i) { return -mugDiameter / 2 - mugDiameter * (i / 9 | 0); })
.attr("width", 80)
.attr("height", 80)
.attr("transform", function (d) { return "translate(" + -d.x / 10 + "," + -d.y / 10 + ")"; });
if (nodes.push(d) > 10) clearInterval(interval);
else { force.start(); }
}, 30);
The translation of an element is relative to its parent element. That is, by default the element will be in the same position as its parent. Therefore, the translation you need to do does not depend on the dynamic data that you pass in, but only on the dimensions of the image. You need to set transform as follows.
.attr("transform", "translate(-40,-40)");
You may also want to make the background of your images transparent such that you can still see the circle.