Skip to content

Commit

Permalink
[reviewable] Organize d3 utilities usage (#6287)
Browse files Browse the repository at this point in the history
* update package.json

* extract changes from another PR

* add d3 prefix

* two more places

* update lockfile
  • Loading branch information
kristw authored and williaster committed Nov 9, 2018
1 parent 8bf9a5b commit 3ffb48c
Show file tree
Hide file tree
Showing 11 changed files with 72 additions and 62 deletions.
4 changes: 4 additions & 0 deletions superset/assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,13 @@
"d3-array": "^1.2.4",
"d3-cloud": "^1.2.1",
"d3-color": "^1.2.0",
"d3-format": "^1.3.2",
"d3-hierarchy": "^1.1.5",
"d3-sankey": "^0.4.2",
"d3-scale": "^2.1.2",
"d3-selection": "^1.3.2",
"d3-svg-legend": "^1.x",
"d3-time-format": "^2.1.3",
"d3-tip": "^0.9.1",
"datamaps": "^0.5.8",
"datatables.net-bs": "^1.10.15",
Expand Down
14 changes: 14 additions & 0 deletions superset/assets/src/modules/geo.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { round as d3Round } from 'd3-format';

export const defaultViewport = {
longitude: 6.85236157047845,
latitude: 31.222656842808707,
Expand All @@ -7,6 +9,7 @@ export const defaultViewport = {
};

const METER_TO_MILE = 1609.34;

export function unitToRadius(unit, num) {
if (unit === 'square_m') {
return Math.sqrt(num / Math.PI);
Expand All @@ -23,3 +26,14 @@ export function unitToRadius(unit, num) {
}
return null;
}

export const EARTH_CIRCUMFERENCE_KM = 40075.16;
export const MILES_PER_KM = 1.60934;

export function kmToPixels(kilometers, latitude, zoomLevel) {
// Algorithm from: http://wiki.openstreetmap.org/wiki/Zoom_levels
const latitudeRad = latitude * (Math.PI / 180);
// Seems like the zoomLevel is off by one
const kmPerPixel = (EARTH_CIRCUMFERENCE_KM * Math.cos(latitudeRad)) / Math.pow(2, zoomLevel + 9);
return d3Round(kilometers / kmPerPixel, 2);
}
44 changes: 24 additions & 20 deletions superset/assets/src/modules/utils.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/* eslint camelcase: 0 */
import $ from 'jquery';
import d3 from 'd3';
import { format as d3Format } from 'd3-format';
import { d3Select } from 'd3-selection';
import { timeFormat as d3TimeFormat } from 'd3-time-format';
import { formatDate, UTC } from './dates';

const siFormatter = d3.format('.3s');
const siFormatter = d3Format('.3s');

export function defaultNumberFormatter(n) {
let si = siFormatter(n);
Expand All @@ -15,27 +17,43 @@ export function defaultNumberFormatter(n) {
}

export function d3FormatPreset(format) {
// like d3.format, but with support for presets like 'smart_date'
// like d3Format, but with support for presets like 'smart_date'
if (format === 'smart_date') {
return formatDate;
}
if (format) {
return d3.format(format);
return d3Format(format);
}
return defaultNumberFormatter;
}

export const d3TimeFormatPreset = function (format) {
const effFormat = format || 'smart_date';
if (effFormat === 'smart_date') {
return formatDate;
}
const f = d3.time.format(effFormat);
const f = d3TimeFormat(effFormat);
return function (dttm) {
const d = UTC(new Date(dttm));
return f(d);
};
};

const formatters = {};

export function d3format(format, number) {
format = format || '.3s';
// Formats a number and memoizes formatters to be reused
if (!(format in formatters)) {
formatters[format] = d3Format(format);
}
try {
return formatters[format](number);
} catch (e) {
return 'ERR';
}
}

/*
Utility function that takes a d3 svg:text selection and a max width, and splits the
text's text across multiple tspan lines such that any given line does not exceed max width
Expand All @@ -47,7 +65,7 @@ export function wrapSvgText(text, width, adjustedY) {
const lineHeight = 1;
// ems
text.each(function () {
const d3Text = d3.select(this);
const d3Text = d3Select(this);
const words = d3Text.text().split(/\s+/);
let word;
let line = [];
Expand Down Expand Up @@ -118,20 +136,6 @@ export const fixDataTableBodyHeight = function ($tableDom, height) {
$tableDom.find('.dataTables_scrollBody').css('max-height', height - headHeight - controlsHeight - paginationHeight);
};

export function d3format(format, number) {
const formatters = {};
// Formats a number and memoizes formatters to be reused
format = format || '.3s';
if (!(format in formatters)) {
formatters[format] = d3.format(format);
}
try {
return formatters[format](number);
} catch (e) {
return 'ERR';
}
}

export function formatSelectOptionsForRange(start, end) {
// outputs array of arrays
// formatSelectOptionsForRange(1, 5)
Expand Down
11 changes: 0 additions & 11 deletions superset/assets/src/utils/common.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
import d3 from 'd3';
import { SupersetClient } from '@superset-ui/connection';
import getClientErrorObject from './getClientErrorObject';

export const EARTH_CIRCUMFERENCE_KM = 40075.16;
export const LUMINANCE_RED_WEIGHT = 0.2126;
export const LUMINANCE_GREEN_WEIGHT = 0.7152;
export const LUMINANCE_BLUE_WEIGHT = 0.0722;
export const MILES_PER_KM = 1.60934;

// Regexp for the label added to time shifted series (1 hour offset, 2 days offset, etc.)
export const TIME_SHIFT_PATTERN = /\d+ \w+ offset/;

export function kmToPixels(kilometers, latitude, zoomLevel) {
// Algorithm from: http://wiki.openstreetmap.org/wiki/Zoom_levels
const latitudeRad = latitude * (Math.PI / 180);
// Seems like the zoomLevel is off by one
const kmPerPixel = (EARTH_CIRCUMFERENCE_KM * Math.cos(latitudeRad)) / Math.pow(2, zoomLevel + 9);
return d3.round(kilometers / kmPerPixel, 2);
}

export function rgbLuminance(r, g, b) {
// Formula: https://en.wikipedia.org/wiki/Relative_luminance
return LUMINANCE_RED_WEIGHT * r + LUMINANCE_GREEN_WEIGHT * g + LUMINANCE_BLUE_WEIGHT * b;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as color from 'd3-color';
import d3 from 'd3';
import { format as d3Format } from 'd3-format';
import { d3FormatPreset } from '../../modules/utils';
import { renderTooltipFactory } from './BigNumber';

Expand Down Expand Up @@ -43,7 +43,7 @@ export default function transformProps(chartProps) {
const compareValue = sortedData[compareIndex][metricName];
percentChange = compareValue === 0
? 0 : (bigNumber - compareValue) / Math.abs(compareValue);
const formatPercentChange = d3.format('+.1%');
const formatPercentChange = d3Format('+.1%');
formattedSubheader = `${formatPercentChange(percentChange)} ${compareSuffix}`;
}
}
Expand Down
4 changes: 2 additions & 2 deletions superset/assets/src/visualizations/Horizon/HorizonChart.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import d3 from 'd3';
import { extent as d3Extent } from 'd3-array';
import HorizonRow, { DEFAULT_COLORS } from './HorizonRow';
import './HorizonChart.css';

Expand Down Expand Up @@ -52,7 +52,7 @@ class HorizonChart extends React.PureComponent {
(acc, current) => acc.concat(current.values),
[],
);
yDomain = d3.extent(allValues, d => d.y);
yDomain = d3Extent(allValues, d => d.y);
}

return (
Expand Down
7 changes: 4 additions & 3 deletions superset/assets/src/visualizations/Horizon/HorizonRow.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import d3 from 'd3';
import { extent as d3Extent } from 'd3-array';
import { scaleLinear } from 'd3-scale';

export const DEFAULT_COLORS = [
'#313695',
Expand Down Expand Up @@ -91,8 +92,8 @@ class HorizonRow extends React.PureComponent {
}

// Create y-scale
const [min, max] = yDomain || d3.extent(data, d => d.y);
const y = d3.scale.linear()
const [min, max] = yDomain || d3Extent(data, d => d.y);
const y = scaleLinear()
.domain([0, Math.max(-min, max)])
.range([0, height]);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import d3 from 'd3';
import Immutable from 'immutable';
import React from 'react';
import PropTypes from 'prop-types';
import ViewportMercator from 'viewport-mercator-project';
import {
kmToPixels,
rgbLuminance,
MILES_PER_KM,
} from '../../utils/common';
import { round as d3Round } from 'd3-format';
import { kmToPixels, MILES_PER_KM } from '../../modules/geo';
import { rgbLuminance } from '../../utils/common';

const propTypes = {
aggregation: PropTypes.string,
Expand Down Expand Up @@ -131,7 +128,7 @@ class ScatterPlotGlowOverlay extends React.Component {
if ((props.renderWhileDragging || !props.isDragging) && props.locations) {
props.locations.forEach(function _forEach(location, i) {
const pixel = mercator.project(props.lngLatAccessor(location));
const pixelRounded = [d3.round(pixel[0], 1), d3.round(pixel[1], 1)];
const pixelRounded = [d3Round(pixel[0], 1), d3Round(pixel[1], 1)];

if (pixelRounded[0] + radius >= 0
&& pixelRounded[0] - radius < props.width
Expand All @@ -140,8 +137,8 @@ class ScatterPlotGlowOverlay extends React.Component {
ctx.beginPath();
if (location.get('properties').get('cluster')) {
let clusterLabel = clusterLabelMap[i];
const scaledRadius = d3.round(Math.pow(clusterLabel / maxLabel, 0.5) * radius, 1);
const fontHeight = d3.round(scaledRadius * 0.5, 1);
const scaledRadius = d3Round(Math.pow(clusterLabel / maxLabel, 0.5) * radius, 1);
const fontHeight = d3Round(scaledRadius * 0.5, 1);
const gradient = ctx.createRadialGradient(
pixelRounded[0], pixelRounded[1], scaledRadius,
pixelRounded[0], pixelRounded[1], 0,
Expand Down Expand Up @@ -177,17 +174,17 @@ class ScatterPlotGlowOverlay extends React.Component {
if (radiusProperty !== null) {
const pointLatitude = props.lngLatAccessor(location)[1];
if (props.pointRadiusUnit === 'Kilometers') {
pointLabel = d3.round(pointRadius, 2) + 'km';
pointLabel = d3Round(pointRadius, 2) + 'km';
pointRadius = kmToPixels(pointRadius, pointLatitude, props.zoom);
} else if (props.pointRadiusUnit === 'Miles') {
pointLabel = d3.round(pointRadius, 2) + 'mi';
pointLabel = d3Round(pointRadius, 2) + 'mi';
pointRadius = kmToPixels(pointRadius * MILES_PER_KM, pointLatitude, props.zoom);
}
}

if (pointMetric !== null) {
pointLabel = Number.isFinite(parseFloat(pointMetric))
? d3.round(pointMetric, 2)
? d3Round(pointMetric, 2)
: pointMetric;
}

Expand All @@ -196,13 +193,13 @@ class ScatterPlotGlowOverlay extends React.Component {
pointRadius = defaultRadius;
}

ctx.arc(pixelRounded[0], pixelRounded[1], d3.round(pointRadius, 1), 0, Math.PI * 2);
ctx.arc(pixelRounded[0], pixelRounded[1], d3Round(pointRadius, 1), 0, Math.PI * 2);
ctx.fillStyle = 'rgb(' + rgb[1] + ', ' + rgb[2] + ', ' + rgb[3] + ')';
ctx.fill();

if (pointLabel !== undefined) {
this.drawText(ctx, pixelRounded, {
fontHeight: d3.round(pointRadius, 1),
fontHeight: d3Round(pointRadius, 1),
label: pointLabel,
radius: pointRadius,
rgb,
Expand Down
7 changes: 4 additions & 3 deletions superset/assets/src/visualizations/Table/Table.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import dt from 'datatables.net-bs';
import 'datatables.net-bs/css/dataTables.bootstrap.css';
import dompurify from 'dompurify';
import { format as d3Format } from 'd3-format';
import { fixDataTableBodyHeight, d3TimeFormatPreset } from '../../modules/utils';
import './Table.css';

Expand Down Expand Up @@ -45,8 +46,8 @@ const propTypes = {
]),
};

const formatValue = d3.format('0,000');
const formatPercent = d3.format('.3p');
const formatValue = d3Format(',.0d');
const formatPercent = d3Format('.3p');
function NOOP() {}

function TableVis(element, props) {
Expand Down Expand Up @@ -129,7 +130,7 @@ function TableVis(element, props) {
html = `<span class="like-pre">${dompurify.sanitize(val)}</span>`;
}
if (isMetric) {
html = d3.format(format || '0.3s')(val);
html = d3Format(format || '0.3s')(val);
}
if (key[0] === '%') {
html = formatPercent(val);
Expand Down
6 changes: 3 additions & 3 deletions superset/assets/src/visualizations/TimeTable/TimeTable.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import d3 from 'd3';
import Mustache from 'mustache';
import { scaleLinear } from 'd3-scale';
import { Table, Thead, Th, Tr, Td } from 'reactable';

import MetricOption from '../../components/MetricOption';
Expand All @@ -19,7 +19,7 @@ function colorFromBounds(value, bounds, colorBounds = ACCESSIBLE_COLOR_BOUNDS) {
const [min, max] = bounds;
const [minColor, maxColor] = colorBounds;
if (min !== null && max !== null) {
const colorScale = d3.scale.linear()
const colorScale = scaleLinear()
.domain([min, (max + min) / 2, max])
.range([minColor, 'grey', maxColor]);
return colorScale(value);
Expand Down Expand Up @@ -131,7 +131,7 @@ class TimeTable extends React.PureComponent {
showYAxis={column.showYAxis}
renderTooltip={({ index }) => (
<div>
<strong>{d3format(column.d3Format, sparkData[index])}</strong>
<strong>{d3format(column.d3format, sparkData[index])}</strong>
<div>{formatDate(entries[index].time)}</div>
</div>
)}
Expand Down
8 changes: 4 additions & 4 deletions superset/assets/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3943,7 +3943,7 @@ d3-ease@1:
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.5.tgz#8ce59276d81241b1b72042d6af2d40e76d936ffb"
integrity sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ==

d3-format@1, d3-format@^1.2.0:
d3-format@1, d3-format@^1.2.0, d3-format@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.3.2.tgz#6a96b5e31bcb98122a30863f7d92365c00603562"
integrity sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ==
Expand Down Expand Up @@ -4009,7 +4009,7 @@ d3-scale@^1.0.5, d3-scale@^1.0.6:
d3-time "1"
d3-time-format "2"

d3-scale@^2.0.0:
d3-scale@^2.0.0, d3-scale@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.1.2.tgz#4e932b7b60182aee9073ede8764c98423e5f9a94"
integrity sha512-bESpd64ylaKzCDzvULcmHKZTlzA/6DGSVwx7QSDj/EnX9cpSevsdiwdHFYI9ouo9tNBbV3v5xztHS2uFeOzh8Q==
Expand All @@ -4021,7 +4021,7 @@ d3-scale@^2.0.0:
d3-time "1"
d3-time-format "2"

d3-selection@1, d3-selection@^1.1.0, d3-selection@^1.3.0:
d3-selection@1, d3-selection@^1.1.0, d3-selection@^1.3.0, d3-selection@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.2.tgz#6e70a9df60801c8af28ac24d10072d82cbfdf652"
integrity sha512-OoXdv1nZ7h2aKMVg3kaUFbLLK5jXUFAMLD/Tu5JA96mjf8f2a9ZUESGY+C36t8R1WFeWk/e55hy54Ml2I62CRQ==
Expand All @@ -4038,7 +4038,7 @@ d3-svg-legend@^1.x:
resolved "https://registry.yarnpkg.com/d3-svg-legend/-/d3-svg-legend-1.13.0.tgz#6217478c9add9d62cb333617e1961311a41a4db3"
integrity sha1-YhdHjJrdnWLLMzYX4ZYTEaQaTbM=

d3-time-format@2:
d3-time-format@2, d3-time-format@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.1.3.tgz#ae06f8e0126a9d60d6364eac5b1533ae1bac826b"
integrity sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==
Expand Down

0 comments on commit 3ffb48c

Please sign in to comment.