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
Related
I'm doing a visual project to show natural disaster in 1900-2018 using d3. I want add an interactive action that one can choose the first year and last year to show.
Originally I create the picture as the following:
d3.csv("output.csv", rowConventer, function (data) {
dataset = data;
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.range([padding, width - padding])
.paddingInner(0.05);
var yScale = d3.scaleLinear()
.domain([0,
d3.max(dataset, function (d) {
return d.AllNaturalDisasters;
})])
.range([height - padding, padding])
.nice();
stack = d3.stack().keys(["Drought", "Earthquake", "ExtremeTemperature", "ExtremeWeather", "Flood", "Impact", "Landslide", "MassMovementDry", "VolcanicActivity", "Wildfire"]);
series = stack(dataset);
gr = svg.append("g");
groups = gr.selectAll("g")
.data(series)
.enter()
.append("g")
.style("fill", function(d, i) {
return colors(i);
})
.attr("class", "groups");
rects = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("height", function(d) {
return yScale(d[0]) - yScale(d[1]);
})
.attr("width", xScale.bandwidth())
.append("title")
.text(function (d) {
var rect = this.parentNode;// the rectangle, parent of the title
var g = rect.parentNode;// the g, parent of the rect.
return d.data.Year + ", " + d3.select(g).datum().key + "," + (d[1]-d[0]);
});
d3.select("button")
.on("click", choosePeriod);
I have simplified some code to make my question simple. At the last row, I add an event listener to achieve what I described above. And the update function is choosePeriod. Now it is as following:
function choosePeriod() {
firstYear = parseInt(document.getElementById("FirstYear").value);
lastYear = parseInt(document.getElementById("LastYear").value);
d3.csv("output.csv", rowConventer, function (newdata) {
dataset = newdata;
series=stack(dataset);
groups.data(series);
groups.selectAll("rect")
.data(function (d) {
return d;
})
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScales(i);
})
.attr("y", function(d) {
return yScales(d[1]);
})
.attr("height", function(d) {
return yScales(d[0]) - yScales(d[1]);
})
.attr("width", xScales.bandwidth())
.append("title")
.text(function (d) {
var rect = this.parentNode;// the rectangle, parent of the title
var g = rect.parentNode;// the g, parent of the rect.
return d.data.Year + ", " + d3.select(g).datum().key + "," + (d[1]-d[0]);
});
groups.selectAll("rect")
.data(function (d) {
return d;
})
.exit()
.remove();
})
}
The change of dataset is achieved by rowConventer, which is not important in this question. Now the functionchoosePeriod is not running as envisioned! Theenter and the exit and update are all not work well! The whole picture is a mess! What I want is, for instance, if I input the firstYear=1900 and the lastYear=2000, then the picture should be updated with the period 1900-2000 to show. How can I achieve it?
I am unfamiliar the arrangement of the entire structure, I mean, at some place using d3.select() by class or id instead of label is better, right?
It looks like you've dealt with the enter and the exit selections. The only bit you're missing is the update selection, which will deal with the rectangles that already exist and don't need adding or removing. To do this copy your update pattern but just remove the enter().append() bit, e.g.:
groups.selectAll("rect")
.data(function (d) {
return d;
})
.attr("x", function(d, i) {
return xScales(i);
})
.attr("y", function(d) {
return yScales(d[1]);
})
.attr("height", function(d) {
return yScales(d[0]) - yScales(d[1]);
})
.attr("width", xScales.bandwidth())
.append("title")
.text(function (d) {
var rect = this.parentNode;// the rectangle, parent of the title
var g = rect.parentNode;// the g, parent of the rect.
return d.data.Year + ", " + d3.select(g).datum().key + "," + (d[1]-d[0]);
})
Hey guys I created a time series line chart using publicly available stock data.
Where I got to is the following:
Looks like what it is doing is connecting the first datapoint with the last datapoint which is why it is creating a line across the entire chart.
I looked online and read that to create a non continuous line chart I can add
.defined(function(d) { return d; })
I did but it didn't help.
My code:
//Set dimensions and margins for the graph
var margin = {top: 20, right: 20, bottom: 100, left: 70},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//Create canvas
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 + ")");
//Parse date
var parseDate = d3.timeParse("%Y-%m-%d");
//Set the ranges
var x = d3.scaleTime()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
// Define the line
var valueLine = d3.line()
.defined(function(d) { return d; })
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); });
//Import data from api
d3.json("api_all.php", function(error, data) {
if (error) throw error;
data.forEach(e => {
e.date = parseDate(e.date);
e.value = +e.close;
e.stockName = e.stock_name;
e.stockSymbol = e.stock_symbol;
});
//Create nest variable
var nest = d3.nest()
.key(function(d){ return d.stockSymbol; })
.entries(data);
console.log(nest);
//Scale the range of the data
//x axis scale for entire dataset
x.domain(d3.extent(data, function(d) { return d.date; }));
//y.domain([0, d3.max(data, function(d) { return d.value; })]);
//Add the x axis
var xaxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class", "x axis")
.call(d3.axisBottom(x));
//Add x axis label
svg.append("text")
.attr("transform", "translate(" + (width/2) + "," + (height + margin.top + 10) + ")")
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Date")
.attr("class", "x axis label");
//Create dropdown
var dropDown = d3.select("#dropDown")
dropDown
.append("select")
.selectAll("option")
.data(nest)
.enter()
.append("option")
.attr("value", function(d){ return d.key; })
.text(function(d){ return d.key; })
// Function to create the initial graph
var initialGraph = function(stock){
// Filter the data to include only stock of interest
var selectStock = nest.filter(function(d){
return d.key == stock;
})
console.log(selectStock)
//Unnest selectStock for y axis
var unnested = function(data, children){
var out = [];
data.forEach(function(d, i){
console.log(i, d);
d_keys = Object.keys(d);
console.log(i, d_keys)
values = d[children];
values.forEach(function(v){
d_keys.forEach(function(k){
if (k != children) { v[k] = d[k]}
})
out.push(v);
})
})
return out;
}
var selectStockUnnested = unnested(selectStock, "values");
//Scale y axis
var selectStockGroups = svg.selectAll(".stockGroups")
.data(selectStock, function(d){
return d ? d.key : this.key;
})
.enter()
.append("g")
.attr("class", "stockGroups")
.each(function(d){
y.domain([0, d3.max(selectStockUnnested, function(d) { return d.value; })])
console.log(selectStockUnnested);
});
var initialPath = selectStockGroups.selectAll(".line")
.data(selectStock)
.enter()
.append("path")
initialPath
.attr("d", function(d){ return valueLine(d.values) })
.attr("class", "line")
//Add the y axis
var yaxis = svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y)
.ticks(5)
.tickSizeInner(0)
.tickPadding(6)
.tickSize(0, 0));
//Add y axis label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - 60)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Price")
.attr("class", "y axis label");
}
// Create initial graph
initialGraph("1301.T")
// Update the data
var updateGraph = function(stock){
// Filter the data to include only stock of interest
var selectStock = nest.filter(function(d){
return d.key == stock;
})
console.log(selectStock);
//Unnest selectStock for y axis
var unnested = function(data, children){
var out = [];
data.forEach(function(d, i){
console.log(i, d);
d_keys = Object.keys(d);
console.log(i, d_keys)
values = d[children];
values.forEach(function(v){
d_keys.forEach(function(k){
if (k != children) { v[k] = d[k]}
})
out.push(v);
})
})
return out;
}
var selectStockUnnested = unnested(selectStock, "values");
// Select all of the grouped elements and update the data
var selectStockGroups = svg.selectAll(".stockGroups")
.data(selectStock)
.each(function(d){
y.domain([0, d3.max(selectStockUnnested, function(d) { return d.value; })])
});
// Select all the lines and transition to new positions
selectStockGroups.selectAll("path.line")
.data(selectStock)
.transition()
.duration(1000)
.attr("d", function(d){
return valueLine(d.values)
})
// Update the Y-axis
d3.select(".y")
.transition()
.duration(1500)
.call(d3.axisLeft(y)
.ticks(5)
.tickSizeInner(0)
.tickPadding(6)
.tickSize(0, 0));
}
// Run update function when dropdown selection changes
dropDown.on('change', function(){
// Find which stock was selected from the dropdown
var selectedStock = d3.select(this)
.select("select")
.property("value")
console.log(selectedStock);
// Run update function with the selected stock
updateGraph(selectedStock)
});
});
</script>
</body>
I have csv data with a column of years and then columns of age groups with rates:
year,15-19,20-24,25-29,...
1999,0,1.3,8.1,...
2000,0,1.3,8.3,...
.
.
I've mapped the data as shown below. Once mapped the data is in the form of:
>Array[Object, Object, ...]
Each Object is of the form:
>id:"age group"
values:Array[]
>Object
>rate: ""
>year: ""
I'm having trouble with the max value for the y.domain. It seems to be pulling the max value from my 4th object and stopping there. Any help figuring out what's wrong with the max call would be appreciated.
var svg = d3.select("svg"),
margin = {top: 20, right: 80, bottom: 30, left: 50},
width = svg.attr("width") - margin.left - margin.right,
height = svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// define scales
var x = d3.scaleLinear().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
//define color scale
z = d3.scaleOrdinal(d3.schemeCategory10);
//define line generator
var line = d3.line()
.curve(d3.curveBasis)
.x(function(d) { return x(d.year); })
.y(function(d) { return y(d.rate); });
//load data
d3.csv("breaseByageSpread.csv", function(error, data) {
if(error) throw error;
var ages = data.columns.slice(1).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {year: d.year, rate: d[id]};
})
};
});
//define x axis
x.domain(d3.extent(data, function(d) { return d.year; }));
//define y axis
y.domain([
d3.min(ages, function(c) { return d3.min(c.values, function(d) { return d.rate; }); }),
d3.max(ages, function(c) { return d3.max(c.values, function(d) { return d.rate; }); })
]);
// define color scale
z.domain(ages.map(function(c) { return c.id; }));
//append x axis
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.ticks(15)
.tickFormat(d3.format('d')));
//append y axis
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -50)
.attr('x', -125)
.attr("dy", "0.71em")
.attr("fill", "#000")
.text("Rate per 100,000 ppl");
//append rate data to svg
var rate = g.selectAll(".rate")
.data(ages)
.enter().append("g")
.attr("class", "age");
//append rate path to svg
rate.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return z(d.id); });
rate.append("text")
.datum(function(d) { return {id: d.id, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.year) + "," + y(d.value.rate) + ")"; })
.attr("x", 3)
.attr("dy", "0.35em")
.style("font", "10px sans-serif")
.text(function(d) { return d.id; });
function type(d, _, columns) {
//d.date = parseTime(d.date);
for (var i = 1, n = columns.length, c; i < n; ++i) d[c = columns[i]] = +d[c];
return d;
};
});
I found the problem. The rates data was coming in as strings. To convert it to a numeric I changed the following code when loading the data:
Old:
values: data.map(function(d) {
return {year: d.year, rate: d[id]};
New:
values: data.map(function(d) {
return {year: d.year, rate: +d[id]};
I have a project that almost works the way I want. When a smaller dataset is added, slices are removed. It fails when a larger dataset is added. The space for the arc is added but no label or color is added for it.
This is my enter() code:
newArcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
What am I doing wrong?
I've fixed the code such that it works now:
// Tween Function
var arcTween = function(a) {
var i = d3.interpolate(this.current || {}, a);
this.current = i(0);
return function(t) {
return arc(i(t));
};
};
// Setup all the constants
var duration = 500;
var width = 500
var height = 300
var radius = Math.floor(Math.min(width / 2, height / 2) * 0.9);
var colors = ["#d62728", "#ff9900", "#004963", "#3497D3"];
// Test Data
var d2 = [{
label: 'apples',
value: 20
}, {
label: 'oranges',
value: 50
}, {
label: 'pears',
value: 100
}];
var d1 = [{
label: 'apples',
value: 100
}, {
label: 'oranges',
value: 20
}, {
label: 'pears',
value: 20
}, {
label: 'grapes',
value: 20
}];
// Set the initial data
var data = d1
var updateChart = function(dataset) {
arcs = arcs.data(donut(dataset), function(d) { return d.data.label });
arcs.exit().remove();
arcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
arcs.transition()
.duration(duration)
.attrTween("d", arcTween);
sliceLabel = sliceLabel.data(donut(dataset), function(d) { return d.data.label });
sliceLabel.exit().remove();
sliceLabel.enter()
.append("text")
.attr("class", "arcLabel")
.attr("transform", function(d) {
return "translate(" + (arc.centroid(d)) + ")";
})
.attr("text-anchor", "middle")
.style("fill-opacity", function(d) {
if (d.value === 0) {
return 1e-6;
} else {
return 1;
}
})
.text(function(d) {
return d.data.label;
});
sliceLabel.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + (arc.centroid(d)) + ")";
})
.style("fill-opacity", function(d) {
if (d.value === 0) {
return 1e-6;
} else {
return 1;
}
});
};
var color = d3.scale.category20();
var donut = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.innerRadius(radius * .4)
.outerRadius(radius);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var arc_grp = svg.append("g")
.attr("class", "arcGrp")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var label_group = svg.append("g")
.attr("class", "lblGroup")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var arcs = arc_grp.selectAll("path");
var sliceLabel = label_group.selectAll("text");
updateChart(data);
// returns random integer between min and max number
function getRand() {
var min = 1,
max = 2;
var res = Math.floor(Math.random() * (max - min + 1) + min);
//console.log(res);
return res;
}
// Update the data
setInterval(function(model) {
var r = getRand();
return updateChart(r == 1 ? d1 : d2);
}, 2000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I'm looking for some hints as to what I am doing wrong with a Sankey diagram I'm creating. I am charting changes in food consumption over time, and using the Sankey layout to visualize how these values changed over a period of forty years.
The bl.ock and small dataset are here. The relevant code:
var margin = {top: 1, right: 1, bottom: 6, left: 1},
width = 1260 - margin.left - margin.right,
height = 1000 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"),
format = function(d) { return formatNumber(d) + " TWh"; },
color = d3.scale.category20();
var svg = d3.select("#chart").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 sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
// ========================== Prepare data ==========================
queue()
.defer(d3.csv, "grains.csv")
.await(ready);
// ========================== Start viz ==========================
function ready(error, csv_data) {
nodes = [];
edges = [];
nodesArray = [];
// Scales
yearScale = d3.scale.linear().domain([1640,1688]).range([20,width -20]);
radiusScale = d3.scale.linear().domain([0,300]).range([2,12]).clamp(true);
chargeScale = d3.scale.linear().domain([0,100]).range([0,-100]).clamp(true);
uniqueValues = d3.set(nodesArray.map(function(d) {return d.name})).values();
colorScale = d3.scale.category20b(uniqueValues);
sortScale = d3.scale.ordinal().domain(uniqueValues).rangePoints([-0.001,.001]);
// Create a JSON link array
// This creates unique nodes for each item and its corresponding date.
// For example, nodes are rendered as "peas-1640," "peas-1641," etc.
csv_data.forEach(function(link) {
key = link.translation + '-' + link.date;
link.source = nodes[key] || (nodes[key] = {name: link.translation, date: link.date, origX: yearScale(parseInt(link.date)), value: link.value || 0});
});
// Build the edgesArray array
// This creates the edgesArray to correspond with unique nodes. We're telling
// items and dates to remain together. So, the code below tells the graph
// layout that `1641` is preceded by `1640` and followed by `1642`, etc.
var y = "→";
for (x in nodes) {
nodesArray.push(nodes[x])
if(nodes[y]) {
nodes[y].date = parseInt(nodes[y].date);
if (nodes[y].name == nodes[x].name) {
var newLink = {source:nodes[y], target:nodes[x]}
edges.push(newLink);
}
}
y = x;
}
sankey
.nodeWidth(10)
.nodePadding(10)
.size([1200, 1200])
.nodes(nodesArray.filter(function(d,i) {return d.date < 1650}))
.links(edges.filter(function(d,i) { return i < 50 && d.source.date < 1650 && d.target.date < 1650} )) // filtering to test a smaller data set
.layout(32);
var link = svg.append("g").selectAll(".link")
.data(edges.filter(function(d,i) { return i < 50 && d.source.date < 1650 && d.target.date < 1650} )) // filtering to test a smaller data set
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
link.append("title")
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
var node = svg.append("g").selectAll(".node")
.data(nodesArray.filter(function(d,i) {return d.date < 1650})) // filtering to test a smaller data set
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() { this.parentNode.appendChild(this); })
.on("drag", dragmove));
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) { return d.color = color(d.name.replace(/ .*/, "")); })
.style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
.append("title")
.text(function(d) { return d.name + "\n" + format(d.value); });
node.append("text")
.attr("x", -6)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.name; })
.filter(function(d) { return d.x < width / 2; })
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
function dragmove(d) {
d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
sankey.relayout();
link.attr("d", path);
}
};
Unfortunately, I'm getting an error as you can see in the bl.ock. The Boss suggested it might be a circular link but I'm at a bit of a loss. Any hints or suggestions?
EDIT: For some clarity, I'm after something like this:
(Source)
From what I can tell, I think I'm building the nodes and edges correctly. If we look at the console for the nodes array and edges array:
It's not like a usual Sankey or alluvial diagram, which, as I've often seen them, shows collapses and expansions of items. In my case the date, food item, and value are all a single stream throughout the length of the visualization but are resized/repositioned based on the value for a given year (like the example image above).