I think I'm missing something very obvious here. Basically what I am trying to do is create a treemap that on button click will go to the server and retrieve the next level into the treemap...This is necessary because the treemap structure is too large and takes too long to calculate so jumping one level at a time is the only option we have.
[Note to IE users, in this example the treemap node names don't appear to be working. Try using Chrome]
http://plnkr.co/edit/simVGU
This code is taken almost exactly from
http://bost.ocks.org/mike/treemap/
I'm using vizData1.json for the "first" level and on mouse click I'm using vizData2.json as the "second" level. You can see that the two end up overlapping. I've tried to do svg.exit() as well as svg.clear() without any luck.
I should also note that I have already tried the sticky(false) suggestion from this post
Does the d3 treemap layout get cached when a root node is passed to it?
UPDATE:
To continue my hunt I have found an example that successfully adds new nodes to an existing treemap. However I am having trouble adapting this logic as the treemap I am attempting to fit this logic into has been heavily customized by Michael Bostock - #mbostock to allow for the nice breadcrumb trail bar at the top.
Code snippet that proves appending to existing treemap nodes is possible:
http://jsfiddle.net/WB5jh/3/
Also, Stackoverflow is forcing me to post code because I'm linking to plnkr so I have dumped my script.js here for those who would rather not interact with plunker
$(function() {
var margin = { top: 20, right: 0, bottom: 0, left: 0 },
width = 960,
height = 500,
formatNumber = d3.format(",d"),
transitioning;
var x = d3.scale.linear()
.domain([0, width])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, height])
.range([0, height]);
var treemap = d3.layout.treemap()
.children(function (d, depth) { return depth ? null : d._children; })
.sort(function (a, b) { return a.value - b.value; })
.ratio(height / width * 0.5 * (1 + Math.sqrt(5)))
.round(false)
.sticky(false);
var svg = d3.select("#treemap")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.bottom + margin.top)
.style("margin-left", -margin.left + "px")
.style("margin.right", -margin.right + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.style("shape-rendering", "crispEdges");
var grandparent = svg.append("g")
.attr("class", "grandparent");
grandparent.append("rect")
.attr("y", -margin.top)
.attr("width", width)
.attr("height", margin.top);
grandparent.append("text")
.attr("x", 6)
.attr("y", 6 - margin.top)
.attr("dy", ".75em");
d3.json("vizData1.json", function (root) {
initialize(root);
accumulate(root);
layout(root);
display(root);
});
function initialize(root) {
root.x = root.y = 0;
root.dx = width;
root.dy = height;
root.depth = 0;
}
// Aggregate the values for internal nodes. This is normally done by the
// treemap layout, but not here because of our custom implementation.
// We also take a snapshot of the original children (_children) to avoid
// the children being overwritten when when layout is computed.
function accumulate(d) {
return (d._children = d.children)
? d.value = d.children.reduce(function (p, v) { return p + accumulate(v); }, 0)
: d.value;
}
// Compute the treemap layout recursively such that each group of siblings
// uses the same size (1×1) rather than the dimensions of the parent cell.
// This optimizes the layout for the current zoom state. Note that a wrapper
// object is created for the parent node for each group of siblings so that
// the parent’s dimensions are not discarded as we recurse. Since each group
// of sibling was laid out in 1×1, we must rescale to fit using absolute
// coordinates. This lets us use a viewport to zoom.
function layout(d) {
if (d._children) {
treemap.nodes({ _children: d._children });
d._children.forEach(function (c) {
c.x = d.x + c.x * d.dx;
c.y = d.y + c.y * d.dy;
c.dx *= d.dx;
c.dy *= d.dy;
c.parent = d;
layout(c);
});
}
}
function display(d) {
console.log(d);
grandparent
.datum(d.parent)
.on("click", transition)
.select("text")
.text(name(d));
var g1 = svg.insert("g", ".grandparent")
.datum(d)
.attr("class", "depth");
var g = g1.selectAll("g")
.data(d._children)
.enter().append("g");
g.filter(function (d) { return d._children; })
.classed("children", true)
.on("click", transition);
g.selectAll(".child")
.data(function (d) { return d._children || [d]; })
.enter().append("rect")
.attr("class", "child")
.call(rect);
g.append("rect")
.attr("class", "parent")
.call(rect)
.append("title")
.text(function (d) { return formatNumber(d.value); });
g.append("foreignObject")
.call(rect)
.attr("class", "foreignobj")
.append("xhtml:div")
.attr("dy", ".75em")
.html(function (d) { return d.name; })
.attr("class", "textdiv");
function transition(d) {
if (transitioning || !d) return;
transitioning = true;
d3.json("vizData2.json", function (root) {
initialize(root);
accumulate(root);
layout(root);
display(root);
});
var g2 = display(d),
t1 = g1.transition().duration(750),
t2 = g2.transition().duration(750);
// Update the domain only after entering new elements.
x.domain([d.x, d.x + d.dx]);
y.domain([d.y, d.y + d.dy]);
// Enable anti-aliasing during the transition.
svg.style("shape-rendering", null);
// Draw child nodes on top of parent nodes.
svg.selectAll(".depth").sort(function (a, b) { return a.depth - b.depth; });
// Fade-in entering text.
g2.selectAll("text").style("fill-opacity", 0);
g2.selectAll("foreignObject div").style("display", "none"); /*added*/
// Transition to the new view.
t1.selectAll("text").call(text).style("fill-opacity", 0);
t2.selectAll("text").call(text).style("fill-opacity", 1);
t1.selectAll("rect").call(rect);
t2.selectAll("rect").call(rect);
t1.selectAll(".textdiv").style("display", "none"); /* added */
t1.selectAll(".foreignobj").call(foreign);
t2.selectAll(".textdiv").style("display", "block"); /* added */
t2.selectAll(".foreignobj").call(foreign); /* added */
// Remove the old node when the transition is finished.
t1.remove().each("end", function () {
svg.style("shape-rendering", "crispEdges");
transitioning = false;
});
}
return g;
}
function text(text) {
text.attr("x", function (d) { return x(d.x) + 6; })
.attr("y", function (d) { return y(d.y) + 6; });
}
function rect(rect) {
rect.attr("x", function (d) { return x(d.x); })
.attr("y", function (d) { return y(d.y); })
.attr("width", function (d) { return x(d.x + d.dx) - x(d.x); })
.attr("height", function (d) { return y(d.y + d.dy) - y(d.y); });
}
function foreign(foreign) { /* added */
foreign.attr("x", function (d) { return x(d.x); })
.attr("y", function (d) { return y(d.y); })
.attr("width", function (d) { return x(d.x + d.dx) - x(d.x); })
.attr("height", function (d) { return y(d.y + d.dy) - y(d.y); });
}
function name(d) {
return d.parent
? name(d.parent) + "." + d.name
: d.name;
}
});
Related
This is my treeMap right now:
I want to append a button between the nodes. The nodes have parents and child relationships. The 2 white circle with plus sign are children of the coursera picture. So I am trying to append the button by calling a function that takes the parent's (x,y) and the children(x,y), then create a circle there probably.
Here is my full code, I did not write most of this code so I am not understanding things fully. I recognize that the link path between nodes were drawn by this function: ` function diagonal(s, d) {
var path = `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x},
${(s.y + d.y) / 2} ${d.x},
${d.y} ${d.x}`;
return path;
}`
How would like append a circle between 2 nodes in my code?
Full code: `
import React, { Component } from "react";
import * as d3 from "d3";
import { hierarchy, tree } from "d3-hierarchy";
import "../Tree.css";
//import "./Tree.css";
class Tree extends Component {
constructor(props) {
super(props);
this.state = { collapse: false, text: "hi", visible: true };
//this.toggle = this.toggle.bind(this);
}
handleChange = d => {
this.props.on_click_change(d);
};
componentDidMount() {
var that = this;
var treeData = this.props.roadmapData;
// Set the dimensions and margins of the diagramS
var height1 = window.innerHeight;
var margin = { top: 0, right: 0, bottom: 0, left: 0 },
width = 1080 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// append the svg object to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3
.select("li")
.append("svg")
.call(
d3.zoom().on("zoom", function() {
svg.attr("transform", d3.event.transform);
})
)
.attr("width", 1800 - margin.right - margin.left)
.attr("height", 900 - margin.top - margin.bottom)
.append("g")
.attr("transform", "translate(" + +"," + margin.top + ")");
var i = 0,
duration = 500,
root;
// declares a tree layout and assigns the size
var treemap = d3.tree().size([window.innerHeight, window.innerWidth]);
root = d3.hierarchy(treeData, function(d) {
return d.children;
});
root.x0 = height / 2;
root.y0 = 0;
console.log(this.props.treeData);
// Collapse after the second level
root.children.forEach(collapse);
update(root);
// Collapse the node and all it's children
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
function update(source) {
// Assigns the x and y position for the nodes
var treeData = treemap(root);
// Compute the new tree layout.
var nodes = treeData.descendants(),
links = treeData.descendants().slice(1),
more_button = treeData.descendants();
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// ****************** Nodes section ***************************
// Update the nodes...
var node = svg.selectAll("g.node").data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new modes at the parent's previous position.
var nodeEnter = node
.enter()
.append("g")
.attr("class", "node")
//if deleted, bubbles come from the very top, is weird
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
});
// Add Circle for the nodes
nodeEnter
.append("circle")
.attr("class", "node")
.attr("r", 1e-6)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
/*
// Add labels for the nodes
nodeEnter
.append("text")
.attr("dy", 0)
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.data.name;
});
*/
var diameter = 30;
nodeEnter
.append("image")
.on("click", click)
.attr("xlink:href", function(d) {
return d.data.website_image;
})
.attr("height", diameter * 2)
.attr("transform", "translate(-30," + -30 + ")");
// UPDATE
var nodeUpdate = nodeEnter.merge(node);
// Transition to the proper position for the node
nodeUpdate
.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Update the node attributes and style
nodeUpdate
.select("circle.node")
.attr("r", diameter)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
})
.attr("cursor", "pointer");
nodeUpdate
.append("circle")
.on("click", click2)
.attr("additional", "extra_circle")
.attr("r", 10)
.attr("transform", "translate(0," + -40 + ")");
// Remove any exiting nodes
var nodeExit = node
.exit()
.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
// On exit reduce the node circles size to 0
nodeExit.select("circle").attr("r", 1e-6);
// On exit reduce the opacity of text labels
nodeExit.select("text").style("fill-opacity", 1e-6);
// ****************** links section ***************************
// Update the links...
var link = svg.selectAll("path.link").data(links, function(d) {
return d.id;
});
// Enter any new links at the parent's previous position.
var linkEnter = link
.enter()
.insert("path", "g")
.attr("class", "link")
.style("fill", "red")
.attr("d", function(d) {
var o = { x: source.x0, y: source.y0 };
return diagonal(o, o);
});
// UPDATE
var linkUpdate = linkEnter.merge(link);
// Transition back to the parent element position
linkUpdate
.transition()
.duration(duration)
.attr("d", function(d) {
console.log(d, d.parent);
return diagonal(d, d.parent);
});
// Remove any exiting links
var linkExit = link
.exit()
.transition()
.duration(duration)
.attr("d", function(d) {
var o = { x: source.x, y: source.y };
return diagonal(o, o);
})
.remove();
// Store the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(s, d) {
var path = `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x},
${(s.y + d.y) / 2} ${d.x},
${d.y} ${d.x}`;
return path;
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
function click2(d) {
console.log(d.data.name);
that.setState({ text: d.data.details });
that.handleChange(d);
}
}
}
render() {
return null;
}
}
export default Tree;
`
Thanks to Coola, here's the solution:
`nodeEnter
.append("circle")
.attr("class", "extra_info")
.on("click", function(d) {})
.attr("cy", function(d) {
if (d.parent != null) {
d.x_pos = d.x;
d.parent_x_pos = d.parent.x;
}
if (d.parent_x_pos != null) {
return (d.x_pos + d.parent_x_pos) / 2 - d.x_pos;
}
})
.attr("cx", -90)
.attr("r", 7);`
So basically whenever you do function(d){} instead of just calling a single thing, the d variable gets passed and it contains a lot of information, including the parent of the current element and the child if there is one, and the x y positions, etc. d refers to the current element we are at. So we can comfortably use d.parent.x and d.x to calculate positions, here's what it looks like now:
Btw, d3 and svg's x and y seems to be reversed sometimes. As you can see: I am deciding cy, SVG circle's attribute on determining its y position by d.x.
I've converted Mike Botock's Hierarchical Bar Chart to v4 and made some tweaks to fit my needs(vertical, tool-tip, widths of bars fit canvas, etc).
Now I'm trying to make this a stacked bar chart. In my JSON file I have two types of downtime Machine and Die. For the original chart I just add these up to get my overall but now i want to stack them and I'm unsure how to pull these out separately after doing a root.sum on the Hierarchy. This is my first chart so pardon some of the coding but feel free to correct me on anything. I also could clean some things up with if statements but I'm leaving everything separate as it is easier to troubleshoot. Any thoughts on how to make stacked hierarchical bar chart would be appreciated. Even if it means throwing away this code and starting over.
<body>
<script src="d3.min.js"></script>
<script>
//canvas variables
var margin = { top: 30, right: 120, bottom: 300, left: 120 },
width = 960 - margin.left - margin.right,
height = 800 - margin.top - margin.bottom;
// scale y on canvas from largest number to 0
var y = d3.scaleLinear().range([height, 0]);
var barWidth;
var barPadding = 5;
var oldBarWidth = width;
var depth = 0;
var color = d3.scaleOrdinal()
.range(["steelblue", "#ccc"]);
var duration = 750,
delay = 25;
//attach SVG to body with canvas variables declared above
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var divTooltip = d3.select("body").append("div").attr("class", "toolTip");
//attach a rectangle to the entire background for drilling
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height)
.on("click", drillUp);
//append axis to the SVG
svg.append("g")
.attr("class", "yaxis");
svg.append("g")
.append("line")
.attr("transform", "translate(0," + height + ")")
.attr("x1", width)
.attr("stroke", "black")
.attr("shape-rendering", "crispEdges");
//import JSON file
d3.json("data/drilljson2.json", function (error, root) {
if (error) throw error;
//declare root of the JSON file
root = d3.hierarchy(root);
//add all children in hierarchy and get value for all parents
root.sum(function (d) { return (+d.DieDowntime + (+d.MachineDowntime)); });
//scale the 'Y' domain/axis from 0 to root value
y.domain([0, root.value]).nice();
//call the drill down function
drillDown(root, 0);
drillDown(root.children[3], 3);
});
function drillDown(d, i) {
if (!d.children) return;
// get the number of children to parent and calculate barWidth and keep track of depth of drill down.
numChildNodes = d.children.length;
barWidth = (width / numChildNodes) - barPadding;
depth += 1;
var end = duration + numChildNodes * delay;
// Mark any currently-displayed bars as exiting.
var exit = svg.selectAll(".enter")
.attr("class", "exit");
// Entering nodes immediately obscure the clicked-on bar, so hide it.
exit.selectAll("rect").filter(function (p) { return p === d; })
.style("fill-opacity", 0);
// Enter the new bars for the clicked-on data.
// Entering bars are immediately visible.
var enter = drillDownBars(d)
.attr("transform", stackDown(i))
.attr("width", oldBarWidth)
.style("opacity", 1);
// Update the y-scale domain.
y.domain([0, d3.max(d.children, function (d) { return d.value; })]).nice();
// Have the text fade-in, even though the bars are visible.
// Color the bars as parents; they will fade to children if appropriate.
enter.select("text").style("fill-opacity", 0);
enter.select("rect").style("fill", color(true));
// Update the y-axis.
svg.selectAll(".yaxis").transition()
.duration(duration)
.call(d3.axisLeft(y));
// Transition entering bars to their new position.
var enterTransition = enter.transition()
.duration(duration)
.delay(function (d, i) { return i * delay; })
.style("opacity", 1)
.attr("transform", function (d, i) { var transBar = (barWidth +barPadding) * i +barPadding; return "translate("+ transBar + ")"; });
// Transition entering text.
enterTransition.select("text")
.attr("transform", function (d) { return "translate("+(barWidth/2)+","+((height+5) + 10 * depth)+")rotate(90)" })
// working .attr("y", height + 15)
.style("fill-opacity", 1);
// Transition entering rects to the new y-scale.
enterTransition.select("rect")
.attr("y", function (d) { return y(d.value); })
.attr("height", function (d) { return height - y(d.value); })
.attr("width",barWidth)
.style("fill", function (d) { return color(!!d.children); });
// Transition exiting bars to fade out.
var exitTransition = exit.transition()
.duration(duration)
.style("opacity", 0)
.remove();
// Transition exiting bars to the new y-scale.
exitTransition.selectAll("rect")
.attr("y", function (d) { return y(d.value); })
.attr("height", function (d) { return height - y(d.value); });
// Rebind the current node to the background.
svg.select(".background")
.datum(d)
.transition()
.duration(end);
d.index = i;
oldBarWidth = barWidth;
}
function drillUp(d) {
if (!d.parent || this.__transition__) return;
numChildNodes = d.parent.children.length;
barWidth = (width / numChildNodes) - barPadding;
depth -= 1;
var end = duration + d.children.length * delay;
// Mark any currently-displayed bars as exiting.
var exit = svg.selectAll(".enter")
.attr("class", "exit");
// Enter the new bars for the clicked-on data's parent.
var enter = drillUpBars(d.parent)
.attr("transform", function (d, i) {
transBarWidth = (barWidth + barPadding) * i + barPadding;
return "translate(" + transBarWidth + "," + 0 + ")";
})
.style("opacity", 0);
// Color the bars as appropriate.
// Exiting nodes will obscure the parent bar, so hide it.
enter.select("rect")
.style("fill", function (d) { return color(!!d.children); })
.filter(function (p) { return p === d; })
.style("fill-opacity", 0);
// Update the y-scale domain.
y.domain([0, d3.max(d.parent.children, function (d) { return d.value; })]).nice();
// Update the y-axis.
svg.selectAll(".yaxis").transition()
.duration(duration)
.call(d3.axisLeft(y));
// Transition entering bars to fade in over the full duration.
var enterTransition = enter.transition()
.duration(end)
.style("opacity", 1);
// Transition entering rects to the new y-scale.
// When the entering parent rect is done, make it visible!
enterTransition.select("rect")
.attr("y", function (d) { return y(d.value); })
.attr("height", function (d) { return height - y(d.value); })
.on("end", function (p) { if (p === d) d3.select(this).style("fill-opacity", null); });
// Transition entering text.
enterTransition.select("text")
.attr("transform", function (d) { return "translate("+(barWidth/2)+","+((height+5) + 10 * depth)+")rotate(90)" })
.style("fill-opacity", 1);
// Transition exiting bars to the parent's position.
var exitTransition = exit.selectAll("g").transition()
.duration(duration)
.delay(function (d, i) { return i * delay; })
.attr("transform", stackUp(d.index));
// Transition exiting text to fade out.
exitTransition.select("text")
.style("fill-opacity", 0);
// Transition exiting rects to the new scale and fade to parent color.
exitTransition.select("rect")
.attr("y", function (d) { return y(d.value); })
.attr("height", function (d) { return height - y(d.value); })
.attr("width", barWidth)
.style("fill", color(true));
// Remove exiting nodes when the last child has finished transitioning.
exit.transition()
.duration(end)
.remove();
// Rebind the current parent to the background.
svg.select(".background")
.datum(d.parent)
.transition()
.duration(end);
oldBarWidth = barWidth;
}
// Creates a set of bars for the given data node, at the specified index.
function drillUpBars(d) {
var bar = svg.insert("g")
.attr("class", "enter")
.selectAll("g")
.data(d.children)
.enter().append("g")
.style("cursor", function (d) { return !d.children ? null : "pointer"; })
.on("click", drillDown);
bar.append("text")
.attr("dx", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function (d) { return "translate(" + barWidth / 2 + "," + (height + 15) + ") rotate(90)" })
.text(function (d) { return d.data.name; });
bar.append("rect")
.attr("y", function (d) { return y(d.value); } )
.attr("height", function (d) { return height - y(d.value); })
.attr("width", barWidth)
.attr("stroke-width", 1)
.attr("stroke", "white");
return bar;
}
function drillDownBars(d) {
var bar = svg.insert("g")
.attr("class", "enter")
.selectAll("g")
.data(d.children)
.enter().append("g")
.style("cursor", function (d) { return !d.children ? null : "pointer"; })
.on("click", drillDown)
.on("mouseover", mouseover)
.on("mousemove", function (d) {
divTooltip
.text(d.data.name +" " + d.value)
.style("left", (d3.event.pageX - 34) + "px")
.style("top", (d3.event.pageY - 30) + "px");
});
bar.append("text")
.attr("dx", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function (d) { return "translate(" + barWidth / 2 + "," + (height + 15 - y(d.value)) + ") rotate(90)" })
.text(function (d) { return d.data.name; });
bar.append("rect")
.attr("height", function (d) { return height - y(d.value); })
.attr("width", oldBarWidth)
.attr("stroke-width", 1)
.attr("stroke", "white");
return bar;
}
//Creates a stack of bars
function stackDown(i) {
var x0 = (oldBarWidth + barPadding) * i + barPadding;
var y0 = height;
return function (d) {
y0 -= height - y(d.value);
var ty = "translate(" + x0 + "," + y0 + ")";
return ty;
};
}
//
function stackUp(i) {
var x0 = barWidth * i + (barPadding * (i + 1));
var y0 = 0;
return function (d) {
var ty = "translate(" + x0 + "," + y0 + ")";
y0 -= height - y(d.value);
return ty;
};
}
function mouseover() {
divTooltip.style("display", "inline");
}
</script>
Here is a piece of the JSON file which could also use a little cleaning but minor details for now.
{
"name" : "Down Time",
"children" : [{
"name" : "2013",
"children" : [{
"name" : "May 2013",
"children" : [{
"name" : "21 May 2013",
"children" : [{
"name" : "110",
"children" : [{
"MachineDowntime" : ".00000000000000000000",
"DieDowntime" : ".50000000000000000000"
}
]
}, {
"name" : "115",
"children" : [{
"MachineDowntime" : "5.23333333333333333300",
"DieDowntime" : ".00000000000000000000"
}
]
}
]
}, {
"name" : "22 May 2013",
"children" : [{
"name" : "115",
"children" : [{
"MachineDowntime" : "2.96666666666666666730",
"DieDowntime" : ".00000000000000000000"
}
]
}, {
"name" : "110",
"children" : [{
"MachineDowntime" : ".00000000000000000000",
"DieDowntime" : "10.36666666666666667000"
My requirement is to draw a category-grouped bar chart in which each category has a different number of groups, using pure d3. I have no idea how to take domain and range to meet my requirement.
I tried in the way given in the answer to d3 nested grouped bar chart, but it did not work in my case.
Here my graph structure is like:
The issue with the plunker of the answer that you mention is that it will just work for values that have the same children. In order to handle the dynamic children values I took this approach
Lets create the color mapping for our groups:
var color = {
Mechanical: '#4A7B9D',
Electrical: '#54577C',
Hydraulic: '#ED6A5A'
};
We also need a structure with nested values that will be the inner groups:
// Simulated data structure
var data = [{
key: 'Mechanical',
values: [{
key: 'Gear',
value: 11
}, {
key: 'Bearing',
value: 8
}, {
key: 'Motor',
value: 3
}]
}];
I created a barPadding value which will dictate the separation between bars:
var barPadding = 120;
We are going to need a dummy scale to get the rangeBand of the bars, lets do that:
// dummy array
var rangeBands = [];
// cummulative value to position our bars
var cummulative = 0;
data.forEach(function(val, i) {
val.cummulative = cummulative;
cummulative += val.values.length;
val.values.forEach(function(values) {
rangeBands.push(i);
})
});
// set scale to cover whole svg
var x_category = d3.scale.linear()
.range([0, width]);
// create dummy scale to get rangeBands (width/childrenValues)
var x_defect = d3.scale.ordinal().domain(rangeBands)
.rangeRoundBands([0, width], .1);
var x_category_domain = x_defect.rangeBand() * rangeBands.length;
x_category.domain([0, x_category_domain]);
Then lets add all our category groups g elements:
var category_g = svg.selectAll(".category")
.data(data)
.enter().append("g")
.attr("class", function(d) {
return 'category category-' + d.key;
})
.attr("transform", function(d) { // offset by inner group size
var x_group = x_category((d.cummulative * x_defect.rangeBand()));
return "translate(" + x_group + ",0)";
})
.attr("fill", function(d) { // make child elements of group "inherit" this fill
return color[d.key];
});
Adding our inner groups g elements:
var defect_g = category_g.selectAll(".defect")
.data(function(d) {
return d.values;
})
.enter().append("g")
.attr("class", function(d) {
return 'defect defect-' + d.key;
})
.attr("transform", function(d, i) { // offset by index
return "translate(" + x_category((i * x_defect.rangeBand())) + ",0)";
});
Having our g elements lets add the labels:
var category_label = category_g.selectAll(".category-label")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
console.log(d)
return 'category-label category-label-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((d.values.length * x_defect.rangeBand() + barPadding) / 2);
var y_label = height + 30;
return "translate(" + x_label + "," + y_label + ")";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
var defect_label = defect_g.selectAll(".defect-label")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
console.log(d)
return 'defect-label defect-label-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((x_defect.rangeBand() + barPadding) / 2);
var y_label = height + 10;
return "translate(" + x_label + "," + y_label + ")";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
And finally our rects:
var rects = defect_g.selectAll('.rect')
.data(function(d) {
return [d];
})
.enter().append("rect")
.attr("class", "rect")
.attr("width", x_category(x_defect.rangeBand() - barPadding))
.attr("x", function(d) {
return x_category(barPadding);
})
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
});
Here's the above code in plnkr: https://plnkr.co/edit/L0eQwtEMQ413CpoS5nvo?p=preview
I've constructed a D3 visualization that was working on my local machine. However, now i've exported to my server, the code breaks and throws several errors:
Error: invalid value for <circle> attribute transform="translate"(NaN,NaN)"
Error: invalid value for <text> attribute transform="translate"(NaN,NaN)"
Error: invalid value for <circle> attribute r="NaN"
I've had these errors before with similar code and was able to solve them. However, i cannot grasp what is going wrong. Any suggestions? Thanx!
function drawBubbles() {
var margin = 20,
diameter = 740;
var color = d3.scale.linear()
.domain([-1, 10])
.range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl);
var pack = d3.layout.pack()
.size([diameter - margin, diameter - margin])
.value(function (d) { return d.size; })
var svg = d3.select("form").append("svg")
.attr("width", 1280)
.attr("height", 800)
.append("g")
.attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
d3.json("../Resources/output.json", function (error, root) {
if (error) throw error;
var focus = root,
nodes = pack.nodes(root),
view;
var circle = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function (d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.style("fill", function (d) { return d.children ? color(d.depth) : null; })
.on("click", function (d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); });
var text = svg.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function (d) { return d.parent === root ? 1 : 0; })
.style("display", function (d) { return d.parent === root ? "inline" : "none"; })
.text(function (d) { return d.name; });
var node = svg.selectAll("circle,text");
d3.select("form")
.on("click", function () { zoom(root); });
zoomTo([root.x, root.y, root.r * 2 + margin]);
function zoom(d) {
var focus0 = focus; focus = d;
var transition = d3.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", function (d) {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
return function (t) { zoomTo(i(t),d); };
});
transition.selectAll("text")
.filter(function (d) { return d.parent === focus || this.style.display === "inline"; })
.style("fill-opacity", function (d) { return d.parent === focus ? 1 : 0; })
.each("start", function (d) { if (d.parent === focus) this.style.display = "inline"; })
.each("end", function (d) { if (d.parent !== focus) this.style.display = "none"; });
}
function zoomTo(v) {
var k = diameter / v[2]; view = v;
console.log(d.x)
console.log(d.y)
console.log(d.r)
console.log(k)
node.attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; });
circle.attr("r", function(d) { return d.r * k; });
}
});
d3.select(self.frameElement).style("height", diameter + "px"); }
I had exactly this issue - although most browsers seemed to treat NaN as zero and so not complain, but Chrome wasn't happy at all.
For me I found the packed nodes collection returned from the packer had NaNs for zero value items passed in (which my data could contain). I just looped all the nodes after the pack call and set them to zero. Something like this should do it (it should probably be a recursive call to avoid duplication, but you get the idea!)...
// Replace any zero value items with zero x/y/r values
nodes.forEach( function (currNode) {
if (!currNode.count) {
currNode.x = 0;
currNode.y = 0;
currNode.r = 0;
currNode.children.forEach( function (currChild) {
if (!currChild.count) {
currChild.x = 0;
currChild.y = 0;
currChild.r = 0;
}
});
}
});
Hope this helps you
This is probably the simplest graph possible to create using d3js. And yet I am struggling.
The graph runs everything given to it in enter() and exit(). But everything in ENTER + UPDATE is completely ignored. WHY?
// Setup dimensions
var width = 200,
height = 200,
radius = Math.min(width, height) / 2,
// Setup a color function with 20 colors to use in the graph
color = d3.scale.category20(),
// Configure pie container
arc = d3.svg.arc().outerRadius(radius - 10).innerRadius(0), // Define the arc element
svg = d3.select(".pie").append("svg:svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"),
// This is the layout manager for the pieGraph
pie = d3.layout.pie()
.sort(null)
.value(function (d) {
return d.answers;
}),
// Allow two groups in the container. One overlapping the other, just to make sure that
// text labels never get hidden below pie arcs.
graphGroup = svg.append("svg:g").attr("class", "graphGroup"),
textGroup = svg.append("svg:g").attr("class", "labelGroup");
// Data is loaded upon user interaction. On angular $scope update, refresh graph...
$scope.$watch('answers', function (data) {
// === DATA ENTER ===================
var g = graphGroup.selectAll("path.arc").data(pie(data)),
gEnter = g.enter()
.append("path")
.attr("d", arc)
.attr("class", "arc"),
t = textGroup.selectAll("text.label").data(data),
tEnter = t.enter()
.append("text")
.attr("class", "label")
.attr("dy", ".35em")
.style("text-anchor", "middle");
// === ENTER + UPDATE ================
g.select("path.arc")
.attr("id", function (d) {
return d.data.id + "_" + d.data.selection;
})
.attr("fill", function (d, i) {
return color(i);
})
.transition().duration(750).attrTween("d", function (d) {
var i = d3.interpolate(this._current, d);
this._current = i(0);
return function (t) {
return arc(i(t));
};
});
t.select("text.label")
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
.text(function (d) {
return d.data.opt;
});
// === EXIT ==========================
g.exit().remove();
t.exit().remove();
});
This one example of the json structure given to the update function as "data":
[{"selection":"0","opt":"1-2 timer","answers":"7"},
{"selection":"1","opt":"3-4 timer","answers":"13"},
{"selection":"2","opt":"5-6 timer","answers":"5"},
{"selection":"3","opt":"7-8 timer","answers":"8"},
{"selection":"4","opt":"9-10 timer","answers":"7"},
{"selection":"5","opt":"11 timer eller mer","answers":"11"},
{"selection":"255","opt":"Blank","answers":"8"}]
You don't need the additional .select() to access the update selection. This will in fact return empty selections in your case, which means that nothing happens. To make it work, simply get rid of the additional .select() and do
g.attr("id", function (d) {
return d.data.id + "_" + d.data.selection;
})
// etc
t.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
// etc