Accessing data properties for d3 v4 stacked bar chart - javascript

I have been working on creating a stacked bar chart using d3 v4 but with a JSON file input. This question is a continuation of a past question:Converting data structure of a JSON array for stacked bar chart.
My data array consists of numerous objects (each object is a hospital and has values such as the count of males, females, and categories).
Based from this, how can I access the individual properties of my data array and populate it accordingly? This means that when I hover over a certain bar, the gender type, count, percentage, and categories change.
**EDIT: I changed my post to show a better understanding about my data structure. Also, I do not think the provided link doesn't solve my problem that I have. I did used that method and it didn't work.
**EDIT2: Added more of my JavaScript code
The data structure is in a JSON format (An array of objects) looks like:
[{
"hospitalName": "hospital1",
"Diseases of the Circulatory System": 1,
"Diseases of the Digestive System": 1,
"Diseases of the Nervous System & Sense Organs": 2,
"Diseases of the Respiratory System": 1,
"Infectious and Parasitic Diseases": 278,
"Injury and Poisoning": 9,
"Mental Disorders": 4,
"Neoplasms": 4,
"No Diagnosis Code in Medical Record": 10,
"Perinatal Period Conditions": 1,
"Females": 223,
"Males": 88,
"Unknown": 0,
"count": 311
},
{
"hospitalName": "hospital2",
"No Diagnosis Code in Medical Record": 1,
"Females": 0,
"Males": 1,
"Unknown": 0,
"count": 1
},
{
"hospitalName": "hospital3",
"Neoplasms": 5,
"Females": 0,
"Males": 2,
"Unknown": 3,
"count": 5
}]
This is my current stacked bar chart.
The output of the tooltip would look something like this:
*My JavaScript code:
var svg = d3.select("svg"),
margin = { top: 20, right: 20, bottom: 120, left: 40 },
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 x = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.15)
.align(0.1);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(["#a95e65", "#5eaaa2", "#6b486b"]);
d3.json("hospitals2.json", function(error, data) {
if (error) throw error;
var newData = {};
data.forEach(element => {
var name = element.hospitalName;
var hospital = newData[name];
if (!hospital) {
hospital = { hospitalName: name, Females: 0, Males: 0, Unknown: 0, count: 0 };
newData[name] = hospital;
}
hospital[element.category] = +element.count;
hospital.Females += +element.Females;
hospital.Males += +element.Males;
hospital.Unknown += +element.Unknown;
hospital.count += +element.count;
});
data = [];
for (const key in newData) {
if (newData.hasOwnProperty(key)) {
data.push(newData[key]);
}
}
console.log(data);
var columns = d3.keys(data[0]);
var keys = columns.slice(1, 4);
console.log(keys);
data.sort(function(a, b) { return b.total - a.total; });
x.domain(data.map(function(d, i) { return i; }));
y.domain([0, d3.max(data, function(d) { return d.count; })]).nice();
z.domain(keys);
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("class", "bar")
.attr("x", function(d, i) { return x(i); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth())
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] - 60;
var yPosition = d3.mouse(this)[1] - 60;
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select(".count").html("Count: " + (d[1] - d[0]) + "<br>")
tooltip.select(".percent").html("(" + (Math.round((d[1] - d[0]) / d.data.count * 100)) + "%" + ")")
})
.on("mouseover", function() {
tooltip.style("display", "inline");
})
.on("mouseout", function() {
tooltip.style("display", "none");
});
g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickFormat(function(d, i) { return data[i].MTFID }))
.selectAll("text")
.attr("x", -11)
.attr("y", 7)
.attr("dy", ".35em")
.attr("transform", "rotate(290)")
.style("text-anchor", "end");
g.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y).ticks(null, "s"))
//.call(d3.axisLeft(y).tickFormat(d => Math.round(d * 100 / d3.max(data, function(d) { return d.count })) + "%"))
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks().pop()) + 0.5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-family", "Montserrat, sans-serif")
.attr("font-size", "13px")
.attr("text-anchor", "start")
.text("Population");
var legend = g.append("g")
.attr("class", "legend")
.attr("text-anchor", "end")
.selectAll("g")
.data(keys.slice().reverse())
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 19)
.attr("width", 19)
.attr("height", 19)
.attr("fill", z);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9.5)
.attr("dy", "0.32em")
.text(function(d) { return d; });
var tooltip = svg.append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("rect")
.attr("width", 100)
.attr("height", 105)
.attr("fill", "#DCDCDC")
tooltip.append("text")
.attr("class", "count")
.attr("x", 50)
.attr("dy", "1.2em")
.style("text-anchor", "middle")
tooltip.append("text")
.attr("class", "percent")
.attr("x", 50)
.attr("dy", "2em")
.style("text-anchor", "middle")
tooltip.append("text")
.attr("class", "category")
});

Related

Show yearly X labels for June instead of January on d3.js chart

I have a line chart in d3.js where I have labels on the X-axis every year (showing 20 years of data). The labels are created with:
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickFormat(d3.timeFormat("%b/%d/%Y")).ticks(d3.timeYear))
.selectAll("text")
.style("text-anchor", "end")
.attr("dy", ".25em")
.attr("transform", "rotate(-45)");
The outcome looks like this:
Now, the thing is, I need the labels to not be placed on January 1st of each year - I need them on June 30th. How can I accomplish that?
See the Fiddle here to try for yourself.
One way of doing this is to specify explicitly each desired axis tick's value. The function axis.tickValues is designed for this.
The following function generates an array of dates, starting from the min date (june 28th, 2000 in your case), and adding a year until the max date (june 28th, 2020) is reached. It is necessary to go through this generation step because the dataset does not contain data for all years.
function generateTickvalues(min, max) {
let res = []
, currentExtent = new Date(min.valueOf())
while(currentExtent <= max) {
res.push(new Date(currentExtent.valueOf()))
currentExtent.setFullYear(currentExtent.getFullYear() + 1);
}
return res
}
Remark: new Date(date.valueOf()) is necessary in this function so that the date values from the original dataset are not overwritten.
The min and max dates from the dataset can conveniently be found using d3.extent. This array can also be used when calling x.domain.
let dateExtent = d3.extent(data, function(d) { return d.date})
let tickValues = generateTickvalues(dateExtent[0], dateExtent[1])
x.domain(dateExtent);
Then, when generating the axis, call the function axis.tickValues, passing the array of years starting from June just generated:
d3.axisBottom(x)
.tickFormat(d3.timeFormat("%b/%d/%Y"))
.ticks(d3.timeYear)
.tickValues(tickValues)
Demo in the snippet below:
const data = [
{ value: 46, date: '2000-06-28', formatted_date: '06/28/2000' },
{ value: 48, date: '2003-06-28', formatted_date: '06/28/2003' },
{ value: 26, date: '2004-06-28', formatted_date: '06/28/2004' },
{ value: 36, date: '2006-06-28', formatted_date: '06/28/2006' },
{ value: 40, date: '2010-06-28', formatted_date: '06/28/2010' },
{ value: 48, date: '2012-06-28', formatted_date: '06/28/2012' },
{ value: 34, date: '2018-06-28', formatted_date: '06/28/2018' },
{ value: 33, date: '2020-06-28', formatted_date: '06/28/2020' }
];
create_area_chart(data, 'history-chart-main');
function generateTickvalues(min, max) {
let res = []
, currentExtent = new Date(min.valueOf())
while(currentExtent <= max) {
res.push(new Date(currentExtent.valueOf()))
currentExtent.setFullYear(currentExtent.getFullYear() + 1);
}
return res
}
function create_area_chart(data, target){
document.getElementById(target).innerHTML = '';
var parentw = document.getElementById(target).offsetWidth;
var parenth = 0.6*parentw;
var svg = d3.select('#'+target).append("svg").attr("width", parentw).attr("height", parenth),
margin = {top: 20, right: 20, bottom: 40, 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 + ")");
var parseTime = d3.timeParse("%Y-%m-%d");
bisectDate = d3.bisector(function(d) { return d.date; }).left;
var x = d3.scaleTime()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var area = d3.area()
.x(function (d) { return x(d.date); })
.y1(function (d) { return y(d.value); });
data.forEach(function (d) {
//only parse time if not already parsed (i.e. when using time period filters)
if(parseTime(d.date))
d.date = parseTime(d.date);
d.value = +d.value;
});
let dateExtent = d3.extent(data, function(d) { return d.date})
let tickValues = generateTickvalues(dateExtent[0], dateExtent[1])
x.domain(dateExtent);
y.domain([0, 1.05 * d3.max(data, function (d) { return d.value; })]);
area.y0(y(0));
g.append("rect")
.attr("transform", "translate(" + -margin.left + "," + -margin.top + ")")
.attr("width", svg.attr("width"))
.attr('class', 'overlay')
.attr("height", svg.attr("height"))
.on("mouseover", function () {
d3.selectAll(".eps-tooltip").remove();
d3.selectAll(".eps-remove-trigger").remove();
focus.style("display", "none");
});
g.append("path")
.datum(data)
.attr("fill", "#f6f6f6")
.attr("d", area);
//create line
var valueline = d3.line()
.x(function (d) { return x(d.date); })
.y(function (d) { return y(d.value); });
g.append("path")
.data([data])
.attr('fill', 'none')
.attr('stroke', '#068d46')
.attr("class", "line")
.attr("d", valueline);
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickFormat(d3.timeFormat("%b/%d/%Y")).ticks(d3.timeYear).tickValues(tickValues))
.selectAll("text")
.style("text-anchor", "end")
.attr("dy", ".25em")
.attr("transform", "rotate(-45)");
g.append("g")
.call(d3.axisLeft(y)
.tickFormat(function (d) { return "$" + d }))
.append("text")
.attr("fill", "#068d46")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end");
var focus = g.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("line")
.attr("class", "x-hover-line hover-line")
.attr("y1", 0)
.attr("y2", height);
focus.append("line")
.attr("class", "y-hover-line hover-line")
.attr("x1", width)
.attr("x2", width);
focus.append("circle")
.attr("fill", "#068d46")
.attr("r", 4);
focus.append("text")
.attr("class", "text-date focus-text")
.attr("x", 0)
.attr("y", -20)
.attr("dy", ".31em")
.style("text-anchor", "middle");
focus.append("text")
.attr("class", "text-val focus-text")
.attr("x", 0)
.attr("y", -30)
.attr("dy", ".31em")
.style("text-anchor", "middle");
g.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function () { focus.style("display", null); })
.on("mousemove", function () {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.year > d1.year - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")");
focus.select(".text-date").text(function () { return d.formatted_date; });
focus.select(".text-val").text(function () { return '$' + d.value; });
focus.select(".x-hover-line").attr("y2", height - y(d.value));
focus.select(".y-hover-line").attr("x2", width + width);
});
}
.chart {
text-align: center;
padding: 10px 10px 25px 10px;
background: #f6f6f6;
}
.chart svg {
overflow: visible;
}
.chart .overlay {
fill: none;
pointer-events: all;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.8.0/d3.min.js"></script>
<div class="chart" id="history-chart-main"></div>
You can specify an interval with axis.ticks(), D3 provides a number of built in intervals which we can use and then filter for the appropriate day/month/time.
If we wanted June 1 every year we could use:
var axis = d3.axisBottom(x)
.tickFormat(d3.timeFormat("%b/%d/%Y"))
.ticks(d3.timeMonth.filter(function(d) { return d.getMonth() == 5; })))
If we want June 30 we can specify with a bit more specificity:
var axis = d3.axisBottom(x)
.tickFormat(d3.timeFormat("%b/%d/%Y"))
.ticks(d3.timeDay.filter(function(d) { return d.getMonth() == 5 && d.getDate() == 30 }))
The d3-time docs have some more description of d3 intervals and the d3-scale documentation time scale details have some example implementations of this method here.
Here's an updated fiddle

Show values on top of line graph

I'm trying to display values on the top of line graph using d3 js. How can I append values to the path of the line graph?
And How can I display month-year as X-axis ticks like May'18, June'19?
I tried using the below code, but the values are not displaying.
lineSvg.selectAll("path")
.data(data)
.enter().append("text")
.attr("class", "line")
.attr("text-anchor", "middle")
.attr("x", function(d) { return 10; })
.attr("y", function(d) { return yscale(d.value) - 5; })
.text(function(d) { return d.value; });
Here is my complete code:
var data = [{
"date": "2018-1",
"value": 40.13,
"status": 1
}, {
"date": "2018-2",
"value": 45.88,
"status": 1
}, {
"date": "2018-3",
"value": 50.89,
"status": 1
}, {
"date": "2018-4",
"value": 55.87,
"status": 1
}, {
"date": "2018-5",
"value": 88.54,
"status": 1
}, {
"date": "2018-6",
"value": 74.41,
"status": 1
}, {
"date": "2018-7",
"value": 98.56,
"status": 1
}, {
"date": "2018-8",
"value": 81.05,
"status": 1
}, {
"date": "2018-9",
"value": 58.13,
"status": 1
}, {
"date": "2018-10",
"value": 95.86,
"status": 1
}, {
"date": "2018-11",
"value": 78.13,
"status": 1
}, {
"date": "2018-12",
"value": 98.86,
"status": 1
}, {
"date": "2019-1",
"value": 105.86,
"status": 0
}, {
"date": "2019-2",
"value": 110.86,
"status": 0
}];
/* Monday 2012 */
var data1 = data
var dateformat = "%Y-%m"
drawTimeSeriesGraph(data1, dateformat);
/*
Tooltip from: http://bl.ocks.org/d3noob/6eb506b129f585ce5c8a
line graph from here: http://www.d3noob.org/2012/12/starting-with-basic-d3-line-graph.html
*/
function drawTimeSeriesGraph(data, dateformat) {
//Set bounds for red dots
var lbound = 0.045,
ubound = 0.075;
// Set the dimensions of the canvas / graph
var margin = {
top: 50,
right: 150,
bottom: 50,
left: 50
},
width = 900 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format(dateformat).parse,
formatDate = d3.time.format(dateformat),
bisectDate = d3.bisector(function(d) {
return d.date;
}).left;
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(10);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(10);
// Define the line
var valueline = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.value);
}).interpolate("linear");
var full_date = new Date();
var day = full_date.getDay(); //returns 0 - 6
var month = full_date.getMonth() + 1; //returns 0 - 11
var year = full_date.getFullYear(); //returns 4 digit year ex: 2000
var my = year + "-" + month;
//alert(my);
// Adds the svg 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 + ")");
var lineSvg = svg.append("g");
var focus = svg.append("g")
.style("display", "none");
// Get the data
data.forEach(function(d) {
d.date = parseDate(d.date);
d.value = +d.value;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return d.value;
})]);
//Use below if instead you want to define the y limits:
//y.domain([0, 0.11]);
// Add the valueline path.
var lineGraph2 = lineSvg.append("path")
.attr("class", "line")
.attr("d", valueline(data.filter(function(d) {
return d.status > 0;
})))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none");
var lineGraph1 = lineSvg.append("path")
.attr("class", "line")
.attr("d", valueline(data.slice(-3)))
.attr("stroke", "red")
.attr("stroke-width", 2)
.attr("fill", "none");
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// append the x line
focus.append("line")
.attr("class", "x")
.style("stroke", "blue")
.style("stroke-dasharray", "3,3")
.style("opacity", 0.5)
.attr("y1", 0)
.attr("y2", height);
// append the y line
focus.append("line")
.attr("class", "y")
.style("stroke", "blue")
.style("stroke-dasharray", "3,3")
.style("opacity", 0.5)
.attr("x1", width)
.attr("x2", width);
// append the circle at the intersection
focus.append("circle")
.attr("class", "y")
.style("fill", "none")
.style("stroke", "blue")
.attr("r", 4);
// place the value at the intersection
focus.append("text")
.attr("class", "y1")
.style("stroke", "white")
.style("stroke-width", "3.5px")
.style("opacity", 0.8)
.attr("dx", 8)
.attr("dy", "-.3em");
focus.append("text")
.attr("class", "y2")
.attr("dx", 8)
.attr("dy", "-.3em");
// place the date at the intersection
focus.append("text")
.attr("class", "y3")
.style("stroke", "white")
.style("stroke-width", "3.5px")
.style("opacity", 0.8)
.attr("dx", 8)
.attr("dy", "1em");
focus.append("text")
.attr("class", "y4")
.attr("dx", 8)
.attr("dy", "1em");
// append the rectangle to capture mouse
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.on("mouseover", function() {
focus.style("display", null);
})
.on("mouseout", function() {
focus.style("display", "none");
})
.on("mousemove", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus.select("circle.y")
.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")");
focus.select("text.y1")
.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")")
.text(d.value.toFixed(2));
focus.select("text.y2")
.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")")
.text(d.value.toFixed(2));
focus.select("text.y3")
.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")")
.text(formatDate(d.date));
focus.select("text.y4")
.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")")
.text(formatDate(d.date));
focus.select(".x")
.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")")
.attr("y2", height - y(d.value));
focus.select(".y")
.attr("transform", "translate(" + width * -1 + "," + y(d.value) + ")")
.attr("x2", width + width);
};
svg.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("text-decoration", "underline")
};
I'm trying to get line graph like this:
For the axis you have to set the tickFormat.
xAxis.tickFormat(d3.time.format("%b'%y"));
or if you want full month name
xAxis.tickFormat(d3.time.format("%B'%y"));
For the text just add them like you do with circles or rects
svg.selectAll(".val").data(data)
.enter()
.append('text')
.attr("x", d => x(d.date))
.attr("y", d => y(d.value))
.attr("dy", "-0.5em")
.attr("text-anchor", "middle")
.text(d => d.value);

Grouping bar and line charts

I am trying to group bar chart and line chart in d3 js and I followed one Link for that purpose,
Here is what my Ajax is returning in response:
[
{
"date_created": "2017-12-27",
"jobs_fail": 19,
"jobs_resub": 31,
"jobs_success": 50
},
{
"date_created": "2017-12-29",
"jobs_fail": 18,
"jobs_resub": 25,
"jobs_success": 44
},
{
"date_created": "2017-12-28",
"jobs_fail": 8,
"jobs_resub": 24,
"jobs_success": 44
},
{
"date_created": "2018-01-02",
"jobs_fail": 2,
"jobs_resub": 0,
"jobs_success": 0
}
]
And what I am trying to show is displaying the jobs_fail and jobs_resub as a bar and jobs_sucess as line chart in same graphs with respect to date_created,
Here is my code for that purpose.
<script>
function get_data() {
console.log("create post is working!") // sanity check
return $.ajax({
url : "/group/guest/query/", // the endpoint
type : "GET", // http method
});
};
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var colorRange = d3.scale.category20();
var color = d3.scale.ordinal()
.range(colorRange.range());
var divTooltip = d3.select("body").append("div").attr("class", "toolTip");
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10, "");
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 ajdata = get_data();
var k = [];
ajdata.success(function (data) {
var obj = jQuery.parseJSON(data);
alert(data);
var options = d3.keys(obj[0]).filter(function(key) { if (key != "date_created" & key != "jobs_success" ) { return key }}); // & key != "date_created"){return key} });
var line_option = d3.keys(obj[0]).filter(function(key) { if (key == "jobs_success" & key == "date_created"){return key} });
alert(options);
obj.forEach(function(d) {
d.valores = options.map(function(name) {return { name: name, value: +d[name]}; });});
x0.domain(obj.map(function(d) { return d.date_created; }));
x1.domain(options).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(obj, function(d) { return d3.max(d.valores, function(d) { return d.value; }); })]);
var line = d3.svg.line()
.x(function(d) { return x1(d.date_created); })
.y(function(d) { return y(d.jobs_success); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Number of jobs");
var bar = svg.selectAll(".bar")
.data(obj)
.enter().append("g")
.attr("class", "rect")
.attr("transform", function(d) { return "translate(" + x0(d.date_created) + ",0)"; });
bar.selectAll("rect")
.data(function(d) { return d.valores; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("value", function(d){return d.name;})
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); });
bar
.on("mousemove", function(d){
divTooltip.style("left", d3.event.pageX+10+"px");
divTooltip.style("top", d3.event.pageY-25+"px");
divTooltip.style("display", "inline-block");
var x = d3.event.pageX, y = d3.event.pageY
var elements = document.querySelectorAll(':hover');
l = elements.length
l = l-1
elementData = elements[l].__data__
divTooltip.html((d.date_created)+"<br>"+elementData.name+"<br>"+elementData.value);
});
bar
.on("mouseout", function(d){
divTooltip.style("display", "none");
});
var legend = svg.selectAll(".legend")
.data(options.slice())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
svg.append("path")
//.data(obj)
.attr("class", "line")
.attr("d", line(obj));
UPDATE
What problem I am facing is I am able to render bar but not the line chart on bars.
I am trying to debug but not able to do so.
Please let me know what might I am doing wrong here.
You have some minor problems and a big problem.
The minor problems are:
Your y scale should take into account the maximum value in your dataset:
y.domain([0, d3.max(obj, function(d) {
return d.jobs_success
})]);
Your line generator should use x0. Besides that, you'll have to move the line by half rangeBand:
.x(function(d) {
return x0(d.date_created) + x0.rangeBand() / 2;
})
By default, a <path> has a black fill. Change it:
.style("fill", "none")
Those, however, are minor problems. The biggest problem lies here, in the data() method:
svg.append("path")
.data(obj)
.attr("class", "line")
.attr("d", line);
Let's see in detail what's happening here. You're passing the obj array to the data(). However, if you do this, each element of that array will be passed, individually, to the line generator.
So, supposing that this is your array...
["foo", "bar", "baz"]
...what you're passing to the line generator is just:
"foo".
You have some different solutions here. First, you can pass the array to the line generator directly, as you did in your edit. Second, you can wrap the array in an outer array:
svg.append("path")
.data([obj])
.attr("class", "line")
.attr("d", line);
That way, the whole obj array will be passed to the line generator.
Or, third, you can use datum:
svg.append("path")
.datum(obj)
.attr("class", "line")
.attr("d", line);
Here is your code with those changes and using datum to draw the path:
var obj = [{
"date_created": "2017-12-27",
"jobs_fail": 19,
"jobs_resub": 31,
"jobs_success": 50
}, {
"date_created": "2017-12-29",
"jobs_fail": 18,
"jobs_resub": 25,
"jobs_success": 44
}, {
"date_created": "2017-12-28",
"jobs_fail": 8,
"jobs_resub": 24,
"jobs_success": 44
}, {
"date_created": "2018-01-02",
"jobs_fail": 2,
"jobs_resub": 0,
"jobs_success": 0
}];
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var colorRange = d3.scale.category20();
var color = d3.scale.ordinal()
.range(colorRange.range());
var divTooltip = d3.select("body").append("div").attr("class", "toolTip");
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10, "");
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 options = d3.keys(obj[0]).filter(function(key) {
if (key != "date_created" & key != "jobs_success") {
return key
}
}); // & key != "date_created"){return key} });
var line_option = d3.keys(obj[0]).filter(function(key) {
if (key == "jobs_success" & key == "date_created") {
return key
}
});
obj.forEach(function(d) {
d.valores = options.map(function(name) {
return {
name: name,
value: +d[name]
};
});
});
x0.domain(obj.map(function(d) {
return d.date_created;
}));
x1.domain(options).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(obj, function(d) {
return d.jobs_success
})]);
var line = d3.svg.line()
.x(function(d) {
return x0(d.date_created) + x0.rangeBand() / 2;
})
.y(function(d) {
return y(d.jobs_success);
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Number of jobs");
var bar = svg.selectAll(".bar")
.data(obj)
.enter().append("g")
.attr("class", "rect")
.attr("transform", function(d) {
return "translate(" + x0(d.date_created) + ",0)";
});
bar.selectAll("rect")
.data(function(d) {
return d.valores;
})
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) {
return x1(d.name);
})
.attr("y", function(d) {
return y(d.value);
})
.attr("value", function(d) {
return d.name;
})
.attr("height", function(d) {
return height - y(d.value);
})
.style("fill", function(d) {
return color(d.name);
});
bar
.on("mousemove", function(d) {
divTooltip.style("left", d3.event.pageX + 10 + "px");
divTooltip.style("top", d3.event.pageY - 25 + "px");
divTooltip.style("display", "inline-block");
var x = d3.event.pageX,
y = d3.event.pageY
var elements = document.querySelectorAll(':hover');
l = elements.length
l = l - 1
elementData = elements[l].__data__
divTooltip.html((d.date_created) + "<br>" + elementData.name + "<br>" + elementData.value);
});
bar
.on("mouseout", function(d) {
divTooltip.style("display", "none");
});
var legend = svg.selectAll(".legend")
.data(options.slice())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});
svg.append("path")
.datum(obj)
.attr("class", "line")
.attr("d", line)
.style("fill", "none")
.style("stroke", "red")
.style("stroke-width", 2);
.axis line,
.axis path {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
Ok a couple of things. Your line's x function should be relying on x0 not x1:
var line = d3.svg.line()
.x(function(d) { return x0(d.date_created); })
.y(function(d) { return y(d.jobs_success); });
And your path needs to be called like this:
svg.append("path")
.attr("class", "line")
.attr("d", line(obj));
That should get you most of the way there - you might want to tweak the maximum y-value, and shift the x co-ordinate of the line by x0.rangeBand()/2 as well to make it line up properly with the centre of the bars.

Stacked bar graph in D3 using internal JSON variable

So trying to create a stacked bar graph in D3.js. I have got the axes working, but the graph data isn't showing, any ideas where I'm going wrong?
JS:
var svg = d3.select("#recovery__table"),
margin = {top: 20, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
aspect = width/height,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1)
.align(0.1);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(["#717C8B", "#7FDDC3", "#39B3CD"]);
var stack = d3.stack();
data.forEach(function(d) {
d.year = d['trades.closed_half_year_year'];
d.loss = d['loss'];
d.recovered = d['recovered'];
d.recovery = d['in_recovery'];
d.total = d.loss + d.recovery + d.recovered;
});
var div = d3.select("body").append("div")
.attr("class", "tooltip3")
.style("opacity", "0");
x.domain(data.map(function(d) { return d.year; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
z.domain(d3.keys(data[0]).filter(function(key){ return key == 'loss' && key == 'recovered' && key == 'in_recovery' }));
g.selectAll(".serie")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("fill", function(d){ return z(d.keys); })
.attr("x", function(d) { return x(d.year); })
.attr("width", x.bandwidth())
.attr("y", function(d) { return y(d.total); })
.attr("height", function(d) { return y[0] - y[1]; })
.on("mouseover", function(d) {
var value = parseInt($(this).attr('data-value'));
div.transition()
.duration(200)
.style("opacity", .5);
div.html(d.data.year + "<br/>£" + total.formatMoney())
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
;
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.attr('x', 20)
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(5, "s"))
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks(10).pop()))
.attr("dy", "0.35em")
.attr("text-anchor", "start")
.attr("fill", "#000");
var legend = g.selectAll(".legend")
.data(data)
.enter().append("g")
.attr('width', 100)
.attr("class", "legend")
.attr('transform', function(d, i) {
var horz = 100*i; // NEW
var vert = 0;
if (horz >= width) {
horz = 100 * (i - 3);
vert = 40;
}
return 'translate(' + horz + ',' + vert + ')'; // NEW
})
.style("font", "10px sans-serif");
legend.append("rect")
.attr("x", "33%")
.attr("width", 18)
.attr("height", 18)
.attr("fill", z);
legend.append("text")
.attr("x", "43%")
.attr("y", 9)
.attr("dy", ".35em")
.attr("text-anchor", "end")
.text(function(d) { return d; });
JSON example
[{"trades.closed_half_year_year":"2017","auctioncollectioninfos.total_advanced_amount_delinquent_and_collection_completed_gbp_daily":"£0.00","auctioncollectioninfos.total_crystallized_loss_gbp_daily":"£0.00","auctioncollectioninfos.total_outstanding_amount_delinquent_gbp_daily":"£","auctioncollectioninfos.total_advanced_amount_delinquent_gbp_daily":"£0.00","loss":"£0.00","recovered":"£0.00","in_recovery":"£0"},
{"trades.closed_half_year_year":"2016","auctioncollectioninfos.total_advanced_amount_delinquent_and_collection_completed_gbp_daily":"£123,456.78","auctioncollectioninfos.total_crystallized_loss_gbp_daily":"£0.00","auctioncollectioninfos.total_outstanding_amount_delinquent_gbp_daily":"£1,234,234","auctioncollectioninfos.total_advanced_amount_delinquent_gbp_daily":"£1,321,245.56","loss":"£0.00","recovered":"£457,468.31","in_recovery":"£1,890,567"},
{"trades.closed_half_year_year":"2015","auctioncollectioninfos.total_advanced_amount_delinquent_and_collection_completed_gbp_daily":"£3,345,768.54","auctioncollectioninfos.total_crystallized_loss_gbp_daily":"£555,555.08","auctioncollectioninfos.total_outstanding_amount_delinquent_gbp_daily":"£321,321","auctioncollectioninfos.total_advanced_amount_delinquent_gbp_daily":"£3,321,321.32","loss":"£456,324.33","recovered":"£2,324,234.345","in_recovery":"£333,333"}]
Essentially, need the loss, recovery and recovered to stack on the graph, but there is no data loading onto the graph as previously mentioned.
Any ideas?
There is a little issue, the data you are using is a JSON thus the object will receive the values as strings, you have to parse them correctly into numbers. An easy way to parse a string to a number is the following:
d.loss = +d['loss'];
But even if we did that we would still have problems with your data. Why? Because some of the numbers in your dataset are formatted:
"loss":"£456,324.33"
so if you are trying to do something like this:
d.total = d.loss + d.in_recovery + d.recovered;
You will get an invalid value because we may be issuing an operation like the following:
d.total = "£456,324.33" + 0 + "£4,324.33" // "£456,324.330£4,324.33"
This will screw the scales in our chart.
y.domain([0, d3.max(data, function(d) {
return d.total;
})]).nice(); // spooky domain here :S
Lets take care of the formatting of your values (assuming values are always formatted the way presented in the JSON you provided):
data.forEach(function(d) {
d.year = +d['trades.closed_half_year_year'];
d.loss = typeof d.loss === 'number' ? d.loss : +d['loss'].replace(/£|,/g, '')
d.recovered = typeof d.recovered === 'number' ? d.recovered : +d['recovered'].replace(/£|,/g, '');
d.in_recovery = typeof d.in_recovery === 'number' ? d.in_recovery : +d['in_recovery'].replace(/£|,/g, '');
d.total = d.loss + d.in_recovery + d.recovered;
});
Now that we have a correct dataset we should be ready to start using d3 and the stack layout:
var keys = ['loss', 'recovered', 'in_recovery']; // Declare the keys we will want in our stack
z.domain(keys); // Set them as our z domain so we can retrieve our fill color
var stackLayout = d3.stack().keys(keys)(data); // Create our stack layout
Which will create the following structure:
[
[
[
0,
0
],
[
0,
0
],
[
0,
456324.33
]
// key: loss
],
[
[
0,
0
],
[
0,
457468.31
],
[
456324.33,
2780558.6750000003
]
// key: recovered
],
[
[
0,
0
],
[
457468.31,
2348035.31
],
[
2780558.6750000003,
3113891.6750000003
]
// key: in_recovery
]
]
With the structure above we now can create our bars by key-block, as you can see each array has three values and a key. We will need to create a group element for each array element:
g.selectAll(".serie")
.data(stackLayout) // Set stack layout as data
.enter()
.append("g") // Creating group for each key
.attr("fill", function(d) { return z(d.key); }) // Fill inner elements with the color provided by our z Scale
.selectAll("rect")
.data(function(d) { // Use the inner array to create our rects
return d;
})
.enter().append("rect")
.attr("x", function(d) { // Position by our x Scale
return x(d.data.year);
})
.attr("y", function(d) { // Position by our y Scale
return y(d[1]);
})
.attr("height", function(d) { // Find the height value by using the values provided in the inner arrays
return y(d[0]) - y(d[1]);
})
.attr("width", x.bandwidth());
We also have to change a little the labels:
var legend = g.selectAll(".legend")
.data(keys.reverse()) // Use our keys
.enter().append("g")
.attr("class", "legend")
.attr('transform', function(d, i) {
var horz = width - margin.right - (100 * i); // NEW
var vert = 0;
return 'translate(' + horz + ',' + vert + ')'; // NEW
})
.style("font", "10px sans-serif");
legend.append("text")
.attr("x", "-5")
.attr("y", 9)
.attr("dy", ".35em")
.attr("text-anchor", "end")
.text(function(d) {
return d;
});
Working plnkr: https://plnkr.co/edit/eTKsOz8jlaqm1Mf3Esej?p=preview

vertical stacked bar chart using D3.js from Json

i want a vertical stacked bar chart with x axis and y axis labels and tool tip showing values and legends. please show me how to reduce the size of the graph also....
Now bar stacked bar chart is showing with very big size But no x and y axis... amd tool tip and legends is also not coming...
var w = 700,
h = 500;
var margin = {top: 25, right: 75, bottom: 85, left: 85};
// create canvas
var svg = d3.select("#bar-chart").append("svg:svg")
.attr("class", "chart")
.attr("width", w)
.attr("height", h )
.append("svg:g")
.attr("transform", "translate(10,470)");
x = d3.scale.ordinal().rangeRoundBands([0, w-50]);
y = d3.scale.linear().range([0, h-50]);
z = d3.scale.ordinal().range(["yellow", "grey", "orange"]);
var colors = [["Cat 980", "#D0D0D0"],
["Kom500", "#4DAF4A"],
["Kom501", "#D0D0D0"],
["Kom502", "#D0D0D0"]];
console.log("RAW MATRIX---------------------------");
// 4 columns: ID,c1,c2,c3
var matrix = [
[ 1, 5871, 8916, 2868],
[ 2, 10048, 2060, 6171],
[ 3, 16145, 8090, 8045],
[ 4, 990, 940, 6907]
];
console.log(matrix);
console.log("REMAP---------------------------");
var remapped =["c1","c2","c3"].map(function(dat,i){
return matrix.map(function(d,ii){
return {x: ii, y: d[i+1] };
});
});
console.log(remapped);
console.log("LAYOUT---------------------------");
var stacked = d3.layout.stack()(remapped);
console.log(stacked);
var xScale = d3.scale.ordinal().domain(d3.range(stacked.length)).rangeRoundBands([0, w], 0.05);
// ternary operator to determine if global or local has a larger scale
var yScale = d3.scale.linear().domain([0, d3.max(remapped)]).range([h, 0]);
var xAxis = d3.svg.axis().scale(xScale).tickFormat(function(d) { return stacked[d].keyword; }).orient("bottom");
var yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(5);
x.domain(stacked[0].map(function(d) { return d.x; }));
y.domain([0, d3.max(stacked[stacked.length - 1], function(d) { return d.y0 + d.y; })]);
// show the domains of the scales
console.log("x.domain(): " + x.domain())
console.log("y.domain(): " + y.domain())
console.log("------------------------------------------------------------------");
// Add a group for each column.
var valgroup = svg.selectAll("g.valgroup")
.data(stacked)
.enter().append("svg:g")
.attr("class", "valgroup")
.style("fill", function(d, i) { return z(i); })
.style("stroke", function(d, i) { return d3.rgb(z(i)).darker(); });
// xAxis
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + (h) + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function(d) {
return "rotate(-25)";
})
;
// yAxis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0 ,0)")
.call(yAxis)
;
// xAxis label
svg.append("text")
.attr("transform", "translate(" + (w / 2) + " ," + (h + margin.bottom - 5) +")")
.style("text-anchor", "middle")
.text("Keyword");
//yAxis label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (h / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Product");
// Add a rect for each date.
var rect = valgroup.selectAll("rect")
.data(function(d){return d;})
.enter().append("svg:rect")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return -y(d.y0) - y(d.y); })
.attr("height", function(d) { return y(d.y); })
.attr("width", x.rangeBand());
// add legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("transform", "translate(70,10)")
;
var legendRect = legend.selectAll('rect').data(colors);
legendRect.enter()
.append("rect")
.attr("x", w - 65)
.attr("y", 0) // use this to flip horizontal
.attr("width", 10)
.attr("height", 10)
.attr("y", function(d, i) {
return i * 20;
})
// .attr("x", function(d, i){return w - 65 - i * 70}) // use this to flip horizontal
.style("fill", function(d) {
return d[1];
});
var legendText = legend.selectAll('text').data(colors);
legendText.enter()
.append("text")
.attr("x", w - 52)
.attr("y", function(d, i) {
return i * 20 + 9;
})
.text(function(d) {
return d[0];
});
svg.select("g.x").call(xAxis);
svg.select("g.y").call(yAxis);

Categories

Resources