D3 - Make children fill parent group - javascript

I am trying to make a simple rectangular structure for single-nested data in D3. I would like the result to look like the following image:
In other words, each group's items should be sized so that all groups take up the same space.
The JSFiddle that I have made does not yield the correct result:
var item_groups_enter = item_groups.enter()
.append("g")
.classed("item-group", true)
.attr("transform", function (d, i) {
return ("translate(0, " + 50 * i + ")"); // !!! THIS NEEDS TO CHANGE !!!
});
// Append a rectangle for each item
item_groups_enter.append("rect")
.attr("width", main_group_width)
.attr("height", 50) // !!! THIS NEEDS TO CHANGE !!!
.attr("fill", function (d, i) {
return colours(i)
});
// Also append a label for each item
item_groups_enter.append("text")
.text(function (d) {
return (d)
})
.attr("x", main_group_width * 0.5)
.attr("y", 25) // !!! THIS NEEDS TO CHANGE !!!
.style("text-anchor", "middle");
I realise that I would somehow need to need to pass the main-groups' data (specifically, the number of children) to the item_groups, but I am unsure how to do this. I did try setting a custom attribute childCount on the main_group, but the code became quite messy referencing parent nodes (and then grandparent nodes later on).
What would be the way to do this? I am unsure if I should even be thinking about the solution in terms of D3, or in terms of CSS?

When you use a function with selection.attr, this is set to the current DOM element. You can use it to access the parent selection and through it the underlying data :
For example,
var item_groups_enter = item_groups.enter()
.append("g")
.classed("item-group", true)
.attr("transform", function (d, i) {
// this is the g node
var parentdatum = d3.select(this.parentNode).datum();
var itemY = available_height/parentdatum.values.length * i;
return ("translate(0, " + itemY + ")");
});
var data = [{
group: "Fruits",
values: ["Apple", "Banana", "Pear", "Plum"]
}, {
group: "Cakes",
values: ["Chocolate Cake", "Red Velvet Cake", "Carrot Cake"]
}, {
group: "Dogs",
values: ["Spaniel", "Chow", "Dachshund", "Bulldog", "Beagle", "Boxer", "Pug"]
}]
// Get a handle on the svg HTML element
var svg = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 500)
// Calculate spacing
var available_width = parseInt(svg.style("width"));
var available_height = parseInt(svg.style("height"));
var main_group_width = available_width / data.length;
// Define the colours to use
var colours = d3.scale.category10();
// Make an HTML group for each of the groups in the data
var main_groups = svg.selectAll("g")
.data(data);
// For each datum entered, append a new HTML group
main_groups.enter()
.append("g")
.classed("main-group", true)
.attr("transform", function (d, i) {
return ("translate(" + i * main_group_width + ", 0)");
})
// Append a new group, an "item group" for each of the values in each of the main groups
var item_groups = main_groups.selectAll("g")
.data(function (d) {
return (d.values)
});
var item_groups_enter = item_groups.enter()
.append("g")
.classed("item-group", true)
.attr("transform", function (d, i) {
var parentdatum = d3.select(this.parentNode).datum();
return ("translate(0, " + available_height/parentdatum.values.length * i + ")");
});
// Append a rectangle for each item
item_groups_enter.append("rect")
.attr("width", main_group_width)
.attr("height", function() {
// we want the grand parent node
var parentdatum = d3.select(this.parentNode.parentNode).datum();
return available_height/parentdatum.values.length;
})
.attr("fill", function (d, i) {
return colours(i)
});
// Also append a label for each item
item_groups_enter.append("text")
.text(function (d) {
return (d)
})
.attr("x", main_group_width * 0.5)
.attr("y", 25)
.style("text-anchor", "middle");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
You could could also traverse the groups defined by the item_groups selection (one group per parent element in main_groups.selectAll("g")) and assign your properties. For example
item_groups_enter.forEach(function(g, i) {
var parentdatum = d3.select(g.parentNode).datum();
var h = available_height/parentdatum.values.length;
var selection = d3.selectAll(g);
selection.attr("transform", function (d, i) {
return ("translate(0, " + h * i + ")");
});
selection.select('rect')
.attr("height", h);
selection.select('text')
.attr("y", h/2);
});
You can use the parentNode defined on each group to determine the correct parent data.
var data = [{
group: "Fruits",
values: ["Apple", "Banana", "Pear", "Plum"]
}, {
group: "Cakes",
values: ["Chocolate Cake", "Red Velvet Cake", "Carrot Cake"]
}, {
group: "Dogs",
values: ["Spaniel", "Chow", "Dachshund", "Bulldog", "Beagle", "Boxer", "Pug"]
}]
// Get a handle on the svg HTML element
var svg = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 500)
// Calculate spacing
var available_width = parseInt(svg.style("width"));
var available_height = parseInt(svg.style("height"));
var main_group_width = available_width / data.length;
// Define the colours to use
var colours = d3.scale.category10();
// Make an HTML group for each of the groups in the data
var main_groups = svg.selectAll("g")
.data(data);
// For each datum entered, append a new HTML group
main_groups.enter()
.append("g")
.classed("main-group", true)
.attr("transform", function (d, i) {
return ("translate(" + i * main_group_width + ", 0)");
})
// Append a new group, an "item group" for each of the values in each of the main groups
var item_groups = main_groups.selectAll("g")
.data(function (d) {
return (d.values)
});
var item_groups_enter = item_groups.enter()
.append("g")
.classed("item-group", true);
item_groups_enter.append("rect")
.attr("width", main_group_width)
.attr("fill", function (d, i) {
return colours(i)
});
item_groups_enter.append("text")
.text(function (d) {
return (d)
})
.attr("x", main_group_width * 0.5)
.attr("y", 25)
.style("text-anchor", "middle");
item_groups_enter.forEach(function(g, i) {
var parentdatum = d3.select(g.parentNode).datum();
var h = available_height/parentdatum.values.length;
var selection = d3.selectAll(g);
selection.attr("transform", function (d, i) {
return ("translate(0, " + h * i + ")");
});
selection.select('rect')
.attr("height", h);
selection.select('text')
.attr("y", h/2);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Related

Map data from an array for only some keys (not filtered)

I have a d3 pie chart. It reads data from a json file (in this example it's just an array variable).
Some of the key names are not very helpful, so I am trying to write a map function to pass some different strings, but other key names are fine, so I want to skip over those or return them as normal.
If a key name is not declared my function returns undefined. Also the names are not being added to the legend.
I do know all of the key names, but I would rather continue over the ones I have not defined as they can be returned as they are, if this is possible (I thought an else/if with a continue condition). This code would be more useful if new keys are added to the data.
I have tested a version with all key names declared, it stops throwing an undefined variable, but is not appending the new key names to the legend.
There is a full jsfiddle here: https://jsfiddle.net/lharby/ugweordj/6/
Here is my js function (truncated).
var w = 400,
h = 400,
r = 180,
inner = 70,
color = d3.scale.category20c();
data = [{"label":"OOS", "value":194},
{"label":"TH10", "value":567},
{"label":"OK", "value":1314},
{"label":"KO", "value":793},
{"label":"Spark", "value":1929}];
mapData = data.map(function(d){
if(d.label == 'OOS'){
return 'Out of scope';
}else if(d.label == 'TH10'){
return 'Threshold 10';
}else{
// I don't care about the other keys, they can map as they are.
continue;
}
});
var total = d3.sum(data, function(d) {
return d3.sum(d3.values(d));
});
var vis = d3.select("#chart")
.append("svg:svg")
.data([data])
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(" + r * 1.1 + "," + r * 1.1 + ")")
var arc = d3.svg.arc()
.innerRadius(inner)
.outerRadius(r);
var pie = d3.layout.pie()
.value(function(d) { return d.value; });
var arcs = vis.selectAll("g.slice")
.data(pie)
.enter()
///
};
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } )
.attr("d", arc);
var legend = d3.select("#chart").append("svg")
.attr("class", "legend")
.attr("width", r)
.attr("height", r * 2)
.selectAll("g")
.data(mapData) // returning mapData up until undefined variables
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) { return color(i); });
legend.append("text")
.attr("x", 24)
.attr("y", 9)
.attr("dy", ".35em")
.text(function(d) { return d.label; }); // should this point to a reference in mapData?
The HTML is a simple element with an id of #chart.
You can use .forEach() to iterate over your data and change just the labels of interest while keeping all others unchanged:
data = [{"label":"OOS", "value":194},
{"label":"TH10", "value":567},
{"label":"OK", "value":1314},
{"label":"KO", "value":793},
{"label":"Spark", "value":1929}];
data.forEach(d => {
if(d.label == 'OOS'){
d.label = 'Out of scope';
} else if(d.label == 'TH10'){
d.label = 'Threshold 10';
}
});
console.log(data);
You cannot skip elements using map. However, you can skip them using reduce:
var data = [
{"label":"OOS", "value":194},
{"label":"TH10", "value":567},
{"label":"OK", "value":1314},
{"label":"KO", "value":793},
{"label":"Spark", "value":1929}
];
var mapData = data.reduce(function(result, d) {
if (d.label == 'OOS') {
result.push('Out of scope');
} else if (d.label == 'TH10') {
result.push('Threshold 10');
};
return result;
}, []);
console.log(mapData)
EDIT: after your comment your desired outcome is clearer. You ca do this using map, just return the property's value:
var data = [{"label":"OOS", "value":194},
{"label":"TH10", "value":567},
{"label":"OK", "value":1314},
{"label":"KO", "value":793},
{"label":"Spark", "value":1929}];
var mapData = data.map(function(d) {
if (d.label == 'OOS') {
return 'Out of scope';
} else if (d.label == 'TH10') {
return 'Threshold 10';
} else {
return d.label
}
});
console.log(mapData);

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 add multiple images to svg in D3 (quantity based on value in dataset)

How to apply an image to svg multiple times, with the quantity based on value from a dataset? I'm trying to do smth like this:
dataset:
var dataset = [{"attribute": "att1", "data": "5"}, {"attribute": "att2", "data": "10"}]
SVG:
att1
$$$$$
att2
$$$$$$$$$$
...with $ being an image.
Below code adds an image based on number of attribute occurencies instead of the "data". So I get 2 images. How to get 2 sets of images with 5 and 10 occurencies?
var w = 300;
var h = 300;
var p = 20;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.style("border", "1px solid black");
var img = svg.selectAll("image")
.data(dataset)
.enter()
.append("svg:image")
.attr("x", function(d,i) {return (i * 10);})
.attr("width", 50)
.attr("height", 50)
.style("border", "1px solid black")
.attr("xlink:href", "images/xyz.png");
Here is a solution using d3.range() to set the number of repetitions.
In this solution, I'm binding the data to the parent groups...
var groups = svg.selectAll("foo")
.data(data)
... and, in the image selection, I'm using an array whose length is determined by the property data in the dataset:
var images = groups.selectAll("bar")
.data(d => d3.range(d.data))
Check the demo:
var dataset = [{
image: "http://icons.webpatashala.com/icons/Blueberry-Basic-Icons/Png/rss-icon.PNG",
data: 5
}, {
image: "http://www.ucdmc.ucdavis.edu/global/images/icons/instagram-32x32.png",
data: 3
}, {
image: "http://www.axadledirect.com/assets/images/icons/share/32x32/google.png",
data: 8
}];
var svg = d3.select("svg");
var groups = svg.selectAll("foo")
.data(dataset)
.enter()
.append("g")
.attr("transform", (d, i) => "translate(10," + (10 + i * 40) + ")");
var images = groups.selectAll("bar")
.data(d => d3.range(d.data))
.enter()
.append("svg:image")
.attr("x", function(d, i) {
return (i * 35);
})
.attr("xlink:href", function(d) {
return d3.select(this.parentNode).datum().image
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

Multiple d3 charts in the same row

The code below draws 1 pie chart and a legend on the left side of the screen. Right now, I am trying to draw another pie chart with legend right next to the one on the left (same row). I've tried using multiple divs in the html to make this work, but I want a more pure d3 solution in which the duplication happens in the d3 code rather than in the html or css.
var w = 200;
var h = 200;
var r = h / 2;
var color = d3.scale.category20c();
var vis = d3.select(divId).append("svg:svg").data([descArray]).attr("width",w).attr("height", h).append("svg:g").attr("transform", "translate(" + r + "," + r + ")");
var pie = d3.layout.pie().value(function (d, i) {
return countArray[i];
});
// declare an arc generator function
var arc = d3.svg.arc().outerRadius(r);
// select paths, use arc generator to draw
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice");
arcs.append("svg:path")
.on("click", function(d) {//clicking on individual arcs
arcs.selectAll("path").style("opacity", 1);//resets all arcs' opacity to 1
d3.select(this).style("opacity", 0.5);//sets clicked arc's opacity down
alert(d.data + " " + d.value);
})
.style("fill", function(d,i) { return color(i); })
.transition().delay(function(d, i) { return i * 100; }).duration(1000)
.attrTween('d', function(d) {
var i = d3.interpolate(d.startAngle+0.7, d.endAngle);
return function(t) {
d.endAngle = i(t);
return arc(d);
}
})
.attr("fill", function (d, i) {
return color(i);
});
var legend = d3.select(divId).append("svg")
.attr("class", "legend")
.attr("width", r * 4)
.attr("height", r * 4)
.selectAll("g")
.data(color.domain().slice().reverse())
.enter().append("g")
.attr("transform", function(d, i) { return "translate(230," + i * 27 + ")"; });
legend.append("rect")
.on("click", function(d) {
alert(d.data + " " + d.value);
})
.attr("width", 18)
.attr("height", 18)
.style("fill", function (d, i) {
return color(i);
})
put them in seperate divs but in the same SVG element
Presuming vis is your svgElement:
var firstChart = vis.append(div). // then put your first chart here
var secondChart = vis.append(div). // then put your second chart here

d3.js dynamic data with labels

I've managed to get the following working (pie chart changing dynamically based on a slider value):
var x = function() {
return $("#slider").val();
}
var data = function() {
return [x(), ((100-x())/2), ((100-x())/2)];
}
var w = 100,
h = 100,
r = 50,
color = d3.scale.category20(),
pie = d3.layout.pie().sort(null),
arc = d3.svg.arc().outerRadius(r);
var svg = d3.select("#chart").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
var arcs = svg.selectAll("path")
.data(pie(data()))
.enter().append("svg:path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.each(function(d) { this._current = d; });
var redraw = function() {
newdata = data(); // swap the data
arcs = arcs.data(pie(newdata)); // recompute the angles and rebind the data
arcs.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
};
// Store the currently-displayed angles in this._current.
// Then, interpolate from this._current to the new angles.
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
I can't seem to modify it to include dynamic labels (use an object for data rather than an array {'label': 'label1', 'value': value}. How would I modify the above code to add labels?
This should work, keeping the labels and the pie chart as separate entities:
var x = function() {
return $("#slider").val();
}
var data = function() {
return [x(), ((100-x())/2), ((100-x())/2)];
}
var labels = function() {
var label1 = "LABEL 1: " + x();
var label2 = "LABEL 2: " + ((100-x())/2);
var label3 = "LABEL 3: " + ((100-x())/2);
return [label1, label2, label3];
}
var w = 200,
h = 100,
r = 50,
color = d3.scale.category20(),
pie = d3.layout.pie().sort(null),
arc = d3.svg.arc().outerRadius(r);
var svg = d3.select("#chart").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(" + 50 + "," + h / 2 + ")");
var arcs = svg.selectAll("path")
.data(pie(data()))
.enter().append("svg:path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.each(function(d) { this._current = d; });
var label_group = svg.selectAll("text")
.data(labels())
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("x", 60)
.attr("y", function(d, i) { return (i * 20) - 16; });
var redraw = function() {
newdata = data(); // swap the data
arcs = arcs.data(pie(newdata)); // recompute the angles and rebind the data
arcs.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
label_group = label_group.data(labels());
label_group.transition().delay(300).text(function(d) {
return d;
});

Categories

Resources