D3js charts reponsive - javascript

I am trying to get responsive d3js charts. I have set the width and height as 100% for the chart have a resize function which should reduce the size . In what I have done so far it just manages to reduce the labels on the x-axis but the line chart remains of the same size.Is the way I am trying to make the responsive the correct way or is their a better way with which any d3js chart(bar/pie/line) could be made responsive.
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
};
var width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right,
height = parseInt(d3.select("#chart").style("height")) - 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").ticks(5).tickFormat(function (d) {
return d.replace('SEASONAL_', '');
});;
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
var valueline = d3.svg.line()
.x(function (d) {
return x(d.name);
})
.y(function (d) {
return y(d.count);
});
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 + ")");
// Get the data
var all = [{
"name": "Seasonal Pop",
"code": "SEASONAL_POP",
"children": [{
"name": "SEASONAL_LYQ1",
"code": "SEASONAL_LYQ1",
"count": 1200
}, {
"name": "SEASONAL_LYQ2",
"code": "SEASONAL_LYQ2",
"count": 2000
}, {
"name": "SEASONAL_LYQ3",
"code": "SEASONAL_LYQ3",
"count": 1060
}, {
"name": "SEASONAL_LYQ4",
"code": "SEASONAL_LYQ4",
"count": 2300
}, {
"name": "SEASONAL_CYQ1",
"code": "SEASONAL_CYQ1",
"count": 1300
}, {
"name": "SEASONAL_CYQ2",
"code": "SEASONAL_CYQ2",
"count": 3400
}, {
"name": "SEASONAL_CYQ3",
"code": "SEASONAL_CYQ3",
"count": 4500
}, {
"name": "SEASONAL_CYQ4",
"code": "SEASONAL_CYQ4",
"count": 5500
}]
}];
var data = all[0].children;
data.forEach(function (d) {
// d.name = +d.date ;
d.count = +d.count;
});
// Scale the range of the data
x.domain(data.map(function (d) {
return d.name;
}));
y.domain([0, d3.max(data, function (d) {
return d.count;
})]);
svg.append("path") // Add the valueline path.
.attr("d", valueline(data));
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
// Define responsive behavior
function resize() {
var width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right,
height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom;
// Update the range of the scale with new width/height
x.range([0, width]);
y.range([height, 0]);
// Update the axis and text with the new scale
svg.select('.x.axis')
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.select('.y.axis')
.call(yAxis);
// Force D3 to recalculate and update the line
svg.selectAll('.line')
.attr("d", function(d) { return line(d.count); });
// Update the tick marks
xAxis.ticks(Math.max(width/75, 2));
yAxis.ticks(Math.max(height/50, 2));
};
// Call the resize function whenever a resize event occurs
d3.select(window).on('resize', resize);
// Call the resize function
resize();
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;
}
#chart {
width: 100%;
height: 100%;
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body><svg id="chart"></svg></body>

Well your code kinda works. Save that you're dealing with two SVGs. The on the page with id="chart" and this one thats appended.
var svg = d3.select("body")
.append("svg")
But its not really a D3 issue its more to do with responsive SVGs. Responsive charts have problems scaling. The X & Y axis can become misrepresented.
One approach is to scale the entire Chart/SVG. Forget about listening to window resize events. Just set a viewBox property on your main svg. And set a static width hight thats too the correct ratio of your chat.
var svg = d3.select("#chart")
.attr("viewBox", "0 0 " + viewWidth + " " + viewHeight)
example
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
};
var width = 500 - margin.left - margin.right,
height = 240 - 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").ticks(5).tickFormat(function (d) {
return d.replace('SEASONAL_', '');
});;
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
var valueline = d3.svg.line()
.x(function (d) {
return x(d.name);
})
.y(function (d) {
return y(d.count);
});
var viewWidth = width - margin.left - margin.right;
var viewHeight = height + margin.top + margin.bottom;
var svg = d3.select("#chart")
.attr("viewBox", "0 0 " + viewWidth + " " + viewHeight)
// Get the data
var all = [{
"name": "Seasonal Pop",
"code": "SEASONAL_POP",
"children": [{
"name": "SEASONAL_LYQ1",
"code": "SEASONAL_LYQ1",
"count": 1200
}, {
"name": "SEASONAL_LYQ2",
"code": "SEASONAL_LYQ2",
"count": 2000
}, {
"name": "SEASONAL_LYQ3",
"code": "SEASONAL_LYQ3",
"count": 1060
}, {
"name": "SEASONAL_LYQ4",
"code": "SEASONAL_LYQ4",
"count": 2300
}, {
"name": "SEASONAL_CYQ1",
"code": "SEASONAL_CYQ1",
"count": 1300
}, {
"name": "SEASONAL_CYQ2",
"code": "SEASONAL_CYQ2",
"count": 3400
}, {
"name": "SEASONAL_CYQ3",
"code": "SEASONAL_CYQ3",
"count": 4500
}, {
"name": "SEASONAL_CYQ4",
"code": "SEASONAL_CYQ4",
"count": 5500
}]
}];
var data = all[0].children;
data.forEach(function (d) {
// d.name = +d.date ;
d.count = +d.count;
});
// Scale the range of the data
x.domain(data.map(function (d) {
return d.name;
}));
y.domain([0, d3.max(data, function (d) {
return d.count;
})]);
svg.append("path") // Add the valueline path.
.attr("d", valueline(data));
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
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;
}
#chart {
width: 100%;
height: 100%;
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body><svg id="chart"></svg></body>

Related

D3 Chart SVG Responsive Bars and Axis

I have created a D3 chart step by step. I have made some changes to make the SVG responsive. My target now is to make the bar graph more responsive in order to make it easier to read when the screen size is smaller (width). I have pasted the snippet below and at the bottom of the page, I focus on the part that I am thinking the solution is hidden.
var data = [
{"area": "one ", "value": 18000},
{"area": "Two ", "value": 17000},
{"area": "three ", "value": 80000},
{"area": "four ", "value": 55000},
{"area": "five ", "value": 100000},
{"area": "six", "value": 50000},
{"area": "seven", "value": 50000}
];
var margin = {top: 10, right: 10, bottom: 70, left: 30};
var width = 1900 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
//A fully-responsive chart area
var svg = d3.select("#chart-div")
.append("svg")
.attr("width","100%")
.attr("height","500px")
.attr("viewBox","0 0 "+
(width+margin.left+margin.right)+
" "+
(height+margin.top+margin.bottom) )
.append("g")
.attr("transform","translate("+
margin.left+","+margin.top+")");
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleBand().range([height, 0]);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
data.sort(function(a, b) { return a.value - b.value; });
x.domain([0, d3.max(data, function(d) { return d.value; })]);
y.domain(data.map(function(d) { return d.area; })).padding(0.1);
g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(5).tickFormat(function(d) { return parseInt(d / 1000); }).tickSizeInner([-height]));
g.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y));
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", 0)
.attr("height", y.bandwidth())
.attr("y", function(d) { return y(d.area); })
.attr("width", function(d) { return x(d.value); })
.on("mousemove", function(d){
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html((d.area) + "<br>" + "£" + (d.value));
})
.on("mouseout", function(d){ tooltip.style("display", "none");});
#import url('https://fonts.googleapis.com/css?family=Roboto');
body {
margin: 15px;
background-color: #F1F3F3;
font-family: 'Roboto'!important;
}
.bar {
fill: #6F257F;
}
.axis path,
.axis line {
fill: none;
stroke: #D4D8DA;
stroke-width: 1px;
shape-rendering: crispEdges;
}
.x path {
display: none;
}
.toolTip {
position: absolute;
display: none;
min-width: 80px;
height: auto;
background: none repeat scroll 0 0 #ffffff;
border: 1px solid #6F257F;
padding: 14px;
text-align: center;
}
.svg-container {
display: inline-block;
position: relative;
width: 100%;
padding-bottom: 100%; /* aspect ratio */
vertical-align: top;
overflow: hidden;
}
.svg-content-responsive {
display: inline-block;
position: absolute;
top: 10px;
left: 0;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="chart-div" style="width:100%;height:100%;"></div>
I changed a part of the code with the following:
var parentwidth = $("#chart-div").parent().width();
var margin = {top: 10, right: 10, bottom: 70, left: 30};
var width = parentwidth - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
where I am actually getting the parentwidth using jQuery.
I am actually thinking whether :
a) It is possible to avoid jQuery in this case.
b) Ideally, make the bar to scale differently so that everything will be easily read by the user (small text size is an issue):
I am testing the function below but I am possibly getting errors related to some chrome addons to avoid the cross-origin error. I can update the question if the below is the best solution:
function resize() {
var width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right,
height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom;
// Update the range of the scale with new width/height
xScale.range([0, width]);
yScale.rangeRoundBands([height, 0], 0.1);
// Update the axis and text with the new scale
svg.select(".x.axis")
.call(xAxis)
.attr("transform", "translate(0," + height + ")")
.select(".label")
.attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")");
svg.select(".y.axis")
.call(yAxis);
// Update the tick marks
xAxis.ticks(Math.max(width/75, 2), " $");
// Force D3 to recalculate and update the line
svg.selectAll(".bar")
.attr("width", function(d) { return xScale(d["total"]); })
.attr("y", function(d) { return yScale(d["Name"]); })
.attr("height", yScale.rangeBand());
};
Your question is basically the same as this one.
The TL;DR is: give your <svg> a viewBox attribute and a preserveAspectRatio (e.g. xMinYMin meet) attribute. Then wrap the <svg> in a <div> that has position: relative.
This is not the only solution, but it's probably the easiest to implement and (I think) the most used.
For an overview and discussion of several other solutions, see this article by Amelia Bellamy-Royds.
Also, for an in-depth explanation of the SVG coordinate system, read this series of articles by Sara Soueidan.
As for your doubts about jQuery and the text being too small on mobile phones:
a) you can totally avoid jQuery
b) you can avoid having a text too small by "counter-scaling" the text, namely when your entire barchart (i.e. bars, axis, labels) scales down, your text scales up. This is sometime called sticky text. You can see an example here.
I have altered my script to make it work. Please see the snippet below:
var margin = {top: 20, right: 20, bottom: 50, left: 100},
width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right,
height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom;
var yScale = d3.scale.ordinal()
.rangeRoundBands([height, 0], 0.1);
var xScale = d3.scale.linear()
.range([0, width]);
var dollarFormatter = d3.format(",.0f")
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickFormat(function(d) { return "$" + dollarFormatter(d);});
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<div><span>Name:</span> <span style='color:white'>" + d.Name + "</span></div>" +
"<div><span>Sub-Category:</span> <span style='color:white'>" + d["Sub-Category"] + "</span></div>" +
"<div><span>Total Sales:</span> <span style='color:white'>" + "$"+ dollarFormatter(d.total) + "</span></div>";
})
svg.call(tip);
//Get CSV, JSON from URL
//var url = "http://bl.ocks.org/josiahdavis/raw/7d84b2f1837eab9c24d9/top.csv";
var data = [
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Tom Stivers",
"total": 1889.8,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Keith Herrera",
"total": 2020.161,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Jack O'Briant",
"total": 2122.545,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Nora Paige",
"total": 2154.9,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Anna Gayman",
"total": 2396.2656,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Tracy Blumstein",
"total": 3083.43,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Maribeth Schnelling",
"total": 3406.664,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Greg Tran",
"total": 4007.84,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Quincy Jones",
"total": 4404.9,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Peter Fuller",
"total": 6232.624,
"Type": "Customer"
}];
//d3.csv(url, format, function(error, data){
//if (error) throw error;
// Filter to select a subset
var subset = data.filter(function(el){
return (el["metric"] === "Sales")
&& (el["Sub-Category"] === "Bookcases")
&& (el["Type"] === "Customer");
});
// Sort the data so bar chart is sorted in decreasing order
subset = subset.sort(function(a, b) { return a["total"] - b["total"]; });
console.log(JSON.stringify(subset, null, 2));
yScale.domain(subset.map(function(d) { return d["Name"]; }));
xScale.domain([0, d3.max(subset, function(d) { return d["total"]; })]);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.call(xAxis)
.attr("transform", "translate(0," + height + ")")
.append("text")
.attr("class", "label")
.attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")")
.style("text-anchor", "middle")
.text("Sales");
svg.selectAll(".bar")
.data(subset)
.enter().append("rect")
.attr("class", "bar")
.attr("width", function(d) { return xScale(d["total"]); })
.attr("y", function(d) { return yScale(d["Name"]); })
.attr("height", yScale.rangeBand())
.on('mouseover', tip.show)
.on('mouseout', tip.hide);;
//});
// Define responsive behavior
function resize() {
var width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right,
height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom;
// Update the range of the scale with new width/height
xScale.range([0, width]);
yScale.rangeRoundBands([height, 0], 0.1);
// Update the axis and text with the new scale
svg.select(".x.axis")
.call(xAxis)
.attr("transform", "translate(0," + height + ")")
.select(".label")
.attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")");
svg.select(".y.axis")
.call(yAxis);
// Update the tick marks
xAxis.ticks(Math.max(width/75, 2), " $");
// Force D3 to recalculate and update the line
svg.selectAll(".bar")
.attr("width", function(d) { return xScale(d["total"]); })
.attr("y", function(d) { return yScale(d["Name"]); })
.attr("height", yScale.rangeBand());
};
// Call the resize function whenever a resize event occurs
d3.select(window).on('resize', resize);
// Call the resize function
resize();
// Define the format function
function format(d) {
d.total = +d.total;
return d;
}
#import url('https://fonts.googleapis.com/css?family=Roboto');
body {
margin: 5px;
background-color: #F1F3F3;
font-family: 'Roboto'!important;
}
.bar {
fill: #14405F;
}
.bar:hover {
fill: #33A1EE;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #D4D8DA;
stroke-width: 1px;
shape-rendering: crispEdges;
}
.x path {
display: none;
}
#chart {
width: 100%;
height: 100%;
position: absolute;
}
.d3-tip {
line-height: 1;
font: 14px sans-serif;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: rgb(185, 185, 185);
border-radius: 2px;
}
/*
.toolTip {
position: absolute;
display: none;
min-width: 80px;
height: auto;
background: none repeat scroll 0 0 #ffffff;
border: 1px solid #6F257F;
padding: 14px;
text-align: center;
}
*/
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<svg id="chart"></svg>

d3-Draw grid line at a location based on the y coordinates?

I am kind a new to d3.js and want to draw a horizontal grid line based on y coordinate value fiddle , i've tried with if condition at y1 and y2 locations but the lines are overlapping at the top of graph.
Any help is much appreciated.
Your if statement makes little sense to me:
if (y(d) === thresholdValues.minValue) {
return y(d);
}
This means that only when the value in the screen is exactly thresholdValues.minValue (which is 40 in your code) the line will be painted.
Solution: drop the if statement. Actually, I kept the if just to avoid the first gridline, over the x axis:
if (d != 0) {
return y(d);
}
Here is your code with that change:
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
};
var thresholdValues = {
minValue: 40,
maxValue: 85
};
var width = 600 - margin.left - margin.right;
var height = 270 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
var valueline = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.close);
});
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 + ")");
// Get the data
var data = [{
date: "1-May-12",
close: "58.13"
}, {
date: "30-Apr-12",
close: "53.98"
}, {
date: "27-Apr-12",
close: "67.00"
}, {
date: "26-Apr-12",
close: "89.70"
}, {
date: "25-Apr-12",
close: "99.00"
}];
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, 100]);
svg.append("path") // Add the valueline path.
.attr("d", valueline(data));
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
svg.selectAll("line.horizontalGrid").data(y.ticks(4)).enter()
.append("line")
.attr({
"class": "horizontalGrid",
"x1": margin.right,
"x2": width - margin.right,
"y1": function(d) {
if (d != 0) {
return y(d);
}
},
"y2": function(d) {
if (d != 0) {
return y(d);
}
},
"fill": "none",
"shape-rendering": "crispEdges",
"stroke": "grey",
"stroke-width": "2px",
"opacity": 0.4,
"stroke-dasharray": 8
});
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;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
EDIT: If you want to draw only two lines, at the thresholds, you have to pass their values to data, not the scale ticks:
.data(Object.values(thresholdValues))
Here is the demo:
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
};
var thresholdValues = {
minValue: 40,
maxValue: 85
};
var width = 600 - margin.left - margin.right;
var height = 270 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
var valueline = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.close);
});
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 + ")");
// Get the data
var data = [{
date: "1-May-12",
close: "58.13"
}, {
date: "30-Apr-12",
close: "53.98"
}, {
date: "27-Apr-12",
close: "67.00"
}, {
date: "26-Apr-12",
close: "89.70"
}, {
date: "25-Apr-12",
close: "99.00"
}];
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, 100]);
svg.append("path") // Add the valueline path.
.attr("d", valueline(data));
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
svg.selectAll("line.horizontalGrid").data(Object.values(thresholdValues)).enter()
.append("line")
.attr({
"class": "horizontalGrid",
"x1": margin.right,
"x2": width - margin.right,
"y1": function(d) {
if (d != 0) {
return y(d);
}
},
"y2": function(d) {
if (d != 0) {
return y(d);
}
},
"fill": "none",
"shape-rendering": "crispEdges",
"stroke": "grey",
"stroke-width": "2px",
"opacity": 0.4,
"stroke-dasharray": 8
});
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
<script src="https://d3js.org/d3.v3.js"></script>

d3 line chart not showing data - based of tutorial

I am having issues with my line chart not showing data. I am basing my code off of here. I am using WebStorm and there are no errors in my code or the developer tools in Chrome so I'm at a loss for what is going wrong.
Code:
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatDate = d3.time.format("%d-%b-%y");
var x = d3.time.scale()
.range([0, width]);
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 line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.users); });
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("lineChart.csv", type, 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.users; }));
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");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
});
function type(d) {
d.date = formatDate.parse(d.date);
d.users = +d.users;
return d;
}
</script>
CSV:
date, users
6-Mar-15,19
11-Mar-15,12
22-Apr-15,22
29-Apr-15,32
3-May-15,1
6-May-15,9
4-Sep-15,2
8-Sep-15,13
10-Sep-15,147
21-Sep-15,4
1-Oct-15,264
4-Oct-15,114
7-Oct-15,63
12-Oct-15,79
The problem is to be found within the header line of your CSV. You have a space character right in front of column header users:
date, users
6-Mar-15,19
d3.csv.parse() will turn this into an attribute d[" users"] containing a space character. Because there is no attribute users in the parsed data's objects, your accessor function type(d) will therefore convert +undefined to NaN and assign this value to d["users"].
This is in compliance with RFC 4180 which states:
2. Definition of the CSV Format
Within the header and each record, there may be one or more
fields, separated by commas. Each line should contain the same
number of fields throughout the file. Spaces are considered part
of a field and should not be ignored. The last field in the
record must not be followed by a comma.
The solution to your problem will be as simple as removing the unwanted space character from your CSV while leaving the rest unchanged:
date,users
6-Mar-15,19
Your code seems to be working fine when using static data. So you should conform the data is loaded correctly inside d3.csv method and converted properly using type function.
Ensure date is parsed and users is an aggregated sum.
EDIT: Try loading csv data as shown below.
d3.csv("lineChart.csv", function(error, data) {
data.forEach(function(d) {
d.date = formatDate.parse(d.date);
d.users = +d.users;
});
---------------------
---------------------
});
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatDate = d3.time.format("%d-%b-%y");
var x = d3.time.scale()
.range([0, width]);
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 line = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.users);
});
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": "6-Mar-15",
"users": 19
}, {
"date": "11-Mar-15",
"users": 12
}, {
"date": "22-Apr-15",
"users": 22
}, {
"date": "29-Apr-15",
"users": 32
}, {
"date": "3-May-15",
"users": 1
}, {
"date": "6-May-15",
"users": 9
}, {
"date": "4-Sep-15",
"users": 2
}, {
"date": "8-Sep-15",
"users": 13
}, {
"date": "10-Sep-15",
"users": 147
}, {
"date": "21-Sep-15",
"users": 4
}, {
"date": "1-Oct-15",
"users": 264
}, {
"date": "4-Oct-15",
"users": 114
}, {
"date": "7-Oct-15",
"users": 63
}, {
"date": "12-Oct-15",
"users": 79
}];
data.forEach(function(d) {
d.date = formatDate.parse(d.date);
d.users = +d.users;
});
alert(JSON.stringify(data));
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain(d3.extent(data, function(d) {
return d.users;
}));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

D3 graph with single Y value over time

I have data with a constant value across multiple dates.
[{"date":"13-Sep-15","data0":2464},{"date":"12-Sep-15","data0":2464},
{"date":"11-Sep-15","data0":2464},{"date":"10-Sep-15","data0":2464},
{"date":"9-Sep-15","data0":2464},{"date":"8-Sep-15","data0":2464},
{"date":"7-Sep-15","data0":2464}]
What are the settings of d3.axis() to get a graph like this?
This needs to work from 1->n y values.
.domain doesn't like an extent of [2464, 2464] no matter the value of .ticks.
Ok I am assuming, want to have distinct y ticks depending on the values in the data.
I am doing some thing like this to get the unique ticks needed in the y axis:
var yData = [];
data.forEach(function (d) {
d.date = parseDate(d.date);
d.close = +d.data0;
yData.push(+d.data0)
});
yData = d3.set(yData).values();//get a set of values for y axis.
Full code here:
// Set the dimensions of the canvas / graph
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%d-%b-%y").parse;
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the line
var valueline = d3.svg.line()
.x(function (d) {
return x(d.date);
})
.y(function (d) {
return y(d.close);
});
// Adds the svg canvas
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 + ")");
data = [{
"date": "13-Sep-15",
"data0": 2464
}, {
"date": "12-Sep-15",
"data0": 2464
}, {
"date": "11-Sep-15",
"data0": 2464
}, {
"date": "10-Sep-15",
"data0": 2464
}, {
"date": "9-Sep-15",
"data0": 2464
}, {
"date": "8-Sep-15",
"data0": 2464
}, {
"date": "7-Sep-15",
"data0": 2464
}];
var yData = [];
data.forEach(function (d) {
d.date = parseDate(d.date);
d.close = +d.data0;
yData.push(+d.data0)
});
yData = d3.set(yData).values()
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").tickValues(yData);
// Scale the range of the data
x.domain(d3.extent(data, function (d) {
return d.date;
}));
y.domain([0, d3.max(data, function (d) {
return d.close;
})]);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
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;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Hope this helps!
Figured it out. Here is the graph with the line centered on single y.
// Set the dimensions of the canvas / graph
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%d-%b-%y").parse;
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the line
var valueline = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.close) / 2;
});
// Adds the svg canvas
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 + ")");
data = [{
"date": "13-Sep-15",
"data0": 2464
}, {
"date": "12-Sep-15",
"data0": 2464
}, {
"date": "11-Sep-15",
"data0": 2464
}, {
"date": "10-Sep-15",
"data0": 2464
}, {
"date": "9-Sep-15",
"data0": 2464
}, {
"date": "8-Sep-15",
"data0": 2464
}, {
"date": "7-Sep-15",
"data0": 2464
}];
var yData = [];
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.data0;
yData.push(+d.data0)
});
yData = d3.set(yData).values()
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").tickValues(yData);
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain(d3.extent(data, function(d) {
return d.close;
}));
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
d3.select('.y .tick').attr("transform", "translate(0," + height / 2 + ")");
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;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Changes:
Translated the y tick down by height / 2:
d3.select('.y .tick').attr("transform", "translate(0," + height / 2 + ")");
Changed y extent:
y.domain(d3.extent(data, function (d) {
return d.close;
}));
Halved the y of the line:
var valueline = d3.svg.line()
.x(function (d) {
return x(d.date);
})
.y(function (d) {
return y(d.close) / 2;
});

Filter with underscorejs and d3 data

I want to filter my key value pair array into two sets and draw a line chart for each sets. I used underscore.js to filter the data. I am using d3.js to draw the line chart. When I add spectrum1Data as data object in D3 it doesn't draw the line chart - Can someone look at the below code and let me know what I am missing?
my data is like as below
spectrum_data = [
{
"SpectrumName": "Spectrum1",
"Mass": "27.19",
"Intensity": "20.2"
},
{
"SpectrumName": "Spectrum1",
"Mass": "11.39",
"Intensity": "10.7"
},
{
"SpectrumName": "Spectrum2",
"key": "value",
"Intensity": "12.9"
},
{
"SpectrumName": "Spectrum2",
"Mass": "21.83",
"Intensity": "30.9"
}];
The underscore JS function to filter data for spectrum 1 is as below.
var spectrum1Data = _(spectrum_data).chain()
.filter(function(x){ return x.SpectrumName=="Spectrum1"}).value()[0];
The d3.js function to draw a line is as below
var line = d3.svg.line()
.interpolate("cardinal")
.x(function(d) {//console.log(xScale(parseInt(d.Mass)));
return xScale(d.Mass); })
.y(function(d) {//console.log(xScale(d.Intensity));
return yScale(d.Intensity); });
focus.append("path")
.datum(spectrum1Data)
.attr("class", "line")
.attr("d", line)
<!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>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
var spectrum_data = [
{
"SpectrumName": "Spectrum1",
"Mass": "27.19",
"Intensity": "20.2"
},
{
"SpectrumName": "Spectrum1",
"Mass": "11.39",
"Intensity": "10.7"
},
{
"SpectrumName": "Spectrum2",
"Mass": "30",
"Intensity": "12.9"
},
{
"SpectrumName": "Spectrum2",
"Mass": "21.83",
"Intensity": "30.9"
}];
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 900 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0,width]);
var y = d3.scale.linear()
.range([height,0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("cardinal")
.x(function(d) { return x(d.Intensity); })
.y(function(d) { return y(d.Mass); });
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 + ")");
color.domain(d3.keys(spectrum_data[0]).filter(function(key) { return key == "SpectrumName"; }));
//filter data based on spectrumname
var nested_data = d3.nest().key(function(d) { return d.SpectrumName; }).entries(spectrum_data);
console.log(nested_data);
x.domain([d3.min(nested_data, function(d) { return d3.min(d.values, function (d) { return d.Intensity; }); }),
d3.max(nested_data, function(d) { return d3.max(d.values, function (d) { return d.Intensity; }); })]);
y.domain([0, d3.max(nested_data, function(d) { return d3.max(d.values, function (d) { return d.Mass; }); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
var Spectrum = svg.selectAll(".spectrum")
.data(nested_data, function(d) { return d.key; })
.enter().append("g")
.attr("class", "spectrum");
Spectrum.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.key); });
</script>
Instead of using underscore.js ,you can use the d3.nest() of d3.js for filtering. This is the code with your data drawing line chart.

Categories

Resources