@@ -80,31 +74,24 @@ FilterBox.propTypes = {
};
FilterBox.defaultProps = {
origSelectedValues: {},
- onChange: function () {},
+ onChange() {},
};
function filterBox(slice) {
- const filtersObj = {};
const d3token = d3.select(slice.selector);
const refresh = function () {
d3token.selectAll('*').remove();
- const container = d3token
- .append('div')
- .classed('padded', true);
- const preSelectDict = slice.getFilters() || {};
// filter box should ignore the dashboard's filters
- const url = slice.jsonEndpoint({ extraFilters: false});
+ const url = slice.jsonEndpoint({ extraFilters: false });
$.getJSON(url, (payload) => {
ReactDOM.render(
- (
-
- ),
+
,
document.getElementById(slice.containerId)
);
slice.done(payload);
diff --git a/caravel/assets/visualizations/heatmap.js b/caravel/assets/visualizations/heatmap.js
index c51b748584742..c0750ba56a2c4 100644
--- a/caravel/assets/visualizations/heatmap.js
+++ b/caravel/assets/visualizations/heatmap.js
@@ -1,19 +1,16 @@
-// JS
-var $ = window.$ || require('jquery');
-var d3 = require('d3');
-import { colorScalerFactory } from '../javascripts/modules/colors'
+import d3 from 'd3';
+import { colorScalerFactory } from '../javascripts/modules/colors';
-d3.tip = require('d3-tip'); //using window.d3 doesn't capture events properly bc of multiple instances
+const $ = require('jquery');
+d3.tip = require('d3-tip');
-// CSS
require('./heatmap.css');
// Inspired from http://bl.ocks.org/mbostock/3074470
// https://jsfiddle.net/cyril123/h0reyumq/
function heatmapVis(slice) {
-
function refresh() {
- var margin = {
+ const margin = {
top: 10,
right: 10,
bottom: 35,
@@ -21,38 +18,27 @@ function heatmapVis(slice) {
};
d3.json(slice.jsonEndpoint(), function (error, payload) {
- slice.container.html('');
- var matrix = {};
- if (error) {
- slice.error(error.responseText, error);
- return '';
- }
- var fd = payload.form_data;
- var data = payload.data;
-
+ const data = payload.data;
// Dynamically adjusts based on max x / y category lengths
- function adjustMargins(data, margins) {
- var pixelsPerCharX = 4.5; // approx, depends on font size
- var pixelsPerCharY = 6.8; // approx, depends on font size
- var longestX = 1;
- var longestY = 1;
- var datum;
-
- for (var i = 0; i < data.length; i++) {
+ function adjustMargins() {
+ const pixelsPerCharX = 4.5; // approx, depends on font size
+ const pixelsPerCharY = 6.8; // approx, depends on font size
+ let longestX = 1;
+ let longestY = 1;
+ let datum;
+
+ for (let i = 0; i < data.length; i++) {
datum = data[i];
longestX = Math.max(longestX, datum.x.length || 1);
longestY = Math.max(longestY, datum.y.length || 1);
}
- margins.left = Math.ceil(Math.max(margins.left, pixelsPerCharY * longestY));
- margins.bottom = Math.ceil(Math.max(margins.bottom, pixelsPerCharX * longestX));
+ margin.left = Math.ceil(Math.max(margin.left, pixelsPerCharY * longestY));
+ margin.bottom = Math.ceil(Math.max(margin.bottom, pixelsPerCharX * longestX));
}
- function ordScale(k, rangeBands, reverse) {
- if (reverse === undefined) {
- reverse = false;
- }
- var domain = {};
+ function ordScale(k, rangeBands, reverse = false) {
+ let domain = {};
$.each(data, function (i, d) {
domain[d[k]] = true;
});
@@ -64,29 +50,37 @@ function heatmapVis(slice) {
}
if (rangeBands === undefined) {
return d3.scale.ordinal().domain(domain).range(d3.range(domain.length));
- } else {
- return d3.scale.ordinal().domain(domain).rangeBands(rangeBands);
}
+ return d3.scale.ordinal().domain(domain).rangeBands(rangeBands);
}
- adjustMargins(data, margin);
- var width = slice.width();
- var height = slice.height();
- var hmWidth = width - (margin.left + margin.right);
- var hmHeight = height - (margin.bottom + margin.top);
- var fp = d3.format('.3p');
+ slice.container.html('');
+ const matrix = {};
+ if (error) {
+ slice.error(error.responseText, error);
+ return;
+ }
+ const fd = payload.form_data;
+
+ adjustMargins();
+
+ const width = slice.width();
+ const height = slice.height();
+ const hmWidth = width - (margin.left + margin.right);
+ const hmHeight = height - (margin.bottom + margin.top);
+ const fp = d3.format('.3p');
- var xScale = ordScale('x');
- var yScale = ordScale('y', undefined, true);
- var xRbScale = ordScale('x', [0, hmWidth]);
- var yRbScale = ordScale('y', [hmHeight, 0]);
- var X = 0,
- Y = 1;
- var heatmapDim = [xRbScale.domain().length, yRbScale.domain().length];
+ const xScale = ordScale('x');
+ const yScale = ordScale('y', undefined, true);
+ const xRbScale = ordScale('x', [0, hmWidth]);
+ const yRbScale = ordScale('y', [hmHeight, 0]);
+ const X = 0;
+ const Y = 1;
+ const heatmapDim = [xRbScale.domain().length, yRbScale.domain().length];
- var color = colorScalerFactory(fd.linear_color_scheme);
+ const color = colorScalerFactory(fd.linear_color_scheme);
- var scale = [
+ const scale = [
d3.scale.linear()
.domain([0, heatmapDim[X]])
.range([0, hmWidth]),
@@ -95,107 +89,106 @@ function heatmapVis(slice) {
.range([0, hmHeight]),
];
- var container = d3.select(slice.selector);
-
- var canvas = container.append("canvas")
- .attr("width", heatmapDim[X])
- .attr("height", heatmapDim[Y])
- .style("width", hmWidth + "px")
- .style("height", hmHeight + "px")
- .style("image-rendering", fd.canvas_image_rendering)
- .style("left", margin.left + "px")
- .style("top", margin.top + "px")
- .style("position", "absolute");
-
- var svg = container.append("svg")
- .attr("width", width)
- .attr("height", height)
- .style("left", "0px")
- .style("top", "0px")
- .style("position", "absolute");
-
- var rect = svg.append('g')
- .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
+ const container = d3.select(slice.selector);
+
+ const canvas = container.append('canvas')
+ .attr('width', heatmapDim[X])
+ .attr('height', heatmapDim[Y])
+ .style('width', hmWidth + 'px')
+ .style('height', hmHeight + 'px')
+ .style('image-rendering', fd.canvas_image_rendering)
+ .style('left', margin.left + 'px')
+ .style('top', margin.top + 'px')
+ .style('position', 'absolute');
+
+ const svg = container.append('svg')
+ .attr('width', width)
+ .attr('height', height)
+ .style('left', '0px')
+ .style('top', '0px')
+ .style('position', 'absolute');
+
+ const rect = svg.append('g')
+ .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.append('rect')
.style('fill-opacity', 0)
.attr('stroke', 'black')
- .attr("width", hmWidth)
- .attr("height", hmHeight);
+ .attr('width', hmWidth)
+ .attr('height', hmHeight);
- var tip = d3.tip()
+ const tip = d3.tip()
.attr('class', 'd3-tip')
.offset(function () {
- var k = d3.mouse(this);
- var x = k[0] - (hmWidth / 2);
+ const k = d3.mouse(this);
+ const x = k[0] - (hmWidth / 2);
return [k[1] - 20, x];
})
- .html(function (d) {
- var s = "";
- var k = d3.mouse(this);
- var m = Math.floor(scale[0].invert(k[0]));
- var n = Math.floor(scale[1].invert(k[1]));
+ .html(function () {
+ let s = '';
+ const k = d3.mouse(this);
+ const m = Math.floor(scale[0].invert(k[0]));
+ const n = Math.floor(scale[1].invert(k[1]));
if (m in matrix && n in matrix[m]) {
- var obj = matrix[m][n];
- s += "
" + fd.all_columns_x + ": " + obj.x + "
";
- s += "
" + fd.all_columns_y + ": " + obj.y + "
";
- s += "
" + fd.metric + ": " + obj.v + "
";
- s += "
%: " + fp(obj.perc) + "
";
- tip.style("display", null);
+ const obj = matrix[m][n];
+ s += '
' + fd.all_columns_x + ': ' + obj.x + '
';
+ s += '
' + fd.all_columns_y + ': ' + obj.y + '
';
+ s += '
' + fd.metric + ': ' + obj.v + '
';
+ s += '
%: ' + fp(obj.perc) + '
';
+ tip.style('display', null);
} else {
// this is a hack to hide the tooltip because we have map it to a single
// d3-tip toggles opacity and calling hide here is undone by the lib after this call
- tip.style("display", "none");
+ tip.style('display', 'none');
}
return s;
});
rect.call(tip);
- var xAxis = d3.svg.axis()
+ const xAxis = d3.svg.axis()
.scale(xRbScale)
.tickValues(xRbScale.domain().filter(
function (d, i) {
return !(i % (parseInt(fd.xscale_interval, 10)));
}))
- .orient("bottom");
+ .orient('bottom');
- var yAxis = d3.svg.axis()
+ const yAxis = d3.svg.axis()
.scale(yRbScale)
.tickValues(yRbScale.domain().filter(
function (d, i) {
return !(i % (parseInt(fd.yscale_interval, 10)));
}))
- .orient("left");
+ .orient('left');
- svg.append("g")
- .attr("class", "x axis")
- .attr("transform", "translate(" + margin.left + "," + (margin.top + hmHeight) + ")")
+ svg.append('g')
+ .attr('class', 'x axis')
+ .attr('transform', 'translate(' + margin.left + ',' + (margin.top + hmHeight) + ')')
.call(xAxis)
- .selectAll("text")
- .style("text-anchor", "end")
- .attr("transform", "rotate(-45)");
+ .selectAll('text')
+ .style('text-anchor', 'end')
+ .attr('transform', 'rotate(-45)');
- svg.append("g")
- .attr("class", "y axis")
- .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
+ svg.append('g')
+ .attr('class', 'y axis')
+ .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.call(yAxis);
rect.on('mousemove', tip.show);
rect.on('mouseout', tip.hide);
- var context = canvas.node().getContext("2d");
+ const context = canvas.node().getContext('2d');
context.imageSmoothingEnabled = false;
- createImageObj();
// Compute the pixel colors; scaled by CSS.
function createImageObj() {
- var imageObj = new Image();
- var image = context.createImageData(heatmapDim[0], heatmapDim[1]);
- var pixs = {};
+ const imageObj = new Image();
+ const image = context.createImageData(heatmapDim[0], heatmapDim[1]);
+ const pixs = {};
$.each(data, function (i, d) {
- var c = d3.rgb(color(d.perc));
- var x = xScale(d.x);
- var y = yScale(d.y);
+ const c = d3.rgb(color(d.perc));
+ const x = xScale(d.x);
+ const y = yScale(d.y);
pixs[x + (y * xScale.domain().length)] = c;
if (matrix[x] === undefined) {
matrix[x] = {};
@@ -205,10 +198,10 @@ function heatmapVis(slice) {
}
});
- var p = -1;
- for (var i = 0; i < heatmapDim[0] * heatmapDim[1]; i++) {
- var c = pixs[i];
- var alpha = 255;
+ let p = -1;
+ for (let i = 0; i < heatmapDim[0] * heatmapDim[1]; i++) {
+ let c = pixs[i];
+ let alpha = 255;
if (c === undefined) {
c = d3.rgb('#F00');
alpha = 0;
@@ -222,8 +215,9 @@ function heatmapVis(slice) {
imageObj.src = canvas.node().toDataURL();
}
- slice.done(payload);
+ createImageObj();
+ slice.done(payload);
});
}
return {
diff --git a/caravel/assets/visualizations/histogram.js b/caravel/assets/visualizations/histogram.js
index a7e10e0ddac0c..0cc247b73bd74 100644
--- a/caravel/assets/visualizations/histogram.js
+++ b/caravel/assets/visualizations/histogram.js
@@ -149,4 +149,3 @@ function histogram(slice) {
}
module.exports = histogram;
-
diff --git a/caravel/assets/visualizations/horizon.js b/caravel/assets/visualizations/horizon.js
index a0d0633fa1789..8d713e05d1849 100644
--- a/caravel/assets/visualizations/horizon.js
+++ b/caravel/assets/visualizations/horizon.js
@@ -1,29 +1,36 @@
+/* eslint-disable prefer-rest-params, no-param-reassign */
// Copied and modified from
// https://github.com/kmandov/d3-horizon-chart
-var d3 = require('d3');
+import d3 from 'd3';
require('./horizon.css');
-var horizonChart = function () {
- var colors = ["#313695", "#4575b4", "#74add1", "#abd9e9", "#fee090", "#fdae61", "#f46d43", "#d73027"];
- var bands = colors.length >> 1; // number of bands in each direction (positive / negative)
- var width = 1000;
- var height = 30;
- var offsetX = 0;
- var spacing = 0;
- var mode = 'offset';
- var axis = null;
- var title = null;
- var extent = null; // the extent is derived from the data, unless explicitly set via .extent([min, max])
- var x = null;
- var y = d3.scale.linear().range([0, height]);
- var canvas = null;
-
- var b;
+const horizonChart = function () {
+ let colors = [
+ '#313695',
+ '#4575b4',
+ '#74add1',
+ '#abd9e9',
+ '#fee090',
+ '#fdae61',
+ '#f46d43',
+ '#d73027',
+ ];
+ let height = 30;
+ const y = d3.scale.linear().range([0, height]);
+ let bands = colors.length >> 1; // number of bands in each direction (positive / negative)
+ let width = 1000;
+ let offsetX = 0;
+ let spacing = 0;
+ let mode = 'offset';
+ let axis;
+ let title;
+ let extent; // the extent is derived from the data, unless explicitly set via .extent([min, max])
+ let x;
+ let canvas;
function my(data) {
-
- var horizon = d3.select(this);
- var step = width / data.length;
+ const horizon = d3.select(this);
+ const step = width / data.length;
horizon.append('span')
.attr('class', 'title')
@@ -38,24 +45,24 @@ var horizonChart = function () {
.attr('width', width)
.attr('height', height);
- var context = canvas.node().getContext('2d');
+ const context = canvas.node().getContext('2d');
context.imageSmoothingEnabled = false;
// update the y scale, based on the data extents
- var _extent = extent || d3.extent(data, function (d) { return d.y; });
+ const ext = extent || d3.extent(data, (d) => d.y);
- var max = Math.max(-_extent[0], _extent[1]);
+ const max = Math.max(-ext[0], ext[1]);
y.domain([0, max]);
- //x = d3.scaleTime().domain[];
+ // x = d3.scaleTime().domain[];
axis = d3.svg.axis(x).ticks(5);
context.clearRect(0, 0, width, height);
- //context.translate(0.5, 0.5);
+ // context.translate(0.5, 0.5);
// the data frame currently being shown:
- var startIndex = ~~ Math.max(0, -(offsetX / step));
- var endIndex = ~~ Math.min(data.length, startIndex + width / step);
+ const startIndex = ~~ Math.max(0, -(offsetX / step));
+ const endIndex = ~~ Math.min(data.length, startIndex + width / step);
// skip drawing if there's no data to be drawn
if (startIndex > data.length) {
@@ -64,10 +71,11 @@ var horizonChart = function () {
// we are drawing positive & negative bands separately to avoid mutating canvas state
// http://www.html5rocks.com/en/tutorials/canvas/performance/
- var negative = false;
+ let negative = false;
// draw positive bands
- var i, value, bExtents;
- for (b = 0; b < bands; b++) {
+ let value;
+ let bExtents;
+ for (let b = 0; b < bands; b++) {
context.fillStyle = colors[bands + b];
// Adjust the range based on the current band index.
@@ -75,7 +83,7 @@ var horizonChart = function () {
y.range([bands * height + bExtents, bExtents]);
// only the current data frame is being drawn i.e. what's visible:
- for (i = startIndex; i < endIndex; i++) {
+ for (let i = startIndex; i < endIndex; i++) {
value = data[i].y;
if (value <= 0) { negative = true; continue; }
if (value === undefined) {
@@ -87,14 +95,13 @@ var horizonChart = function () {
// draw negative bands
if (negative) {
-
// mirror the negative bands, by flipping the canvas
if (mode === 'offset') {
context.translate(0, height);
context.scale(1, -1);
}
- for (b = 0; b < bands; b++) {
+ for (let b = 0; b < bands; b++) {
context.fillStyle = colors[bands - b - 1];
// Adjust the range based on the current band index.
@@ -102,7 +109,7 @@ var horizonChart = function () {
y.range([bands * height + bExtents, bExtents]);
// only the current data frame is being drawn i.e. what's visible:
- for (var ii = startIndex; ii < endIndex; ii++) {
+ for (let ii = startIndex; ii < endIndex; ii++) {
value = data[ii].y;
if (value >= 0) {
continue;
@@ -140,7 +147,6 @@ var horizonChart = function () {
// update the number of bands
bands = colors.length >> 1;
-
return my;
};
@@ -185,33 +191,32 @@ var horizonChart = function () {
};
function horizonViz(slice) {
-
function refresh() {
d3.json(slice.jsonEndpoint(), function (error, payload) {
- var fd = payload.form_data;
+ const fd = payload.form_data;
if (error) {
slice.error(error.responseText, error);
- return '';
+ return;
}
- var div = d3.select(slice.selector);
+ const div = d3.select(slice.selector);
div.selectAll('*').remove();
- var extent = null;
+ let extent;
if (fd.horizon_color_scale === 'overall') {
- var allValues = [];
+ let allValues = [];
payload.data.forEach(function (d) {
allValues = allValues.concat(d.values);
});
- extent = d3.extent(allValues, function (d) { return d.y; });
+ extent = d3.extent(allValues, (d) => d.y);
} else if (fd.horizon_color_scale === 'change') {
payload.data.forEach(function (series) {
- var t0y = series.values[0].y; // value at time 0
- series.values.forEach(function (d, i) {
- d.y = d.y - t0y;
- });
+ const t0y = series.values[0].y; // value at time 0
+ series.values = series.values.map((d) =>
+ Object.assign({}, d, { y: d.y - t0y })
+ );
});
}
- div.selectAll(".horizon")
+ div.selectAll('.horizon')
.data(payload.data)
.enter()
.append('div')
diff --git a/caravel/assets/visualizations/iframe.js b/caravel/assets/visualizations/iframe.js
index 5238d20346d46..ae2855e0fff8d 100644
--- a/caravel/assets/visualizations/iframe.js
+++ b/caravel/assets/visualizations/iframe.js
@@ -1,17 +1,16 @@
-var $ = window.$ || require('jquery');
+const $ = require('jquery');
function iframeWidget(slice) {
-
function refresh() {
$('#code').attr('rows', '15');
$.getJSON(slice.jsonEndpoint(), function (payload) {
- var url = slice.render_template(payload.form_data.url);
- slice.container.html('');
- var iframe = slice.container.find('iframe');
- iframe.css('height', slice.height());
- iframe.attr('src', url);
- slice.done(payload);
- })
+ const url = slice.render_template(payload.form_data.url);
+ slice.container.html('');
+ const iframe = slice.container.find('iframe');
+ iframe.css('height', slice.height());
+ iframe.attr('src', url);
+ slice.done(payload);
+ })
.fail(function (xhr) {
slice.error(xhr.responseText, xhr);
});
diff --git a/caravel/assets/visualizations/main.js b/caravel/assets/visualizations/main.js
index 709b162c45fc8..e94046ce3ec07 100644
--- a/caravel/assets/visualizations/main.js
+++ b/caravel/assets/visualizations/main.js
@@ -1,5 +1,5 @@
+/* eslint-disable global-require */
const vizMap = {
- filter_box: require('./filter_box.jsx'),
area: require('./nvd3_vis.js'),
bar: require('./nvd3_vis.js'),
big_number: require('./big_number.js'),
@@ -10,6 +10,7 @@ const vizMap = {
compare: require('./nvd3_vis.js'),
directed_force: require('./directed_force.js'),
dist_bar: require('./nvd3_vis.js'),
+ filter_box: require('./filter_box.jsx'),
heatmap: require('./heatmap.js'),
histogram: require('./histogram.js'),
horizon: require('./horizon.js'),
diff --git a/caravel/assets/visualizations/mapbox.jsx b/caravel/assets/visualizations/mapbox.jsx
index 75a6909f81ce3..2d0e9d4052490 100644
--- a/caravel/assets/visualizations/mapbox.jsx
+++ b/caravel/assets/visualizations/mapbox.jsx
@@ -1,6 +1,6 @@
-const d3 = window.d3 || require('d3');
-require('./mapbox.css');
+/* eslint-disable no-param-reassign */
+import d3 from 'd3';
import React from 'react';
import ReactDOM from 'react-dom';
import MapGL from 'react-map-gl';
@@ -15,11 +15,13 @@ import {
MILES_PER_KM,
DEFAULT_LONGITUDE,
DEFAULT_LATITUDE,
- DEFAULT_ZOOM
+ DEFAULT_ZOOM,
} from '../utils/common';
+require('./mapbox.css');
+
class ScatterPlotGlowOverlay extends ScatterPlotOverlay {
- _drawText(ctx, pixel, options = {}) {
+ drawText(ctx, pixel, options = {}) {
const IS_DARK_THRESHOLD = 110;
const { fontHeight = 0, label = '', radius = 0, rgb = [0, 0, 0], shadow = false } = options;
const maxWidth = radius * 1.8;
@@ -56,8 +58,8 @@ class ScatterPlotGlowOverlay extends ScatterPlotOverlay {
const radius = props.dotRadius;
const mercator = ViewportMercator(props);
const rgb = props.rgb;
+ const clusterLabelMap = [];
let maxLabel = -1;
- let clusterLabelMap = [];
props.locations.forEach(function (location, i) {
if (location.get('properties').get('cluster')) {
@@ -100,7 +102,6 @@ class ScatterPlotGlowOverlay extends ScatterPlotOverlay {
&& pixelRounded[0] - radius < props.width
&& pixelRounded[1] + radius >= 0
&& pixelRounded[1] - radius < props.height) {
-
ctx.beginPath();
if (location.get('properties').get('cluster')) {
let clusterLabel = clusterLabelMap[i];
@@ -118,15 +119,17 @@ class ScatterPlotGlowOverlay extends ScatterPlotOverlay {
ctx.fill();
if (isNumeric(clusterLabel)) {
- clusterLabel = clusterLabel >= 10000 ? Math.round(clusterLabel / 1000) + 'k' :
- clusterLabel >= 1000 ? (Math.round(clusterLabel / 100) / 10) + 'k' :
- clusterLabel;
- this._drawText(ctx, pixelRounded, {
- fontHeight: fontHeight,
+ if (clusterLabel >= 10000) {
+ clusterLabel = Math.round(clusterLabel / 1000) + 'k';
+ } else if (clusterLabel >= 1000) {
+ clusterLabel = (Math.round(clusterLabel / 100) / 10) + 'k';
+ }
+ this.drawText(ctx, pixelRounded, {
+ fontHeight,
label: clusterLabel,
radius: scaledRadius,
- rgb: rgb,
- shadow: true
+ rgb,
+ shadow: true,
});
}
} else {
@@ -136,7 +139,7 @@ class ScatterPlotGlowOverlay extends ScatterPlotOverlay {
let pointRadius = radiusProperty === null ? defaultRadius : radiusProperty;
let pointLabel;
- if (radiusProperty !== null) {
+ if (radiusProperty !== null) {
const pointLatitude = props.lngLatAccessor(location)[1];
if (props.pointRadiusUnit === 'Kilometers') {
pointLabel = d3.round(pointRadius, 2) + 'km';
@@ -161,12 +164,12 @@ class ScatterPlotGlowOverlay extends ScatterPlotOverlay {
ctx.fill();
if (pointLabel !== undefined) {
- this._drawText(ctx, pixelRounded, {
+ this.drawText(ctx, pixelRounded, {
fontHeight: d3.round(pointRadius, 1),
label: pointLabel,
radius: pointRadius,
- rgb: rgb,
- shadow: false
+ rgb,
+ shadow: false,
});
}
}
@@ -187,11 +190,11 @@ class MapboxViz extends React.Component {
this.state = {
viewport: {
- longitude: longitude,
- latitude: latitude,
+ longitude,
+ latitude,
zoom: this.props.viewportZoom || DEFAULT_ZOOM,
- startDragLngLat: [longitude, latitude]
- }
+ startDragLngLat: [longitude, latitude],
+ },
};
this.onChangeViewport = this.onChangeViewport.bind(this);
@@ -199,7 +202,7 @@ class MapboxViz extends React.Component {
onChangeViewport(viewport) {
this.setState({
- viewport: viewport
+ viewport,
});
}
@@ -209,7 +212,7 @@ class MapboxViz extends React.Component {
height: this.props.sliceHeight,
longitude: this.state.viewport.longitude,
latitude: this.state.viewport.latitude,
- zoom: this.state.viewport.zoom
+ zoom: this.state.viewport.zoom,
});
const topLeft = mercator.unproject([0, 0]);
const bottomRight = mercator.unproject([this.props.sliceWidth, this.props.sliceHeight]);
@@ -229,7 +232,8 @@ class MapboxViz extends React.Component {
width={this.props.sliceWidth}
height={this.props.sliceHeight}
mapboxApiAccessToken={this.props.mapboxApiKey}
- onChangeViewport={this.onChangeViewport}>
+ onChangeViewport={this.onChangeViewport}
+ >
+ }}
+ />
);
}
}
+MapboxViz.propTypes = {
+ aggregatorName: React.PropTypes.string,
+ clusterer: React.PropTypes.object,
+ globalOpacity: React.PropTypes.number,
+ mapStyle: React.PropTypes.string,
+ mapboxApiKey: React.PropTypes.string,
+ pointRadius: React.PropTypes.number,
+ pointRadiusUnit: React.PropTypes.string,
+ renderWhileDragging: React.PropTypes.bool,
+ rgb: React.PropTypes.array,
+ sliceHeight: React.PropTypes.number,
+ sliceWidth: React.PropTypes.number,
+ viewportLatitude: React.PropTypes.number,
+ viewportLongitude: React.PropTypes.number,
+ viewportZoom: React.PropTypes.number,
+};
function mapbox(slice) {
const DEFAULT_POINT_RADIUS = 60;
@@ -258,20 +279,18 @@ function mapbox(slice) {
const div = d3.select(slice.selector);
let clusterer;
- let render = function () {
-
+ const render = function () {
d3.json(slice.jsonEndpoint(), function (error, json) {
-
if (error !== null) {
slice.error(error.responseText);
- return '';
+ return;
}
// Validate mapbox color
const rgb = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/.exec(json.data.color);
if (rgb === null) {
slice.error('Color field must be of form \'rgb(%d, %d, %d)\'');
- return '';
+ return;
}
const aggName = json.data.aggregatorName;
@@ -293,13 +312,12 @@ function mapbox(slice) {
}
a.push(b);
return a;
- } else {
- if (b instanceof Array) {
- b.push(a);
- return b;
- }
- return [a, b];
}
+ if (b instanceof Array) {
+ b.push(a);
+ return b;
+ }
+ return [a, b];
};
}
@@ -307,7 +325,7 @@ function mapbox(slice) {
radius: json.data.clusteringRadius,
maxZoom: DEFAULT_MAX_ZOOM,
metricKey: 'metric',
- metricReducer: reducer
+ metricReducer: reducer,
});
clusterer.load(json.data.geoJSON.features);
@@ -320,7 +338,8 @@ function mapbox(slice) {
sliceWidth={slice.width()}
clusterer={clusterer}
pointRadius={DEFAULT_POINT_RADIUS}
- aggregatorName={aggName}/>,
+ aggregatorName={aggName}
+ />,
div.node()
);
@@ -329,8 +348,8 @@ function mapbox(slice) {
};
return {
- render: render,
- resize: function () {}
+ render,
+ resize() {},
};
}
diff --git a/caravel/assets/visualizations/markup.js b/caravel/assets/visualizations/markup.js
index 9f55146d6e43d..70abd4c38c995 100644
--- a/caravel/assets/visualizations/markup.js
+++ b/caravel/assets/visualizations/markup.js
@@ -1,4 +1,4 @@
-var $ = window.$ || require('jquery');
+const $ = require('jquery');
require('./markup.css');
@@ -6,9 +6,9 @@ function markupWidget(slice) {
function refresh() {
$('#code').attr('rows', '15');
$.getJSON(slice.jsonEndpoint(), function (payload) {
- slice.container.html(payload.data.html);
- slice.done(payload);
- })
+ slice.container.html(payload.data.html);
+ slice.done(payload);
+ })
.fail(function (xhr) {
slice.error(xhr.responseText, xhr);
});
diff --git a/caravel/assets/visualizations/nvd3_vis.js b/caravel/assets/visualizations/nvd3_vis.js
index 4b4a734d80746..d131928b86a91 100644
--- a/caravel/assets/visualizations/nvd3_vis.js
+++ b/caravel/assets/visualizations/nvd3_vis.js
@@ -1,8 +1,8 @@
// JS
-var d3 = window.d3 || require('d3');
-import { category21 } from '../javascripts/modules/colors'
-import { timeFormatFactory, formatDate } from '../javascripts/modules/dates'
-var nv = require('nvd3');
+import { category21 } from '../javascripts/modules/colors';
+import { timeFormatFactory, formatDate } from '../javascripts/modules/dates';
+const d3 = require('d3');
+const nv = require('nvd3');
// CSS
require('../node_modules/nvd3/build/nv.d3.min.css');
@@ -11,23 +11,66 @@ require('./nvd3_vis.css');
const minBarWidth = 15;
const animationTime = 1000;
+const addTotalBarValues = function (chart, data, stacked) {
+ const svg = d3.select('svg');
+ const format = d3.format('.3s');
+ const countSeriesDisplayed = data.length;
+
+ const totalStackedValues = stacked && data.length !== 0 ?
+ data[0].values.map(function (bar, iBar) {
+ const bars = data.map(function (series) {
+ return series.values[iBar];
+ });
+ return d3.sum(bars, function (d) {
+ return d.y;
+ });
+ }) : [];
+
+ const rectsToBeLabeled = svg.selectAll('g.nv-group').filter(
+ function (d, i) {
+ if (!stacked) {
+ return true;
+ }
+ return i === countSeriesDisplayed - 1;
+ }).selectAll('rect.positive');
+
+ const groupLabels = svg.select('g.nv-barsWrap').append('g');
+ rectsToBeLabeled.each(
+ function (d, index) {
+ const rectObj = d3.select(this);
+ const transformAttr = rectObj.attr('transform');
+ const yPos = parseFloat(rectObj.attr('y'));
+ const xPos = parseFloat(rectObj.attr('x'));
+ const rectWidth = parseFloat(rectObj.attr('width'));
+ const t = groupLabels.append('text')
+ .attr('x', xPos) // rough position first, fine tune later
+ .attr('y', yPos - 5)
+ .text(format(stacked ? totalStackedValues[index] : d.y))
+ .attr('transform', transformAttr)
+ .attr('class', 'bar-chart-label');
+ const labelWidth = t.node().getBBox().width;
+ t.attr('x', xPos + rectWidth / 2 - labelWidth / 2); // fine tune
+ });
+};
+
function nvd3Vis(slice) {
- var chart;
- var colorKey = 'key';
+ let chart;
+ let colorKey = 'key';
- var render = function () {
+
+ const render = function () {
d3.json(slice.jsonEndpoint(), function (error, payload) {
slice.container.html('');
// Check error first, otherwise payload can be null
if (error) {
slice.error(error.responseText, error);
- return '';
+ return;
}
- var width = slice.width();
- var fd = payload.form_data;
- var barchartWidth = function () {
- var bars;
+ let width = slice.width();
+ const fd = payload.form_data;
+ const barchartWidth = function () {
+ let bars;
if (fd.bar_stacked) {
bars = d3.max(payload.data, function (d) { return d.values.length; });
} else {
@@ -35,17 +78,16 @@ function nvd3Vis(slice) {
}
if (bars * minBarWidth > width) {
return bars * minBarWidth;
- } else {
- return width;
}
+ return width;
};
- var viz_type = fd.viz_type;
- var f = d3.format('.3s');
- var reduceXTicks = fd.reduce_x_ticks || false;
- var stacked = false;
-
+ const vizType = fd.viz_type;
+ const f = d3.format('.3s');
+ const reduceXTicks = fd.reduce_x_ticks || false;
+ let stacked = false;
+ let row;
nv.addGraph(function () {
- switch (viz_type) {
+ switch (vizType) {
case 'line':
if (fd.show_brush) {
chart = nv.models.lineWithFocusChart();
@@ -93,7 +135,7 @@ function nvd3Vis(slice) {
.showControls(fd.show_controls)
.reduceXTicks(reduceXTicks)
.rotateLabels(45)
- .groupSpacing(0.1); //Distance between each group of bars.
+ .groupSpacing(0.1); // Distance between each group of bars.
chart.xAxis
.showMaxMin(false);
@@ -120,7 +162,7 @@ function nvd3Vis(slice) {
chart.donut(true);
}
chart.labelsOutside(fd.labels_outside);
- chart.labelThreshold(0.05) //Configure the minimum slice size for labels to show up
+ chart.labelThreshold(0.05) // Configure the minimum slice size for labels to show up
.labelType(fd.pie_label_type);
chart.cornerRadius(true);
break;
@@ -140,20 +182,21 @@ function nvd3Vis(slice) {
break;
case 'bubble':
- var row = function (col1, col2) {
- return "" + col1 + " | " + col2 + " |
";
- };
+ row = (col1, col2) => `${col1} | ${col2} |
`;
chart = nv.models.scatterChart();
chart.showDistX(true);
chart.showDistY(true);
chart.tooltip.contentGenerator(function (obj) {
- var p = obj.point;
- var s = "";
- s += '' + p[fd.entity] + ' (' + p.group + ') |
';
+ const p = obj.point;
+ let s = '';
+ s += (
+ `` +
+ `${p[fd.entity]} (${p.group})` +
+ ' |
');
s += row(fd.x, f(p.x));
s += row(fd.y, f(p.y));
s += row(fd.size, f(p.size));
- s += "
";
+ s += '
';
return s;
});
chart.pointRange([5, fd.max_bubble_size * fd.max_bubble_size]);
@@ -180,20 +223,19 @@ function nvd3Vis(slice) {
break;
default:
- throw new Error("Unrecognized visualization for nvd3" + viz_type);
+ throw new Error('Unrecognized visualization for nvd3' + vizType);
}
- if ("showLegend" in chart && typeof fd.show_legend !== 'undefined') {
+ if ('showLegend' in chart && typeof fd.show_legend !== 'undefined') {
chart.showLegend(fd.show_legend);
}
- var height = slice.height();
- height -= 15; // accounting for the staggered xAxis
+ let height = slice.height() - 15;
chart.height(height);
slice.container.css('height', height + 'px');
- if ((viz_type === "line" || viz_type === "area") && fd.rich_tooltip) {
+ if ((vizType === 'line' || vizType === 'area') && fd.rich_tooltip) {
chart.useInteractiveGuideline(true);
}
if (fd.y_axis_zero) {
@@ -204,8 +246,8 @@ function nvd3Vis(slice) {
if (fd.x_log_scale) {
chart.xScale(d3.scale.log());
}
- var xAxisFormatter;
- if (viz_type === 'bubble') {
+ let xAxisFormatter;
+ if (vizType === 'bubble') {
xAxisFormatter = d3.format('.3s');
} else if (fd.x_axis_format === 'smart_date') {
xAxisFormatter = formatDate;
@@ -215,12 +257,12 @@ function nvd3Vis(slice) {
chart.xAxis.tickFormat(xAxisFormatter);
}
- if (chart.hasOwnProperty("x2Axis")) {
+ if (chart.hasOwnProperty('x2Axis')) {
chart.x2Axis.tickFormat(xAxisFormatter);
height += 30;
}
- if (viz_type === 'bubble') {
+ if (vizType === 'bubble') {
chart.xAxis.tickFormat(d3.format('.3s'));
} else if (fd.x_axis_format === 'smart_date') {
chart.xAxis.tickFormat(formatDate);
@@ -233,17 +275,14 @@ function nvd3Vis(slice) {
if (fd.y_axis_format) {
chart.yAxis.tickFormat(d3.format(fd.y_axis_format));
-
if (chart.y2Axis !== undefined) {
chart.y2Axis.tickFormat(d3.format(fd.y_axis_format));
}
}
- chart.color(function (d, i) {
- return category21(d[colorKey]);
- });
+ chart.color((d) => category21(d[colorKey]));
if (fd.x_axis_label && fd.x_axis_label !== '' && chart.xAxis) {
- var distance = 0;
+ let distance = 0;
if (fd.bottom_margin) {
distance = fd.bottom_margin - 50;
}
@@ -258,9 +297,9 @@ function nvd3Vis(slice) {
if (fd.bottom_margin) {
chart.margin({ bottom: fd.bottom_margin });
}
- var svg = d3.select(slice.selector).select("svg");
+ let svg = d3.select(slice.selector).select('svg');
if (svg.empty()) {
- svg = d3.select(slice.selector).append("svg");
+ svg = d3.select(slice.selector).append('svg');
}
svg
@@ -277,57 +316,15 @@ function nvd3Vis(slice) {
});
};
- var update = function () {
+ const update = function () {
if (chart && chart.update) {
chart.update();
}
};
- var addTotalBarValues = function (chart, data, stacked) {
- var svg = d3.select("svg"),
- rectsToBeLabeled,
- format = d3.format('.3s'),
- countSeriesDisplayed = data.length;
-
- var totalStackedValues = stacked && data.length !== 0 ?
- data[0].values.map(function (bar, iBar) {
- var bars = data.map(function (series) {
- return series.values[iBar];
- });
- return d3.sum(bars, function (d) {
- return d.y;
- });
- }) : [];
-
- rectsToBeLabeled = svg.selectAll("g.nv-group").filter(
- function (d, i) {
- if (!stacked) {
- return true;
- }
- return i === countSeriesDisplayed - 1;
- }).selectAll("rect.positive");
-
- var groupLabels = svg.select("g.nv-barsWrap").append("g");
- rectsToBeLabeled.each(
- function (d, index) {
- var rectObj = d3.select(this);
- var transformAttr = rectObj.attr("transform");
- var yPos = parseFloat(rectObj.attr("y"));
- var xPos = parseFloat(rectObj.attr("x"));
- var rectWidth = parseFloat(rectObj.attr("width"));
- var t = groupLabels.append("text")
- .attr("x", xPos) // rough position first, fine tune later
- .attr("y", yPos - 5)
- .text(format(stacked ? totalStackedValues[index] : d.y))
- .attr("transform", transformAttr)
- .attr("class", "bar-chart-label");
- var labelWidth = t.node().getBBox().width;
- t.attr("x", xPos + rectWidth / 2 - labelWidth / 2); // fine tune
- });
- };
return {
- render: render,
+ render,
resize: update,
};
}
diff --git a/caravel/assets/visualizations/parallel_coordinates.js b/caravel/assets/visualizations/parallel_coordinates.js
index b24e95de405f3..fdc2f1e62170e 100644
--- a/caravel/assets/visualizations/parallel_coordinates.js
+++ b/caravel/assets/visualizations/parallel_coordinates.js
@@ -1,59 +1,56 @@
-// JS
-var $ = window.$ || require('jquery');
-var d3 = window.d3 || require('d3');
+const $ = require('jquery');
+import d3 from 'd3';
d3.parcoords = require('../vendor/parallel_coordinates/d3.parcoords.js');
d3.divgrid = require('../vendor/parallel_coordinates/divgrid.js');
-// CSS
require('../vendor/parallel_coordinates/d3.parcoords.css');
require('./parallel_coordinates.css');
function parallelCoordVis(slice) {
-
function refresh() {
$('#code').attr('rows', '15');
$.getJSON(slice.jsonEndpoint(), function (payload) {
- var fd = payload.form_data;
- var data = payload.data;
+ const fd = payload.form_data;
+ const data = payload.data;
- var cols = fd.metrics;
- if (fd.include_series) {
- cols = [fd.series].concat(fd.metrics);
- }
+ let cols = fd.metrics;
+ if (fd.include_series) {
+ cols = [fd.series].concat(fd.metrics);
+ }
- var ttypes = {};
- ttypes[fd.series] = 'string';
- fd.metrics.forEach(function (v) {
- ttypes[v] = 'number';
- });
+ const ttypes = {};
+ ttypes[fd.series] = 'string';
+ fd.metrics.forEach(function (v) {
+ ttypes[v] = 'number';
+ });
- var ext = d3.extent(data, function (d) {
- return d[fd.secondary_metric];
- });
- ext = [ext[0], (ext[1] - ext[0]) / 2, ext[1]];
- var cScale = d3.scale.linear()
- .domain(ext)
- .range(['red', 'grey', 'blue'])
- .interpolate(d3.interpolateLab);
+ let ext = d3.extent(data, function (d) {
+ return d[fd.secondary_metric];
+ });
+ ext = [ext[0], (ext[1] - ext[0]) / 2, ext[1]];
+ const cScale = d3.scale.linear()
+ .domain(ext)
+ .range(['red', 'grey', 'blue'])
+ .interpolate(d3.interpolateLab);
- var color = function (d) {
- return cScale(d[fd.secondary_metric]);
- };
- var container = d3.select(slice.selector);
- container.selectAll('*').remove();
- var eff_height = fd.show_datatable ? (slice.height() / 2) : slice.height();
+ const color = function (d) {
+ return cScale(d[fd.secondary_metric]);
+ };
+ const container = d3.select(slice.selector);
+ container.selectAll('*').remove();
+ const effHeight = fd.show_datatable ? (slice.height() / 2) : slice.height();
- container.append('div')
+ container.append('div')
.attr('id', 'parcoords_' + slice.container_id)
- .style('height', eff_height + 'px')
- .classed("parcoords", true);
+ .style('height', effHeight + 'px')
+ .classed('parcoords', true);
- var parcoords = d3.parcoords()('#parcoords_' + slice.container_id)
+ const parcoords = d3.parcoords()('#parcoords_' + slice.container_id)
.width(slice.width())
.color(color)
.alpha(0.5)
- .composite("darken")
- .height(eff_height)
+ .composite('darken')
+ .height(effHeight)
.data(data)
.dimensions(cols)
.types(ttypes)
@@ -61,39 +58,39 @@ function parallelCoordVis(slice) {
.createAxes()
.shadows()
.reorderable()
- .brushMode("1D-axes");
+ .brushMode('1D-axes');
- if (fd.show_datatable) {
+ if (fd.show_datatable) {
// create data table, row hover highlighting
- var grid = d3.divgrid();
- container.append("div")
- .style('height', eff_height + 'px')
+ const grid = d3.divgrid();
+ container.append('div')
+ .style('height', effHeight + 'px')
.datum(data)
.call(grid)
- .classed("parcoords grid", true)
- .selectAll(".row")
+ .classed('parcoords grid', true)
+ .selectAll('.row')
.on({
- mouseover: function (d) {
+ mouseover(d) {
parcoords.highlight([d]);
},
mouseout: parcoords.unhighlight,
});
// update data table on brush event
- parcoords.on("brush", function (d) {
- d3.select(".grid")
- .datum(d)
- .call(grid)
- .selectAll(".row")
- .on({
- mouseover: function (d) {
- parcoords.highlight([d]);
- },
- mouseout: parcoords.unhighlight,
- });
- });
- }
- slice.done(payload);
- })
+ parcoords.on('brush', function (d) {
+ d3.select('.grid')
+ .datum(d)
+ .call(grid)
+ .selectAll('.row')
+ .on({
+ mouseover(dd) {
+ parcoords.highlight([dd]);
+ },
+ mouseout: parcoords.unhighlight,
+ });
+ });
+ }
+ slice.done(payload);
+ })
.fail(function (xhr) {
slice.error(xhr.responseText, xhr);
});
diff --git a/caravel/assets/visualizations/pivot_table.js b/caravel/assets/visualizations/pivot_table.js
index 643f1e59dcf8b..09cddc9bf0488 100644
--- a/caravel/assets/visualizations/pivot_table.js
+++ b/caravel/assets/visualizations/pivot_table.js
@@ -1,30 +1,29 @@
-var $ = window.$ = require('jquery');
-var jQuery = window.jQuery = $;
+import { fixDataTableBodyHeight } from '../javascripts/modules/utils';
+const $ = require('jquery');
require('datatables.net-bs');
require('./pivot_table.css');
-require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
-var utils = require('../javascripts/modules/utils');
+require('datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
module.exports = function (slice) {
- var container = slice.container;
+ const container = slice.container;
function refresh() {
$.getJSON(slice.jsonEndpoint(), function (json) {
- var form_data = json.form_data;
+ const fd = json.form_data;
container.html(json.data);
- if (form_data.groupby.length === 1) {
- var height = container.height();
- var table = container.find('table').DataTable({
+ if (fd.groupby.length === 1) {
+ const height = container.height();
+ const table = container.find('table').DataTable({
paging: false,
searching: false,
bInfo: false,
- scrollY: height + "px",
+ scrollY: height + 'px',
scrollCollapse: true,
scrollX: true,
});
table.column('-1').order('desc').draw();
- utils.fixDataTableBodyHeight(
+ fixDataTableBodyHeight(
container.find('.dataTables_wrapper'), height);
}
slice.done(json);
diff --git a/caravel/assets/visualizations/sankey.js b/caravel/assets/visualizations/sankey.js
index a4d61a9218d24..84768d97a9e43 100644
--- a/caravel/assets/visualizations/sankey.js
+++ b/caravel/assets/visualizations/sankey.js
@@ -1,55 +1,56 @@
-// CSS
-require('./sankey.css');
-// JS
-import { category21 } from '../javascripts/modules/colors'
-var d3 = window.d3 || require('d3');
+/* eslint-disable no-param-reassign */
+import { category21 } from '../javascripts/modules/colors';
+import d3 from 'd3';
+
d3.sankey = require('d3-sankey').sankey;
-function sankeyVis(slice) {
- var div = d3.select(slice.selector);
+require('./sankey.css');
- var render = function () {
- var margin = {
+function sankeyVis(slice) {
+ const div = d3.select(slice.selector);
+ const render = function () {
+ const margin = {
top: 5,
right: 5,
bottom: 5,
left: 5,
};
- var width = slice.width() - margin.left - margin.right;
- var height = slice.height() - margin.top - margin.bottom;
+ const width = slice.width() - margin.left - margin.right;
+ const height = slice.height() - margin.top - margin.bottom;
- var formatNumber = d3.format(",.2f");
+ const formatNumber = d3.format(',.2f');
- div.selectAll("*").remove();
- var svg = div.append("svg")
- .attr("width", width + margin.left + margin.right)
- .attr("height", height + margin.top + margin.bottom)
- .append("g")
- .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+ div.selectAll('*').remove();
+ const svg = div.append('svg')
+ .attr('width', width + margin.left + margin.right)
+ .attr('height', height + margin.top + margin.bottom)
+ .append('g')
+ .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
- var tooltip = div.append("div")
- .attr("class", "sankey-tooltip")
- .style("opacity", 0);
+ const tooltip = div.append('div')
+ .attr('class', 'sankey-tooltip')
+ .style('opacity', 0);
- var sankey = d3.sankey()
+ const sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
- var path = sankey.link();
+ const path = sankey.link();
d3.json(slice.jsonEndpoint(), function (error, json) {
if (error !== null) {
slice.error(error.responseText, error);
- return '';
+ return;
}
- var links = json.data;
- var nodes = {};
+ let nodes = {};
// Compute the distinct nodes from the links.
- links.forEach(function (link) {
+ const links = json.data.map(function (row) {
+ const link = Object.assign({}, row);
link.source = nodes[link.source] || (nodes[link.source] = { name: link.source });
link.target = nodes[link.target] || (nodes[link.target] = { name: link.target });
link.value = Number(link.value);
+ return link;
});
nodes = d3.values(nodes);
@@ -58,118 +59,124 @@ function sankeyVis(slice) {
.links(links)
.layout(32);
- var link = svg.append("g").selectAll(".link")
+ function getTooltipHtml(d) {
+ let html;
+
+ if (d.sourceLinks) { // is node
+ html = d.name + " Value: " + formatNumber(d.value) + '';
+ } else {
+ const val = formatNumber(d.value);
+ const sourcePercent = d3.round((d.value / d.source.value) * 100, 1);
+ const targetPercent = d3.round((d.value / d.target.value) * 100, 1);
+
+ html = [
+ "Path Value: ", val, '
',
+ "",
+ "",
+ (isFinite(sourcePercent) ? sourcePercent : '100'),
+ '% of ', d.source.name, '
',
+ "" +
+ (isFinite(targetPercent) ? targetPercent : '--') +
+ '% of ', d.target.name, 'target',
+ '
',
+ ].join('');
+ }
+ return html;
+ }
+
+ function onmouseover(d) {
+ tooltip
+ .html(function () { return getTooltipHtml(d); })
+ .transition()
+ .duration(200)
+ .style('left', (d3.event.layerX + 10) + 'px')
+ .style('top', (d3.event.layerY + 10) + 'px')
+ .style('opacity', 0.95);
+ }
+
+ function onmouseout() {
+ tooltip.transition()
+ .duration(100)
+ .style('opacity', 0);
+ }
+
+ const link = svg.append('g').selectAll('.link')
.data(links)
- .enter().append("path")
- .attr("class", "link")
- .attr("d", path)
- .style("stroke-width", function (d) {
- return Math.max(1, d.dy);
- })
- .sort(function (a, b) {
- return b.dy - a.dy;
- })
- .on("mouseover", onmouseover)
- .on("mouseout", onmouseout);
+ .enter()
+ .append('path')
+ .attr('class', 'link')
+ .attr('d', path)
+ .style('stroke-width', (d) => Math.max(1, d.dy))
+ .sort((a, b) => b.dy - a.dy)
+ .on('mouseover', onmouseover)
+ .on('mouseout', onmouseout);
- var node = svg.append("g").selectAll(".node")
+ function dragmove(d) {
+ d3.select(this)
+ .attr(
+ 'transform',
+ `translate(${d.x},${(d.y = Math.max(0, Math.min(height - d.dy, d3.event.y)))})`
+ );
+ sankey.relayout();
+ link.attr('d', path);
+ }
+
+ const node = svg.append('g').selectAll('.node')
.data(nodes)
- .enter().append("g")
- .attr("class", "node")
- .attr("transform", function (d) {
- return "translate(" + d.x + "," + d.y + ")";
+ .enter()
+ .append('g')
+ .attr('class', 'node')
+ .attr('transform', function (d) {
+ return 'translate(' + d.x + ',' + d.y + ')';
})
.call(d3.behavior.drag()
.origin(function (d) {
return d;
})
- .on("dragstart", function () {
+ .on('dragstart', function () {
this.parentNode.appendChild(this);
})
- .on("drag", dragmove));
+ .on('drag', dragmove)
+ );
- node.append("rect")
- .attr("height", function (d) {
+ node.append('rect')
+ .attr('height', function (d) {
return d.dy;
})
- .attr("width", sankey.nodeWidth())
- .style("fill", function (d) {
- d.color = category21(d.name.replace(/ .*/, ""));
+ .attr('width', sankey.nodeWidth())
+ .style('fill', function (d) {
+ d.color = category21(d.name.replace(/ .*/, ''));
return d.color;
})
- .style("stroke", function (d) {
+ .style('stroke', function (d) {
return d3.rgb(d.color).darker(2);
})
- .on("mouseover", onmouseover)
- .on("mouseout", onmouseout);
+ .on('mouseover', onmouseover)
+ .on('mouseout', onmouseout);
- node.append("text")
- .attr("x", -6)
- .attr("y", function (d) {
+ node.append('text')
+ .attr('x', -6)
+ .attr('y', function (d) {
return d.dy / 2;
})
- .attr("dy", ".35em")
- .attr("text-anchor", "end")
- .attr("transform", null)
+ .attr('dy', '.35em')
+ .attr('text-anchor', 'end')
+ .attr('transform', null)
.text(function (d) {
return d.name;
})
.filter(function (d) {
return d.x < width / 2;
})
- .attr("x", 6 + sankey.nodeWidth())
- .attr("text-anchor", "start");
-
- function dragmove(d) {
- d3.select(this)
- .attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
-
- sankey.relayout();
- link.attr("d", path);
- }
-
- function getTooltipHtml(d) {
- var html;
-
- if (d.sourceLinks) { // is node
- html = d.name + " Value: " + formatNumber(d.value) + "";
- } else {
- var val = formatNumber(d.value);
- var sourcePercent = d3.round((d.value / d.source.value) * 100, 1);
- var targetPercent = d3.round((d.value / d.target.value) * 100, 1);
-
- html = [
- "Path Value: ", val, "
",
- "",
- "", (isFinite(sourcePercent) ? sourcePercent : "100"), "% of ", d.source.name, "
",
- "" + (isFinite(targetPercent) ? targetPercent : "--") + "% of ", d.target.name, "target",
- "
",
- ].join("");
- }
- return html;
- }
+ .attr('x', 6 + sankey.nodeWidth())
+ .attr('text-anchor', 'start');
- function onmouseover(d) {
- tooltip
- .html(function () { return getTooltipHtml(d); })
- .transition()
- .duration(200)
- .style("left", (d3.event.layerX + 10) + "px")
- .style("top", (d3.event.layerY + 10) + "px")
- .style("opacity", 0.95);
- }
-
- function onmouseout(d) {
- tooltip.transition()
- .duration(100)
- .style("opacity", 0);
- }
slice.done(json);
});
};
return {
- render: render,
+ render,
resize: render,
};
}
diff --git a/caravel/assets/visualizations/sunburst.js b/caravel/assets/visualizations/sunburst.js
index 529ffcb315021..391d252bbe921 100644
--- a/caravel/assets/visualizations/sunburst.js
+++ b/caravel/assets/visualizations/sunburst.js
@@ -1,41 +1,42 @@
-var d3 = window.d3 || require('d3');
-import { category21 } from '../javascripts/modules/colors'
-import { wrapSvgText } from '../javascripts/modules/utils'
+/* eslint-disable no-underscore-dangle, no-param-reassign */
+import d3 from 'd3';
+import { category21 } from '../javascripts/modules/colors';
+import { wrapSvgText } from '../javascripts/modules/utils';
require('./sunburst.css');
// Modified from http://bl.ocks.org/kerryrodden/7090426
function sunburstVis(slice) {
- var container = d3.select(slice.selector);
+ const container = d3.select(slice.selector);
- var render = function () {
+ const render = function () {
// vars with shared scope within this function
- var margin = { top: 10, right: 5, bottom: 10, left: 5 };
- var containerWidth = slice.width();
- var containerHeight = slice.height();
- var breadcrumbHeight = containerHeight * 0.085;
- var visWidth = containerWidth - margin.left - margin.right;
- var visHeight = containerHeight - margin.top - margin.bottom - breadcrumbHeight;
- var radius = Math.min(visWidth, visHeight) / 2;
- var colorByCategory = true; // color by category if primary/secondary metrics match
-
- var maxBreadcrumbs, breadcrumbDims, // set based on data
- totalSize, // total size of all segments; set after loading the data.
- colorScale,
- breadcrumbs, vis, arcs, gMiddleText; // dom handles
+ const margin = { top: 10, right: 5, bottom: 10, left: 5 };
+ const containerWidth = slice.width();
+ const containerHeight = slice.height();
+ const breadcrumbHeight = containerHeight * 0.085;
+ const visWidth = containerWidth - margin.left - margin.right;
+ const visHeight = containerHeight - margin.top - margin.bottom - breadcrumbHeight;
+ const radius = Math.min(visWidth, visHeight) / 2;
+
+ let colorByCategory = true; // color by category if primary/secondary metrics match
+ let maxBreadcrumbs;
+ let breadcrumbDims; // set based on data
+ let totalSize; // total size of all segments; set after loading the data.
+ let colorScale;
+ let breadcrumbs;
+ let vis;
+ let arcs;
+ let gMiddleText; // dom handles
// Helper + path gen functions
- var partition = d3.layout.partition()
+ const partition = d3.layout.partition()
.size([2 * Math.PI, radius * radius])
.value(function (d) { return d.m1; });
- var arc = d3.svg.arc()
- .startAngle(function (d) {
- return d.x;
- })
- .endAngle(function (d) {
- return d.x + d.dx;
- })
+ const arc = d3.svg.arc()
+ .startAngle((d) => d.x)
+ .endAngle((d) => d.x + d.dx)
.innerRadius(function (d) {
return Math.sqrt(d.y);
})
@@ -43,290 +44,223 @@ function sunburstVis(slice) {
return Math.sqrt(d.y + d.dy);
});
- var formatNum = d3.format(".3s");
- var formatPerc = d3.format(".3p");
-
- container.select("svg").remove();
+ const formatNum = d3.format('.3s');
+ const formatPerc = d3.format('.3p');
- var svg = container.append("svg:svg")
- .attr("width", containerWidth)
- .attr("height", containerHeight);
+ container.select('svg').remove();
- d3.json(slice.jsonEndpoint(), function (error, rawData) {
- if (error !== null) {
- slice.error(error.responseText, error);
- return '';
- }
- createBreadcrumbs(rawData);
- createVisualization(rawData);
-
- slice.done(rawData);
- });
+ const svg = container.append('svg:svg')
+ .attr('width', containerWidth)
+ .attr('height', containerHeight);
function createBreadcrumbs(rawData) {
- var firstRowData = rawData.data[0];
- maxBreadcrumbs = (firstRowData.length - 2) + 1; // -2 bc row contains 2x metrics, +extra for %label and buffer
-
+ const firstRowData = rawData.data[0];
+ // -2 bc row contains 2x metrics, +extra for %label and buffer
+ maxBreadcrumbs = (firstRowData.length - 2) + 1;
breadcrumbDims = {
width: visWidth / maxBreadcrumbs,
- height: breadcrumbHeight *0.8, // more margin
+ height: breadcrumbHeight * 0.8, // more margin
spacing: 3,
tipTailWidth: 10,
};
- breadcrumbs = svg.append("svg:g")
- .attr("class", "breadcrumbs")
- .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+ breadcrumbs = svg.append('svg:g')
+ .attr('class', 'breadcrumbs')
+ .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
- breadcrumbs.append("svg:text")
- .attr("class", "end-label");
+ breadcrumbs.append('svg:text')
+ .attr('class', 'end-label');
}
- // Main function to draw and set up the visualization, once we have the data.
- function createVisualization(rawData) {
- var tree = buildHierarchy(rawData.data);
-
- vis = svg.append("svg:g")
- .attr("class", "sunburst-vis")
- .attr("transform", "translate(" + (margin.left + (visWidth / 2)) + "," + (margin.top + breadcrumbHeight + (visHeight / 2)) + ")")
- .on("mouseleave", mouseleave);
-
- arcs = vis.append("svg:g")
- .attr("id", "arcs");
-
- gMiddleText = vis.append("svg:g")
- .attr("class", "center-label");
+ // Given a node in a partition layout, return an array of all of its ancestor
+ // nodes, highest first, but excluding the root.
+ function getAncestors(node) {
+ const path = [];
+ let current = node;
+ while (current.parent) {
+ path.unshift(current);
+ current = current.parent;
+ }
+ return path;
+ }
- // Bounding circle underneath the sunburst, to make it easier to detect
- // when the mouse leaves the parent g.
- arcs.append("svg:circle")
- .attr("r", radius)
- .style("opacity", 0);
+ // Generate a string that describes the points of a breadcrumb polygon.
+ function breadcrumbPoints(d, i) {
+ const points = [];
+ points.push('0,0');
+ points.push(breadcrumbDims.width + ',0');
+ points.push(
+ breadcrumbDims.width + breadcrumbDims.tipTailWidth + ',' + (breadcrumbDims.height / 2));
+ points.push(breadcrumbDims.width + ',' + breadcrumbDims.height);
+ points.push('0,' + breadcrumbDims.height);
+ if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
+ points.push(breadcrumbDims.tipTailWidth + ',' + (breadcrumbDims.height / 2));
+ }
+ return points.join(' ');
+ }
- // For efficiency, filter nodes to keep only those large enough to see.
- var nodes = partition.nodes(tree)
- .filter(function (d) {
- return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
+ function updateBreadcrumbs(sequenceArray, percentageString) {
+ const g = breadcrumbs.selectAll('g')
+ .data(sequenceArray, function (d) {
+ return d.name + d.depth;
});
- var ext;
+ // Add breadcrumb and label for entering nodes.
+ const entering = g.enter().append('svg:g');
- if (rawData.form_data.metric !== rawData.form_data.secondary_metric) {
- colorByCategory = false;
+ entering.append('svg:polygon')
+ .attr('points', breadcrumbPoints)
+ .style('fill', function (d) {
+ return colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1);
+ });
- ext = d3.extent(nodes, function (d) {
- return d.m2 / d.m1;
- });
+ entering.append('svg:text')
+ .attr('x', (breadcrumbDims.width + breadcrumbDims.tipTailWidth) / 2)
+ .attr('y', breadcrumbDims.height / 4)
+ .attr('dy', '0.35em')
+ .style('fill', function (d) {
+ // Make text white or black based on the lightness of the background
+ const col = d3.hsl(colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1));
+ return col.l < 0.5 ? 'white' : 'black';
+ })
+ .attr('class', 'step-label')
+ .text(function (d) { return d.name.replace(/_/g, ' '); })
+ .call(wrapSvgText, breadcrumbDims.width, breadcrumbDims.height / 2);
- colorScale = d3.scale.linear()
- .domain([ext[0], ext[0] + ((ext[1] - ext[0]) / 2), ext[1]])
- .range(["#00D1C1", "white", "#FFB400"]);
- }
+ // Set position for entering and updating nodes.
+ g.attr('transform', function (d, i) {
+ return 'translate(' + i * (breadcrumbDims.width + breadcrumbDims.spacing) + ', 0)';
+ });
- var path = arcs.data([tree]).selectAll("path")
- .data(nodes)
- .enter().append("svg:path")
- .attr("display", function (d) {
- return d.depth ? null : "none";
- })
- .attr("d", arc)
- .attr("fill-rule", "evenodd")
- .style("fill", function (d) {
- return colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1);
- })
- .style("opacity", 1)
- .on("mouseenter", mouseenter);
+ // Remove exiting nodes.
+ g.exit().remove();
- // Get total size of the tree = value of root node from partition.
- totalSize = path.node().__data__.value;
+ // Now move and update the percentage at the end.
+ breadcrumbs.select('.end-label')
+ .attr('x', (sequenceArray.length + 0.5) * (breadcrumbDims.width + breadcrumbDims.spacing))
+ .attr('y', breadcrumbDims.height / 2)
+ .attr('dy', '0.35em')
+ .text(percentageString);
+
+ // Make the breadcrumb trail visible, if it's hidden.
+ breadcrumbs.style('visibility', null);
}
// Fade all but the current sequence, and show it in the breadcrumb trail.
function mouseenter(d) {
+ const sequenceArray = getAncestors(d);
+ const parentOfD = sequenceArray[sequenceArray.length - 2] || null;
- var sequenceArray = getAncestors(d);
- var parentOfD = sequenceArray[sequenceArray.length - 2] || null;
+ const absolutePercentage = (d.m1 / totalSize).toPrecision(3);
+ const conditionalPercentage = parentOfD ? (d.m1 / parentOfD.m1).toPrecision(3) : null;
- var absolutePercentage = (d.m1 / totalSize).toPrecision(3);
- var conditionalPercentage = parentOfD ? (d.m1 / parentOfD.m1).toPrecision(3) : null;
+ const absolutePercString = formatPerc(absolutePercentage);
+ const conditionalPercString = parentOfD ? formatPerc(conditionalPercentage) : '';
- var absolutePercString = formatPerc(absolutePercentage);
- var conditionalPercString = parentOfD ? formatPerc(conditionalPercentage) : "";
-
- var yOffsets = ["-25", "7", "35", "60"]; // 3 levels of text if inner-most level, 4 otherwise
- var offsetIndex = 0;
+ // 3 levels of text if inner-most level, 4 otherwise
+ const yOffsets = ['-25', '7', '35', '60'];
+ let offsetIndex = 0;
// If metrics match, assume we are coloring by category
- var metricsMatch = Math.abs(d.m1 - d.m2) < 0.00001;
+ const metricsMatch = Math.abs(d.m1 - d.m2) < 0.00001;
- gMiddleText.selectAll("*").remove();
+ gMiddleText.selectAll('*').remove();
- gMiddleText.append("text")
- .attr("class", "path-abs-percent")
- .attr("y", yOffsets[offsetIndex++])
- .text(absolutePercString + " of total");
+ gMiddleText.append('text')
+ .attr('class', 'path-abs-percent')
+ .attr('y', yOffsets[offsetIndex++])
+ .text(absolutePercString + ' of total');
if (conditionalPercString) {
- gMiddleText.append("text")
- .attr("class", "path-cond-percent")
- .attr("y", yOffsets[offsetIndex++])
- .text(conditionalPercString + " of parent");
+ gMiddleText.append('text')
+ .attr('class', 'path-cond-percent')
+ .attr('y', yOffsets[offsetIndex++])
+ .text(conditionalPercString + ' of parent');
}
- gMiddleText.append("text")
- .attr("class", "path-metrics")
- .attr("y", yOffsets[offsetIndex++])
- .text("m1: " + formatNum(d.m1) + (metricsMatch ? "" : ", m2: " + formatNum(d.m2)));
+ gMiddleText.append('text')
+ .attr('class', 'path-metrics')
+ .attr('y', yOffsets[offsetIndex++])
+ .text('m1: ' + formatNum(d.m1) + (metricsMatch ? '' : ', m2: ' + formatNum(d.m2)));
- gMiddleText.append("text")
- .attr("class", "path-ratio")
- .attr("y", yOffsets[offsetIndex++])
- .text((metricsMatch ? "" : ("m2/m1: " + formatPerc(d.m2 / d.m1))) );
+ gMiddleText.append('text')
+ .attr('class', 'path-ratio')
+ .attr('y', yOffsets[offsetIndex++])
+ .text((metricsMatch ? '' : ('m2/m1: ' + formatPerc(d.m2 / d.m1))));
// Reset and fade all the segments.
- arcs.selectAll("path")
- .style("stroke-width", null)
- .style("stroke", null)
- .style("opacity", 0.7);
+ arcs.selectAll('path')
+ .style('stroke-width', null)
+ .style('stroke', null)
+ .style('opacity', 0.7);
// Then highlight only those that are an ancestor of the current segment.
- arcs.selectAll("path")
+ arcs.selectAll('path')
.filter(function (node) {
return (sequenceArray.indexOf(node) >= 0);
})
- .style("opacity", 1)
- .style("stroke-width", "2px")
- .style("stroke", "#000");
+ .style('opacity', 1)
+ .style('stroke-width', '2px')
+ .style('stroke', '#000');
updateBreadcrumbs(sequenceArray, absolutePercString);
}
// Restore everything to full opacity when moving off the visualization.
- function mouseleave(d) {
-
+ function mouseleave() {
// Hide the breadcrumb trail
- breadcrumbs.style("visibility", "hidden");
+ breadcrumbs.style('visibility', 'hidden');
- gMiddleText.selectAll("*").remove();
+ gMiddleText.selectAll('*').remove();
// Deactivate all segments during transition.
- arcs.selectAll("path").on("mouseenter", null);
- //gMiddleText.selectAll("*").remove();
+ arcs.selectAll('path').on('mouseenter', null);
// Transition each segment to full opacity and then reactivate it.
- arcs.selectAll("path")
+ arcs.selectAll('path')
.transition()
.duration(200)
- .style("opacity", 1)
- .style("stroke", null)
- .style("stroke-width", null)
- .each("end", function () {
- d3.select(this).on("mouseenter", mouseenter);
+ .style('opacity', 1)
+ .style('stroke', null)
+ .style('stroke-width', null)
+ .each('end', function () {
+ d3.select(this).on('mouseenter', mouseenter);
});
}
- // Given a node in a partition layout, return an array of all of its ancestor
- // nodes, highest first, but excluding the root.
- function getAncestors(node) {
- var path = [];
- var current = node;
- while (current.parent) {
- path.unshift(current);
- current = current.parent;
- }
- return path;
- }
-
- // Generate a string that describes the points of a breadcrumb polygon.
- function breadcrumbPoints(d, i) {
- var points = [];
- points.push("0,0");
- points.push(breadcrumbDims.width + ",0");
- points.push(breadcrumbDims.width + breadcrumbDims.tipTailWidth + "," + (breadcrumbDims.height / 2));
- points.push(breadcrumbDims.width+ "," + breadcrumbDims.height);
- points.push("0," + breadcrumbDims.height);
- if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
- points.push(breadcrumbDims.tipTailWidth + "," + (breadcrumbDims.height / 2));
- }
- return points.join(" ");
- }
-
- function updateBreadcrumbs(sequenceArray, percentageString) {
- var g = breadcrumbs.selectAll("g")
- .data(sequenceArray, function (d) {
- return d.name + d.depth;
- });
-
- // Add breadcrumb and label for entering nodes.
- var entering = g.enter().append("svg:g");
-
- entering.append("svg:polygon")
- .attr("points", breadcrumbPoints)
- .style("fill", function (d) {
- return colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1);
- });
-
- entering.append("svg:text")
- .attr("x", (breadcrumbDims.width + breadcrumbDims.tipTailWidth) / 2)
- .attr("y", breadcrumbDims.height / 4)
- .attr("dy", "0.35em")
- .style("fill", function (d) {
- // Make text white or black based on the lightness of the background
- var col = d3.hsl(colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1));
- return col.l < 0.5 ? 'white' : 'black';
- })
- .attr("class", "step-label")
- .text(function (d) { return d.name.replace(/_/g, " "); })
- .call(wrapSvgText, breadcrumbDims.width, breadcrumbDims.height / 2);
-
- // Set position for entering and updating nodes.
- g.attr("transform", function (d, i) {
- return "translate(" + i * (breadcrumbDims.width + breadcrumbDims.spacing) + ", 0)";
- });
-
- // Remove exiting nodes.
- g.exit().remove();
-
- // Now move and update the percentage at the end.
- breadcrumbs.select(".end-label")
- .attr("x", (sequenceArray.length + 0.5) * (breadcrumbDims.width + breadcrumbDims.spacing))
- .attr("y", breadcrumbDims.height / 2)
- .attr("dy", "0.35em")
- .text(percentageString);
-
- // Make the breadcrumb trail visible, if it's hidden.
- breadcrumbs.style("visibility", null);
- }
function buildHierarchy(rows) {
- var root = {
- name: "root",
+ const root = {
+ name: 'root',
children: [],
};
- for (var i = 0; i < rows.length; i++) { // each record [groupby1val, groupby2val, ( or 0)n, m1, m2]
- var row = rows[i];
- var m1 = Number(row[row.length - 2]);
- var m2 = Number(row[row.length - 1]);
- var levels = row.slice(0, row.length - 2);
+ // each record [groupby1val, groupby2val, ( or 0)n, m1, m2]
+ for (let i = 0; i < rows.length; i++) {
+ const row = rows[i];
+ const m1 = Number(row[row.length - 2]);
+ const m2 = Number(row[row.length - 1]);
+ const levels = row.slice(0, row.length - 2);
if (isNaN(m1)) { // e.g. if this is a header row
continue;
}
- var currentNode = root;
- for (var level = 0; level < levels.length; level++) {
- var children = currentNode.children || [];
- var nodeName = levels[level];
- // If the next node has the name "0", it will
- var isLeafNode = (level >= levels.length - 1) || levels[level+1] === 0;
- var childNode, currChild;
+ let currentNode = root;
+ for (let level = 0; level < levels.length; level++) {
+ const children = currentNode.children || [];
+ const nodeName = levels[level];
+ // If the next node has the name '0', it will
+ const isLeafNode = (level >= levels.length - 1) || levels[level + 1] === 0;
+ let childNode;
+ let currChild;
if (!isLeafNode) {
// Not yet at the end of the sequence; move down the tree.
- var foundChild = false;
- for (var k = 0; k < children.length; k++) {
+ let foundChild = false;
+ for (let k = 0; k < children.length; k++) {
currChild = children[k];
if (currChild.name === nodeName &&
- currChild.level === level) { // must match name AND level
+ currChild.level === level) {
+ // must match name AND level
childNode = currChild;
foundChild = true;
@@ -338,18 +272,17 @@ function sunburstVis(slice) {
childNode = {
name: nodeName,
children: [],
- level: level,
+ level,
};
children.push(childNode);
}
currentNode = childNode;
-
} else if (nodeName !== 0) {
// Reached the end of the sequence; create a leaf node.
childNode = {
name: nodeName,
- m1: m1,
- m2: m2,
+ m1,
+ m2,
};
children.push(childNode);
}
@@ -358,10 +291,10 @@ function sunburstVis(slice) {
function recurse(node) {
if (node.children) {
- var sums;
- var m1 = 0;
- var m2 = 0;
- for (var i = 0; i < node.children.length; i++) {
+ let sums;
+ let m1 = 0;
+ let m2 = 0;
+ for (let i = 0; i < node.children.length; i++) {
sums = recurse(node.children[i]);
m1 += sums[0];
m2 += sums[1];
@@ -375,10 +308,80 @@ function sunburstVis(slice) {
recurse(root);
return root;
}
+
+ // Main function to draw and set up the visualization, once we have the data.
+ function createVisualization(rawData) {
+ const tree = buildHierarchy(rawData.data);
+
+ vis = svg.append('svg:g')
+ .attr('class', 'sunburst-vis')
+ .attr('transform', (
+ 'translate(' +
+ `${(margin.left + (visWidth / 2))},` +
+ `${(margin.top + breadcrumbHeight + (visHeight / 2))}` +
+ ')'
+ ))
+ .on('mouseleave', mouseleave);
+
+ arcs = vis.append('svg:g')
+ .attr('id', 'arcs');
+
+ gMiddleText = vis.append('svg:g')
+ .attr('class', 'center-label');
+
+ // Bounding circle underneath the sunburst, to make it easier to detect
+ // when the mouse leaves the parent g.
+ arcs.append('svg:circle')
+ .attr('r', radius)
+ .style('opacity', 0);
+
+ // For efficiency, filter nodes to keep only those large enough to see.
+ const nodes = partition.nodes(tree)
+ .filter(function (d) {
+ return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
+ });
+
+ let ext;
+
+ if (rawData.form_data.metric !== rawData.form_data.secondary_metric) {
+ colorByCategory = false;
+ ext = d3.extent(nodes, (d) => d.m2 / d.m1);
+ colorScale = d3.scale.linear()
+ .domain([ext[0], ext[0] + ((ext[1] - ext[0]) / 2), ext[1]])
+ .range(['#00D1C1', 'white', '#FFB400']);
+ }
+
+ const path = arcs.data([tree]).selectAll('path')
+ .data(nodes)
+ .enter()
+ .append('svg:path')
+ .attr('display', function (d) {
+ return d.depth ? null : 'none';
+ })
+ .attr('d', arc)
+ .attr('fill-rule', 'evenodd')
+ .style('fill', (d) => colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1))
+ .style('opacity', 1)
+ .on('mouseenter', mouseenter);
+
+ // Get total size of the tree = value of root node from partition.
+ totalSize = path.node().__data__.value;
+ }
+
+
+ d3.json(slice.jsonEndpoint(), function (error, rawData) {
+ if (error !== null) {
+ slice.error(error.responseText, error);
+ return;
+ }
+ createBreadcrumbs(rawData);
+ createVisualization(rawData);
+ slice.done(rawData);
+ });
};
return {
- render: render,
+ render,
resize: render,
};
}
diff --git a/caravel/assets/visualizations/table.js b/caravel/assets/visualizations/table.js
index e95c6f90c6cd1..9e91ee7b4b5d1 100644
--- a/caravel/assets/visualizations/table.js
+++ b/caravel/assets/visualizations/table.js
@@ -1,64 +1,63 @@
-var $ = window.$ = require('jquery');
-var d3 = require('d3');
-import { fixDataTableBodyHeight } from '../javascripts/modules/utils'
-import { timeFormatFactory, formatDate } from '../javascripts/modules/dates'
+const $ = require('jquery');
+import d3 from 'd3';
+import { fixDataTableBodyHeight } from '../javascripts/modules/utils';
+import { timeFormatFactory, formatDate } from '../javascripts/modules/dates';
require('./table.css');
require('datatables.net-bs');
require('datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
function tableVis(slice) {
- var fC = d3.format('0,000');
- var timestampFormatter;
+ const fC = d3.format('0,000');
+ let timestampFormatter;
function refresh() {
- $.getJSON(slice.jsonEndpoint(), onSuccess).fail(onError);
-
function onError(xhr) {
slice.error(xhr.responseText, xhr);
+ return;
}
-
function onSuccess(json) {
- var data = json.data;
- var form_data = json.form_data;
- var metrics = json.form_data.metrics;
-
+ const data = json.data;
+ const fd = json.form_data;
// Removing metrics (aggregates) that are strings
- var real_metrics = [];
- for (var k in data.records[0]) {
- if (metrics.indexOf(k) > -1 && !isNaN(data.records[0][k])) {
- real_metrics.push(k);
+ const realMetrics = [];
+ for (const k in data.records[0]) {
+ if (fd.metrics.indexOf(k) > -1 && !isNaN(data.records[0][k])) {
+ realMetrics.push(k);
}
}
- metrics = real_metrics;
+ const metrics = realMetrics;
function col(c) {
- var arr = [];
- for (var i = 0; i < data.records.length; i++) {
+ const arr = [];
+ for (let i = 0; i < data.records.length; i++) {
arr.push(data.records[i][c]);
}
return arr;
}
- var maxes = {};
- for (var i = 0; i < metrics.length; i++) {
+ const maxes = {};
+ for (let i = 0; i < metrics.length; i++) {
maxes[metrics[i]] = d3.max(col(metrics[i]));
}
- if (json.form_data.table_timestamp_format === 'smart_date') {
+ if (fd.table_timestamp_format === 'smart_date') {
timestampFormatter = formatDate;
- } else if (json.form_data.table_timestamp_format !== undefined) {
- timestampFormatter = timeFormatFactory(json.form_data.table_timestamp_format);
+ } else if (fd.table_timestamp_format !== undefined) {
+ timestampFormatter = timeFormatFactory(fd.table_timestamp_format);
}
- var div = d3.select(slice.selector);
+ const div = d3.select(slice.selector);
div.html('');
- var table = div.append('table')
- .classed('dataframe dataframe table table-striped table-bordered table-condensed table-hover dataTable no-footer', true)
+ const table = div.append('table')
+ .classed(
+ 'dataframe dataframe table table-striped table-bordered ' +
+ 'table-condensed table-hover dataTable no-footer', true)
.attr('width', '100%');
table.append('thead').append('tr')
.selectAll('th')
- .data(data.columns).enter()
+ .data(data.columns)
+ .enter()
.append('th')
.text(function (d) {
return d;
@@ -66,42 +65,45 @@ function tableVis(slice) {
table.append('tbody')
.selectAll('tr')
- .data(data.records).enter()
+ .data(data.records)
+ .enter()
.append('tr')
.selectAll('td')
- .data(function (row, i) {
- return data.columns.map(function (c) {
- var val = row[c];
- if (c === 'timestamp') {
- val = timestampFormatter(val);
- }
- return {
- col: c,
- val: val,
- isMetric: metrics.indexOf(c) >= 0,
- };
- });
- }).enter()
+ .data((row) => data.columns.map((c) => {
+ let val = row[c];
+ if (c === 'timestamp') {
+ val = timestampFormatter(val);
+ }
+ return {
+ col: c,
+ val,
+ isMetric: metrics.indexOf(c) >= 0,
+ };
+ }))
+ .enter()
.append('td')
.style('background-image', function (d) {
if (d.isMetric) {
- var perc = Math.round((d.val / maxes[d.col]) * 100);
- return "linear-gradient(to right, lightgrey, lightgrey " + perc + "%, rgba(0,0,0,0) " + perc + "%";
+ const perc = Math.round((d.val / maxes[d.col]) * 100);
+ return (
+ `linear-gradient(to right, lightgrey, lightgrey ${perc}%, ` +
+ `rgba(0,0,0,0) ${perc}%`
+ );
}
+ return null;
})
- .attr('title', function (d) {
+ .attr('title', (d) => {
if (!isNaN(d.val)) {
return fC(d.val);
}
+ return null;
})
.attr('data-sort', function (d) {
- if (d.isMetric) {
- return d.val;
- }
+ return (d.isMetric) ? d.val : null;
})
- .on("click", function (d) {
+ .on('click', function (d) {
if (!d.isMetric) {
- var td = d3.select(this);
+ const td = d3.select(this);
if (td.classed('filtered')) {
slice.removeFilter(d.col, [d.val]);
d3.select(this).classed('filtered', false);
@@ -111,43 +113,41 @@ function tableVis(slice) {
}
}
})
- .style("cursor", function (d) {
- if (!d.isMetric) {
- return 'pointer';
- }
+ .style('cursor', function (d) {
+ return (!d.isMetric) ? 'pointer' : '';
})
- .html(function (d) {
+ .html((d) => {
if (d.isMetric) {
return slice.d3format(d.col, d.val);
- } else {
- return d.val;
}
+ return d.val;
});
- var height = slice.container.height();
- var datatable = slice.container.find('.dataTable').DataTable({
+ const height = slice.container.height();
+ const datatable = slice.container.find('.dataTable').DataTable({
paging: false,
aaSorting: [],
- searching: form_data.include_search,
+ searching: fd.include_search,
bInfo: false,
- scrollY: height + "px",
+ scrollY: height + 'px',
scrollCollapse: true,
scrollX: true,
});
fixDataTableBodyHeight(
slice.container.find('.dataTables_wrapper'), height);
// Sorting table by main column
- if (form_data.metrics.length > 0) {
- var main_metric = form_data.metrics[0];
- datatable.column(data.columns.indexOf(main_metric)).order('desc').draw();
+ if (fd.metrics.length > 0) {
+ const mainMetric = fd.metrics[0];
+ datatable.column(data.columns.indexOf(mainMetric)).order('desc').draw();
}
slice.done(json);
slice.container.parents('.widget').find('.tooltip').remove();
}
+ $.getJSON(slice.jsonEndpoint(), onSuccess).fail(onError);
}
return {
render: refresh,
- resize: function () {},
+ resize() {},
};
}
diff --git a/caravel/assets/visualizations/treemap.js b/caravel/assets/visualizations/treemap.js
index c4420a8c930bf..5d0ef1381a3f7 100644
--- a/caravel/assets/visualizations/treemap.js
+++ b/caravel/assets/visualizations/treemap.js
@@ -1,66 +1,63 @@
-// JS
-var d3 = window.d3 || require('d3');
-import { category21 } from '../javascripts/modules/colors'
+/* eslint-disable no-shadow, no-param-reassign, no-underscore-dangle, no-use-before-define*/
+import d3 from 'd3';
+import { category21 } from '../javascripts/modules/colors';
-// CSS
require('./treemap.css');
/* Modified from http://bl.ocks.org/ganeshv/6a8e9ada3ab7f2d88022 */
function treemap(slice) {
-
- var div = d3.select(slice.selector);
-
- var _draw = function (data, eltWidth, eltHeight, formData) {
-
- var margin = { top: 0, right: 0, bottom: 0, left: 0 },
- navBarHeight = 36,
- navBarTitleSize = navBarHeight / 3,
- navBarBuffer = 10,
- width = eltWidth - margin.left - margin.right,
- height = eltHeight - navBarHeight - navBarBuffer -
- margin.top - margin.bottom,
- transitioning,
- formatNumber = d3.format(formData.number_format);
-
- var x = d3.scale.linear()
+ const div = d3.select(slice.selector);
+
+ const _draw = function (data, eltWidth, eltHeight, formData) {
+ const margin = { top: 0, right: 0, bottom: 0, left: 0 };
+ const navBarHeight = 36;
+ const navBarTitleSize = navBarHeight / 3;
+ const navBarBuffer = 10;
+ const width = eltWidth - margin.left - margin.right;
+ const height = (eltHeight - navBarHeight - navBarBuffer -
+ margin.top - margin.bottom);
+ const formatNumber = d3.format(formData.number_format);
+ let transitioning;
+
+ const x = d3.scale.linear()
.domain([0, width])
.range([0, width]);
- var y = d3.scale.linear()
+ const y = d3.scale.linear()
.domain([0, height])
.range([0, height]);
- var treemap = d3.layout.treemap()
+ const treemap = d3.layout.treemap()
.children(function (d, depth) { return depth ? null : d._children; })
.sort(function (a, b) { return a.value - b.value; })
.ratio(formData.treemap_ratio)
- .mode("squarify")
+ .mode('squarify')
.round(false);
- var svg = div.append("svg")
- .attr("width", eltWidth)
- .attr("height", eltHeight);
+ const svg = div.append('svg')
+ .attr('width', eltWidth)
+ .attr('height', eltHeight);
- var chartContainer = svg.append("g")
- .attr("transform", "translate(" + margin.left + "," +
- (margin.top + navBarHeight + navBarBuffer) + ")")
- .style("shape-rendering", "crispEdges");
+ const chartContainer = svg.append('g')
+ .attr('transform', 'translate(' + margin.left + ',' +
+ (margin.top + navBarHeight + navBarBuffer) + ')')
+ .style('shape-rendering', 'crispEdges');
- var grandparent = svg.append("g")
- .attr("class", "grandparent")
- .attr("transform", "translate(0," + (margin.top + navBarBuffer / 2) + ")");
+ const grandparent = svg.append('g')
+ .attr('class', 'grandparent')
+ .attr('transform', 'translate(0,' + (margin.top + navBarBuffer / 2) + ')');
- grandparent.append("rect")
- .attr("width", width)
- .attr("height", navBarHeight);
+ grandparent.append('rect')
+ .attr('width', width)
+ .attr('height', navBarHeight);
- grandparent.append("text")
- .attr("x", width / 2)
- .attr("y", navBarHeight / 2 + navBarTitleSize / 2)
- .style("font-size", navBarTitleSize + "px")
- .style("text-anchor", "middle");
+ grandparent.append('text')
+ .attr('x', width / 2)
+ .attr('y', navBarHeight / 2 + navBarTitleSize / 2)
+ .style('font-size', navBarTitleSize + 'px')
+ .style('text-anchor', 'middle');
- var initialize = function (root) {
+ const initialize = function (root) {
root.x = root.y = 0;
root.dx = width;
root.dy = height;
@@ -71,7 +68,7 @@ function treemap(slice) {
// treemap layout, but not here because of our custom implementation.
// We also take a snapshot of the original children (_children) to avoid
// the children being overwritten when when layout is computed.
- var accumulate = function (d) {
+ const accumulate = function (d) {
d._children = d.children;
if (d._children) {
d.value = d.children.reduce(function (p, v) { return p + accumulate(v); }, 0);
@@ -86,7 +83,7 @@ function treemap(slice) {
// the parents dimensions are not discarded as we recurse. Since each group
// of sibling was laid out in 1x1, we must rescale to fit using absolute
// coordinates. This lets us use a viewport to zoom.
- var layout = function (d) {
+ const layout = function (d) {
if (d._children) {
treemap.nodes({ _children: d._children });
d._children.forEach(function (c) {
@@ -100,156 +97,156 @@ function treemap(slice) {
}
};
- var display = function (d) {
-
- var transition = function (d) {
+ const display = function (d) {
+ const transition = function (d) {
if (transitioning || !d) { return; }
transitioning = true;
- var g2 = display(d),
- t1 = g1.transition().duration(750),
- t2 = g2.transition().duration(750);
+ const g2 = display(d);
+ const t1 = g1.transition().duration(750);
+ const t2 = g2.transition().duration(750);
// Update the domain only after entering new elements.
x.domain([d.x, d.x + d.dx]);
y.domain([d.y, d.y + d.dy]);
// Enable anti-aliasing during the transition.
- chartContainer.style("shape-rendering", null);
+ chartContainer.style('shape-rendering', null);
// Draw child nodes on top of parent nodes.
- chartContainer.selectAll(".depth").sort(function (a, b) { return a.depth - b.depth; });
+ chartContainer.selectAll('.depth').sort(function (a, b) { return a.depth - b.depth; });
// Fade-in entering text.
- g2.selectAll("text").style("fill-opacity", 0);
+ g2.selectAll('text').style('fill-opacity', 0);
// Transition to the new view.
- t1.selectAll(".ptext").call(text).style("fill-opacity", 0);
- t1.selectAll(".ctext").call(text2).style("fill-opacity", 0);
- t2.selectAll(".ptext").call(text).style("fill-opacity", 1);
- t2.selectAll(".ctext").call(text2).style("fill-opacity", 1);
- t1.selectAll("rect").call(rect);
- t2.selectAll("rect").call(rect);
+ t1.selectAll('.ptext').call(text).style('fill-opacity', 0);
+ t1.selectAll('.ctext').call(text2).style('fill-opacity', 0);
+ t2.selectAll('.ptext').call(text).style('fill-opacity', 1);
+ t2.selectAll('.ctext').call(text2).style('fill-opacity', 1);
+ t1.selectAll('rect').call(rect);
+ t2.selectAll('rect').call(rect);
// Remove the old node when the transition is finished.
- t1.remove().each("end", function () {
- chartContainer.style("shape-rendering", "crispEdges");
+ t1.remove().each('end', function () {
+ chartContainer.style('shape-rendering', 'crispEdges');
transitioning = false;
});
};
grandparent
.datum(d.parent)
- .on("click", transition)
- .select("text")
+ .on('click', transition)
+ .select('text')
.text(name(d));
- var g1 = chartContainer.append("g")
+ const g1 = chartContainer.append('g')
.datum(d)
- .attr("class", "depth");
+ .attr('class', 'depth');
- var g = g1.selectAll("g")
+ const g = g1.selectAll('g')
.data(d._children)
- .enter().append("g");
+ .enter()
+ .append('g');
g.filter(function (d) { return d._children; })
- .classed("children", true)
- .on("click", transition);
+ .classed('children', true)
+ .on('click', transition);
- var children = g.selectAll(".child")
+ const children = g.selectAll('.child')
.data(function (d) { return d._children || [d]; })
- .enter().append("g");
+ .enter()
+ .append('g');
- children.append("rect")
- .attr("class", "child")
+ children.append('rect')
+ .attr('class', 'child')
.call(rect)
- .append("title")
- .text(function (d) { return d.name + " (" + formatNumber(d.value) + ")"; });
+ .append('title')
+ .text(function (d) { return d.name + ' (' + formatNumber(d.value) + ')'; });
- children.append("text")
- .attr("class", "ctext")
+ children.append('text')
+ .attr('class', 'ctext')
.text(function (d) { return d.name; })
.call(text2);
- g.append("rect")
- .attr("class", "parent")
+ g.append('rect')
+ .attr('class', 'parent')
.call(rect);
- var t = g.append("text")
- .attr("class", "ptext")
- .attr("dy", ".75em");
+ const t = g.append('text')
+ .attr('class', 'ptext')
+ .attr('dy', '.75em');
- t.append("tspan")
+ t.append('tspan')
.text(function (d) { return d.name; });
- t.append("tspan")
- .attr("dy", "1.0em")
+ t.append('tspan')
+ .attr('dy', '1.0em')
.text(function (d) { return formatNumber(d.value); });
t.call(text);
- g.selectAll("rect")
- .style("fill", function (d) { return category21(d.name); });
+ g.selectAll('rect')
+ .style('fill', function (d) { return category21(d.name); });
return g;
};
- var text = function (selection) {
- selection.selectAll("tspan")
- .attr("x", function (d) { return x(d.x) + 6; });
- selection.attr("x", function (d) { return x(d.x) + 6; })
- .attr("y", function (d) { return y(d.y) + 6; })
- .style("opacity", function (d) { return this.getComputedTextLength() < x(d.x + d.dx) - x(d.x) ? 1 : 0; });
+ const text = function (selection) {
+ selection.selectAll('tspan')
+ .attr('x', function (d) { return x(d.x) + 6; });
+ selection.attr('x', function (d) { return x(d.x) + 6; })
+ .attr('y', function (d) { return y(d.y) + 6; })
+ .style('opacity', function (d) {
+ return this.getComputedTextLength() < x(d.x + d.dx) - x(d.x) ? 1 : 0;
+ });
};
- var text2 = function (selection) {
- selection.attr("x", function (d) { return x(d.x + d.dx) - this.getComputedTextLength() - 6; })
- .attr("y", function (d) { return y(d.y + d.dy) - 6; })
- .style("opacity", function (d) { return this.getComputedTextLength() < x(d.x + d.dx) - x(d.x) ? 1 : 0; });
+ const text2 = function (selection) {
+ selection.attr('x', function (d) { return x(d.x + d.dx) - this.getComputedTextLength() - 6; })
+ .attr('y', function (d) { return y(d.y + d.dy) - 6; })
+ .style('opacity', function (d) {
+ return this.getComputedTextLength() < x(d.x + d.dx) - x(d.x) ? 1 : 0;
+ });
};
- var rect = function (selection) {
- selection.attr("x", function (d) { return x(d.x); })
- .attr("y", function (d) { return y(d.y); })
- .attr("width", function (d) { return x(d.x + d.dx) - x(d.x); })
- .attr("height", function (d) { return y(d.y + d.dy) - y(d.y); });
+ const rect = function (selection) {
+ selection.attr('x', function (d) { return x(d.x); })
+ .attr('y', function (d) { return y(d.y); })
+ .attr('width', function (d) { return x(d.x + d.dx) - x(d.x); })
+ .attr('height', function (d) { return y(d.y + d.dy) - y(d.y); });
};
- var name = function (d) {
+ const name = function (d) {
return d.parent
- ? name(d.parent) + " / " + d.name + " (" + formatNumber(d.value) + ")"
- : d.name + " (" + formatNumber(d.value) + ")";
+ ? name(d.parent) + ' / ' + d.name + ' (' + formatNumber(d.value) + ')'
+ : d.name + ' (' + formatNumber(d.value) + ')';
};
initialize(data);
accumulate(data);
layout(data);
display(data);
-
};
- var render = function () {
-
+ const render = function () {
d3.json(slice.jsonEndpoint(), function (error, json) {
-
if (error !== null) {
slice.error(error.responseText, error);
- return '';
+ return;
}
- div.selectAll("*").remove();
- var width = slice.width();
+ div.selectAll('*').remove();
+ const width = slice.width();
// facet muliple metrics (no sense in combining)
- var height = slice.height() / json.data.length;
- for (var i = 0, l = json.data.length; i < l; i ++) {
+ const height = slice.height() / json.data.length;
+ for (let i = 0, l = json.data.length; i < l; i ++) {
_draw(json.data[i], width, height, json.form_data);
}
slice.done(json);
-
});
-
};
return {
- render: render,
+ render,
resize: render,
};
}
diff --git a/caravel/assets/visualizations/word_cloud.js b/caravel/assets/visualizations/word_cloud.js
index 430da19434403..d999b494870a7 100644
--- a/caravel/assets/visualizations/word_cloud.js
+++ b/caravel/assets/visualizations/word_cloud.js
@@ -1,83 +1,69 @@
-var d3 = window.d3 || require('d3');
-import cloudLayout from 'd3-cloud'
-import { category21 } from '../javascripts/modules/colors'
+/* eslint-disable no-use-before-define */
+import d3 from 'd3';
+import cloudLayout from 'd3-cloud';
+import { category21 } from '../javascripts/modules/colors';
function wordCloudChart(slice) {
- var chart = d3.select(slice.selector);
+ const chart = d3.select(slice.selector);
function refresh() {
d3.json(slice.jsonEndpoint(), function (error, json) {
if (error !== null) {
slice.error(error.responseText, error);
- return '';
+ return;
}
- var data = json.data;
- var range = [
+ const data = json.data;
+ const range = [
json.form_data.size_from,
json.form_data.size_to,
];
- var rotation = json.form_data.rotation;
- var f_rotation;
- if (rotation === "square") {
- f_rotation = function () {
- return ~~(Math.random() * 2) * 90;
- };
- } else if (rotation === "flat") {
- f_rotation = function () {
- return 0;
- };
+ const rotation = json.form_data.rotation;
+ let fRotation;
+ if (rotation === 'square') {
+ fRotation = () => ~~(Math.random() * 2) * 90;
+ } else if (rotation === 'flat') {
+ fRotation = () => 0;
} else {
- f_rotation = function () {
- return (~~(Math.random() * 6) - 3) * 30;
- };
+ fRotation = () => (~~(Math.random() * 6) - 3) * 30;
}
- var size = [slice.width(), slice.height()];
+ const size = [slice.width(), slice.height()];
- var scale = d3.scale.linear()
- .range(range)
- .domain(d3.extent(data, function (d) {
- return d.size;
- }));
-
- var layout = cloudLayout()
- .size(size)
- .words(data)
- .padding(5)
- .rotate(f_rotation)
- .font("serif")
- .fontSize(function (d) {
- return scale(d.size);
- })
- .on("end", draw);
-
- layout.start();
+ const scale = d3.scale.linear()
+ .range(range)
+ .domain(d3.extent(data, function (d) {
+ return d.size;
+ }));
function draw(words) {
- chart.selectAll("*").remove();
+ chart.selectAll('*').remove();
- chart.append("svg")
- .attr("width", layout.size()[0])
- .attr("height", layout.size()[1])
- .append("g")
- .attr("transform", "translate(" + layout.size()[0] / 2 + "," + layout.size()[1] / 2 + ")")
- .selectAll("text")
- .data(words)
- .enter().append("text")
- .style("font-size", function (d) {
- return d.size + "px";
- })
- .style("font-family", "Impact")
- .style("fill", function (d) {
- return category21(d.text);
- })
- .attr("text-anchor", "middle")
- .attr("transform", function (d) {
- return "translate(" + [d.x, d.y] + ") rotate(" + d.rotate + ")";
- })
- .text(function (d) {
- return d.text;
- });
+ chart.append('svg')
+ .attr('width', layout.size()[0])
+ .attr('height', layout.size()[1])
+ .append('g')
+ .attr('transform', `translate(${layout.size()[0] / 2},${layout.size()[1] / 2})`)
+ .selectAll('text')
+ .data(words)
+ .enter()
+ .append('text')
+ .style('font-size', (d) => d.size + 'px')
+ .style('font-family', 'Impact')
+ .style('fill', (d) => category21(d.text))
+ .attr('text-anchor', 'middle')
+ .attr('transform', (d) => `translate(${d.x}, ${d.y}) rotate(${d.rotate})`)
+ .text((d) => d.text);
}
+
+ const layout = cloudLayout()
+ .size(size)
+ .words(data)
+ .padding(5)
+ .rotate(fRotation)
+ .font('serif')
+ .fontSize((d) => scale(d.size))
+ .on('end', draw);
+
+ layout.start();
slice.done(json);
});
}
diff --git a/caravel/assets/visualizations/world_map.js b/caravel/assets/visualizations/world_map.js
index f807e4e976048..9884f12917e99 100644
--- a/caravel/assets/visualizations/world_map.js
+++ b/caravel/assets/visualizations/world_map.js
@@ -1,62 +1,58 @@
// JS
-var d3 = window.d3 || require('d3');
-//var Datamap = require('../vendor/datamaps/datamaps.all.js');
-var Datamap = require('datamaps');
+const d3 = require('d3');
+const Datamap = require('datamaps');
// CSS
require('./world_map.css');
function worldMapChart(slice) {
- var render = function () {
- var container = slice.container;
- var div = d3.select(slice.selector);
+ const render = function () {
+ const container = slice.container;
+ const div = d3.select(slice.selector);
container.css('height', slice.height());
d3.json(slice.jsonEndpoint(), function (error, json) {
- div.selectAll("*").remove();
+ div.selectAll('*').remove();
if (error !== null) {
slice.error(error.responseText, error);
- return '';
+ return;
}
- var fd = json.form_data;
- var data = json.data.filter(function (d) {
- // Ignore XXX's to get better normalization
- return d.country && d.country !== 'XXX';
- });
+ const fd = json.form_data;
+ // Ignore XXX's to get better normalization
+ let data = json.data.filter((d) => (d.country && d.country !== 'XXX'));
- var ext = d3.extent(data, function (d) {
+ const ext = d3.extent(data, function (d) {
return d.m1;
});
- var extRadius = d3.extent(data, function (d) {
+ const extRadius = d3.extent(data, function (d) {
return d.m2;
});
- var radiusScale = d3.scale.linear()
+ const radiusScale = d3.scale.linear()
.domain([extRadius[0], extRadius[1]])
.range([1, fd.max_bubble_size]);
- data.forEach(function (d) {
- d.radius = radiusScale(d.m2);
- });
-
- var colorScale = d3.scale.linear()
+ const colorScale = d3.scale.linear()
.domain([ext[0], ext[1]])
- .range(["#FFF", "black"]);
+ .range(['#FFF', 'black']);
- var d = {};
- for (var i = 0; i < data.length; i++) {
- var country = data[i];
- country.fillColor = colorScale(country.m1);
- d[country.country] = country;
- }
+ data = data.map((d) => Object.assign({}, d, {
+ radius: radiusScale(d.m2),
+ fillColor: colorScale(d.m1),
+ }));
- var f = d3.format('.3s');
+ const mapData = {};
+ data.forEach((d) => {
+ mapData[d.country] = d;
+ });
+
+ const f = d3.format('.3s');
container.show();
- var map = new Datamap({
+ const map = new Datamap({
element: slice.container.get(0),
- data: data,
+ data,
fills: {
defaultFill: '#ddd',
},
@@ -68,9 +64,9 @@ function worldMapChart(slice) {
highlightBorderColor: '#fff',
highlightFillColor: '#005a63',
highlightBorderWidth: 1,
- popupTemplate: function (geo, data) {
- return '' + data.name + '
' + f(data.m1) + '
';
- },
+ popupTemplate: (geo, d) => (
+ `${d.name}
${f(d.m1)}
`
+ ),
},
bubblesConfig: {
borderWidth: 1,
@@ -78,9 +74,9 @@ function worldMapChart(slice) {
borderColor: '#005a63',
popupOnHover: true,
radius: null,
- popupTemplate: function (geo, data) {
- return '' + data.name + '
' + f(data.m2) + '
';
- },
+ popupTemplate: (geo, d) => (
+ `${d.name}
${f(d.m2)}
`
+ ),
fillOpacity: 0.5,
animate: true,
highlightOnHover: true,
@@ -94,22 +90,17 @@ function worldMapChart(slice) {
},
});
- map.updateChoropleth(d);
+ map.updateChoropleth(mapData);
if (fd.show_bubbles) {
map.bubbles(data);
- div.selectAll("circle.datamaps-bubble").style('fill', '#005a63');
+ div.selectAll('circle.datamaps-bubble').style('fill', '#005a63');
}
-
slice.done(json);
-
});
};
- return {
- render: render,
- resize: render,
- };
+ return { render, resize: render };
}
module.exports = worldMapChart;