My data points and the values in the scaleBand y axis are not aligned. I am not able to align them properly, when I read the documentation, saw that by default the alignment is 0.5 and that's why my data points are plotted between the two points in the axis. But I tried to override the alignment my giving the alignment as 0, but there seems to be no change.
The following is my code.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>D3 v4 - linechart</title>
<style>
#graph {
width: 900px;
height: 500px;
}
.tick line {
stroke-dasharray: 2 2 ;
stroke: #ccc;
}
.y path{
fill: none;
stroke: none;
}
</style>
</head>
<body>
<div id="graph"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.1.1/d3.min.js"></script>
<script>
!(function(){
"use strict"
var width,height
var chartWidth, chartHeight
var margin
var svg = d3.select("#graph").append("svg")
var axisLayer = svg.append("g").classed("axisLayer", true)
var chartLayer = svg.append("g").classed("chartLayer", true)
var xScale = d3.scaleLinear()
var yScale = d3.scaleBand()
var align = 0
//d3.tsv("data1.tsv", cast, main)
d3.json("http://localhost/d32.json",cast)
//データの方変換
function cast(data) {
console.log("got it");
data.forEach(function(data) {
console.log(data.Letter);
data.Letter = data.Letter;
data.Freq = +data.Freq;
});
main(data);
}
function main(data) {
console.log("in main");
setSize(data)
drawAxis()
drawChart(data)
}
function setSize(data) {
width = document.querySelector("#graph").clientWidth
height = document.querySelector("#graph").clientHeight
margin = {top:40, left:100, bottom:40, right:0 }
chartWidth = width - (margin.left+margin.right+8)
chartHeight = height - (margin.top+margin.bottom)
svg.attr("width", width).attr("height", height)
axisLayer.attr("width", width).attr("height", height)
chartLayer
.attr("width", chartWidth)
.attr("height", chartHeight)
.attr("transform", "translate("+[margin.left, margin.top]+")")
xScale.domain([0, d3.max(data, function(d) { return d.Freq; })]).range([0,chartWidth])
yScale.domain(data.map(function(d) { return d.Letter; })).range([chartHeight, 0]).align(1)
}
function drawChart(data) {
console.log("in drawChart");
var t = d3.transition()
.duration(8000)
.ease(d3.easeLinear)
.on("start", function(d){ console.log("transiton start") })
.on("end", function(d){ console.log("transiton end") })
var lineGen = d3.line()
.x(function(d) { return xScale(d.Freq) })
.y(function(d) { return yScale(d.Letter) })
.curve(d3.curveStepAfter)
var line = chartLayer.selectAll(".line")
.data([data])
line.enter().append("path").classed("line", true)
.merge(line)
.attr("d", lineGen)
.attr("fill", "none")
.attr("stroke", "blue")
.attr("stroke-width","2px")
.attr("stroke-dasharray", function(d){ return this.getTotalLength() })
.attr("stroke-dashoffset", function(d){ return this.getTotalLength() })
chartLayer.selectAll(".line").transition(t)
.attr("stroke-dashoffset", 0)
chartLayer.selectAll("circle").classed("circle",true)
.data(data)
.enter().append("circle")
.attr("class", "circle")
.attr("fill","none")
.attr("stroke","black")
.attr("cx", function(d) { return xScale(d.Freq); })
.attr("cy", function(d) { return yScale(d.Letter); })
.attr("r", 4);
chartLayer.selectAll(".logo").transition(t)
.attr("stroke-dashoffset", 0)
}
function drawAxis(){
var yAxis = d3.axisLeft(yScale)
.tickSizeInner(-chartWidth)
axisLayer.append("g")
.attr("transform", "translate("+[margin.left, margin.top]+")")
.attr("class", "axis y")
.call(yAxis);
var xAxis = d3.axisBottom(xScale)
axisLayer.append("g")
.attr("class", "axis x")
.attr("transform", "translate("+[margin.left, chartHeight+margin.top]+")")
.call(xAxis);
}
}());
</script>
</body>
</html>
The output is shown here:
The band scale is the wrong tool in this situation. The main reason is that a band scale has an associated bandwidth.
You can tweak the paddingInner and paddingOuter values of the band scale to give you the expected result. However, the easiest solution is using a point scale instead. Point scales:
...are a variant of band scales with the bandwidth fixed to zero. (emphasis mine)
So, it should be:
var yScale = d3.scalePoint()
Related
I am new to D3 library for Data Visualisation.
I am trying to create a vertical legend.
And below is my implementation.
We can see there is huge gap between the column of rects(are on extreme right) and vertical ticks.
I guess, I am missing something in g.call because of my limited knowledge.
Can someone please, what mistake I am doing ?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.counties {
fill: none;
}
.states {
fill: none;
stroke: #fff;
stroke-linejoin: round;
}
</style>
<svg width="1260" height="600"></svg>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
</head>
<body>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var poverty = d3.map();
var path = d3.geoPath();
var x = d3.scaleLinear()
//.domain([1, 10])
.domain([1, 10])
.rangeRound([600, 860]);
//console.log("x:==>", x);
var y = d3.scaleLinear()
//.domain([1, 10])
.domain([1, 10])
.rangeRound([15, 160]);
var color = d3.scaleThreshold()
//.domain(d3.range(2, 10))
.domain(d3.range(2, 10))
.range(d3.schemeBlues[9]);
var g = svg.append("g")
.attr("class", "key")
//.attr("transform", "translate(0,40)");
.attr("transform", "translate(350,40)");
g.selectAll("rect")
.data(color.range().map(function(d) {
d = color.invertExtent(d);
if (d[0] == null) d[0] = x.domain()[0];
if (d[1] == null) d[1] = x.domain()[1];
return d;
}))
.enter().append("rect")
/*.attr("height", 8)
.attr("x", function(d) { return x(d[0]); })
.attr("width", function(d) { return x(d[1]) - x(d[0]); })
.attr("fill", function(d) { return color(d[0]); })*/
.attr("height", 15)
.attr("x", 600)
.attr("y", function(d) { return y(d[0]); })
.attr("width", function(d) { return x(d[1]) - x(d[0]); })
.attr("fill", function(d) { return color(d[0]); }) ;
g.append("text")
.attr("class", "caption")
/*.attr("x", x.range()[0])
.attr("y", -6)*/
.attr("x",x.range()[0])
.attr("y", -6)
.attr("fill", "#000")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("Poverty rate");
g.call(d3.axisRight(y)
//.tickSize(13)
.tickFormat(function(x, i) { return i ? 2*x : 2*x + "%"; })
.tickValues(color.domain()))
.select(".domain")
.remove();
var promises = [
d3.json("https://snippetnuggets.com/TnD/us.json"),
d3.csv("https://snippetnuggets.com/TnD/county_poverty.csv", function(d) { poverty.set(d.id, +d.rate); console.log(d); })
]
Promise.all(promises).then(ready)
function ready([us]) {
svg.append("g")
.attr("class", "counties")
.selectAll("path")
.data(topojson.feature(us, us.objects.counties).features)
.enter().append("path")
.attr("fill", function(d) { return color(d.rate = poverty.get(d.id)); })
.attr("d", path)
.append("title")
.text(function(d) { return d.rate + "%"; });
svg.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("class", "states")
.attr("d", path);
}
</script>
</body>
</html>
Your large gap between your axis ticks/labels and legend rects is there because you set x to 600 on your rects (.attr("x", 600)), which means you position the rects 600 pixels to the right, relative to the rects' parent container.
What happens is that first you append a g element, which you translate 350 pixels horizontally to the right (and 40 vertically downwards). When you later append rects to this g element, the rects are positioned relative to the g elements position. Therefore, setting the x attribute on the rects to 600 means in effect that you position the rects 950 pixels (350 + 600) to the right of the left side of the SVG.
To fix this, you should lower your x attribute on the rects. Negative values are valid too.
Check the reference for SVG rect elements here: SVG
So I need to read a .CSV file using a HTML webpage loaded locally (e.g with file:///) and plot the contants on a graph using d3.js. So far I have attempted to join two examples together without success...
If you open it, it is able to read the contents of the local csv file, but for some reason the graph does not appear.
Any help much appreciated!
<!DOCTYPE html>
<style> /* set the CSS */
body { font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="chrome=1" />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<!--this doesn't seem to help-->
<meta http-equiv="Access-Control-Allow-Origin" content="*"/>
<title>Process local CSV file</title>
<script src="d3.js" charset="utf-8"></script>
</head>
<body>
<script>
var rowToHtml = function( row ) {
var result = "";
for (key in row) {
result += key + ": " + row[key] + "<br/>"
}
return result;
}
var previewCsvUrl = function( csvUrl ) {
d3.csv( csvUrl, function( rows ) {
d3.select("div#preview").html(
"<b>First row:</b><br/>" + rowToHtml(rows[0]));
})
}
d3.select("html")
.style("height","100%")
d3.select("body")
.style("height","100%")
.style("font", "12px sans-serif")
.append("input")
.attr("type", "file")
.attr("accept", ".csv")
.style("margin", "5px")
.on("change", function() {
var file = d3.event.target.files[0];
if (file) {
var reader = new FileReader();
reader.onloadend = function(event1) {
var dataUrl = event1.target.result;
// The following call results in an "Access denied" error in IE.
previewCsvUrl(dataUrl);
rendergraph(dataUrl);
};
reader.readAsDataURL(file);
}
})
d3.select("body")
.append("div")
.attr("id", "preview")
.style("margin", "5px")
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); });
var rendergraph = function( url1 ) {
d3.csv(url1, 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>
</body>
</html>
svg is being selected here but its not there in dom:
var svg = d3.select("svg"),
Either add svg element in the html or append to body using d3 by replacing the above code with:
var svg = d3.select("body").append('svg').attr('width', 300).attr('height', 300),
EDIT:
I have edited my code. Now you can directly run the code inside stackoverflow.
Last day, I started with D3 and having some problems in fixing the dynamic axis. I am new to D3 :)
I am trying to build the following.
I am trying to plot some 2d streaming data with D3. Data comes in through web sockets. So, as the data comes in I plot the new points. I have been somewhat succesful in that but my axis are not behaving as expected.
As the new data comes in, I rescale my x and y axis. Forexample if (20,100) comes in the following plot then I will scale my y-axis to accomadate 100. But if you look closely the point where x and y axis meet is not on 0. Initially it was on (0,0) but after rescaling it went way below. How can I fix the center a (0,0) while dynamically rescaling my axis ?
Can you please help me. Here is my code.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.line {
fill: none;
stroke: #000;
stroke-width: 1.5px;
}
div.bar {
display: inline-block;
width: 20px;
height: 75px; /* We'll override this later */
background-color: teal;
}
/* Format X and Y Axis */
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
</style>
<svg width="960" height="500"></svg>
<br/>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var dataset = []; // Initialize empty array
var numDataPoints = 15; // Number of dummy data points
for(var i=0; i<numDataPoints; i++) {
dataset.push([getRandomInt(-80,80), getRandomInt(-60,60)]); // Add new number to array
}
var width = 1020;
var height = 840;
var xScale = d3.scale.linear().domain([d3.min(dataset, function(d) { return d[0]; } ), d3.max(dataset, function(d) { return d[0]; })])
.range([20, width - 20 * 2])
.nice();
var yScale = d3.scale.linear().domain([d3.min(dataset, function(d) { return d[1]; } ), d3.max(dataset, function(d) { return d[1]; })])
.range([height - 20, 20])
.nice();
var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
var yAxis = d3.svg.axis().scale(yScale).orient("left");
var svg = d3.select("svg")
.attr("height",height)
.attr("width", width)
.append("g")
.attr("transform","translate(20,20)")
.attr("width", width- 40)
.attr("height", height- 40);
// x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + yScale(0) + ")")
//.attr("transform", "translate(0," + (height - 20) + ")")
//.style("position", "fixed")
.call(xAxis);
//y-axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + xScale(0) + ",0)")
//.attr("transform", "translate(" + 20 + ",0)")
//.style("position", "fixed")
.call(yAxis);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle") // Add circle svg
.attr("cx", function(d) {
return xScale(d[0]); // Circle's X
})
.attr("cy", function(d) { // Circle's Y
return yScale(d[1]);
})
.attr("r", 2)
.call(updatePlotWithSocketData);
function updatePlotWithSocketData() {
console.log(dataset.length);
for(var i=0; i<numDataPoints; i++) {
dataset.push([getRandomInt(-200,200), getRandomInt(-100,100)]); // Add new number to array
}
// Update scale domains
xScale.domain([d3.min(dataset, function(d) { return d[0]; } ), d3.max(dataset, function(d) { return d[0]; })]);
yScale.domain([d3.min(dataset, function(d) { return d[1]; } ), d3.max(dataset, function(d) { return d[1]; })]);
xScale.range([20, width - 20 * 2]).nice();
yScale.range([height - 20, 20]).nice();
// Update old points to the new scale
svg.selectAll("circle")
.transition()
.duration(1000)
.attr("cx", function(d) {
return xScale(d[0]); // Circle's X
})
.attr("cy", function(d) {
return yScale(d[1]); // Circle's Y
});
// Update circles
svg.selectAll("circle")
.data(dataset) // Update with new data
.enter()
.append("circle")
.transition() // Transition from old to new
.duration(1000) // Length of animation
.each("start", function() { // Start animation
d3.select(this) // 'this' means the current element
.attr("fill", "red") // Change color
.attr("r", 5); // Change size
})
.delay(function(d, i) {
return i / dataset.length * 500; // Dynamic delay (i.e. each item delays a little longer)
})
.attr("cx", function(d) {
return xScale(d[0]); // Circle's X
})
.attr("cy", function(d) {
return yScale(d[1]); // Circle's Y
})
.each("end", function() { // End animation
d3.select(this) // 'this' means the current element
.transition()
//.duration(500)
.attr("fill", "black") // Change color
.attr("r", 2); // Change radius
});
/* force.on("tick", function() {
nodes[0].x = width / 2;
nodes[1].y = height / 2;
}); */
/* var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
var yAxis = d3.svg.axis().scale(yScale).orient("left"); */
svg.selectAll(".x.axis")
.transition()
.duration(1000)
.call(xAxis);
// Update Y Axis
svg.selectAll(".y.axis")
.transition()
.duration(1000)
.call(yAxis);
setTimeout(updatePlotWithSocketData, 3000);
}
// Socker code
/*
var socket;
var registered = false;
function startDataReceptionFromSocket() {
console.log("opening socket");
//on http server use document.domain instead od "localhost"
//Start the websocket client
socket = new WebSocket("ws://localhost:8887/");
//When the connection is opened, login.
socket.onopen = function() {
console.log("Opened socket.");
//register the user
};
socket.onmessage = function(a) {
//process the message here
console.log("received message socker => : " + a.data);
var message = JSON.parse(a.data);
console.log("message =>" + message.message);
var numbers = message.message.split(" ")
dataset.push([numbers[0],numbers[1]]);
updatePlotWithSocketData();
}
socket.onclose = function() { console.log("Closed socket."); };
socket.onerror = function() { console.log("Error during transfer."); };
}
// On document load, open the socket connection
(function() {
startDataReceptionFromSocket();
})();*/
</script>
It seems to me that the solution is setting the translate again when you update the axes.
Right now, in your code, the domains (for the x and y scales) are changing, but the axes are translated using the original xScale(0) and yScale(0) positions. This demo reproduces the problem:
var margin = 10;
var width = 250, height = 250;
var svg = d3.select("#mydiv")
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scale.linear()
.range([margin, width - margin])
.domain([-10, 10]);
var yScale = d3.scale.linear()
.range([margin, height - margin])
.domain([-10, 10]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
svg.append("g").attr("class", "x axis")
.attr("transform", "translate(0," + yScale(0) + ")")
.call(xAxis);
svg.append("g").attr("class", "y axis")
.attr("transform", "translate(" + xScale(0) + ",0)")
.call(yAxis);
d3.select("#btn").on("click", randomize);
function randomize(){
xScale.domain([Math.floor(Math.random()*50)*(-1),Math.floor(Math.random()*50)]);
yScale.domain([Math.floor(Math.random()*50)*(-1),Math.floor(Math.random()*50)]);
svg.selectAll(".x.axis")
.transition()
.call(xAxis);
svg.selectAll(".y.axis")
.transition()
.call(yAxis);
}
.axis path, .axis line{
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<button id="btn">Randomize</button>
<div id="mydiv"></div>
Now the solution. The next demo has the same code, but updating the positions like this:
svg.selectAll(".x.axis")
.transition()
.duration(1000)
.attr("transform", "translate(0," + yScale(0) + ")")
.call(xAxis);
svg.selectAll(".y.axis")
.transition()
.duration(1000)
.attr("transform", "translate(" + xScale(0) + ",0)")
.call(yAxis);
Check the demo:
var margin = 10;
var width = 250, height = 250;
var svg = d3.select("#mydiv")
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scale.linear()
.range([margin, width - margin])
.domain([-10, 10]);
var yScale = d3.scale.linear()
.range([margin, height - margin])
.domain([-10, 10]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
svg.append("g").attr("class", "x axis")
.attr("transform", "translate(0," + yScale(0) + ")")
.call(xAxis);
svg.append("g").attr("class", "y axis")
.attr("transform", "translate(" + xScale(0) + ",0)")
.call(yAxis);
d3.select("#btn").on("click", randomize);
function randomize(){
xScale.domain([Math.floor(Math.random()*50)*(-1),Math.floor(Math.random()*50)]);
yScale.domain([Math.floor(Math.random()*50)*(-1),Math.floor(Math.random()*50)]);
svg.selectAll(".x.axis")
.transition()
.duration(1000)
.attr("transform", "translate(0," + yScale(0) + ")")
.call(xAxis);
svg.selectAll(".y.axis")
.transition()
.duration(1000)
.attr("transform", "translate(" + xScale(0) + ",0)")
.call(yAxis);
}
.axis path, .axis line{
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<button id="btn">Randomize</button>
<div id="mydiv"></div>
I am fairly new to D3 and am hitting a problem.
I've created a simplied example of what I'm trying to achieve.
Firstly, I have a CSV file with data. In this example, it consists of phone sales data for some popular phones for several months for 2 stores. The data is shown below:
Store,Product,Month,Sold
London,iPhone,0,5
London,iPhone,1,4
London,iPhone,2,3
London,iPhone,3,5
London,iPhone,4,6
London,iPhone,5,7
London,Android Phone,0,3
London,Android Phone,1,4
London,Android Phone,2,5
London,Android Phone,3,7
London,Android Phone,4,8
London,Android Phone,5,9
London,Windows Phone,0,1
London,Windows Phone,1,2
London,Windows Phone,2,6
London,Windows Phone,3,7
London,Windows Phone,4,8
London,Windows Phone,5,5
Glasgow,iPhone,0,3
Glasgow,iPhone,1,4
Glasgow,iPhone,2,5
Glasgow,iPhone,3,2
Glasgow,iPhone,4,1
Glasgow,iPhone,5,3
Glasgow,Android Phone,0,4
Glasgow,Android Phone,1,3
Glasgow,Android Phone,2,7
Glasgow,Android Phone,3,4
Glasgow,Android Phone,4,3
Glasgow,Android Phone,5,6
Glasgow,Windows Phone,0,3
Glasgow,Windows Phone,1,6
Glasgow,Windows Phone,2,7
Glasgow,Windows Phone,3,5
Glasgow,Windows Phone,4,3
Glasgow,Windows Phone,5,4
I've written the following code in JS/D3.js:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
fill:none;
stroke:#000;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke-width: 1.5px;
}
</style>
<body>
<p id="menu"><b>Test</b>
<br>Select Store:
<select>
<option value="0">London</option>
<option value="1">Glasgow</option>
</select>
</p>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 900 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// construct a linear scale for x axis
var x = d3.scale.linear()
.range([0,width]);
// construct a linear scale for y axis
var y = d3.scale.linear()
.range([height,0]);
// use the default line colours (see http://stackoverflow.com/questions/21208031/how-to-customize-the-color-scale-in-a-d3-line-chart for info on setting colours per line)
var color = d3.scale.category10();
// create the x axis and orient of ticks and labels at the bottom
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
// create the y axis and orient of ticks and labels on the left
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// line generator function
var line = d3.svg.line()
//.interpolate("basis")
.x(function(d) { return x(d.Month); })
.y(function(d) { return y(d.Sold); });
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 + ")");
d3.csv("sampleData.csv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key == "Product"; }));
// first we need to corerce the data into the right formats
// map the data from the CSV file
data = data.map( function (d) {
return {
Store: d.Store,
Product: d.Product,
Month: +d.Month,
Sold: +d.Sold };
});
// nest the data by regime and then CI
var salesDataByStoreProduct = d3.nest()
.key(function(d) { return d.Store; })
.key(function(d) { return d.Product; })
.entries(data);
// get the first regime's nest
var salesDataForLondon;
salesDataForLondon = salesDataByStoreProduct[0].values;
console.log(salesDataForLondon);
x.domain([d3.min(salesDataForLondon, function(d) { return d3.min(d.values, function (d) { return d.Month; }); }),
d3.max(salesDataForLondon, function(d) { return d3.max(d.values, function (d) { return d.Month; }); })]);
y.domain([0, d3.max(salesDataForLondon, function(d) { return d3.max(d.values, function (d) { return d.Sold; }); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
var Products = svg.selectAll(".Product")
.data(salesDataForLondon, function(d) { return d.key; })
.enter().append("g")
.attr("class", "Product");
Products.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.key); });
function redraw()
{
var salesDataByStoreProduct = d3.nest()
.key(function(d) { return d.Store; })
.key(function(d) { return d.Product; })
.entries(data);
var salesDataForGlasgow;
salesDataForGlasgow = salesDataByStoreProduct[1].values;
console.log(salesDataForGlasgow);
x.domain([d3.min(salesDataForGlasgow, function(d) { return d3.min(d.values, function (d) { return d.Product; }); }),
d3.max(salesDataForGlasgow, function(d) { return d3.max(d.values, function (d) { return d.Product; }); })]);
y.domain([0, d3.max(salesDataForGlasgow, function(d) { return d3.max(d.values, function (d) { return d.Sales; }); })]);
svg.select("g")
.call(xAxis);
svg.select("g")
.call(yAxis);
var Products = svg.selectAll(".Product")
.data(salesDataForGlasgow, function(d) { return d.key; })
.enter().select("g")
.attr("class", "Product");
Products.select("path")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.key); });
}
/******************************************************/
var menu = d3.select("#menu select")
.on("change", change);
function change()
{
clearTimeout(timeout);
d3.transition()
.duration(altKey ? 7500 : 1500);
redraw();
}
var timeout = setTimeout(function() {
menu.property("value", "ENEUSE").node().focus();
change();
}, 7000);
var altKey;
d3.select(window)
.on("keydown", function() { altKey = d3.event.altKey; })
.on("keyup", function() { altKey = false; });
/******************************************************/
});
</script>
</body>
</html>
where I've read in the CSV file and then used D3 nests to create a hierarchy as shown below:
Store->Product->Month->Sales
I want the chart to present the sales data per product by month for London and then if the selection is changed, to show the sales data by month for Glasgow.
However, although the London data is being presented, the chart isn't being updated when I select Glasgow.
To rule out anything too obvious, I've hardcoded the array index for each store.
I've also added console.log and can see the right data is being used but just not rendered in the chart when redraw() is being called.
I'd be grateful of any suggestions of the cause of the problem which I suspect it related to the following code:
var Products = svg.selectAll(".Product")
.data(salesDataForGlasgow, function(d) { return d.key; })
.enter().select("g")
.attr("class", "Product");
Products.select("path")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.key); });
Any other advice on improving or simplifying the code would be very gratefully appreciated.
As you suspect, the problem is indeed in these two statements:
var Products = svg.selectAll(".Product")
.data(salesDataForGlasgow, function(d) { return d.key; })
.enter().select("g")
.attr("class", "Product");
Products.select("path")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.key); });
Products is derived from the .enter() selection. This contains one element for each data item that isn't joined to an existing element in the DOM. When changing the graph to show the Glasgow data, there are no new elements to add (the London data has three products as does the Glasgow data), so the .enter() selection is empty.
Instead, you need to restart the selection from .Product. Change the second of the two statements to the following:
svg.selectAll(".Product")
.select("path")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.key); });
There are some other issues I found in your code. Firstly, the three lines that set x.domain() and y.domain() use the wrong property names at the end. This causes various NaNs to appear in the ranges of the x and y scales as D3 attempts to convert product names or undefined to numbers. At the end of these three lines, replace d.Product with d.Month and d.Sales with d.Sold, so that they are consistent with the lines that set the ranges of the x and y scales for the London sales data.
Finally, you need to adjust how you reset the axes. At the moment you are using the following code:
svg.select("g")
.call(xAxis);
svg.select("g")
.call(yAxis);
This ends calling the xAxis and then yAxis functions on all g elements, including both axes, all axis ticks and the three graph lines, so the graph looks a bit confused. You've set the class of the X-axis to x axis, but because class names can't have spaces in you've actually given the axis the classes x and axis. A similar thing happens with the y-axis.
What you need to do is to select the axes individually, using the classes you've assigned to them, before calling xAxis or yAxis. Replace the lines above with the following:
svg.select("g.x.axis")
.call(xAxis);
svg.select("g.y.axis")
.call(yAxis);
After you've made all these changes the graph should hopefully do what you want.
While this isn't a code review site, your code could take significant refactoring. First, you aren't handling the enter, update, exit pattern correctly. Second, once you handle the pattern correctly you don't need a seperate redraw function. Just one function to handle creation and update.
Here's a quick refactor:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke-width: 1.5px;
}
</style>
<body>
<p id="menu"><b>Test</b>
<br>Select Store:
<select>
<option value="London">London</option>
<option value="Glasgow">Glasgow</option>
</select>
</p>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
var margin = {
top: 20,
right: 80,
bottom: 30,
left: 50
},
width = 900 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// construct a linear scale for x axis
var x = d3.scale.linear()
.range([0, width]);
// construct a linear scale for y axis
var y = d3.scale.linear()
.range([height, 0]);
// use the default line colours (see http://stackoverflow.com/questions/21208031/how-to-customize-the-color-scale-in-a-d3-line-chart for info on setting colours per line)
var color = d3.scale.category10();
// create the x axis and orient of ticks and labels at the bottom
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
// create the y axis and orient of ticks and labels on the left
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// line generator function
var line = d3.svg.line()
//.interpolate("basis")
.x(function(d) {
return x(d.Month);
})
.y(function(d) {
return y(d.Sold);
});
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 + ")");
// create gs for axis but don't add yet
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
svg.append("g")
.attr("class", "y axis");
var salesDataByStoreProduct = null;
d3.csv("data.csv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) {
return key == "Product";
}));
// first we need to corerce the data into the right formats
// map the data from the CSV file
data = data.map(function(d) {
return {
Store: d.Store,
Product: d.Product,
Month: +d.Month,
Sold: +d.Sold
};
});
// nest the data by regime and then CI
salesDataByStoreProduct = d3.nest()
.key(function(d) {
return d.Store;
})
.key(function(d) {
return d.Product;
})
.entries(data);
draw("London");
});
function draw(which) {
// get the first regime's nest
var salesData = null;
salesDataByStoreProduct.forEach(function(d) {
if (d.key === which) {
salesData = d.values;
}
});
// set domains
x.domain([d3.min(salesData, function(d) {
return d3.min(d.values, function(d) {
return d.Month;
});
}),
d3.max(salesData, function(d) {
return d3.max(d.values, function(d) {
return d.Month;
});
})
]);
y.domain([0, d3.max(salesData, function(d) {
return d3.max(d.values, function(d) {
return d.Sold;
});
})]);
// draw axis
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
// this is the update selection
var Products = svg.selectAll(".Product")
.data(salesData, function(d) {
return d.key;
});
// this is the enter selection
Products
.enter().append("g")
.attr("class", "Product")
.append("path");
// now do update
Products.selectAll("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", function(d) {
return color(d.key);
});
}
var menu = d3.select("#menu select")
.on("change", change);
function change() {
draw(this.options[this.selectedIndex].value);
}
</script>
</body>
</html>
Running code here.
I've used the following code to visualize the data I have in csv file. I'v scaled the data to dynamically fit within the svg element, but the x and y axises still display the pre-scaled values even that I passed the scale function to both. Does anybody know how to fix that?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3 Test</title>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<style>
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
</style>
</head>
<body>
<script type="text/javascript">
//Width and height
var w = 500;
var h = 300;
var padding = 80;
var dataset = []
var generateVisualization = function(){
//Create scale functions
var xScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[0]; })])
.range([padding, w - padding]);
var yScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[1]; }), d3.max(dataset, function(d) { return d[1]; })])
.range([h - padding, padding]);
//Define X axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5);
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create circles
svg.append("g")
.attr("id", "circles")
.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 2);
//Create X axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h - padding + 5) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + (padding - 5) + ",0)")
.call(yAxis);
};
d3.csv("OuputMatrix2.csv", function(error, d) {
dataset = d.map(function(d) { return [ +d["x"], +d["y"] ];
});
generateVisualization();});
</script>
</body>
</html>
The following is a screenshot of the output:
https://www.dropbox.com/s/2th83r4r42nn4el/Screenshot%202014-11-11%2015.45.55.png?dl=0