D3 Multicolumn Relative Stack Chart - javascript

I am currently modifying the following example to produce a multi horizontal relative stack chart. The following example is a "single" stack chart example.
http://jsfiddle.net/zDkWP/
Here is my version however I get NaN X and Width values and I'm looking to find out why. Thank you.
Within the SVG the structure is as follows for each of the : -
g[ g [rect, rect, rect]], g[ g [rect, rect, rect]]
Here is my code: http://jsfiddle.net/scottietom/7c3vb4e9/
var dataset = [
[
[{x: 0,y: 100}],[{x: 0,y: 30}],[{x: 0,y: 50}]],
[[{x: 0,y: 100}],[{x: 0,y: 30}],[{x: 0,y: 50}]]
];
//Set up stack method
var stack = d3.layout.stack();
//Data, stacked
for (i=0; i<dataset.length; i++) {
stack(dataset[i]);
}
//Set up scales
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function (d) {
return d3.max(d, function (d) {
return d.y0 + d.y;
});
})])
.range([0, w]);
//Easy colors accessible via a 10-step ordinal scale
var colors = d3.scale.category10();
//or make your own colour palet
var color = d3.scale.ordinal()
.range(["#1459D9", "#148DD9", "#87ceeb", "#daa520"]);
//Create SVG element
var svg = d3.select(".pathanalysis_diagram")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Add a group for each row of data
var groups = svg.selectAll("g")
.data(dataset)
.enter()
.append("g")
.style("fill", function (d, i) {
return color(i);
});
// Add a rect for each data value
var rects = groups.selectAll("rect")
.data(function (d) {
return d;
})
.enter()
.append("rect")
.attr("x", function (d) {
return xScale(d.y0);
})
.attr("y", 50)
.attr("height", 50)
.attr("width", function (d) {
return xScale(d.y);
});

Here's a JSFiddle with your solution: http://jsfiddle.net/ee2todev/z73u6mro/
I called the nested dataset arrayOfDatasets so the changes become more clear.
First you have to call stack() for each of the datasets:
arrayOfDatasets.forEach(function (dataset) {stack(dataset); });
Then you have to retrieve the max for xScale:
var xScale = d3.scale.linear()
.domain([0, d3.max(arrayOfDatasets, function (dataset) {
return d3.max(dataset, function (d) {
return d3.max(d, function (d) {
return d.y0 + d.y;
});
})
})])
.range([0, w]);
Finally, you just have to access the data properly and translate the datasets so they don't overlap:
var groupOfGroups = svg.selectAll("g.toplevel")
.data(arrayOfDatasets)
.enter()
.append("g")
.attr("class", "toplevel")
.attr("transform", function(d, i ) {return "translate(0, " + (-i * 55) + ")"; });
// Add a group for each row of data
var groups = groupOfGroups.selectAll("g.dataset")
.data(function(d) {return d; })
.enter()
.append("g")
.attr("class", "dataset")
.style("fill", function (d, i) {
return color(i);
});
You might still store your datasets differently since it's not clear why you have the x values in them.

Related

d3.v5.min.js:2 Error: <path> attribute d: Expected moveto path command ('M' or 'm'), "function a(a){va…"

I am trying to plot a multi-line graph with d3.js but I am getting this error:
Error: attribute d: Expected moveto path command ('M' or 'm'), "function t(t){va…".
I have been stuck at it a while now and tried everything I could think of, to help you in your reflections, Find the code that I use below.
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
const dateParser = d3.timeFormat("%Y-%m-%d %H:%M:%S");
data.forEach(function(d) {
d.ts = dateParser(new Date(d.ts));
d.value = parseFloat(d.value)
});
// append the svg object to the body of the page
var svg = d3.select("#graph")
.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 + ")");
//Read the data
// group the data: I want to draw one line per group
var sumstat = d3.nest() // nest function allows to group the calculation per level of a factor
.key(function(d) { return d.key;})
.entries(data);
// Add X axis --> it is a date format
var x = d3.scaleLinear()
.domain(d3.extent(data, function(d) { return d.ts; }))
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(5));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.value; })])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y));
// color palette
var res = sumstat.map(function(d){ return d.key }) // list of group names
var color = d3.scaleOrdinal()
.domain(res).range(['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33','#a65628','#f781bf','#999999'])
// Draw the line
svg.selectAll(".line")
.data(sumstat)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", function(d){ return color(d.key) })
.attr("stroke-width", 1.5)
.attr("d", function(d){
return d3.line()
.x(function(d) { return x(d.ts); })
.y(function(d) { return y(d.value); })
})
I cannot figure what I am doing wrong, in case the example I am looking at is this link. I am fairly new to d3 and it is not an easy library to use
When you set the d attribute, you return the line generator itself from the data linking function, but you fail to execute it. Configuring the generator and executing it is a two-step process:
first, contruct and configure the generator (line is a function)
var line = d3.line()
.x(function(d) { return x(d.ts); })
.y(function(d) { return y(d.value); })
then, pass the function to attr(), so it will be executed as line(d)
svg.selectAll(".line")
.data(sumstat)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", function(d){ return color(d.key) })
.attr("stroke-width", 1.5)
.attr("d", line)

d3 bar graph error says xScale.bandwidth() is not a function

Can anyone tell me what I am doing wrong? I am getting an error.
Uncaught (in promise) TypeError: xScale.bandwidth is not a function
at barChart (bar_chart.js:53:27)
at bar_chart.js:84:5
I am trying to create a bar graph of this data.
year,total_ghg
2000,661.97
2001,665.72
2002,660.75
2003,583.65
2004,635.5
2005,598.44
2006,646.91
2007,646.46
2008,617.09
2009,633.8
2010,601.14
2011,644.74
2012,643.12
2013,555.26
2014,566.21
2015,566.47
2016,577.32
2017,623.08
2018,619.26
my js
var dataset;
function barChart(dataset) {
//declaring Varibales
var margin = {top:50, right:50, bottom:50, left:50};
var width = 500-margin.left-margin.right;
var height = 500-margin.top-margin.bottom;
//creating svg
var svg = d3
.select("#barchart")
.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 + ")");
//setting up scales
var xScale = d3
.scaleTime()
.domain([
d3.min(dataset, function (d) {
return d.year;
}),
d3.max(dataset, function (d) {
return d.year;
}),
])
.range([0,width]);
var yScale = d3.scaleLinear()
.domain([0,d3.max(dataset, function (d) {
return d.total_ghg;
})
])
.range([height,0]);
// Plotting axis
svg.append("g").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(xScale));
svg.append("g").call(d3.axisLeft(yScale));
//Set up groups
svg.selectAll("mybar")
.enter()
.data(dataset).append("rect")
.attr("x", function(d) {
return xScale(d.year);
})
.attr('y', function(d) {
return yScale(d.total_ghg);
})
.attr("width", xScale.rangeBand())
.attr('height', function(d) {
return height - yScale(d.total_ghg);
})
.attr("fill", "#004DA5")
.on("mouseover", function(event, d) {
d3.select(this).attr("fill", "orange");
var xPosition = parseFloat(d3.select(this).attr("x")) + xScale.bandwidth() / 2 - 5;
var yPosition = parseFloat(d3.select(this).attr("y")) + 20;
svg.append("text")
.attr("id", "tooltip")
.attr("x", xPosition)
.attr("y", yPosition)
.text(d);
}).on("mouseout", function(d) {
d3.select("#tooltip").remove();
d3.select(this)
.attr("fill", "#004DA5")
});
}
function init() {
d3.csv("src/data/total_ghp.csv", function (d) {
// + : force the year and month to be typed as a number instead of string
return {
year: d3.timeParse("%Y")(d.year),
total_ghg: +d.total_ghg,
};
}).then(function (data) {
dataset = data;
barChart(dataset);
});
}
window.addEventListener("load", init);
Any suggestions Please
What I have tried
ScaleOrdinal
rangeBandRounds
RangeBand()
instead of bandwidth
and a few more things like using a different d3 script same error in every scenario
Your xScale uses scaleTime, which is meant for charts where the axis represents time (i.e. line charts). For bar charts you should use scaleBand instead which does have the bandwidth function:
const xScale = d3
.scaleBand()
.domain([
d3.min(dataset, (d) => d.year),
d3.max(dataset, (d) = > d.year),
])
.range([0, width]);
More information on scaleBand can be found here: https://observablehq.com/#d3/d3-scaleband
There are some other mistakes in your code that prevent your bars from rendering:
Replace scaleTime with scaleBand for xScale
Replace xScale.rangeBand() with xScale.bandwidth()
Move .enter() to come after .data(dataset)

d3.js-adding different colors to one bar in stacked bar chart

I have created the stacked bar chart by using d3.js.In that I would like to display a single bar with different colors to highlight the data for particular x axis value like below.
The script i have used to plot stacked chart is below:
// Set the dimensions of the canvas / graph
var svg = d3.select("#svgID"),
margin = {top: 80, right: 140, bottom: 100, left: 100},
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 + ")");
var padding = -100;
//set the ranges
var x = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.20)
.align(0.1);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(["#008000", "#C00000", "#404040", "#4d4d4d"]);
var data = $("#svgID").data("values");
var keys = ["Pass", "Fail", "Average", "Worst"];
var legendKeysbar = ["Pass", "Fail", "Average", "Worst"];
var legendColorsbar = d3.scaleOrdinal()
.range(["#008000", "#C00000", "#404040", "#4d4d4d"]);
// Scale the range of the data
x.domain(data.map(function (d) {
return d.year;
}));
y.domain([0, d3.max(data, function (d) {
return d.total;
})]).nice();
z.domain(keys);
// add the Y gridlines
g.append("g").selectAll(".hline").data(y.ticks(10)).enter()
.append("svg:line")
.attr("x1", 0)
.attr("y1", function(d){ return y(d);})
.attr("x2", width)
.attr("y2", function(d){ return y(d);})
.style("stroke", "white")
.style("stroke-width", 1);
// append the rectangles for the bar chart
g.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", function (d) {
return z(d.key);
})
.selectAll("rect")
.data(function (d) {
return d;
})
.enter().append("rect")
.attr("x", function (d) {
return x(d.data.year);
})
.attr("y", function (d) {
return y(d[1]);
})
.attr("height", function (d) {
return y(d[0]) - y(d[1]);
})
Can you help me to update colors for single bar? is that possible by d3.js
Create a second color scale, then in the method where you assign color, perform a check to determine which color scale to use, e.g.,:
var z2 = d3.scaleOrdinal().range(<your color array here>)
...
.attr("fill", function (d) {
return d.data.year === "Dec" ? z2(d.key) : z(d.key);
})

D3js exit() and update working, can't enter()

I have a scatter plot with D3 and I'm trying to add circles to a selection based on changing data. I'm passing the data selection down to two functions: render and update. The render function is the initial render and update has the enter() and exit() methods. I can easily add the initial data set and get the circles no longer in the data set to exit. I'm using d.id as the d3 placeholder.
The problem: when I try to enter() the added data points, nothing happens. I've checked the length of the new data selection, and it's larger than the pre-existing. On the DOM, the smaller data set remains (the circles that were already there), but no new circles enter, even though the data set has changed.
I've looked through lots of tutorials regarding data joins, and I think I've appropriately called my enter() and exit() methods. What gives?
Here is my code:
var container = angular.element(document.querySelector('.chart-container'))[0];
var margin = {
top: container.clientHeight / 12,
right: container.clientWidth / 14,
bottom: container.clientHeight / 10,
left: container.clientWidth / 11
};
var w = container.clientWidth - margin.left - margin.right;
var h = container.clientHeight - margin.top - margin.bottom;
// ******** **************** ******** //
// ******** INITIAL RENDER ******** //
function render(input) {
console.log(Object.keys(input).length);
var xScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ctc"]; })])
.range([0, w])
.nice();
var yScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ttc"]; })])
.range([h, 0])
.nice();
var rScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["effective"]; })])
.range([2, 15]);
// *********** //
// SVG ELEMENT //
var svg = d3.select('.chart-container')
.append('svg')
.attr('class', 'scatter')
.attr('viewBox', '0, 0, ' + Math.round(w + margin.left + margin.right) + ', ' + Math.round(h + margin.top + margin.bottom))
.attr('preserveAspectRatio', 'xMinYMin')
// used to center element and make use of margins
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// add circles in group
var circles = svg.append('g')
.attr('class','circles')
.attr('clip-path','url(#chart-area)');
// add individual circles
var circle = circles.selectAll('circle')
.data(input, function(d) {return d.id;})
.enter()
.append('circle')
.attr('class', 'circle')
.attr('cx', function(d) { return xScale(d["ctc"]); })
.attr('cy', function(d) { return yScale(d["ttc"]); })
.attr('r', function(d) { return rScale(d["effective"]); })
.attr('fill', function(d, i) { return d["effective"]; })
.on('mouseover', function(d) {
tooltip.style('visibility', 'visible');
return tooltip.text(d["technology"]);
})
.on("mousemove", function(){ return tooltip.style("top",
(d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");})
// append clip path
svg.append('clipPath')
.attr('id','chart-area')
.append('rect')
.attr('class', 'rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', w)
.attr('height', h);
};
// ******** **************** ******** //
// ******** UPDATE ******** //
function update(updateObject) {
var input = updateObject;
var xScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ctc"]; })])
.range([0, w])
.nice();
var yScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ttc"]; })])
.range([h, 0])
.nice();
var rScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["effective"]; })])
.range([2, 15]);
var svg = d3.select('svg')
.data(input)
.attr('viewBox', '0, 0, ' + Math.round(w + margin.left + margin.right) + ', ' + Math.round(h + margin.top + margin.bottom))
.attr('preserveAspectRatio', 'xMinYMin')
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// BIND TO DATA
var circles = d3.selectAll('circle')
.data(input, function(d) { return d.id; });
// Circles Enter
circles.enter()
.insert('svg:circle')
.attr('class', 'circle')
.attr('cx', function(d) { return xScale(d["ctc"]); })
.attr('cy', function(d) { return yScale(d["ttc"]); })
.attr('r', function(d) { return rScale(d["effective"]); });
/*
.on('mouseover', function(d) {
tooltip.style('visibility', 'visible');
return tooltip.text(d["technology"]);
})
.on("mousemove", function(){ return tooltip.style("top",
(d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");})
*/
// UPDATE
circles.transition()
.duration(1000)
.attr('cx', function(d) { return xScale(d["ctc"]); })
.attr('cy', function(d) { return yScale(d["ttc"]); })
.attr('r', function(d) { return rScale(d["effective"]); });
// EXIT
circles.exit()
.transition()
.duration(500)
.attr('r', 0)
.style('opacity', 0)
.style('fill', 'gray')
.remove();
}
Update
here is a codepen for testing: http://codepen.io/himmel/pen/JdNJMM
The problem with the code is, that you are trying to use an object as data. d3.selection.data() takes an array, not an object. See the d3 wiki for more information on the data() function.
I have created an updated version of your codepen. I changed the data to an array and applied the correct conventional margin. Moreover I simplified the code by removing the double initialization of scales and the svg element.

Adding dynamic tooltips to a streamgraph d3.js

Im working on a streamgraph at the moment, I want to add tooltips to each layer similar to this http://archive.stamen.com/mtvmovies-streamgraph/chart.html
The tooltips I have now dont really work at all. All I get is 'NaN' displayed in the tooltip box.
Any suggestions?? My code is below.
Thanks in advance.
var customPalette = [
"#ff7f0e", "#2ca02c", "#00FFFF", "#d62728", "#9467bd",
"#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"
];
var format = d3.time.format("%y");
//creating margins around the graph
var margin = {top: 20, right: 30, bottom: 30, left: 200},
width = 1200 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//OUTPUT RANGE
var x = d3.time.scale()
.range([0, width]);
//OUTPUT RANGE
var y = d3.scale.linear()
.range([height, 0]);
//assining custom colors to layers
var colours = d3.scale.ordinal().range(customPalette);
var xAxis = d3.svg.axis()
.scale(x)
.orient("top")
.ticks(d3.time.years);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
//ctreate stack layout
var stack = d3.layout.stack()
.offset("wiggle")
.order("reverse")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.amount; });
//creates array of datya elements for stacked bar graph
var nest = d3.nest()
.key(function(d) { return d.age; });
//create area
var area = d3.svg.area()
//adds curviture
.interpolate("monotone")
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
var svg = d3.select("body").append("svg")
//defines length of x-axis
.attr("width", width + margin.left + margin.right)
//defines height of y-axis
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("data6.csv", function(data) {
data.forEach(function(d) {
// Convert strings to numbers
d.date = format.parse(d.date);
d.amount = +d.amount;
});
//returns an array of objects with a key feild (0-20yrs....)
//and a value array which contains associated records
var layers = stack(nest.entries(data));
//.extent() returns min and max values of argument
x.domain(d3.extent(data, function(d) { return d.date; }));
//
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return colours(i); });
//CURRENT TOOLTIP CODE
var toolTip = svg.selectAll("path")
.append("svg:title")
.text(function(d) { return (d.date) + (d.amount) });;
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + 0 + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
Are you hoping to concatenate the values of d.date and d.close, or are you interested in adding the values and returning the result?
If the latter:
I would bet that the types of d.date and d.close are not what you expect. I'd recommend, if you haven't already, putting some debug code in to check the types of those variables. Example:
//CURRENT TOOLTIP CODE
var toolTip = svg.selectAll("path")
.append("svg:title")
.text(function(d) {
console.log('d.date type:' + typeof d.date + 'd.close type:' + typeof d.close);
return (d.date) + (d.close);
}
);
Also, you have an extra semicolon at the end of that statement in your code snippet.
If the former:
One or both are of type number and Javascript will try to add them when you use the + operator instead of concatenating them. To return the strings:
.text(function(d) {
return d.date.toString() + ' ' + d.close.toString();
});

Categories

Resources