How to sort transitioning bar charts? - javascript
I have a project where i am trying to use transitions to display data for years 1800-1805
I have created a bar chart and could get the transition, but the problem here is that i am trying to sort the bar chart so that the data will be displayed in descending order. how ever when i sort the data and do the transition instead of changing the "y" attribute values of rectangles my code is replacing the existing rectangles to the sorted ones for every single transition.
I want my code in such a way that the rectangles should move its position to its new position from current one .
How could i do that.
My data is as below
year 1800
China - 20000
USA - 80000
France - 15000
year 1801
China - 25000
USA -90000
France - 35000
now for this data my code is replacing France and china data it is not moving up and down. what should i add in my code to do that?
main.js
-------------
/*
* main.js
* Mastering Data Visualization with D3.js
* 2.5 - Activity: Adding SVGs to the screen
*/
var margin = { left:80, right:20, top:50, bottom:100 };
var width = 600 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var g = d3.select("#chart-area")
.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 time = 0;
/*var xLabel = g.append("text")
.attr("y", height + 200)
.attr("x", width / 2)
.attr("font-size", "20px")
.attr("text-anchor", "middle")
.text("GDP Per Capita ($)");
var yLabel = g.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -40)
.attr("x", -170)
.attr("font-size", "20px")
.attr("text-anchor", "middle")
.text("Life Expectancy (Years)")*/
var timeLabel = g.append("text")
.attr("y", height +100)
.attr("x", width + 100)
.attr("font-size", "40px")
.attr("opacity", "0.4")
.attr("text-anchor", "middle")
.text("1800");
var data = d3.json("buildings.json").then(function(data){
// console.log(data);
// Clean data
data.forEach(function(d) {
d.Year = +d.Year;
d.Budget=+d.Budget;
});
const formattedData = data.map(function(year){
return year["countries"].filter(function(country){
var dataExists = (country.budget);
return dataExists
})
});
// Run the code every 0.1 second
d3.interval(function(){
// At the end of our data, loop back
time = (time < 5) ? time+1 : 0
update(formattedData[time]);
}, 10000);
// First run of the visualization
update(formattedData[0]);
})
function update(data) {
// Standard transition time for the visualization
var t = d3.transition()
.duration(10000);
// JOIN new data with old elements.
var rects = g.selectAll("rect").data(data, function(d){
return d;
});
// EXIT old elements not present in new data.
//rects.exit()
//.attr("class", "exit")
//.remove();
// ENTER new elements present in new data.
rects.enter()
.append("rect")
.attr("class", "enter")
//.attr("fill", function(d) { return continentColor(d.continent); })
// .merge(rects)
.attr("x", 0)
.attr("y", function(d,i){
return i*20;
}).transition(t)
.attr("width", function(d,i){
return d.budget/100;
console.log(d.budget);
})
.attr("height", 18)
.attr("fill",function(d,i){
if(d.country=="Italy")
{
return "green";
}else if(d.country=="Australia"){
return "blue";
}
});
// Update the time label
timeLabel.text(+(time + 1800))
}
Your problem is just the key function, which is fundamental for achieving object constancy.
Right now you have this:
var rects = g.selectAll("rect").data(data, function(d){
return d;
});
Which returns d, that is, the whole datum. Given your data structure, the datum is an object.
However, the API is clear about the value returned by the key function:
If a key function is not specified, then the first datum in data is assigned to the first selected element, the second datum to the second selected element, and so on. A key function may be specified to control which datum is assigned to which element, replacing the default join-by-index, by computing a string identifier for each datum and element. (emphasis mine)
Therefore, change the key function to:
var rects = g.selectAll("rect").data(data, function(d){
return d.country;
//string---^
});
Related
apply sum function to parse sum of a column from csv into d3.js-chart
I have the following dataset in csv format: Month,S40201,S40202,S40203 JAN,79,0,70 FEB,58,26,70 MAR,48,47,46 APR,64,98,77 MAY,79,71,64 JUN,86,103,116 JUL,95,75,95 AUG,0,40,3,5 SEP,60,82,79 OCT,98,101,79 NOV,60,81,75 DEC,7,30,46 The D3.js bar chart should display the sum of each column "S40201", "S40202", "S40203" as bar with the corresponding label on the X-axis. Label should be the column name (first row). <script> // Set the margins var margin = {top: 60, right: 100, bottom: 20, left: 80}, width = 850 - margin.left - margin.right, height = 370 - margin.top - margin.bottom; // Parse the month variable var parseMonth = d3.timeParse("%b"); var formatMonth = d3.timeFormat("%b"); // Set the ranges var x = d3.scaleBand().rangeRound([0, width]).padding(0.1) var y = d3.scaleLinear().range([height, 0]); // Create the svg canvas in the "graph" div var svg = d3.select("#graph") .append("svg") .style("width", width + margin.left + margin.right + "px") .style("height", height + margin.top + margin.bottom + "px") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform","translate(" + margin.left + "," + margin.top + ")") .attr("class", "svg"); // Import the CSV data d3.csv("data.csv", function(error, data) { if (error) throw error; // Format the data data.forEach(function(d) { d.Month = parseMonth(d.Month); d.S40201 = +d.S40201; d.S40202 = +d.S40202; d.S40203 = +d.S40203; }); var nest = d3.nest() .key(function(d){ return d.S40201,d.S40202,d.S40203; }) .sortKeys(d3.ascending) .rollup(function(leaves){ return d3.sum(leaves, function(d) {return (d.S40201,d.S40203,d.S40203)}); }) .entries(data) console.log(nest) // Scale the range of the data x.domain(nest.map(function(d) { return d.key; })); y.domain([0, d3.max(nest, function(d) { return d.value; })]); // Set up the x axis var xaxis = svg.append("g") .attr("transform", "translate(0," + height + ")") .attr("class", "x axis") .call(d3.axisBottom(x) //.ticks(d3.timeMonth) .tickSize(0, 0) //.tickFormat(d3.timeFormat("%B")) .tickSizeInner(0) .tickPadding(10)); // 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)); // yaxis.select(".domain").style("display","none") // Add a label to the y axis svg.append("text") .attr("transform", "rotate(-90)") .attr("y", 0 - 60) .attr("x", 0 - (height / 2)) .attr("dy", "1em") .style("text-anchor", "middle") .text("Annual Sales") .attr("class", "y axis label"); // Draw the bars svg.selectAll(".rect") .data(nest) .enter() .append("rect") .attr("class", "bar") .attr("x", function(d) { return x(d.key); }) .attr("y", function(d) { return y(d.value); }) .attr("width", x.bandwidth()) .attr("height", function(d) { return height - y(d.value); }); }) </script> With just one column it works fine, but If I want to add more than one column it doesn´t work correctly.
Welcome to StackOverflow. The problem you face is your nested data is not the way you like. If you console log your nested data the way you have it, the key and the value are both the same and not the headers. Instead if you manually summarize the data the way you like it would be easier. For example: var nest = [];//create empty array var keys = Object.keys(data[0]); //get the headers for your data //for each header push the sum as an object keys.forEach(function (d, i) { if (i == 0) return;//ignore first column as that is month //get the sumfrom your data for all the values of this key i.e. d var sum = d3.sum (data, function(e){ return e[d] }); //create an object with this key value pair var obj = { key: d, //column name value: sum //sum for the column } nest.push(obj); //push this as an object in the nest array }) Here is a block with the correct code showing the headers as the labels on the x-axis and the sum values. https://bl.ocks.org/akulmehta/724d63f0108304ede84a14fc145aad28 Please feel free to comment if you need more explanation /guidance and please remember to mark it as an answer if this answers your question. Update 1- For selected columns only Based on the comments below, if you want selected columns only simply replace the var keys = Object.keys(data[0]); with an array of headers like var keys = ['S40201','S40202','S40203'] and also remove the line if (i == 0) return;//ignore first column as that is month as we do not have the month's column anymore. Final code for this part should look like this: var nest = [];//create empty array var keys = ['S40201','S40202','S40203']; //the headers for your data //for each header push the sum as an object keys.forEach(function (d, i) { //get the sumfrom your data for all the values of this key i.e. d var sum = d3.sum (data, function(e){ return e[d] }); //create an object with this key value pair var obj = { key: d, //column name value: sum //sum for the column } nest.push(obj); //push this as an object in the nest array }) And here is the block to demonstrate it works. Notice the CSV file has an additional column but it the graph ignores this. https://bl.ocks.org/akulmehta/724d63f0108304ede84a14fc145aad28
Animate position of svg rect on transition
Edit: here is an example Fiddle: https://jsfiddle.net/3c9dtLyh/6/ I have a layout that I am attempting to animate to compare the arrangement on two different dates. What I would like to accomplish is a transition where items whose x,y position is different on the second date smoothly fly to the new position. I have attempted to do this using an updateData function set to trigger onclick. The layout looks like this: I do not neccesarily expect this approach to work because how would the transition know which (x,y) pairs correspond to the correct item name in the new arrangement. What am I missing about how these transitions work and how could I improve my approach? Here is the code I am using. It's a relatively simple sequence of appending and svg element, drawing the rectangles, then (failing) to update their position on click. <!DOCTYPE html> <meta charset="utf-8"> <style> body { font: 10px sans-serif; } .axis path, .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; } .dot { stroke: #000; } </style> <body> <div id = "chart"> </div> <div id = "select_params"> <input name="updateButton" type="button" value="Update" onclick="updateData()" /> </div> </body> <!-- load js libraries --> <script src="https://d3js.org/d3.v4.min.js"></script> <!-- uses v4 of d3 --> <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.min.js"></script> <!-- need to use this older version for tipsy --> <script type="text/javascript" src="jquery.tipsy.js"></script> <!-- load from locally hosted source code --> <!-- build the visualization --> <script type='text/javascript'> var item_width = 40, item_height = 60; var margin = {top: 20, right: 50, bottom: 75, left: 40}, width = 700 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var x = d3.scaleLinear() .range([0, width]); var y = d3.scaleLinear() .range([height, 0]); var color = d3.scaleOrdinal(d3.schemeCategory10); 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 + ")"); d3.csv("http://localhost:8080/udacity_test_vis_1/combined_item_pos.csv", function(data) { // cast string to numeric data.forEach(function(d) { d.x_pos = +d.x_pos; d.y_pos = +d.y_pos; d.sales = +d.sales; }); console.log(data); var x_offset = 5, y_offset = 5; x.domain(d3.extent(data, function(d) { return d.x_pos; })); // set the x domain y.domain(d3.extent(data, function(d) { return d.y_pos; })); // set the y domain svg.selectAll("g") .data(data) .enter() .append("rect") .filter(function(d){ return d.date == '1-20-2017'}) .attr("class", "dot") .attr("width", item_width) .attr("height", item_height) .attr("x", function(d) { return x(d.x_pos) + x_offset; }) // x position of dots .attr("y", function(d) { return y(d.y_pos) + y_offset; }) // y position of dots .attr("rx", 5) .attr("ry", 5) .style("fill", "#1f5fc6") // color factor variable .style("fill-opacity", 0.5); svg.selectAll("g") .data(data) .enter() .append("text") .filter(function(d){ return d.date == '1-20-2017'}) .attr("x", function(d) { return x(d.x_pos) + item_width/2 + x_offset; }) .attr("y", function(d) { return y(d.y_pos) + item_height/2 + y_offset; }) .attr("font-size", 10) .attr("text-anchor", "middle") .attr("fill", "black") .text(function(d){ return d.item_name}); }); function updateData() { // grab the data again d3.csv("http://localhost:8080/udacity_test_vis_1/combined_item_pos.csv", function(data) { // cast string to numeric data.forEach(function(d) { d.x_pos = +d.x_pos; d.y_pos = +d.y_pos; d.sales = +d.sales; }); var svg = d3.select("#chart").transition(); svg.selectAll("g") .data(data) .enter() .append("rect") .filter(function(d){ return d.date == '2-10-2017'}) .attr("class", "dot") .attr("width", item_width) .attr("height", item_height) .attr("x", function(d) { return x(d.x_pos) + x_offset; }) // x position of dots .attr("y", function(d) { return y(d.y_pos) + y_offset; }) // y position of dots .attr("rx", 5) .attr("ry", 5) .style("fill", "#1f5fc6") // color factor variable .style("fill-opacity", 0.5); svg.selectAll("g") .data(data) .enter() .append("text") .filter(function(d){ return d.date == '2-10-2017'}) .attr("x", function(d) { return x(d.x_pos) + item_width/2 + x_offset; }) .attr("y", function(d) { return y(d.y_pos) + item_height/2 + y_offset; }) .attr("font-size", 10) .attr("text-anchor", "middle") .attr("fill", "black") .text(function(d){ return d.item_name}); }); } </script> Here is my data: ,x_pos,y_pos,item_name,sales,date 0,1,1,S8221,2022,1-20-2017 1,2,1,NLC11,518,1-20-2017 2,3,1,35UUY,1614,1-20-2017 3,4,1,PPTNV,1059,1-20-2017 4,5,1,G0CWS,2183,1-20-2017 5,6,1,3JHUA,2513,1-20-2017 6,7,1,4HXGA,2251,1-20-2017 7,8,1,RYM9K,2330,1-20-2017 8,9,1,T8PUB,1476,1-20-2017 9,10,1,PLULW,1225,1-20-2017 10,1,2,YJ6S0,2403,1-20-2017 11,2,2,E9RGD,1361,1-20-2017 12,3,2,E2SW4,1131,1-20-2017 13,4,2,BZPGX,698,1-20-2017 14,5,2,0K682,1855,1-20-2017 15,6,2,D8UZW,2371,1-20-2017 16,7,2,USKY7,1851,1-20-2017 17,8,2,D0L0Y,1767,1-20-2017 18,9,2,P1AGP,1025,1-20-2017 19,10,2,9LT7O,1380,1-20-2017 20,1,3,1J184,1108,1-20-2017 21,2,3,RJDEG,2106,1-20-2017 22,3,3,LTSLR,1980,1-20-2017 23,4,3,ET3DF,2700,1-20-2017 24,5,3,42W1W,2194,1-20-2017 25,6,3,5QTJN,958,1-20-2017 26,7,3,O8XKY,2381,1-20-2017 27,8,3,LS9NW,516,1-20-2017 28,9,3,0MPZ7,2198,1-20-2017 29,10,3,R4E3J,2494,1-20-2017 30,1,4,WFPPY,2349,1-20-2017 31,2,4,MT2DB,2525,1-20-2017 32,3,4,6DRYS,600,1-20-2017 33,4,4,NVV0S,1556,1-20-2017 34,5,4,ODGZ2,912,1-20-2017 35,6,4,E3NLS,931,1-20-2017 36,7,4,9FFZ7,722,1-20-2017 37,8,4,UKZGF,2170,1-20-2017 38,9,4,XXORI,896,1-20-2017 39,10,4,QYU9Q,1104,1-20-2017 40,1,5,4KQPU,1562,1-20-2017 41,2,5,S3AYK,2298,1-20-2017 42,3,5,5W3CE,2580,1-20-2017 43,4,5,T0S7H,1677,1-20-2017 44,5,5,02SJG,1972,1-20-2017 45,6,5,GBMNZ,1845,1-20-2017 46,7,5,2Y7KH,982,1-20-2017 47,8,5,3WMOL,1952,1-20-2017 48,9,5,93KLU,2240,1-20-2017 49,10,5,K80OQ,2467,1-20-2017 50,1,6,2SIJS,1788,1-20-2017 51,2,6,5ZJ7V,2277,1-20-2017 52,3,6,HTL99,873,1-20-2017 53,4,6,C06QP,2185,1-20-2017 54,5,6,2S1YI,580,1-20-2017 55,6,6,IQ0L8,2395,1-20-2017 56,7,6,PEE2Y,2299,1-20-2017 57,8,6,6DEWK,2019,1-20-2017 58,9,6,9FY5B,1517,1-20-2017 59,10,6,NZQ54,2624,1-20-2017 60,1,7,C4SVV,1823,1-20-2017 61,2,7,Q4C4I,2339,1-20-2017 62,3,7,996OQ,1621,1-20-2017 63,4,7,PISK6,895,1-20-2017 64,5,7,KOKHE,1315,1-20-2017 65,6,7,6P4FT,1467,1-20-2017 66,7,7,3FY75,2085,1-20-2017 67,8,7,9YCNB,992,1-20-2017 68,9,7,NXXK1,2080,1-20-2017 69,10,7,4RDHV,2031,1-20-2017 0,6,1,9FFZ7,592,2-10-2017 1,1,6,E2SW4,622,2-10-2017 2,6,7,PLULW,1699,2-10-2017 3,8,3,ET3DF,784,2-10-2017 4,9,4,KOKHE,1092,2-10-2017 5,2,6,5ZJ7V,1691,2-10-2017 6,4,5,9FY5B,630,2-10-2017 7,9,4,G0CWS,1523,2-10-2017 8,9,2,PISK6,1778,2-10-2017 9,6,4,35UUY,2107,2-10-2017 10,3,5,5QTJN,1751,2-10-2017 11,6,6,NLC11,526,2-10-2017 12,8,2,C06QP,2308,2-10-2017 13,8,3,XXORI,1453,2-10-2017 14,5,1,E9RGD,1864,2-10-2017 15,7,2,HTL99,1222,2-10-2017 16,3,3,PEE2Y,2050,2-10-2017 17,9,7,GBMNZ,1941,2-10-2017 18,3,1,T8PUB,1440,2-10-2017 19,5,1,3WMOL,2692,2-10-2017 20,7,7,S3AYK,523,2-10-2017 21,1,5,BZPGX,2245,2-10-2017 22,2,1,S8221,2241,2-10-2017 23,9,7,IQ0L8,566,2-10-2017 24,8,5,D8UZW,1769,2-10-2017 25,3,1,RYM9K,1044,2-10-2017 26,4,6,4HXGA,2650,2-10-2017 27,2,2,WFPPY,2203,2-10-2017 28,2,4,93KLU,2289,2-10-2017 29,7,3,P1AGP,1084,2-10-2017 30,4,3,3JHUA,1364,2-10-2017 31,1,4,9LT7O,1198,2-10-2017 32,4,6,4RDHV,771,2-10-2017 33,10,7,T0S7H,873,2-10-2017 34,3,6,NXXK1,2391,2-10-2017 35,8,2,2SIJS,811,2-10-2017 36,8,4,LTSLR,1670,2-10-2017 37,6,7,02SJG,1880,2-10-2017 38,9,3,0MPZ7,2090,2-10-2017 39,2,6,E3NLS,2350,2-10-2017 40,7,6,QYU9Q,1092,2-10-2017 41,6,3,0K682,894,2-10-2017 42,1,5,LS9NW,1928,2-10-2017 43,7,7,NVV0S,951,2-10-2017 44,9,4,996OQ,670,2-10-2017 45,7,6,USKY7,706,2-10-2017 46,10,4,Q4C4I,2270,2-10-2017 47,4,2,UKZGF,1691,2-10-2017 48,10,3,RJDEG,597,2-10-2017 49,10,2,1J184,1921,2-10-2017 50,2,3,5W3CE,2604,2-10-2017 51,5,5,3FY75,1260,2-10-2017 52,1,1,6DEWK,2491,2-10-2017 53,7,5,9YCNB,1743,2-10-2017 54,4,7,6DRYS,2450,2-10-2017 55,5,2,MT2DB,1292,2-10-2017 56,8,5,C4SVV,1395,2-10-2017 57,3,7,ODGZ2,2685,2-10-2017 58,10,4,2S1YI,2617,2-10-2017 59,1,2,YJ6S0,1611,2-10-2017 60,6,3,2Y7KH,2188,2-10-2017 61,5,4,4KQPU,1413,2-10-2017 62,10,1,D0L0Y,2291,2-10-2017 63,5,1,NZQ54,1405,2-10-2017 64,5,2,6P4FT,1885,2-10-2017 65,3,1,PPTNV,1442,2-10-2017 66,1,5,K80OQ,2140,2-10-2017 67,4,5,42W1W,1697,2-10-2017 68,2,7,O8XKY,1007,2-10-2017 69,10,6,R4E3J,887,2-10-2017
So, I took a few minutes to completely refactor your code into proper d3 style. This aims to demonstrate a couple things: The proper use of the enter, update, exit pattern. Removed cut / paste duplicate code. The proper way to use g to group elements and position them together. How to add the transitions. Here is the code running. Commented code: <!DOCTYPE html> <meta charset="utf-8"> <style> body { font: 10px sans-serif; } .axis path, .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; } .dot { stroke: #000; } </style> <body> <div id="chart"> </div> <div id="select_params"> <input name="updateButton" type="button" value="Update" onclick="updateData()" /> </div> </body> <!-- load js libraries --> <script src="https://d3js.org/d3.v4.min.js"></script> <!-- uses v4 of d3 --> <!-- build the visualization --> <script type='text/javascript'> var item_width = 40, item_height = 60; var margin = { top: 20, right: 50, bottom: 75, left: 40 }, width = 700 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var x = d3.scaleLinear() .range([0, width]); var y = d3.scaleLinear() .range([height, 0]); var color = d3.scaleOrdinal(d3.schemeCategory10); 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 + ")"); // a single function to draw function draw(data, someDate) { data.forEach(function(d) { d.x_pos = +d.x_pos; d.y_pos = +d.y_pos; d.sales = +d.sales; }); // pre-filter data data = data.filter(function(d) { return d.date === someDate }); var x_offset = 5, y_offset = 5; x.domain(d3.extent(data, function(d) { return d.x_pos; })); // set the x domain y.domain(d3.extent(data, function(d) { return d.y_pos; })); // set the y domain // create an update selection with a key function var g_sel = svg.selectAll("g") .data(data, function(d) { return d.item_name; }); // get rid of those leaving the update g_sel.exit().remove(); // our entering g var g_ent = g_sel.enter() .append("g"); // add our rects to our g g_ent.append("rect") .attr("class", "dot") .attr("width", item_width) .attr("height", item_height) .attr("rx", 5) .attr("ry", 5) .style("fill", "#1f5fc6") // color factor variable .style("fill-opacity", 0.5); // add our text to our g g_ent.append("text") .attr("font-size", 10) .attr("text-anchor", "middle") .attr("fill", "black") .attr("dx", item_width / 2) .attr("dy", item_height / 2) .text(function(d) { return d.item_name }); // UPDATE + ENTER selection g_sel = g_ent.merge(g_sel); // move them into position with transition g_sel .transition() .attr("transform", function(d) { return "translate(" + (x(d.x_pos) + x_offset) + "," + (y(d.y_pos) + y_offset) + ")"; }); } d3.csv("test.csv", function(data) { draw(data, '1-20-2017'); }); function updateData() { d3.csv("test.csv", function(data) { draw(data, '2-10-2017'); }); } </script>
Heres my attempt: https://jsfiddle.net/guanzo/3c9dtLyh/10/ There are multiple data points that share the same position, which is why some rectangles are overlapping. I made a lot of changes to your code that resulted in less repetition. Your data contains duplicate item_names with different dates/positions, but in your visualization you seem to want to only show items at a single date. Therefore, you only need to pass d3 data for a certain date, versus passing d3 ALL the data and then filtering. Your code: svg.selectAll("g") .data(data) .enter() .append("rect") .filter(function(d){ return d.date == '1-20-2017'}) My code: var firstDateData = data.filter(d=>d.date == '1-20-2017'); var groups = svg.selectAll("g") .data(firstDateData, d=> d.item_name) The difference between these 2 is that in my example, D3 is only aware of a single set of item_names on date 1-20-2017. Therefore when i update the date with item_names on date 2-10-2017, D3 will automatically move all updated rectangles to their new position. How? Here is where your question comes into play: I do not neccesarily expect this approach to work because how would the transition know which (x,y) pairs correspond to the correct item name in the new arrangement This is because i associated each rectangle with an item_name. D3s data function can take an optional 2nd parameter that specifies HOW the data is bound to the rectangles. This is called the key function. svg.selectAll("g").data(firstDateData, d=> d.item_name) In this case, i told d3 that each group (rectangles and their text) is bound to item_name. Therefore, the next time i pass data to D3, it tries to match existing elements (that are associated with an item_name) to the data (which contains item_names). If in my new data, i pass an item_name that corresponds to an existing element, and the data contains a new x and y position, D3 will move to element to that new position. Note that even though i'm talking about rectangles, i bound data to the g element, which contains the rectangle and the text. Feel free to ask any questions, i made a lot of changes that i didn't discuss.
How do I get data to show up in my graph?
I'm trying to get my data to show up in my graph, however I get an error that says that my data is "NaN" after I converted the Year and Miles column from strings to integers. I'm guessing that it's something with my x_scale & y_scale...? <!DOCTYPE html> <html lang="en"> <head> <description> <!--charts - avg vehicle trip per mile, source: http://nhts.ornl.gov/2009/pub/stt.pdf--> </description> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> <link rel="stylesheet" href="/bootstrap-3.3.7-dist/css/bootstrap.min.css"> <script type="text/javascript" src="https://d3js.org/d3.v4.min.js"></script> </head> <body> <script type="text/javascript"> // global variables var dataset; d3.csv("avgVehicleTripLengthMiles.csv", function (error, data) { if (error) { console.log(error); } else { console.log(data); } // once loaded, data is copied to dataset because js is asynchronous dataset = data; createScatterplot(); }); /* function typeConv() { // type conversion from string to integer var typeConv = dataset.forEach(function (d) { d["Year"] = +d["Year"]; d["Miles"] = +d["Miles"]; return d; }); } */ function createScatterplot() { // TEST var typeConv = dataset.forEach(function (d) { d["Year"] = +d["Year"]; d["Miles"] = +d["Miles"]; return d; }); var title = d3.select("body") .append("h4") .text("Avg. Vehicle Trip Length per Mile"); // dimensions of canvas var padding = 30; var margin = { top: 20, right: 40, bottom: 20, left: 40 }, w = 800 - margin.left - margin.right, h = 400 - margin.top - margin.bottom; // create svg canvas var svg_canvas = d3.select("body") .append("svg") .attr("width", w + margin.left + margin.right) .attr("height", h + margin.top + margin.bottom); // create scale for axis var x_scale = d3.scaleLinear().domain([1969, 2009]).range([padding, w - padding * 2]); var y_scale = d3.scaleLinear().domain([0, 20]).range([h - padding, padding]); // r_scale created specifically for circles' radius to be mapped unto axes var r_scale = d3.scaleLinear().domain([0, d3.max(dataset, function (d) { return d[1]; })]).range([0, 20]); // define axis & ticks // .ticks(5) to x_axis and .ticks(1) to y_axis var x_axis = d3.axisBottom().scale(x_scale); var y_axis = d3.axisLeft().scale(y_scale); // create group, "g" element, to create x_axis and y_axis var x_group = svg_canvas.append("g") .attr("class", "axis") .attr("transform", "translate(0," + (h - padding) + ")") .call(x_axis); var y_group = svg_canvas.append("g") .attr("class", "axis") .attr("transform", "translate(" + padding + ",0)") .call(y_axis); // create circles svg_canvas.selectAll("circle") .data(dataset) .enter() .append("circle") .attr("cx", function (d) { return x_scale(d[0]); }) .attr("cy", function (d) { console.log(d); // TEST return y_scale(d[1]); }) .attr("cr", function (d) { return r_scale(d[1]); }); } </script> </body> </html>
EDIT (new answer): There are several issues, and I'll try to step through them one by one. In order to test, I had to make up my own data. My test CSV file looked like this (so your final answer might change slightly if your file is different) Year,Miles 2006,5.0 2007,7.2 2008,19.3 As was pointed out by #altocumulus in the comments above, your .attr() calls are referencing non-existant indexes, which might be part of the trouble. The radius attribute for circles is r, not cr I simplified the code by not calling a function for r, but rather doing a static value. You may want to play further with this. The significantly changed portion of code is svg_canvas.selectAll("circle") .data(dataset) .enter() .append("circle") .attr("cx", function (d) { return x_scale(d["Year"]); }) .attr("cy", function (d) { return y_scale(d["Miles"]); }) .attr("r", function (d) { return 5; //return r_scale(d[1]); }); You still have an issue with the x-axis acting like numbers, and not dates, making 2006 look like 2,006, for example. I haven't solved that issue here. Lastly, I feel like you're complicating your code for no reason by trying to handle margin AND padding via the D3 code, when these end up meaning similar things in the Javascript context. I suggest accomplishing most of the margin/padding via CSS, which would simplify your code. Another example of an unnecessary complication is in the previous answer, below. FIDDLE OLD (incomplete, incorrect) ANSWER: The return value of Array.forEach() is undefined, so it can't be assigned. dataset.forEach(function (d) { //d["Year"] = +d["Year"]; d["Miles"] = +d["Miles"]; // NOT NEEDED: return d; }); If you need to keep your converted array separate, use Array.map().
// global variables var dataset; d3.csv("avgVehicleTripLengthMiles.csv", function (error, data) { if (error) { console.log(error); } else { console.log(data); } // once loaded, data is copied to dataset because js is asynchronous dataset = data; createScatterplot(); }); function createScatterplot() { // TEST var typeConv = dataset.forEach(function (d) { // d["Year"] = +d["Year"]; d["Miles"] = +d["Miles"]; // return d; }); var title = d3.select("body") .append("h4") .text("Avg. Vehicle Trip Length per Mile"); // dimensions of canvas var padding = 30; var margin = { top: 20, right: 40, bottom: 20, left: 40 }, w = 800 - margin.left - margin.right, h = 400 - margin.top - margin.bottom; // create svg canvas var svg_canvas = d3.select("body") .append("svg") .attr("width", w + margin.left + margin.right) .attr("height", h + margin.top + margin.bottom); // create scale for axis var x_scale = d3.scaleLinear().domain([1965, 2009]).range([padding, w - padding * 2]); var y_scale = d3.scaleLinear().domain([0, 20]).range([h - padding, padding]); // r_scale created specifically for circles' radius to be mapped unto axes var r_scale = d3.scaleLinear().domain([0, d3.max(dataset, function (d) { return d[1]; })]).range([0, 20]); // define axis & ticks // .ticks(5) to x_axis and .ticks(1) to y_axis var x_axis = d3.axisBottom().scale(x_scale); var y_axis = d3.axisLeft().scale(y_scale); // create group, "g" element, to create x_axis and y_axis var x_group = svg_canvas.append("g") .attr("class", "axis") .attr("transform", "translate(0," + (h - padding) + ")") .call(x_axis); var y_group = svg_canvas.append("g") .attr("class", "axis") .attr("transform", "translate(" + padding + ",0)") .call(y_axis); // create & color circles svg_canvas.selectAll("circle") .data(dataset) .enter() .append("circle") .attr("cx", function (d) { return x_scale(d["Year"]); }) .attr("cy", function (d) { return y_scale(d["Miles"]); }) .attr("r", function (d) { return 5; }) .style("fill", function (d) { if (d["Trip Purpose"] === "All Purposes") { return "pink"; } else if (d["Trip Purpose"] === "To or From Work") { return "red"; } else if (d["Trip Purpose"] === "Shopping") { return "blue"; } else if (d["Trip Purpose"] === "Other Family/Personal Errands") { return "green"; } else if (d["Trip Purpose"] === "Social and Recreational") { return "gray"; }; }); // create text label for x-axis svg_canvas.append("text") .attr("x", w / 2) .attr("y", h + margin.top + 20) .style("text-anchor", "middle") .text("Year"); // create text label for y-axis svg_canvas.append("text") .attr("transform", "rotate(-90)") .attr("x", (0 - margin.left / 2)) .attr("y", (h/2)) .style("text-anchor", "middle") .text("Miles");
Adding labels to both ends of <rect> in a bar chart
I'm using D3 to present some data as a horizontal bar chart. Values will typically range between -10 and +10 on 8 different scales. I have the bars rendering as I want, but I can't work out how to add lables for each of the extreems of the axes. so far I have: but I want to achieve something like: In other words a label for each extreme of each scale. I have found lots of examples that add data labels to the bars them selves (e.g. the value), but I want to some how force the array of strings to be rendered at the extremes of the container. At the moment, I am rendering the data from an array, and I have the labels stored in 2 other arrays e.g. var data = [10, 5, -5, -10, 2, -2, 8, -8]; var leftLabels = ["label 1","label 2", ...]; var rightLabels = ["label 1", "label 2", ...]; Any ideas or links to examples most welcome.
I am not an expert in d3.js, but I think this can be easily done. There are different ways to go about it. I have created a pen for your use case. I will paste the important part of the code below. In your chart, you will have to certainly make some adjustments to suit your needs. Feel free to play around with the values until you feel they are stable. // Your array containing labels for left and right values var leftSideData = ["left1", "left2", "left3", "left4", "left5", "left6", "left7", "left8"]; var rightSideData = ["right1", "right2", "right3", "right4", "right5", "right6", "right7", "right8"]; var left = svg.selectAll(".leftData") .data(leftSideData) .enter().append("g") .attr("class", "leftVal") .attr("transform", function(d, i) { return "translate(0," + i * 57 + ")"; }); left.append("text") .attr("x", 0) .attr("y", 9) .attr("dy", ".35em") .style("text-anchor", "end") .text(function(d) { return d; }); var right = svg.selectAll(".rightData") .data(rightSideData) .enter().append("g") .attr("class", "rightVal") .attr("transform", function(d, i) { return "translate(0," + i * 57 + ")"; }); right.append("text") .attr("x", width + 30) .attr("y", 9) .attr("dy", ".35em") .style("text-anchor", "end") .text(function(d) { return d; }); I won't say this is perfect, but I hope you get an idea about how to approach it. All the best!!
It's funny, just by asking the q on SE I find it helps me reformulate the problem.. and then some time later a new try yields a result. Anyone else find that? I managed to make it work by changing the way the SVG was created. So I now have the following structure: <SVG> ><g> (one for each bar) >><text> >><rect> >><text> ><other stuff like axies> It turns out that <text> elements cannot be added to <rect> elements (well they can, be added but they won't render). the code is: var data = [10,2,4,-10,...etc...]; var leftLabels = ["left 1","left 1", ...etc...]; var rightLabels = ["right 1","right 2", ...etc...]; //chart dimentions var margin = { top: 20, right: 30, bottom: 40, left: 30 }, width = 600 - margin.left - margin.right, barHeight = 30, height = barHeight * data.length; //chart bar scaling var x = d3.scale.linear() .range([100, width-100]); var y = d3.scale.ordinal() .rangeRoundBands([0, height], 0.1); var chart = d3.select(".chartsvg") .attr("width", width + margin.left + margin.right) .attr("height", barHeight * data.length + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); x.domain([d3.min(data), d3.max(data)]); //append a g for each data item var bar = chart.selectAll(".bar") .data(data) .enter() .append("g"); //in each bar add a rect for the bar chart bar bar.append("rect") .attr("class", function (d) { return "bar--" + (d < 0 ? "negative" : "positive"); }) .attr("x", function (d) { return x(Math.min(0, d)); }) .attr("y", function (d, i) { return i* barHeight; }) .attr("width", function (d) { return Math.abs(x(d) - x(0)); }) .attr("height", barHeight-1); //append the labels to each g using the label data bar.append("text") .data(rightLabels) .attr("x", width) .attr("y", function (d, i) { return (i * barHeight)+barHeight/2; }) .attr("dy", ".5em") .attr("fill","steelblue") .attr("text-anchor","end") .text(function (d) { return d; }); bar.append("text") .data(leftLabels) .attr("x", 0) .attr("y", function (d, i) { return (i * barHeight) + barHeight / 2; }) .attr("dy", ".5em") .attr("fill","darkorange") .attr("text-anchor", "start") .text(function (d) { return d; }); //then append axis etc... Formatting: something else to note. It turns out that to color the text in the label you need to use "stroke" and "fill" attributes. These are broadly equiv to the HTML "color" attribute on text.
I am trying to visualize my json object with D3. I want date to be the x axis and y to be sales. number values stored a string
I have a json object that I am trying to visualize with D3.js. I want the x axis to represent the date in the json object which is stored as a string and the y axis to represent sales projections which is also a number in a string i.e "85,000.00" example of my json object: [{"Num":78689,"Client":"Health Services" ,"TotalEstSales":"85,000,000.00","Date ":"2/15/2015","RFP Receipt Date":null,"Exp. Proposal Due Date":"3/6/2015","Proposal Submission Date":null,"estAwardDate":"4/15/2015","Procurement Type":"New - Incumbent","Bid Type":"Standalone Contract"}] and my d3 code: // Various accessors that specify the four dimensions of data to visualize. function x(d) { return d.date; } function y(d) { return d.TotalEstSales; } function radius(d) { return parseFloat(d.TotalEstSales);} function color(d) { return d.region; } function key(d) { return d.Title;} // Chart dimensions. var margin = {top: 19.5, right: 19.5, bottom: 19.5, left: 39.5}, width = 960 - margin.right, height = 500 - margin.top - margin.bottom; // Various scales. These domains make assumptions of data, naturally. var xScale = d3.scale.log().domain([300, 1e5]).range([0, width]), yScale = d3.scale.linear().domain([10000, 85000000]).range([height, 0]), radiusScale = d3.scale.sqrt().domain([0, 5e8]).range([0, 40]), colorScale = d3.scale.category10(); // The x & y axes. var xAxis = d3.svg.axis().orient("bottom").scale(xScale).ticks(12, d3.format(",d")), yAxis = d3.svg.axis().scale(yScale).orient("left"); // Create the SVG container and set the origin. 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 + ")"); // 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); // Add an x-axis label. svg.append("text") .attr("class", "x label") .attr("text-anchor", "end") .attr("x", width) .attr("y", height - 6) .text("Data of RFP"); // Add a y-axis label. svg.append("text") .attr("class", "y label") .attr("text-anchor", "end") .attr("y", 6) .attr("dy", ".75em") .attr("transform", "rotate(-90)") .text("Award amount"); // Add the year label; the value is set on transition. var label = svg.append("text") .attr("class", "year label") .attr("text-anchor", "end") .attr("y", height - 24) .attr("x", width) .text(2015); // Load the data. d3.json("rfpdata.json", function(data) { // A bisector since many nation's data is sparsely-defined. // var bisect = d3.bisector(function(d) { return d[0]; }); // Add a dot per nation. Initialize the data at 1800, and set the colors. var dot = svg.append("g") .attr("class", "dots") .selectAll(".dot") .data(data) .enter().append("circle") .attr("class", "dot") .style("fill", function(d) { return colorScale(color(d)); }) .call(position) .sort(order); // Add a title. dot.append("title") .text(function(d) { return d.Client; }) // Positions the dots based on data. function position(dot) { dot .attr("cx", function(d) { return xScale(x(d)); }) // .attr("cy", function(d) { return yScale(y(d)); }) .attr("r", function(d) { return radiusScale(radius(d)); }); } // Defines a sort order so that the smallest dots are drawn on top. function order(a, b) { return radius(b) - radius(a); } // After the transition finishes, you can mouseover to change the year. function enableInteraction() { var yearScale = d3.scale.linear() .domain([1800, 2009]) .range([box.x + 10, box.x + box.width - 10]) .clamp(true); // Cancel the current transition, if any. function mouseover() { label.classed("active", true); } function mouseout() { label.classed("active", false); } function mousemove() { displayYear(yearScale.invert(d3.mouse(this)[0])); } } // this is the function needed to bring in data // Interpolates the dataset for the given (fractional) year. function interpolateData(date) { return data.map(function(d) { return { title: d.Title, client: d.Client, sales: parseFloat(d.TotalEstSales), sales: interpolateValues(d.TotalEstSales, date), }; }); } // Finds (and possibly interpolates) the value for the specified year. function interpolateValues(values, date) { var i = bisect.left(values, date, 0, values.length - 1), a = values[i]; if (i > 0) { var b = values[i - 1], t = (date - a[0]) / (b[0] - a[0]); return a[1] * (1 - t) + b[1] * t; } return a[1]; } }); I am not sure what I am doing wrong but the data is not displaying? Am i properly parsing the date string? This was a graph available on the d3 site. I want a bubble graph where the radius changes depending on the size of the sale and the date is on the x axis.
#all Update: I was able to make the proper adjustment for date on the xaxis here: var xAxis = d3.svg.axis().orient("bottom").scale(xScale).tickFormat(d3.time.format("%m/%d")), yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(23, d3.format(" ,d")); d3.time.format was what I was looking for. Once data was loaded I needed to parse the date: month = data.Date; parseDate = d3.time.format("%m/%d/%Y").parse; data.forEach(function(d) { d.Date = parseDate(d.Date); }); // update Dates here when new report comes in monthly xScale.domain([parseDate("1/1/2015"),parseDate("6/1/2015")]); obviously, using "Date" as a name column in the excel file was not idea for "Date" in js(because it is an oject).