2018-03-08 15:49:23 -08:00

246 lines
7.3 KiB
JavaScript

document.addEventListener("DOMContentLoaded", function init() {
var config = {
table: "contributions_donotmodify",
valueColumn: "contributions_donotmodify.amount",
joinColumn: "contributions_donotmodify.contributor_zipcode",
polyTable: "zipcodes",
polyJoinColumn: "ZCTA5CE10",
timeColumn: "contrib_date",
timeLabel: "Number of Contributions",
domainBoundMin: 0,
domainBoundMax: 2600,
numTimeBins: 423
}
new MapdCon()
.protocol("http")
.host("kali.mapd.com")
.port("9092")
.dbName("mapd")
.user("mapd")
.password("HyperInteractive")
.connect(function(error, con) {
crossfilter
.crossfilter(
con,
["contributions_donotmodify", "zipcodes"],
[
{
table1: "contributions_donotmodify",
attr1: "contributor_zipcode",
table2: "zipcodes",
attr2: "ZCTA5CE10"
}
]
)
.then(function(cf) {
crossfilter
.crossfilter(con, "contributions_donotmodify")
.then(cf2 => {
createPolyMap(cf, con, dc, config, cf2)
createTimeChart(cf, dc, config, cf2)
})
})
})
function createPolyMap(crossFilter, con, dc, config, cf2) {
window.cf = crossFilter
var parent = document.getElementById("polymap")
// The values in the table and column specified in crossFilter.dimension
// must correspond to values in the table and keysColumn specified in polyRasterChart.polyJoin.
var dim = crossFilter.dimension("zipcodes.rowid") // Values to join on.
var grp = dim
.group()
.reduceAvg("contributions_donotmodify.amount", "avgContrib") // Values to color on.
// var dim = crossFilter.dimension("tweets_nov_feb.state_abbr") // Values to join on.
// var grp = dim.group().reduceAvg("tweets_nov_feb.tweet_count") // Values to color on.
// Can use getDomainBounds to dynamically find min and max of values that will be colored,
// or the domain [min, max] can be set directly
// (in which case nesting chart creation inside this callback is unnecessary).
getDomainBounds(config.valueColumn, cf2.groupAll(), function(domainBounds) {
// Can set colorDomain directly or use domainFromBoundsAndRange to generate a .
var colorRange = [
"#115f9a",
"#1984c5",
"#22a7f0",
"#48b5c4",
"#76c68f",
"#a6d75b",
"#c9e52f",
"#d0ee11",
"#d0f400"
]
var colorDomain = [config.domainBoundMin, config.domainBoundMax]
// domainFromBoundsAndRange(
// config.domainBoundMin,
// config.domainBoundMax,
// colorRange
// )
// var colorDomain = domainFromBoundsAndRange(domainBounds.minimum, domainBounds.maximum, colorRange)
var mapboxToken =
"pk.eyJ1IjoibWFwZCIsImEiOiJjaWV1a3NqanYwajVsbmdtMDZzc2pneDVpIn0.cJnk8c2AxdNiRNZWtx5A9g"
var polyMap = dc
.rasterChart(parent, true)
.con(con)
.height(height() / 1.5)
.width(width())
.mapUpdateInterval(750) // ms
.mapStyle("mapbox://styles/mapbox/light-v8")
.mapboxToken(mapboxToken) // need a mapbox accessToken for loading the tiles
var polyLayer = dc
.rasterLayer("polys")
.crossfilter(crossFilter)
.setState({
data: [
{
table: "contributions_donotmodify",
attr: "contributor_zipcode"
},
{
table: "zipcodes",
attr: "ZCTA5CE10"
}
],
mark: {
type: "poly",
strokeColor: "white",
strokeWidth: 0,
lineJoin: "miter",
miterLimit: 10
},
encoding: {
color: {
type: "quantitative",
aggregrate: "AVG(contributions_donotmodify.amount)",
domain: colorDomain,
range: colorRange
}
}
})
polyLayer.popupColumns(["color"])
polyMap.pushLayer("polys", polyLayer).init().then(() => {
// polyMap.borderWidth(zoomToBorderWidth(polyMap.map().getZoom()))
// Keeps the border widths reasonable regardless of zoom level.
polyMap.map().on("zoom", function() {
// polyMap.borderWidth(zoomToBorderWidth(polyMap.map().getZoom()))
})
dc.renderAllAsync()
window.addEventListener(
"resize",
_.debounce(function() {
resizeChart(polyMap, 1.5)
}, 500)
)
})
// hover effect with popup
var debouncedPopup = _.debounce(displayPopupWithData, 250)
polyMap.map().on('mousewheel', polyMap.hidePopup);
polyMap.map().on('wheel', polyMap.hidePopup);
polyMap.map().on('mousemove', polyMap.hidePopup);
polyMap.map().on('mousemove', debouncedPopup);
function displayPopupWithData (event) {
polyMap.getClosestResult(event.point, polyMap.displayPopup)
}
})
}
function getDomainBounds(column, groupAll, callback) {
groupAll
.reduce([
{ expression: column, agg_mode: "min", name: "minimum" },
{ expression: column, agg_mode: "max", name: "maximum" }
])
.valuesAsync(true)
.then(callback)
}
function domainFromBoundsAndRange(min, max, range) {
return _.range(0, range.length).map(
(_, i) => min + Math.round(i * max / (range.length - 1))
)
}
function zoomToBorderWidth(zoomLevel) {
var MIN_ZOOM = 0.8626373575587937
var ZOOM_BORDER_DIVISOR = 20
return zoomLevel / ZOOM_BORDER_DIVISOR - MIN_ZOOM / ZOOM_BORDER_DIVISOR
}
function createTimeChart(crossFilter, dc, config, cf2) {
getDomainBounds(config.timeColumn, cf2.groupAll(), function(
timeChartBounds
) {
var timeChartDimension = crossFilter.dimension(config.timeColumn)
var timeChartGroup = timeChartDimension.group().reduceCount("*")
var timeChart = dc
.lineChart("#timechart")
.width(width())
.height(height() / 2.5)
.elasticY(true)
.renderHorizontalGridLines(true)
.brushOn(true)
.xAxisLabel("Time")
.yAxisLabel(config.timeLabel)
.dimension(timeChartDimension)
.group(timeChartGroup)
.binParams({
numBins: config.numTimeBins,
binBounds: [timeChartBounds.minimum, timeChartBounds.maximum]
})
timeChart.x(
d3.time.scale
.utc()
.domain([timeChartBounds.minimum, timeChartBounds.maximum])
)
timeChart.yAxis().ticks(5)
timeChart
.xAxis()
.scale(timeChart.x())
.tickFormat(dc.utils.customTimeFormat)
.orient("bottom")
dc.renderAllAsync()
window.addEventListener(
"resize",
_.debounce(function() {
resizeChart(timeChart, 2.5)
}, 500)
)
})
}
function width() {
return document.documentElement.clientWidth - 30
}
function height() {
return (
Math.max(document.documentElement.clientHeight, window.innerHeight || 0) -
200
)
}
function resizeChart(chart, heightDivisor) {
if (typeof chart.map === "function") {
chart.map().resize()
chart.isNodeAnimate = false
}
chart.width(width()).height(height() / heightDivisor).renderAsync()
dc.redrawAllAsync()
}
function noop() {}
})