mirror of
https://github.com/heavyai/heavyai-charting.git
synced 2026-01-25 14:57:45 +00:00
click filtering fix examples push layer filter to chart filter handlers; filter null values from popup rewrite query to use table aliases for self-join change query to use non-join when tables are the same improved click filtering fix race between mapbox style load and data fetch layer filter listeners import d3
242 lines
7.2 KiB
JavaScript
242 lines
7.2 KiB
JavaScript
document.addEventListener("DOMContentLoaded", function init() {
|
|
var config = {
|
|
table: "contributions_donotmodify",
|
|
valueColumn: "amount",
|
|
joinColumn: "contributor_zipcode",
|
|
polyTable: "zipcodes",
|
|
polyJoinColumn: "ZCTA5CE10",
|
|
timeColumn: "contrib_date",
|
|
timeLabel: "Number of Contributions",
|
|
domainBoundMin: 1,
|
|
domainBoundMax: 2600, //00000,
|
|
numTimeBins: 423
|
|
}
|
|
|
|
new MapdCon()
|
|
.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
|
|
|
|
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",
|
|
aggregrate: `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() {}
|
|
})
|