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.
Related
I'm new to d3js and i'm trying to manipulate a simple graph with 2 axis and some rect to show some data.
I've set the range of data to my y axis with some object name. This object has also a type "technical" or "canonical".
I'm trying to replace this "technical" or "canonical" with a bootstrap's glyphicon.
I've tried to replace the datas from the range with a internal text containing the proper glyphicon but without success
//datas is the data structure containing my chart datas.
//objects will be the array use for the domain
var objects = datas.map(function (d) {
return d.object + getExchangeObjectType(d.type);
});
var margin = {top: 40, right: 20, bottom: 200, left: 400},
width = 1200 - margin.left - margin.right,
height = (objects.length*30) - margin.top - margin.bottom;
var canonical = "<span class='glyphicon glyphicon-copyright-mark'></span>";
var technical = "<span class='glyphicon glyphicon-wrench'></span>";
function getExchangeObjectType(type){
if (type == 'Technical')
return technical;
else
return canonical;
}
//datas is the data structure containing my chart datas.
//objects will be the array use for the domain
var objects = datas.map(function (d) {
return d.object + getExchangeObjectType(d.type);
});
var x = d3.scale.ordinal().rangePoints([0, width]);
var y = d3.scale.ordinal().rangeBands([height, 0],.1,.1);
// define x & y axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("top")
.ticks(percents.length)
;
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(objects.length)
;
// define the domain of datas
y.domain(objects);
x.domain(percents);
Here is the svg part:
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 + ")");
// draw x axis
svg.append("g")
.attr("class", "x axis")
.call(xAxis)
.selectAll("text")
.attr("x",20)
.style("text-anchor", "end")
;
// draw y axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll("text")
.style("text-anchor", "end")
;
svg.selectAll("bar")
.data(datas)
.enter().append("rect")
.style("fill", function (d) { return getColor(d.value);})
.attr("y", function(d){return d.object + getExchangeObjectType(d.type);})
.attr("height", y.rangeBand())
.attr("x", 0 )
.attr("width", function(d) { return ( d.value * width) / 100 ; })
;
I've found out my solution use the unicode !
I've added my type in the label text and then replace it with an unicode :
.text(function(d){
var oldText=d.split(":");
if (oldText[1]=="Canonical")
return oldText[0]+" \ue194";
else
return oldText[0]+" \ue136"
})
I'm trying to make a graph using d3 javascript but i get an error the the .tsv can't load resource and Uncaught TypeError: Cannot read property 'map' of undefined at the .js file.
This is my .js code:
var svg;
var Statistics = {
//flag to determine if graph has already been generated
HaveGraph: new Array(),
//turn on or off relevan statistics details
ShowHideDetails: function (itemId) {
var detailsId = '#' +itemId + 'Result'; //calc id of result div
if ($(detailsId).css('display') == 'block') //if item is visible
$(detailsId).hide();
else {
$(detailsId).show();
//make ajax call to update the most recent statistics data
var url = "/Statistics/ReGenerateStatisticFiles";
$.get(url,
null, //here goes the params if we intend to pass params to function in format: { paramName: data }
function (data) {
if(data == "OK") //if data was updated successfully --> show it
{
Statistics.DisplayGraph(itemId + 'Result');
}
});
}
},
//generate graph to show results
DisplayGraph: function (reportName) {
if (Statistics.HaveGraph == null || Statistics.HaveGraph[reportName] == null) {
svg = d3.select('#' + reportName).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.tsv(reportName + ".tsv",
function (d) {
d.Count = +d.Count;
return d;
},
function (error, data) {
x.domain(data.map(function (d) { return d.Item; }));
y.domain([0, d3.max(data, function (d) { return d.Count; })]);
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("Count"); //CHANGE!!!!!!!!!!
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function (d) { return x(d.Item); })
.attr("width", x.rangeBand())
.attr("y", function (d) { return y(d.Count); })
.attr("height", function (d) { return height - y(d.Count); });
});
Statistics.HaveGraph[reportName] = true;
}
}
}
var margin = { top: 20, right: 20, bottom: 30, left: 40 },
width = 600 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
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")
.ticks(10, "");
I can see that the .tsv file get the needed data but it doesn't show the data on the graph.
Thanks
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]);
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?
I am Trying to Implement this code: http://bl.ocks.org/3883245, but instead of loading a TSV file, i am loading the data from an array.
Here is how the array looks Like:
[["2012-10-02",2],["2012-10-09", 2], ["2012-10-12", 2]]
and then I applied this function on it to get CSV Format: var data = d3.csv.format(BigWordsDates2[ArrayIndex]);
but still nothing shows up.
Here is the whole code: I think I am not that far from getting it but I have been working on it for 3 days and still cant get it to work:
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 80 - margin.left - margin.right,
height = 80 - margin.top - margin.bottom;
var data = d3.csv.format(BigWordsDates2[ArrayIndex]);
var parseDate = d3.time.format("%Y-%b-%d").parse;
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("#Graph").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.parseRows(data, function(data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = parseInt(d.close);
});
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);
});`
I think I am also doing something wrong with the d.date and d.close but I can't figure it out either.
The chart uses data in the following format
[{ date: '...', close: '...'},
{ date: '...', close: '...'}]
So, you need to parse your array:
// fix your data parser
var parseDate = d3.time.format("%Y-%m-%d").parse;
var arrData = [
["2012-10-02",200],
["2012-10-09", 300],
["2012-10-12", 150]];
// create a new array that follows the format
var data = arrData.map(function(d) {
return {
date: parseDate(d[0]),
close: d[1]
};
});
Here's the working version: http://jsfiddle.net/jaimem/T546B/
pd: depending on the data you might have to modify your y scale's domain.