How to update nested child nodes using D3 v4's update pattern - d3.js

I'm having trouble getting the certain descendants to update their data when using the D3 v4 update pattern.
What is the proper way to get changes in a data-join to propagate to children?
Here is an example
https://bl.ocks.org/yanofsky/5b30b12582b6b66bc262b165806a6dc5
When you click the buttons, only the text changes rather than both the text and the colors of the rects.

You should follow the pattern of adding data for every element you add. In this way you always have a update, enter and exit selections. After removing the exit and adding new elements for enter, you can merge update and enter and update the attributes:
Here is the updated block
//define some data
var data = [
{"location": 1, "month": "Jan", "year": 2017, "value": "#ccc"},
{"location": 2, "month": "Jan", "year": 2017, "value": "#999"},
{"location": 3, "month": "Jan", "year": 2017, "value": "#666"},
{"location": 4, "month": "Jan", "year": 2017, "value": "#333"},
{"location": 1, "month": "Jan", "year": 2018, "value": "#fcc"},
{"location": 2, "month": "Jan", "year": 2018, "value": "#f99"},
{"location": 3, "month": "Jan", "year": 2018, "value": "#f66"},
{"location": 4, "month": "Jan", "year": 2018, "value": "#f33"},
{"location": 1, "month": "Feb", "year": 2017, "value": "#cfc"},
{"location": 2, "month": "Feb", "year": 2017, "value": "#9f9"},
{"location": 3, "month": "Feb", "year": 2017, "value": "#6f6"},
{"location": 4, "month": "Feb", "year": 2017, "value": "#3f3"},
{"location": 1, "month": "Feb", "year": 2018, "value": "#ccf"},
{"location": 2, "month": "Feb", "year": 2018, "value": "#99f"},
{"location": 3, "month": "Feb", "year": 2018, "value": "#66f"},
{"location": 4, "month": "Feb", "year": 2018, "value": "#33f"},
{"location": 5, "month": "Feb", "year": 2018, "value": "#0505ff"},
]
// nest the data by month then year
var by_month = d3.nest()
.key(function(d){return d.month})
.key(function(d){return d.year})
.entries(data)
function render(ident, key_month) {
var w = 500
var h = 100
var container = d3.select(ident)
// select and add a container div for each year in the data
// using only the data for the target month
var year = container
.selectAll("div.year")
var data = by_month.filter(function(d){return d.key == key_month})[0].values;
var yearAll= year.data(data);
yearAll.exit().remove();
var yearEnter= yearAll
.enter()
.append("div")
.classed("year",true);
//Add h2 and svg
yearEnter.append("h2");
yearEnter.append("svg")
.append("g")
.classed("gwrapper",true);
yearEnter = yearEnter.merge(yearAll);
yearEnter.select("h2")
.text(function(d) {return d.values[0].month + " " + d.values[0].year ;});
// update the svg dimensions
yearEnter.select("svg")
.attr("width", w)
.attr("height", h);
// select and add element g location
var gloc = yearEnter.select(".gwrapper").selectAll("g")
.data(function(d){return d.values;});
gloc.exit().remove();
var glocEnter = gloc.enter()
.append("g")
.classed("loc",true);
glocEnter.append("rect");
;
glocEnter = glocEnter.merge(gloc);
// merge and position element wrappers
glocEnter
.attr("transform", function(d,i){return "translate("+[w/5*i]+")"});
var t = d3.transition().duration(1000);
glocEnter.select("rect")
.attr("x", (w/5 - 5)/2)
.attr("y", (w/5 - 5)/2)
.attr("width", 0)
.attr("height", 0)
.attr("fill", "black")
.transition(t)
.attr("x", 5)
.attr("y", 0)
.attr("width", w/5 - 5)
.attr("height", w/5 - 5)
.attr("fill", function(d){return d.value});
}
// on button click change the subset of data being used
d3.selectAll(".month").on("click", function(){
render("#toggle",this.getAttribute("data-month"))
})
d3.selectAll("#clearb").on("click", function(){
d3.selectAll("g.loc").remove();
})
render("#toggle","Jan")

Related

Laravel collection sum of columns group by month

I'm working on Laravel project where I need to fetch some data, sum the columns and group the results in M-y (Jan-19) format to show in a monthly bar graph chart.
I was able to successfully get the data sum'ed and grouped but the problem is, for the months without any records, I still want to show up in the graph with a total equals to 0.
This is my current approach:
$data = $this->user->income()
->whereBetween('game_date', ['2019-01-01', '2019-04-30'])
->selectRaw('sum(total_score) as score,
sum(total_stars) as stars,
MONTH(game_date) as month,
DATE_FORMAT(game_date,"%b") as month_name
')
->groupBy('month', 'month_name')
->get();
This gives me the following result (missing months without any records):
[
{
"score": "707",
"stars": "64",
"month": 1,
"month_name": "Jan"
},
{
"score": "200",
"stars": "29",
"month": 3,
"month_name": "Mar"
}
]
And the expected result is including missing months to making visually clear chart as:
[
{
"score": "707",
"stars": "64",
"month": 1,
"month_name": "Jan"
},
{
"score": "0",
"stars": "0",
"month": 2,
"month_name": "Feb"
},
{
"score": "200",
"stars": "29",
"month": 3,
"month_name": "Mar"
},
]
Please note that the ->whereBetween('game_date', []) will aways have dates from start of month and end of month (covering full monthly records).
you have to use COALESCE() function. It's because at the month of Feb you get null at sum(total_score) as score instead of 0. look at
here and here
hop it help

tickformat for d3.js time scale

I am using a d3.js time.scale like this:
var xScale = d3.time.scale()
.domain(d3.extent(data, function(d) { return d.date; }))
.range([0, chartWidth]);
The data is like this:
var testData = [
{
"date": "2015-04-01T00:00:00.000Z",
"value": 200.00
},
{
"date": "2015-05-01T00:00:00.000Z",
"value": 2000.00
},
{
"date": "2015-06-01T00:00:00.000Z",
"value": 4000.01
},
{
"date": "2015-07-01T00:00:00.000Z",
"value": 1000.00
},
{
"date": "2015-08-01T00:00:00.000Z",
"value": 750.00
},
{
"date": "2015-09-01T00:00:00.000Z",
"value": 1568.00
},
{
"date": "2015-10-01T00:00:00.000Z",
"value": 3789.00
},
{
"date": "2015-11-01T00:00:00.000Z",
"value": 5678.00
},
{
"date": "2015-12-01T00:00:00.000Z",
"value": 4898.00
},
{
"date": "2016-01-01T00:00:00.000Z",
"value": 9002.00
},
{
"date": "2016-02-01T00:00:00.000Z",
"value": 3320.00
},
{
"date": "2016-03-01T00:00:00.000Z",
"value": 12000.00
}
];
But the ticks and the ordering are not as I would expect:
I would expect the ticks to be ordered from april 2015 to march 2016 and I do not understand where the 2016 tick is coming from.
How can I order the ticks as I would expect and also where is the 2016 tick coming from?
Here is a jsbin that shows the problem
If you want the ticks to show the month name and the year, add this to the axis:
.tickFormat(d3.time.format("%B %Y"));
Alternatively, you can display the abbreviated month name:
.tickFormat(d3.time.format("%b %Y"));
Here is the updated jsbin: http://jsbin.com/wikafuluqu/1/edit?js,output
PS: To show the first tick, add nice() to the scale. This is the jsbin with nice():
http://jsbin.com/loyupelazu/1/edit?js,output

How I can draw several line graph in one single graph using amcharts

I am drawing a intractive graph using amcharts. I want to draw several line graphs in one single graph. But my code draw only one graph. When I add code for second line graph , it did not show anything due to error. How I can add second line graph in it. Here is .js file.
var chart = AmCharts.makeChart("chartdiv", {
"type": "serial",
"theme": "light",
"marginTop":0,
"marginRight": 80,
"dataProvider": [{
"D": "100",
"value": 10
}, {
"D": "200",
"value": 20
}, {
"D": "200",
"value": 30
}, {
"D": "400",
"value": 40
}, {
"D": "500",
"value": 50
}],
"graphs": [{
"id":"g1",
"balloonText": "[[category]]<br><b><span style='font-size:14px;'>[[value]]</span></b>",
"bullet": "round",
"bulletSize": 8,
"lineColor": "#d1655d",
"lineThickness": 2,
"negativeLineColor": "#637bb6",
"type": "smoothedLine",
"valueField": "value"
}],
"chartCursor": { /* required for zoom effect */
"cursorAlpha": 0,
"valueLineEnabled":true,
"valueLineBalloonEnabled":true,
"valueLineAlpha":0.5,
"fullWidth":true
},
/*show x axis values on graph*/
"categoryField": "D",
});
chart.addListener("rendered", zoomChart);
if(chart.zoomChart){
chart.zoomChart();
}
function zoomChart(){
chart.zoomToIndexes(Math.round(chart.dataProvider.length * 0.4), Math.round(chart.dataProvider.length * 0.55));
}
I found a solution. If you want to plot more linegraphs in one single graph. Just add extra id' in "graph": and add extra valueField inside that id. And add needed points in "dataProvider":. Similarly you can do this for more than 2 graphs by adding ids inside "graph":. And save it with .js extension.
var chart = AmCharts.makeChart("chartdiv", {
"type": "serial",
"theme": "light",
"marginTop":0,
"marginRight": 80,
"dataProvider": [{
"D": "5",
"value":0.30,
"value1":0.5,
}, {
"D": "10",
"value": 0.29,
"value1":0.27,
}, {
"D": "15",
"value": 0.28,
"value1":0.20,
}, {
"D": "20",
"value": 0.27,
"value1":0.32,
}, {
"D": "25",
"value": 0.26,
"value1":0.25,
}],
"graphs": [{
"id":"g1",
"balloonText": "[[category]]<br><b><span style='font-size:14px;'>[[value]]</span></b>",
"bullet": "round",
"bulletSize": 8,
"lineColor": "#d1655d",
"lineThickness": 2,
"negativeLineColor": "#637bb6",
"type": "smoothedLine",
"title": "redpoint",
"valueField": "value"
}, {
"id":"g2",
"balloonText": "[[category]]<br><b><span style='font-size:14px;> [value]]</span></b>",
"bullet": "round",
"bulletSize": 8,
"lineColor": "#20acd4",
"lineThickness": 2,
"negativeLineColor": "#637bb6",
"type": "smoothedLine",
"title": "bluepoint",
"valueField": "value1"
}],
"chartCursor": { /* required for zoom effect */
"cursorAlpha": 0,
"valueLineEnabled":true,
"valueLineBalloonEnabled":true,
"valueLineAlpha":0.5,
"fullWidth":true
},
/*legend show points value on top*/
"legend": {
"useGraphSettings": true,
"position": "top"
},
/*show x axis values on graph*/
"categoryField": "D",
});
chart.addListener("rendered", zoomChart);/*zoom effect*/
if(chart.zoomChart){
chart.zoomChart();
}
function zoomChart(){
chart.zoomToIndexes(Math.round(chart.dataProvider.length * 0.4), Math.round(chart.dataProvider.length * 0.55));
}

How to use nest and rollup functions in D3 to create a bar chart

I'm new to D3 and struggling understand the nest function. I'm trying to get it working to produce a bar chart that will have three different bars for the years '2013', '2014' and '2015' and the length of each bar to be the number of entries for that year.
I've looked at lots of examples, but I'm missing some of the steps. I'd like to know how to view / troubleshoot what nest does to my data (listing keys and values etc.) And I'd also like to know how to utilise the data in a simple bar chart (what data do I call and what do I return for 'height' etc.)
I have made a fiddle here http://jsfiddle.net/hellococomo/zz81pwkm/4/ but can't figure out why I can't get any height on the bars. I'd really appreciate any help.
This is my example code:
var fruit = [
{"name": "Apple", "year": 2013, "win": "1"},
{"name": "Banana", "year": 2013, "win": "1"},
{"name": "Orange", "year": 2013, "win": "1"},
{"name": "Grapefruit", "year": 2013, "win": "0"},
{"name": "Grape", "year": 2014, "win": "0"},
{"name": "Pineapple", "year": 2014, "win": "1"},
{"name": "Melon", "year": 2014, "win": "1"},
{"name": "Grape", "year": 2014, "win": "1"},
{"name": "Pineapple", "year": 2014, "win": "1"},
{"name": "Pineapple", "year": 2015, "win": "1"},
];
data = d3.nest()
.key(function(d){ return d.year }) // `GROUP BY date`
.rollup(function(values){
// `values` is all the rows of a particular date
var counts = {}, keys = ['name', 'win']
keys.forEach(function(key){
counts[key] = d3.sum(values, function(d){ return d[key] })
})
return counts
})
.entries(fruit)
var svg =
d3.select('svg g')
.selectAll('rect')
.data(data)
.enter();
svg.append('rect')
.attr('x', function(d, i) {return i * 60 })
.attr('y', 50)
.attr('width', 50)
.attr('height', function(d) { return d['win'] })
.style('fill', 'steelblue');
Check this out: http://jsfiddle.net/yojm65nk/7/
.rollup(function(values){
// `values` is all the rows of a particular date
var counts = {}; var keys = ['name', 'win']
values.forEach(function(d){
for (var i = 0; i<keys.length; i++) {
counts[keys[i]] = d3.sum(values, function(d){ return d[keys[i]] })
}
Basically in your nest function, especially in your roll up you need to iterate through the data to expose your data variables. Once you have that you can draw you bar graph accordingly.
If you log the value of each of the Objects in your data (for example, using the Console in Chrome), you'll see that the Objects do not have a "win" property.
{"key":"2013","values":{"name":0,"win":3}}
As a result, the height of the <rect>s is not being set correctly (all of them are being set to null).
However, you can also see that you can access the "win" property of your data through the "values" Object:
.attr('height', function(d) { return d.values.win }) // <---- access .values first!
which will set the "height" according to the "wins" value in fruit.

How to select individual rows in a D3 table

Currently learning D3 and I'm a bit confused with tables. I was able to find this code in the internet which creates a simple HTML table:
function tabulate(data, columns) {
var table = d3.select("#container").append("table"),
thead = table.append("thead"),
tbody = table.append("tbody");
// append the header row
thead.append("tr")
.selectAll("th")
.data(columns)
.enter()
.append("th")
.text(function(column) { return column; });
// create a row for each object in the data
var rows = tbody.selectAll("tr")
.data(data)
.enter()
.append("tr");
// create a cell in each row for each column
var cells = rows.selectAll("td")
.data(function(row) {
return columns.map(function(column) {
return {column: column, value: row[column]};
});
})
.enter()
.append("td")
.text(function(d) { return d.value; });
return table;
}
And I have this as my JSON file:
[
{"name": "John", "age": 11},
{"name": "Jane", "age": 20},
{"name": "Robert", "age": 35},
{"name": "Meg", "age": 56},
{"name": "Ryan", "age": 80},
{"name": "Emma", "age": 11},
{"name": "Dale", "age": 20},
{"name": "Sam", "age": 35},
{"name": "Belle", "age": 56},
{"name": "Snow", "age": 80}
]
It's going to create a simple two-column table. Now my question is, how can I select individual rows? if I use selectAll ("tbody tr"), it will of course select all the rows. But if I use select("tbody tr"), it will only select the first one.
Please keep in mind that I'm a complete newbie at this. I learn mostly through reading and trial and error.

Resources