heavyai-charting/example/example2.html
2017-11-28 08:21:31 -05:00

462 lines
17 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<title>MapD</title>
<meta charset="UTF-8">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link href='https://fonts.googleapis.com/css?family=Roboto:400,700,300' rel='stylesheet' type='text/css'>
<style>
.title {
font-weight: bold;
text-align:center;
}
.mapd {
position: relative;
top: 2px;
}
.search{
display: inline-block;
margin-top: 12px;
margin-left: 50px;
}
.data-count {
padding-right:20px;
}
.filter-count {
font-weight: bold;
color: #45B1E8;
}
</style>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header" style="margin-top:10px">
<img alt="Brand" src="images/favicon.png" height="30" width="30">
<span class="mapd">MapD Demo</span>
</div>
<div class='search'>Filter Term(s): <small>use a comma between terms to search multiple values</small>
<input class='search-bar'>
</div>
<div class="navbar-text navbar-right">
<div class="data-count">
<span class="total"><span><span class="filter-count"></span></span>
<span>of <span class="total-count"></span> tweets</span>
</div>
</div>
</div>
</nav>
<div class="main-container">
<div class="col-xs-12">
<div class="title">Point Map with Backend Rendering</div>
<div id="chart1-example"></div>
</div>
<div class="col-xs-12">
<div class="title"># of Tweets per 5 Minutes</div>
<div class="chart2-example"></div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<script src="assets/app.bundle.js"></script>
<script>
/*
* This is example code that shows how to make 3 cross-filtered charts with the
* mapdc.js API. This example is not meant to be a replacement for dc.js
* documentation. For the dc.js API docs, see here
* - https://github.com/dc-js/dc.js/blob/master/web/docs/api-latest.md.
* For an annotated example of using dc.js - see here:
* https://dc-js.github.io/dc.js/docs/stock.html.
*/
var globalGeocoder = null;
function createCharts(crossFilter, con, tableName) {
var w = document.documentElement.clientWidth - 30;
var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0) - 200;
/*---------------------BASIC COUNT ON CROSSFILTER--------------------------*/
/*
* A basic operation is getting the filtered count and total count
* of crossFilter. This performs that operation. It is built into DC.
* Note that for the count we use crossFilter itself as the dimension.
*/
var countGroup = crossFilter.groupAll();
var dataCount = dc.countWidget(".data-count")
.dimension(crossFilter)
.group(countGroup);
/*----------------BACKEND RENDERED POINT MAP EXAMPLE-----------------------*/
var langDomain = ['en', 'pt', 'es', 'in', 'und', 'ja', 'tr', 'fr', 'tl', 'ru', 'ar', 'th', 'it', 'nl', 'sv', 'ht', 'de', 'et', 'pl', 'sl', 'ko', 'fi', 'lv', 'sk', 'uk', 'da', 'zh', 'ro', 'no', 'cy', 'iw', 'hu', 'bg', 'lt', 'bs', 'vi', 'el', 'is', 'hi', 'hr', 'fa', 'ur', 'ne', 'ta', 'sr', 'bn', 'si', 'ml', 'hy', 'lo', 'iu', 'ka', 'ps', 'te', 'pa', 'am', 'kn', 'chr', 'my', 'gu', 'ckb', 'km', 'ug', 'sd', 'bo', 'dv'];
var langOriginColors = ["#27aeef", "#ea5545", "#87bc45", "#b33dc6", "#f46a9b", "#ede15b", "#bdcf32", "#ef9b20", "#4db6ac", "#edbf33", "#7c4dff"]
var langColors = [];
var pointMapDim = crossFilter.dimension(null).projectOn(["conv_4326_900913_x(lon) as x", "conv_4326_900913_y(lat) as y", "lang as color", "followers as size"]);
var xDim = crossFilter.dimension("lon");
var yDim = crossFilter.dimension("lat");
var parent = document.getElementById("chart1-example");
mapLangColors(40);
var mapboxToken = "pk.eyJ1IjoibWFwZCIsImEiOiJjaWV1a3NqanYwajVsbmdtMDZzc2pneDVpIn0.cJnk8c2AxdNiRNZWtx5A9g";
/* Point Map Radius Size:
* in order to calculate the radius size. We use d3 scale and pass in a
* domain and range.
To learn more about d3 scales, please read this:
https://github.com/d3/d3-scale
We then pass this scale into the r function within bubbleRasterChart
*/
var sizeScale = d3.scale.linear().domain([0,5000]).range([1,5]);
var pointMapChart = dc
.rasterChart(parent, true)
.con(con)
.height(h/1.5)
.width(w)
.mapUpdateInterval(750)
.mapStyle('json/dark-v8.json')
.mapboxToken(mapboxToken) // need a mapbox accessToken for loading the tiles
var pointLayer = dc
.rasterLayer("points")
.crossfilter(crossFilter)
.setState({
transform: {
sample: true,
limit: 500000
},
mark: "point",
encoding: {
x: {
type: "quantitative",
field: "conv_4326_900913_x(lon)"
},
y: {
type: "quantitative",
field: "conv_4326_900913_y(lat)"
},
size: 11,
color: {
type: "ordinal",
field: "lang",
domain: langDomain,
range: langColors
}
},
config: {
point: {
shape: "triangle-up"
}
}
})
.xDim(xDim)
.yDim(yDim)
.popupColumns(['tweet_text', 'sender_name', 'tweet_time', 'lang', 'origin', 'followers'])
function mapLangColors(n) {
langDomain = langDomain.slice(0, n);
for (var i = 0; i < langDomain.length; i++) {
langColors.push(langOriginColors[i%langOriginColors.length]);
}
}
pointMapChart.pushLayer("points", pointLayer).init().then((chart) => {
// custom click handler with just event data (no network calls)
pointMapChart.map().on('mouseup', logClick)
function logClick (result) {
console.log("clicked!", result)
}
// disable with pointMapChart.map().off('mouseup', logClick)
// custom click handler with event and nearest row data
pointMapChart.map().on('mouseup', logClickWithData)
function logClickWithData (event) {
pointMapChart.getClosestResult(event.point, function(result){
console.log(result && result.row_set[0])
})
}
// hover effect with popup
var debouncedPopup = _.debounce(displayPopupWithData, 250)
pointMapChart.map().on('mousewheel', pointMapChart.hidePopup);
pointMapChart.map().on('mousemove', pointMapChart.hidePopup)
pointMapChart.map().on('mousemove', debouncedPopup)
function displayPopupWithData (event) {
pointMapChart.getClosestResult(event.point, pointMapChart.displayPopup)
}
initGeocoder(pointMapChart);
/* Find additional mapbox styles here:
*
* https://github.com/mapbox/mapbox-gl-styles/tree/master/styles
*/
/*
* Callback used for JSONP request for Google's Geocoder
*/
function mapApiLoaded() {
globalGeocoder = new google.maps.Geocoder();
geocoderObject.geocoder = globalGeocoder;
}
/*
* Style and position your geocoder textbox here using standard jQuery
* Also, set any event listeners you'd like.
*/
function initGeocoder (chart) {
this.geocoder = new Geocoder(chart);
this.geocoder.init(chart.map());
this.geocoderInput = $('<input class="geocoder-input" type="text" placeholder="Zoom to"></input>').appendTo($("#chart1-example"));
this.geocoderInput.css({ top: '5px', right: '5px', float: 'right', position: 'absolute'});
// set a key-up event handler for the enter key
this.geocoderInput.keyup(function(e) {
if(e.keyCode == 13) { this.geocoder.geocode(this.geocoderInput.val()); }
}.bind(this));
}
/*
* The Geocoder wrapper for Google Maps' geocoder.
* Has an ultra-small API that simply allows for geocoding a placeName.
*/
function Geocoder(chart) {
this.geocoder = null;
this.geocode = function(placeName) {
this.geocoder.geocode({ 'address': placeName }, this._onResult);
}
this._onResult = function(data, status) {
if (status != google.maps.GeocoderStatus.OK) {
//throw "Geocoder error";
return null;
}
var viewport = data[0].geometry.viewport;
var sw = viewport.getSouthWest();
var ne = viewport.getNorthEast();
chart.map().fitBounds([ // api specifies lng/lat pairs
[sw.lng(), sw.lat()],
[ne.lng(), ne.lat()]
], { animate: false }); // set animate to true if you want to pan-and-zoom to the location
}
this.init = function() {
if (globalGeocoder == null) { // have to give global callback for when the loaded google js is executed
geocoderObject = this;
$.getScript("https://maps.google.com/maps/api/js?sensor=false&async=2&callback=mapApiLoaded", function() {});
}
else {
this.geocoder = globalGeocoder;
}
}
};
/*---------------------TIME CHART EXAMPLE----------------------------------*/
/*
* First we want to determine the extent (min,max) of the time variable so we
* can set the bounds on the time chart appropriately.
*
* If you know the bounds a priori you can do this manually but here we will
* do it dymaically via a query sent to the backend through the crossfilter
* api.
*
* We create a reduceMulti expression that will get the min and max of the
* variable dep_timestamp.
*
*/
var timeChartMeasures = [
{
expression: "tweet_time",
agg_mode:"min",
name: "minimum"
},
{
expression: "tweet_time",
agg_mode:"max",
name: "maximum"}
]
/* Note than when we are doing aggregations over the entire dataset we use
* the crossfilter object itself as the dimension with the groupAll method
*
* values(true) gets the values for our groupAll measure (here min and max
* of dep_timestamp) - true means to ignore currently set filters - i.e.
* get a global min and max
*/
crossFilter
.groupAll()
.reduce(timeChartMeasures)
.valuesAsync(true).then(function(timeChartBounds) {
var timeChartDimension = crossFilter.dimension("tweet_time");
/* We would like to bin or histogram the time values. We do this by
* invoking setBinParams on the group. Here we are asking for 400 equal
* sized bins from the min to the max of the time range
*/
var timeChartGroup = timeChartDimension
.group()
.reduceCount('*')
/* We create the time chart as a line chart
* with the following parameters:
*
* Width and height - as above
*
* elasticY(true) - cause the y-axis to scale as filters are changed
*
* renderHorizontalGridLines(true) - add grid lines to the chart
*
* brushOn(true) - Request a filter brush to be added to the chart - this
* will allow users to drag a filter window along the time chart and filter
* the rest of the data accordingly
*
*/
var dcTimeChart = dc.lineChart('.chart2-example')
.width(w)
.height(h/2.5)
.elasticY(true)
.renderHorizontalGridLines(true)
.brushOn(true)
.xAxisLabel('Time of Day')
.yAxisLabel('Number of Tweets')
.dimension(timeChartDimension)
.group(timeChartGroup)
.binParams({
numBins: 288, // 288 * 5 = number of minutes in a day
binBounds: [timeChartBounds.minimum, timeChartBounds.maximum]
});
/* Set the x and y axis formatting with standard d3 functions */
dcTimeChart
.x(d3.time.scale.utc().domain([timeChartBounds.minimum, timeChartBounds.maximum]))
.yAxis().ticks(5);
dcTimeChart
.xAxis()
.scale(dcTimeChart.x())
.tickFormat(dc.utils.customTimeFormat)
.orient('top');
/*---------------------SET UP FILTER ----------------------------------*/
/* We create the filter
* with the following parameters:
*
* Dimension: What column you would like to search.
*
* OPTIONAL: setDrillDownFilter: Boolean
* if set to true, your search will "AND" for all values, instead of
* the default "OR" for all values.
*
* eg. If searching "Keyboard Cat":
* This will only only return results where the tweet contains:
* "Keyboard" AND "Cat"
*
*/
var searchFilterTweets = crossFilter.dimension("tweet_tokens")
.setDrillDownFilter(true);
function createMultiFilterArray(search) {
// Empty Search
if (search == "") {
return undefined;
}
return search.split(',').map(function(searchValue) {
return searchValue.trim();
});
}
$('.search-bar').keypress(function(e){
if (e.keyCode === 13) {
var mutipleFilterArray = createMultiFilterArray(e.target.value);
if (mutipleFilterArray) {
searchFilterTweets.filterMulti(mutipleFilterArray);
} else {
// Clears all filters
searchFilterTweets.filter();
}
// You must call redrawAll after applying custom filters.
dc.redrawAllAsync();
}
})
/* Calling dc.renderAll() will render all of the charts we set up. Any
crossFilters applied by the user (via clicking the bar chart, scatter plot or dragging the time brush) will automagically call redraw on the charts without any intervention from us
*/
dc.renderAllAsync()
/*--------------------------RESIZE EVENT------------------------------*/
/* Here we listen to any resizes of the main window. On resize we resize the corresponding widgets and call dc.renderAll() to refresh everything */
window.addEventListener("resize", _.debounce(reSizeAll, 500));
function reSizeAll(){
var w = document.documentElement.clientWidth - 30;
var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0) - 200;
pointMapChart.map().resize();
pointMapChart.isNodeAnimate = false;
pointMapChart
.width(w)
.height(h/1.5)
.render();
dcTimeChart
.width(w)
.height(h/2.5)
dc.redrawAllAsync();
}
});
})
}
function init() {
// A MapdCon instance is used for performing raw queries on a MapD GPU database.
new MapdCon()
.protocol("https")
.host("metis.mapd.com")
.port("443")
.dbName("mapd")
.user("mapd")
.password("HyperInteractive")
.connect(function(error, con) {
con.logging(true)
// Get a table from the database
var tableName = 'tweets_nov_feb';
// A CrossFilter instance is used for generating the raw query strings for your MapdCon.
var crossFilter = crossfilter.crossfilter(con, tableName)
.then(function(cf) {
createCharts(cf, con, tableName)
})
// Pass instance of crossfilter into our CreateCharts.
;
});
}
document.addEventListener('DOMContentLoaded', init, false);
function mapApiLoaded() {
globalGeocoder = new google.maps.Geocoder();
geocoderObject.geocoder = globalGeocoder;
}
</script>
</body>
</html>