Minimap view for a d3 diagram in a layout? - d3.js

I'd like to include a helicopter view (or minimap view) in a layout with d3 diagrams. I am using ariutta's svg-pan-zoom, but the thumbnail view doesn't behave as expected. After many tests, I've found no way to get it. I'd really appreciate any suggestion.
Here there is the jsfiddle with a simplified version of what I have. To make it easy, a big part of code has been removed and a d3noob tree diagram has been used. We also use dhtmlx, but this part has also been removed from this jsfiddle (maybe later we can consider including it) : jsfiddle with our code
This is what I want to get:
enter image description here
So as to display the diagram in the thumbnail view, the d3 chart is encoded (base64) and used as background image for the thumbnail view (also tried as src).
Possible causes:
dhtmlx
dynamic svg
encoded (base64) svg
Thanks in advance!
That's a simplified version of the code (without using dhtmlx).
HTML code
<div id="mainScreen" style="height:100%; width:100%; position:absolute;" >
<!-- Original: svg instead of div -->
<!-- <svg id="diagramLayout" style="height:100%;width:100%;position:absolute;">
</svg> -->
<div id="diagramLayout" style="height:100%; width:100%; position:absolute;"></div>
<div id="thumbViewContainer">
<svg id="scopeContainer" class="thumbViewClass">
<g>
<rect id="scope" fill="red" fill-opacity="0.1" stroke="red" stroke-width="2px" x="0" y="0" width="0" height="0"/>
<line id="line1" stroke="red" stroke-width="2px" x1="0" y1="0" x2="0" y2="0"/>
<line id="line2" stroke="red" stroke-width="2px" x1="0" y1="0" x2="0" y2="0"/>
</g>
</svg>
<img id="thumbView" type="image/svg+xml" src="" class="thumbViewClass" />
</div>
</div>
Main JS code (it needs thumbnailViewer.js and svg-pan-zoom.js, but not included here)
//------ Tree Layout: https://bl.ocks.org/d3noob/43a860bc0024792f8803bba8ca0d5ecd
var treeData =
{
"name": "Top Level",
"children": [
{
"name": "Level 2: A",
"children": [
{ "name": "Son of A" },
{ "name": "Daughter of A" }
]
},
{ "name": "Level 2: B" }
]
};
// Set the dimensions and margins of the diagram
var margin = {top: 20, right: 90, bottom: 30, left: 90},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// append the svg object to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("#diagramLayout").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate("
+ margin.left + "," + margin.top + ")");
var i = 0,
duration = 750,
root;
// declares a tree layout and assigns the size
var treemap = d3.tree().size([height, width]);
// Assigns parent, children, height, depth
root = d3.hierarchy(treeData, function(d) { return d.children; });
root.x0 = height / 2;
root.y0 = 0;
// Collapse after the second level
root.children.forEach(collapse);
update(root);
// Collapse the node and all it's children
function collapse(d) {
if(d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
function update(source) {
// Assigns the x and y position for the nodes
var treeData = treemap(root);
// Compute the new tree layout.
var nodes = treeData.descendants(),
links = treeData.descendants().slice(1);
// Normalize for fixed-depth.
nodes.forEach(function(d){ d.y = d.depth * 180});
// ****************** Nodes section ***************************
// Update the nodes...
var node = svg.selectAll('g.node')
.data(nodes, function(d) {return d.id || (d.id = ++i); });
// Enter any new modes at the parent's previous position.
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on('click', click);
// Add Circle for the nodes
nodeEnter.append('circle')
.attr('class', 'node')
.attr('r', 1e-6)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
// Add labels for the nodes
nodeEnter.append('text')
.attr("dy", ".35em")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) { return d.data.name; });
// UPDATE
var nodeUpdate = nodeEnter.merge(node);
// Transition to the proper position for the node
nodeUpdate.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Update the node attributes and style
nodeUpdate.select('circle.node')
.attr('r', 10)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
})
.attr('cursor', 'pointer');
// Remove any exiting nodes
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
// On exit reduce the node circles size to 0
nodeExit.select('circle')
.attr('r', 1e-6);
// On exit reduce the opacity of text labels
nodeExit.select('text')
.style('fill-opacity', 1e-6);
// ****************** links section ***************************
// Update the links...
var link = svg.selectAll('path.link')
.data(links, function(d) { return d.id; });
// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('path', "g")
.attr("class", "link")
.attr('d', function(d){
var o = {x: source.x0, y: source.y0}
return diagonal(o, o)
});
// UPDATE
var linkUpdate = linkEnter.merge(link);
// Transition back to the parent element position
linkUpdate.transition()
.duration(duration)
.attr('d', function(d){ return diagonal(d, d.parent) });
// Remove any exiting links
var linkExit = link.exit().transition()
.duration(duration)
.attr('d', function(d) {
var o = {x: source.x, y: source.y}
return diagonal(o, o)
})
.remove();
// Store the old positions for transition.
nodes.forEach(function(d){
d.x0 = d.x;
d.y0 = d.y;
});
// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(s, d) {
path = `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x},
${(s.y + d.y) / 2} ${d.x},
${d.y} ${d.x}`
return path
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
}
// Don't use window.onLoad like this in production, because it can only listen to one function.
function Reset() {
if(panZoom !== undefined){
panZoom.destroy();
}
panZoom = svgPanZoom('#diagramLayout', {
zoomEnabled: true,
dblClickZoomEnabled: false,
fit:true,
controlIconsEnabled: false
});
loadMiniMap();
}
function load() {
panZoom = svgPanZoom('#diagramLayout', {
zoomEnabled: true,
// dblClickZoomEnabled: false,
// fit:true,
// controlIconsEnabled: false
});
loadMiniMap();
}
function loadMiniMap() {
var emebd = document.getElementById("thumbView");
//get svg element.
var svg = document.getElementById("diagramLayout");
//get svg source.
var serializer = new XMLSerializer();
var source = serializer.serializeToString(svg);
var mySVG64 = window.btoa(source);
emebd.style.backgroundImage = "url('data:image/svg+xml;base64," + mySVG64 + "')";
emebd.style.backgroundRepeat = "no-repeat";
emebd.backgroundAttachment = "fixed";
emebd.style.backgroundPosition = "center";
emebd.style.backgroundSize = "contain";
//emebd.src = "data:image/svg+xml;base64, "+ mySVG64
thumbnailViewer({mainViewId: 'diagramLayout',thumbViewId: 'thumbView'});
}
CSS code
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
#mainViewContainer {
width: 95%;
height: 95%;
border: 1px solid black;
margin: 10px;
padding: 3px;
overflow: hidden;
}
#mainView {
width: 100%;
height: 100%;
min-height: 100%;
display: inline;
}
.thumbViewClass {
border: 1px solid black;
position: absolute;
bottom: 12px;
right: 15px;
width: 12%;
height: 15%;
margin: 3px;
padding: 3px;
overflow: hidden;
}
#thumbView {
z-index: 110;
background: white;
}
#scopeContainer {
z-index: 120;
}

Related

How to stop jumping of svg while added zoom in and out feature

hi i just addedd the zoom feature for D3 tree.js but while dragging the svg it's jumping up and down can anyone help me with it and everything is working but only drag gable the svg is not working and i tried few solutions given in google of appending g but still the issue was not resolved can any one help me with it?
Tree D3.js Without Zoom
Tree D3.js With Zoom
<!DOCTYPE html>
<meta charset="UTF-8">
<style>
.tooltip {
position: absolute;
text-align: left;
white-space: normal;
padding: 4px;
font-size: 14px;
background: tan;
border: 1px solid gray;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
.d3-context-menu {
position: absolute;
display: none;
background-color: #f2f2f2;
border-radius: 4px;
font-family: Arial, sans-serif;
font-size: 14px;
min-width: 150px;
border: 1px solid #d4d4d4;
z-index:1200;
}
.d3-context-menu ul {
list-style-type: none;
margin: 4px 0px;
padding: 0px;
cursor: default;
}
.d3-context-menu ul li {
padding: 4px 16px;
}
.d3-context-menu ul li:hover {
background-color: #4677f8;
color: #fefefe;
}
</style>
<body>
<div></div>
<!-- load the d3.js library -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script >
function tooltip(){
return d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);}
d3.contextMenu = function (menu, openCallback) {
// create the div element that will hold the context menu
d3.selectAll('.d3-context-menu').data([1])
.enter()
.append('div')
.attr('class', 'd3-context-menu');
// close menu
d3.select('div').on('click.d3-context-menu', function() {
d3.select('.d3-context-menu').style('display', 'none');
});
// this gets executed when a contextmenu event occurs
return function() {
let elm = this;
d3.selectAll('.d3-context-menu').html('');
let list = d3.selectAll('.d3-context-menu').append('ul');
list.selectAll('li').data(menu).enter()
.append('li')
.html(function(d) {
return d.title;
})
.on('click', function(d, i) {
d.action(elm, data, index);
d3.select('.d3-context-menu').style('display', 'none');
});
// the openCallback allows an action to fire before the menu is displayed
// an example usage would be closing a tooltip
if (openCallback) openCallback(data, index);
// display context menu
d3.select('.d3-context-menu')
.style('left', (d3.event.pageX - 2) + 'px')
.style('top', (d3.event.pageY - 2) + 'px')
.style('display', 'block');
d3.event.preventDefault();
};
};</script>
<script>
var menu = [
{
title: 'URL',
action: function(elm, d, i) {
console.log('Item #1 clicked!'+d);
console.log('The data for this circle is: ' + d);
}
},
{
title: 'ExPLORE',
action: function(elm, d, i) {
console.log(d);
console.log('The data for this circle is: ' + d);
}
}
]
var data = [1, 2, 3];
var selected=null;
var treeData =
{
"name": "Top Level",
"children": [
{
"name": "Level 2: A",
"children": [
{ "name": "Son of A" },
{ "name": "Daughter of A" }
]
},
{ "name": "Level 2: B" }
]
};
//Predefined Box Size
let rectNode = {
width: 120,
height: 17,
textMargin: 5
};
// Set the dimensions and margins of the diagram
var margin = {top: 20, right: 90, bottom: 30, left: 90},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// append the svg object to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("div")
.append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom);
svg.call(d3.zoom().on("zoom", function () { svg.attr("transform", d3.event.transform) }));
svg.append("g")
.attr("transform", "translate("
+ margin.left + "," + margin.top + ")")
;
var i = 0,
duration = 750,
root;
// declares a tree layout and assigns the size
var treemap = d3.tree().size([height, width]);
// Assigns parent, children, height, depth
root = d3.hierarchy(treeData, function(d) { return d.children; });
root.x0 = height / 2;
root.y0 = 0;
// Collapse after the second level
root.children.forEach(collapse);
update(root);
// Collapse the node and all it's children
function collapse(d) {
if(d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
function update(source) {
// Assigns the x and y position for the nodes
var treeData = treemap(root);
// Compute the new tree layout.
var nodes = treeData.descendants(),
links = treeData.descendants().slice(1);
// Normalize for fixed-depth.
nodes.forEach(function(d){ d.y = d.depth * 180});
// ****************** Nodes section ***************************
// Update the nodes...
var node = svg.selectAll('g.node')
.data(nodes, function(d) {return d.id || (d.id = ++i); });
// Enter any new modes at the parent's previous position.
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
});
//.on('click', click);
// Add Circle for the nodes
nodeEnter.append('circle')
.attr('class', 'node')
.attr('r', 0)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
let rectGrpEnter = nodeEnter.append('g')
.attr('class', 'node-rect-text-grp');
rectGrpEnter.append('rect')
.attr('rx', 6)
.attr('ry', 6)
.attr('x',-20)
.attr('y',-20)
.style('fill', "#337ab7")
.attr('width', 50)
.attr('height',50)
.on('contextmenu', d3.contextMenu(menu));
// Add labels for the nodes
nodeEnter.append('text')
.attr("dy", ".35em")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) { return d.data.name; });
// UPDATE
var nodeUpdate = nodeEnter.merge(node);
// Transition to the proper position for the node
nodeUpdate.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Update the node attributes and style
nodeUpdate.select('circle.node')
.attr('r', 10)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
})
.attr('cursor', 'pointer');
// Remove any exiting nodes
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
// On exit reduce the node circles size to 0
nodeExit.select('circle')
.attr('r', 1e-6);
// On exit reduce the opacity of text labels
nodeExit.select('text')
.style('fill-opacity', 1e-6);
// ****************** links section ***************************
// Update the links...
var link = svg.selectAll('path.link')
.data(links, function(d) { return d.id; });
// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('path', "g")
.attr("class", "link")
.attr('d', function(d){
var o = {x: source.x0, y: source.y0}
return diagonal(o, o)
});
// UPDATE
var linkUpdate = linkEnter.merge(link);
// Transition back to the parent element position
linkUpdate.transition()
.duration(duration)
.attr('d', function(d){ return diagonal(d, d.parent) });
// Remove any exiting links
var linkExit = link.exit().transition()
.duration(duration)
.attr('d', function(d) {
var o = {x: source.x, y: source.y}
return diagonal(o, o)
})
.remove();
// Store the old positions for transition.
nodes.forEach(function(d){
d.x0 = d.x;
d.y0 = d.y;
});
// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(s, d) {
path = `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x},
${(s.y + d.y) / 2} ${d.x},
${d.y} ${d.x}`
return path
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
function contextmenu(d){
if(d.children){
alert("Has Children")
}
else{
alert("NO Has Children")
selected = d;
update(d);
var myObj, i, x = "";
myObj = {
"name":"John",
"age":30,
"cars":[ {name: "child1"}, { name: "child2"}, {name: "child3"}]
};
for (i in myObj.cars) {
var newNodeObj =myObj.cars[i];
;
//Creates new Node
var newNode = d3.hierarchy(newNodeObj);
newNode.depth = selected.depth + 1;
newNode.height = selected.height - 1;
newNode.parent = selected;
if(!selected.children){
selected.children = [];
selected.data.children = [];
}
selected.children.push(newNode);
}
update(selected);
}
}
}
</script>
</body>

d3 on click on circle pause and resume transition of marker along line

I would like help to correct my code to click the marker circle element to pause or resume transition of this element along the line. My code moves marker along a line and I can pause and resume this transition using on click on button element but I would like to be able to click on the marker circle itself, not the button. I have used various references including :
http://www.nytimes.com/interactive/2013/09/25/sports/americas-cup-course.html
http://jsfiddle.net/meetamit/UJuWX/3/
http://jsfiddle.net/Y62Hq/2/
D3 tween - pause and resume controls
I would ultimately like to be able animate a marker along a geo path, pause and resume this at points along the path and click through on these points.
this is my code so far:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Need help</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-array.v1.min.js"></script>
<script src="https://d3js.org/d3-geo.v1.min.js"></script>
<script src="https://d3js.org/d3-queue.v3.min.js"></script>
<style type="text/css">
body{
font-family:"Helvetica Neue", Helvetica, sans-serif;
color: red;
}
button {
position: absolute;
top: 15px;
left: 10px;
background: #004276;
padding-right: 26px;
border-radius: 2px;
cursor: pointer;
}
circle {
fill: steelblue;
stroke: pink;
stroke-width: 3px;
}
.point{
fill:green;
}
.line{
fill: none;
stroke: red;
stroke-width: 4;
stroke-dasharray: 4px,8px;
}
</style>
</head>
<body>
<button>Start</button>
<script>
var w = 960,
h = 500;
var duration = 10000;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
var line = d3.line()
.x(function(d){return (d)[0];})
.y(function(d){return (d)[1];});
var data =
[
[480, 200],
[580, 400],
[680, 100],
[780, 300],
[180, 300],
[280, 100],
[380, 400]
];
//path to animate
var linepath = svg.append("path")
.data([data])
.attr("d", line)
.attr('class', 'line')
.attr("d", function(d){
console.log(this);
return line(d)
});
var points = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", 7)
.attr("transform", function(d) { return "translate(" + (d) + ")"; })
.attr("class", "point");
var pauseValues = {
lastTime: 0,
currentTime: 0
};
var marker = svg.append("circle")
.attr("r", 19)
.attr("transform", "translate(" + (data[0]) + ")")
.on('click', function(d,i){
d3.select(this)
.style("fill", "orange")
.transition()
});
function transition() {
marker.transition()
.duration(duration - (duration * pauseValues.lastTime))
.attrTween("transform", translateAlong(linepath.node()))
.on("end", function(){
pauseValues = {
lastT: 0,
currentT: 0
};
transition()
});
}
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
t += pauseValues.lastTime;
var p = path.getPointAtLength(t * l);
pauseValues.currentTime = t;
return "translate(" + p.x + "," + p.y + ")";
};
};
}
d3.select('button').on('click',function(d,i){
var self = d3.select(this);
if (self.text() == "Pause"){
self.text('Start');
marker.transition()
.duration(0);
setTimeout(function(){
pauseValues.lastTime = pauseValues.currentTime;
}, 100);
}else{
self.text('Pause');
transition();
}
});
</script>
</body>
</html>
To check if the circle is moving in the click function use d3.active(), which...
... returns null if there is no such active transition on the specified node.
Like this:
.on('click', function(d, i) {
if (d3.active(this)) {
marker.transition();
setTimeout(function() {
pauseValues.lastTime = pauseValues.currentTime;
}, 100);
} else {
transition();
}
});
Here is your code with that change:
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-array.v1.min.js"></script>
<script src="https://d3js.org/d3-geo.v1.min.js"></script>
<script src="https://d3js.org/d3-queue.v3.min.js"></script>
<style type="text/css">
body {
font-family: "Helvetica Neue", Helvetica, sans-serif;
color: red;
}
button {
position: absolute;
top: 15px;
left: 10px;
background: #004276;
padding-right: 26px;
border-radius: 2px;
cursor: pointer;
}
circle {
fill: steelblue;
stroke: pink;
stroke-width: 3px;
}
.point {
fill: green;
}
.line {
fill: none;
stroke: red;
stroke-width: 4;
stroke-dasharray: 4px, 8px;
}
</style>
<body>
<button>Start</button>
<script>
var w = 960,
h = 500;
var duration = 10000;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
var line = d3.line()
.x(function(d) {
return (d)[0];
})
.y(function(d) {
return (d)[1];
});
var data = [
[480, 200],
[580, 400],
[680, 100],
[780, 300],
[180, 300],
[280, 100],
[380, 400]
];
//path to animate
var linepath = svg.append("path")
.data([data])
.attr("d", line)
.attr('class', 'line')
.attr("d", function(d) {
return line(d)
});
var points = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", 7)
.attr("transform", function(d) {
return "translate(" + (d) + ")";
})
.attr("class", "point");
var pauseValues = {
lastTime: 0,
currentTime: 0
};
var marker = svg.append("circle")
.attr("r", 19)
.attr("transform", "translate(" + (data[0]) + ")")
.on('click', function(d, i) {
if (d3.active(this)) {
marker.transition();
setTimeout(function() {
pauseValues.lastTime = pauseValues.currentTime;
}, 100);
} else {
transition();
}
});
function transition() {
marker.transition()
.duration(duration - (duration * pauseValues.lastTime))
.attrTween("transform", translateAlong(linepath.node()))
.on("end", function() {
pauseValues = {
lastT: 0,
currentT: 0
};
transition()
});
}
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
t += pauseValues.lastTime;
var p = path.getPointAtLength(t * l);
pauseValues.currentTime = t;
return "translate(" + p.x + "," + p.y + ")";
};
};
}
d3.select('button').on('click', function(d, i) {
var self = d3.select(this);
if (self.text() == "Pause") {
self.text('Start');
marker.transition()
.duration(0);
setTimeout(function() {
pauseValues.lastTime = pauseValues.currentTime;
}, 100);
} else {
self.text('Pause');
transition();
}
});
</script>
</body>

Format the legend of my donut chart

I try to make this chart with D3js:
I've no idea how to make my "%" small like a <sup> tag (image). I tried to remove the suffix from the format()... it's give me a float.
How can I do that? Do I need to substring from my current text and add a new text? What's the better way?
Code :
makeDonut(1, 45);
function makeDonut(id, percent) {
var duration = 2000,
transition = 200,
width = 180,
height = 180;
var dataset = {
lower: calcPercent(0),
upper: calcPercent(percent)
},
radius = Math.min(width, height) / 3,
pie = d3.pie().sort(null),
format = d3.format(".0%");
var arc = d3.arc()
.innerRadius(radius * .8)
.outerRadius(radius / .7);
var svg = d3.select("#graph" + id).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(pie(dataset.lower))
.enter().append("path")
.attr("class", function(d, i) {
return "g" + id + "-color" + i
})
.attr("d", arc)
.each(function(d) {
this._current = d;
});
var text = svg.append("text")
.attr("class", "g" + id + "-text")
.attr("text-anchor", "middle")
.attr("dy", ".3em");
var progress = 0;
var timeout = setTimeout(function() {
clearTimeout(timeout);
path = path.data(pie(dataset.upper));
path.transition().duration(duration).attrTween("d", function(a) {
var i = d3.interpolate(this._current, a);
var i2 = d3.interpolate(progress, percent)
this._current = i(0);
return function(t) {
text.text(format(i2(t) / 100));
return arc(i(t));
};
});
}, 200);
};
function calcPercent(percent) {
return [percent, 100 - percent];
};
.g1-color0 {
fill: #5ca747;
}
.g1-color1 {
fill: #dcdcdc;
}
.g1-text {
font-family: 'Roboto Condensed', sans-serif;
fill: #5ca747;
font-size: 50px;
font-weight: bold;
}
.graph1 span {
font-family: 'Roboto Condensed', sans-serif;
text-transform: uppercase;
font-size: 26px;
color: #5ca747;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="graph1 col-sm-4 text-center">
<div id="graph1"></div>
<span>3 projecten klaar</span>
</div>
I created a JSFiffle
An easy (and lazy) solution is just creating another text selection for the % symbol:
var percentText = svg.append("text")
.attr("class", "g" + id + "-percent")
.attr("text-anchor", "middle")
.attr("dx", "1.2em")
.attr("dy", "-0.3em")
.text("%");
Here is the demo:
makeDonut(1, 45);
function makeDonut(id, percent) {
var duration = 2000,
transition = 200,
width = 180,
height = 180;
var dataset = {
lower: calcPercent(0),
upper: calcPercent(percent)
},
radius = Math.min(width, height) / 3,
pie = d3.pie().sort(null);
var arc = d3.arc()
.innerRadius(radius * .8)
.outerRadius(radius / .7);
var svg = d3.select("#graph" + id).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(pie(dataset.lower))
.enter().append("path")
.attr("class", function(d, i) {
return "g" + id + "-color" + i
})
.attr("d", arc)
.each(function(d) {
this._current = d;
});
var text = svg.append("text")
.attr("class", "g" + id + "-text")
.attr("text-anchor", "middle")
.attr("dy", ".3em");
var percentText = svg.append("text")
.attr("class", "g" + id + "-percent")
.attr("text-anchor", "middle")
.attr("dx", "1.2em")
.attr("dy", "-0.3em")
.text("%");
var progress = 0;
var timeout = setTimeout(function() {
clearTimeout(timeout);
path = path.data(pie(dataset.upper));
path.transition().duration(duration).attrTween("d", function(a) {
var i = d3.interpolate(this._current, a);
var i2 = d3.interpolate(progress, percent)
this._current = i(0);
return function(t) {
if(~~(i2(t))>=10){percentText.attr("dx", "1.8em")}
text.text(~~(i2(t)));
return arc(i(t));
};
});
}, 200);
};
function calcPercent(percent) {
return [percent, 100 - percent];
};
.g1-color0 {
fill: #5ca747;
}
.g1-color1 {
fill: #dcdcdc;
}
.g1-text {
font-family: 'Roboto Condensed', sans-serif;
fill: #5ca747;
font-size: 50px;
font-weight: bold;
}
.g1-percent {
font-family: 'Roboto Condensed', sans-serif;
fill: #5ca747;
font-size: 20px;
font-weight: bold;
}
.graph1 span {
font-family: 'Roboto Condensed', sans-serif;
text-transform: uppercase;
font-size: 26px;
color: #5ca747;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="graph1 col-sm-4 text-center">
<div id="graph1"></div>
<span>3 projecten klaar</span>
</div>

Filter table in D3 from selection in parallel coordinates visualization

I have a parallel coordinates visualization and I've added a table below that represents all the data in the visualization.
I want to filter the table based on the selection of the parallel coordinates visualization. So, just showing the data highlighted in the parallel coordinates visualization.
Any ideas on how to do that?
I've tried in the brush section to highlight the cells in the table in red adding a style in for the tags, but it doesn't seem to work. See code below:
// Add and store a brush for each axis.
g.append("g")
.attr("class", "brush")
.each(function(d) {
d3.select(this).call(y[d].brush = d3.svg.brush().y(y[d]).on("brushstart", brushstart).on("brush", brush));
d3.selectAll("td").style('bgcolor', 'red');
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
I've tried to add the highlight of the cells in the brush function. Here I achieve for all the cells to turn red, but I don't know how to highlight only the ones selected in the selection.
function brush() {
var actives = dimensions.filter(function(p) {
return !y[p].brush.empty();
}),
extents = actives.map(function(p) {
return y[p].brush.extent();
});
foreground.style("display", function(d) {
return actives.every(function(p, i) {
return extents[i][0] <= d[p] && d[p] <= extents[i][1];
}) ? null : "none";
});
d3.selectAll("td").style('background-color', 'red');
}
See code below, which you can find also in the following link GitHub:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.background path {
fill: none;
stroke: #ddd;
shape-rendering: crispEdges;
}
.foreground path {
fill: none;
stroke: steelblue;
stroke-width: 2;
}
.brush .extent {
fill-opacity: .3;
stroke: #fff;
shape-rendering: crispEdges;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
cursor: move;
}
.tooltip {
background-color: rgba(220,220,220,0.5);
color: #333;
margin: 10px;
height: 25px;
padding-right: 10px;
padding-left: 10px;
padding-top: 10px;
-webkit-border-radius:10px;
-moz-border-radius:10px;
border-radius:10px;
}
td, th {
padding: 1px 4px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
// Fuente original: https://bl.ocks.org/jasondavies/1341281
var margin = {
top: 30,
right: 10,
bottom: 10,
left: 10
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal().rangePoints([0, width], 1),
y = {},
dragging = {};
var line = d3.svg.line(),
axis = d3.svg.axis().orient("left"),
background,
foreground;
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 + ")");
var notas = [
{
"Nombre": "Emily",
"Matematicas": "10",
"Ciencias": "10",
"Historia": "8",
"Geografia": "8",
"Lengua": "10"
},
{
"Nombre": "Cooper",
"Matematicas": "10",
"Ciencias": "7",
"Historia": "2",
"Geografia": "8",
"Lengua": "10"
}];
var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.text("a simple tooltip")
.attr("class","tooltip");
// Extract the list of dimensions and create a scale for each.
x.domain(dimensions = d3.keys(notas[0]).filter(function(d) {
return d != "Nombre" && (y[d] = d3.scale.linear()
.domain(d3.extent(notas, function(p) {
return +p[d];
}))
.range([height, 0]));
}));
// Add grey background lines for context.
background = svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(notas)
.enter().append("path")
.attr("d", path);
// Add blue foreground lines for focus.
foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(notas)
.enter().append("path")
.attr("d", path)
.on("mouseover", function(n){
d3.select(this)
.transition().duration(100)
.style({'stroke' : '#F00'});
tooltip.text(n.Nombre);
return tooltip.style("visibility", "visible");
})
.on("mousemove", function(){
return tooltip
.style("top", (event.pageY-10)+"px")
.style("left",(event.pageX+10)+"px");
})
.on("mouseout", function(d){
d3.select(this)
.transition().duration(100)
.style({'stroke': 'steelblue' })
.style({'stroke-width' : '2'});
return tooltip.style("visibility", "hidden");
});
// Add a group element for each dimension.
var g = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("g")
.attr("class", "dimension")
.attr("transform", function(d) {
return "translate(" + x(d) + ")";
})
.call(d3.behavior.drag()
.origin(function(d) {
return {
x: x(d)
};
})
.on("dragstart", function(d) {
dragging[d] = x(d);
background.attr("visibility", "hidden");
})
.on("drag", function(d) {
dragging[d] = Math.min(width, Math.max(0, d3.event.x));
foreground.attr("d", path);
dimensions.sort(function(a, b) {
return position(a) - position(b);
});
x.domain(dimensions);
g.attr("transform", function(d) {
return "translate(" + position(d) + ")";
})
})
.on("dragend", function(d) {
delete dragging[d];
transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
transition(foreground).attr("d", path);
background
.attr("d", path)
.transition()
.delay(500)
.duration(0)
.attr("visibility", null);
}));
// Add an axis and title.
g.append("g")
.attr("class", "axis")
.each(function(d) {
d3.select(this).call(axis.scale(y[d]));
})
.append("text")
.style("text-anchor", "middle")
.attr("y", -9)
.text(function(d) {
return d;
});
// Add and store a brush for each axis.
g.append("g")
.attr("class", "brush")
.each(function(d) {
d3.select(this).call(y[d].brush = d3.svg.brush().y(y[d]).on("brushstart", brushstart).on("brush", brush));
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
function position(d) {
var v = dragging[d];
return v == null ? x(d) : v;
}
function transition(g) {
return g.transition().duration(500);
}
// Returns the path for a given data point.
function path(d) {
return line(dimensions.map(function(p) {
return [position(p), y[p](d[p])];
}));
}
function brushstart() {
d3.event.sourceEvent.stopPropagation();
}
// Handles a brush event, toggling the display of foreground lines.
function brush() {
var actives = dimensions.filter(function(p) {
return !y[p].brush.empty();
}),
extents = actives.map(function(p) {
return y[p].brush.extent();
});
foreground.style("display", function(d) {
return actives.every(function(p, i) {
return extents[i][0] <= d[p] && d[p] <= extents[i][1];
}) ? null : "none";
});
}
// The table generation function
function tabulate(data, columns) {
var table = d3.select("body").append("table")
.attr("style", "margin-left: 250px"),
thead = table.append("thead"),
tbody = table.append("tbody");
// append the header row
thead.append("tr")
.selectAll("th")
.data(columns)
.enter()
.append("th")
.text(function(column) { return column; });
// create a row for each object in the data
var rows = tbody.selectAll("tr")
.data(notas)
.enter()
.append("tr");
// create a cell in each row for each column
var cells = rows.selectAll("td")
.data(function(row) {
return columns.map(function(column) {
return {column: column, value: row[column]};
});
})
.enter()
.append("td")
.attr("style", "font-family: Courier") // sets the font style
.html(function(d) { return d.value; });
return table;
}
// render the table
var peopleTable = tabulate(notas, ["Nombre", "Matematicas","Ciencias", "Historia","Geografia", "Lengua"]);
</script>
</body>
</html>
I've added also the code in JSFiddle but it doesn't display anything. I'm not sure what I'm doing wrong. I'm not familiar with JSFiddle, so I might be doing something wrong.
I've tried also to use divgrid (DivGrid) and with the following code I can see how when I attempt to brush the table gets updated. The problem is that it doesn't get updated with the data I need. I just have it to show 5 records.
<script src="divgrid.js"></script>
...
// Handles a brush event, toggling the display of foreground lines.
function brush() {
var actives = dimensions.filter(function(p) {
return !y[p].brush.empty();
}),
extents = actives.map(function(p) {
return y[p].brush.extent();
});
foreground.style("display", function(d) {
return actives.every(function(p, i) {
return extents[i][0] <= d[p] && d[p] <= extents[i][1];
}) ? null : "none";
});
//d3.selectAll("td").style('background-color', 'red');
var grid = d3.divgrid();
d3.select('body')
.datum(notas.slice(0,5))
.call(grid);
}
The problems I have (I think) is that (1) the update of the table is not really in the right place as it is only updating when starting to brush, not when brushing over the lines and (2) I need to find a way to pickup the data from the lines that are selected. I've added to code in github as a reference (GitHub)
Any ideas?
Thanks

Adding data to a selection

I have trouble to add paths to a g element created by the nodes with selectAll:
// define the nodes
var node = svg.selectAll(".node").data(force.nodes()).enter()
.append("g").attr("id", function(d) {
return d.id;
}).attr("class", "node").style("fill", function(d) {
return color(d.group);
}).call(force.drag);
// add the nodes
...
// add the links and the arrows
var path = node.selectAll("g").data(force.links(), function(d) {
return d.id
}).append("path")
// .attr("class", function(d) { return "link " + d.type; })
.attr("class", "link").attr("marker-end", "url(#end)");
The input data looks like this:
{
"nodes": [
{
"id": 0,
"name": "N1",
"group": 4
},
{
"id": 1,
"name": "N2",
"group": 1
},
{
"id": 2,
"name": "N3",
"group": 1
}
],
"links": [
{
"id": 0,
"source": 0,
"target": 1
},
{
"id": 0,
"source": 0,
"target": 2
}
]
}
I'm trying to modify this example
My goal is that a node has a mouse over (.node:hover) including all the out-going links. With my simple data it would look like that node N1 would have a mouse over including the two links.
Thanks for your help!
Here is all the code:
<!DOCTYPE html>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.js"></script>
<style>
.node {
opacity: 0.8;
stroke: #fff;
stroke-width: 1.5px;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
stroke-width: 0px;
}
.node:hover {
opacity: 1;
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
</style>
<body>
<script>
// get the data
d3.json("data2.json", function(error, graph) {
var color = d3.scale.category20();
var width = 960, height = 500;
var force = d3.layout.force().nodes(graph.nodes).links(graph.links)
.size([ width, height ]).linkDistance(300).charge(-300).on(
"tick", tick).start();
var svg = d3.select("body").append("svg").attr("width", width)
.attr("height", height);
// build the arrow.
svg.append("svg:defs").selectAll("marker").data([ "end" ]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String).attr("viewBox", "0 -5 10 10").attr("refX", 15)
.attr("refY", -1.5).attr("markerWidth", 6).attr(
"markerHeight", 6).attr("orient", "auto").append(
"svg:path").attr("d", "M0,-5L10,0L0,5");
// define the nodes
var node = svg.selectAll(".node").data(force.nodes()).enter()
.append("g").attr("id", function(d) {
return d.id;
}).attr("class", "node").style("fill", function(d) {
return color(d.group);
}).call(force.drag);
// add the nodes
node.append("circle").attr("r", function(d) {
return 3 * d.group
});
// add the text
node.append("text").attr("x", 12).attr("dy", ".35em").style(
"color", "black").text(function(d) {
return d.name;
});
// add the links and the arrows
var path = node.selectAll("g").data(force.links(), function(d) {
return d.id
}).append("path")
// .attr("class", function(d) { return "link " + d.type; })
.attr("class", "link").attr("marker-end", "url(#end)");
// add the curvy lines
function tick() {
path.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;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
});
</script>
</body>
</html>
I solved my issue with mouseover and mouseout:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta name="author" content="myborobudur">
<meta name="date" content="2014-09-02T00:00:00+01:00">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Job Recommender: d3js Graph Study</title>
<script src="http://d3js.org/d3.v3.js"></script>
<style>
.node {
opacity: 0.8;
stroke: #fff;
stroke-width: 1.5px;
}
.text {
pointer-events: none;
font: 12px sans-serif;
stroke-width: 0px;
color: #000;
}
.node:hover {
opacity: 1;
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<h2>Job Recommender: d3js Graph Study</h2>
<script>
function selectLinks(thisObject) {
changeLinkStyle(thisObject, function(node, link) {
link.style("stroke", node.attr("fillColor"));
link.style("stroke-width", "10px");
link.style("opacity", "0.5");
link.attr("marker-end", "");
});
}
function deSelectLinks(thisObject) {
changeLinkStyle(thisObject, function(node, link) {
link.style("stroke", "#666");
link.style("stroke-width", "1.5px");
link.style("opacity", "1");
link.attr("marker-end", "url(#end)");
});
}
function changeLinkStyle(thisObject, changeStyle) {
var source = d3.select(thisObject);
var sourceId = d3.select(thisObject).attr('id');
d3.selectAll('.link').each(function(d, i) {
var link = d3.select(this);
var linkSourceId = link.attr('source');
if (linkSourceId === sourceId) {
changeStyle.call(undefined, source, link);
}
});
}
// get the data
d3.json("data.json", function(error, graph) {
var color = d3.scale.category20();
var width = 1200, height = 800;
var force = d3.layout.force().nodes(graph.nodes).links(graph.links)
.size([ width, height ])
.linkDistance(300)
.charge(-300)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data([ "end" ]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", "link")
.attr("source", function(d) { return d.source.id })
.attr("marker-end", "url(#end)");
// define the nodes
var node = svg.selectAll(".node").data(force.nodes())
.enter().append("g").call(force.drag);
// add the nodes
node.append("circle").attr("id", function(d) { return d.id })
.attr("fillColor", function(d) { return color(d.group); })
.attr("onmouseover", "selectLinks(this)")
.attr("onmouseout", "deSelectLinks(this)")
.attr("class", "node")
.attr("r", function(d) { return 3 * d.group })
.style("fill", function(d) { return color(d.group); });
// add the text
node.append("text").attr("x", 12)
.attr("dy", ".35em")
.attr("class", "text")
.text(function(d) { return d.name; });
// add the curvy lines
function tick() {
path.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;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
});
</script>
</body>
</html>

Resources