I have an AJAX call which brings me back to data in JSON format.
[{"KEY":"IA","VALUE":"8"},{"KEY":"GE","VALUE":"1"}]
However, the labelling may change for this data depending on some user interaction (selecting from a drop down may invoke a search for some other data) leading to :
[{"NAME":"STEVE","AGE":"54"},{"NAME":"PETE","AGE":"22"}]
So I need some way to just get the first label and data and push it to the X axis like:
|
|
|
|
|_____________
Steve Pete
Name
and then stick the second label and data up the Y Axis.
so most of the code examples I have seen use some form of d3.name to identify the labels in the returned data but as I need it to dynamically name the axis keys/values Im not sure how I can achieve this.
Also, the JSON data I have is stored in a variable called jdata so I wouldnt use
the d3.json method.
The examples im working from is on : http://codepen.io/mrev/pen/waKvbw
JS:
var margin ={top:20, right:30, bottom:30, left:40},
width=960-margin.left - margin.right,
height=500-margin.top-margin.bottom;
// scale to ordinal because x axis is not numerical
var x = d3.scale.ordinal().rangeRoundBands([0, width], .1);
//scale to numerical value by height
var y = d3.scale.linear().range([height, 0]);
var chart = d3.select("#chart")
.append("svg") //append svg element inside #chart
.attr("width", width+(2*margin.left)+margin.right) //set width
.attr("height", height+margin.top+margin.bottom); //set height
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom"); //orient bottom because x-axis will appear below the bars
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
d3.json("http://codepen.io/superpikar/pen/kcJDf.js", function(error, data){
x.domain(data.map(function(d){ return d.letter}));
y.domain([0, d3.max(data, function(d){return d.frequency})]);
var bar = chart.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform", function(d, i){
return "translate("+x(d.letter)+", 0)";
});
bar.append("rect")
.attr("y", function(d) {
return y(d.frequency);
})
.attr("x", function(d,i){
return x.rangeBand()+(margin.left/2);
})
.attr("height", function(d) {
return height - y(d.frequency);
})
.attr("width", x.rangeBand()); //set width base on range on ordinal data
bar.append("text")
.attr("x", x.rangeBand()+margin.left )
.attr("y", function(d) { return y(d.frequency) -10; })
.attr("dy", ".75em")
.text(function(d) { return d.frequency; });
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate("+margin.left+","+ height+")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.attr("transform", "translate("+margin.left+",0)")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
});
function type(d) {
d.letter = +d.letter; // coerce to number
return d;
}
HTML :
<div id="chart"></div>
EDITED CODE
var jsplit = jdata.split('"');
var keyX = jsplit[1];
var keyY = "";
var data = JSON.parse(jdata);
data[0].keys().forEach(function(k) {
if (k!=keyX) keyY=k;
});
var margin ={top:20, right:30, bottom:30, left:40},
width=960-margin.left - margin.right,
height=500-margin.top-margin.bottom;
// scale to ordinal because x axis is not numerical
var x = d3.scale.ordinal().rangeRoundBands([0, width], .1);
//scale to numerical value by height
var y = d3.scale.linear().range([height, 0]);
var chart = d3.select("#chart")
.append("svg") //append svg element inside #chart
.attr("width", width+(2*margin.left)+margin.right) //set width
.attr("height", height+margin.top+margin.bottom); //set height
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom"); //orient bottom because x-axis will appear below the bars
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
x.domain(data.map(function(d){ return d[keyX]}));
y.domain([0, d3.max(data, function(d){return d[keyY]})]);
var bar = chart.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform", function(d, i){
return "translate("+x(d[keyX])+", 0)";
});
bar.append("rect")
.attr("y", function(d) {
return y(d[keyY]);
})
.attr("x", function(d,i){
return x.rangeBand()+(margin.left/2);
})
.attr("height", function(d) {
return height - y(d[keyY]);
})
.attr("width", x.rangeBand()); //set width base on range on ordinal data
bar.append("text")
.attr("x", x.rangeBand()+margin.left )
.attr("y", function(d) { return y(d[keyY]) -10; })
.attr("dy", ".75em")
.text(function(d) { return d[keyY]; });
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate("+margin.left+","+ height+")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.attr("transform", "translate("+margin.left+",0)")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
function type(d) {
d[keyX] = +d[keyX]; // coerce to number
return d;
}
Quick & dirty trick to find the names of the first and second keys: simply split the json text data around ":
var jsplit = jdata.split('"');
var keyX = jsplit[1];
var keyY = jsplit[5];
this is assuming your data format doesn't change, and that the " character does not appear within the values
Edit: taking comments into account:
var jsplit = jdata.split('"');
var keyX = jsplit[1];
var keyY = "";
var data = JSON.parse(jdata);
for (k in data[0]) {
if (k!=keyX) keyY=k;
}
Note that all this code, as well as the rest of the graph building parts, should appear in the callback function from your ajax method.
You need to use d[keyX] and d[keyY], respectively, instead of d.letter and d.frequency in your example.
For the labels, .text("Frequency") should be .text(keyY), and you need to add an x label, maybe with (untested):
.call(xAxis) //add the following lines:
.append("text")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(keyX);
Related
I have a graph made with D3, with two problems to solve (I attached an example image):
1) How can I add the font size for the Y axis? It is taking the value indicated for the "cantidad" title only.
2) The text of the first tick on the x-axis appears below the line of the rest. What is missing indicate?
Some code, for the creation of the axes:
var svg = d3.select('#graf_act_tiempo'),
margin = { top: 20, right: 108, bottom: 60, left: 70 },
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 + ')');
// Function to convert a string into a time
var parseTime = d3.time.format('%Y-%m-%d').parse;
// Set the X scale
var x = d3.time.scale().range([0, width], 0.5);
// Set the Y scale
var y = d3.scale.linear().range([height, 0]);
// Set the color scale
var color = d3.scale.ordinal()
.range(["orange", "blue", "red"]);
var tickValuesForAxis = data.map(d => parseTime(d.fecha));
var ticks = data.length;
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(ticks)
.tickFormat(d3.time.format('%d/%m/%y'))
;
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(1, 0)
.tickFormat(d3.format("d"))
;
// Set the X domain
x.domain(d3.extent(data, function(d) {
return d.fecha;
}));
// Set the Y domain
y.domain([
d3.min(currencies, function(c) {
return d3.min(c.values, function(v) {
return v.worth;
});
}),
d3.max(currencies, function(c) {
return d3.max(c.values, function(v) {
return v.worth;
});
})
]);
// Set the X axis
g.append("g")
.attr("class", "x axis")
.call(xAxis)
.attr("transform", function(d) { console.log(height); return "translate(0," + height + ")"; })
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)")
.style("font-size", "10px")
;
// Set the Y axis
g.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("y", 0)
.attr("x", -20)
.attr("dy", "4px")
.style("text-anchor", "end")
.text("Cantidad")
.style("font-size", "11px")
;
Thanks
1> You can use the class name of Yaxis and select text and give font Size in your css file
eg- in your css file
.y.axis text {
font-size: 14px;
}
2> Can you be more specific about the 2nd question?
So I've found a half dozen posts regarding this error but none seem to resolve my issue.
The data as it comes from firebase:
data = [{percent:24,year:1790},....]
var parseDate = d3.time.format("%Y").parse;
// so I want to convert the year to a 'date' for d3 time scale
data.forEach(function(d) {
d.year = parseDate(d.year.toString());
d.percent = +d.percent;
});
which then the data looks like
console.log(data);
[{percent:24,year:Fri Jan 01 1790 00:00:00 GMT-0500 (EST)}...]
my x scale
var x = d3.time.scale().range([0, this.width]);
my x domain
x.domain(d3.extent(data, function(d){return d.year; }));
my line
var line = d3.svg.line()
.x(function(d) { return x(d.year); })
.y(function(d) { return y(d.percent); });
then my chart (there is a base chart that this is extending)
this.chart.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
Which when added has the exception ...
Error: <path> attribute d: Expected number,
UPDATE
So here is the working code using sample data the code is more or less a copy paste from d3 example site using a csv instead of tsv
This works fine
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatDate = d3.time.format("%Y");
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
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 + ")");
d3.csv("./data/debt-public-percent-gdp.csv", type, function(error, data) {
if (error) throw error;
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.close; }));
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("Price ($)");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
});
function type(d) {
d.date = formatDate.parse(d.date);
d.close = +d.close;
return d;
}
This does not work
Using the same datasource here is my 'Base Chart'
export class BaseChart{
constructor(data,elem,config = {}){
var d3 = require('d3');
this.data = data;
this.svg = d3.select('#'+elem).append('svg');
this.margin = {
left: 30,
top: 30,
right: 30,
bottom: 30,
};
let width = config.width ? config.width : 600;
let height = config.height ? config.height : 300;
this.svg.attr('height', height);
this.svg.attr('width', width);
this.width = width - this.margin.left - this.margin.right;
this.height = height - this.margin.top - this.margin.bottom;
this.chart = this.svg.append('g')
.attr('width', this.width)
.attr('height', this.height)
.attr('transform', `translate(${this.margin.left},${this.margin.top})`);
}
}
Chart that Extends the Base Chart
export class FedDebtPercentGDP extends BaseChart{
constructor(data, elem, config){
super(data, elem, config);
var x = d3.time.scale().range([0, this.width]);
var y = d3.scale.linear().range([this.height, 0]);
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.close; }));
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
this.chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + this.height + ")")
.call(xAxis);
//
this.chart.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("Price ($)");
this.chart.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
}
}
The code that calls the chart
d3.csv('./data/debt-public-percent-gdp.csv', type, function(error, data) {
new FedDebtPercentGDP(data, "debt-to-gdp", {width: '100%'});
});
The data
date,close
1790,30
1791,29
1792,28
1793,24
1794,22
1795,19
1796,16
1797,17
1798,16
1799,16
1800,15
1801,13
1802,14
1803,14
1804,13
1805,11
1806,10
1807,10
1808,9
1809,7
1810,6
1811,6
1812,7
1813,8
1814,9
1815,10
1816,10
1817,8
1818,7
1819,7
1820,8
1821,9
1822,8
1823,8
1824,8
1825,7
1826,6
1827,6
1828,5
1829,4
1830,3
1831,2
1832,1
1833,0
1834,0
1835,0
1836,0
1837,0
1838,1
1839,0
1840,0
1841,1
1842,1
1843,2
1844,1
1845,1
1846,1
1847,2
1848,2
1849,3
1850,2
...
There are quite a few problems with the code that you've provided. I guess you did not paste it all. The excerpt you pasted definitely does not have this.chart defined, for example, so it is not possible to reconstruct your error message.
That being said, it is still possible to identify the main problem:
The error message says it all: as you have parsed d.year and turned it into a string, but the path needs a number as an argument there is a 'type mismatch'-kind of error in your code.
You can see how your data looks like in the console log that you've provided. year is turned into a string.
I would recommend leaving d.year as it is, and if you really need, you can create a new attribute called for example date if you need the date in that format. So if it is needed for displaying the date, you can use that one, while year can be used for path calculation in its original format.
I have a d3.js plot that I want to improve it but I can't figure out how to do it!
This is my plot:
Mainly I am trying to change axis and add a little legend so I can get something like this (with x and y zeros centered in the plot ):
This is how I define x and y axis in my d3.js/JavaScript code
var xScale = d3.scale.linear().domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[0]; })]).range([padding, w - padding]);
var yScale = d3.scale.linear().domain([d3.min(dataset, function(d) { return d[1]; }), d3.max(dataset, function(d) { return d[1]; })]).range([h - padding, padding]);
// Create axis
var xAxis = d3.svg.axis().scale(xScale).orient("bottom").ticks(5);
//Define Y axis
var yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(5);
//Create SVG element
var svg = d3.select("#mydiv").append("svg").attr("width", w).attr("height", h);
svg.append("g").attr("class", "axis").attr("transform", "translate(0," + (h - padding) + ")").call(xAxis);
svg.append("g").attr("class", "axis").attr("transform", "translate(" + padding + ",0)").call(yAxis);
Thanks in advance:
If you provide your actual dataset I can give you more exact code for your case, without it the best we can really do is give you examples from the docs.
How to create a legend:
// draw legend
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
// draw legend colored rectangles
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
// draw legend text
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d;})
How to hardcode max/min on your axes:
var yScale = d3.scale.linear().domain([0,6]).range([h - padding, padding]);
How to add gridlines:
var yAxisGrid = yAxis
.tickSize(width, 0)
.tickFormat("")
.orient("right")
var xAxisGrid = xAxis
.tickSize(-height, 0)
.tickFormat("")
.orient("top")
svg.append("g")
.classed('y', true)
.classed('axis', true)
.call(yAxisGrid)
svg.append("g")
.classed('x', true)
.classed('axis', true)
.call(xAxisGrid)
Having issues with getting JSON data on a line chart, i can do this for a bar chart totally fine but for some reason it gives me an error when doing a line chart. There isn't much documentation around so see if anyone knows.
I get this error
Error: Problem parsing d="MNaN,400LNaN,258.65447419986936LNaN,221.90289571086436LNaN,183.32244720226433LNaN,134.29131286740693LNaN,149.70607446113652LNaN,63.1395602003048LNaN,37.44829087742215LNaN,69.40997169605924LNaN,0LNaN,169.91073372523405LNaN,643.2397126061397"
JSON data is as follows:
[{"logins":"3333","month_name":"January"},{"logins":"4956","month_name":"February"},{"logins":"5378","month_name":"March"},{"logins":"5821","month_name":"April"},{"logins":"6384","month_name":"May"},{"logins":"6207","month_name":"June"},{"logins":"7201","month_name":"July"},{"logins":"7496","month_name":"August"},{"logins":"7129","month_name":"September"},{"logins":"7926","month_name":"October"},{"logins":"5975","month_name":"November"},{"logins":"540","month_name":"December"}]
Now the code:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = $("svg").parent().width();
height = $("svg").parent().height();
aspect = 500 / 950;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) { return x(d.month_name); })
.y(function(d) { return y(d.logins); });
var svg = d3.select(document.createElement("div")).append("svg")
.attr("preserveAspectRatio", "xMidYMid")
.attr("viewBox", "0 0 950 500")
.attr("width", width)
.attr("height", width * aspect)
.attr("id", "art_chart")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(d3.extent(data, function(d) { return d.month_name; }));
y.domain(d3.extent(data, function(d) { return d.logins; }));
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("Logins");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
Anyone know how to fix it so it displays the line chart?
You need to parse your dates and pass in the numbers as numbers:
data.forEach(function(d) {
d.month_name = d3.time.format("%B").parse(d.month_name);
d.logins = +d.logins;
});
If you run this code just after loading the JSON, everything should work fine.
I'm trying out d3js and I have a problem with getting my first basic column(vertical bar) chart work. The only thing I find a bit difficult to understand is the scaling thing. I want to make the x and y axis ticks with labels but I have the following problems:
First of all here is my data:
{
"regions":
["Federal","Tigray","Afar","Amhara","Oromia","Gambella","Addis Ababa","Dire Dawa","Harar","Benishangul-Gumuz","Somali","SNNPR "],
"institutions":
[0,0,34,421,738,0,218,22,22,109,0,456]
}
On the y-axis the values are there but the order is reversed. Here is the code:
var y = d3.scale.linear().domain([0, d3.max(data.institutions)]).range([0, height]);
then I use this scale to create a y-axis:
var yAxis = d3.svg.axis().scale(y).orient("left");
and add this axis to the svg element
svgContainer.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("Institutions");
the problem here is that the y-axis start from 0 at the top and with 700 at the bottom which is OK but it should be in reverse order.
The other problem I have it the x-axis. I want to have an ordinal scale since the values I want to put are in the regions names I have above. So here's what I've done.
var x = d3.scale.ordinal()
.domain(data.regions.map(function(d) { return d.substring(0, 2); }))
.rangeRoundBands([0, width], .1);
then the axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
and finally add it to the svg element
svgContainer.append("g")
.attr("class", "x axis")
.attr("transform", "translate( 0," + height + ")")
.call(xAxis);
Here the problem is the ticks as well as the labels appear but they are not spaced out evenly and do not correspond with the center of the rectangles I'm drawing. Here is the complete code so you can see what's happening.
$(document).ready(function(){
d3.json("institutions.json", draw);
});
function draw(data){
var margin = {"top": 10, "right": 10, "bottom": 30, "left": 50}, width = 700, height = 300;
var x = d3.scale.ordinal()
.domain(data.regions.map(function(d) { return d.substring(0, 2); }))
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.domain([0, d3.max(data.institutions)])
.range([0, height]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svgContainer = d3.select("div.container").append("svg")
.attr("class", "chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" +margin.left+ "," +margin.right+ ")");
svgContainer.append("g")
.attr("class", "x axis")
.attr("transform", "translate( 0," + height + ")")
.call(xAxis);
svgContainer.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("Institutions");
svgContainer.selectAll(".bar")
.data(data.institutions)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d, i) {return i* 41;})
.attr("y", function(d){return height - y(d);})
.attr("width", x.rangeBand())
.attr("height", function(d){return y(d);});
}
I put the code to Fiddle: http://jsfiddle.net/GmhCr/4/
Feel free to edit it! I already fixed both problems.
To fix the upside-down y-axis just swap the values of the range function arguments:
var y = d3.scale.linear().domain([0, d3.max(data.institutions)]).range([height, 0]);
Do not forget to adjust the code for the bars if you change the scale!
The source of the mismatch between bars and the x-axis can be found here:
var x = d3.scale.ordinal()
.domain(data.regions.map(function(d) {
return d.substring(0, 2);}))
.rangeRoundBands([0, width], .1);
svgContainer.selectAll(".bar")
.data(data.institutions)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d, i) {return i* 41;})
.attr("y", function(d){return height - y(d);})
.attr("width", x.rangeBand())
.attr("height", function(d){return y(d);});
You specify the padding for rangeRoundBands at 0.1 but you ignore the padding when computing the x and width values for the bars. This for example is correct with a padding of 0:
var x = d3.scale.ordinal()
.domain(data.regions.map(function(d) {
return d.substring(0, 2);}))
.rangeRoundBands([0, width], 0);
svgContainer.selectAll(".bar").data(data.institutions).enter().append("rect")
.attr("class", "bar")
.attr("x", function(d, i) {
return i * x.rangeBand();
})
.attr("y", function(d) {
return y(d);
})
.attr("width", function(){
return x.rangeBand();
})
.attr("height", function(d) {
return height -y(d);
});
The padding determines how much of the domain is reserved for padding. When using a width of 700 and a padding of 0.1 exactly 70 pixels are used for padding. This means you have to add 70 / data["regions"].length pixels to every bar's x value to make this work with a padding.