JSON keys for x axis, values for y - javascript

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); })

Related

Create D3 Bar Chart from array of timestamps

I am looking for some insight on best practices/how to get started here. I have a json object with 1000 timestamps from a given day. I want to build the x-axis as a 24 hour time frame with a tick for 4 hour periods...12am - 4am, 4am - 8am, etc. I then want to build the y-axis based on volume. So that each bar for the 4-hour periods will be populated based on the number of timestamps in that period. My json looks like this (except with many more entries):
[
{"Time":"2017-02-07 16:14:06"},
{"Time":"2017-02-07 16:58:49"},
{"Time":"2017-02-07 17:07:11"},
{"Time":"2017-02-07 18:13:19"},
{"Time":"2017-02-07 13:56:06"},
{"Time":"2017-02-07 19:07:57"},
{"Time":"2017-02-07 12:08:58"},
{"Time":"2017-02-07 01:41:00"},
{"Time":"2017-02-07 11:56:49"},
{"Time":"2017-02-07 02:45:29"}
]
I have been doing a lot of reading on D3, specifically about how to use the built in 'time' method but I am hoping to get some pointers on how to get started. The end result that I would want would look something like the image attached here (disregard the black bars as those will come from another source). Any help/pointers are much appreciated.
This is not a pure D3 solution but what I would do is parse that JSON object and then create a new one that has a totals count for each time interval and then feed that into the d3 graph.
Not sure what you have tried yet, but this should get you on the right path.
https://jsfiddle.net/9f2vp8gp/
var margin = {
top: 20,
right: 20,
bottom: 70,
left: 40
},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%m").parse;
var x = d3.scale.ordinal().rangeRoundBands([0, width], .05);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%m"));
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10);
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 + ")");
var data = [{
"date": "1",
"value": 44
}, {
"date": "2",
"value": 24
} ,{
"date": "3",
"value": 31
},{
"date": "4",
"value": 38
},{
"date": "5",
"value": 46
},{
"date": "6",
"value": 23
}];
data.forEach(function(d) {
d.date = parseDate(d.date);
d.value = +d.value;
});
x.domain(data.map(function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return d.value;
})]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)");
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("Value ($)");
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", "steelblue")
.attr("x", function(d) {
return x(d.date);
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
});

Creating a categorical line chart in D3.js (V4)

I'm relatively new to D3.js and I'm visualising the 'PassengersIn' & 'PassengersOut' values from my busdatasimple.json file. For reference, one of the JSON objects looks like this;
{
"BusNo": "1",
"Date": "21 November 2016",
"Time": "09:10:34 AM",
"Destination": "Pier 50",
"Longitude": "-122.383262",
"Latitude": "37.773644",
"PassengersIn": "8",
"PassengersOut": "1"
}
I'm now trying to graph the PassengersIn & PassengersOut against the Destination using two lines on a line graph. I'm struggling with the axes as the x has only 2 ticks and the y is not scaling to my data. As seen below;
My code is as follows. I've removed the irrelevant Google Maps and jQuery.
//Setting The Dimensions Of The Canvas
var margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 650 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
//Setting X & Y Ranges
var x = d3.scaleOrdinal().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
//Define The Axes
var xAxis = d3.axisBottom().scale(x);
var yAxis = d3.axisLeft().scale(y).ticks(10);
//Add The SVG Element
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right + 50)
.attr("height", height + margin.top + margin.bottom + 200)
.attr("class", "svg")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Load Data From JSON
d3.json("busdatasimple.json", function(error, data) {
//Functions for Y-Axis Grid Lines
function yGridLines() {
return d3.axisLeft().scale(y).ticks(5);
}
//Adding the Y-Axis Grid Lines
svg.append("g")
.attr("class", "grid-lines")
.call(yGridLines().tickSize(-width, 0, 0).tickFormat(""));
//Adding Y-Axis
svg.append("g")
.attr("class", "y axis").call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 5)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Passengers In");
//Adding X-Axis (Added to the end of the code so the label show over bottom bars)
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "middle")
//.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "translate(-5, 15)")
.attr("font-family", "Arial")
.attr("font-weight", "bold")
.attr("font-size", "1.1em");
x.domain(data.map(function(d){return d.Destination;}));
y.domain([d3.min(data, function(d){return d.PassengersIn;}), d3.max(data, function(d) {return d.PassengersIn;})]);
var line = d3.line()
.x(function(d){return x(d.Destination);})
.y(function(d){return y(d.PassengersIn);});
svg.append("path").datum(data)
.attr("class", "line")
.attr("d", function(d){return d.PassengersIn;})
.attr("stroke", "green")
.attr("stroke-width", 2);
});
I've managed to find a few examples that use a categorical ordinal scale, however, they are all using v3 of d3.js and after reading through the v4 API countless times I still can't figure it out.
You want a categorical scale, that's right, but you don't want an ordinal scale here.
There was a lot of changes from v3 to v4. In v3, you could set .rangeBands, .rangeRoundBands, .rangePoints and .rangeRoundPoints to your ordinal scale, which therefore could accept an continuous range. Not anymore: in D3 v4 you have the brand new scaleBand and scalePoint.
In v4, in a regular ordinal scale (which is scaleOrdinal):
If range is specified, sets the range of the ordinal scale to the specified array of values. The first element in the domain will be mapped to the first element in range, the second domain value to the second range value, and so on. If there are fewer elements in the range than in the domain, the scale will reuse values from the start of the range. (emphases mine)
So, in an scaleOrdinal, the range need to have the same length (number of elements) of the domain.
That being said, you want a point scale (scalePoint) here. Band scales and point scales...
...are like ordinal scales except the output range is continuous and numeric. (emphasis mine)
Check this snippet, look at the console and compare the two scales:
var destinations = ["foo", "bar", "baz", "foobar", "foobaz"];
var scale1 = d3.scaleOrdinal()
.range([0, 100])
.domain(destinations);
var scale2 = d3.scalePoint()
.range([0, 100])
.domain(destinations);
destinations.forEach(d=>{
console.log(d + " in an ordinal scale: " + scale1(d) + " / in a point scale: " + scale2(d))
})
<script src="https://d3js.org/d3.v4.min.js"></script>
Regarding your y-axis problem:
Set the domain of the y scale before calling the axis. So, instead of this:
svg.append("g")
.attr("class", "y axis").call(yAxis);
y.domain([d3.min(data, function(d){
return d.PassengersIn;
}), d3.max(data, function(d){
return d.PassengersIn;
})]);
Change the order:
y.domain([d3.min(data, function(d){
return d.PassengersIn;
}), d3.max(data, function(d){
return d.PassengersIn;
})]);//set the domain first!
svg.append("g")
.attr("class", "y axis").call(yAxis);

Position a chart top left

I am a newbie using d3 and javascript, but have managed to cobble together some code (using borrowed code from online examples) to create a simple column chart with different colours indicating the status of a variable. Everything works well except that the chart will not position at the top left of the canvas despite adjusting the margin.top or margin.left to small values. If I adjust the browser window to portrait, the chart aligns left but with white significant amounts of white space above it. If I adjust the browser window to landscape the chart aligns to the top but with significant amounts of white space to the left of it. Can someone please advise as to where I have gone wrong. Thanks in advance.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.axis text {
font: 10px sans-serif;
}
</style>
<svg class="chart"></svg>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var data = [
{
"status": "Low",
"value": "20",
"color": "red"
},
{
"status": "Marginal",
"value": "10",
"color": "orange"
},
{
"status": "High",
"value": "70",
"color": "green"
}
];
var margin = {top: 10, right: 20, bottom: 30, left: 30},
width = 200 - margin.left - margin.right,
height = 200 - 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);
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 + ")");
x.domain(data.map(function(d) { return d.status; }));
y.domain([0, 100]);
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("Probability");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.status); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return d.color; });
function type(d) {
d.frequency = +d.frequency;
return d;
}
</script>
The problem seems to be that you have an <svg class="chart"> element, but your code appends yet another <svg> element to the page after that one, and the first one takes some space

D3 charts - Data overlapping issue with huge data

I have generated a bar chart using D3 charts. Here is the js code
function renderChart(filename) {
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//var formatPercent = d3.format(".0%");
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");
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 + ")");
var data = [
{"name": "india", "population": 120},
{"name": "uk", "population": 200},
{"name": "us", "population": 300},
{"name": "china", "population": 50}
];
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.population; })]);
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("Frequency");
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.name); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.population); })
.attr("height", function(d) { return height - y(d.population); });
}
Here is the link to fiddle - http://jsfiddle.net/scfx9/
Here, the width of the bars increases and decreases according to the size of the data. When ever I pass huge data the bars shrink and data gets overlapped as the div size is fixed. How do I make the size of the bars constant and allow the users to use the arrow keys to move the data as in the example below
http://bl.ocks.org/mbostock/4062085
If you look at the example you posted, he is handling the keydown event for arrow keys:
d3.select(window).on("keydown", function() {
switch (d3.event.keyCode) {
case 37: year = Math.max(year0, year - 10); break;
case 39: year = Math.min(year1, year + 10); break;
}
update();
});
Your data will need to work a little bit differently but the concept of windowing is the same. Each time the arrow key is pressed your handler will produce a data array that represents the current window into the overall data. Then you will bind the array to enter new bars and exit old ones and you can decide the kind of transition you want.
One difference in your case compared with the example is that his example uses a fixed scale on his X axis, so he never needs to change the axis. In your case because you're using an ordinal scale and your values will be different for each data window, you'll need to update the domain on your X scale and redraw the axis (possible in a transition if you like).

Getting key and value of object

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?

Categories

Resources