I designed line graph using D3.js . Then I set data points and tool tips for it. There was a line drew. But it doesn't show data points and tool tips yet. Can anyone show me where I was wrong?
Thank you.
Here is my code....
<html>
<head>
<title>myD3Trial1</title>
<script src="http://mbostock.github.com/d3/d3.v2.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="jquery.tipsy.js"></script>
<link href="tipsy.css" rel="stylesheet" type="text/css" />
<style>
.axis path,
.axis line{
fill: none;
stroke: blue;
stroke-width: 2px;
}
.line{
fill: none;
stroke: black;
stroke-width: 1px;
}
.tick text{
font-size: 12px;
}
.tick line{
opacity: 0.2;
}
</style>
</head>
<body>
<script>
var xAxisGroup = null,
dataCirclesGroup = null,
dataLinesGroup = null;
var maxDataPointsForDots = 50,
transitionDuration = 1000;
var pointRadius = 4;
var data = [{
"creat_time": "2013-03-12 05:09:04",
"record_status": "ok",
"roundTripTime": "0"
}, {
"creat_time": "2013-03-12 14:59:06",
"record_status": "ok",
"roundTripTime": "0"
}, {
"creat_time": "2013-03-12 14:49:04",
"record_status": "ok",
"roundTripTime": "0"
}, {
"creat_time": "2013-03-13 14:39:06",
"record_status": "ok",
"roundTripTime": "0"
},{
"creat_time": "2013-03-12 14:29:03",
"record_status": "ok",
"roundTripTime": "0"
}];
var margin = {top: 20, right: 20, bottom: 30, left: 50};
var width = 960 - margin.left - margin.right;
var height = 100 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").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");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) { return x(d.creat_time); })
.y(function(d) { return y(d.roundTripTime); });
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.forEach(function(d) {
d.creat_time = parseDate(d.creat_time);
d.roundTripTime = +d.roundTripTime;
});
x.domain(d3.extent(data, function(d) { return d.creat_time; }));
y.domain(d3.extent(data, function(d) { return d.roundTripTime;}));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
// Draw the points
if (!dataCirclesGroup) {
dataCirclesGroup = svg.append('svg:g');
}
var circles = dataCirclesGroup.selectAll('.data-point')
.data(data);
circles
.enter()
.append('svg:circle')
.attr('class', 'data-point')
.style('opacity', 1e-6)
.attr('cx', function(d) { return xAxis(d.x) })
.attr('cy', function() { return yAxis(d.y) })
.attr('r', function() { return (data.length <= maxDataPointsForDots) ? pointRadius : 0 })
.transition()
.duration(transitionDuration)
.style('opacity', 1)
.attr('cx', function(d) { return xAxis(d.x) })
.attr('cy', function(d) { return yAxis(d.y) });
circles
.transition()
.duration(transitionDuration)
.attr('cx', function(d) { return xAxis(d.x) })
.attr('cy', function(d) { return yAxis(d.y) })
.attr('r', function() { return (data.length <= maxDataPointsForDots) ? pointRadius : 0 })
.style('opacity', 1);
circles
.exit()
.transition()
.duration(transitionDuration)
// Leave the cx transition off. Allowing the points to fall where they lie is best.
//.attr('cx', function(d, i) { return xAxis(i) })
.attr('cy', function() { return y(0) })
.style("opacity", 1e-6)
.remove();
$('svg circle').tipsy({
gravity: 'width',
html: true,
title: function() {
var d = this.__data__;
//var pDate = d.x;
return d.x;
}
});
</script>
</body>
</html>
First, the scale functions x and y should be used to position your circles, not the xAxis and yAxis functions.
Second, you keep using d.x and d.y but your data does not have x and y properties (hint, it does have "creat_time" and "roundTripTime" though).
Third, you are very confused about how to handle the enter and update pattern. On enter just do the append. On update do the positioning.
Forth, only put the things you wish to transition after the .transition() call (ie the opacity).
Putting all the advice together:
<html>
<head>
<title>myD3Trial1</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.js" charset="utf-8"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="https://rawgit.com/jaz303/tipsy/master/src/javascripts/jquery.tipsy.js"></script>
<link href="https://rawgit.com/jaz303/tipsy/master/src/stylesheets/tipsy.css" rel="stylesheet" type="text/css" />
<style>
.axis path,
.axis line{
fill: none;
stroke: blue;
stroke-width: 2px;
}
.line{
fill: none;
stroke: black;
stroke-width: 1px;
}
.tick text{
font-size: 16px;
}
.tick line{
opacity: 0.2;
}
</style>
</head>
<body>
<script>
var xAxisGroup = null,
dataCirclesGroup = null,
dataLinesGroup = null;
var maxDataPointsForDots = 50,
transitionDuration = 1000;
var pointRadius = 4;
var data = [{
"creat_time": "2013-03-12 05:09:04",
"record_status": "ok",
"roundTripTime": "0"
}, {
"creat_time": "2013-03-12 14:59:06",
"record_status": "ok",
"roundTripTime": "0"
}, {
"creat_time": "2013-03-12 14:49:04",
"record_status": "ok",
"roundTripTime": "0"
}, {
"creat_time": "2013-03-13 14:39:06",
"record_status": "ok",
"roundTripTime": "0"
},{
"creat_time": "2013-03-12 14:29:03",
"record_status": "ok",
"roundTripTime": "0"
}];
var margin = {top: 20, right: 20, bottom: 30, left: 50};
var width = 960 - margin.left - margin.right;
var height = 100 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").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");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) { return x(d.creat_time); })
.y(function(d) { return y(d.roundTripTime); });
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.forEach(function(d) {
d.creat_time = parseDate(d.creat_time);
d.roundTripTime = +d.roundTripTime;
});
x.domain(d3.extent(data, function(d) { return d.creat_time; }));
y.domain(d3.extent(data, function(d) { return d.roundTripTime;}));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
// Draw the points
if (!dataCirclesGroup) {
dataCirclesGroup = svg.append('svg:g');
}
var circles = dataCirclesGroup.selectAll('.data-point')
.data(data);
circles
.enter()
.append('svg:circle')
.attr('class', 'data-point')
.style('opacity', 1e-6);
circles
.attr('cx', function(d) { return x(d.creat_time) })
.attr('cy', function(d) { return y(d.roundTripTime) })
.attr('r', function() { return (data.length <= maxDataPointsForDots) ? pointRadius : 0 })
.transition()
.duration(transitionDuration)
.style('opacity', 1);
circles
.exit()
.transition()
.duration(transitionDuration)
// Leave the cx transition off. Allowing the points to fall where they lie is best.
//.attr('cx', function(d, i) { return xAxis(i) })
.attr('cy', function() { return y(0) })
.style("opacity", 1e-6)
.remove();
$('svg circle').tipsy({
gravity: 'width',
html: true,
title: function() {
console.log(this.__data__);
var d = this.__data__;
//var pDate = d.x;
return d.creat_time.toLocaleDateString();
}
});
</script>
</body>
</html>
Related
I'm having issues getting a nested line chart to appear. The data is in the console, but maybe I am missing something crucial. I was following this for reference: https://amber.rbind.io/2017/05/02/nesting/
Possibly I am calling the nested data incorrectly, or maybe i need to append it to the svg? Any help greatly appreciated!
The chart needs to have year on the x-axis, sum of events on the y axis, and each line should be a region.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Nested Chart</title>
<script src="../lib/d3.v5.min.js"></script>
<style type="text/css">
.pagebreak { page-break-before: always; }
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
.point {
fill:none;
size: 2px
}
</style>
</head>
<div style= "width:800px; margin:0 auto;" class ='body'></div>
<div class="pagebreak"> </div>
<body>
<script type="text/javascript">
var parseTime = d3.timeParse("%Y");
var margin = {top: 20, right: 20, bottom: 30, left: 50},
w = 960 - margin.left - margin.right,
h = 500 - margin.top - margin.bottom;
var padding =20;
/////////////////get the data//////////////////
d3.csv("state-year-earthquakes.csv").then(function(dataset) {
dataset.forEach(function(d) {
d.date = parseTime(d.year);
d.region = d['region'];
d.state = d['state'];
d.count = d['count'];
//console.log(d)
});
/////////////////scales the data//////////////////
var xScale = d3.scaleTime()
.domain([d3.min(dataset,function (d) { return d.date }),d3.max(dataset,function (d) { return d.date }) ]).range([padding, w - padding * 2])
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset,function (d) { return d.count }) ]).range([h- padding, padding])
var xAxis = d3.axisBottom().scale(xScale);
var yAxis = d3.axisLeft().scale(yScale);
/////////////////charts start here//////////////////
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 + ")");
//Define the line
var valueLine = d3.line()
.x(function(d) { return xScale(d.date); })
.y(function(d) { return yScale(+d.count); })
var nest = d3.nest()
.key(function(d){
return d.region;
})
.key(function(d){
return d.date;
})
.rollup(function(leaves){
return d3.sum(leaves, function(d) {return (d.count)});
})
.entries(dataset)
var color = d3.scaleOrdinal(d3.schemeCategory10); // set the colour scale
console.log(nest)
var regYear = svg.selectAll(".regYear")
.data(nest)
.enter()
.append("g")
// console.log(regYear)
var paths = regYear.selectAll(".line")
.data(function(d){
return d.values
})
.enter()
.append("path");
console.log(paths)
// Draw the line
paths
.attr("d", function(d){
return d.values
})
.attr("class", "line")
svg.append("g").attr("class", "axis").attr("transform", "translate(0," + (h - padding) + ")").call(xAxis);
//draw Y axis
svg.append("g").attr("class", "axis").attr("transform", "translate(" + padding + ",0)").call(yAxis);
// add label
svg.append("text").attr("x", (w/2)).attr("y", h+30).attr("text-anchor", "middle").text("Year");
svg.append("text").attr("x", padding).attr("y", padding-20).attr("text-anchor", "middle").text("# of Events");
//add title
svg.append("text").attr("x", (w/2)).attr("y", padding).attr("text-anchor", "middle").text("Events per Year by Category");
// add legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("x", w - 65)
.attr("y", 25)
.attr("height", 100)
.attr("width", 100);
////////////////////////////////////END///////////////////////////
} );
</script>
</body>
</html>
data.csv
state,region,year,count
Alabama,South,2010,1
Alabama,South,2011,1
Alabama,South,2012,0
Alabama,South,2013,0
Alabama,South,2014,2
Alabama,South,2015,6
Alaska,West,2010,2245
Alaska,West,2011,1409
Alaska,West,2012,1166
Alaska,West,2013,1329
Alaska,West,2014,1296
Alaska,West,2015,1575
Connecticut,Northeast,2010,0
Connecticut,Northeast,2011,0
Connecticut,Northeast,2012,0
Connecticut,Northeast,2013,0
Connecticut,Northeast,2014,0
Connecticut,Northeast,2015,1
Missouri,Midwest,2010,2
Missouri,Midwest,2011,3
Missouri,Midwest,2012,2
Missouri,Midwest,2013,0
Missouri,Midwest,2014,1
Missouri,Midwest,2015,5
You have a couple of problems. First, you're not using your line generator. It should be:
.attr("d", function(d) {
return valueLine(d)
})
Second, you are parsing the date strings, but then converting them to strings again. So, change your line generator (or do not use them as a key, which returns a string):
var valueLine = d3.line()
.x(function(d) {
return xScale(new Date(d.key));
})
.y(function(d) {
return yScale(d.value);
})
Finally, each datum for the line generator must be an array itself. So:
var paths = regYear.selectAll(".line")
.data(function(d) {
return [d.values]
})
Here is your code with those changes (and a little CSS for the paths):
path {
fill: none;
stroke: black;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Nested Chart</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<style type="text/css">
.pagebreak {
page-break-before: always;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
.point {
fill: none;
size: 2px
}
</style>
</head>
<div style="width:800px; margin:0 auto;" class='body'></div>
<div class="pagebreak"> </div>
<body>
<script type="text/javascript">
var parseTime = d3.timeParse("%Y");
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
w = 960 - margin.left - margin.right,
h = 500 - margin.top - margin.bottom;
var padding = 20;
/////////////////get the data//////////////////
const csv = `state,region,year,count
Alabama,South,2010,1
Alabama,South,2011,1
Alabama,South,2012,0
Alabama,South,2013,0
Alabama,South,2014,2
Alabama,South,2015,6
Alaska,West,2010,2245
Alaska,West,2011,1409
Alaska,West,2012,1166
Alaska,West,2013,1329
Alaska,West,2014,1296
Alaska,West,2015,1575
Connecticut,Northeast,2010,0
Connecticut,Northeast,2011,0
Connecticut,Northeast,2012,0
Connecticut,Northeast,2013,0
Connecticut,Northeast,2014,0
Connecticut,Northeast,2015,1
Missouri,Midwest,2010,2
Missouri,Midwest,2011,3
Missouri,Midwest,2012,2
Missouri,Midwest,2013,0
Missouri,Midwest,2014,1
Missouri,Midwest,2015,5`;
const dataset = d3.csvParse(csv);
dataset.forEach(function(d) {
d.date = parseTime(d.year);
d.region = d['region'];
d.state = d['state'];
d.count = d['count'];
//console.log(d)
});
/////////////////scales the data//////////////////
var xScale = d3.scaleTime()
.domain([d3.min(dataset, function(d) {
return d.date
}), d3.max(dataset, function(d) {
return d.date
})]).range([padding, w - padding * 2])
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) {
return d.count
})]).range([h - padding, padding])
var xAxis = d3.axisBottom().scale(xScale);
var yAxis = d3.axisLeft().scale(yScale);
/////////////////charts start here//////////////////
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 + ")");
//Define the line
var valueLine = d3.line()
.x(function(d) {
return xScale(new Date(d.key));
})
.y(function(d) {
return yScale(d.value);
})
var nest = d3.nest()
.key(function(d) {
return d.region;
})
.key(function(d) {
return d.date;
})
.rollup(function(leaves) {
return d3.sum(leaves, function(d) {
return (d.count)
});
})
.entries(dataset)
var color = d3.scaleOrdinal(d3.schemeCategory10); // set the colour scale
var regYear = svg.selectAll(".regYear")
.data(nest)
.enter()
.append("g")
// console.log(regYear)
var paths = regYear.selectAll(".line")
.data(function(d) {
return [d.values]
})
.enter()
.append("path");
// Draw the line
paths
.attr("d", function(d) {
return valueLine(d)
})
.attr("class", "line")
svg.append("g").attr("class", "axis").attr("transform", "translate(0," + (h - padding) + ")").call(xAxis);
//draw Y axis
svg.append("g").attr("class", "axis").attr("transform", "translate(" + padding + ",0)").call(yAxis);
// add label
svg.append("text").attr("x", (w / 2)).attr("y", h + 30).attr("text-anchor", "middle").text("Year");
svg.append("text").attr("x", padding).attr("y", padding - 20).attr("text-anchor", "middle").text("# of Events");
//add title
svg.append("text").attr("x", (w / 2)).attr("y", padding).attr("text-anchor", "middle").text("Events per Year by Category");
// add legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("x", w - 65)
.attr("y", 25)
.attr("height", 100)
.attr("width", 100);
////////////////////////////////////END///////////////////////////
</script>
</body>
</html>
I'm learing how to plot graphs with D3 and I want to plot on the graph an area ,or similar, to indicate that the dates of the weekend are different than the rest (I have attach and image to explain myself better). What I have right now is the Fiddle I show later.
As you can see, on the data, there are days that don't have values and their respective days are missing.
JSFiddle of what I have accoplish right now:
JSFiddle
var data = [
{"date":"1-May-13","close":58.13},
{"date":"30-Apr-13","close":53.98},
{"date":"27-Apr-13","close":67.00},
{"date":"26-Apr-13","close":89.70},
{"date":"25-Apr-13","close":99.00},
{"date":"24-Apr-13","close":130.28},
{"date":"23-Apr-13","close":166.70},
{"date":"20-Apr-13","close":234.98},
{"date":"19-Apr-13","close":345.44},
{"date":"18-Apr-13","close":443.34},
];
var margin = {top: 20, right: 50, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse,
bisectDate = d3.bisector(function(d) { return d.date; }).left,
formatValue = d3.format(",.2f"),
formatCurrency = function(d) { return "$" + formatValue(d); };
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.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 + ")");
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
data.sort(function(a, b) {
return a.date - b.date;
});
x.domain([data[0].date, data[data.length - 1].date]);
y.domain(d3.extent(data, function(d) { return d.close; }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Price ($)");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
focus.select("text").text(formatCurrency(d.close));
}
});
CSS:
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: none;
stroke: steelblue;
}
And an image of what I want to accomplish:
Given your x scale domain, which can be better defined with:
x.domain(d3.extent(data, function(d) {
return d.date;
}))
We can populate an array with all the Saturdays between the first date (which is x.domain()[0]) and the last date (which is x.domain()[1]). We only need to find the Saturdays: the bars' width will be Saturday + 2 days (which will cover the whole Saturday and the whole Sunday).
There are several functions to do this, here is one:
function findSat(date1, date2) {
if (date1 > date2) return false;
var saturdays = [];
while (date1 < date2) {
if (date1.getDay() === 6) saturdays.push(new Date(date1));
date1.setDate(date1.getDate() + 1);
}
return saturdays;
}
Using that function, we can populate an array with all the Saturdays between your first and last date:
var rectDate = findSat(x.domain()[0], x.domain()[1]);
Now, we use that rectDate array to create the rectangles:
var rects = svg.selectAll(".rects")
.data(rectDate)
.enter()
.append("rect")
.attr("y", margin.top)
.attr("height", height - margin.bottom)
.attr("x", d => x(d))
.attr("width", d => x(d3.time.day.offset(d, +2)) - x(d))
.attr("fill", "yellow");
Notice that, for the width, we subtract "Saturday + 2 days" from Saturday:
.attr("width", d => x(d3.time.day.offset(d, +2)) - x(d))
Here is your updated fiddle: http://jsfiddle.net/36yaot6t/
And here the same code, in a Stack snippet:
var data = [{
"date": "1-May-13",
"close": 58.13
}, {
"date": "30-Apr-13",
"close": 53.98
}, {
"date": "27-Apr-13",
"close": 67.00
}, {
"date": "26-Apr-13",
"close": 89.70
}, {
"date": "25-Apr-13",
"close": 99.00
}, {
"date": "24-Apr-13",
"close": 130.28
}, {
"date": "23-Apr-13",
"close": 166.70
}, {
"date": "20-Apr-13",
"close": 234.98
}, {
"date": "19-Apr-13",
"close": 345.44
}, {
"date": "18-Apr-13",
"close": 443.34
}, ];
var margin = {
top: 20,
right: 50,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse,
bisectDate = d3.bisector(function(d) {
return d.date;
}).left,
formatValue = d3.format(",.2f"),
formatCurrency = function(d) {
return "$" + formatValue(d);
};
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.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 + ")");
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
data.sort(function(a, b) {
return a.date - b.date;
});
x.domain(d3.extent(data, function(d) {
return d.date;
}))
y.domain(d3.extent(data, function(d) {
return d.close;
}));
function findSat(date1, date2) {
if (date1 > date2) return false;
var saturdays = [];
while (date1 < date2) {
if (date1.getDay() === 6) saturdays.push(new Date(date1));
date1.setDate(date1.getDate() + 1);
}
return saturdays;
}
var rectDate = findSat(x.domain()[0], x.domain()[1]);
var rects = svg.selectAll(".rects")
.data(rectDate)
.enter()
.append("rect")
.attr("y", margin.top)
.attr("height", height - margin.bottom)
.attr("x", d => x(d))
.attr("width", d => x(d3.time.day.offset(d, +2)) - x(d))
.attr("fill", "yellow");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Price ($)");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() {
focus.style("display", null);
})
.on("mouseout", function() {
focus.style("display", "none");
})
.on("mousemove", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
focus.select("text").text(formatCurrency(d.close));
}
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: none;
stroke: steelblue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I'm new to d3, but pretty familiar with the HighCharts api.
I've seen lots of examples of multiple d3 charts on the same page; but can't seem to find examples of one chart overlaying/sitting directly on top of another chart. Is this possible?
With HighCharts, you can define multiple chart types in the plotOptions config object. Is there something similar with d3? Or, how could you do this with d3?
I would effectively like to have a line graph on top of a bar chart. There will be different 'stages' according to the data, so some of the bar's could be inactive/empty.
Additionally, I need to display an indicator to show where the 'stage' is currently; and ensure that this is all responsive.
Example (rough mockup):
After researching d3 and looking for similar examples, I am thinking that maybe d3 isn't the best choice for this; maybe a custom CSS/JS/HTML solution (inside an angular app) would be better.
Any recommendations or pointers would be very appreciated.
Here's a quick mock-up started from this excellent bar chart example:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.point rect {
fill: steelblue;
}
.point circle {
fill: orange;
}
.point rect:hover {
fill: brown;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: orange
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 75, right: 20, bottom: 30, left: 40},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
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 = "abcdefghijklmnopqrstuvwxyz".split("").map(function(d){
return {
letter: d,
bar: Math.random() * 10,
line: Math.random() * 10
};
})
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d3.max([d.bar, d.line]); })]);
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");
var points = svg.selectAll(".point")
.data(data)
.enter().append("g")
.attr("class", "point");
points.append('rect')
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.bar); })
.attr("height", function(d) { return height - y(d.bar); });
points.append('circle')
.attr("r", 5)
.attr("cx", function(d){ return x(d.letter) + x.rangeBand() / 2; })
.attr("cy", function(d){ return y(d.line)});
var line = d3.svg.line()
.x(function(d) { return x(d.letter) + x.rangeBand() / 2; })
.y(function(d) { return y(d.line); });
svg.append("path")
.attr("class", "line")
.datum(data)
.attr("d", line);
var indicator = svg.append("g")
.attr("r", 5)
.attr("transform", "translate(" + (x("q") + x.rangeBand() / 2) + "," + -20 + ")");
indicator.append("circle")
.attr("r", 40)
.style("fill", "red");
indicator.append("text")
.text("!")
.style("fill", "white")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.style("font-size", 70);
indicator.append("line")
.attr("y1", 20)
.attr("y2", height + 20)
.attr("x1", 0)
.attr("x2", 0)
.style("stroke", "red")
.style("stroke-width", "4px");
</script>
New Solution Based on Comments
Given your input data, here's a new example. I went a bit overboard here, so please ask question on any confusing bits. I tried to comment it out:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
rect {
fill: steelblue;
}
circle {
fill: orange;
}
rect:hover {
fill: brown;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: orange
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {
top: 75,
right: 20,
bottom: 30,
left: 40
},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
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 + ")");
// here's your data
var data =
{
'point1': [{
'value': 50
}, {
'value': 100
}, {
'value': 100
}, {
'value': 150
}],
'point2': [{
'value': 25
}, {
'value': 40
}, {
'value': 60
}],
'point3': [{
'value': 25
}]
};
// d3ify your data
// d3 likes arrays of objects, you have an object of objects
// so first make it an array
var barData = d3.entries(data);
// set x domain
x.domain(barData.map(function(d){ return d.key }));
// create lineData
var lineData = [];
barData.forEach(function(d0, i){
d0.mean = d3.mean(d0.value, function(d1){ return d1.value });
d0.max = d3.max(d0.value, function(d1){ return d1.value});
var N = d0.value.length,
// this is an inner scale
// that represents each bar
s = d3.scale.linear().range([
x(d0.key) + (x.rangeBand() / N) / 2,
x(d0.key) + x.rangeBand()
]).domain([
0, N
])
d0.value.forEach(function(d1, j){
lineData.push({
x: s(j), // this is the pixel position of x, it's jittered on the bar
y: d1.value // this is the user position of y
})
});
});
// set y domain
y.domain([0, d3.max(barData, function(d) {
return d.max;
})]);
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");
// draw bars
var bars = svg.selectAll(".bar")
.data(barData)
.enter()
.append('rect')
.attr('class', 'bar')
.attr("x", function(d) {
return x(d.key);
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.mean);
})
.attr("height", function(d) {
return height - y(d.mean);
});
// add points
var points = svg.selectAll('point')
.data(lineData)
.enter()
.append('circle')
.attr('class', 'point')
.attr("r", 5)
.attr("cx", function(d) {
return d.x; // already pixel position
})
.attr("cy", function(d) {
return y(d.y)
});
var line = d3.svg.line()
.x(function(d) {
return d.x; // already pixel position
})
.y(function(d) {
return y(d.y);
});
svg.append("path")
.attr("class", "line")
.datum(lineData)
.attr("d", line);
var indicator = svg.append("g")
.attr("transform", "translate(" + (x("point2") + x.rangeBand() / 2) + "," + -20 + ")");
indicator.append("circle")
.attr("r", 40)
.style("fill", "red");
indicator.append("text")
.text("!")
.style("fill", "white")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.style("font-size", 70);
indicator.append("line")
.attr("y1", 20)
.attr("y2", height + 20)
.attr("x1", 0)
.attr("x2", 0)
.style("stroke", "red")
.style("stroke-width", "4px");
</script>
Happy d3ing!
i am creating a stacked bar chart using d3. The graph axis ares getting rendered but not the bar chart and JS console is also not throwing any errors.
Here is the code:
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var data = [{
data: [
["2016-01-20T10:36:00.000Z", 95.6981132075472],
["2016-01-20T11:10:00.000Z", 95.8882352941176],
["2016-01-20T11:44:00.000Z", 95.8470588235294],
["2016-01-20T12:18:00.000Z", 95.7941176470588],
["2016-01-20T12:52:00.000Z", 95.675],
["2016-01-20T13:26:00.000Z", 95.7573529411765],
["2016-01-20T14:00:00.000Z", 95.8294117647059],
["2016-01-20T14:34:00.000Z", 95.7736842105263]
],
label: "a"
}, {
data: [
["2016-01-20T10:36:00.000Z", 3.18867924528302],
["2016-01-20T11:10:00.000Z", 3.15441176470588],
["2016-01-20T11:44:00.000Z", 3.15],
["2016-01-20T12:18:00.000Z", 3.16323529411765],
["2016-01-20T12:52:00.000Z", 3.13970588235294],
["2016-01-20T13:26:00.000Z", 3.17794117647059],
["2016-01-20T14:00:00.000Z", 3.16617647058824],
["2016-01-20T14:34:00.000Z", 3.18888888888889]
],
label: "b"
}];
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 x = d3.scale.ordinal()
.rangeRoundBands([0, width]);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var z = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var stack = d3.layout.stack()
.values(function(d) {
return d.data;
})
.x(function(d) {
return new Date(d[0]);
})
.y(function(d) {
return d[1];
});
var layers = stack(data);
var ary = [];
layers.forEach(function(d) {
ary.push(d.data);
});
x.domain(d3.extent(d3.merge(ary), function(d) {
return new Date(d[0]);
}));
y.domain([0, d3.max(d3.merge(ary), function(d) {
return d.y0 + d.y;
})]);
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
return z(i);
});
layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
})
.attr("width", x.rangeBand() - 1);
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "axis y")
.call(yAxis);
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Please let me know what i am doing wrong in the above posted code and help me fix this code.
I have fixed the issues with my axis in the above code. And able to display the stack bar due to the error pointed by #adilapapaya.
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var data = [{
data: [
["2016-01-20T10:36:00.000Z", 95.6981132075472],
["2016-01-20T11:10:00.000Z", 95.8882352941176],
["2016-01-20T11:44:00.000Z", 95.8470588235294],
["2016-01-20T12:18:00.000Z", 95.7941176470588],
["2016-01-20T12:52:00.000Z", 95.675],
["2016-01-20T13:26:00.000Z", 95.7573529411765],
["2016-01-20T14:00:00.000Z", 95.8294117647059],
["2016-01-20T14:34:00.000Z", 95.7736842105263]
],
label: "a"
}, {
data: [
["2016-01-20T10:36:00.000Z", 3.18867924528302],
["2016-01-20T11:10:00.000Z", 3.15441176470588],
["2016-01-20T11:44:00.000Z", 3.15],
["2016-01-20T12:18:00.000Z", 3.16323529411765],
["2016-01-20T12:52:00.000Z", 3.13970588235294],
["2016-01-20T13:26:00.000Z", 3.17794117647059],
["2016-01-20T14:00:00.000Z", 3.16617647058824],
["2016-01-20T14:34:00.000Z", 3.18888888888889]
],
label: "b"
}];
var parseDate = d3.time.format("%Y-%m-%dT%H:%M:%S.%LZ").parse;
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 x = d3.scale.ordinal()
.rangeRoundBands([0, width], 0.7);
var y = d3.scale.linear()
.range([height, 0]);
var z = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom").ticks(d3.time.hour, 1)
.tickSize(0).innerTickSize(-height).outerTickSize(0).tickFormat(d3.time.format("%H:%M"));
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var stack = d3.layout.stack()
.values(function(d) {
return d.data;
})
.x(function(d) {
return parseDate(d[0]);
})
.y(function(d) {
return d[1];
});
var layers = stack(data);
var ary = [];
layers.forEach(function(d) {
ary.push(d.data);
});
console.log("d = ", ary)
x.domain(ary[0].map(function(d) {
return parseDate(d[0]);
}));
y.domain([0, d3.max(d3.merge(ary), function(d) {
return d.y0 + d.y;
})]);
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
return z(i);
});
layer.selectAll("rect")
.data(function(d) {
return d.data;
})
.enter().append("rect")
.attr("x", function(d) {
return x(parseDate(d[0]));
})
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
})
.attr("width", x.rangeBand() - 1);
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "axis y")
.call(yAxis);
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
The issue is in this section:
layer.selectAll("rect")
.data(function(d) {
return d;
})
.attr("x", function(d) {
console.log("d = ", d) // this doesn't get called since there is no data
return x(d.x);
})
d is an object but d3 wants an array so it's not storing the data you pass it.
I think this is what you want (though the output graph looks way off so I'm not 100% sure...). Either way, just remember that you want to return an array in the data function.
layer.selectAll("rect")
.data(function(d) {
return d.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.