I'm using d3js to build a bar chart from a .csv file, and as my programming knowledge is not good, I get an example from the web and start to working in. I can get a specific value from the .csv and draw the chart, but I would like to implement a dropdown list to select a specific value from the .csv as showed in the image below.
Imagem:
I'm unable to load this data from .csv. It doesn't seem complicated, but I tried it in some ways and it didn't work out. Could someone give me any tips on how to do this?
Best regards!
.csv file
country,year,hdi,gdp,unemp
Australia,2000,45,5.6,9
Austria,2000,54,6.1,8
Canada,2000,23,4.5,6
...
...
HTML + JS
<html>
<head>
<style>
.bar {
fill: steelblue;
}
.bar:hover {
fill: brown;
}
</style>
<script src="//d3js.org/d3.v5.min.js"></script>
</head>
<body>
<div>
<select id="S_indicators"></select>
</div>
<svg width="560" height="300"></svg>
<script>
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,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parseTime = d3.timeParse("%d-%b-%y");
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
d3.csv("output.csv").then(function (data) {
var allGroup = ["gdp","hdi","unemp"]
d3.select("#S_indicators")
.selectAll('myOptions')
.data(allGroup)
.enter()
.append('option')
.text(function (d) { return d; })
.attr("value", function (d) { return d; })
x.domain(data.map(function (d) {
return d.year;
}));
y.domain([0, d3.max(data, function (d) {
return Math.max(d.unemp);
})]);
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
g.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function (d) {
return x(d.year);
})
.attr("y", function (d) {
return y(Number(d.unemp));
})
.attr("width", x.bandwidth())
.attr("height", function (d) {
return height - y(Number(d.unemp));
});
});
</script>
</body>
</html>
-Add a dropdown to the html file (I named it dd):
<!DOCTYPE html>
<html lang="en">
<title>My Scatter2</title>
<body>
<select id="dd"></select>
<div id="wrap"></div>
<script src="https://d3js.org/d3.v5.js"></script>
<script src="scatterplot.js"></script>
<link rel='stylesheet' href='styles.css'>
</body>
</html>
Populate it in D3 via:
var specs = ['A','B','C']
var dropdown = d3.select('#dd')
dropdown.selectAll('option')
.data(specs)
.enter().append("option")
.attr("value",d=>d)
.text(d => d)
Or if you want an observeable example:
https://observablehq.com/#kickout/basic-scatterplot
Related
My code now looks like this:
<!DOCTYPE html>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
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,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parseTime = d3.timeParse("%d-%b-%y");
var x = d3.scaleTime()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
d3.tsv("data.tsv", function(d) {
d.date = parseTime(d.date);
d.close = +d.close;
return d;
}, 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; }));
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.select(".domain")
.remove();
g.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Price ($)");
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
});
</script>
Now I would like to redo it to upload CSV file right there on this page and to read data from there.
The file is going to be a table with two columns: first one is going to be a column with dates, but the second one - with numbers, with which the graph will be built.
Now I also tried doing it like this:
<!DOCTYPE html>
<html lang="lv"><head>
<title>HTML formas</title>
<meta charset="UTF-8">
</head>
<body>
<p>Norādi datni</p>
<input type="file" id="datne" onchange="datnesApstrade()">
<p>Ielasītie dati</p>
<pre id="rezultats"></pre>
<script>
function datnesApstrade(){
var fr=new FileReader();
fr.onload=function(){
apstradat( fr.result );
}
f = document.getElementById("datne");
fr.readAsText(f.files[0]);
}
function apstradat( dati ) {
document.getElementById('rezultats')
.textContent=dati;
var rindas = dati.split("\n");
//document.getElementById('rezultats').textContent="Rindas:"+rindas.length;
//alert( "In file is " + rindas.length + " lines" );
var r, i, c, co2 = [];
// pirmā rindā ir tabulas galva, tāpēc to nelasām
for( r=1; r<rindas.length; r=r+1 ) {
c = rindas[r].split(",");
co2.push( Number(c[1]) );
}
var rl = rindas.length-2;
for (i=1; i<rl; i=i+1){
document.getElementById('rezultats').textContent=co2[i];
}
}
</script>
</body></html>
But I don't know how to modify it to achieve my goal. Please help!
I am a beginner, I am learning d3.js but I am unable to figure out on how to draw a graph or code according to the data is in json format from api.
Here is one of the things I have tried but I am unable to change it according to the new data in another api. Can anybody help me?
where do I make the changes?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="960" height="500"></svg>
</head>
<style>
body {
text-align: center;
margin-top: 5em;
background-color: #74b9ff;
}
h1 {
color: snow;
}
</style>
<body>
<h1>Bitcoin Prices in U.S. Dollars</h1>
<script>
var url = "https://min-api.cryptocompare.com/data/histoday?fsym=BTC&tsym=USD&limit=200&aggregate=3&e=CCCAGG";
d3.json(url).get(function(error, d) {
var data = d.Data;
data.forEach(function(d){ d.time = new Date(d.time * 1000) });
if (error) throw error;
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,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleTime()
.range([0, width])
var y = d3.scaleLinear()
.range([height, 0]);
var line = d3.line()
.x(function(d) { return x(d.time); })
.y(function(d) { return y(d.close); });
x.domain(d3.extent(data, function(d) { return d.time; }));
y.domain(d3.extent(data, function(d) { return d.close; }));
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.attr("stroke-width", 2)
.attr("fill", "none")
.style("font-size",".8em");
g.append("g")
.call(d3.axisLeft(y))
.attr("stroke-width", 2)
.style("font-size",".8em")
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 20)
.attr("text-anchor", "end")
.attr("font-size", "1.2em")
.text("Price ($)")
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "#ffeaa7")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 2)
.attr("d", line);
});
</script>
</body>
</html>
I get the correct output for the above mentioned code but I want to change the api to https://blockchain.info/ticker
where could I make the changes to make it work?
Because it is the convertion rate of bitcoin compared to other coins it does not make sense to plot them all in one graph. The JPY would crush all the other bars.
Why buy/sell bitcoin using different coins if they just are conversions given the current rate.
If you draw a single coin values you get the following graph
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.bar {
fill: steelblue;
}
.bar:hover {
fill: brown;
}
.axis--x path {
display: none;
}
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("https://blockchain.info/ticker", function(error, data) {
if (error) throw error;
data = data.EUR;
var keys = ["15m", "last", "buy", "sell"];
x.domain(keys);
y.domain([0, d3.max(keys, function(k) { return data[k]; })]);
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))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("price");
g.selectAll(".bar")
.data(keys)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(k) { return x(k); })
.attr("y", function(k) { return y(data[k]); })
.attr("width", x.bandwidth())
.attr("height", function(k) { return height - y(data[k]); });
});
</script>
Also not useful. Because all the variation is in the top few pixels.
What you need is a way to record the values from the JSON file over time for a particular coin and graph that like your original dataset.
Can be done by fetching the data every x minutes and then modify the graph with the enter/exit/remove data() call or just redraw the graph like this.
Place comments before the DEBUG sections for the real data. And uncomment the following line
//setInterval(getData, 5 * 60 * 1000);
Here I generate dummy data every 5 seconds for the demo.
To prevent out of memory data length is limited to 1000 samples.
Edit
It now shows the date of the sample on the x-axis.
<!DOCTYPE html>
<style>
.p15m { stroke: steelblue;}
.pbuy { stroke: red;}
.plast { stroke: green;}
.psell { stroke: orange;}
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
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 data = [];
var x = d3.scaleTime()
.range([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var line = d3.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); });
function getData() {
//DEBUG
data.push( {"15m": Math.random()*100 + 100, "last": Math.random()*100 + 100, "buy": Math.random()*100 + 100, "sell": Math.random()*100 + 100, "date": new Date() } );
updateGraph();
return;
// DEBUG END
d3.json("https://blockchain.info/ticker", function(error, dataNew) {
if (error) throw error;
var d = dataNew.EUR;
d.date = new Date();
data.push();
if (data.length > 1000) data = data.shift();
updateGraph();
});
}
getData();
setTimeout(getData, 5000);
//DEBUG
setInterval(getData, 5 * 1000);
//DEBUG END
//setInterval(getData, 5 * 60 * 1000);
function updateGraph() {
if (data.length < 2) return;
svg.select("g").remove(); // clean the graph
var keys = ["15m", "last", "buy", "sell"];
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(d3.extent(data, d => d.date));
var flat = [];
data.map( d => keys.map(k => d[k]) ).forEach(e => { flat = flat.concat(e); });
y.domain(d3.extent(flat , function(d) { return d; }));
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.select(".domain")
.remove();
g.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Price (EUR)");
g.selectAll("g.key")
.data(keys)
.enter()
.append("g")
.attr("class", d => "key p" + d )
.append("path")
.datum(k => data.map( (d, i) => [d.date, d[k]]))
.attr("fill", "none")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
}
</script>
I am using D3.js to showcase my data. However, I am unable to get two different objects to not overlap. For example, the code below shows a line graph and a bar graph. I am using code from https://github.com/d3/d3/wiki/Gallery for this example to show my issue. The line graph code is from https://bl.ocks.org/mbostock/3883245. The bar graph code is from https://bl.ocks.org/mbostock/3885304. I tried using as shown in this example http://www.d3noob.org/2013/07/arranging-more-than-one-d3js-graph-on.html, but it did not work. I also made sure they both used the same version of d3.js. The data is from two tsv files that are on the links above for the line and bar graph. Any help would be appreciated!
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.bar {
fill: steelblue;
}
.bar:hover {
fill: brown;
}
.axis--x path {
display: none;
}
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="lineg"></div>
<div id="barg"></div>
<lineg width="960" height="500"></lineg>
<barg width="960" height="500"></barg>
<script>
var svga = d3.select("#lineg"),
margina = {top: 20, right: 20, bottom: 30, left: 40},
widtha = +svga.attr("width") - margina.left - margina.right,
heighta = +svga.attr("height") - margina.top - margina.bottom;
var xa = d3.scaleBand().rangeRound([0, widtha]).padding(0.1),
ya = d3.scaleLinear().rangeRound([heighta, 0]);
var ga = svga.append("g")
.attr("transform", "translate(" + margina.left + "," + margina.top + ")");
d3.tsv("bardata.tsv", function(d) {
d.frequency = +d.frequency;
return d;
}, function(error, data) {
if (error) throw error;
xa.domain(data.map(function(d) { return d.letter; }));
ya.domain([0, d3.max(data, function(d) { return d.frequency; })]);
ga.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + heighta + ")")
.call(d3.axisBottom(xa));
ga.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(ya).ticks(10, "%"))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Frequency");
ga.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return xa(d.letter); })
.attr("y", function(d) { return ya(d.frequency); })
.attr("width", xa.bandwidth())
.attr("height", function(d) { return heighta - ya(d.frequency); });
});
</script>
<script>
var svgb = d3.select("barg"),
marginb = {top: 20, right: 20, bottom: 30, left: 50},
widthb = +svgb.attr("width") - marginb.left - marginb.right,
heightb = +svgb.attr("height") - marginb.top - marginb.bottom,
gb = svgb.append("g").attr("transform", "translate(" + marginb.left + "," + marginb.top + ")");
var parseTime = d3.timeParse("%d-%b-%y");
var xb = d3.scaleTime()
.rangeRound([0, widthb]);
var yb = d3.scaleLinear()
.rangeRound([heightb, 0]);
var line = d3.line()
.x(function(d) { return xb(d.date); })
.y(function(d) { return yb(d.close); });
d3.tsv("linedata.tsv", function(d) {
d.date = parseTime(d.date);
d.close = +d.close;
return d;
}, function(error, data) {
if (error) throw error;
xb.domain(d3.extent(data, function(d) { return d.date; }));
yb.domain(d3.extent(data, function(d) { return d.close; }));
gb.append("g")
.attr("transform", "translate(0," + heightb + ")")
.call(d3.axisBottom(xb))
.select(".domain")
.remove();
gb.append("g")
.call(d3.axisLeft(yb))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Price ($)");
gb.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
});
</script>
In your code barg and lineg are just divs:
<div id="lineg"></div>
<div id="barg"></div>
Instead of that, they should be SVG elements, like this:
<svg id="barg" width="960" height="500"></svg>
<svg id="lineg" width="960" height="500"></svg>
Or, alternatively, append an SVG in your selection:
var svga = d3.select("#lineg").append("svg");
var svgb = d3.select("#barg").append("svg");
However, in that case, you cannot use the getters to get the width and height of the SVGs.
Finally, there is no HTML tag named <lineg> or <barg>.
I'm using d3.js for charting, I'm getting this error while I want to load custom data from local system.
Failed to load file:///C:/Users/foo/Desktop/d3/barChart/data.tsv:
Cross origin requests are only supported for protocol schemes: http,
data, chrome, chrome-extension, https.
Inside d3 folder there is the index.html and the data.tsv.
I have installed WAMP Server and it's active.
Update
index.html
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.bar {
fill: steelblue;
}
.bar:hover {
fill: brown;
}
.axis--x path {
display: none;
}
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.tsv("data.tsv", function(d) {
d.frequency = +d.frequency;
return d;
}, function(error, data) {
if (error) throw error;
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
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(10, "%"))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Frequency");
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.letter); })
.attr("y", function(d) { return y(d.frequency); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.frequency); });
});
</script>
data.tsv
letter frequency
A .08167
B .01492
C .02782
D .04253
I'm trying to mimic the simple blocks example here, however I only seem to get the x and y axis to draw, no sign of the line. I'm loading data differently, via an array rather than a tsv file, though I've combed through a few times and I think everything is feeding into the scales and the line the same way. Non-working JSBin here:
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<svg width="960" height="500"></svg>
<script>
var data = [["2014-12-31", 0.31999999999999],["2014-11-30", 2.71],["2014-10-31", -4.05],["2014-09-30", 4.22],["2014-08-31", 2.17],["2014-07-31", 5.36],["2014-06-30", 3.99],["2014-05-31", 3.52],["2014-04-30", -.46],["2014-03-31", -8.22],["2014-02-28", 5.89]]
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,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parseTime = d3.timeParse("%Y-%m-%d");
var x = d3.scaleTime().rangeRound([0, width]);
var y = d3.scaleLinear().rangeRound([height, 0]);
var data = data.map(function(d){
return [parseTime(d[0]), +d[1]]
});
var line = d3.line()
.x(function(d){
return d[0];
})
.y(function(d){
return d[1];
});
x.domain(d3.extent(data, function(d) { return d[0]; }));
y.domain(d3.extent(data, function(d) { return d[1]; }));
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.select(".domain")
.remove();
g.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 50)
.attr("dy", "0.9em")
.attr("text-anchor", "end")
.text("Price ($)");
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 3)
.attr("d", line);
</script>
</body>
</html>
anything obvious that I'm doing wrong?
The issue is that you are not scaling your data to fit properly within your svg coordinate space:
var line = d3.line()
.x(function(d){
return d[0];
})
.y(function(d){
return d[1];
});
This section of code sets the plotted coordinates of your data, and you are currently using your data values as your x and y svg values without any scaling. You need to scale it according to your scale:
var line = d3.line()
.x(function(d){
console.log(x(d[0])); return x(d[0]);
})
.y(function(d){
return y(d[1]);
});
This will allow your graph to draw as intended.