Create D3 Bar Chart from array of timestamps - javascript

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

Related

D3 V5 Bar chart

Okay I am trying to make a bar chart. This is the Javascript I have.
var data = [
{
"date": "1459468800000", // 1 April 2016
"values": [{
"name": "US",
"value": 72580613,
"domainname": "com"
}, {
"name": "US",
"value": 161645,
"domainname": "nl"
}]
}, {
"date": "1467331200000", // 1 Juli 2016
"values": [{
"name": "US",
"value": 73129243,
"domainname": "com"
}, {
"name": "US",
"value": 166152,
"domainname": "nl"
}]
}
]
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var tooltip = d3.select("body").append("div").attr("class");
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
var colours = d3.scaleOrdinal()
.range(["#6F257F", "#CA0D59"]);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(data.map(function(d) { return d.values.domainname; }));
y.domain([0, d3.max(data, function(d) { return d.values.value; })]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(25).tickFormat(function(d) { return parseInt(d / 1000) + "K"; }).tickSizeInner([-width]))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.attr("fill", "#5D6971")
.text("Aantal domeinen");
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("x", function(d) { return x(d.values.domainname); })
.attr("y", function(d) { return y(d.values.value); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.values.value); })
.attr("fill", function(d) { return colours(d.values.domainname); })
As you can see I have the data in an array. In the data I have 2 different dates.
For now I only want to show the first date in the bar chart but both domains (com & nl as bars (In the future I will ad more domains to the dates). (If you are interested : The idea is to make a dropdown on the page so the user can select another date and the bars will update).
What I have now is that I can see the axises on my screen so that is working good. But I got this error :
So D3 is expecting a number but it doesn't get one on the Y axis and the heigth as I understand the error...
How can I fix that it is displaying the first date with the domains correct?
The access to the values was incorrect instead of d.values.domainname or d.values.value you have to use d.values[i].domainname and d.values[i].value.
Here you can see your example running.
var data = [
{
"date": "1459468800000", // 1 April 2016
"values": [{
"name": "US",
"value": 72580613,
"domainname": "com"
}, {
"name": "US",
"value": 161645,
"domainname": "nl"
}]
}, {
"date": "1467331200000", // 1 Juli 2016
"values": [{
"name": "US",
"value": 73129243,
"domainname": "com"
}, {
"name": "US",
"value": 166152,
"domainname": "nl"
}]
}
]
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var tooltip = d3.select("body").append("div").attr("class");
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
var colours = d3.scaleOrdinal()
.range(["#6F257F", "#CA0D59"]);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(data.map(function(d, i) { return d.values[i].domainname; }));
y.domain([0, d3.max(data, function(d, i) { return d.values[i].value; })]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(25).tickFormat(function(d) { return parseInt(d / 1000) + "K"; }).tickSizeInner([-width]))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.attr("fill", "#5D6971")
.text("Aantal domeinen");
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("x", function(d, i) { return x(d.values[i].domainname); })
.attr("y", function(d, i) { return y(d.values[i].value); })
.attr("width", x.bandwidth())
.attr("height", function(d, i) {return height - y(d.values[i].value)})
.attr("fill", function(d, i) { return colours(d.values[i].domainname); })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="400" height="400"></svg>
As a note the column nl looks especially small because the values are too different.

bar height issue in bar chart of d3 js

I am trying to draw a Bar Chart using D3.js but its not working successfully. I am trying the following:
var margin = {top:40, right: 40, bottom: 70, left: 40},
width = 500 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width],.5);
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 tip = d3.tip()
.attr('class', 'd3-tip')
.html(function(d) {
return "<strong>Value:</strong> <span style='color:red'>" + d.ActiveCount + "</span>";
})
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.call(tip);
var data = [{"CreateMonth":"5","ActiveCount":"6"},
{"CreateMonth":"6","ActiveCount":"20"},
{"CreateMonth":"7","ActiveCount":"43"},
{"CreateMonth":"8","ActiveCount":"125"},
{"CreateMonth":"9","ActiveCount":"356"},
{"CreateMonth":"10","ActiveCount":"557"}];
x.domain(data.map(function(d) { return d.CreateMonth; }));
y.domain([0, d3.max(data, function(d) { return d.ActiveCount; })]);
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", ".15em")
.attr("transform", "rotate(-65)");
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("Active");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.CreateMonth); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.ActiveCount); })
.attr("height", function(d) { return height - y(d.ActiveCount); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
I have done this bar graph by using this tutorial http://bl.ocks.org/d3noob/8952219, but I am facing the bar(rect) height problem with uneven ActiveCount data in array object. Is some other problem that I am unable to identify?
Also, why it taking y axis as like this as shown below
and also can't determine the height of tooltip position because I think due to some un-ordered data point or some other issue..
Any suggestion should be appreciated
The problem here is that ActiveCount in your data is a string, not a number.
You have to convert it to number. For instance, using the unary plus operator:
data.forEach(function(d) {
d.ActiveCount = +d.ActiveCount;
});
Regarding the axis, you have to remove the default fill. The easiest way is using the CSS (let's also change the ticks):
line, path {
fill: none;
shape-rendering: crispEdges;
stroke: black;
}
Here is your code with those changes (without d3.tip):
var margin = {
top: 40,
right: 40,
bottom: 70,
left: 40
},
width = 500 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
var format = d3.time.format("%b");
var parser = d3.time.format("%m").parse;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .5);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(function(d){
return format(parser(d))
});
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 = [{
"CreateMonth": "5",
"ActiveCount": "6"
}, {
"CreateMonth": "6",
"ActiveCount": "20"
}, {
"CreateMonth": "7",
"ActiveCount": "43"
}, {
"CreateMonth": "8",
"ActiveCount": "125"
}, {
"CreateMonth": "9",
"ActiveCount": "356"
}, {
"CreateMonth": "10",
"ActiveCount": "557"
}];
data.forEach(function(d) {
d.ActiveCount = +d.ActiveCount;
});
x.domain(data.map(function(d) {
return d.CreateMonth;
}));
y.domain([0, d3.max(data, function(d) {
return d.ActiveCount;
})]);
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", ".15em")
.attr("transform", "rotate(-65)");
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("Active");
svg.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.style("fill", "darkorange")
.attr("class", "bar")
.attr("x", function(d) {
return x(d.CreateMonth);
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.ActiveCount);
})
.attr("height", function(d) {
return height - y(d.ActiveCount);
})
line,
path {
fill: none;
shape-rendering: crispEdges;
stroke: black;
}
<script src="https://d3js.org/d3.v3.min.js"></script>

How to make scatterplot highlight data on-click

Dear fellow programmers,
I'm trying to make 2 interactive visualisations. The first one is a worldmap on which the user can click. With every click i want the second visualisation, the scatterplot, to highlight the specific circle/dot which displays the data of the country that was clicked on. Could you please help me?
So far i made sure that when a country is clicked, the country code is returned.
The code of the worldmap:
new Datamap({
scope: 'world',
done: function(datamap) {
datamap.svg.selectAll('.datamaps-subunit').on('click', function(geography) {
console.log(geography.id);
});
},
element: document.getElementById('container1'),
fills: {
A: '#fdd0a2',
B: '#fdae6b',
C: '#fd8d3c',
D: '#f16913',
E: '#d94801',
F: '#a63603',
G: '#7f2704',
defaultFill: 'grey'
},
geographyConfig: {
borderColor: 'rgba(255,255,255,0.3)',
highlightBorderColor: 'rgba(0,0,0,0.5)',
popupTemplate: function(geo, data) {
return data && data.GDP ?
'<div class="hoverinfo"><strong>' + geo.properties.name + '</strong><br/>GDP: <strong> $ ' + data.GDP + '</strong></div>' :
'<div class="hoverinfo"><strong>' + geo.properties.name + '</strong></div>';
}
},
data: {
"ABW": {
"country": "Aruba",
"fillKey": "No data",
"GDP": "No data"
},
"AND": {
"country": "Andorra",
"fillKey": "No data",
"GDP": "No data"
},
....
The Scatterplot code:
function ScatterCorruption(){
// determine parameters
var margin = {top: 20, right: 20, bottom: 200, left: 50},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// determine x scale
var x = d3.scale.linear()
.range([0, width]);
// determine y scale
var y = d3.scale.linear()
.range([height, 0]);
// determine x-axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
// determine y-axis
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// make svg
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 + ")");
// load in data
d3.tsv("ScatCor.txt", function(error, data) {
if (error) throw error;
// convert data
data.forEach(function(d) {
d.GDP = +d.GDP;
d.Variable = +d.Variable;
});
// extract the x labels for the axis and scale domain
// var xLabels = data.map(function (d) { return d['GDP']; })
// x and y labels
x.domain(d3.extent(data, function(d) { return d.GDP; }));
y.domain(d3.extent(data, function(d) { return d.Variable; }));
// make x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.5em")
.attr("dy", ".15em")
.attr("transform", "rotate(-40)" )
// make x-axis label
svg.append("text")
.attr("x", (width -20))
.attr("y", height - 5)
.attr("class", "text-label")
.attr("text-anchor", "end")
.text("GDP");
// make y-axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", -40)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("corruption points")
// make dots
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 2.5)
.attr("cx", function(d) { return x(d.GDP); })
.attr("cy", function(d) { return y(d.Variable); });
// chart title
svg.append("text")
.attr("x", (width + (margin.left + margin.right) )/ 2)
.attr("y", 0)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("font-family", "sans-serif")
.text("Corruption");
}
The data is an tsv file and has the following structure:
Country Name CountryCode GDP Variable
Gambia GMB 850902397.34 72
Guinea-Bissau GNB 1022371991.53 83
Timor-Leste TLS 1417000000.00 72
Seychelles SYC 1422608276.1 45
Liberia LBR 2013000000.00 63
Any help would be greatly appreciated!
Thanks
A quick option would be to add country code as class or id attribute to the circle.
Something like this
// make dots
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", function(d) { return d.CountryCode + " dot"; }) // <--- See this line
.attr("r", 2.5)
.attr("cx", function(d) { return x(d.GDP); })
.attr("cy", function(d) { return y(d.Variable); });
Now, you can use the countryCode you returned to just set another class or directly add color style.
var highlightData = function(country){
// remove any highlights
d3.selectAll('.dot').classed('active', false);
d3.select('.' + country).classed('active',true);
}
Now all you need is some styling that will apply the look you want for the highlighted points
.dot.active {
fill: #bada55;
}
You could also apply the style to the g tag and do more with the active data point.

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

JSON keys for x axis, values for y

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

Categories

Resources