[explore] improve bubble viz (#2927)

* [explore] improve bubble viz

* allow for custom axis formatters
* reorg the control panels

* Addressing comments
This commit is contained in:
Maxime Beauchemin 2017-06-19 22:09:09 -07:00 committed by GitHub
parent 591e512327
commit 3e51c61dbf
7 changed files with 116 additions and 67 deletions

View File

@ -7,7 +7,7 @@ import ColumnOption from '../../components/ColumnOption';
const D3_FORMAT_DOCS = 'D3 format syntax: https://github.com/d3/d3-format';
// input choices & options
const D3_TIME_FORMAT_OPTIONS = [
const D3_FORMAT_OPTIONS = [
['.3s', '.3s | 12.3k'],
['.3%', '.3% | 1234543.210%'],
['.4r', '.4r | 12350'],
@ -20,8 +20,7 @@ const ROW_LIMIT_OPTIONS = [10, 50, 100, 250, 500, 1000, 5000, 10000, 50000];
const SERIES_LIMITS = [0, 5, 10, 25, 50, 100, 500];
export const TIME_STAMP_OPTIONS = [
export const D3_TIME_FORMAT_OPTIONS = [
['smart_date', 'Adaptative formating'],
['%m/%d/%Y', '%m/%d/%Y | 01/14/2019'],
['%Y-%m-%d', '%Y-%m-%d | 2019-01-14'],
@ -594,7 +593,7 @@ export const controls = {
label: 'Number format',
renderTrigger: true,
default: '.3s',
choices: D3_TIME_FORMAT_OPTIONS,
choices: D3_FORMAT_OPTIONS,
description: D3_FORMAT_DOCS,
},
@ -682,8 +681,8 @@ export const controls = {
type: 'SelectControl',
label: 'Y Axis',
default: null,
description: 'Metric assigned to the [Y] axis',
validators: [v.nonEmpty],
description: 'Metric assigned to the [Y] axis',
optionRenderer: m => <MetricOption metric={m} />,
valueRenderer: m => <MetricOption metric={m} />,
valueKey: 'metric_name',
@ -765,7 +764,7 @@ export const controls = {
freeForm: true,
label: 'Table Timestamp Format',
default: 'smart_date',
choices: TIME_STAMP_OPTIONS,
choices: D3_TIME_FORMAT_OPTIONS,
description: 'Timestamp Format',
},
@ -788,12 +787,22 @@ export const controls = {
},
x_axis_format: {
type: 'SelectControl',
freeForm: true,
label: 'X Axis Format',
renderTrigger: true,
default: '.3s',
choices: D3_FORMAT_OPTIONS,
description: D3_FORMAT_DOCS,
},
x_axis_time_format: {
type: 'SelectControl',
freeForm: true,
label: 'X Axis Format',
renderTrigger: true,
default: 'smart_date',
choices: TIME_STAMP_OPTIONS,
choices: D3_TIME_FORMAT_OPTIONS,
description: D3_FORMAT_DOCS,
},
@ -803,7 +812,7 @@ export const controls = {
label: 'Y Axis Format',
renderTrigger: true,
default: '.3s',
choices: D3_TIME_FORMAT_OPTIONS,
choices: D3_FORMAT_OPTIONS,
description: D3_FORMAT_DOCS,
},
@ -812,7 +821,7 @@ export const controls = {
freeForm: true,
label: 'Right Axis Format',
default: '.3s',
choices: D3_TIME_FORMAT_OPTIONS,
choices: D3_FORMAT_OPTIONS,
description: D3_FORMAT_DOCS,
},

View File

@ -1,3 +1,5 @@
import { D3_TIME_FORMAT_OPTIONS } from './controls';
export const sections = {
druidTimeSeries: {
label: 'Time',
@ -143,6 +145,11 @@ const visTypes = {
},
sections.NVD3TimeSeries[1],
],
controlOverrides: {
x_axis_format: {
choices: D3_TIME_FORMAT_OPTIONS,
},
},
},
dual_line: {
@ -158,15 +165,13 @@ const visTypes = {
{
label: 'Y Axis 1',
controlSetRows: [
['metric'],
['y_axis_format'],
['metric', 'y_axis_format'],
],
},
{
label: 'Y Axis 2',
controlSetRows: [
['metric_2'],
['y_axis_2_format'],
['metric_2', 'y_axis_2_format'],
],
},
],
@ -410,19 +415,41 @@ const visTypes = {
label: null,
controlSetRows: [
['series', 'entity'],
['x', 'y'],
['size', 'limit'],
],
},
{
label: 'Chart Options',
controlSetRows: [
['show_legend', 'max_bubble_size'],
['x_axis_label', 'y_axis_label'],
['x_log_scale', 'y_log_scale'],
['show_legend', null],
],
},
{
label: 'Bubbles',
controlSetRows: [
['size', 'max_bubble_size'],
],
},
{
label: 'X Axis',
controlSetRows: [
['x', 'x_axis_format'],
['x_axis_label', 'x_log_scale'],
],
},
{
label: 'Y Axis',
controlSetRows: [
['y', 'y_axis_format'],
['y_axis_label', 'y_log_scale'],
],
},
],
controlOverrides: {
x_axis_format: {
default: '.3s',
},
},
},
bullet: {

View File

@ -2,7 +2,7 @@ import moment from 'moment';
const d3 = require('d3');
function UTC(dttm) {
export function UTC(dttm) {
return new Date(
dttm.getUTCFullYear(),
dttm.getUTCMonth(),
@ -67,13 +67,6 @@ export const formatDate = function (dttm) {
// d = new Date(d.getTime() - 1 * 60 * 60 * 1000);
return tickMultiFormat(d);
};
export const timeFormatFactory = function (d3timeFormat) {
const f = d3.time.format(d3timeFormat);
return function (dttm) {
const d = UTC(new Date(dttm));
return f(d);
};
};
export const fDuration = function (t1, t2, f = 'HH:mm:ss.SS') {
const diffSec = t2 - t1;
const duration = moment(new Date(diffSec));

View File

@ -1,6 +1,29 @@
/* eslint camelcase: 0 */
const d3 = require('d3');
const $ = require('jquery');
import d3 from 'd3';
import $ from 'jquery';
import { formatDate, UTC } from './dates';
export function d3FormatPreset(format) {
// like d3.format, but with support for presets like 'smart_date'
if (format === 'smart_date') {
return formatDate;
}
if (format) {
return d3.format(format);
}
return d3.format('.3s');
}
export const d3TimeFormatPreset = function (format) {
if (format === 'smart_date') {
return formatDate;
}
const f = d3.time.format(format);
return function (dttm) {
const d = UTC(new Date(dttm));
return f(d);
};
};
/*
Utility function that takes a d3 svg:text selection and a max width, and splits the

View File

@ -3,7 +3,6 @@ import { expect } from 'chai';
import {
tickMultiFormat,
formatDate,
timeFormatFactory,
fDuration,
now,
epochTimeXHoursAgo,
@ -23,12 +22,6 @@ describe('formatDate', () => {
});
});
describe('timeFormatFactory', () => {
it('is a function', () => {
assert.isFunction(timeFormatFactory);
});
});
describe('fDuration', () => {
it('is a function', () => {
assert.isFunction(fDuration);

View File

@ -2,6 +2,7 @@ import { it, describe } from 'mocha';
import { expect } from 'chai';
import {
tryNumify, slugify, formatSelectOptionsForRange, d3format,
d3FormatPreset, d3TimeFormatPreset,
} from '../../../javascripts/modules/utils';
describe('utils', () => {
@ -35,4 +36,20 @@ describe('utils', () => {
expect(d3format('.3s', 1237)).to.equal('1.24k');
expect(d3format('', 1237)).to.equal('1.24k');
});
describe('d3FormatPreset', () => {
it('is a function', () => {
assert.isFunction(d3FormatPreset);
});
it('returns a working formatter', () => {
expect(d3FormatPreset('.3s')(3000000)).to.equal('3.00M');
});
});
describe('d3TimeFormatPreset', () => {
it('is a function', () => {
assert.isFunction(d3TimeFormatPreset);
});
it('returns a working time formatter', () => {
expect(d3FormatPreset('smart_date')(0)).to.equal('1970');
});
});
});

View File

@ -2,20 +2,19 @@
import $ from 'jquery';
import throttle from 'lodash.throttle';
import d3 from 'd3';
import nv from 'nvd3';
import { category21 } from '../javascripts/modules/colors';
import { timeFormatFactory, formatDate } from '../javascripts/modules/dates';
import { customizeToolTip, tryNumify } from '../javascripts/modules/utils';
import { customizeToolTip, d3TimeFormatPreset, d3FormatPreset, tryNumify } from '../javascripts/modules/utils';
import { TIME_STAMP_OPTIONS } from '../javascripts/explore/stores/controls';
import { D3_TIME_FORMAT_OPTIONS } from '../javascripts/explore/stores/controls';
const nv = require('nvd3');
// CSS
require('../node_modules/nvd3/build/nv.d3.min.css');
require('./nvd3_vis.css');
import '../node_modules/nvd3/build/nv.d3.min.css';
import './nvd3_vis.css';
const timeStampFormats = TIME_STAMP_OPTIONS.map(opt => opt[0]);
const timeStampFormats = D3_TIME_FORMAT_OPTIONS.map(opt => opt[0]);
const minBarWidth = 15;
const animationTime = 1000;
@ -317,16 +316,6 @@ function nvd3Vis(slice, payload) {
if (fd.x_log_scale) {
chart.xScale(d3.scale.log());
}
let xAxisFormatter;
if (vizType === 'bubble') {
xAxisFormatter = d3.format('.3s');
} else if (fd.x_axis_format === 'smart_date') {
xAxisFormatter = formatDate;
chart.xAxis.tickFormat(xAxisFormatter);
} else if (fd.x_axis_format !== undefined) {
xAxisFormatter = timeFormatFactory(fd.x_axis_format);
chart.xAxis.tickFormat(xAxisFormatter);
}
const isTimeSeries = timeStampFormats.indexOf(fd.x_axis_format) > -1;
// if x axis format is a date format, rotate label 90 degrees
@ -334,28 +323,26 @@ function nvd3Vis(slice, payload) {
chart.xAxis.rotateLabels(45);
}
if (chart.hasOwnProperty('x2Axis')) {
let xAxisFormatter = d3FormatPreset(fd.x_axis_format);
if (isTimeSeries && fd.x_axis_format) {
xAxisFormatter = d3TimeFormatPreset(fd.x_axis_format);
}
if (chart.x2Axis && chart.x2Axis.tickFormat) {
chart.x2Axis.tickFormat(xAxisFormatter);
height += 30;
}
if (vizType === 'bubble') {
chart.xAxis.tickFormat(d3.format('.3s'));
} else if (fd.x_axis_format === 'smart_date') {
chart.xAxis.tickFormat(formatDate);
} else if (fd.x_axis_format !== undefined) {
chart.xAxis.tickFormat(timeFormatFactory(fd.x_axis_format));
}
if (chart.yAxis !== undefined) {
chart.yAxis.tickFormat(d3.format('.3s'));
if (chart.xAxis && chart.xAxis.tickFormat) {
chart.xAxis.tickFormat(xAxisFormatter);
}
if (fd.y_axis_format && chart.yAxis) {
chart.yAxis.tickFormat(d3.format(fd.y_axis_format));
if (chart.y2Axis !== undefined) {
chart.y2Axis.tickFormat(d3.format(fd.y_axis_format));
}
const yAxisFormatter = d3FormatPreset(fd.y_axis_format);
if (chart.yAxis && chart.yAxis.tickFormat) {
chart.yAxis.tickFormat(yAxisFormatter);
}
if (chart.y2Axis && chart.y2Axis.tickFormat) {
chart.y2Axis.tickFormat(yAxisFormatter);
}
if (vizType !== 'bullet') {
chart.color(d => category21(d[colorKey]));
}