While I'm trying to access the key and value of an object, it's giving undefined. Below is my code
<script type="text/javascript">
var data;
var xAxisName;
var yAxisName;
var jso;
function getX(d) {
return d[xAxisName];
}
function getY(d) {
return d[yAxisName];
}
d3.json("response.json", function (json) {
console.log("hi");
console.log(json); //getting the values
console.log("this " +json.users); //getting the values
xAxisName = json.attribute1.;
console.log("xAxisName=" + xAxisName); //Not getting the values
yAxisName = json.attribute2;
console.log("yAxisName=" + yAxisName); //Not getting the values
data = json.users;
alert(data);
data.map(function(d) { console.log(getX(d));});
data.map(function(i) {console.log(i);});
visualize(data); //then start the visualization
});
function visualize (data) {
var padding = 40;
var margin = {top:30, right: 30, bottom: 30, left:100};
var w = 700 - margin.left - margin.right;
var h = 400 - margin.top - margin.bottom;
//the svg
var svg = d3.select("#container")
.append("svg")
.attr("class", "chart")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//the scales
var xScale = d3.scale.ordinal()
.domain(d3.range(data.length))
.rangeRoundBands([0, w], 0.04);
var yScale = d3.scale.linear()
.domain([d3.max(data, getY), 0])
.range([0, h]);
//the axes
var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
var yAxis = d3.svg.axis().scale(yScale).orient("left");
//add the data and bars
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d, i) { return xScale(i);})
.attr("y", function(d) { return yScale(getY(d));})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return h - yScale(getY(d));})
.attr("class", "bar");
//create axes
svg.append("g").attr("class", "x axis")
.attr("transform", "translate(0," + h + ")").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(yAxisName);
}
alert("done");
</script>
It's giving undefined for the xAxisName and yAxisName. In svg.selectAll("rect") y and height giving NaN.
My JSON is
{
"users": [
{
"name": "warchicken",
"score": 30
},
{
"name": "daydreamt",
"score": 100
},
{
"name": "Anas2001",
"score": 30
},
{
"name": "ocjojo",
"score": 30
},
{
"name": "joklawitter",
"score": 30
}
]
}
It looks likes you want to extract property names from the user objects. To do that, you can either use Object.keys() or iterate over the object with for...in (related question: How do I enumerate the properties of a JavaScript object?).
var keys = Object.keys(json.users[0]);
xAxisName = keys[0];
yAxisName = keys[1];
Beware though that object properties are not ordered. You might end up with xAxisName being "score" and vice versa.
If you need xAxisName to be a certain value, you either have to hardcode it, or add the information to the JSON you return from the server. For example:
{
"axes": ["name", "score"],
"users": [...]
}
Then you get it with
xAxisName = json.axes[0];
// ...
Side note: Choosing json as variables name for an object is not optimal because it suggests that the variables holds a string containing JSON, while it actually holds an object. How about chartData instead?
Related
I'm a newbie at D3. I have this nice example of a candlestick chart that loads its data from a csv file. I got that example to work but now I want to do the same thing except load the data from an ajax call which returns json data. I can't figure out how to do it.
After reading a few comments, here is my second attempt:
function showChart() {
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = $(window).width()*0.6 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.timeParse("%Y-%m-%d");
var x = techan.scale.financetime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var candlestick = techan.plot.candlestick()
.xScale(x)
.yScale(y);
var xAxis = d3.axisBottom().scale(x);
var yAxis = d3.axisLeft().scale(y);
$.ajax("http://www.mycom.net/getQuoteHistory.php?symbol='A'", {
success: function(data) {
console.log("getQuoteHistory:data="+JSON.stringify(data));
var accessor = candlestick.accessor();
data = JSON.parse(data);
var newData = [];
for (var i=0; i<data.length; i++) {
var o = data[i];
var newObj = {};
newObj.date = parseDate(o.Date);
newObj.open = o.Open;
newObj.high = o.High;
newObj.low = o.Low;
newObj.close = o.Close;
newObj.volume = o.Volume;
newData.push(newObj);
}
console.log("getQuoteHistory:newData="+JSON.stringify(newData));
var svg = d3.select("svg")
.data(newData)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g").attr("class", "candlestick");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
svg.append("g")
.attr("class", "y axis")
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Price ($)");
// Data to display initially
draw(newData.slice(0, newData.length-20));
// Only want this button to be active if the data has loaded
d3.select("button").on("click", function() {
draw(newData);
}).style("display", "inline");
},
error: function() {
console.log("something went wrong in ajax call");
}
});
function draw(data) {
x.domain(data.map(candlestick.accessor().d));
y.domain(techan.scale.plot.ohlc(data, candlestick.accessor()).domain());
svg.selectAll("g.candlestick").datum(data).call(candlestick);
svg.selectAll("g.x.axis").call(xAxis);
svg.selectAll("g.y.axis").call(yAxis);
}
}
The ajax call returns good json array data and it is converted to newData which has the date parsed correctly and the field names in lower case as reqd. Here is a snippet of each:
getQuoteHistory:data="[{\"Symbol\":\"A\",\"Date\":\"2018-06-28\",\"Open\":\"61.13\",\"High\":\"61.64\",\"Low\":\"60.42\",\"Close\":\"61.29\",\"Volume\":\"15641\"},{\"Symbol\":\"A\",\"Date\":\"2018-06-29\",\"Open\":\"61.68\",\"High\":\"62.47\",\"Low\":\"61.57\",\"Close\":\"61.84\",\"Volume\":\"18860\"},
getQuoteHistory:newData=[{"date":"2018-06-28T06:00:00.000Z","open":"61.13","high":"61.64","low":"60.42","close":"61.29","volume":"15641"},{"date":"2018-06-29T06:00:00.000Z","open":"61.68",
Now the failure happens in the draw function on this line:
svg.selectAll("g.candlestick").datum(data).call(candlestick);
where the chrome javascript console shows "svg is not defined".
But it is defined in the html:
<svg></svg>
Even if I pass svg as a parameter to draw method, then it says "cannot read property selectAll of undefined".
Any ideas how to get this to work from a json array instead of a csv file?
You need to reselect your svg in function draw(data), because your variable var svg is a local variable which is only defined within the success function from your ajax call.
Just add:
function draw(data) {
var svg = d3.select("svg");
// The rest of your function
}
Here is the working code. Thanks to all the contributors. I learned from each of you to get this to work.
function showChart(symbol) {
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = $(window).width()*0.6 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.timeParse("%Y-%m-%d");
var x = techan.scale.financetime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var candlestick = techan.plot.candlestick()
.xScale(x)
.yScale(y);
var xAxis = d3.axisBottom().scale(x);
var yAxis = d3.axisLeft().scale(y);
$.ajax("http://www.mycom.net/getQuoteHistory.php?symbol='" +symbol +"'", {
success: function(data) {
console.log("getQuoteHistory:data="+JSON.stringify(data));
var accessor = candlestick.accessor();
data = JSON.parse(data);
data = data.slice(0, 200).map(function(d) {
return {
date: parseDate(d.Date),
open: +d.Open,
high: +d.High,
low: +d.Low,
close: +d.Close,
volume: +d.Volume
};
}).sort(function(a, b) { return d3.ascending(accessor.d(a), accessor.d(b)); });
console.log("getQuoteHistory:newData="+JSON.stringify(data));
var svg = d3.select("svg")
.data(data)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g").attr("class", "candlestick");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
svg.append("g")
.attr("class", "y axis")
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Price ($)");
draw(data,svg);
},
error: function() {
console.log("something went wrong in ajax call");
}
});
function draw(data,svg) {
x.domain(data.map(candlestick.accessor().d));
y.domain(techan.scale.plot.ohlc(data, candlestick.accessor()).domain());
svg.selectAll("g.candlestick").datum(data).call(candlestick);
svg.selectAll("g.x.axis").call(xAxis);
svg.selectAll("g.y.axis").call(yAxis);
}
}
There's probably a way to do it with d3.json() and avoid the extra ajax stuff.
I am trying to create a simple histogram with values stored in a csv (that I will be modifying through the time).
The code I am using now is: (edited code!)
var values = []
d3.csv('../static/CSV/Chart_data/histogram_sub.csv?rnd='+(new Date).getTime(),function(data){
values = Object.keys(data).map(function(k){ return data[k]['Calculus I']});
var color = "steelblue";
// Generate a 1000 data points using normal distribution with mean=20, deviation=5
// A formatter for counts.
var formatCount = d3.format(",.0f");
var margin = {top: 20, right: 30, bottom: 30, left: 30},
width = 800 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var max = d3.max(values);
var min = d3.min(values);
var x = d3.scale.linear()
.domain([min, max])
.range([0, width]);
// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(20))
(values);
var yMax = d3.max(data, function(d){return d.length});
var yMin = d3.min(data, function(d){return d.length});
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var y = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var svg = d3.select("#Histogram2").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 bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.append("rect")
.attr("x", 1)
.attr("width", (x(data[0].dx) - x(0)) - 1)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.append("text")
.attr("dy", ".75em")
.attr("y", -12)
.attr("x", (x(data[0].dx) - x(0)) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
});
And my csv file looks like this:
Calculus I
5.0
5.1
5.7
...
And I am getting errors that I think refer to data[0]:
Uncaught TypeError: Cannot read property 'dx' of undefined
Any help? Thanks in advance!
Here's a plunkr using d3.csv and fetching data from the file:
http://plnkr.co/edit/2xCvrwiXWzrS6gtbmIU7?p=preview
And please go through the docs for d3.csv
Using the same, here are the relevant changes to the code:
Added a new file test.csv with the content.
Fetched the file using d3.csv:
d3.csv("test.csv", parse, function(error, data) {
console.log(data);
});
The parse that you see above is a accessor function that receives every row from the csv and I'm using it to parse the integer value.
function parse(row) {
row['Calculus I'] = +row['Calculus I'];
return row;
}
And as you were assuming values to be array of integers, I'm mapping the fetched data in the same format as desired using map
values = data.map(function(d) { return d['Calculus I']; });
Hope this helps.
What you need to do is first read the csv using d3.csv and then convert it to an array of values
var values = []
d3.csv("**csv file path**",function(data){
//This will internally convert csv to a json and then we can extract all values and transform it into an array
values = Object.keys(data).map(function(k){ return data[k]['Calculus I']});
//If the above code is too complex for you
//for(i in data){
// values.push(data[i]['Calculus I']
//}
});
//Rest of the chart rendering code goes here
I am trying to make a line chart in d3, with time on x-axis. I am using json data in variable. I used d3.time.format() function to format the time, but it gives me above error. I am learning d3 so please help. my code is :
<div id="viz"></div>
<script>
var width = 640;
var height = 480;
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().domain([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left");
var data = [
{ "at": "2014-11-18T07:29:03.859Z", "value": 0.553292},
{ "at": "2014-11-18T07:28:53.859Z", "value": 0.563292},
{ "at": "2014-11-18T07:28:43.859Z", "value": 0.573292},
{ "at": "2014-11-18T07:28:33.859Z", "value": 0.583292},
{ "at": "2014-11-18T07:28:13.859Z", "value": 0.553292},
{ "at": "2014-11-18T07:28:03.859Z", "value": 0.563292}];
var line = d3.svg.line()
.x(function(d, i) { return x(d.x_axis); })
.y(function(d, i) { return y(d.y_axis); })
.interpolate("linear");
var vis = d3.select("#viz")
.append("svg:svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g").attr("transform","translate(" + margin.left + "," + margin.top + ")");
data.forEach(function(d) {
d.xAxis = d3.time.format("%d-%b-%y").parse(d.xAxis);
d.yAxis = +d.value;
});
x.domain(d3.extent(data, function(d) { return d.x_axis; }));
y.domain(d3.extent(data, function(d) { return d.y_axis; }));
vis.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
vis.append("g")
.attr("class", "y axis")
.call(yAxis);
vis.append("svg:path")
.datum(data)
.attr("class", "line")
.attr("d", line);
There are several issues in your code so I have created a new one below with some comments on the changes.
<style>
/* You need some styling for your line */
.line{
stroke:steelblue;
fill:none
}
</style>
<script>
var width = 640;
var height = 480;
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
var x = d3.time.scale().range([0, width]);
// You had a mistake filling the domain structure of your scale.
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 data = [
{ "at": "2014-11-18T07:29:03.859Z", "value": 0.553292},
{ "at": "2014-11-18T07:28:53.859Z", "value": 0.563292},
{ "at": "2014-11-18T07:28:43.859Z", "value": 0.573292},
{ "at": "2014-11-18T07:28:33.859Z", "value": 0.583292},
{ "at": "2014-11-18T07:28:13.859Z", "value": 0.553292},
{ "at": "2014-11-18T07:28:03.859Z", "value": 0.563292}];
//You were using non existing variables
var line = d3.svg.line()
.x(function(d) { return x(d.xAxis); })
.y(function(d) { return y(d.yAxis); })
.interpolate("linear");
var vis = d3.select("#viz")
.append("svg:svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g").attr("transform","translate(" + margin.left + "," + margin.top + ")");
//You are using ISO format, use the correct time parser
var iso = d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");
data.forEach(function(d) {
d.xAxis = iso.parse(d.at); //You were parsing a non existing variable
d.yAxis = parseFloat(d.value); //You were parsing a non existing variable
});
console.log(data);
//The variables for the domain were not correct
x.domain(d3.extent(data, function(d) { return d.xAxis; }));
y.domain(d3.extent(data, function(d) { return d.yAxis; }));
vis.append("g").attr("class", "x axis").attr("transform", "translate(0," + height + ")").call(xAxis);
vis.append("g").attr("class", "y axis").call(yAxis);
vis.append("svg:path").datum(data).attr("class", "line").attr("d", line);
</script>
Let me know if this helps
Here's a fiddle which shows what I think you're after: http://jsfiddle.net/henbox/n9s542w0/1/
There were a couple of changes to make.
Firstly, there's some inconsistency about how you use d.y_axis vs d.yAxis (same with x) to set \ refer to the new elements you add to the data set which you want to plot. I've set these keys to d.y_axis and d.x_axis.
Next:
d.xAxis = d3.time.format("%d-%b-%y").parse(d.xAxis);
Your format here should refer to the input format, rather than the output, and you should be parsing d.at rather than d.xAxis (see this answer for more), so you should have:
d.x_axis = d3.time.format("%Y-%m-%dT%H:%M:%S.%LZ").parse(d.at);
Thirdly, when you append the line you should handle the data differently. Change:
vis.append("svg:path")
.datum(data)
.attr("class", "line")
.attr("d", line);
to
vis.append("svg:path")
.attr("class", "line")
.attr("d", line(data));
As per this similar example
And finally, you were setting the y domain twice but never setting the range. The line:
var y = d3.scale.linear().domain([height, 0]);
should read
var y = d3.scale.linear().range([height, 0]);
Basically, I am trying to recreate this graph which was created in Excel:
My code so far is this...
var theData = [
{
'FB':4,
'Mv':4,
'CB':5,
'SL':3,
'CH':2,
'OT':2,
'Ctrl':6,
'Cmd':6,
'Del':5,
'AA':6,
},
{
'FB':2,
'Mv':3,
'CB':4,
'SL':5,
'CH':4,
'OT':3,
'Ctrl':5,
'Cmd':6,
'Del':6,
'AA':5,
},
etc...
];
var margin = {top:10, right:10, bottom:30, left:40},
width = 600 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(d3.keys(theData[0]))
.rangeRoundBands([0, width]);
var y = d3.scale.linear()
.domain([2,8])
.range([height,0]);
var xAx = d3.svg.axis()
.scale(x)
.orient('bottom')
var yAx = d3.svg.axis()
.scale(y)
.orient('left')
.ticks(3);
var svgContainer = d3.select('#d3Stuff').append('svg')
.attr('width',width + margin.left + margin.right)
.attr('height',height + margin.top + margin.bottom)
.style('border','1px solid black')
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svgContainer.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAx)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end");
svgContainer.append("g")
.attr("class", "y axis")
.call(yAx)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end");
I'm having problems trying to get circles to appear for the values in the data object. I would like them to line up with the x axis keys, obviously. If I can at least get the initial values to show, I can calculate the min/max/avg later.
Here is what the code creates so far:
Any help would be awesome!
You can use the ordinal scale to find the x position of any circle using the key (since the ordinal domain is made up of keys). For example x("FB"), x("Mv"), etc.
To create the circles, you need to bind to an array, in the typical d3 fashion, using the enter, update, exit stuff.
Since your data is a hash, not an array, you need to first get it into an array form. That's easy using d3.map() with theData (I'd recommend removing the array [] wrapping around the hash inside theData, since it doesn't do anything, but still):
d3.map(theData[0]).entries()
/* returns [
{
"key": "FB",
"value": 4
},
{
"key": "Mv",
"value": 4
},
{
"key": "CB",
"value": 5
},
{
"key": "SL",
"value": 3
},
{
"key": "CH",
"value": 2
},
...
]"
Once you have this associative array, you can do the usual data(...) binding, append the circles, and then position them with something like
.attr("x", function(d) { return x(d.key); })
I'm trying to display single bars in D3. I have a data of the type:
data = [
"value1": 1,
"value2": 2,
"value3": 3,
]
Because the y scale is not the same, I'm trying to display three different bar-charts, each one of them just with a bar. I don't need x-axis.
As you can see in this fiddle: http://jsfiddle.net/GPk7s/, The bar is not showing up, although if you inspect the source code, it has been added. I think it is because I'm not providing a x range, but I don't know how, because I don't really have one.
I just want a bar whose height is related to the range I provide (in the fiddle example this is [10, 30]).
I copy here the code just in case:
var margin = {
top: 50,
right: 0,
bottom: 100,
left: 30
},
width = 200 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var data = [{ "Value": 22.5 } ];
var yRange = d3.scale.linear().range([height, 0]);
yRange.domain([10, 30]);
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 + ")");
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("class", "bar")
.attr("y", function (d) {
return yRange(d.Value);
})
.attr("height", function (d) {
return height - yRange(d.Value);
})
.attr("y", function (d) {
return yRange(d.Value) + 3;
})
.attr("dy", ".75em")
.text(function (d) {
return d.Value;
});
Thanks for your help!
There are two problems with what you are doing:
1) Your rectangle doesn't have a width. I added this:
...
.attr("class", "bar")
.attr("width", 10)
.attr("y", function (d) {
...
2) Your data is not an array. D3 expects arrays of data to be provided with the .data() method, and you have data = {Value : 22.5} (effectively). I changed it to this:
...
var data = [{'Value' : 22.5}];
...
Updated fiddle is here.