I'm trying to create a calendar heatmap with D3, very similar to the Github contribution calendar.
I can't get the day of week to align correctly. It seems to repeat for every month and doesn't have correct margins or alignment. I only want the days to display once, on the left side of the calendar.
Just like this:
Here is what mine looks like:
Here is my code:
<style>
#calendar {
margin: 20px;
}
.month {
margin-right: 8px;
}
.month-name {
font-size: 85%;
fill: #777;
font-family: Muli, san-serif;
}
.day.hover {
stroke: #6d6E70;
stroke-width: 2;
}
.day.focus {
stroke: #ffff33;
stroke-width: 2;
}
</style>
<div style="text-align:center;" id="calendar"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script>
function drawCalendar(dateData){
var weeksInMonth = function(month){
var m = d3.timeMonth.floor(month)
return d3.timeWeeks(d3.timeWeek.floor(m), d3.timeMonth.offset(m,1)).length;
}
//var minDate = new Date(2018, 12, 31);
var minDate = d3.min(dateData, function(d) { return new Date(2018, 12, 1 ) });
//var minDate = d3.min(dateData, function(d) { return new Date(d.day) });
console.log(minDate);
//var maxDate = new Date(2019, 11, 30);
var maxDate = d3.max(dateData, function(d) { return new Date(2019, 11, 30 ) });
console.log(maxDate);
var cellMargin = 2,
calY=10,//offset of calendar in each group
xOffset=-5,
dayName = ['Su','Mo','Tu','We','Th','Fr','Sa'],
cellSize = 20;
var day = d3.timeFormat("%w"),
week = d3.timeFormat("%U"),
format = d3.timeFormat("%Y-%m-%d"),
titleFormat = d3.utcFormat("%a, %d-%b"),
monthName = d3.timeFormat("%B"),
months= d3.timeMonth.range(d3.timeMonth.floor(minDate), maxDate);
var svg = d3.select("#calendar").selectAll("svg")
.data(months)
.enter().append("svg")
.attr("class", "month")
.attr("height", ((cellSize * 7) + (cellMargin * 8) + 20) ) // the 20 is for the month labels
.attr("width", function(d) {
var columns = weeksInMonth(d);
return ((cellSize * columns) + (cellMargin * (columns + 1)));
})
.append("g")
svg.append("text")
.attr("class", "month-name")
.attr("y", (cellSize * 7) + (cellMargin * 8) + 15 )
.attr("x", function(d) {
var columns = weeksInMonth(d);
return (((cellSize * columns) + (cellMargin * (columns + 1))) / 2);
})
.attr("text-anchor", "middle")
.text(function(d) { return monthName(d); })
//create day labels
var days = ['Su','Mo','Tu','We','Th','Fr','Sa'];
var dayLabels=svg.append("g").attr("id","dayLabels")
days.forEach(function(d,i) {
dayLabels.append("text")
.attr("class","dayLabel")
.attr("x",xOffset)
.attr("y",function(d) { return calY+(i * cellSize); })
.text(d);
})
var rect = svg.selectAll("rect.day")
.data(function(d, i) { return d3.timeDays(d, new Date(d.getFullYear(), d.getMonth()+1, 1)); })
.enter().append("rect")
.attr("class", "day")
.attr("width", cellSize)
.attr("height", cellSize)
.attr("rx", 3).attr("ry", 3) // rounded corners
.attr("fill", '#eaeaea') // default light grey fill
.attr("y", function(d) { return (day(d) * cellSize) + (day(d) * cellMargin) + cellMargin; })
.attr("x", function(d) { return ((week(d) - week(new Date(d.getFullYear(),d.getMonth(),1))) * cellSize) + ((week(d) - week(new Date(d.getFullYear(),d.getMonth(),1))) * cellMargin) + cellMargin ; })
.on("mouseover", function(d) {
d3.select(this).classed('hover', true);
})
.on("mouseout", function(d) {
d3.select(this).classed('hover', false);
})
.datum(format);
rect.append("title")
.text(function(d) { return titleFormat(new Date(d)); });
var lookup = d3.nest()
.key(function(d) { return d.day; })
.rollup(function(leaves) {
return d3.sum(leaves, function(d){ return parseInt(d.count); });
})
.object(dateData);
var scale = d3.scaleLinear()
.domain(d3.extent(dateData, function(d) { return parseInt(d.count); }))
.range([0.2,1]); // the interpolate used for color expects a number in the range [0,1] but i don't want the lightest part of the color scheme
rect.filter(function(d) { return d in lookup; })
.style("fill", function(d) { return d3.interpolateYlGn(scale(lookup[d])); })
.select("title")
.text(function(d) { return titleFormat(new Date(d)) + ": " + lookup[d]; });
}
d3.csv("dates.csv", function(response){
drawCalendar(response);
})
</script>
There is also an input csv file that contains the following values:
day,count
2019-05-12,171
2019-06-17,139
2019-05-02,556
2019-04-10,1
2019-05-04,485
2019-03-27,1
2019-05-26,42
2019-05-25,337
2019-05-23,267
2019-05-05,569
2019-03-31,32
2019-03-25,128
2019-05-13,221
2019-03-30,26
2019-03-15,3
2019-04-24,10
2019-04-27,312
2019-03-20,99
2019-05-10,358
2019-04-01,15
2019-05-11,199
2019-07-06,744
2019-05-08,23
2019-03-28,98
2019-03-29,64
2019-04-30,152
2019-03-21,148
2019-03-19,20
2019-05-07,69
2019-04-29,431
2019-04-25,330
2019-04-28,353
2019-04-18,9
2019-01-10,1
2019-01-09,2
2019-03-26,21
2019-05-27,18
2019-04-19,10
2019-04-06,1
2019-04-12,214
2019-05-03,536
2019-07-03,3
2019-06-16,1
2019-03-24,138
2019-04-26,351
2019-04-23,14
2019-05-01,19
2019-07-05,523
2019-05-22,3
2019-05-09,430
2019-05-24,472
2019-04-11,172
2019-03-17,7
2019-05-14,10
2019-05-06,449
2019-07-04,295
2019-05-15,12
2019-03-23,216
2019-03-18,47
2019-03-22,179
Typically you allow for a margin in your SVG, something like this:
const margin = { top: 10, right: 20, bottom: 10, left: 5 }
const svg = d3
.select('#chart')
.append('svg')
.attr('width', 900 + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
Basically you create an SVG element that is bigger than your drawing area, and then you move (translate) the chart in by the margins. Then your axis can appear in the margin
I had perfectly adequate ticks in my earlier statically sized plot using d3.js v4; once I made it resizable, the ticks and values disappeared from the y axis.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test Plot Viewer</title>
<script src="js/lib/d3.v4.min.js"></script>
<script src="js/lib/jquery.min.js"></script>
<style>
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
#chart {
position: fixed;
left: 55px;
right: 15px;
top: 10px;
bottom: 55px;
}
</style>
</head>
<body>
<div id="chart"></div>
<script>
var chartDiv = document.getElementById("chart");
var svg = d3.select(chartDiv).append("svg");
// parse the date time
var parseTime = d3.timeParse("%m/%d %H:%M");
function render() {
$("svg").empty();
// Extract the width and height that was computed by CSS.
var width = chartDiv.clientWidth;
var height = chartDiv.clientHeight;
// Use the extracted size to set the size of an SVG element.
svg
.attr("width", width)
.attr("height", height);
var margin = {top: 10, right: 15, bottom: 55, left: 55};
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the line
var line = d3.line()
.x(function(d) { return x(d.time); })
.y(function(d) { return y(d.solar); });
// Get the data
d3.csv("data_fred.csv", function(error, data) {
if (error) throw error;
// format the data
data.forEach(function(d) {
d.time = parseTime(d.time);
d.solar = +d.solar;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.time; }));
y.domain([0, d3.max(data, function(d) { return d.solar; })]);
// Add the valueline path.
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", line);
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.tickFormat(d3.timeFormat("%m/%d %H:%M ")));
// Add the Y Axis
svg.append("g")
.call(d3.axisLeft(y))
.ticks(10);
});
}
render();
// Redraw based on the new size whenever the browser window is resized
window.addEventListener("resize", render);
</script>
</body>
</html>
The submitter function wants more details, but I have none...
blah
blah
blah
blah
characters added to pad non-code content.
The ticks are now gone on the y axis. I've added the .tick attribute to the y axis, but no joy.
How do I get my y axis ticks back on this responsive version of the chart? TIA
Posted later: Anyone? My non-responsive version of the code is drawing correctly; "responsifying" it makes the y-axis ticks and units disappear. I've tried almost every permutation of command ordering and placement, but no luck.
Whats happening here is your Y axis ticks are getting hidden because they're not in the viewport. What you need to do is put all the elements in your svg in a <g> wrapper and translate it by left and top margins.
Here's a fiddle
var chartDiv = document.getElementById("chart");
var svg = d3.select(chartDiv).append("svg");
var g = svg.append('g');
function render() {
$('svg').empty();
// Extract the width and height that was computed by CSS.
var width = $('#chart').width();
var height = $('#chart').height();
// Use the extracted size to set the size of an SVG element.
svg
.attr("width", width)
.attr("height", height);
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 50,
left: 40
};
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
// parse the date time
var parseTime = d3.timeParse("%m/%d %H:%M");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the line
var valueline = d3.line()
.x(function(d) {
return x(d.time);
})
.y(function(d) {
return y(d.solar);
});
// Get the data
var data = [{
'time': '11/30 04:55',
'solar': -1.1
}, {
'time': '11/30 05:00',
'solar': -1.1
}, {
'time': '11/30 05:05',
'solar': -1.5
}, {
'time': '11/30 05:10',
'solar': -2
}, {
'time': '11/30 05:15',
'solar': 1
}]
// format the data
data.forEach(function(d) {
d.time = parseTime(d.time);
d.solar = +d.solar;
});
console.log(data)
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.time;
}));
var yExtent = d3.extent(data, function(d) {
return d.solar;
})
y.domain(yExtent);
g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Add the valueline path.
g.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline);
// Add the X Axis
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.tickFormat(d3.timeFormat("%m/%d %H:%M ")))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-45)");
// Add the Y Axis
g.append("g")
.call(d3.axisLeft(y));
}
// d3.select("svg").remove();
// svg.remove();
// d3.selectAll("g > *").remove()
// d3.selectAll("chartDiv.path.line").remove();
// d3.select("path.line").remove();
render();
// Redraw based on the new size whenever the browser window is resized.
window.addEventListener("resize", render);
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="chart"></div>
Happy coding :)
Got it - the axis ticks were disappearing off the left edge of the window - fixed that with a transform/translate:
// Add the Y Axis
svg.append("g")
.attr("transform", "translate(40 ,10)")
.call(d3.axisLeft(y));
...with a similar translation of the x axis and path to match.
Also, the axis scale now appeared with an extent of 0 to 1.0, as it wasn't being passed out of the file read loop since it was an asynchronous operation. Bringing the svg.append's into the data read loop restored my "normal" units to the axis.
I'm having an issue where I'm plotting circles on a multi-line chart which has a different color for each line (circles match the colors). The catch is the way I have the function I'm writing circles on top of circles - which is an issue when I try to hide specific ones.
I want to plot circles based on then name but I'm unsure how to limit the above D3 functions to one name only - currently it plots all circles for each line.
Is there a way to use d.name to limit the plotting to one name each time?
thanks
I think d3.nest is what you want:
var names = d3.nest()
.key(function(d){ return d.name; })
.entries(data);
var data = [
{"name":"1.0E-6MHz","value":"20.0","date":"2017-08-25 21:00:00"},{"name":"1.0E-6MHz","value":"93.8","date":"2017-08-25 22:00:00"},{"name":"1.0E-6MHz","value":"86.2","date":"2017-08-25 23:00:00"},{"name":"1.0E-6MHz","value":"79.2","date":"2017-08-26 00:00:00"},{"name":"1.0E-6MHz","value":"81.7","date":"2017-08-26 01:00:00"},{"name":"1.0E-6MHz","value":"76.2","date":"2017-08-26 02:00:00"},{"name":"1.0E-6MHz","value":"86.2","date":"2017-08-26 03:00:00"},{"name":"1.0E-6MHz","value":"89.2","date":"2017-08-26 04:00:00"},{"name":"1.0E-6MHz","value":"91.9","date":"2017-08-26 05:00:00"},{"name":"1.0E-6MHz","value":"78.2","date":"2017-08-26 06:00:00"},{"name":"1.0E-6MHz","value":"86.2","date":"2017-08-26 07:00:00"},{"name":"1.0E-6MHz","value":"82.2","date":"2017-08-26 08:00:00"},{"name":"1.0E-6MHz","value":"96.2","date":"2017-08-26 09:00:00"},{"name":"1.0E-6MHz","value":"88.7","date":"2017-08-26 10:00:00"},{"name":"1.0E-6MHz","value":"92.3","date":"2017-08-26 11:00:00"},{"name":"1.0E-6MHz","value":"80.2","date":"2017-08-26 12:00:00"},{"name":"1.0E-6MHz","value":"76.2","date":"2017-08-26 13:00:00"},{"name":"1.0E-6MHz","value":"93.2","date":"2017-08-26 14:00:00"},{"name":"1.0E-6MHz","value":"89.2","date":"2017-08-26 15:00:00"},{"name":"1.0E-6MHz","value":"79.2","date":"2017-08-26 16:00:00"},{"name":"1.0E-6MHz","value":"90.2","date":"2017-08-26 17:00:00"},{"name":"1.0E-6MHz","value":"85.2","date":"2017-08-26 18:00:00"},{"name":"1.0E-6MHz","value":"86.5","date":"2017-08-26 19:00:00"},{"name":"1.0E-6MHz","value":"76.2","date":"2017-08-26 20:00:00"},{"name":"1.0E-6MHz","value":"94.5","date":"2017-08-26 21:00:00"},
{"name":"2.0E-6MHz","value":"26.2","date":"2017-08-25 21:00:00"},{"name":"2.0E-6MHz","value":"33.8","date":"2017-08-25 22:00:00"},{"name":"2.0E-6MHz","value":"26.2","date":"2017-08-25 23:00:00"},{"name":"2.0E-6MHz","value":"66.2","date":"2017-08-26 00:00:00"},{"name":"2.0E-6MHz","value":"3.7","date":"2017-08-26 01:00:00"},{"name":"2.0E-6MHz","value":"76.2","date":"2017-08-26 02:00:00"},{"name":"2.0E-6MHz","value":"36.2","date":"2017-08-26 03:00:00"},{"name":"2.0E-6MHz","value":"22.2","date":"2017-08-26 04:00:00"},{"name":"2.0E-6MHz","value":"31.6","date":"2017-08-26 05:00:00"},{"name":"2.0E-6MHz","value":"44.2","date":"2017-08-26 06:00:00"},{"name":"2.0E-6MHz","value":"7.2","date":"2017-08-26 07:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 08:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 09:00:00"},{"name":"2.0E-6MHz","value":"21.7","date":"2017-08-26 10:00:00"},{"name":"2.0E-6MHz","value":"22.3","date":"2017-08-26 11:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 12:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 13:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 14:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 15:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 16:00:00"},{"name":"2.0E-6MHz","value":"96.2","date":"2017-08-26 17:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 18:00:00"},{"name":"2.0E-6MHz","value":"33.5","date":"2017-08-26 19:00:00"},{"name":"2.0E-6MHz","value":"46.2","date":"2017-08-26 20:00:00"},{"name":"2.0E-6MHz","value":"44.5","date":"2017-08-26 21:00:00"},
{"name":"3.0E-6MHz","value":"38.2","date":"2017-08-25 21:00:00"},{"name":"3.0E-6MHz","value":"43.8","date":"2017-08-25 22:00:00"},{"name":"3.0E-6MHz","value":"56.2","date":"2017-08-25 23:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 00:00:00"},{"name":"3.0E-6MHz","value":"53.7","date":"2017-08-26 01:00:00"},{"name":"3.0E-6MHz","value":"3.2","date":"2017-08-26 02:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 03:00:00"},{"name":"3.0E-6MHz","value":"66.2","date":"2017-08-26 04:00:00"},{"name":"3.0E-6MHz","value":"37.9","date":"2017-08-26 05:00:00"},{"name":"3.0E-6MHz","value":"42.2","date":"2017-08-26 06:00:00"},{"name":"3.0E-6MHz","value":"4.2","date":"2017-08-26 07:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 08:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 09:00:00"},{"name":"3.0E-6MHz","value":"21.7","date":"2017-08-26 10:00:00"},{"name":"3.0E-6MHz","value":"22.3","date":"2017-08-26 11:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 12:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 13:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 14:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 15:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 16:00:00"},{"name":"3.0E-6MHz","value":"96.2","date":"2017-08-26 17:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 18:00:00"},{"name":"3.0E-6MHz","value":"33.5","date":"2017-08-26 19:00:00"},{"name":"3.0E-6MHz","value":"46.2","date":"2017-08-26 20:00:00"},{"name":"3.0E-6MHz","value":"34.5","date":"2017-08-26 21:00:00"},
{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-25 21:00:00"},{"name":"4.0E-6MHz","value":"53.8","date":"2017-08-25 22:00:00"},{"name":"4.0E-6MHz","value":"86.2","date":"2017-08-25 23:00:00"},{"name":"4.0E-6MHz","value":"56.2","date":"2017-08-26 00:00:00"},{"name":"4.0E-6MHz","value":"23.7","date":"2017-08-26 01:00:00"},{"name":"4.0E-6MHz","value":"16.2","date":"2017-08-26 02:00:00"},{"name":"4.0E-6MHz","value":"76.2","date":"2017-08-26 03:00:00"},{"name":"4.0E-6MHz","value":"82.2","date":"2017-08-26 04:00:00"},{"name":"4.0E-6MHz","value":"39.9","date":"2017-08-26 05:00:00"},{"name":"4.0E-6MHz","value":"6.2","date":"2017-08-26 06:00:00"},{"name":"4.0E-6MHz","value":"22.2","date":"2017-08-26 07:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 08:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 09:00:00"},{"name":"4.0E-6MHz","value":"21.7","date":"2017-08-26 10:00:00"},{"name":"4.0E-6MHz","value":"22.3","date":"2017-08-26 11:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 12:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 13:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 14:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 15:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 16:00:00"},{"name":"4.0E-6MHz","value":"96.2","date":"2017-08-26 17:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 18:00:00"},{"name":"4.0E-6MHz","value":"33.5","date":"2017-08-26 19:00:00"},{"name":"4.0E-6MHz","value":"46.2","date":"2017-08-26 20:00:00"},{"name":"4.0E-6MHz","value":"24.5","date":"2017-08-26 21:00:00"},
{"name":"5.0E-6MHz","value":"66.2","date":"2017-08-25 21:00:00"},{"name":"5.0E-6MHz","value":"63.8","date":"2017-08-25 22:00:00"},{"name":"5.0E-6MHz","value":"16.2","date":"2017-08-25 23:00:00"},{"name":"5.0E-6MHz","value":"86.2","date":"2017-08-26 00:00:00"},{"name":"5.0E-6MHz","value":"13.7","date":"2017-08-26 01:00:00"},{"name":"5.0E-6MHz","value":"36.2","date":"2017-08-26 02:00:00"},{"name":"5.0E-6MHz","value":"6.2","date":"2017-08-26 03:00:00"},{"name":"5.0E-6MHz","value":"21.2","date":"2017-08-26 04:00:00"},{"name":"5.0E-6MHz","value":"41.1","date":"2017-08-26 05:00:00"},{"name":"5.0E-6MHz","value":"86.2","date":"2017-08-26 06:00:00"},{"name":"5.0E-6MHz","value":"69.2","date":"2017-08-26 07:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 08:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 09:00:00"},{"name":"5.0E-6MHz","value":"21.7","date":"2017-08-26 10:00:00"},{"name":"5.0E-6MHz","value":"22.3","date":"2017-08-26 11:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 12:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 13:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 14:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 15:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 16:00:00"},{"name":"5.0E-6MHz","value":"96.2","date":"2017-08-26 17:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 18:00:00"},{"name":"5.0E-6MHz","value":"33.5","date":"2017-08-26 19:00:00"},{"name":"5.0E-6MHz","value":"46.2","date":"2017-08-26 20:00:00"},{"name":"5.0E-6MHz","value":"4.5","date":"2017-08-26 21:00:00"}
];
// parsing
data.forEach(function(d){
d.value = +d.value;
d.date = new Date(d.date);
})
// after this you will have 5 name keys of its values(in your case)
var names = d3.nest()
.key(function(d){ return d.name; })
.entries(data);
// console.log(names)
var chart = d3.select("#chart");
d3.select("#names")
.selectAll("button")
.data(names.map(function(d){ return d.key; }))
.enter()
.append("button")
.text(function(d){ return d; })
.on("click", redraw);
var svgWidth = 500,
svgHeight = 400,
radius = 5,
margin = { top: 30, right: 30, bottom: 30, left: 30 },
width = svgWidth - margin.right - margin.left,
height = svgHeight - margin.top - margin.bottom;
var xScale = d3.time.scale().range([0, width]),
yScale = d3.scale.linear().range([0, height]),
xAxis = d3.svg.axis().orient("bottom").scale(xScale),
yAxis = d3.svg.axis().orient("left");
var svg = chart.append("svg").attr({ width: svgWidth, height: svgHeight });
var gMain = svg.append("g").attr({
class: "gMain",
transform: "translate(" + [margin.left, margin.top] + ")"
}),
gYAxis = gMain.append("g").attr("class", "axis yaxis"),
gXAxis = gMain.append("g").attr({
class: "axis xaxis",
transform: "translate(0," + height + ")"
})
gPlot = gMain.append("g").attr({
class: "plot",
transform: "translate(0," + height + ")"
});
redraw("1.0E-6MHz", 0);
function redraw(name, index){
var points = names[index].values;
xScale.domain(d3.extent(points, function(d){ return d.date; }));
yScale.domain(d3.extent(points, function(d){ return d.value; }));
gXAxis.transition().call(xAxis);
gYAxis.transition().call(yAxis.scale(yScale.copy().range([height, 0])));
var update = gPlot.selectAll("circle").data(points),
enter = update.enter()
.append("circle")
.attr({
class: "circle",
r: radius,
fill: "steelblue",
cx: function(d){ return xScale(d.date); },
cy: function(d){ return -yScale(d.value); }
});
update.exit().remove();
update.transition()
.duration(700)
.attr({
cx: function(d){ return xScale(d.date); },
cy: function(d){ return -yScale(d.value); }
});
}
.axis path{
fill: none;
stroke: black;
stroke-width: 1;
}
.axis line{
stroke: black;
stroke-width: 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="names"></div>
<div id="chart"></div>
I'm wrestling with a problem of a brush not being removed correctly on a bar chart. You can see the Bl.ock here and see what's not working correctly.
In short, the brush highlights the bars that have been selected by the brush, as well as snaps to the edge of the rect to make selecting spans of time easier (there's a secondary bug here where the brush snapping isn't quite mapping correctly to the dates -- you'll see this if you try to draw the brush up to the edge of the barchart). Somewhere along the way (maybe with the rect snapping?) the background click-to-remove-brush feature stopped working (it now selects a single year span, although doesn't highlight the rect correctly). To make it easier for users, I wanted to add a button that a user can click to remove the brush when they're done (the resetBrush() function below).
My understanding was the brush selection can be cleared with brush.extent(), but when you clear the extent you then have to redraw the brush. I thought I was doing that correctly, but alas, I'm running into some problem somewhere that I can't seem to track down. Any pointers on where I'm tripping up would be greatly appreciated!
Code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: sans-serif;
color: #000;
text-rendering: optimizeLegibility;
}
.barchart {
z-index: 30;
display: block;
visibility: visible;
position: relative;
padding-top: 15px;
margin-top: 15px;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.resize path {
fill: #666;
fill-opacity: .8;
stroke: #000;
stroke-width: 1.5px;
}
.brush .extent {
stroke: #fff;
stroke-opacity: .6;
stroke-width: 2px;
fill-opacity: .1;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
brushYearStart = 1848;
brushYearEnd = 1905;
// Scales
var x = d3.scale.ordinal().rangeRoundBands([0, width - 60], .1);
var y = d3.scale.linear().range([height, 0]);
// Prepare the barchart canvas
var barchart = d3.select("body").append("svg")
.attr("class", "barchart")
.attr("width", "100%")
.attr("height", height + margin.top + margin.bottom)
.attr("y", height - height - 100)
.append("g");
var z = d3.scale.ordinal().range(["steelblue", "indianred"]);
var brushYears = barchart.append("g")
brushYears.append("text")
.attr("id", "brushYears")
.classed("yearText", true)
.text(brushYearStart + " - " + brushYearEnd)
.attr("x", 35)
.attr("y", 12);
d3.csv("years_count.csv", function (error, post) {
// Coercion since CSV is untyped
post.forEach(function (d) {
d["frequency"] = +d["frequency"];
d["frequency_discontinued"] = +d["frequency_discontinued"];
d["year"] = d3.time.format("%Y").parse(d["year"]).getFullYear();
});
var freqs = d3.layout.stack()(["frequency", "frequency_discontinued"].map(function (type) {
return post.map(function (d) {
return {
x: d["year"],
y: +d[type]
};
});
}));
x.domain(freqs[0].map(function (d) {
return d.x;
}));
y.domain([0, d3.max(freqs[freqs.length - 1], function (d) {
return d.y0 + d.y;
})]);
// Axis variables for the bar chart
x_axis = d3.svg.axis().scale(x).tickValues([1850, 1855, 1860, 1865, 1870, 1875, 1880, 1885, 1890, 1895, 1900]).orient("bottom");
y_axis = d3.svg.axis().scale(y).orient("right");
// x axis
barchart.append("g")
.attr("class", "x axis")
.style("fill", "#000")
.attr("transform", "translate(0," + height + ")")
.call(x_axis);
// y axis
barchart.append("g")
.attr("class", "y axis")
.style("fill", "#000")
.attr("transform", "translate(" + (width - 85) + ",0)")
.call(y_axis);
// Add a group for each cause.
var freq = barchart.selectAll("g.freq")
.data(freqs)
.enter().append("g")
.attr("class", "freq")
.style("fill", function (d, i) {
return z(i);
})
.style("stroke", "#CCE5E5");
// Add a rect for each date.
rect = freq.selectAll("rect")
.data(Object)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function (d) {
return x(d.x);
})
.attr("y", function (d) {
return y(d.y0) + y(d.y) - height;
})
.attr("height", function (d) {
return height - y(d.y);
})
.attr("width", x.rangeBand())
.attr("id", function (d) {
return d["year"];
});
// Draw the brush
brush = d3.svg.brush()
.x(x)
.on("brush", brushmove)
.on("brushend", brushend);
var arc = d3.svg.arc()
.outerRadius(height / 15)
.startAngle(0)
.endAngle(function(d, i) { return i ? -Math.PI : Math.PI; });
brushg = barchart.append("g")
.attr("class", "brush")
.call(brush);
brushg.selectAll(".resize").append("path")
.attr("transform", "translate(0," + height / 2 + ")")
.attr("d", arc);
brushg.selectAll("rect")
.attr("height", height);
});
// ****************************************
// Brush functions
// ****************************************
function brushmove() {
y.domain(x.range()).range(x.domain()).clamp(true);
b = brush.extent();
brushYearStart = Math.ceil(y(b[0]));
brushYearEnd = Math.ceil(y(b[1]));
// Snap to rect edge
d3.select("g.brush").call(brush.extent([y.invert(brushYearStart), y.invert(brushYearEnd)]));
// Fade all years in the histogram not within the brush
d3.selectAll("rect.bar").style("opacity", function (d, i) {
return d.x >= brushYearStart && d.x < brushYearEnd ? "1" : ".4"
});
}
function brushend() {
// Additional calculations happen here...
// filterPoints();
// colorPoints();
// styleOpacity();
// Update start and end years in upper right-hand corner of the map
d3.select("#brushYears").text(brushYearStart + " - " + brushYearEnd);
}
function resetBrush() {
d3.selectAll(".brush").remove();
d3.selectAll("brushg.resize").remove();
brush.clear();
brushg.call(brush);
}
</script>
<div id="resetMap">
<button
id="returnBrush"
class="btn btn-default"
onclick="resetBrush()"/>Remove Brush
</div>
</body>
</html>
If you execute d3.selectAll(".brush").remove(); you remove <g class="brush"></g> and his childs.
This d3.selectAll("brushg.resize").remove(); is a bug. Must to be brushg.selectAll(".resize").remove(); but is the same case that d3.selectAll(".brush").remove();.
You have to do this:
For reset the brush.extent() and fire the brush event.
function resetBrush() {
brush
.clear()
.event(d3.select(".brush"));
}
For reset #brushYears to the initial state
function brushend() {
var localBrushYearStart = (brush.empty()) ? brushYearStart : Math.ceil(y(b[0])),
localBrushYearEnd = (brush.empty()) ? brushYearEnd : Math.ceil(y(b[1]));
// Update start and end years in upper right-hand corner of the map
d3.select("#brushYears").text(localBrushYearStart + " - " + localBrushYearEnd);
}
For reset to initial values on brush event
function brushmove() {
y.domain(x.range()).range(x.domain()).clamp(true);
b = brush.extent();
3.1. To set the localBrushYearStart and localBrushYearEnd variables to initial state on brush.empty() or set to Math.ceil(brush.extent()))
var localBrushYearStart = (brush.empty()) ? brushYearStart : Math.ceil(y(b[0])),
localBrushYearEnd = (brush.empty()) ? brushYearEnd : Math.ceil(y(b[1]));
3.2. To execute brush.extent() on selection, or brush.clear() on brush.empty()
// Snap to rect edge
d3.select("g.brush").call((brush.empty()) ? brush.clear() : brush.extent([y.invert(localBrushYearStart), y.invert(localBrushYearEnd)]));
3.3. To set opacity=1 years on brush.empty() or selection, and opacity=.4 on not selected years
// Fade all years in the histogram not within the brush
d3.selectAll("rect.bar").style("opacity", function(d, i) {
return d.x >= localBrushYearStart && d.x < localBrushYearEnd || brush.empty() ? "1" : ".4";
});
}
Check the corrections on my BL.OCKS
Just do this
function resetBrush() {
d3.select("g.brush").call(brush.extent([0, 0]))
d3.selectAll("rect.bar").style("opacity", "0.4");
//reset year labels at top
}
I have extended the pie-chart example at:
with pies that vary in radius depending on a percentage. I would like to add gridlines (circles) every 20 percent, but I can't figure out how.
here is the updated csv:
age,population,percent
<5,2704659,67
5-13,4499890,38
14-17,2159981,91
18-24,3853788,49
25-44,14106543,71
45-64,8819342,88
=65,612463,64
and here is the updated code with pie-parts of different radius:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
background: #333;
}
.arc path {
stroke: #fff;
stroke-width: 2px;
}
.arc grid {
stroke: #fff;
stroke-width: 1;
stroke-dasharray: 5,5;
}
.arc text {
fill:#fff;
font-size:12px;
font-weight:bold;
}
.arc line {
stroke: #fff;
}
</style>
<body>
<script src="d3.js"></script>
<script>
var width = 960,
height = 500,
radius = Math.min(width, height) / 2 - 10;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(function(d) { return 50 + (radius - 50) * d.data.percent / 100; })
.innerRadius(20);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
var grid = d3.svg.area.radial()
.radius(150);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d.population = +d.population;
d.percent = d.percent;
});
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.age); });
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; });
});
</script>
First set the number of ticks:
var numTicks = 5; // Each tick is 20%
Then create the data to create the gridlines:
var sdat = [];
for (i=0; i<=numTicks; i++) {
sdat[i] = (radius/numTicks) * i;
}
And then you can use a function to create the radial gridlines, and you can call it from within the d3.csv block:
addCircleAxes = function() {
var circleAxes, i;
svg.selectAll('.circle-ticks').remove();
circleAxes = svg.selectAll('.circle-ticks')
.data(sdat)
.enter().append('svg:g')
.attr("class", "circle-ticks");
// radial tick lines
circleAxes.append("svg:circle")
.attr("r", String)
.attr("class", "circle")
.style("stroke", "#CCC")
.style("opacity", 0.5)
.style("fill", "none");
// Labels for each circle
circleAxes.append("svg:text")
.attr("text-anchor", "center")
.attr("dy", function(d) { return d - 5 })
.style("fill", "#fff")
.text(function(d,i) { return i * (100/numTicks) });
};
An example is here: http://bl.ocks.org/3994129
(Borrowed from: http://kreese.net/blog/2012/08/26/d3-js-creating-a-polar-area-diagram-radial-bar-chart/)