Skip to content

Instantly share code, notes, and snippets.

@robyngit
Last active October 18, 2018 22:01
Show Gist options
  • Save robyngit/981590aa194d930b22aa45cdba79beaf to your computer and use it in GitHub Desktop.
Save robyngit/981590aa194d930b22aa45cdba79beaf to your computer and use it in GitHub Desktop.
D3 v3 bar chart showing gaps in data

Option D

barchart


<!DOCTYPE html>
<meta charset="utf-8">
<style>
/**********************************************************
* Metric Modal Chart (Views, Downloads, Citations) *
***********************************************************/
/*
============ GENERAL CHART TEXT STYLE =========== [metric modal chart]
*/
#metric-modal .metric-chart text {
fill: #565656;
font-size: 9px;
font-family: Helvetica, Arial, "sans serif";
}
/*
============ WHEN THERE IS NO DATA: =========== [metric modal chart]
*/
#metric-modal .metric-chart text.no-data {
font-size: 16px;
font-weight: 100;
fill:#d0d0d0;
}
#metric-modal .metric-chart rect.no-data {
fill: #f5f5f5;
}
/*
============ WHEN THERE IS DATA: =========== [metric modal chart]
*/
#metric-modal {/* CB: padding to display better on bl.ocks.org */
padding: 64px;
}
/*
--- AXES --- [metric modal chart]
*/
/* --- x axis focus --- */
#metric-modal .metric-chart .focus .axis {
shape-rendering: crispEdges;
}
#metric-modal .metric-chart .focus .x.axis {
clip-path: url(#clip);
}
#metric-modal .metric-chart .focus .x.axis line {
display: none;
}
#metric-modal .metric-chart .focus .x.axis .tick:hover {
cursor: default;
}
/* --- x axis context --- */
#metric-modal .metric-chart .context .x.axis line {
display: none;
}
#metric-modal .metric-chart .context .x.axis .domain {
display:none;
}
/* --- focus y-axis --- */
#metric-modal .metric-chart .focus .y.axis .domain {
display: none;
}
#metric-modal .metric-chart .y.axis.title {
font-size: 13px;
font-weight: 100;
}
/* horizontal focus gridlines */
#metric-modal .metric-chart .y.axis line {
stroke: #565656;
stroke-dasharray: 2,2;
stroke-opacity: 0.3;
}
/*
--- BRUSH --- [metric modal chart]
*/
/* --- brush window --- */
#metric-modal .metric-chart .brush .extent {
fill-opacity: .07;
shape-rendering: crispEdges;
clip-path: url(#clip);
fill: #70706c;
}
/* --- brush handles --- */
#metric-modal .metric-chart .resize .handle {
fill: #555;
}
#metric-modal .metric-chart .resize .handle-mini {
fill: white;
stroke-width: 1px;
stroke: #555;
}
/*
--- ZOOM TO BUTTONS --- [metric modal chart]
*/
#metric-modal .metric-chart .scale_button rect {
fill: #eaeaea;
}
#metric-modal .metric-chart .scale_button:hover text {
fill: white;
transition: all 0.1s cubic-bezier(.25,.8,.25,1);
}
#metric-modal .metric-chart .scale_button:hover rect {
fill: #00AA8D;
transition: all 0.1s cubic-bezier(.25,.8,.25,1);
}
/*
--- EXPLANATORY TEXT (# views/downloads) --- [metric modal chart]
*/
#metric-modal .metric-chart text#totalCount {
font-size: 15px;
font-weight: bold;
}
#metric-modal .metric-chart text#displayDates {
font-weight: bold;
}
/*
--- BARS --- [metric modal chart]
*/
#metric-modal .metric-chart .bar,
#metric-modal .metric-chart .bar_context {
fill: #00AA8D;
stroke-width: 0;
}
#metric-modal .metric-chart .bar {
stroke: #00AA8D;
clip-path: url(#clip);
cursor: default;
}
/* --- context bars --- */
/*
--- PANE CURSORS --- [metric modal chart]
*/
#metric-modal .metric-chart rect.pane {
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
fill: #FAFAFA;
pointer-events: all;
}
#metric-modal .metric-chart rect.pane:active {
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grabbing;
}
/*
--- TOOLTIPS --- [metric modal chart]
*/
div.tooltip {
position: fixed;
text-align: center;
width: 60px;
height: 25px;
padding: 2px;
font: 10px sans-serif;
background: white;
color: #565656;
border: 0px;
border-radius: 2px;
pointer-events: none;
box-shadow: 0 0px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
}
</style>
<body>
<div id="metric-modal"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
// example data and other inputs
var metricName = "views";
var metricCount = [0, 3, 1, 2, 1, 1, 1, 1, 2, 2, 3, 1, 2, 1, 4, 3, 2, 1, 1, 1, 1, 1, 4, 2, 1, 2, 1, 2, 1, 4, 2, 4, 7, 3, 1, 2, 1, 1, 3, 1, 1, 5, 1, 1, 4];
var metricMonths = ["2018-06", "2013-04", "2015-11", "2012-10", "2014-09", "2014-02", "2016-02", "2016-04", "2016-06", "2014-12", "2013-07", "2017-01", "2015-10", "2012-12", "2013-05", "2018-04", "2015-06", "2017-03", "2014-08",
"2017-07", "2013-02", "2012-07", "2016-03", "2017-06", "2018-07", "2014-10", "2013-01", "2013-10", "2017-11", "2014-05", "2012-11", "2015-01", "2018-03", "2015-12", "2015-08", "2016-08", "2014-11", "2014-01",
"2013-06", "2012-08", "2015-09", "2016-07", "2013-03", "2012-09", "2016-05"];
var optwidth = 600;
var optheight = 370;
/*
* ========================================================================
* Global variables and options
* ========================================================================
*/
// the format of the date in the input data
var input_date_format = d3.time.format("%Y-%m");
// how dates will be displayed in the chart in most cases
var display_date_format = d3.time.format("%b %Y");
// the length of a day/year in milliseconds
var day_in_ms = 86400000,
ms_in_year = 31540000000;
// focus chart sizing
var margin = {top: 30, right: 30, bottom: 95, left: 20},
width = optwidth - margin.left - margin.right,
height = optheight - margin.top - margin.bottom;
// context chart sizing
var margin_context = {top: 315, right: 30, bottom: 20, left: 20},
height_context = optheight - margin_context.top - margin_context.bottom;
// zoom button sizing
var button_width = 40,
button_height = 14,
button_padding = 10;
/*
* ========================================================================
* Prepare data
* ========================================================================
*/
// change dates to milliseconds
metricMonths.forEach(function(part, index, theArray) {
theArray[index] = d3.time.format("%Y-%m").parse(part).getTime();
});
// get a list of all possible months in the range of data (even if not listed in metricMonths)
var all_months = d3.time.scale()
.domain(d3.extent(metricMonths))
.ticks(d3.time.months, 1);
// for each month, check whether there is a count available, if so append it, otherwise append zero.
var dataset = [];
for(var i=0; i<all_months.length; i++){
var match_index = metricMonths.indexOf(all_months[i].getTime());
if (match_index == -1) { // no match in data
dataset.push({month: all_months[i], count:0});
} else { // match in data
dataset.push({month: all_months[i], count:metricCount[match_index]});
}
};
/*
* ========================================================================
* x and y coordinates
* ========================================================================
*/
// add about a month to the end of the x range to show the last bar (which starts on the first of the month)
var x_full_extent = d3.extent(dataset, function(d) { return d.month; });//,
new_max_date = new Date(x_full_extent[1].getTime() + (day_in_ms * 24)),
x_full_extent = [x_full_extent[0], new_max_date];
var y_full_range = [0, d3.max(dataset, function(d) { return d.count; })];
/* === Focus Chart === */
var x = d3.time.scale()
.range([0, width])
.domain(x_full_extent);
// The ordinal scale is used only for its `rangeBands` method, to automatially
// calculate the width of columns of column chart for details, see:
// https://stackoverflow.com/questions/12186366/d3-js-evenly-spaced-bars-on-a-time-scale
var x_ordinal = d3.scale.ordinal()
.rangeBands([0, width], 0, 25)
.domain(dataset.map(function(d){ return d.month}));
var y = d3.scale.linear()
.range([height, 0])
.domain(y_full_range);
var x_axis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(-(height))
.ticks(generate_ticks)
.tickFormat(format_months);
var y_axis = d3.svg.axis()
.scale(y)
.ticks(4)
.tickFormat(d3.format("d"))
.tickSize(-(width))
.orient("right");
/* === Context Chart === */
var x_context = d3.time.scale()
.range([0, width])
.domain([x_full_extent[0], x_full_extent[1]]);
var x_context_ordinal = d3.scale.ordinal()
.rangeBands([0, width], 0, 35)
.domain(dataset.map(function(d){ return d.month }));
var y_context = d3.scale.linear()
.range([height_context, 0])
.domain(y.domain());
var x_axis_context = d3.svg.axis()
.scale(x_context)
.orient("bottom")
.ticks(generate_ticks)
.tickFormat(format_months);
/*
* ========================================================================
* Variables for brushing and zooming behaviour
* ========================================================================
*/
var brush = d3.svg.brush()
.x(x_context)
.on("brush", change_focus_brush)
.on("brushend", check_bounds);
var zoom = d3.behavior.zoom()
.on("zoom", change_focus_zoom)
.on("zoomend", check_bounds);
/*
* ========================================================================
* Define the SVG area ("vis") and append all the layers
* ========================================================================
*/
// === the main components === //
var vis = d3.select("#metric-modal").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("class", "metric-chart");// CB -- "line-chart" -- CB //
// clipPath is used to keep elements from moving outside of plot area when viwer zooms/scrolls/brushes
vis.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var pane = vis.append("rect")
.attr("class", "pane")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = vis.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin_context.left + "," + margin_context.top + ")");
var focus = vis.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// === current date range text & zoom buttons === //
var expl_text = vis.append("g")
.attr("id", "buttons_group")
.attr("transform", "translate(" + 0 + ","+ 0 +")");
expl_text.append("text")
.attr("id", "totalCount")
.style("text-anchor", "start")
.attr("transform", "translate(" + 18 + ","+ 11 +")");
expl_text.append("text")
.attr("id", "displayDates")
.style("text-anchor", "start")
.attr("transform", "translate(" + 20 + ","+ 22 +")");
update_context();
// === the zooming/scaling buttons === //
if ((x_full_extent[1] - x_full_extent[0]) < ms_in_year) {
var button_data =["month","all"];
} else {
var button_data =["year","month","all"];
};
var button_count = button_data.length -1,
button_g_width = (button_count*button_width) +
(button_count*button_padding) +
margin.right - button_padding;
expl_text.append("text")
.attr("class", "zoomto_text")
.text("Zoom to")
.style("text-anchor", "start")
.attr("transform", "translate(" + (width - button_g_width - 45) + ","+ 14 +")")
.style("opacity", "0");
var button = expl_text.selectAll("g")
.data(button_data)
.enter().append("g")
.attr("class", "scale_button")
.attr("transform", function(d, i) { return "translate(" + ((width - button_g_width) + i*button_width + i*button_padding) + ",4)"; })
.style("opacity", "0");
button.append("rect")
.attr("class", "button_rect")
.attr("width", button_width)
.attr("height", button_height)
.attr("rx", 1)
.attr("ry", 1);
button.append("text")
.attr("dy", (button_height/2 + 3))
.attr("dx", button_width/2)
.style("text-anchor", "middle")
.text(function(d) { return d; });
/* === focus chart === */
focus.append("g")
.attr("class", "y axis")
.call(y_axis)
.attr("transform", "translate(" + (width) + ", 0)")
.style("text-anchor", "middle");
// x-axis
focus.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(x_axis)
.style("text-anchor", "middle");
// enter bars
focus.selectAll(".bar")
.data(dataset)
.enter().append("rect")
.attr("class", "bar")
.attr("id", function(d){return "bar_" + d.month.getTime()}) //id of each bar is "bar_" plus it's associated date in ms
.attr("x", function (d) {return x(d.month); })
.attr("y", height)
.attr("height", 0)
.attr("width", x_ordinal.rangeBand())
.style("opacity", 0)
.on("mouseover", function(d) {
var floor_month = d3.time.month.floor(d.month).getTime();
highlight_bar("#bar_" + floor_month);
highlight_label("#label_" + floor_month);
show_tooltip(d);
})
.on("mouseout", function(d) {
var floor_month = d3.time.month.floor(d.month).getTime();
unhighlight_bar("#bar_" + floor_month);
unhighlight_label("#label_" + floor_month);
hide_tooltip(d);
});
// animate bars
focus.selectAll(".bar")
.transition()
.duration(450)
.ease("elastic", 1.03, 0.98)
.delay(function(d, i) {
var max_delay = 600;
var z = i / (dataset.length-1);
var line_z = z * max_delay * 0.4;
var log_z = Math.log2(z + 1) * max_delay * 0.6;
return(250+line_z + log_z);
})
.attr("y", function (d) {return y(d.count); })
.attr("height", function (d) {return y(0) - y(d.count); })
.style("opacity", 1);
/* === tooltip === */
var div = d3.select("#metric-modal").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
/* === context chart === */
// enter context bars
context.selectAll(".bar_context")
.data(dataset)
.enter().append("rect")
.attr("class", "bar_context")
.attr("x", function (d) {return x_context(d.month); })
.attr("y", height_context)
.attr("height", 0)
.attr("width", x_context_ordinal.rangeBand())
.style("opacity", 0);
// animate context bars
context.selectAll(".bar_context")
.transition()
.duration(450)
.ease("elastic", 1.03, 0.98)
.delay(function(d, i) {
var max_delay = 600;
var z = i / (dataset.length-1);
var line_z = z * max_delay * 0.4;
var log_z = Math.log2(z + 1) * max_delay * 0.6;
return(line_z + log_z);
})
.attr("y", function (d) {return y_context(d.count); })
.attr("height", function (d) {return y_context(0) - y_context(d.count); })
.style("opacity", 1);
// x-axis
context.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height_context + ")")
.call(x_axis_context);
/* === brush === */
var brushg = context.append("g")
.attr("class", "x brush")
.call(brush);
brushg.selectAll(".extent")
.attr("y", -6)
.attr("height", height_context + 8)
.style("opacity" , "0");
brushg.selectAll(".resize")
.append("rect")
.attr("class", "handle")
.attr("transform", "translate(0," + -3 + ")")
.attr('rx', 1)
.attr('ry', 1)
.attr("height", 0)
.attr("width", 3)
.style("opacity" , "0");
brushg.selectAll(".resize")
.append("rect")
.attr("class", "handle-mini")
.attr("transform", "translate(-2,8.5)")
.attr('rx', 2)
.attr('ry', 2)
.attr("height", 0)
.attr("width", 7)
.style("opacity" , "0");
/* === y axis title === */
vis.append("text")
.attr("class", "y axis title")
.text("Monthly " + this.metricName)
.attr("x", (-((height+margin.top+margin.bottom-50)/2)))
.attr("y", 0)
.attr("dy", "1em")
.attr("transform", "rotate(-90)")
.style("text-anchor", "middle");
// allow zoom, brush, and scale behavior after a small delay,
// so that user does not interrupt bar entrance animation.
// show UI elements only once user is able to interact with them.
setTimeout(function(){
// add behaviours
pane.call(zoom)
.call(change_focus_zoom);
zoom.x(x);
vis.selectAll(".scale_button")
.style("cursor", "pointer")
.on("click", zoom_to_interval);
// fade in buttons
vis.selectAll(".scale_button,.zoomto_text")
.transition()
.duration(100)
.ease("cubic")
.style("opacity","1");
// fade in brush elements
brushg.selectAll(".extent")
.transition()
.duration(100)
.ease("cubic")
.style("opacity" , "1");
brushg.selectAll(".handle-mini")
.transition()
.duration(170)
.ease("linear")
.attr("height", (height_context/2))
.style("opacity" , "1");
brushg.selectAll(".handle")
.transition()
.duration(170)
.ease("linear")
.attr("height", height_context + 6)
.style("opacity" , "1");
},
900);
/*
* ========================================================================
* Functions
* ========================================================================
*/
/* ------------------------------------------------------
HELPER FUNCTIONS
------------------------------------------------------ */
function get_zoom_scale(){
// custom zoom scale needed to calculate width of bars with zoom/brush.
// can't use zoom.scale() because this needs to be reset (to one) when using change_focus_brush()
var x_current_width = x.domain()[1] - x.domain()[0],
x_total_width = x_full_extent[1] - x_full_extent[0],
zoom_scale = x_total_width/x_current_width;
return(zoom_scale);
};
function convert_metric_name(n){
// remove s from metric name if count is 1
if (n == 1) {
return metricName.slice(0, -1);
} else {
return metricName;
}
};
/* ------------------------------------------------------
HOVER BEHAVIOUR: X-AXIS LABELS, BARS, TOOLTIPS
------------------------------------------------------ */
function highlight_bar(bar_id){
// mouseover effect on bar
focus.select(bar_id)
.style("stroke-width", "1")
.style("opacity", "0.9");
};
function unhighlight_bar(bar_id) {
// undo mouseover effect on bar
focus.select(bar_id)
.style("stroke-width", "0")
.style("opacity", "1");
};
function highlight_label(label_id){
// mouseover effect on label
focus.select(label_id).selectAll("text")
.style("font-weight", "bold");
};
function unhighlight_label(label_id){
// undo mouseover effect on label
focus.select(label_id).selectAll("text")
.style("font-weight", "normal");
};
function add_tick_behaviour() {
focus.selectAll(".x.axis .tick")[0].forEach(function(tick) {
d3.select(tick)
.attr("id", function(d,i) {
return "label_" + d3.time.month.floor(d).getTime();
})
.on("mouseover", function(tick) {
// extract the datapoint from dataset that is associated with x-axis label
var floor_month = d3.time.month.floor(tick).getTime();
var d = dataset.filter( function(d) { return d.month.getTime() === floor_month; })[0];
highlight_bar("#bar_" + floor_month);
highlight_label("#label_" + floor_month);
show_tooltip(d);
})
.on("mouseout", function(tick) {
var floor_month = d3.time.month.floor(tick).getTime();
unhighlight_bar("#bar_" + floor_month);
unhighlight_label("#label_" + floor_month);
hide_tooltip(tick);
});
});
};
function show_tooltip(d) {
div.transition()
.duration(60)
.style("opacity", 0.98);
div.html("<b>" + display_date_format(d.month) + "</b><br/>" + d.count + " " + convert_metric_name(d.count))
.style("left", (x(d.month) + 60 + (x_ordinal.rangeBand() * 0.5 * get_zoom_scale())) + "px")
.style("top", (y(d.count)+ 68 ) + "px");
};
function hide_tooltip(d) {
div.transition()
.duration(60)
.style("opacity", 0);
};
/* ------------------------------------------------------
TICK FORMATTING FUNCTIONS (focus x-axis)
------------------------------------------------------ */
function generate_ticks(t0, t1, dt) {
var label_size = 45;
var max_total_labels = Math.floor(width / label_size);
var offset = day_in_ms * 9; //add a slight offset so that labels are at the center of each month.
function step(date, next_step) {
date.setMonth(date.getMonth() + next_step);
}
var time = d3.time.month.floor(t0),
time = new Date(time.getTime() + offset),
times = [],
monthFactors = [1,3,4,12];
while (time < t1) { times.push(new Date(+time)), step(time, 1)};
var timesCopy = times;
var i;
for(i=0 ; times.length > max_total_labels ; i++){
var times = _.filter(timesCopy, function(d){ return (d.getMonth()) % monthFactors[i] == 0; } )
};
return times;
};
function format_months(d){
add_tick_behaviour(); // add tick hover behaviour everytime ticks are re-formatted;
var test = (x.domain()[1] - x.domain()[0]) > 132167493818; // when to switch from yyyy to mm-yyyy
if(d.getMonth()==0 & test){//if january
var yearOnly = d3.time.format("%Y");
return(yearOnly(d));
} else {
return(display_date_format(d))
}
}
/* ------------------------------------------------------
BRUSH & ZOOM BEHAVIOUR
------------------------------------------------------ */
function change_focus_brush() {
// make the x domain match the brush domain
x.domain(brush.empty() ? x_context.domain() : brush.extent());
// reset zoom
zoom.x(x);
// re-draw axis and elements at new scale
update_focus();
// update the explanatory text (total views, date range)
update_context();
}
function change_focus_zoom() {
// make the brush range change with the x domain in focus
brush.extent(x.domain());
vis.select(".brush").call(brush);
// re-draw axis and elements at new scale
update_focus();
// update the explanatory text (total views, date range)
update_context();
}
function update_focus() {
// calculate new y-max in focus data
var left_date = x.domain()[0];
if(left_date.getDate() < 19){ // date range should switch to next month on the 19th.
var left_date = d3.time.month.floor(left_date),
left_date = new Date(left_date.getTime())
};
var data_subset_focus = dataset.filter( function(d) {
return d.month <= x.domain()[1] && d.month >= left_date
});
var y_max_focus = d3.max(data_subset_focus, function(d) { return d.count; }) || 1;
var y_change_duration = 85;
// reset y-axis
y.domain([0, y_max_focus]);
focus.select(".y.axis")
.transition()
.duration(y_change_duration*0.95)
//.ease()
.call(y_axis);
// reset bar height given y-axis
focus.selectAll(".bar")
.transition()
.duration(y_change_duration)
.attr("y", function (d) {return y(d.count); })
.attr("height", function (d) {return y(0) - y(d.count); });
// redraw other elements
focus.select(".x.axis").call(x_axis);
focus.selectAll(".bar")
.attr("x", function (d) {return x(d.month); })
.attr("width", x_ordinal.rangeBand() * get_zoom_scale())
.style("opacity", "1"); // incase user scrolls before entrance animation finishes.
};
function update_context() {
// updates display dates, total count, and decreases opacity of context bars out of focus
var b = brush.extent();
// given bar width, date range should switch to next month on the 19th
if(b[0].getDate() >= 19){
var left_date = d3.time.month.ceil(b[0]),
left_date = new Date(left_date.getTime())
} else {
left_date = d3.time.month.floor(b[0])
}
// get the range of data in focus
var start_month = (brush.empty()) ? display_date_format(x_full_extent[0]) : display_date_format(left_date),
end_month = (brush.empty()) ? display_date_format(x_full_extent[1]) : display_date_format(b[1]);
var data_subset_focus = dataset.filter( function(d) {
return d.month <= display_date_format.parse(end_month) && d.month >= left_date
});
focus_x_extent = d3.extent(data_subset_focus, function(d) { return d.month; });
// calcualte the total views/downloads within focus area
var total_count = 0;
for (var i = 0; i < data_subset_focus.length; i++) {
total_count += data_subset_focus[i].count;
}
// Update start and end dates and total count
vis.select("#displayDates")
.text(start_month == end_month ? "in " + start_month : "from " + start_month + " to " + end_month);
vis.select("#totalCount")
.text(total_count + " " + convert_metric_name(total_count));
// Fade all years in the bar chart not within the brush
context.selectAll(".bar_context")
.style("opacity", function(d, i) {
return d.month <= display_date_format.parse(end_month) && d.month >= left_date || brush.empty() ? "1" : ".3";
});
};
function check_bounds() {
// when brush stops moving:
// check whether chart was scrolled out of bounds and fix,
var b = brush.extent();
var out_of_bounds = brush.extent().some(function(e) { return e < x_full_extent[0] | e > x_full_extent[1]; });
if (out_of_bounds){ b = move_in_bounds(b) };
};
function move_in_bounds(b) {
// move back to boundaries if user pans outside min and max date.
var ms_in_year = 31536000000,
brush_start_new,
brush_end_new;
if (b[0] < x_full_extent[0]) { brush_start_new = x_full_extent[0]; }
else if (b[0] > x_full_extent[1]) { brush_start_new = new Date(x_full_extent[1].getTime() - ms_in_year); }
else { brush_start_new = b[0]; };
if (b[1] > x_full_extent[1]) { brush_end_new = x_full_extent[1]; }
else if (b[1] < x_full_extent[0]) { brush_end_new = new Date(x_full_extent[0].getTime() + ms_in_year); }
else { brush_end_new = b[1]; };
brush.extent([brush_start_new, brush_end_new]);
brush(d3.select(".brush").transition());
change_focus_brush();
change_focus_zoom();
return(brush.extent())
};
function zoom_to_interval(d,i) {
// action for buttons that zoom focus to certain time interval
var b = brush.extent(),
interval_ms,
brush_end_new,
brush_start_new;
if (d == "year") { interval_ms = 31536000000}
else if (d == "month") { interval_ms = 2592000000 };
if ( d == "year" | d == "month" ) {
if((x_full_extent[1].getTime() - b[1].getTime()) < interval_ms){
// if brush is too far to the right that increasing the right-hand brush boundary would make the chart go out of bounds....
brush_start_new = new Date(x_full_extent[1].getTime() - interval_ms); // ...then decrease the left-hand brush boundary...
brush_end_new = x_full_extent[1]; //...and set the right-hand brush boundary to the maxiumum limit.
} else {
// otherwise, increase the right-hand brush boundary.
brush_start_new = b[0];
brush_end_new = new Date(b[0].getTime() + interval_ms);
};
} else if ( d == "all") {
brush_start_new = x_full_extent[0];
brush_end_new = x_full_extent[1]
} else {
brush_start_new = b[0];
brush_end_new = b[1];
};
brush.extent([brush_start_new, brush_end_new]);
// now draw the brush to match our extent
brush(d3.select(".brush").transition());
// now fire the brushstart, brushmove, and check_bounds events
brush.event(d3.select(".brush").transition());
};
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment