mirror of
https://github.com/apache/superset.git
synced 2026-04-23 18:14:56 +00:00
* simple mapbox viz use react-map-gl superclustering of long/lat points Added hook for map style, huge performance boost from bounding box fix, added count text on clusters variable gradient size based on metric count Ability to aggregate over any point property This needed a change in the supercluster npm module, a PR was placed here: https://github.com/mapbox/supercluster/pull/12 Aggregator function option in explore, tweaked visual defaults better radius size management clustering radius, point metric/unit options scale cluster labels that don't fit, non-numeric labels for points Minor fixes, label field affects points, text changes serve mapbox apikey for slice global opacity, viewport saves (hacky), bug in point labels fixing mapbox-gl dependency mapbox_api_key in config expose row_limit, fix minor bugs Add renderWhileDragging flag, groupby. Only show numerical columns for point radius Implicitly group by lng/lat columns and error when label doesn't match groupby 'Fix' radius in miles problem, still some jankiness derived fields cannot be typed as of now -> reverting numerical number change better grouping error checking, expose count(*) for labelling Custom colour for clusters/points + smart text colouring Fixed bad positioning and overflow in explore view + small bugs + added thumbnail * landscaping & eslint & use izip * landscapin' * address js code review
411 lines
12 KiB
JavaScript
411 lines
12 KiB
JavaScript
var $ = require('jquery');
|
|
var jQuery = $;
|
|
var d3 = require('d3');
|
|
var Mustache = require('mustache');
|
|
|
|
// vis sources
|
|
var sourceMap = {
|
|
area: 'nvd3_vis.js',
|
|
bar: 'nvd3_vis.js',
|
|
bubble: 'nvd3_vis.js',
|
|
big_number: 'big_number.js',
|
|
big_number_total: 'big_number.js',
|
|
compare: 'nvd3_vis.js',
|
|
dist_bar: 'nvd3_vis.js',
|
|
directed_force: 'directed_force.js',
|
|
filter_box: 'filter_box.js',
|
|
heatmap: 'heatmap.js',
|
|
iframe: 'iframe.js',
|
|
line: 'nvd3_vis.js',
|
|
markup: 'markup.js',
|
|
para: 'parallel_coordinates.js',
|
|
pie: 'nvd3_vis.js',
|
|
box_plot: 'nvd3_vis.js',
|
|
pivot_table: 'pivot_table.js',
|
|
sankey: 'sankey.js',
|
|
sunburst: 'sunburst.js',
|
|
table: 'table.js',
|
|
word_cloud: 'word_cloud.js',
|
|
world_map: 'world_map.js',
|
|
treemap: 'treemap.js',
|
|
cal_heatmap: 'cal_heatmap.js',
|
|
horizon: 'horizon.js',
|
|
mapbox: 'mapbox.jsx'
|
|
};
|
|
|
|
var color = function () {
|
|
// Color related utility functions go in this object
|
|
var bnbColors = [
|
|
//rausch hackb kazan babu lima beach barol
|
|
'#ff5a5f', '#7b0051', '#007A87', '#00d1c1', '#8ce071', '#ffb400', '#b4a76c',
|
|
'#ff8083', '#cc0086', '#00a1b3', '#00ffeb', '#bbedab', '#ffd266', '#cbc29a',
|
|
'#ff3339', '#ff1ab1', '#005c66', '#00b3a5', '#55d12e', '#b37e00', '#988b4e'
|
|
];
|
|
var spectrums = {
|
|
blue_white_yellow: ['#00d1c1', 'white', '#ffb400'],
|
|
fire: ['white', 'yellow', 'red', 'black'],
|
|
white_black: ['white', 'black'],
|
|
black_white: ['black', 'white']
|
|
};
|
|
var colorBnb = function () {
|
|
// Color factory
|
|
var seen = {};
|
|
return function (s) {
|
|
if (!s) { return; }
|
|
s = String(s);
|
|
// next line is for caravel series that should have the same color
|
|
s = s.replace('---', '');
|
|
if (seen[s] === undefined) {
|
|
seen[s] = Object.keys(seen).length;
|
|
}
|
|
return this.bnbColors[seen[s] % this.bnbColors.length];
|
|
};
|
|
};
|
|
var colorScalerFactory = function (colors, data, accessor) {
|
|
// Returns a linear scaler our of an array of color
|
|
if (!Array.isArray(colors)) {
|
|
colors = spectrums[colors];
|
|
}
|
|
|
|
var ext = [0, 1];
|
|
if (data !== undefined) {
|
|
ext = d3.extent(data, accessor);
|
|
}
|
|
|
|
var points = [];
|
|
var chunkSize = (ext[1] - ext[0]) / colors.length;
|
|
$.each(colors, function (i, c) {
|
|
points.push(i * chunkSize);
|
|
});
|
|
return d3.scale.linear().domain(points).range(colors);
|
|
};
|
|
return {
|
|
bnbColors: bnbColors,
|
|
category21: colorBnb(),
|
|
colorScalerFactory: colorScalerFactory
|
|
};
|
|
};
|
|
|
|
var px = (function () {
|
|
|
|
var visualizations = {};
|
|
var slice;
|
|
|
|
function getParam(name) {
|
|
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
|
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
|
results = regex.exec(location.search);
|
|
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
|
|
}
|
|
|
|
function UTC(dttm) {
|
|
return new Date(dttm.getUTCFullYear(), dttm.getUTCMonth(), dttm.getUTCDate(), dttm.getUTCHours(), dttm.getUTCMinutes(), dttm.getUTCSeconds());
|
|
}
|
|
var tickMultiFormat = d3.time.format.multi([
|
|
[".%L", function (d) {
|
|
return d.getMilliseconds();
|
|
}], // If there are millisections, show only them
|
|
[":%S", function (d) {
|
|
return d.getSeconds();
|
|
}], // If there are seconds, show only them
|
|
["%a %b %d, %I:%M %p", function (d) {
|
|
return d.getMinutes() !== 0;
|
|
}], // If there are non-zero minutes, show Date, Hour:Minute [AM/PM]
|
|
["%a %b %d, %I %p", function (d) {
|
|
return d.getHours() !== 0;
|
|
}], // If there are hours that are multiples of 3, show date and AM/PM
|
|
["%a %b %d", function (d) {
|
|
return d.getDate() !== 1;
|
|
}], // If not the first of the month, do "month day, year."
|
|
["%B %Y", function (d) {
|
|
return d.getMonth() !== 0 && d.getDate() === 1;
|
|
}], // If the first of the month, do "month day, year."
|
|
["%Y", function (d) {
|
|
return true;
|
|
}] // fall back on month, year
|
|
]);
|
|
|
|
function formatDate(dttm) {
|
|
var d = UTC(new Date(dttm));
|
|
//d = new Date(d.getTime() - 1 * 60 * 60 * 1000);
|
|
return tickMultiFormat(d);
|
|
}
|
|
|
|
function timeFormatFactory(d3timeFormat) {
|
|
var f = d3.time.format(d3timeFormat);
|
|
return function (dttm) {
|
|
var d = UTC(new Date(dttm));
|
|
return f(d);
|
|
};
|
|
}
|
|
|
|
function initFavStars() {
|
|
var baseUrl = '/caravel/favstar/';
|
|
// Init star behavihor for favorite
|
|
function show() {
|
|
if ($(this).hasClass('selected')) {
|
|
$(this).html('<i class="fa fa-star"></i>');
|
|
} else {
|
|
$(this).html('<i class="fa fa-star-o"></i>');
|
|
}
|
|
}
|
|
$('.favstar')
|
|
.attr('title', 'Click to favorite/unfavorite')
|
|
.each(show)
|
|
.each(function () {
|
|
var url = baseUrl + $(this).attr("class_name");
|
|
var star = this;
|
|
url += '/' + $(this).attr("obj_id") + '/';
|
|
$.getJSON(url + 'count/', function (data) {
|
|
if (data.count > 0) {
|
|
$(star)
|
|
.addClass('selected')
|
|
.each(show);
|
|
}
|
|
});
|
|
})
|
|
.click(function () {
|
|
$(this).toggleClass('selected');
|
|
var url = baseUrl + $(this).attr("class_name");
|
|
url += '/' + $(this).attr("obj_id") + '/';
|
|
if ($(this).hasClass('selected')) {
|
|
url += 'select/';
|
|
} else {
|
|
url += 'unselect/';
|
|
}
|
|
$.get(url);
|
|
$(this).each(show);
|
|
})
|
|
.tooltip();
|
|
}
|
|
|
|
var Slice = function (data, dashboard) {
|
|
var timer;
|
|
var token = $('#' + data.token);
|
|
var container_id = data.token + '_con';
|
|
var selector = '#' + container_id;
|
|
var container = $(selector);
|
|
var slice_id = data.slice_id;
|
|
var dttm = 0;
|
|
var stopwatch = function () {
|
|
dttm += 10;
|
|
var num = dttm / 1000;
|
|
$('#timer').text(num.toFixed(2) + " sec");
|
|
};
|
|
var qrystr = '';
|
|
var always = function (data) {
|
|
//Private f, runs after done and error
|
|
clearInterval(timer);
|
|
$('#timer').removeClass('btn-warning');
|
|
};
|
|
slice = {
|
|
data: data,
|
|
container: container,
|
|
container_id: container_id,
|
|
selector: selector,
|
|
querystring: function () {
|
|
var parser = document.createElement('a');
|
|
parser.href = data.json_endpoint;
|
|
if (dashboard !== undefined) {
|
|
var flts = encodeURIComponent(JSON.stringify(dashboard.filters));
|
|
qrystr = parser.search + "&extra_filters=" + flts;
|
|
} else if ($('#query').length === 0) {
|
|
qrystr = parser.search;
|
|
} else {
|
|
qrystr = '?' + $('#query').serialize();
|
|
}
|
|
return qrystr;
|
|
},
|
|
getWidgetHeader: function () {
|
|
return this.container.parents("div.widget").find(".chart-header");
|
|
},
|
|
render_template: function (s) {
|
|
var context = {
|
|
width: this.width,
|
|
height: this.height
|
|
};
|
|
return Mustache.render(s, context);
|
|
},
|
|
jsonEndpoint: function () {
|
|
var parser = document.createElement('a');
|
|
parser.href = data.json_endpoint;
|
|
var endpoint = parser.pathname + this.querystring();
|
|
endpoint += "&json=true";
|
|
endpoint += "&force=" + this.force;
|
|
return endpoint;
|
|
},
|
|
done: function (data) {
|
|
clearInterval(timer);
|
|
token.find("img.loading").hide();
|
|
container.show();
|
|
|
|
var cachedSelector = null;
|
|
if (dashboard === undefined) {
|
|
cachedSelector = $('#is_cached');
|
|
if (data !== undefined && data.is_cached) {
|
|
cachedSelector
|
|
.attr('title', 'Served from data cached at ' + data.cached_dttm + '. Click to force-refresh')
|
|
.show()
|
|
.tooltip('fixTitle');
|
|
} else {
|
|
cachedSelector.hide();
|
|
}
|
|
} else {
|
|
var refresh = this.getWidgetHeader().find('.refresh');
|
|
if (data !== undefined && data.is_cached) {
|
|
refresh
|
|
.addClass('danger')
|
|
.attr(
|
|
'title',
|
|
'Served from data cached at ' + data.cached_dttm + '. Click to force-refresh')
|
|
.tooltip('fixTitle');
|
|
} else {
|
|
refresh
|
|
.removeClass('danger')
|
|
.attr(
|
|
'title',
|
|
'Click to force-refresh')
|
|
.tooltip('fixTitle');
|
|
}
|
|
}
|
|
if (data !== undefined) {
|
|
$("#query_container").html(data.query);
|
|
}
|
|
$('#timer').removeClass('btn-warning');
|
|
$('#timer').addClass('btn-success');
|
|
$('span.query').removeClass('disabled');
|
|
$('#json').click(function () {
|
|
window.location = data.json_endpoint;
|
|
});
|
|
$('#csv').click(function () {
|
|
window.location = data.csv_endpoint;
|
|
});
|
|
$('.btn-group.results span,a').removeAttr('disabled');
|
|
$('.query-and-save button').removeAttr('disabled');
|
|
always(data);
|
|
},
|
|
getErrorMsg: function (xhr) {
|
|
if (xhr.statusText === "timeout") {
|
|
return "The request timed out";
|
|
}
|
|
var msg = "";
|
|
if (!xhr.responseText) {
|
|
var status = xhr.status;
|
|
msg += "An unknown error occurred. (Status: " + status + ")";
|
|
if (status === 0) {
|
|
// This may happen when the worker in gunicorn times out
|
|
msg += " Maybe the request timed out?";
|
|
}
|
|
}
|
|
return msg;
|
|
},
|
|
error: function (msg, xhr) {
|
|
token.find("img.loading").hide();
|
|
var err = msg ? ('<div class="alert alert-danger">' + msg + '</div>') : "";
|
|
if (xhr) {
|
|
err += '<div class="alert alert-danger">' + this.getErrorMsg(xhr) + '</div>';
|
|
}
|
|
container.html(err);
|
|
container.show();
|
|
$('span.query').removeClass('disabled');
|
|
$('#timer').addClass('btn-danger');
|
|
$('.btn-group.results span,a').removeAttr('disabled');
|
|
$('.query-and-save button').removeAttr('disabled');
|
|
always(data);
|
|
},
|
|
width: function () {
|
|
return token.width();
|
|
},
|
|
height: function () {
|
|
var others = 0;
|
|
var widget = container.parents('.widget');
|
|
var slice_description = widget.find('.slice_description');
|
|
if (slice_description.is(":visible")) {
|
|
others += widget.find('.slice_description').height() + 25;
|
|
}
|
|
others += widget.find('.chart-header').height();
|
|
return widget.height() - others - 10;
|
|
},
|
|
bindResizeToWindowResize: function () {
|
|
var resizeTimer;
|
|
var slice = this;
|
|
$(window).on('resize', function (e) {
|
|
clearTimeout(resizeTimer);
|
|
resizeTimer = setTimeout(function () {
|
|
slice.resize();
|
|
}, 500);
|
|
});
|
|
},
|
|
render: function (force) {
|
|
if (force === undefined) {
|
|
force = false;
|
|
}
|
|
this.force = force;
|
|
token.find("img.loading").show();
|
|
container.css('height', this.height());
|
|
dttm = 0;
|
|
timer = setInterval(stopwatch, 10);
|
|
$('#timer').removeClass('btn-danger btn-success');
|
|
$('#timer').addClass('btn-warning');
|
|
this.viz.render();
|
|
},
|
|
resize: function () {
|
|
token.find("img.loading").show();
|
|
container.css('height', this.height());
|
|
this.viz.render();
|
|
this.viz.resize();
|
|
},
|
|
addFilter: function (col, vals) {
|
|
if (dashboard !== undefined) {
|
|
dashboard.addFilter(slice_id, col, vals);
|
|
}
|
|
},
|
|
setFilter: function (col, vals) {
|
|
if (dashboard !== undefined) {
|
|
dashboard.setFilter(slice_id, col, vals);
|
|
}
|
|
},
|
|
clearFilter: function () {
|
|
if (dashboard !== undefined) {
|
|
delete dashboard.clearFilter(slice_id);
|
|
}
|
|
},
|
|
removeFilter: function (col, vals) {
|
|
if (dashboard !== undefined) {
|
|
delete dashboard.removeFilter(slice_id, col, vals);
|
|
}
|
|
}
|
|
};
|
|
var visType = data.form_data.viz_type;
|
|
px.registerViz(visType);
|
|
slice.viz = visualizations[data.form_data.viz_type](slice);
|
|
return slice;
|
|
};
|
|
|
|
function registerViz(name) {
|
|
var visSource = sourceMap[name];
|
|
|
|
if (visSource) {
|
|
var visFactory = require('../../visualizations/' + visSource);
|
|
if (typeof visFactory === 'function') {
|
|
visualizations[name] = visFactory;
|
|
}
|
|
} else {
|
|
throw new Error("require(" + name + ") failed.");
|
|
}
|
|
}
|
|
|
|
// Export public functions
|
|
return {
|
|
registerViz: registerViz,
|
|
Slice: Slice,
|
|
formatDate: formatDate,
|
|
timeFormatFactory: timeFormatFactory,
|
|
color: color(),
|
|
getParam: getParam,
|
|
initFavStars: initFavStars
|
|
};
|
|
})();
|
|
|
|
module.exports = px;
|