how to wrap text and add space between polylines using d3.js - d3.js

I have found this donut chart example. it is good one but I am having trouble understanding it and I am having problems with long text (wrappping) and labels overlapping one another when text is wrapped?
https://plnkr.co/edit/sAbnep00GMRkx5Xey6gL?p=preview&preview
This is the code: I removed the css as it was taking a lot of space.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="normalize.css">
</head>
<body>
<div id="chart"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
<script>
(function(d3) {
'use strict';
var width = 660;
var height = 690;
var radius = 150;
var donutWidth = 75;
var legendRectSize = 18;
var legendSpacing = 4;
var color = d3.scale.category20(); //builtin range of colors
var s = d3.select('#chart')
.append('svg')
.attr('width', width)
.attr('height', height);
var legend_group = s.append('g').attr('transform',
'translate(' + (width / 3) + ',' + (height / 1.4) + ')');
var svg = s.append('g')
.attr('transform', 'translate(' + (width / 2) +
',' + (radius) + ')');
var arc = d3.svg.arc()
.innerRadius(radius - donutWidth)
.outerRadius(radius);
var outerArc = d3.svg.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
var pie = d3.layout.pie()
.value(function(d) {
console.log(d);
return +d.count;
})
.sort(null);
var tooltip = d3.select('#chart')
.append('div')
.attr('class', 'tooltip');
tooltip.append('div')
.attr('class', 'label');
tooltip.append('div')
.attr('class', 'count');
tooltip.append('div')
.attr('class', 'percent');
d3.csv('weekdays.csv', function(error, dataset) {
dataset.forEach(function(d) {
d.count = +d.count;
d.enabled = true; // NEW
});
var path = svg.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d, i) {
return color(d.data.label);
}) // UPDATED (removed semicolon)
.each(function(d) {
this._current = d;
}); // NEW
path.on('mouseover', function(d) {
var total = d3.sum(dataset.map(function(d) {
return (d.enabled) ? d.count : 0; // UPDATED
}));
var percent = Math.round(1000 * d.data.count / total) / 10;
tooltip.select('.label').html(d.data.label);
tooltip.select('.count').html(d.data.count);
tooltip.select('.percent').html(percent + '%');
tooltip.style('display', 'block');
});
path.on('mouseout', function() {
tooltip.style('display', 'none');
});
var key = function(d) {
return d.data.label;
};
makeTexts();
makePolyLines();
/* OPTIONAL
path.on('mousemove', function(d) {
tooltip.style('top', (d3.event.pageY + 10) + 'px')
.style('left', (d3.event.pageX + 10) + 'px');
});
*/
var legend = legend_group.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * color.domain().length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color) // UPDATED (removed semicolon)
.on('click', function(label) { // NEW
var rect = d3.select(this); // NEW
var enabled = true; // NEW
var totalEnabled = d3.sum(dataset.map(function(d) { // NEW
return (d.enabled) ? 1 : 0; // NEW
})); // NEW
if (rect.attr('class') === 'disabled') { // NEW
rect.attr('class', ''); // NEW
} else { // NEW
if (totalEnabled < 2) return; // NEW
rect.attr('class', 'disabled'); // NEW
enabled = false; // NEW
} // NEW
pie.value(function(d) { // NEW
if (d.label === label) d.enabled = enabled; // NEW
return (d.enabled) ? d.count : 0; // NEW
}); // NEW
path = path.data(pie(dataset)); // NEW
path.transition() // NEW
.duration(750) // NEW
.attrTween('d', function(d) { // NEW
var interpolate = d3.interpolate(this._current, d); // NEW
this._current = interpolate(0); // NEW
return function(t) { // NEW
return arc(interpolate(t)); // NEW
}; // NEW
}); // NEW
makeTexts();
makePolyLines();
}); // NEW
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) {
return d;
});
function midAngle(d) {
return d.startAngle + (d.endAngle - d.startAngle) / 2;
}
function makeTexts() {
var text = svg.selectAll(".labels")
.data(pie(dataset), key);
text.enter()
.append("text")
.attr("dy", ".35em")
.classed("labels", true)
.text(function(d) {
return d.data.label + " (" + d.data.count + ")";
});
svg.selectAll(".labels").style("display", function(d) {
if (d.value == 0) {
return "none";
} else {
return "block";
}
});
text.transition().duration(1000)
.attrTween("transform", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2);
pos[0] = radius * (midAngle(d2) < Math.PI ? 1 : -1);
return "translate(" + pos + ")";
};
})
.styleTween("text-anchor", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
return midAngle(d2) < Math.PI ? "start" : "end";
};
});
text.exit()
.remove();
}
function makePolyLines() {
var polyline = svg.selectAll("polyline")
.data(pie(dataset), key);
polyline.enter()
.append("polyline");
svg.selectAll("polyline").style("display", function(d) {
console.log(d, "hello")
if (d.value == 0) {
return "none";
} else {
return "block";
}
});
polyline.transition().duration(1000)
.attrTween("points", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2);
pos[0] = radius * 0.95 * (midAngle(d2) < Math.PI ? 1 : -1);
return [arc.centroid(d2), outerArc.centroid(d2), pos];
};
});
polyline.exit()
.remove();
}
});
})(window.d3);
</script>
</body>
</html>
label,count
Testing with some long textAnd it conitues,3
Active_Integrated,286
Assigned,19
Active_not_Integrated,56
Assigned_Waiting,13
Complete,184
Dev_Waiting,17
Global_Screening,23
In Progress,14
In_Development,12
New,76
Pending_CTL_Approval,38
Test,1
Rejected,50
RETIRED with long text and contiues,37
This is the wrap function:
const wrap=(_text: { each: (arg0: (i: any, d: any, p: any) => void) => void; }, width: number)=> {
_text.each((d: any,i: any,nodes: any[])=> {
var text = d3.select(nodes[i]),
words = text.text().split(/\s+/).reverse(),
word,
line: string[] = [],
lineNumber = 0,
lineHeight = 1.1, // ems
y = text.attr("y"),
dy = parseFloat(text.attr("dy") || "0"),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan?.node()!.getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", lineHeight + "em").text(word);
}
}
});
}
If you uncheck some of the labels (see print screen below), it will move the labels and they start overlapping.
Any help, please?

Related

D3 Donut transition, d,i getting undefine

var arcMin = 75; // inner radius of the first arc
var arcWidth = 25; // width
var arcPad = 10; // padding between arcs
var arc = d3.arc()
.innerRadius(function(d, i) {
return arcMin + i*(arcWidth) + arcPad;
})
.outerRadius(function(d, i) {
return arcMin + (i+1)*(arcWidth);
})
.startAngle(0 * (PI/180))
.endAngle(function(d, i) {
// console.log(d); <----getting undefine under attrTween Call
return 2*PI*d.value/100;
});
var path = g.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d, i) {
return d.data.color;
})
.transition()
.delay(function(d, i) {
return i * 800;
});
// .attrTween('d', function(d) {
// // This part make my chart disapear
// var i = d3.interpolate(d.startAngle, d.endAngle);
// return function(t) {
// d.endAngle = i(t);
// return arc(d);
// }
// // This part make my chart disapear
// });
arc(d) always return "M0,0Z"..
I found that the reason is when calling arc under arcTween, all d,i return undefine. How can i solve this.
Codes here: https://jsfiddle.net/m8oupfne/3/
Final product:
Couple things:
At first glance your attrTween function doesn't work because your arc function is dependent on both d,i and you only pass d to it.
But, fixing that doesn't make your chart transition nicely? Why? Because your arc function doesn't seem to make any sense. You use pie to calculate angles and then overwrite them in your arc function. And each call to the arc function calculates endAngle the same since it's based on d.value.
So, if you want a custom angle calculation, don't call pie at all, but pre-calculate your endAngle and don't do it in your arc function.
arc becomes:
var arc = d3.arc()
.innerRadius(function(d, i) {
return arcMin + i*(arcWidth) + arcPad;
})
.outerRadius(function(d, i) {
return arcMin + (i+1)*(arcWidth);
});
Pre-calculate the data:
dataset.forEach(function(d,i){
d.endAngle = 2*PI*d.value/100;
d.startAngle = 0;
});
arcTween becomes:
.attrTween('d', function(d,i) {
var inter = d3.interpolate(d.startAngle, d.endAngle);
return function(t) {
d.endAngle = inter(t);
return arc(d,i);
}
});
Running code:
(function(d3) {
'use strict';
var dataset = [
{ label: 'a', value: 88, color : '#898989'},
{ label: 'b', value: 56 , color : '#898989'},
{ label: 'c', value: 20 , color : '#FDD000'},
{ label: 'd', value: 46 , color : '#898989'},
];
var PI = Math.PI;
var arcMin = 75; // inner radius of the first arc
var arcWidth = 25; // width
var arcPad = 10; // padding between arcs
var arcBgColor = "#DCDDDD";
var width = 360;
var height = 360;
var radius = Math.min(width, height) / 2;
var donutWidth = 15; // NEW
var svg = d3.select('#canvas')
.append('svg')
.attr('width', width)
.attr('height', height);
var gBg = svg.append('g').attr('transform', 'translate(' + (width / 2) +
',' + (height / 2) + ')');
var g = svg.append('g')
.attr('transform', 'translate(' + (width / 2) +
',' + (height / 2) + ')');
var arc = d3.arc()
.innerRadius(function(d, i) {
return arcMin + i*(arcWidth) + arcPad;
})
.outerRadius(function(d, i) {
return arcMin + (i+1)*(arcWidth);
});
var arcBg = d3.arc()
.innerRadius(function(d, i) {
return arcMin + i*(arcWidth) + arcPad;
})
.outerRadius(function(d, i) {
return arcMin + (i+1)*(arcWidth);
})
.startAngle(0 * (PI/180))
.endAngle(function(d, i) {
return 2*PI;
});
var pie = d3.pie()
.value(function(d) { return d.value; })
.sort(null);
var pathBg = gBg.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr('d', arcBg)
.attr('fill', arcBgColor );
dataset.forEach(function(d,i){
d.endAngle = 2*PI*d.value/100;
d.startAngle = 0;
});
var path = g.selectAll('path')
.data(dataset)
.enter()
.append('path')
.attr('fill', function(d, i) {
return d.color;
})
.transition()
.duration(800)
.delay(function(d, i) {
return i * 800;
})
.attrTween('d', function(d,i) {
var inter = d3.interpolate(d.startAngle, d.endAngle);
return function(t) {
d.endAngle = inter(t);
return arc(d,i);
}
});
})(window.d3);
<script src="https://cdn.jsdelivr.net/jquery/2.1.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/d3js/4.6.0/d3.min.js"></script>
<div id="canvas"></div>

how to handle null values in donut chart to avoid console errors [duplicate]

I have made a pie chart which works fine when all values are present,but when all values are made 0, in console i get 600+ errors saying:
Error: Invalid value for attribute transform="translate(NaN,NaN)"
Error: Invalid value for attribute d="M4.133182947122317e-15,-67.5A67.5,67.5 0 1,1 NaN,NaNL0,0Z"
I am unable to figure out. Please help.
var data = [
{label:"Category 1", value:0},
{label:"Category 2", value:0},
{label:"Category 3", value:0}
];
var colorRange = d3.scale.category20();
var color = d3.scale.ordinal()
.range(colorRange.range());
var width = 150;
var height = 150;
var radius = Math.min(height,width)/2;
var labelr = radius + 10;
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.outerRadius(width / 2 * 0.9)
.innerRadius(0);
var outerArc = d3.svg.arc()
.innerRadius(0)
.outerRadius(Math.min(width, height) / 2 * 0.9);
var legendRectSize = (radius * 0.05);
var legendSpacing = radius * 0.02;
var svg = d3.select(element[0]).append('svg')
.attr({width: width, height: height})
.append('g');
var div = d3.select("body").append("div").attr("class", "toolTip");
data.forEach(function (d) {
if(d.value == undefined || d.value == NaN){
d.value = 0;
}
});
svg.attr('transform', 'translate(' + 200 + ',' + height / 2 + ')');
svg.append("g")
.attr("class", "slices");
svg.append("g")
.attr("class", "labelName");
svg.append("g")
.attr("class", "labelValue");
svg.append("g")
.attr("class", "lines");
var slice = svg.select(".slices").selectAll("path.slice")
.data(pie(data), function(d){
return d.data.label
});
slice.enter()
.insert("path")
.style("fill", function(d) { return color(d.data.label); })
.attr("class", "slice");
slice
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
slice
.on("mousemove", function(d){
div.style("left", d3.event.pageX+10+"px");
div.style("top", d3.event.pageY-25+"px");
div.style("display", "inline-block");
div.html((d.data.label)+"<br>"+(d.data.value)+"%");
});
slice
.on("mouseout", function(d){
div.style("display", "none");
});
slice.exit()
.remove();
var legend = svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * color.domain().length / 2;
var horz = -3 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz/2 + ',' + 90 + ')';
});
/*legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d; });
------- TEXT LABELS -------*/
var text = svg.select(".labelName").selectAll("text")
.data(pie(data));
text.enter()
.append("text")
.attr("dy", ".35em")
.text(function(d) {
return (d.value+"%");
});
function midAngle(d){
return d.startAngle + (d.endAngle - d.startAngle)/2;
}
text
.transition().duration(1000)
.attrTween("transform", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2),
x = pos[0],
y = pos[1],
h = Math.sqrt(x*x + y*y);
return "translate(" + (x/h * labelr) + ',' + (y/h * labelr) + ")";
};
})
.styleTween("text-anchor", function(d){
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
return (d2.endAngle + d2.startAngle)/2 > Math.PI ? "end" : "start";
};
})
.text(function(d) {
return (d.value+"%");
});
text.exit()
.remove();
I deleted the objects in my dataset wherein the values were 0 and copied them into a new array so that the indices remain uniform and consistent.
var k;
function(object){
for (var key in object) {
if (object[key].value != 0) {
data[k] = object[key];
k++;
}
}
return data;
}
something like this- the pie chart would then only take the updated dataset

Error:Invalid value for <path> attribute d="MNaN,NaNA67.5,67.5 0 1,1 NaN,NaNL0,0Z"

I have made a pie chart which works fine when all values are present,but when all values are made 0, in console i get 600+ errors saying:
Error: Invalid value for attribute transform="translate(NaN,NaN)"
Error: Invalid value for attribute d="M4.133182947122317e-15,-67.5A67.5,67.5 0 1,1 NaN,NaNL0,0Z"
I am unable to figure out. Please help.
var data = [
{label:"Category 1", value:0},
{label:"Category 2", value:0},
{label:"Category 3", value:0}
];
var colorRange = d3.scale.category20();
var color = d3.scale.ordinal()
.range(colorRange.range());
var width = 150;
var height = 150;
var radius = Math.min(height,width)/2;
var labelr = radius + 10;
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.outerRadius(width / 2 * 0.9)
.innerRadius(0);
var outerArc = d3.svg.arc()
.innerRadius(0)
.outerRadius(Math.min(width, height) / 2 * 0.9);
var legendRectSize = (radius * 0.05);
var legendSpacing = radius * 0.02;
var svg = d3.select(element[0]).append('svg')
.attr({width: width, height: height})
.append('g');
var div = d3.select("body").append("div").attr("class", "toolTip");
data.forEach(function (d) {
if(d.value == undefined || d.value == NaN){
d.value = 0;
}
});
svg.attr('transform', 'translate(' + 200 + ',' + height / 2 + ')');
svg.append("g")
.attr("class", "slices");
svg.append("g")
.attr("class", "labelName");
svg.append("g")
.attr("class", "labelValue");
svg.append("g")
.attr("class", "lines");
var slice = svg.select(".slices").selectAll("path.slice")
.data(pie(data), function(d){
return d.data.label
});
slice.enter()
.insert("path")
.style("fill", function(d) { return color(d.data.label); })
.attr("class", "slice");
slice
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
slice
.on("mousemove", function(d){
div.style("left", d3.event.pageX+10+"px");
div.style("top", d3.event.pageY-25+"px");
div.style("display", "inline-block");
div.html((d.data.label)+"<br>"+(d.data.value)+"%");
});
slice
.on("mouseout", function(d){
div.style("display", "none");
});
slice.exit()
.remove();
var legend = svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * color.domain().length / 2;
var horz = -3 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz/2 + ',' + 90 + ')';
});
/*legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d; });
------- TEXT LABELS -------*/
var text = svg.select(".labelName").selectAll("text")
.data(pie(data));
text.enter()
.append("text")
.attr("dy", ".35em")
.text(function(d) {
return (d.value+"%");
});
function midAngle(d){
return d.startAngle + (d.endAngle - d.startAngle)/2;
}
text
.transition().duration(1000)
.attrTween("transform", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2),
x = pos[0],
y = pos[1],
h = Math.sqrt(x*x + y*y);
return "translate(" + (x/h * labelr) + ',' + (y/h * labelr) + ")";
};
})
.styleTween("text-anchor", function(d){
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
return (d2.endAngle + d2.startAngle)/2 > Math.PI ? "end" : "start";
};
})
.text(function(d) {
return (d.value+"%");
});
text.exit()
.remove();
I deleted the objects in my dataset wherein the values were 0 and copied them into a new array so that the indices remain uniform and consistent.
var k;
function(object){
for (var key in object) {
if (object[key].value != 0) {
data[k] = object[key];
k++;
}
}
return data;
}
something like this- the pie chart would then only take the updated dataset

MiniMap for d3.js collapsible tree

I am newbie to d3.js , I am working on a minimap for collapsible tree .
There is always a one click lag in collapsible tree minimap. When user clicks the first node followed by second node , minimap shows the image of first node when user clicks second node.
Could someone please help me with this ?
d3.demo = {};
/** CANVAS **/
d3.demo.canvas = function(width,height) {
"use strict";
var width = 500,
height = 500,
zoomEnabled = true,
dragEnabled = true,
scale = 1,
translation = [0,0],
base = null,
wrapperBorder = 2,
minimap = null,
minimapPadding = 20,
minimapScale = 0.25;
function canvas(selection) {
base = selection;
var xScale = d3.scale.linear()
.domain([-width / 2, width / 2])
.range([0, width]);
var yScale = d3.scale.linear()
.domain([-height / 2, height / 2])
.range([height, 0]);
var zoomHandler = function(newScale) {
if (!zoomEnabled) { return; }
if (d3.event) {
scale = d3.event.scale;
} else {
scale = newScale;
}
if (dragEnabled) {
var tbound = -height * scale,
bbound = height * scale,
lbound = -width * scale,
rbound = width * scale;
// limit translation to thresholds
translation = d3.event ? d3.event.translate : [0, 0];
translation = [
Math.max(Math.min(translation[0], rbound), lbound),
Math.max(Math.min(translation[1], bbound), tbound)
];
}
d3.select(".panCanvas, .panCanvas .bg")
.attr("transform", "translate(" + translation + ")" + " scale(" + scale + ")");
minimap.scale(scale).render();
}; // startoff zoomed in a bit to show pan/zoom rectangle
var zoom = d3.behavior.zoom()
.x(xScale)
.y(yScale)
.scaleExtent([0.5, 5])
.on("zoom.canvas", zoomHandler);
var svg = selection.append("svg")
.attr("class", "svg canvas")
.attr("width", width + (wrapperBorder*2) + minimapPadding*2 + (width*minimapScale))
.attr("height", height + (wrapperBorder*2) + minimapPadding*2)
.attr("shape-rendering", "auto");
var svgDefs = svg.append("defs");
svgDefs.append("clipPath")
.attr("id", "wrapperClipPath")
.attr("class", "wrapper clipPath")
.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
svgDefs.append("clipPath")
.attr("id", "minimapClipPath")
.attr("class", "minimap clipPath")
.attr("width", width)
.attr("height", height)
//.attr("transform", "translate(" + (width + minimapPadding) + "," + (minimapPadding/2) + ")")
.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
var filter = svgDefs.append("svg:filter")
.attr("id", "minimapDropShadow")
.attr("x", "-20%")
.attr("y", "-20%")
.attr("width", "150%")
.attr("height", "150%");
filter.append("svg:feOffset")
.attr("result", "offOut")
.attr("in", "SourceGraphic")
.attr("dx", "1")
.attr("dy", "1");
filter.append("svg:feColorMatrix")
.attr("result", "matrixOut")
.attr("in", "offOut")
.attr("type", "matrix")
.attr("values", "0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.5 0");
filter.append("svg:feGaussianBlur")
.attr("result", "blurOut")
.attr("in", "matrixOut")
.attr("stdDeviation", "10");
filter.append("svg:feBlend")
.attr("in", "SourceGraphic")
.attr("in2", "blurOut")
.attr("mode", "normal");
var minimapRadialFill = svgDefs.append("radialGradient")
.attr({
id:"minimapGradient",
gradientUnits:"userSpaceOnUse",
cx:"500",
cy:"500",
r:"400",
fx:"500",
fy:"500"
});
minimapRadialFill.append("stop")
.attr("offset", "0%")
.attr("stop-color", "#FFFFFF");
minimapRadialFill.append("stop")
.attr("offset", "40%")
.attr("stop-color", "#EEEEEE");
minimapRadialFill.append("stop")
.attr("offset", "100%")
.attr("stop-color", "#E0E0E0");
var outerWrapper = svg.append("g")
.attr("class", "wrapper outer")
.attr("transform", "translate(0, " + minimapPadding + ")");
outerWrapper.append("rect")
.attr("class", "background")
.attr("width", width + wrapperBorder*2)
.attr("height", height + wrapperBorder*2);
var innerWrapper = outerWrapper.append("g")
.attr("class", "wrapper inner")
.attr("clip-path", "url(#wrapperClipPath)")
.attr("transform", "translate(" + (wrapperBorder) + "," + (wrapperBorder) + ")")
.call(zoom);
innerWrapper.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
var panCanvas = innerWrapper.append("g")
.attr("class", "panCanvas")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(0,0)");
panCanvas.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
minimap = d3.demo.minimap()
.zoom(zoom)
.target(panCanvas)
.minimapScale(minimapScale)
.x(width + minimapPadding)
.y(minimapPadding);
svg.call(minimap);
// startoff zoomed in a bit to show pan/zoom rectangle
zoom.scale(1.75);
zoomHandler(1.75);
/** ADD SHAPE **/
canvas.addItem = function(item) {
panCanvas.node().appendChild(item.node());
minimap.render();
};
canvas.loadTree = function (divID,treeData,height,width) {
var totalNodes = 0;
var maxLabelLength = 0;
// Misc. variables
var i = 0;
var duration = 750;
var root,
rootNode;
// size of the diagram
var viewerWidth = width;
var viewerHeight = height;
var tree = d3.layout.tree()
.size([viewerHeight, viewerWidth]);
// define a d3 diagonal projection for use by the node paths later on.
var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.y, d.x];
});
// A recursive helper function for performing some setup by walking through all nodes
function visit(parent, visitFn, childrenFn) {
if (!parent)
return;
visitFn(parent);
var children = childrenFn(parent);
if (children) {
var count = children.length;
for (var i = 0; i < count; i++) {
visit(children[i], visitFn, childrenFn);
}
}
}
// Call visit function to establish maxLabelLength
visit(treeData, function (d) {
totalNodes++;
maxLabelLength = Math.max(d.name.length, maxLabelLength);
}, function (d) {
return d.children && d.children.length > 0 ? d.children : null;
});
// sort the tree according to the node names
function sortTree() {
tree.sort(function (a, b) {
return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1;
});
}
// Sort the tree initially incase the JSON isn't in a sorted order.
sortTree();
// Define the zoom function for the zoomable tree
/*function zoom() {
svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}*/
// define the zoomListener which calls the zoom function on the "zoom" event constrained within the scaleExtents
//var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);
// define the baseSvg, attaching a class for styling and the zoomListener
var baseSvg =panCanvas.append("g");
// Helper functions for collapsing and expanding nodes.
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
function expand(d) {
if (d._children) {
d.children = d._children;
d.children.forEach(expand);
d._children = null;
}
}
// Function to center node when clicked/dropped so node doesn't get lost when collapsing/moving with large amount of children.
// Toggle children function
function toggleChildren(d) {
if (d.children) {
d._children = d.children;
d.children = null;
update(d);
minimap.render();
//centerNode(d);
} else if (d._children) {
d.children = d._children;
d._children = null;
update(d);
minimap.render();
//centerNode(d);
} else {
d.children = null;
var json = {
"useCase" : d.useCase,
"chartType" : d.chartType,
"type" : d.type,
"assetId" : d.assetId,
"name" : d.name,
"childQueriesWithDelim" : d.childQueriesWithDelim,
"imgSrc" : d.imgSrc
};
window.parameterJsonData = JSON.stringify(json);
window.getDataMethod();
window.setChildData = function (childData) {
var childObj = getObjects(childData, 'name', d.name);
if (childObj != null) {
var newnodes = tree.nodes(childObj[0].children).reverse();
d.children = newnodes[0];
update(d);
minimap.render();
//centerNode(d);
}
}
}
}
// Toggle children on click.
function click(d) {
//if (d3.event.defaultPrevented)
//return; // click suppressed
$('#loading' + d.id).show();
toggleChildren(d);
}
function update(source) {
// Compute the new height, function counts total children of root node and sets tree height accordingly.
// This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed
// This makes the layout more consistent.
$('#loading' + source.id).hide();
var levelWidth = [1];
var childCount = function (level, n) {
if (n.children && n.children.length > 0) {
if (levelWidth.length <= level + 1)
levelWidth.push(0);
levelWidth[level + 1] += n.children.length;
n.children.forEach(function (d) {
childCount(level + 1, d);
});
}
};
childCount(0, root);
var newHeight = d3.max(levelWidth) * 25; // 25 pixels per line
tree = tree.size([newHeight, viewerWidth]);
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Set widths between levels based on maxLabelLength.
nodes.forEach(function (d) {
//d.y = (d.depth * (maxLabelLength * 30)); //maxLabelLength * 10px
// alternatively to keep a fixed scale one can set a fixed depth per level
// Normalize for fixed-depth by commenting out below line
d.y = (d.depth * 150); //500px per level.
});
// Update the nodes…
var node = svgGroup.selectAll("g.node")
.data(nodes, function (d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes 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);
/*nodeEnter.append("circle")
.attr('class', 'nodeCircle')
.attr("r", 0)
.style("fill", function (d) {
return d.hasChild ? "lightsteelblue" : "#fff";
});*/
nodeEnter.append("svg:image")
.attr("class", "nodeCircle")
.attr("xlink:href", function (d) {
return d.imgSrc;
})
.attr("x", "-8px")
.attr("y", "-8px")
.attr("width", function (d) {
if (d.id == rootNode.id) {
return "40px";
} else {
return "16px";
}
})
.attr("height", function (d) {
if (d.id == rootNode.id) {
return "40px";
} else {
return "16px";
}
});
nodeEnter.append("foreignObject").attr("width", 100)
.attr("height", 100).attr("id", function (d) {
return "loading" + d.id;
}).style("display", "none")
.append("xhtml:div").html(
"<img src=\"d3/images/loading.gif\"/>");
nodeEnter.append("a")
.attr("xlink:href", function (d) {
return d.url;
})
.on("mousedown.zoom", function (d) {
if (d.url != null) {
disableDrag();
}
})
.append("text")
.attr("x", function (d) {
return d.hasChild ? -10 : 10;
})
.attr("dy", ".02em")
.attr('class', 'nodeText')
.attr("text-anchor", function (d) {
return d.hasChild ? "end" : "start";
})
.text(function (d) {
var name = d.name.substr(0, d.truncationLimit);
if (d.name != null && d.name.length > d.truncationLimit) {
name = name.concat("...");
}
return name;
})
.style("fill-opacity", 0)
.on("mouseover", function (d) {
var res = d.description ? d.description.split(",") : null;
var desc = "";
for (var i = 0; res != null && i < res.length; i++) {
desc = desc + '<div>' + res[i] + '</div>';
}
if (d.description == null) {
desc = '<div>Name : ' + d.name + '</div>';
}
tooltip.show([d3.event.clientX, d3.event.clientY], desc);
})
.on('mouseout', function () {
tooltip.cleanup()
});
/*nodeEnter.append("foreignObject")
.attr('x', 10)
.attr("width", 100)
.attr("height", 200)
.append("xhtml:p")
.attr('style', 'word-wrap: break-word; text-align:center;')
.append("xhtml:a")
.attr("xlink:href", function (d) {
return d.url;
})
.html(function (d) {
return d.name;
});*/
// Update the text to reflect whether node has children or not.
node.select('text')
.attr("x", function (d) {
return d.hasChild ? -10 : 10;
})
.attr("dy", ".02em")
.attr("text-anchor", function (d) {
return d.hasChild ? "end" : "start";
})
.text(function (d) {
var name = d.name.substr(0, d.truncationLimit);
if (d.name != null && d.name.length > d.truncationLimit) {
name = name.concat("...");
}
return name;
});
// Change the circle fill depending on whether it has children and is collapsed
/*node.select("circle.nodeCircle")
.attr("r", 4.5);*/
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Fade the text in
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
/*nodeExit.select("circle")
.attr("r", 0);*/
nodeExit.select("text")
.style("fill-opacity", 0);
// Update the links…
var link = svgGroup.selectAll("path.link")
.data(links, function (d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function (d) {
var o = {
x : source.x0,
y : source.y0
};
return diagonal({
source : o,
target : o
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function (d) {
var o = {
x : source.x,
y : source.y
};
return diagonal({
source : o,
target : o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function (d) {
d.x0 = d.x;
d.y0 = d.y;
});
//canvas.addItem(svgGroup);
minimap.render();
}
// Append a group which holds all nodes and which the zoom Listener can act upon.
var svgGroup = baseSvg.append("g");
// Define the root
root = treeData;
rootNode = treeData;
root.x0 = viewerHeight / 2;
root.y0 = 0;
// Layout the tree initially and center on the root node.
update(root);
function disableDrag() {
baseSvg.on("mousedown.zoom", null);
}
function getObjects(obj, key, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i))
continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getObjects(obj[i], key, val));
} else if (i == key && obj[key] == val) {
objects.push(obj);
}
}
return objects;
}
//d3.select(self.frameElement).style("height", _height + "px");
}
/** RENDER **/
canvas.render = function() {
svgDefs
.select(".clipPath .background")
.attr("width", width)
.attr("height", height);
svg
.attr("width", width + (wrapperBorder*2) + minimapPadding*2 + (width*minimapScale))
.attr("height", height + (wrapperBorder*2));
outerWrapper
.select(".background")
.attr("width", width + wrapperBorder*2)
.attr("height", height + wrapperBorder*2);
innerWrapper
.attr("transform", "translate(" + (wrapperBorder) + "," + (wrapperBorder) + ")")
.select(".background")
.attr("width", width)
.attr("height", height);
panCanvas
.attr("width", width)
.attr("height", height)
.select(".background")
.attr("width", width)
.attr("height", height);
minimap
.x(width + minimapPadding)
.y(minimapPadding)
.render();
};
canvas.zoomEnabled = function(isEnabled) {
if (!arguments.length) { return zoomEnabled }
zoomEnabled = isEnabled;
};
canvas.dragEnabled = function(isEnabled) {
if (!arguments.length) { return dragEnabled }
dragEnabled = isEnabled;
};
canvas.reset = function() {
d3.transition().duration(750).tween("zoom", function() {
var ix = d3.interpolate(xScale.domain(), [-width / 2, width / 2]),
iy = d3.interpolate(yScale.domain(), [-height / 2, height / 2]),
iz = d3.interpolate(scale, 1);
return function(t) {
zoom.scale(iz(t)).x(x.domain(ix(t))).y(y.domain(iy(t)));
zoomed(iz(t));
};
});
};
}
//============================================================
// Accessors
//============================================================
canvas.width = function(value) {
if (!arguments.length) return width;
width = parseInt(value, 10);
return this;
};
canvas.height = function(value) {
if (!arguments.length) return height;
height = parseInt(value, 10);
return this;
};
canvas.scale = function(value) {
if (!arguments.length) { return scale; }
scale = value;
return this;
};
return canvas;
};
/** MINIMAP **/
d3.demo.minimap = function() {
"use strict";
var minimapScale = 0.15,
scale = 1,
zoom = null,
base = null,
target = null,
width = 0,
height = 0,
x = 0,
y = 0,
frameX = 0,
frameY = 0;
function minimap(selection) {
base = selection;
var container = selection.append("g")
.attr("class", "minimap")
.call(zoom);
zoom.on("zoom.minimap", function() {
scale = d3.event.scale;
});
minimap.node = container.node();
var frame = container.append("g")
.attr("class", "frame")
frame.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height)
.attr("filter", "url(#minimapDropShadow)");
var drag = d3.behavior.drag()
.on("dragstart.minimap", function() {
var frameTranslate = d3.demo.util.getXYFromTranslate(frame.attr("transform"));
frameX = frameTranslate[0];
frameY = frameTranslate[1];
})
.on("drag.minimap", function() {
d3.event.sourceEvent.stopImmediatePropagation();
frameX += d3.event.dx;
frameY += d3.event.dy;
frame.attr("transform", "translate(" + frameX + "," + frameY + ")");
var translate = [(-frameX*scale),(-frameY*scale)];
target.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
zoom.translate(translate);
});
frame.call(drag);
/** RENDER **/
minimap.render = function() {
scale = zoom.scale();
container.attr("transform", "translate(" + x + "," + y + ")scale(" + minimapScale + ")");
var node = target.node().cloneNode(true);
node.removeAttribute("id");
base.selectAll(".minimap .panCanvas").remove();
minimap.node.appendChild(node);
var targetTransform = d3.demo.util.getXYFromTranslate(target.attr("transform"));
frame.attr("transform", "translate(" + (-targetTransform[0]/scale) + "," + (-targetTransform[1]/scale) + ")")
.select(".background")
.attr("width", width/scale)
.attr("height", height/scale);
frame.node().parentNode.appendChild(frame.node());
d3.select(node).attr("transform", "translate(1,1)");
};
}
//============================================================
// Accessors
//============================================================
minimap.width = function(value) {
if (!arguments.length) return width;
width = parseInt(value, 10);
return this;
};
minimap.height = function(value) {
if (!arguments.length) return height;
height = parseInt(value, 10);
return this;
};
minimap.x = function(value) {
if (!arguments.length) return x;
x = parseInt(value, 10);
return this;
};
minimap.y = function(value) {
if (!arguments.length) return y;
y = parseInt(value, 10);
return this;
};
minimap.scale = function(value) {
if (!arguments.length) { return scale; }
scale = value;
return this;
};
minimap.minimapScale = function(value) {
if (!arguments.length) { return minimapScale; }
minimapScale = value;
return this;
};
minimap.zoom = function(value) {
if (!arguments.length) return zoom;
zoom = value;
return this;
};
minimap.target = function(value) {
if (!arguments.length) { return target; }
target = value;
width = parseInt(target.attr("width"), 10);
height = parseInt(target.attr("height"), 10);
return this;
};
return minimap;
};
/** UTILS **/
d3.demo.util = {};
d3.demo.util.getXYFromTranslate = function(translateString) {
var split = translateString.split(",");
var x = split[0] ? ~~split[0].split("(")[1] : 0;
var y = split[1] ? ~~split[1].split(")")[0] : 0;
return [x, y];
};
/** RUN SCRIPT **/
treeChart= (function (divID, treeData, height, width) {
var canvasWidth = width;
var shapes = [];
var lastXY = 1;
var zoomEnabled = true;
var dragEnabled = true;
var canvas = d3.demo.canvas(width,height).width(width/2).height(height/2);
d3.select(divID).call(canvas);
canvas.loadTree(divID,treeData,height,width);
});

d3 Pie Chart - Image on Outside Label

Hi am trying to place an image on an outside label of a pie chart. Setting an img or image tag will show the tag written on the label.
replacing all text values does not work like:
var svgs = d3.selectAll("text");
svgs.append("svg:image")
.attr("xlink:href", "/web/images/edit.png")
.attr("width", 24)
.attr("height", 24);
Cannot believe that a simple thing like this cannot be made simple.Can anyone help?
Your question isn't specific enough for a concrete answer but as an example, I've taken this nice pie chart and modified to replace one of the labels with an image:
var text = svg.select(".labels").selectAll("text")
.data(pie(data), key);
var img = svg.select(".images").selectAll("image")
.data(pie(data), key);
text.enter()
.append("text")
.filter(function(d,i){
return d.data.label !== "do"; //<-- on the "do" label suppress the text
})
.attr("dy", ".35em")
.text(function(d) {
return d.data.label;
});
text.transition().duration(1000)
.attrTween("transform", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2);
pos[0] = radius * (midAngle(d2) < Math.PI ? 1 : -1);
return "translate("+ pos +")";
};
})
.styleTween("text-anchor", function(d){
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
return midAngle(d2) < Math.PI ? "start":"end";
};
});
function midAngle(d){
return d.startAngle + (d.endAngle - d.startAngle)/2;
}
text.exit()
.remove();
img.enter()
.append("image")
.filter(function(d,i){
return d.data.label === "do"; //<-- only add image on "do"
})
.attr("xlink:href", "http://placehold.it/24x24")
.attr("width", 24)
.attr("height", 24);
img.transition().duration(1000)
.attrTween("transform", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2);
pos[0] = radius * (midAngle(d2) < Math.PI ? 1 : -1);
return "translate("+ pos +")";
};
})
img.exit()
.remove();
Here's a working example.
EDITS FOR COMMENTS
My original answer was straight d3, but since you are using another library, d3pie, do it like this after calling d3pie:
setTimeout(function(){
var labelG = d3.select('#p0_labelGroup1-outer');
labelG.select('text').remove();
labelG
.append("svg:image")
.attr("xlink:href", "http://lorempixel.com/60/60/animals/")
.attr("width", 61)
.attr("height", 61)
.attr("x", -20)
.attr("y", -30);
}, 10);
Working example here.

Resources