|
<!DOCTYPE html> |
|
|
|
<!-- include polyfills for custom event and Symbol (for IE) --> |
|
<script src="https://unpkg.com/babel-polyfill@6.26.0/dist/polyfill.js"></script> |
|
<script src="https://unpkg.com/custom-event-polyfill@0.3.0/custom-event-polyfill.js"></script> |
|
|
|
<!-- use babel so that we can use arrow functions and other goodness in this block! --> |
|
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> |
|
|
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="https://unpkg.com/d3fc@13.1.1"></script> |
|
|
|
<style> |
|
.plot-area { |
|
overflow: hidden; |
|
} |
|
/* for the navigator chart, hide the y axis, and 'collapse' the chart title |
|
and x-axis label so that they do not occupy any space */ |
|
#navigator-chart .y-axis { |
|
visibility: hidden; |
|
} |
|
#navigator-chart .chart-label, #navigator-chart .x-axis-label { |
|
display: none; |
|
} |
|
</style> |
|
|
|
<div id='main-chart' style='height: 300px'></div> |
|
<div id='navigator-chart' style='height: 100px'></div> |
|
|
|
<script type='text/babel'> |
|
d3.csv('downsample.csv', |
|
(row) => { |
|
// parse the dates and simplify property access |
|
return { |
|
date: new Date(row['date-time']), |
|
temperature: Number(row['surface temperature (C)']) |
|
} |
|
}, |
|
(error, data) => { |
|
if (error) { |
|
console.error(error); |
|
} |
|
|
|
const yExtent = fc.extentLinear() |
|
.accessors([d => d.temperature]) |
|
.pad([0.1, 0.1]); |
|
|
|
const xExtent = fc.extentDate() |
|
.accessors([d => d.date]); |
|
|
|
// create the scales |
|
const x = d3.scaleTime() |
|
.domain(xExtent(data)); |
|
const y = d3.scaleLinear() |
|
.domain(yExtent(data)); |
|
|
|
// create the data that is bound to the charts. It is a combination of the |
|
// chart data and the brushed / navigator range |
|
var chartData = { |
|
series: data, |
|
// set an initial brushed range |
|
brushedRange: [0.75, 1] |
|
}; |
|
|
|
const area = fc.seriesSvgArea() |
|
.crossValue(d => d.date) |
|
.mainValue(d => d.temperature) |
|
.baseValue(y.domain()[0]) |
|
|
|
const line = fc.seriesSvgLine() |
|
.crossValue(d => d.date) |
|
.mainValue(d => d.temperature); |
|
|
|
const brush = fc.brushX() |
|
.on('brush', (evt) => { |
|
// if the brush has zero height there is no selection |
|
if (evt.selection) { |
|
// update the bound data based on the new selcetion |
|
chartData.brushedRange = evt.selection; |
|
// update the domain of the main chart to reflect the brush |
|
mainChart.xDomain(evt.xDomain); |
|
render(); |
|
} |
|
}); |
|
|
|
// create a multi series, combining the brush and area |
|
const multi = fc.seriesSvgMulti() |
|
.series([area, brush]) |
|
.mapping((data, index, series) => { |
|
switch (series[index]) { |
|
case area: |
|
return data.series; |
|
case brush: |
|
return data.brushedRange; |
|
} |
|
}); |
|
|
|
// create the two charts |
|
const mainChart = fc.chartSvgCartesian(x, y) |
|
.plotArea(line); |
|
|
|
const navigatorChart = fc.chartSvgCartesian(x.copy(), y.copy()) |
|
.plotArea(multi); |
|
|
|
// set the initial domain for the main chart based on the |
|
// brushed range |
|
var scale = d3.scaleLinear().domain(x.domain()); |
|
mainChart.xDomain(chartData.brushedRange.map(scale.invert)); |
|
|
|
// each time the bruch changes, re-render both charts |
|
const render = () => { |
|
d3.select('#main-chart') |
|
.datum(chartData.series) |
|
.call(mainChart); |
|
|
|
d3.select('#navigator-chart') |
|
.datum(chartData) |
|
.call(navigatorChart); |
|
} |
|
|
|
render(); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
</script> |