Graphx Visualization - apache-spark

I am looking for a way to visualize the graph constructed in Spark's Graphx. As far as I know Graphx doesn't have any visualization methods so I need to export the data from Graphx to another graph library, but I am stuck here. I ran into this website: https://lintool.github.io/warcbase-docs/Spark-Network-Analysis/
but it didn't help. Which library I should use and how to export the graph.

So you can do something like this
Save to gexf (graph interchange format) Code from Manning | Spark GraphX in Action
def toGexf[VD,ED](g:Graph[VD,ED]) : String = {
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<gexf xmlns=\"http://www.gexf.net/1.2draft\" version=\"1.2\">\n" +
" <graph mode=\"static\" defaultedgetype=\"directed\">\n" +
" <nodes>\n" +
g.vertices.map(v => " <node id=\"" + v._1 + "\" label=\"" +
v._2 + "\" />\n").collect.mkString +
" </nodes>\n" +
" <edges>\n" +
g.edges.map(e => " <edge source=\"" + e.srcId +
"\" target=\"" + e.dstId + "\" label=\"" + e.attr +
"\" />\n").collect.mkString +
" </edges>\n" +
" </graph>\n" +
"</gexf>"
}
Use the GEXF plugin in linkurious.js to load the file
Example: http://gregroberts.github.io

you can use either Gephi or d3 from zeppelin. Check D3.js In Action by Elijah Meeks and Spark GraphX in Action by Michael S. Malak
Give it a go as below from zeppelin in scala and js borrowed from grapxInAction:
import org.apache.spark.graphx._
import scala.reflect.ClassTag
def drawGraph[VD:ClassTag,ED:ClassTag](g:Graph[VD,ED]) = {
val u = java.util.UUID.randomUUID
val v = g.vertices.collect.map(_._1)
println("""%html
<div id='a""" + u + """' style='width:960px; height:500px'></div>
<style>
.node circle { fill: gray; }
.node text { font: 10px sans-serif;
text-anchor: middle;
fill: white; }
line.link { stroke: gray;
stroke-width: 1.5px; }
</style>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
.var width = 960, height = 500;
var svg = d3.select("#a""" + u + """").append("svg")
.attr("width", width).attr("height", height);
var nodes = [""" + v.map("{id:" + _ + "}").mkString(",") + """];
var links = [""" + g.edges.collect.map(
e => "{source:nodes[" + v.indexWhere(_ == e.srcId) +
"],target:nodes[" +
v.indexWhere(_ == e.dstId) + "]}").mkString(",") + """];
var link = svg.selectAll(".link").data(links);
link.enter().insert("line", ".node").attr("class", "link");
var node = svg.selectAll(".node").data(nodes);
var nodeEnter = node.enter().append("g").attr("class", "node")
nodeEnter.append("circle").attr("r", 8);
nodeEnter.append("text").attr("dy", "0.35em")
.text(function(d) { return d.id; });
d3.layout.force().linkDistance(50).charge(-200).chargeDistance(300)
.friction(0.95).linkStrength(0.5).size([width, height])
.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}).nodes(nodes).links(links).start();
</script>
""")
}

If you're using grpahFrames, then I've modified the code provided by #Karol Sudol in his answer for GraphFrames:
def drawGraph[vertices:ClassTag,relations:ClassTag](g:GraphFrame) = {
val u = java.util.UUID.randomUUID
val v = g.vertices.select("id")
val vertexes: Array[String] = g.vertices.select("id").rdd.map(x => x(0).toString).collect()
val edges: Array[Array[String]] = g.edges.select("src", "dst").rdd.map(r => Array(r(0).toString, r(1).toString)).collect()
val edgeCreation = edges.map{ edgeArray =>
"{source:nodes["+ vertexes.indexOf(edgeArray(0).trim()) +"],target:nodes["+ vertexes.indexOf(edgeArray(1).trim())+"]}"
}
println("""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Graph</title>
<div id='a""" + u + """' style='width:960px; height:500px'></div>
<style>
.node circle { fill: gray; }
.node text { font: 10px sans-serif;
text-anchor: middle;
fill: white; }
line.link { stroke: gray;
stroke-width: 1.5px; }
</style>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<script>
var width = 960, height = 500;
var svg = d3.select("#a""" + u + """").append("svg")
.attr("width", width).attr("height", height);
var nodes = [""" + vertexes.map("{id:\"" + _ + "\"}").mkString(",") + """];
var links = ["""+ edgeCreation.mkString(",") + """];
var link = svg.selectAll(".link").data(links);
link.enter().insert("line", ".node").attr("class", "link");
var node = svg.selectAll(".node").data(nodes);
var nodeEnter = node.enter().append("g").attr("class", "node")
nodeEnter.append("circle").attr("r", 8);
nodeEnter.append("text").attr("dy", "0.35em")
.text(function(d) { return d.id; });
d3.layout.force().linkDistance(50).charge(-200).chargeDistance(300)
.friction(0.95).linkStrength(0.5).size([width, height])
.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}).nodes(nodes).links(links).start();
</script>
</body>
</html>
""")
}

You can use echats, a simple tools to visualize your graph. links:baidu echats
demos in this site
You can only get json data from remote url,and visualize your data.

Related

D3.js curved node label

I want to exchange the current node label with a curved label, placed above the node. I miss knowledge regarding, how to use paths proper and how to append a text to a path. Maybe you guys could enlight me.
The text should have the same curve as the node itself.
UPDATE
I implemented the solution but it seems either the path or textPath is to short. Its possible to adjust the innerRadius, outerRadius as well as the startAngle and endAngle. I guess the startAngle and endAngle define the length of the arc. No matter which value I test, the full label isn´t shown.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Curved Text</title>
<!-- Script Import -->
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<style>
body {
background: white;
overflow: hidden;
margin: 0px;
}
circle {
fill: whitesmoke;
stroke-width: 2;
stroke: black;
transform: scale(1);
transition: all 200ms ease-in-out;
}
circle:hover {
transform: scale(1.5)
}
</style>
<body>
<svg id="svg"></svg>
<script>
var width = window.innerWidth,
height = window.innerHeight,
radius = 40, // circle radius
offset = 35; // arrow offset
const svg = d3.select('svg')
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
var graph = {
"nodes": [
{ "id": "Stackoverflow" },
{ "id": "Reddit" },
{ "id": "Google" }
],
"links": [
{ "source": "Stackoverflow", "target": "Reddit"},
{ "source": "Reddit", "target": "Google"},
{ "source": "Google", "target": "Stackoverflow"},
]
}
const arc = d3.arc()
.innerRadius(radius + 5)
.outerRadius(radius + 5)
.startAngle(-Math.PI / 15)
.endAngle(Math.PI / 2 )
svg.append("defs")
.append("path")
.attr("id", "curvedLabelPath")
.attr("d", arc())
svg.append('defs').append('marker')
.attr('id', 'arrowhead')
.attr('viewBox', '-0 -5 10 10')
.attr('refX', 0)
.attr('refY', 0)
.attr('orient', 'auto')
.attr('markerWidth', 10)
.attr('markerHeight', 10)
.attr('xoverflow', 'visible')
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#999')
.style('stroke', 'none');
var linksContainer = svg.append("g").attr("class", "linksContainer")
var nodesContainer = svg.append("g").attr("class", "nodesContainer")
const simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) { return d.id }).distance(250))
.force("charge", d3.forceManyBody().strength(-1000))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(radius))
link = linksContainer.selectAll("g")
.data(graph.links)
.join("g")
.attr("curcor", "pointer")
link = linksContainer.selectAll("path")
.data(graph.links)
.join("path")
.attr("id", function (_, i) {
return "path" + i
})
.attr("stroke", "#000000")
.attr("opacity", 0.75)
.attr("stroke-width", 3)
.attr("fill", "transparent")
.attr("marker-end", "url(#arrowhead)")
node = nodesContainer.selectAll(".node")
.data(graph.nodes, d => d.id)
.join("g")
.attr("cursor", "pointer")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("mouseenter", function (d) {
d3.select(this).select("text").attr("font-size", 15)
})
node.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", radius)
node.append("text")
.append("textPath")
.attr("href", "#curvedLabelPath")
.attr("text-anchor", "middle")
.attr("startoffset", "5%")
.text(function (d) {
return d.id
})
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation
.force("link")
.links(graph.links);
function ticked() {
link.attr("d", function (d) {
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
// Neuberechnung der Distanz
link.attr("d", function (d) {
// Länge des aktuellen Paths
var pl = this.getTotalLength(),
// Kreis Radius und Distanzwert
r = radius + offset,
// Umlaufposition wo der Path den Kreis berührt
m = this.getPointAtLength(pl - r);
var dx = m.x - d.source.x,
dy = m.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + m.x + "," + m.y;
});
node
.attr("transform", d => `translate(${d.x}, ${d.y})`);
}
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
dataFlow()
function dataFlow() {
var lines = linksContainer.selectAll("path")
dataflow = window.setInterval(function () {
lines.style("stroke-dashoffset", offset)
.style("stroke", "black")
.style("stroke-dasharray", 5)
.style("opacity", 0.5)
offset -= 1
}, 40)
var offset = 1;
}
</script>
</body>
</html>
Here's an example for one circle and label that uses a <textPath>.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
// set up
const width = 200;
const height = 200;
const svg = d3.select('#chart')
.append('svg')
.attr('width', width)
.attr('height', height);
// radius of the circle that the label is above
const radius = 50;
// arc generator for the curved path
const arc = d3.arc()
// add a bit of space so that the label
// won't be right on the circle
.innerRadius(radius + 5)
.outerRadius(radius + 5)
.startAngle(-Math.PI / 2)
.endAngle(Math.PI / 2);
// add the path that the label will follow to <defs>
svg.append('defs')
.append('path')
.attr('id', 'curvedLabelPath')
.attr('d', arc());
// create a group for the circle and label
const g = svg.append('g')
.attr('transform', `translate(${width / 2},${height / 2})`);
// draw the circle
g.append('circle')
.attr('stroke', 'black')
.attr('fill', '#d3d3d3')
.attr('r', radius);
// draw the label
g.append('text')
.append('textPath')
.attr('href', '#curvedLabelPath')
// these two lines center along the arc.
// the offset is 25% instead of 50% because d3.arc() creates
// an arc that has an outer and inner part. in this case,
// each parth is ~50% of the path, so the middle of the
// outer arc is 25%
.attr('text-anchor', 'middle')
.attr('startOffset', '25%')
.text('stackoverflow');
</script>
</body>
</html>
Here are the changes to your updated code to make the labels not get cutoff, as explained in my comment below:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Curved Text</title>
<!-- Script Import -->
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<style>
body {
background: white;
overflow: hidden;
margin: 0px;
}
circle {
fill: whitesmoke;
stroke-width: 2;
stroke: black;
transform: scale(1);
transition: all 200ms ease-in-out;
}
circle:hover {
transform: scale(1.5)
}
</style>
<body>
<svg id="svg"></svg>
<script>
var width = window.innerWidth,
height = window.innerHeight,
radius = 40, // circle radius
offset = 35; // arrow offset
const svg = d3.select('svg')
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
var graph = {
"nodes": [
{ "id": "Stackoverflow" },
{ "id": "Reddit" },
{ "id": "Google" }
],
"links": [
{ "source": "Stackoverflow", "target": "Reddit"},
{ "source": "Reddit", "target": "Google"},
{ "source": "Google", "target": "Stackoverflow"},
]
}
const arc = d3.arc()
.innerRadius(radius + 5)
.outerRadius(radius + 5)
.startAngle(-Math.PI / 2)
.endAngle(Math.PI / 2 )
svg.append("defs")
.append("path")
.attr("id", "curvedLabelPath")
.attr("d", arc())
svg.append('defs').append('marker')
.attr('id', 'arrowhead')
.attr('viewBox', '-0 -5 10 10')
.attr('refX', 0)
.attr('refY', 0)
.attr('orient', 'auto')
.attr('markerWidth', 10)
.attr('markerHeight', 10)
.attr('xoverflow', 'visible')
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#999')
.style('stroke', 'none');
var linksContainer = svg.append("g").attr("class", "linksContainer")
var nodesContainer = svg.append("g").attr("class", "nodesContainer")
const simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) { return d.id }).distance(250))
.force("charge", d3.forceManyBody().strength(-1000))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(radius))
link = linksContainer.selectAll("g")
.data(graph.links)
.join("g")
.attr("curcor", "pointer")
link = linksContainer.selectAll("path")
.data(graph.links)
.join("path")
.attr("id", function (_, i) {
return "path" + i
})
.attr("stroke", "#000000")
.attr("opacity", 0.75)
.attr("stroke-width", 3)
.attr("fill", "transparent")
.attr("marker-end", "url(#arrowhead)")
node = nodesContainer.selectAll(".node")
.data(graph.nodes, d => d.id)
.join("g")
.attr("cursor", "pointer")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("mouseenter", function (d) {
d3.select(this).select("text").attr("font-size", 15)
})
node.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", radius)
node.append("text")
.append("textPath")
.attr("href", "#curvedLabelPath")
.attr("text-anchor", "middle")
.attr("startOffset", "25%")
.text(function (d) {
return d.id
})
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation
.force("link")
.links(graph.links);
function ticked() {
link.attr("d", function (d) {
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
// Neuberechnung der Distanz
link.attr("d", function (d) {
// Länge des aktuellen Paths
var pl = this.getTotalLength(),
// Kreis Radius und Distanzwert
r = radius + offset,
// Umlaufposition wo der Path den Kreis berührt
m = this.getPointAtLength(pl - r);
var dx = m.x - d.source.x,
dy = m.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + m.x + "," + m.y;
});
node
.attr("transform", d => `translate(${d.x}, ${d.y})`);
}
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
dataFlow()
function dataFlow() {
var lines = linksContainer.selectAll("path")
dataflow = window.setInterval(function () {
lines.style("stroke-dashoffset", offset)
.style("stroke", "black")
.style("stroke-dasharray", 5)
.style("opacity", 0.5)
offset -= 1
}, 40)
var offset = 1;
}
</script>
</body>
</html>

Live graphing of data with d3 using real data

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>

Replacing a d3.js path transition with a new one?

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>

Setting up a click event in d3 that hides all other elements

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;
})

Add text/label onto links in D3 force directed graph

I've been working on modified force directed graph and having some problems with adding text/label onto links where the links are not properly aligned to nodes. How to fix it?
And how I can add an event listener to an SVG text element? Adding .on("dblclick",function(d) {....} just doesn't work.
Here's the code snippet:
<style type="text/css">
.link { stroke: #ccc; }
.routertext { pointer-events: none; font: 10px sans-serif; fill: #000000; }
.routertext2 { pointer-events: none; font: 9px sans-serif; fill: #000000; }
.linktext { pointer-events: none; font: 9px sans-serif; fill: #000000; }
</style>
<div id="canvas">
</div>
<script type="text/javascript" src="d3/d3.js"></script>
<script type="text/javascript" src="d3/d3.layout.js"></script>
<script type="text/javascript" src="d3/d3.geo"></script>
<script type="text/javascript" src="d3/d3.geom.js"></script>
<script type="text/javascript">
var w = 960,
h = 600,
size = [w, h]; // width height
var vis = d3.select("#canvas").append("svg:svg")
.attr("width", w)
.attr("height", h)
.attr("transform", "translate(0,0) scale(1)")
.call(d3.behavior.zoom().on("zoom", redraw))
.attr("idx", -1)
.attr("idsel", -1)
;
var routers = {
nodes: [
{id:0, name:"ROUTER-1", group:1, ip: "123.123.123.111",
x:394.027, y:450.978,outif:"ge-0/1/0.0",inif:""},
{id:1, name:"ROUTER-2", group:1, ip: "123.123.123.222",
x:385.584, y:351.513,outif:"xe-4/2/0.0",inif:"ge-5/0/3.0"},
{id:2, name:"ROUTER-3", group:1, ip: "123.123.123.333",
x:473.457, y:252.27,outif:"ae1.0",inif:"xe-1/0/1.0"},
{id:3, name:"ROUTER-4", group:2, ip: "123.123.123.444",
x:723.106, y:266.569,outif:"as0.0",inif:"ae1.0"},
{id:4, name:"ROUTER-5", group:3, ip: "123.123.123.555",
x:728.14, y:125.287,outif:"so-4/0/2.0",inif:"as1.0"},
{id:5, name:"ROUTER-6", group:3, ip: "123.123.123.666",
x:738.975, y:-151.772,outif:"",inif:"PO0/2/2/1" }
],
links: [
{source:0, target:1, value:3, name:'link-1',speed:"1000mbps",
outif:"ge-0/1/0.0",nextif:"ge-5/0/3.0"},
{source:1, target:2, value:3, name:'link-2',speed:"10Gbps",
outif:"xe-4/2/0.0",nextif:"xe-1/0/1.0"},
{source:2, target:3, value:3, name:'link-3',speed:"20Gbps",
outif:"ae1.0",nextif:"xe-1/2/1.0"},
{source:3, target:4, value:3, name:'link-4',speed:"1Gbps",
outif:"as0.0",nextif:"as1.0"},
{source:4, target:5, value:3, name:'link-5',speed:"OC3",
outif:"so-4/0/2.0",nextif:"PO0/2/2/1"}
]
};
var force = d3.layout.force()
.nodes(routers.nodes)
.links(routers.links)
.gravity(0)
.distance(100)
.charge(0)
.size([w, h])
.start();
var link = vis.selectAll("g.link")
.data(routers.links)
.enter().append("svg:g");
link.append("svg:line")
.attr("class", "link")
.attr("title", function(d) { return "From: "+d.outif+", To: "+d.nextif })
.attr("style", "stroke:#00d1d6;stroke-width:4px")
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
link.append("svg:text")
.attr("class", "linktext")
.attr("dx", function(d) { return d.source.x; })
.attr("dy", function(d) { return d.source.y; })
.text("some text to add...");
var node = vis.selectAll("g.node")
.data(routers.nodes)
.enter()
.append("svg:g")
.attr("id", function(d) { return d.id;})
.attr("title", function(d) {return d.ip})
.attr("class", "node")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.on("dblclick",function(d) {
alert('router double-clicked'); d3.event.stopPropagation();
})
.on("mousedown", function(d) {
if (d3.event.which==3) {
d3.event.stopPropagation();
alert('Router right-clicked');
}
})
.call(force.drag);
node.append("svg:image")
.attr("class", "node")
.attr("xlink:href", "router.png")
.attr("x", -24)
.attr("y", -18)
.attr("width", 48)
.attr("height", 36);
node.append("svg:text")
.attr("class", "routertext")
.attr("dx", -30)
.attr("dy", 20)
.text(function(d) { return d.name });
node.append("svg:text")
.attr("class", "routertext2")
.attr("dx", 0)
.attr("dy", -20)
.attr("title", "some title to show....")
.text(function(d) { return d.outif })
.on("click", function(d,i) {alert("outif text clicked");})
.call(force.drag);
node.append("svg:text")
.attr("class", "routertext2")
.attr("dx", -40)
.attr("dy", 30)
.text(function(d) { return d.inif });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
});
function redraw() {
vis.attr("transform",
"translate(" + d3.event.translate + ")"
+ "scale(" + d3.event.scale + ")");
};
</script>
Use a smaller example outside of D3 to see how the SVG stuff works. Then just rebuild this structure using D3 and your custom data.
<html>
<body>
<svg width="600px" height="400px">
<defs>
<!-- DEFINE AN ARROW THAT WE CAN PLACE AT THE END OF EDGES. -->
<!-- USE REFX TO MOVE THE ARROW'S TIP TO THE END OF THE PATH. -->
<marker
orient="auto"
markerHeight="12"
markerWidth="12"
refY="0"
refX="9"
viewBox="0 -5 10 10"
id="ARROW_ID"
style="fill: red; fill-opacity: 0.5;">
<path d="M0, -5L10, 0L0, 5"></path>
</marker>
</defs>
<!-- DEFINE A PATH. SET ITS END MARKER TO THE ARROW'S ID. -->
<!-- SET FILL NONE TO DRAW A LINE INSTEAD OF A SHAPE. -->
<path
d="M100,100 A300,250 0 0,1 500,300"
style="fill:none; stroke:grey; stroke-width:2px;"
id="PATH_ID"
marker-end="url(#ARROW_ID)" />
<!-- DEFINE A TEXT ELEMENT AND SET FONT PROPERTIES. -->
<!-- USE DY TO MOVE TEXT ABOVE THE PATH. -->
<text
style="text-anchor:middle; font: 16px sans-serif;"
dy="-12">
<!-- DEFINE A TEXT PATH FOLLOWING THE PATH DEFINED ABOVE. -->
<!-- USE STARTOFFSET TO CENTER TEXT. -->
<textPath
xlink:href="#PATH_ID"
startOffset="50%">Centered edge label</textPath>
</text>
</svg>
</body>
</html>
Have you experimented with creating text elements separately in a standalone (simpler) example? It should give you a better feeling for how the different attributes control positioning.
For vertical alignment, use the "dy" attribute:
by default, the baseline of the text is at the origin (bottom-aligned)
a dy of .35em centers the text vertically
a dy of .72em places the topline of the text at the origin (top-aligned)
Using em units is nice because it will scale automatically based on the font size. If you don't specify units (such as -20 in your code), it defaults to pixels.
For horizontal alignment, use the "text-anchor" attribute:
the default is "start" (left-aligned for left-to-right languages)
"middle"
"end"
There's also the "dx" attribute, which is tempting to use for padding. However, I wouldn't recommend it because there is a bug in Firefox and Opera that cause it to not work as expected in conjunction with text-anchor middle or end.
Created JS fiddle example for showing labels over links in D3 Forced layout chart
See working demo in JS Fiddle: http://jsfiddle.net/bc4um7pc/
Give Id's to your path like below
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", function(d) { return "link " + d.type; })
.attr("id",function(d,i) { return "linkId_" + i; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
Use SVG textPath element for associating labels with above links by specifying its 'xlink:href' attribute to point to its respective link/path.
var linktext = svg.append("svg:g").selectAll("g.linklabelholder").data(force.links());
linktext.enter().append("g").attr("class", "linklabelholder")
.append("text")
.attr("class", "linklabel")
.style("font-size", "13px")
.attr("x", "50")
.attr("y", "-20")
.attr("text-anchor", "start")
.style("fill","#000")
.append("textPath")
.attr("xlink:href",function(d,i) { return "#linkId_" + i;})
.text(function(d) {
return "my text"; //Can be dynamic via d object
});
I am using an arch as a link between nodes with a label text placed in the middle. Here is a code snippet:
var vis = d3.select("body")
.append("svg")
.attr("width", 600)
.attr("height", 400)
.append("g");
var force = d3.layout.force()
.gravity(.05)
.distance(120)
.charge(-100)
.size([600, 400]);
var nodes = force.nodes(), links = force.links();
// make an arch between nodes and a text label in the middle
var link = vis.selectAll("path.link").data(links, function(d) {
return d.source.node_id + "-" + d.target.node_id; });
link.enter().append("path").attr("class", "link");
var linktext = vis.selectAll("g.linklabelholder").data(links);
linktext.enter().append("g").attr("class", "linklabelholder")
.append("text")
.attr("class", "linklabel")
.attr("dx", 1)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return "my label" });
// add your code for nodes ....
force.on("tick", tick); force.start();
function tick () {
// curve
link.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + ","
+ dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
// link label
linktext.attr("transform", function(d) {
return "translate(" + (d.source.x + d.target.x) / 2 + ","
+ (d.source.y + d.target.y) / 2 + ")"; });
// nodes
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
}
Just add this line:
.attr("text-anchor", "middle")
to the code after the line:
node.append("svg:text")
it should look like this:
node.append("svg:text")
.attr("text-anchor", "middle")
......

Resources