Drag events not getting fired easily using d3-drag on svg elements - javascript

drawAll is a two dimensional array where drawAll[i] is an element/drawing containing all the strokes in that drawing. A stroke, represented as a string, is a series of 2D points that can be used to create a <path>.
I am trying to make each .element draggable to anywhere else in the #canvas svg. I have followed this answer, but my elements are not getting dragged easily---I have to try dragging multiple times before the drag events are getting fired. I'm not sure what is going wrong or how the elements could be dragged easily. Thanks in advance. :)
drawAll example with two elements (containing only one stroke per element):
var drawAll = [["M6,239 C6,239 46,149 88,67 127,6 135,0 140,19 143,74 151,129 158,156 185,221 184,243 166,251 127,255 52,252 0,246"],["M113,15 C113,15 45,95 28,114 23,115 0,148 48,166 114,184 255,184 208,125 178,75 147,12 122,0"]];
HTML:
<div id="wrapper">
<svg id="canvas" preserveAspectRatio="none" height="400" width="400" viewbox="0 0 2000 2000"></svg>
</div>
JS:
function getData(){
return new Promise(function(resolve, reject){
var data;
$.getJSON(Flask.url_for("data"))
.done(function(json){
data = json;
var drawAll = [];
var i, j;
var temp1 = data;
for (i in temp1){
var temp2 = temp1[i];
var drawOne = [];
for (j in temp2){
var temp3 = temp2[j][0];
var temp4 = temp2[j][1];
var points = "";
for (k in temp3){
if(k == 0)
{
points = points + "M" + temp3[k] + ",";
points = points + temp4[k] + " ";
points = points + "C" + temp3[k] + ",";
points = points + temp4[k] + " ";
}
else
{
points = points + temp3[k] + ",";
points = points + temp4[k] + " ";
}
}
drawOne.push(points);
}
drawAll.push(drawOne);
}
resolve(drawAll);
reject("Error in fetching JSON");
});
});
}
function draw(drawAll){
var width = 2000;
var spacing = 300;
var x = -spacing;
var temp = -spacing;
var y = 0;
var id = 0;
var elements = d3.select("#canvas");
var element = elements.selectAll("svg.element")
.data(drawAll)
.enter()
.append("svg")
.attr("class", "element")
.attr("id", function(){
return id++;
})
.attr("x", function(){
if(x + spacing > width)
x = -spacing;
x = x + spacing;
return x;
})
.attr("y", function(){
if(temp + spacing > width)
{
temp = -spacing;
y = y + spacing;
}
temp = temp + spacing;
return y;
})
.call(d3.drag()
.on("start", function(d){
d3.select(this).raise();
})
.on("drag", function(d){
d3.select(this)
.attr("x", d.x = d3.event.x)
.attr("y", d.y = d3.event.y);
})
.on("end", function(d){
}));
element.selectAll("path .stroke")
.data(function(d){
return d;
})
.enter()
.append("path")
.attr("class", "stroke")
.attr("d", function(d){
return d;
})
.attr("style", "fill: none; stroke: black; stroke-width: 2;");
return new Promise(function(resolve, reject){
resolve(drawAll);
reject("Error in drawing JSON");
});
}
function runner(){
return getData()
.then(draw)
.catch(function(msg){
console.log(msg);
});
}
runner().catch(function(msg){
console.log(msg);
});

Take a look at the pointer-events presentation attribute to tweak where events can be raised.
To hit a narrow stroke, you could also combine it with a wider, transparent line that is only there to catch the events:
var group = element.selectAll("g .stroke")
.data(function(d){
return d;
})
.enter()
.append("g")
.attr("style", "fill: none; stroke: black")
.attr("class", "stroke");
group.append("path")
.attr("d", function(d){
return d;
})
.attr("style", "stroke-width: 10; stroke-linecap: round; opacity: 0");
group.append("path")
.attr("d", function(d){
return d;
})
.attr("style", "stroke-width: 2;");

Related

exit().remove() doesn't remove node when it goes out of view

I'm using d3js to move circular dots from right to left with respect to current time. I have a couple of issues:
1. .exit().remove() don't work. Node doesn't get removed once it goes out of the view.
2. Transition of circles are a bit jumpy
var circles = g.selectAll('path')
circles.data(data)
.enter()
.append('path')
.attr("d", symbol.type(d3.symbolCircle))
.merge(circles)
.attr("transform", (d) => "translate(" + x(d.x) + "," + y(d.y) + ")");
circles.exit().remove();
You can see my full code here: http://jsfiddle.net/hsdhott/3tdhuLgm/
Besides your enter-exit-update pattern not being correct (please check the snippet bellow), the big problem here is the data:
selection.exit() method won't magically select — normally for using remove() later — an element based on any arbitrary criterion, such as "getting out of the view". It's based on the data only. And the problem is that your data never stops increasing:
if (count % 10 === 0) {
var point = {
x: globalX,
y: ((Math.random() * 200 + 50) >> 0)
};
data.push(point);
}
So, a very quick solution in this case is removing the data points based on your x domain:
data = data.filter(function(d) {
return d.x > globalX - 10000;
});
This is just a suggestion, use the logic you want for removing the objects from the data array. However, regardless the logic you use, you have to remove them.
Regarding the jumpy transition, the problem is that you're using selection.transition and setInterval. That won't work, chose one of them.
Here is your updated code:
var data = [];
var width = 500;
var height = 350;
var globalX = new Date().getTime();
/* var globalX = 0; */
var duration = 250;
var step = 10;
var count = 0;
var chart = d3.select('#chart')
.attr('width', width + 50)
.attr('height', height + 50);
var x = d3.scaleTime()
.domain([globalX, (globalX - 10000)])
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, 300])
.range([300, 0]);
// -----------------------------------
// Draw the axis
var xAxis = d3.axisBottom()
.scale(x)
.ticks(10)
.tickFormat(formatter);
var axisX = chart.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0, 300)')
.call(xAxis);
// Append the holder for line chart and circles
var g = chart.append('g');
function formatter(time) {
if ((time.getSeconds() % 5) != 0) {
return "";
}
return d3.timeFormat('%H:%M:%S')(time);
}
function createData() {
// Generate new data
var point = {
x: globalX,
y: ((Math.random() * 200 + 50) >> 0)
};
data.push(point);
}
function callInterval() {
count++;
if (count % 3 === 0) createData();
}
// Main loop
function tick() {
// Generate new data
if (count % 10 === 0) {
var point = {
x: globalX,
y: ((Math.random() * 200 + 50) >> 0)
};
data.push(point);
}
data = data.filter(function(d) {
return d.x > globalX - 10000;
});
count++;
globalX = new Date().getTime();
var timer = new Date().getTime();
var symbol = d3.symbol().size([100]),
color = d3.schemeCategory10;
var circles = g.selectAll('path')
.data(data);
circles = circles.enter()
.append('path')
.attr("d", symbol.type(d3.symbolCircle))
.merge(circles)
.attr("transform", (d) => "translate(" + x(d.x) + "," + y(d.y) + ")");
circles.exit().remove();
// Shift the chart left
x.domain([timer - 10000, timer]);
axisX.call(xAxis);
g.attr('transform', null)
.attr('transform', 'translate(' + x(globalX - 10000) + ')');
// Remote old data (max 50 points)
if (data.length && (data[data.length - 1].x < (globalX - 10000))) data.shift();
}
tick();
setInterval(tick, 10);
.axis {
font-family: sans-serif;
font-size: 12px;
}
.line {
fill: none;
stroke: #f1c40f;
stroke-width: 3px;
}
.circle {
stroke: #e74c3c;
stroke-width: 3px;
fill: #FFF;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg id="chart"></svg>

Cannot read property 'weight' of undefined and Cannot read property 'x' of undefined d3.js

I have created a fiddle here to replicate the issue: fiddle link
Prior to this we were using a nice flat json file in our project. The new file I have to work with is much more nested.
My d3 function looks like this:
/* chart data */
// return data for relationships between database tables
returnTableRelationshipData = function(){
var url = 'https://api.myjson.com/bins/7ovnb.json';
d3.json(url, function(data){
//find the node index
function find(f){
var i = -1
data.p.nodes.forEach(function(node, index){
node.x = 200 + Math.random()*200;
node.y = 150 + Math.random()*200;
if(node.properties.nodeID.val == f)
i = index;
});
return i;
}
//set the source and target index
data.p.relationships.forEach(function(d){
d.start = find(d.start);
d.end = find(d.end);
});
// used to store the number of links between two nodes.
var mLinkNum = {};
// sort links first
sortLinks();
// set up linkIndex and linkNumer, because it may possible multiple links share the same source and target node
setLinkIndexAndNum();
// check that we don't have empty or null values
checkDataNotEmpty();
var w = 1000;
var h = 400;
var force = d3.layout.force()
.nodes(data.p.nodes)
.links(data.p.relationships)
.alpha(.1)
.gravity(1)
.charge(-10000)
.size([w, h])
.start();
var svg = d3.select('.node-wrapper').append('svg')
.attr('width', w)
.attr('height', h);
var path = svg.append('svg:g')
.selectAll('path')
.data(force.links())
.enter().append('line')
.attr('class', 'link')
.attr('x1', function(d) {
return d.start.x;
})
.attr('y1', function(d) {
return d.start.y;
})
.attr('x2', function(d) {
return d.end.x;
})
.attr('y2', function(d) {
return d.end.y;
});
var node_drag = d3.behavior.drag()
.on('dragstart', dragstart)
.on('drag', dragmove)
.on('dragend', dragend);
var circle = svg.append('svg:g')
.selectAll('circle')
.data(force.nodes())
.enter().append('svg:circle')
.attr('r', 6)
.call(node_drag);
var text = svg.append('svg:g')
.selectAll('g')
.data(force.nodes())
.enter().append('svg:g');
text.append('svg:text')
.text(function(d){
return d.description;
});
force.on('tick', tick);
function tick() {
path.attr('x1', function(d) {
return d.start.x;
})
.attr('y1', function(d) {
return d.start.y;
})
.attr('x2', function(d) {
return d.end.x;
})
.attr('y2', function(d) {
return d.end.y;
});
circle.attr('transform', function(d){
return 'translate(' + d.x + ',' + d.y + ')';
});
text.attr('transform', function(d){
return 'translate(' + d.x + ',' + d.y + ')';
});
}
function dragstart(d, i) {
force.stop(); // stops the force auto positioning before you start dragging
}
function dragmove(d, i) {
d.px += d3.event.dx;
d.py += d3.event.dy;
d.x += d3.event.dx;
d.y += d3.event.dy;
tick();
}
function dragend(d, i) {
d.fixed = true; // of course set the node to fixed so the force doesn't include the node in its auto positioning stuff
tick();
}
// sort the links by source, then target
function sortLinks(){
if(data.p.relationships != null){
data.p.relationships.sort(function(a,b){
if(a.start > b.start){
return 1;
}else if(a.start < b.start){
return -1;
}else{
if(a.end > b.end){
return 1;
}if(a.end < b.end){
return -1;
}else{
return 0;
}
}
});
}
}
//any links with duplicate source and target get an incremented 'linknum'
function setLinkIndexAndNum(){
for(var i = 0; i < data.p.relationships.length; i++){
if(i != 0 &&
data.p.relationships[i].start == data.p.relationships[i-1].start &&
data.p.relationships[i].end == data.p.relationships[i-1].end){
data.p.relationships[i].linkindex = data.p.relationships[i-1].linkindex + 1;
}else{
data.p.relationships[i].linkindex = 1;
}// save the total number of links between two nodes
if(mLinkNum[data.p.relationships[i].end + ',' + data.p.relationships[i].start] !== undefined){
mLinkNum[data.p.relationships[i].end + ',' + data.p.relationships[i].start] = data.p.relationships[i].linkindex;
}else{
mLinkNum[data.p.relationships[i].start + ',' + data.p.relationships[i].end] = data.p.relationships[i].linkindex;
}
}
}
function checkDataNotEmpty(){
data.p.relationships.forEach(function(link, index, list) {
if (typeof link.start === 'undefined') {
console.log('undefined link', data.p.nodes[link.start]);
}
if (typeof link.end === 'undefined') {
console.log('undefined source', data.p.nodes[link.end]);
}
});
}
});
}();
As per this answer if I comment out these lines:
var force = d3.layout.force()
//.nodes(data.p.nodes)
//.links(data.p.relationships)
.alpha(.1)
.gravity(1)
...
Then an svg object is appended to the html.
The answer linked doesn't actually provide a solution to this.
Both nodes and links appear to be iterated over properly.
My only hunch is that somehow I have to map "start" and "end" to source and target, or I need to transform data.p.nodes and data.p.relationships somehow. Or maybe the indexing is not working correctly.
I am able to work with a backend developer to change some of the json properties and types (string, integer and so on).
The json file is being called from here: http://myjson.com/7ovnb
Define a stroke for the links to make them visible:
.node-wrapper line {
stroke: #0D9E1E;
}
Rename all occurences of .start with .source and of .end with .target in the tick() function.
In your find() function, you have to compare to the node.id property:
function find(f){
var i; // do not return an existing value as default
data.p.nodes.forEach(function(node, index){
node.x = 200 + Math.random()*200;
node.y = 150 + Math.random()*200;
if(node.id == f)
i = index;
});
return i;
}
Index 0 if is an existing node index, so it would be a better idea not to set it as default.

How to add legend to sequence sunburst chart of d3js for my own data?

Legend Color not matching Screenshot I am Working on the d3js sequence sunburst chart.
My Sample Data is:
Shift-Mode-Machineid,MachineDuration
DayShift-auto-1091,1
DayShift-auto-1100,5
DayShift-auto-1100,117
DayShift-manual-1111,4
DayShift-manual-1112,6
DayShift-manual-1120,43
NightShift-auto-1150,9
NightShift-auto-1150,85
NightShift-auto-1150,6
NightShift-manual-1120,16
NightShift-manual-1120,1
NightShift-manual-1120,4
Please suggest how to match the legend color and the chart color. The text inside the legend is perfect but the color is a mismatch. I have to put correct colors. Please help.
// Dimensions of sunburst.[Screenshot of wrong color in legend][1]
var width = 750;
var height = 600;
var radius = Math.min(width, height) / 2;
// Breadcrumb dimensions: width, height, spacing, width of tip/tail.
var b = {
w: 75, h: 30, s: 3, t: 10
};
// Mapping of step names to colors.
var colors = {};
var color = d3.scale.category20b();
/*{
"home": "#5687d1",
"product": "#7b615c",
"search": "#de783b",
"account": "#6ab975",
"other": "#a173d1",
"end": "#bbbbbb"
}*/;
// Total size of all segments; we set this later, after loading the data.
var totalSize = 0;
var vis = d3.select("#chart").append("svg:svg")
.attr("width", width)
.attr("height", height)
.append("svg:g")
.attr("id", "container")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var partition = d3.layout.partition()
.size([2 * Math.PI, radius * radius])
.value(function(d) { return d.size; });
var arc = d3.svg.arc()
.startAngle(function(d) { return d.x; })
.endAngle(function(d) { return d.x + d.dx; })
.innerRadius(function(d) { return Math.sqrt(d.y); })
.outerRadius(function(d) { return Math.sqrt(d.y + d.dy); });
// Use d3.text and d3.csv.parseRows so that we do not need to have a header
// row, and can receive the csv as an array of arrays.
d3.text("Data.csv", function(text) {
var csv = d3.csv.parseRows(text);
var json = buildHierarchy(csv);
/*After getting proper json, we are making colors variable to hold our data keys
*/
json.children.forEach(function(root, ind){
colors[root.name]=color(ind);
});
createVisualization(json);
});
// Main function to draw and set up the visualization, once we have the data.
function createVisualization(json) {
// Basic setup of page elements.
initializeBreadcrumbTrail();
drawLegend();
d3.select("#togglelegend").on("click", toggleLegend);
// Bounding circle underneath the sunburst, to make it easier to detect
// when the mouse leaves the parent g.
vis.append("svg:circle")
.attr("r", radius)
.style("opacity", 0);
// For efficiency, filter nodes to keep only those large enough to see.
var nodes = partition.nodes(json)
.filter(function(d) {
return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
});
var path = vis.data([json]).selectAll("path")
.data(nodes)
.enter().append("svg:path")
.attr("display", function(d) { return d.depth ? null : "none"; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
//.style("fill", function(d) { return colors[d.name]; })
.style("fill", function(d) { return color((d.parent ? d : d.children).name); })
.style("opacity", 1)
.on("mouseover", mouseover);
// Add the mouseleave handler to the bounding circle.
d3.select("#container").on("mouseleave", mouseleave);
// Get total size of the tree = value of root node from partition.
totalSize = path.node().__data__.value;
};
// Fade all but the current sequence, and show it in the breadcrumb trail.
function mouseover(d) {
var percentage = (100 * d.value / totalSize).toPrecision(3);
var percentageString = percentage + "%";
if (percentage < 0.1) {
percentageString = "< 0.1%";
}
d3.select("#percentage")
.text(percentageString);
d3.select("#explanation")
.style("visibility", "");
var sequenceArray = getAncestors(d);
updateBreadcrumbs(sequenceArray, percentageString);
// Fade all the segments.
d3.selectAll("path")
.style("opacity", 0.3);
// Then highlight only those that are an ancestor of the current segment.
vis.selectAll("path")
.filter(function(node) {
return (sequenceArray.indexOf(node) >= 0);
})
.style("opacity", 1);
}
// Restore everything to full opacity when moving off the visualization.
function mouseleave(d) {
// Hide the breadcrumb trail
d3.select("#trail")
.style("visibility", "hidden");
// Deactivate all segments during transition.
d3.selectAll("path").on("mouseover", null);
// Transition each segment to full opacity and then reactivate it.
d3.selectAll("path")
.transition()
.duration(1000)
.style("opacity", 1)
.each("end", function() {
d3.select(this).on("mouseover", mouseover);
});
d3.select("#explanation")
.style("visibility", "hidden");
}
// Given a node in a partition layout, return an array of all of its ancestor
// nodes, highest first, but excluding the root.
function getAncestors(node) {
var path = [];
var current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
function initializeBreadcrumbTrail() {
// Add the svg area.
var trail = d3.select("#sequence").append("svg:svg")
.attr("width", width)
.attr("height", 50)
.attr("id", "trail");
// Add the label at the end, for the percentage.
trail.append("svg:text")
.attr("id", "endlabel")
.style("fill", "#000");
}
// Generate a string that describes the points of a breadcrumb polygon.
function breadcrumbPoints(d, i) {
var points = [];
points.push("0,0");
points.push(b.w + ",0");
points.push(b.w + b.t + "," + (b.h / 2));
points.push(b.w + "," + b.h);
points.push("0," + b.h);
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
points.push(b.t + "," + (b.h / 2));
}
return points.join(" ");
}
// Update the breadcrumb trail to show the current sequence and percentage.
function updateBreadcrumbs(nodeArray, percentageString) {
// Data join; key function combines name and depth (= position in sequence).
var g = d3.select("#trail")
.selectAll("g")
.data(nodeArray, function(d) { return d.name + d.depth; });
// Add breadcrumb and label for entering nodes.
var entering = g.enter().append("svg:g");
entering.append("svg:polygon")
.attr("points", breadcrumbPoints)
// .style("fill", function(d) { return colors[d.name]; });
.style("fill", function(d) { return color((d.parent ? d : d.children).name); });
entering.append("svg:text")
.attr("x", (b.w + b.t) / 2)
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name; });
// Set position for entering and updating nodes.
g.attr("transform", function(d, i) {
return "translate(" + i * (b.w + b.s) + ", 0)";
});
// Remove exiting nodes.
g.exit().remove();
// Now move and update the percentage at the end.
d3.select("#trail").select("#endlabel")
.attr("x", (nodeArray.length + 0.5) * (b.w + b.s))
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(percentageString);
// Make the breadcrumb trail visible, if it's hidden.
d3.select("#trail")
.style("visibility", "");
}
function drawLegend() {
// Dimensions of legend item: width, height, spacing, radius of rounded rect.
var li = {
w: 75, h: 30, s: 3, r: 3
};
var legend = d3.select("#legend").append("svg:svg")
.attr("width", li.w)
.attr("height", d3.keys(colors).length * (li.h + li.s));
var g = legend.selectAll("g")
.data(d3.entries(colors))
.enter().append("svg:g")
.attr("transform", function(d, i) {
return "translate(0," + i * (li.h + li.s) + ")";
});
g.append("svg:rect")
.attr("rx", li.r)
.attr("ry", li.r)
.attr("width", li.w)
.attr("height", li.h)
.style("fill", function(d) { return d.value; });
g.append("svg:text")
.attr("x", li.w / 2)
.attr("y", li.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.key; });
}
function toggleLegend() {
var legend = d3.select("#legend");
if (legend.style("visibility") == "hidden") {
legend.style("visibility", "");
} else {
legend.style("visibility", "hidden");
}
}
// Take a 2-column CSV and transform it into a hierarchical structure suitable
// for a partition layout. The first column is a sequence of step names, from
// root to leaf, separated by hyphens. The second column is a count of how
// often that sequence occurred.
function buildHierarchy(csv) {
var root = {"name": "root", "children": []};
for (var i = 0; i < csv.length; i++) {
var sequence = csv[i][0];
var size = +csv[i][1];
if (isNaN(size)) { // e.g. if this is a header row
continue;
}
var parts = sequence.split("-");
var currentNode = root;
for (var j = 0; j < parts.length; j++) {
var children = currentNode["children"];
var nodeName = parts[j];
var childNode;
if (j + 1 < parts.length) {
// Not yet at the end of the sequence; move down the tree.
var foundChild = false;
for (var k = 0; k < children.length; k++) {
if (children[k]["name"] == nodeName) {
childNode = children[k];
foundChild = true;
break;
}
}
// If we don't already have a child node for this branch, create it.
if (!foundChild) {
childNode = {"name": nodeName, "children": []};
children.push(childNode);
}
currentNode = childNode;
} else {
// Reached the end of the sequence; create a leaf node.
childNode = {"name": nodeName, "size": size};
children.push(childNode);
}
}
}
return root;
};
After doing some analysis, I could suggest you below code, try it once.
// Dimensions of sunburst.
var width = 750;
var height = 600;
var radius = Math.min(width, height) / 2;
// Breadcrumb dimensions: width, height, spacing, width of tip/tail.
var b = {
w: 75, h: 30, s: 3, t: 10
};
// Mapping of step names to colors.
var colors = {};/*{
"home": "#5687d1",
"product": "#7b615c",
"search": "#de783b",
"account": "#6ab975",
"other": "#a173d1",
"end": "#bbbbbb"
}*/;
// Total size of all segments; we set this later, after loading the data.
var totalSize = 0;
var vis = d3.select("#chart").append("svg:svg")
.attr("width", width)
.attr("height", height)
.append("svg:g")
.attr("id", "container")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var partition = d3.layout.partition()
.size([2 * Math.PI, radius * radius])
.value(function(d) { return d.size; });
var arc = d3.svg.arc()
.startAngle(function(d) { return d.x; })
.endAngle(function(d) { return d.x + d.dx; })
.innerRadius(function(d) { return Math.sqrt(d.y); })
.outerRadius(function(d) { return Math.sqrt(d.y + d.dy); });
// Use d3.text and d3.csv.parseRows so that we do not need to have a header
// row, and can receive the csv as an array of arrays.
d3.text("visit-sequences.csv", function(text) {
var csv = d3.csv.parseRows(text);
var json = buildHierarchy(csv);
/*After getting proper json, we are making colors variable to hold our data keys
*/
json.children.forEach(function(root, ind){
colors[root.name]=color(root.name); //Changed ind to root.name
});
createVisualization(json);
});
// Main function to draw and set up the visualization, once we have the data.
function createVisualization(json) {
// Basic setup of page elements.
initializeBreadcrumbTrail();
drawLegend();
d3.select("#togglelegend").on("click", toggleLegend);
// Bounding circle underneath the sunburst, to make it easier to detect
// when the mouse leaves the parent g.
vis.append("svg:circle")
.attr("r", radius)
.style("opacity", 0);
// For efficiency, filter nodes to keep only those large enough to see.
var nodes = partition.nodes(json)
.filter(function(d) {
return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
});
var path = vis.data([json]).selectAll("path")
.data(nodes)
.enter().append("svg:path")
.attr("display", function(d) { return d.depth ? null : "none"; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", function(d) { return colors[d.name]; })
.style("opacity", 1)
.on("mouseover", mouseover);
// Add the mouseleave handler to the bounding circle.
d3.select("#container").on("mouseleave", mouseleave);
// Get total size of the tree = value of root node from partition.
totalSize = path.node().__data__.value;
};
// Fade all but the current sequence, and show it in the breadcrumb trail.
function mouseover(d) {
var percentage = (100 * d.value / totalSize).toPrecision(3);
var percentageString = percentage + "%";
if (percentage < 0.1) {
percentageString = "< 0.1%";
}
d3.select("#percentage")
.text(percentageString);
d3.select("#explanation")
.style("visibility", "");
var sequenceArray = getAncestors(d);
updateBreadcrumbs(sequenceArray, percentageString);
// Fade all the segments.
d3.selectAll("path")
.style("opacity", 0.3);
// Then highlight only those that are an ancestor of the current segment.
vis.selectAll("path")
.filter(function(node) {
return (sequenceArray.indexOf(node) >= 0);
})
.style("opacity", 1);
}
// Restore everything to full opacity when moving off the visualization.
function mouseleave(d) {
// Hide the breadcrumb trail
d3.select("#trail")
.style("visibility", "hidden");
// Deactivate all segments during transition.
d3.selectAll("path").on("mouseover", null);
// Transition each segment to full opacity and then reactivate it.
d3.selectAll("path")
.transition()
.duration(1000)
.style("opacity", 1)
.each("end", function() {
d3.select(this).on("mouseover", mouseover);
});
d3.select("#explanation")
.style("visibility", "hidden");
}
// Given a node in a partition layout, return an array of all of its ancestor
// nodes, highest first, but excluding the root.
function getAncestors(node) {
var path = [];
var current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
function initializeBreadcrumbTrail() {
// Add the svg area.
var trail = d3.select("#sequence").append("svg:svg")
.attr("width", width)
.attr("height", 50)
.attr("id", "trail");
// Add the label at the end, for the percentage.
trail.append("svg:text")
.attr("id", "endlabel")
.style("fill", "#000");
}
// Generate a string that describes the points of a breadcrumb polygon.
function breadcrumbPoints(d, i) {
var points = [];
points.push("0,0");
points.push(b.w + ",0");
points.push(b.w + b.t + "," + (b.h / 2));
points.push(b.w + "," + b.h);
points.push("0," + b.h);
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
points.push(b.t + "," + (b.h / 2));
}
return points.join(" ");
}
// Update the breadcrumb trail to show the current sequence and percentage.
function updateBreadcrumbs(nodeArray, percentageString) {
// Data join; key function combines name and depth (= position in sequence).
var g = d3.select("#trail")
.selectAll("g")
.data(nodeArray, function(d) { return d.name + d.depth; });
// Add breadcrumb and label for entering nodes.
var entering = g.enter().append("svg:g");
entering.append("svg:polygon")
.attr("points", breadcrumbPoints)
.style("fill", function(d) { return colors[d.name]; });
entering.append("svg:text")
.attr("x", (b.w + b.t) / 2)
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name; });
// Set position for entering and updating nodes.
g.attr("transform", function(d, i) {
return "translate(" + i * (b.w + b.s) + ", 0)";
});
// Remove exiting nodes.
g.exit().remove();
// Now move and update the percentage at the end.
d3.select("#trail").select("#endlabel")
.attr("x", (nodeArray.length + 0.5) * (b.w + b.s))
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(percentageString);
// Make the breadcrumb trail visible, if it's hidden.
d3.select("#trail")
.style("visibility", "");
}
function drawLegend() {
// Dimensions of legend item: width, height, spacing, radius of rounded rect.
var li = {
w: 75, h: 30, s: 3, r: 3
};
var legend = d3.select("#legend").append("svg:svg")
.attr("width", li.w)
.attr("height", d3.keys(colors).length * (li.h + li.s));
var g = legend.selectAll("g")
.data(d3.entries(colors))
.enter().append("svg:g")
.attr("transform", function(d, i) {
return "translate(0," + i * (li.h + li.s) + ")";
});
g.append("svg:rect")
.attr("rx", li.r)
.attr("ry", li.r)
.attr("width", li.w)
.attr("height", li.h)
.style("fill", function(d) { return d.value; });
g.append("svg:text")
.attr("x", li.w / 2)
.attr("y", li.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.key; });
}
function toggleLegend() {
var legend = d3.select("#legend");
if (legend.style("visibility") == "hidden") {
legend.style("visibility", "");
} else {
legend.style("visibility", "hidden");
}
}
// Take a 2-column CSV and transform it into a hierarchical structure suitable
// for a partition layout. The first column is a sequence of step names, from
// root to leaf, separated by hyphens. The second column is a count of how
// often that sequence occurred.
function buildHierarchy(csv) {
var root = {"name": "root", "children": []};
for (var i = 0; i < csv.length; i++) {
var sequence = csv[i][0];
var size = +csv[i][1];
if (isNaN(size)) { // e.g. if this is a header row
continue;
}
var parts = sequence.split("-");
var currentNode = root;
for (var j = 0; j < parts.length; j++) {
var children = currentNode["children"];
var nodeName = parts[j];
var childNode;
if (j + 1 < parts.length) {
// Not yet at the end of the sequence; move down the tree.
var foundChild = false;
for (var k = 0; k < children.length; k++) {
if (children[k]["name"] == nodeName) {
childNode = children[k];
foundChild = true;
break;
}
}
// If we don't already have a child node for this branch, create it.
if (!foundChild) {
childNode = {"name": nodeName, "children": []};
children.push(childNode);
}
currentNode = childNode;
} else {
// Reached the end of the sequence; create a leaf node.
childNode = {"name": nodeName, "size": size};
children.push(childNode);
}
}
}
return root;
};
Try this code n tell me.
We need to change
json.children.forEach(function(root, ind){
colors[root.name]=color(ind); //Changed ind to root.name
});
so that
json.children.forEach(function(root, ind){
colors[root.name]=color(root.name);
});
Hope this is causing colors not to match.
Try n tell me now.

How to display two d3 wordclouds on one page

I'm trying to put two (or even more -> that depends on the users settings) d3 Wordclouds to one page. However I'll get the following result:
It seems, that the word list won't be parsed correct.
My code looks like this:
(The php $Block variable specifies the position, where the recent wordcloud should be shown.)
var container = "svg_<?php echo $Block;?>";
var w = $('#word_cloud_<?php echo $Block;?>').width();
var h = w*0.75;
if($( window ).width()<400){
var maxRange=20;
}else if($( window ).width()<800){
var maxRange=40;
}else if($( window ).width()<1200){
var maxRange=60;
}else if($( window ).width()>=1200){
var maxRange=95;
}
var list_<?php echo $Block;?>=<?php echo $jsonWort; ?>;
var wordSize=12;
var layout;
generate_<?php echo $Block;?>(list_<?php echo $Block;?>);
function generate_<?php echo $Block;?>(list) {
//Blacklist wird gefiltert!
var blacklist=["ein","sind"];
list=list.filter(function(x) { return blacklist.indexOf(x) < 0 });
// Liste wird verarbeitet
result = { };
for(i = 0; i < list.length; ++i) {
if(!result[list[i]])
result[list[i]] = 0;
++result[list[i]];
}
var newList = _.uniq(list);
var frequency_list = [];
var len = newList.length;
for (var i = 0; i < len; i++) {
var temp = newList[i];
frequency_list.push({
text : temp,
freq : result[newList[i]],
time : 0
});
}
frequency_list.sort(function(a,b) { return parseFloat(b.freq) - parseFloat(a.freq) } );
for(var t = 0 ; t < len ; t++)
{
var addTime = (100 * t) +500;
frequency_list[t].time=addTime;
}
for(i in frequency_list){
if(frequency_list[i].freq*wordSize > 160)
wordSize = 3;
}
var sizeScale = d3.scale.linear().domain([0, d3.max(frequency_list, function(d) { return d.freq} )]).range([5, maxRange]);
layout= d3.layout.cloud().size([w, h])
.words(frequency_list)
.padding(5)
.rotate(function() { return ~~(Math.random() * 2) * 90; })
.font("Impact")
.fontSize(function(d) { return sizeScale(d.freq); })
.on("end",draw)
.start();
}
function draw(words) {
var fill = d3.scale.category20();
d3.select(container).remove();
d3.select("#word_cloud_<?php echo $Block;?>").append(container)
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + [w/2, h/2] + ")")
.selectAll("text")
.data(words)
.enter().append("text")
.transition()
.duration(function(d) { return d.time} )
.attr('opacity', 1)
.style("font-size", function(d) { return d.size + "px"; })
.style("font-family", "Impact")
.style("fill", function(d, i) { return fill(i); })
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "rotate(" + d.rotate + ")";
})
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; });
}
I believe that the mistake has to be somewhere in
var sizeScale = d3.scale.linear().domain([0, d3.max(frequency_list, function(d) { return d.freq} )]).range([5, maxRange]);
layout= d3.layout.cloud().size([w, h])
.words(frequency_list)
.padding(5)
.rotate(function() { return ~~(Math.random() * 2) * 90; })
.font("Impact")
.fontSize(function(d) { return sizeScale(d.freq); })
.on("end",draw)
.start();
But I'm not able to find it.
You overwrite it because of the line
d3.select(container).remove();
With d3 you are basically creating complex SVGs on the fly. You can have as many elements as you want. As long as you do not remove them, they'll remain there. But you need to also make sure that they do not overlap. See here and (very cool!) here

Calendar heatmap: color not assigned

I have created a calendar heatmap, but the problem is that the data that falls into the brake "over 4500" is marked in black instead of the indicated color #3d3768. It looks like the color cannot be found for this category. Why?
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<title>Data Calendar</title>
<style>
.month {
fill: none;
stroke: #000;
stroke-width: 2px;
}
.day {
fill: #fff;
stroke: #ccc;
}
text {
font-family:sans-serif;
font-size:1.5em;
}
.dayLabel {
fill:#aaa;
font-size:0.8em;
}
.monthLabel {
text-anchor:middle;
font-size:0.8em;
fill:#aaa;
}
.yearLabel {
fill:#aaa;
font-size:1.2em;
}
.key {font-size:0.5em;}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<script>
var breaks=[1500,2500,3500,4500,4550];
var colours=["#e2dff1","#b7b1dd","#8b82c8","#584f95","#3d3768"];
//general layout information
var cellSize = 17;
var xOffset=20;
var yOffset=60;
var calY=50;//offset of calendar in each group
var calX=25;
var width = 960;
var height = 163;
var parseDate = d3.time.format("%d/%m/%y").parse;
format = d3.time.format("%d-%m-%Y");
toolDate = d3.time.format("%d/%b/%y");
d3.csv("data.csv", function(error, data) {
//set up an array of all the dates in the data which we need to work out the range of the data
var dates = new Array();
var values = new Array();
//parse the data
data.forEach(function(d) {
dates.push(parseDate(d.date));
values.push(d.value);
d.date=parseDate(d.date);
d.value=d.value;
d.year=d.date.getFullYear();//extract the year from the data
});
var yearlyData = d3.nest()
.key(function(d){return d.year;})
.entries(data);
var svg = d3.select("body").append("svg")
.attr("width","90%")
.attr("viewBox","0 0 "+(xOffset+width)+" 540")
//title
svg.append("text")
.attr("x",xOffset)
.attr("y",20)
.text(title);
//create an SVG group for each year
var cals = svg.selectAll("g")
.data(yearlyData)
.enter()
.append("g")
.attr("id",function(d){
return d.key;
})
.attr("transform",function(d,i){
return "translate(0,"+(yOffset+(i*(height+calY)))+")";
})
var labels = cals.append("text")
.attr("class","yearLabel")
.attr("x",xOffset)
.attr("y",15)
.text(function(d){return d.key});
//create a daily rectangle for each year
var rects = cals.append("g")
.attr("id","alldays")
.selectAll(".day")
.data(function(d) { return d3.time.days(new Date(parseInt(d.key), 0, 1), new Date(parseInt(d.key) + 1, 0, 1)); })
.enter().append("rect")
.attr("id",function(d) {
return "_"+format(d);
//return toolDate(d.date)+":\n"+d.value+" dead or missing";
})
.attr("class", "day")
.attr("width", cellSize)
.attr("height", cellSize)
.attr("x", function(d) {
return xOffset+calX+(d3.time.weekOfYear(d) * cellSize);
})
.attr("y", function(d) { return calY+(d.getDay() * cellSize); })
.datum(format);
//create day labels
var days = ['Su','Mo','Tu','We','Th','Fr','Sa'];
var dayLabels=cals.append("g").attr("id","dayLabels")
days.forEach(function(d,i) {
dayLabels.append("text")
.attr("class","dayLabel")
.attr("x",xOffset)
.attr("y",function(d) { return calY+(i * cellSize); })
.attr("dy","0.9em")
.text(d);
})
//let's draw the data on
var dataRects = cals.append("g")
.attr("id","dataDays")
.selectAll(".dataday")
.data(function(d){
return d.values;
})
.enter()
.append("rect")
.attr("id",function(d) {
return format(d.date)+":"+d.value;
})
.attr("stroke","#ccc")
.attr("width",cellSize)
.attr("height",cellSize)
.attr("x", function(d){return xOffset+calX+(d3.time.weekOfYear(d.date) * cellSize);})
.attr("y", function(d) { return calY+(d.date.getDay() * cellSize); })
.attr("fill", function(d) {
if (d.value<breaks[0]) {
return colours[0];
}
for (i=0;i<breaks.length+1;i++){
if (d.value>=breaks[i]&&d.value<breaks[i+1]){
return colours[i];
}
}
if (d.value>breaks.length-1){
return colours[breaks.length]
}
})
//append a title element to give basic mouseover info
dataRects.append("title")
.text(function(d) { return toolDate(d.date)+":\n"+d.value+units; });
//add montly outlines for calendar
cals.append("g")
.attr("id","monthOutlines")
.selectAll(".month")
.data(function(d) {
return d3.time.months(new Date(parseInt(d.key), 0, 1),
new Date(parseInt(d.key) + 1, 0, 1));
})
.enter().append("path")
.attr("class", "month")
.attr("transform","translate("+(xOffset+calX)+","+calY+")")
.attr("d", monthPath);
//retreive the bounding boxes of the outlines
var BB = new Array();
var mp = document.getElementById("monthOutlines").childNodes;
for (var i=0;i<mp.length;i++){
BB.push(mp[i].getBBox());
}
var monthX = new Array();
BB.forEach(function(d,i){
boxCentre = d.width/2;
monthX.push(xOffset+calX+d.x+boxCentre);
})
//create centred month labels around the bounding box of each month path
//create day labels
var months = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'];
var monthLabels=cals.append("g").attr("id","monthLabels")
months.forEach(function(d,i) {
monthLabels.append("text")
.attr("class","monthLabel")
.attr("x",monthX[i])
.attr("y",calY/1.2)
.text(d);
})
//create key
var key = svg.append("g")
.attr("id","key")
.attr("class","key")
.attr("transform",function(d){
return "translate("+xOffset+","+(yOffset-(cellSize*1.5))+")";
});
key.selectAll("rect")
.data(colours)
.enter()
.append("rect")
.attr("width",cellSize)
.attr("height",cellSize)
.attr("x",function(d,i){
return i*130;
})
.attr("fill",function(d){
return d;
});
key.selectAll("text")
.data(colours)
.enter()
.append("text")
.attr("x",function(d,i){
return cellSize+5+(i*130);
})
.attr("y","1em")
.text(function(d,i){
if (i<colours.length-1){
return "up to "+breaks[i] + "%";
} else {
return "over "+breaks[i-1] + "%";
}
});
});//end data load
//pure Bostock - compute and return monthly path data for any year
function monthPath(t0) {
var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0),
d0 = t0.getDay(), w0 = d3.time.weekOfYear(t0),
d1 = t1.getDay(), w1 = d3.time.weekOfYear(t1);
return "M" + (w0 + 1) * cellSize + "," + d0 * cellSize
+ "H" + w0 * cellSize + "V" + 7 * cellSize
+ "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize
+ "H" + (w1 + 1) * cellSize + "V" + 0
+ "H" + (w0 + 1) * cellSize + "Z";
}
</script>
</body>
</html>
Your color selection code could be reduced somewhat in size, and the use of a d3 scale to chose your color would make this much cleaner as well. But, ultimately, your problem lies here:
if (d.value>breaks.length-1){
return colours[breaks.length]
}
This section of code is only called when a color has not yet been returned, that is, for those values that are greater than 4550. While I am not sure why the if statement in this code block is needed, your arrays breaks and colours are the same length. Essentially you are returning array[array.length], which will not return a value as arrays are zero indexed. You probably want to return colours[colours.length-1] as you want the last specified colour.
Edit: Also, if you assign a color for those below your lowest break (which it appears you do) and those above your highest, you will need one more color than break value. Eg: A one break scale has two colors for those above/below the break. However, you may have intentionally applied colours[0] to both: values under breaks[0] and values between breaks[0] and breaks[1]:
if (d.value<breaks[0]) {
return colours[0];
}
for (i=0;i<breaks.length+1;i++){
if (d.value>=breaks[i]&&d.value<breaks[i+1]){
return colours[i];
}
}

Categories

Resources