I am trying to draw a scatter plot chart with D3 from a csv file. The csv looks something like this:
location smokeSomeDays allFamPovPct
AL 5% 15%
AK 5% 8%
AZ 4% 13%
AR 6% 14%
CA 4% 12%
CO 4% 8%
CT 4% 8%
DE 4% 9%
DC 5% 14%
...
...
...
I want the Y-Axis to be 'smokeSomeDays' and the X-Axis to be 'allFamPovPct'
Here is my js that evokes the "Cannot read property 'getAttribute' of null" error, even though 'getAttribute' is not mentioned anywhere in the code:
var svgW = 960;
var svgH = 500;
var margin = { top: 20, right: 40, bottom: 60, left: 50 };
var chartWidth = svgW - margin.left - margin.right;
var chartHeight = svgH - margin.top - margin.bottom;
var svg = d3.select("body")
.append("svg")
.attr("width", svgW)
.attr("height", svgH)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv('data.csv', function(data) {
data.forEach(function () {
// Scales
var xScale = d3.scaleLinear()
.domain([
d3.min([0,d3.min(data,function (d) { return d.allFamPovPct })]),
d3.max([0,d3.max(data,function (d) { return d.allFamPovPct })])
])
.range([0,chartWidth])
var yScale = d3.scaleLinear()
.domain([
d3.min([0,d3.min(data,function (d) { return d.smokeSomeDays })]),
d3.max([0,d3.max(data,function (d) { return d.smokeSomeDays })])
])
.range([chartHeight,0])
// X-axis
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(5)
// Y-axis
var yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5)
// Circles
var circles = svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx',function (d) { return xScale(d.allFamPovPct) })
.attr('cy',function (d) { return yScale(d.smokeSomeDays) })
.attr('r','10')
.attr('stroke','black')
.attr('stroke-width',1)
.attr('fill')
svg.append('g')
.attr('class','axis')
.attr('transform', 'translate(0,' + chartHeight + ')')
.call(xAxis)
.append('text') // X-axis Label
.attr('class','label')
.attr('y',-10)
.attr('x',chartWidth)
svg.append('g')
.attr('class', 'axis')
.call(yAxis)
.append('text') // y-axis Label
.attr('class','label')
.attr('transform','rotate(-90)')
.attr('x',0)
.attr('y',5)
});
});
The full error msg looks like this:
Uncaught TypeError: Cannot read property 'getAttribute' of null d3.min.js:4
at dt.ul [as attr] (d3.min.js:4)
at app.js:49
at Array.forEach (<anonymous>)
at app.js:17
at d3.min.js:3
at Object.<anonymous> (d3.min.js:7)
at v.call (d3.min.js:4)
at XMLHttpRequest.e (d3.min.js:7)
But all the lines mentioned in the error looks good to me, maybe I'm missing something.
The code also doesn't produce a scatter plot that I want. It gives me something like this:
|
|
|
|
|
|
|
|
|
O ---------------------------------------
Here is my html, if it helps:
<!DOCTYPE html>
<html>
<head>
<title>Data Journalism</title>
<style>
.chart {
}
.main text {
font: 10px sans-serif;
}
.axis circle, .axis path {
shape-rendering: crispEdges;
stroke: black;
fill: none;
}
circle {
fill: steelblue;
}
</style>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.7.3/d3.min.js">
</script>
</head>
<body>
<div class='content'></div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
Thanks for your help in advance!
The error you get right now is due to the fact that...
.attr("fill")
... is a getter, not a setter. Have in mind that this error is thrown by D3 library, but your console shows the line of your code that is the actual culprit.
However, this is the least of your problems here.
The first big problem is that forEach inside the callback. That is completely unnecessary in a D3 code. Get rid of it.
The second problem is that smokeSomeDays and allFamPovPct are strings, not numbers. You have to convert them to numbers to use your scale. Here, in this example, I'm using a row function:
function(d){
d.smokeSomeDays = +d.smokeSomeDays.split("%")[0];
d.allFamPovPct = +d.allFamPovPct.split("%")[0];
return d;
}
Here is your code with those changes (and just a couple of rows of your CSV):
var csv = `location,smokeSomeDays,allFamPovPct
AL,5%,15%
AK,5%,8%
AZ,4%,13%
AR,6%,14%`;
var svgW = 400;
var svgH = 300;
var margin = {
top: 20,
right: 40,
bottom: 60,
left: 50
};
var chartWidth = svgW - margin.left - margin.right;
var chartHeight = svgH - margin.top - margin.bottom;
var svg = d3.select("body")
.append("svg")
.attr("width", svgW)
.attr("height", svgH)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var data = d3.csvParse(csv, function(d) {
d.smokeSomeDays = +d.smokeSomeDays.split("%")[0];
d.allFamPovPct = +d.allFamPovPct.split("%")[0];
return d;
})
var xScale = d3.scaleLinear()
.domain([
d3.min([0, d3.min(data, function(d) {
return d.allFamPovPct
})]),
d3.max([0, d3.max(data, function(d) {
return d.allFamPovPct
})])
])
.range([0, chartWidth])
var yScale = d3.scaleLinear()
.domain([
d3.min([0, d3.min(data, function(d) {
return d.smokeSomeDays
})]),
d3.max([0, d3.max(data, function(d) {
return d.smokeSomeDays
})])
])
.range([chartHeight, 0])
// X-axis
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(5)
// Y-axis
var yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5)
// Circles
var circles = svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', function(d) {
return xScale(d.allFamPovPct)
})
.attr('cy', function(d) {
return yScale(d.smokeSomeDays)
})
.attr('r', '10')
.attr('stroke', 'black')
.attr('stroke-width', 1)
.attr("fill", "teal");
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + chartHeight + ')')
.call(xAxis)
.append('text') // X-axis Label
.attr('class', 'label')
.attr('y', -10)
.attr('x', chartWidth)
svg.append('g')
.attr('class', 'axis')
.call(yAxis)
.append('text') // y-axis Label
.attr('class', 'label')
.attr('transform', 'rotate(-90)')
.attr('x', 0)
.attr('y', 5)
<script src="https://d3js.org/d3.v4.min.js"></script>
It might be issue with loading library multiple times/initialising same id multiple times
Related
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 trying to create a divergent bar chart which uses time scale(date) as x-axis. I am having trouble using ScaleBands with date, the date labels are overlapping.
This is what I got so far. https://jsfiddle.net/14ch7yeo/ when I use scaleTime, Unfortunately, the graph does not load.
I need to use zoom and brush on this graph.
<!DOCTYPE html>
<meta charset="utf-8">
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var data = [{"Date":"2015-01-02T00:00:00.000Z","Buy":554646.5,"Sell":-406301.3547},{"Date":"2015-02-02T00:00:00.000Z","Buy":565499.5,"Sell":-673692.5697},{"Date":"2015-03-02T00:00:00.000Z","Buy":421954.5,"Sell":-571685.4629},{"Date":"2015-04-02T00:00:00.000Z","Buy":466242.0,"Sell":-457477.7121},{"Date":"2015-05-02T00:00:00.000Z","Buy":350199.7,"Sell":-579682.8772},{"Date":"2015-06-02T00:00:00.000Z","Buy":391035.1,"Sell":-338816.6205},{"Date":"2015-07-02T00:00:00.000Z","Buy":437644.6,"Sell":-502329.557},{"Date":"2015-08-02T00:00:00.000Z","Buy":291978.9,"Sell":-504067.0329},{"Date":"2015-09-02T00:00:00.000Z","Buy":360913.8,"Sell":-489519.6652},{"Date":"2015-10-02T00:00:00.000Z","Buy":505799.1,"Sell":-723353.7089},{"Date":"2015-11-02T00:00:00.000Z","Buy":510691.0,"Sell":-374061.8139},{"Date":"2015-12-02T00:00:00.000Z","Buy":527757.1,"Sell":-597800.0116},{"Date":"2016-01-02T00:00:00.000Z","Buy":564799.1,"Sell":-451779.1593},{"Date":"2016-02-02T00:00:00.000Z","Buy":336533.7,"Sell":-522601.1707},{"Date":"2016-03-02T00:00:00.000Z","Buy":460684.6,"Sell":-643556.0079999999},{"Date":"2016-04-02T00:00:00.000Z","Buy":428388.1,"Sell":-349216.2376},{"Date":"2016-05-02T00:00:00.000Z","Buy":525459.5,"Sell":-597258.4075},{"Date":"2016-06-02T00:00:00.000Z","Buy":677659.1,"Sell":-513192.107},{"Date":"2016-07-02T00:00:00.000Z","Buy":365612.8,"Sell":-287845.8089},{"Date":"2016-07-03T00:00:00.000Z","Buy":358775.2,"Sell":-414573.209}]
var parseTime = d3.utcParse("%Y-%m-%dT%H:%M:%S.%LZ");
data.forEach(d => {
d["Date"] = parseTime(d["Date"]);
})
var series = d3.stack()
.keys(["Buy", "Sell"])
.offset(d3.stackOffsetDiverging)
(data);
var svg = d3.select("svg"),
margin = {top: 20, right: 30, bottom: 30, left: 60},
width = +svg.attr("width"),
height = +svg.attr("height");
var x = d3.scaleBand()
.domain(data.map(function(d) { return d['Date']; }))
.rangeRound([margin.left, width - margin.right])
.padding(0.1);
var y = d3.scaleLinear()
.domain([d3.min(series, stackMin), d3.max(series, stackMax)])
.rangeRound([height - margin.bottom, margin.top]);
var z = d3.scaleOrdinal()
.range(["green","red"]);
svg.append("g")
.selectAll("g")
.data(series)
.enter().append("g")
.attr("fill", function(d) { return z(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("width", x.bandwidth)
.attr("x", function(d) { return x(d.data["Date"]); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
svg.append("g")
.attr("transform", "translate(0,"+ (height-margin.top) + ")")
.call(d3.axisBottom(x));
svg.append("g")
.attr("transform", "translate(" + margin.left + ",0)")
.call(d3.axisLeft(y));
function stackMin(serie) {
return d3.min(serie, function(d) { return d[0]; });
}
function stackMax(serie) {
return d3.max(serie, function(d) { return d[1]; });
}
</script>
d3.scaleTime has to be treated differently on a number of fronts.
The scale doesn't take padding as an argument:
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) { return d.Date; }))
.rangeRound([margin.left, width - margin.right]);
Time is continuous rather than discrete, so the widths of the bars need to be calculated manually, as a ratio of rect and series.length. I got this to work, but maybe you want something more elegant:
.attr("width", width/series.length - 450)
I've got as far as making my bar chart with an x axis but can't work out why I can't get the bars and the tick spacing on the x axis to line up exactly. At the moment the ticks are slightly to the right of center of the bar.
csv file example:
crop,records
CASSAVA,350
MAIZE,226
TOMATOES,137
code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Abbie's attempt at D3</title>
<script type="text/javascript" src="https://d3js.org/d3.v4.min.js"></script>
<style>
svg {
background-color: rgba(227, 227, 227, 0.97);
}
.bar {
margin: 20px;
}
</style>
</head>
<body>
<script type="text/javascript">
var margin = {top: 20, right: 30, bottom: 100, left: 40};
var w = 500 - margin.left - margin.right;
var h = 500 - margin.top - margin.bottom;
var barPadding = 5;
var cropData;
// load the csv file
d3.csv("crops.csv", function(error, data) {
data.forEach(function(d) {
d.records = +d.records;
});
cropData = data;
var arrayLength = cropData.length;
var yMax = d3.max(cropData, function(d) {
return d.records;
});
var yScale = d3.scaleLinear()
.domain([0, yMax])
.range([h, 0]);
var xScale = d3.scaleBand()
.domain(cropData.map(function(d) {
return d.crop;
}))
.rangeRound([0, w]);
// create the svg
var svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// bars
svg.selectAll("rect")
.data(cropData)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * xScale.bandwidth() + 5;
})
.attr("y", function(d) {
return yScale(d.records);
})
.attr("width", xScale.bandwidth() - 5)
.attr("height", function(d) {
return h - yScale(d.records);
})
.attr("fill", "teal")
.attr("class", "bar");
// x Axis
var xAxis = d3.axisBottom(xScale);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function(d) {
return "rotate(-60)"
});
// y Axis
var yAxis = d3.axisLeft(yScale);
svg.append("g")
.call(yAxis);
</script>
</body>
Am I doing it right with i * xScale.bandwidth() + 5 and xScale.bandwidth() - 5 in the x and width attributes of rect or is this not the correct way to do it? How do I change the spacing of the ticks on the x axis if I change the bars? They both use xScale so I feel it must be something to do with that.
I'd make the following changes.
First, introduce a padding to your xScale:
var xScale = d3.scaleBand()
.domain(cropData.map(function(d) {
return d.crop;
}))
.rangeRound([0, w])
.padding(0.1);
This will space the bars by a percent of the bandWidth.
Second, place your bars using your xScale:
svg.selectAll("rect")
.data(cropData)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(d.crop); //<-- place by xScale
})
.attr("y", function(d) {
return yScale(d.records);
})
.attr("width", xScale.bandwidth()) //<-- no -5 padding takes care of breaks
Runnable code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Abbie's attempt at D3</title>
<script type="text/javascript" src="https://d3js.org/d3.v4.min.js"></script>
<style>
svg {
background-color: rgba(227, 227, 227, 0.97);
}
.bar {
margin: 20px;
}
</style>
</head>
<body>
<script type="text/javascript">
var margin = {
top: 20,
right: 30,
bottom: 100,
left: 40
};
var w = 500 - margin.left - margin.right;
var h = 500 - margin.top - margin.bottom;
var barPadding = 5;
var cropData;
// load the csv file
//d3.csv("crops.csv", function(error, data) {
var data = [{
"crop": "CASSAVA",
"records": "350"
}, {
"crop": "MAIZE",
"records": "226"
}, {
"crop": "TOMATOES",
"records": "137"
}];
data.forEach(function(d) {
d.records = +d.records;
});
cropData = data;
var arrayLength = cropData.length;
var yMax = d3.max(cropData, function(d) {
return d.records;
});
var yScale = d3.scaleLinear()
.domain([0, yMax])
.range([h, 0]);
var xScale = d3.scaleBand()
.domain(cropData.map(function(d) {
return d.crop;
}))
.rangeRound([0, w])
.padding(0.1);
// create the svg
var svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// bars
svg.selectAll("rect")
.data(cropData)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(d.crop);
})
.attr("y", function(d) {
return yScale(d.records);
})
.attr("width", xScale.bandwidth())
.attr("height", function(d) {
return h - yScale(d.records);
})
.attr("fill", "teal")
.attr("class", "bar");
// x Axis
var xAxis = d3.axisBottom(xScale);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function(d) {
return "rotate(-60)"
});
// y Axis
var yAxis = d3.axisLeft(yScale);
svg.append("g")
.call(yAxis);
// });
</script>
</body>
I've got the following piece of code that takes a mouse movement on an SVG container and will shrink/grow the height/width of a visualization based on a mouse movement. When the user has a mouse movement in the x direction, the bars on the chart appear jittery: the x attribute will increase by, 2 or three and then revert to what it was previously: the rects on the page will scoot right a couple pixels, and then snap back to their original position.
Is there a bug with how I'm changing the ordinal scale? Or should I use a transform instead of manipulating the X value?
'use strict';
var d3 = require("d3");
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var chartData;
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 + ")");
let render = (e,data)=>{
width += d3.event ? d3.event.movementX : 0; //changing x axis here
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([data.height, 0]);
//
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
//
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10, "%")
.tickSize(1);
var yAxisEl = 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");
var bars = svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) {
console.log(x(d.letter));
var currX = x(d.letter); //sometimes gives wrong valuse
return currX;
})
.attr("width", function(){
return x.rangeBand();
})
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return Math.abs(height - y(d.frequency)); })
}
let rerender=(data)=>{
d3.select("svg").select("g").selectAll("*").remove();
render(null,data);
}
d3.tsv("data.tsv", type, function(error, data) {
if (error) throw error;
chartData = data;
chartData.height = height;
chartData.width = width;
render(error,chartData);
});
d3.selectAll('svg').on('mousemove',function(){
if(chartData){
chartData.height += d3.event.movementY;
rerender(chartData);
}
});
data
letter frequency
A .08167
B .01492
C .02782
D .04253
E .12702
F .02288
G .02015
I made the rerender function inside time out (hope this fixes the problem you referring)
var rerender = (data) => {
if (myVar){
clearTimeout(myVar);//clear timeout if called before 1 millisecond
}
myVar = setTimeout(function(){
d3.select("svg").select("g").selectAll("*").remove();
render(null, data);
}, 1);
}
working code here
I currently have a working scatter plot that I make using this
var data = (an array of arrays with two integers in each array)
var margin = {top: 20, right: 15, bottom: 60, left: 60}
, width = 300 - margin.left - margin.right
, height = 250 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d[0]; })])
.range([ 0, width ]);
var y = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d[1]; })])
.range([ height, 0 ]);
var chart = d3.select('.scatterGraph')
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main')
// draw the x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.ticks(5);
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(xAxis);
// draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(yAxis);
var g = main.append("svg:g");
g.selectAll("scatter-dots")
.data(data)
.enter().append("svg:circle")
.attr("cx", function (d,i) { return x(d[0]); } )
.attr("cy", function (d) { return y(d[1]); } )
.attr("r", 2);
I was wondering how I could add a line graph (or alternatively another scatter plot) to this graph. I'm very new to d3 so I'm currently lost on how to do it. For example I would just want to add a line described by a function y = 2t where t is the x axis of the scatterplot. Thanks!
If it is as simple as a line described by a function y=2t you can just append a line element to your chart in this case main like this, assuming that your width is at least greater than twice your height
main.append("line").attr("x1", 0).attr("x2", height/2)
.attr("y1", height).attr("y2", 0);
But if you have a line that connected through multiple points, you will need to add a path element to your svg, and use d3.svg.line() function to generate its d attribute. So something like this,
var lineFunction = d3.svg.line().x(function (d) { x(d[0])}; )
.y(function (d) { y(d[1])}; );
var gLine = main.append("g");
gLine.append("path").attr("d", lineFunction(data));
For another scatter plot, you can reuse
var g = main.append("svg:g");
g.selectAll("scatter-dots")
.data(data2)
.enter().append("svg:circle")
.attr("cx", function (d,i) { return x(d[0]); } )
.attr("cy", function (d) { return y(d[1]); } )
.attr("r", 2);
but with a different set of data, and different accessor functions or scales if needed.