document.addEventListener("DOMContentLoaded", function init() { var config = { table: "contributions_donotmodify", valueColumn: "amount", joinColumn: "contributor_zipcode", polyTable: "zipcodes_2017", polyJoinColumn: "ZCTA5CE10", timeColumn: "contrib_date", timeLabel: "Number of Contributions", domainBoundMin: 1, domainBoundMax: 2600, //00000, numTimeBins: 423 } new DbCon() .protocol("https") .host("metis.mapd.com") .port("443") .dbName("mapd") .user("mapd") .password("HyperInteractive") .connect(function(error, con) { crossfilter .crossfilter(con, "contributions_donotmodify") .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(config.joinColumn) // 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 .useGeoTypes(true) var polyLayer = dc .rasterLayer("polys") .crossfilter(crossFilter) .dimension(dim) .setState({ data: [ { table: config.table, attr: config.joinColumn }, { table: config.polyTable, attr: config.polyJoinColumn } ], mark: { type: "poly", strokeColor: "white", strokeWidth: 0, lineJoin: "miter", miterLimit: 10 }, encoding: { color: { type: "quantitative", aggregate: `SUM(${config.valueColumn})`, domain: colorDomain, range: colorRange } } }) polyLayer.popupColumns(["key0", "color"]) polyLayer.popupColumnsMapped({"key0": "zipcode", "color": "total amount"}) 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() {} })