diff --git a/client/app/visualizations/chart/chart.html b/client/app/visualizations/chart/chart.html
index c6f420a29f..8c2cbedc34 100644
--- a/client/app/visualizations/chart/chart.html
+++ b/client/app/visualizations/chart/chart.html
@@ -1,6 +1,6 @@
-
-
+
-
+
diff --git a/client/app/visualizations/chart/getChartData.js b/client/app/visualizations/chart/getChartData.js
new file mode 100644
index 0000000000..8e8f1ebd39
--- /dev/null
+++ b/client/app/visualizations/chart/getChartData.js
@@ -0,0 +1,101 @@
+import { isNil, each, forOwn, sortBy, values } from 'lodash';
+
+function addPointToSeries(point, seriesCollection, seriesName) {
+ if (seriesCollection[seriesName] === undefined) {
+ seriesCollection[seriesName] = {
+ name: seriesName,
+ type: 'column',
+ data: [],
+ };
+ }
+
+ seriesCollection[seriesName].data.push(point);
+}
+
+export default function getChartData(data, options) {
+ const series = {};
+
+ const mappings = options.columnMapping;
+
+ each(data, (row) => {
+ let point = { $raw: row };
+ let seriesName = null;
+ let xValue = 0;
+ const yValues = {};
+ let eValue = null;
+ let sizeValue = null;
+ let zValue = null;
+
+ forOwn(row, (v, definition) => {
+ definition = '' + definition;
+ const definitionParts = definition.split('::') || definition.split('__');
+ const name = definitionParts[0];
+ const type = mappings ? mappings[definition] : definitionParts[1];
+ let value = v;
+
+ if (type === 'unused') {
+ return;
+ }
+
+ if (type === 'x') {
+ xValue = value;
+ point[type] = value;
+ }
+ if (type === 'y') {
+ if (value == null) {
+ value = 0;
+ }
+ yValues[name] = value;
+ point[type] = value;
+ }
+ if (type === 'yError') {
+ eValue = value;
+ point[type] = value;
+ }
+
+ if (type === 'series') {
+ seriesName = String(value);
+ }
+
+ if (type === 'size') {
+ point[type] = value;
+ sizeValue = value;
+ }
+
+ if (type === 'zVal') {
+ point[type] = value;
+ zValue = value;
+ }
+
+ if (type === 'multiFilter' || type === 'multi-filter') {
+ seriesName = String(value);
+ }
+ });
+
+ if (isNil(seriesName)) {
+ each(yValues, (yValue, ySeriesName) => {
+ point = { x: xValue, y: yValue, $raw: point.$raw };
+ if (eValue !== null) {
+ point.yError = eValue;
+ }
+
+ if (sizeValue !== null) {
+ point.size = sizeValue;
+ }
+
+ if (zValue !== null) {
+ point.zVal = zValue;
+ }
+ addPointToSeries(point, series, ySeriesName);
+ });
+ } else {
+ addPointToSeries(point, series, seriesName);
+ }
+ });
+ return sortBy(values(series), ({ name }) => {
+ if (options.seriesOptions[name]) {
+ return options.seriesOptions[name].zIndex;
+ }
+ return 0;
+ });
+}
diff --git a/client/app/visualizations/chart/index.js b/client/app/visualizations/chart/index.js
index 4343bfce95..2486708eaa 100644
--- a/client/app/visualizations/chart/index.js
+++ b/client/app/visualizations/chart/index.js
@@ -1,7 +1,11 @@
import {
- some, extend, defaults, has, partial, intersection, without, includes, isUndefined,
- sortBy, each, map, keys, difference,
+ some, partial, intersection, without, includes, sortBy, each, map, keys, difference, merge, isNil, trim, pick,
} from 'lodash';
+import { angular2react } from 'angular2react';
+import { registerVisualization } from '@/visualizations';
+import { clientConfig } from '@/services/auth';
+import ColorPalette from '@/visualizations/ColorPalette';
+import getChartData from './getChartData';
import template from './chart.html';
import editorTemplate from './chart-editor.html';
@@ -22,348 +26,296 @@ const DEFAULT_OPTIONS = {
percentFormat: '0[.]00%',
// dateTimeFormat: 'DD/MM/YYYY HH:mm', // will be set from clientConfig
textFormat: '', // default: combination of {{ @@yPercent }} ({{ @@y }} ± {{ @@yError }})
-
- defaultColumns: 3,
- defaultRows: 8,
- minColumns: 1,
- minRows: 5,
};
-function ChartRenderer() {
- return {
- restrict: 'E',
- scope: {
- queryResult: '=',
- options: '=?',
- },
- template,
- replace: false,
- controller($scope, clientConfig) {
- $scope.chartSeries = [];
-
- function zIndexCompare(series) {
- if ($scope.options.seriesOptions[series.name]) {
- return $scope.options.seriesOptions[series.name].zIndex;
- }
- return 0;
- }
-
- function reloadData() {
- if (!isUndefined($scope.queryResult) && $scope.queryResult.getData()) {
- const data = $scope.queryResult.getChartData($scope.options.columnMapping);
- $scope.chartSeries = sortBy(data, zIndexCompare);
- }
- }
+function initEditorForm(options, columns) {
+ const result = {
+ yAxisColumns: [],
+ seriesList: sortBy(keys(options.seriesOptions), name => options.seriesOptions[name].zIndex),
+ valuesList: keys(options.valuesOptions),
+ };
- function reloadChart() {
- reloadData();
- $scope.plotlyOptions = extend({
- showDataLabels: $scope.options.globalSeriesType === 'pie',
- dateTimeFormat: clientConfig.dateTimeFormat,
- }, DEFAULT_OPTIONS, $scope.options);
- }
+ // Use only mappings for columns that exists in query results
+ const mappings = pick(
+ options.columnMapping,
+ map(columns, c => c.name),
+ );
+
+ each(mappings, (type, column) => {
+ switch (type) {
+ case 'x':
+ result.xAxisColumn = column;
+ break;
+ case 'y':
+ result.yAxisColumns.push(column);
+ break;
+ case 'series':
+ result.groupby = column;
+ break;
+ case 'yError':
+ result.errorColumn = column;
+ break;
+ case 'size':
+ result.sizeColumn = column;
+ break;
+ case 'zVal':
+ result.zValColumn = column;
+ break;
+ // no default
+ }
+ });
- $scope.$watch('options', reloadChart, true);
- $scope.$watch('queryResult && queryResult.getData()', reloadData);
- },
- };
+ return result;
}
-function ChartEditor(ColorPalette, clientConfig) {
- return {
- restrict: 'E',
- template: editorTemplate,
- scope: {
- queryResult: '=',
- options: '=?',
- },
- link(scope) {
- scope.currentTab = 'general';
- scope.colors = extend({ Automatic: null }, ColorPalette);
-
- scope.stackingOptions = {
- Disabled: null,
- Stack: 'stack',
- };
-
- scope.changeTab = (tab) => {
- scope.currentTab = tab;
- };
-
- scope.chartTypes = {
- line: { name: 'Line', icon: 'line-chart' },
- column: { name: 'Bar', icon: 'bar-chart' },
- area: { name: 'Area', icon: 'area-chart' },
- pie: { name: 'Pie', icon: 'pie-chart' },
- scatter: { name: 'Scatter', icon: 'circle-o' },
- bubble: { name: 'Bubble', icon: 'circle-o' },
- heatmap: { name: 'Heatmap', icon: 'th' },
- box: { name: 'Box', icon: 'square-o' },
- };
-
- if (clientConfig.allowCustomJSVisualizations) {
- scope.chartTypes.custom = { name: 'Custom', icon: 'code' };
+const ChartRenderer = {
+ template,
+ bindings: {
+ data: '<',
+ options: '<',
+ },
+ controller($scope) {
+ this.chartSeries = [];
+
+ const update = () => {
+ if (this.data) {
+ this.chartSeries = getChartData(this.data.rows, this.options);
}
+ };
- scope.xAxisScales = [
- { label: 'Auto Detect', value: '-' },
- { label: 'Datetime', value: 'datetime' },
- { label: 'Linear', value: 'linear' },
- { label: 'Logarithmic', value: 'logarithmic' },
- { label: 'Category', value: 'category' },
- ];
- scope.yAxisScales = ['linear', 'logarithmic', 'datetime', 'category'];
-
- scope.chartTypeChanged = () => {
- keys(scope.options.seriesOptions).forEach((key) => {
- scope.options.seriesOptions[key].type = scope.options.globalSeriesType;
- });
- scope.options.showDataLabels = scope.options.globalSeriesType === 'pie';
- scope.$applyAsync();
- };
+ $scope.$watch('$ctrl.data', update);
+ $scope.$watch('$ctrl.options', update, true);
+ },
+};
- scope.colorScheme = ['Blackbody', 'Bluered', 'Blues', 'Earth', 'Electric',
- 'Greens', 'Greys', 'Hot', 'Jet', 'Picnic', 'Portland',
- 'Rainbow', 'RdBu', 'Reds', 'Viridis', 'YlGnBu', 'YlOrRd', 'Custom...'];
+const ChartEditor = {
+ template: editorTemplate,
+ bindings: {
+ data: '<',
+ options: '<',
+ onOptionsChange: '<',
+ },
+ controller($scope) {
+ this.currentTab = 'general';
+ this.setCurrentTab = (tab) => {
+ this.currentTab = tab;
+ };
+
+ this.colors = {
+ Automatic: null,
+ ...ColorPalette,
+ };
+
+ this.stackingOptions = {
+ Disabled: null,
+ Stack: 'stack',
+ };
+
+ this.chartTypes = {
+ line: { name: 'Line', icon: 'line-chart' },
+ column: { name: 'Bar', icon: 'bar-chart' },
+ area: { name: 'Area', icon: 'area-chart' },
+ pie: { name: 'Pie', icon: 'pie-chart' },
+ scatter: { name: 'Scatter', icon: 'circle-o' },
+ bubble: { name: 'Bubble', icon: 'circle-o' },
+ heatmap: { name: 'Heatmap', icon: 'th' },
+ box: { name: 'Box', icon: 'square-o' },
+ };
+
+ if (clientConfig.allowCustomJSVisualizations) {
+ this.chartTypes.custom = { name: 'Custom', icon: 'code' };
+ }
+
+ this.xAxisScales = [
+ { label: 'Auto Detect', value: '-' },
+ { label: 'Datetime', value: 'datetime' },
+ { label: 'Linear', value: 'linear' },
+ { label: 'Logarithmic', value: 'logarithmic' },
+ { label: 'Category', value: 'category' },
+ ];
+ this.yAxisScales = ['linear', 'logarithmic', 'datetime', 'category'];
+
+ this.colorScheme = ['Blackbody', 'Bluered', 'Blues', 'Earth', 'Electric',
+ 'Greens', 'Greys', 'Hot', 'Jet', 'Picnic', 'Portland',
+ 'Rainbow', 'RdBu', 'Reds', 'Viridis', 'YlGnBu', 'YlOrRd', 'Custom...'];
+
+ this.chartTypeChanged = () => {
+ keys(this.options.seriesOptions).forEach((key) => {
+ this.options.seriesOptions[key].type = this.options.globalSeriesType;
+ });
+ this.options.showDataLabels = this.options.globalSeriesType === 'pie';
+ $scope.$applyAsync();
+ };
- scope.showSizeColumnPicker = () => some(scope.options.seriesOptions, options => options.type === 'bubble');
- scope.showZColumnPicker = () => some(scope.options.seriesOptions, options => options.type === 'heatmap');
+ this.showSizeColumnPicker = () => some(this.options.seriesOptions, options => options.type === 'bubble');
+ this.showZColumnPicker = () => some(this.options.seriesOptions, options => options.type === 'heatmap');
- if (scope.options.customCode === undefined) {
- scope.options.customCode = `// Available variables are x, ys, element, and Plotly
+ if (isNil(this.options.customCode)) {
+ this.options.customCode = trim(`
+// Available variables are x, ys, element, and Plotly
// Type console.log(x, ys); for more info about x and ys
// To plot your graph call Plotly.plot(element, ...)
-// Plotly examples and docs: https://plot.ly/javascript/`;
- }
-
- function refreshColumns() {
- scope.columns = scope.queryResult.getColumns();
- scope.columnNames = map(scope.columns, i => i.name);
- if (scope.columnNames.length > 0) {
- each(difference(keys(scope.options.columnMapping), scope.columnNames), (column) => {
- delete scope.options.columnMapping[column];
- });
- }
+// Plotly examples and docs: https://plot.ly/javascript/
+ `);
+ }
+
+ this.form = initEditorForm(this.options, this.data.columns);
+
+ const refreshColumns = () => {
+ this.columns = this.data.columns;
+ this.columnNames = map(this.columns, c => c.name);
+ if (this.columnNames.length > 0) {
+ each(difference(keys(this.options.columnMapping), this.columnNames), (column) => {
+ delete this.options.columnMapping[column];
+ });
}
+ };
- function refreshColumnsAndForm() {
- refreshColumns();
- if (!scope.queryResult.getData() ||
- scope.queryResult.getData().length === 0 ||
- scope.columns.length === 0) {
- return;
- }
- scope.form.yAxisColumns = intersection(scope.form.yAxisColumns, scope.columnNames);
- if (!includes(scope.columnNames, scope.form.xAxisColumn)) {
- scope.form.xAxisColumn = undefined;
+ const refreshColumnsAndForm = () => {
+ refreshColumns();
+ const data = this.data;
+ if (data && (data.columns.length > 0) && (data.rows.length > 0)) {
+ this.form.yAxisColumns = intersection(this.form.yAxisColumns, this.columnNames);
+ if (!includes(this.columnNames, this.form.xAxisColumn)) {
+ this.form.xAxisColumn = undefined;
}
- if (!includes(scope.columnNames, scope.form.groupby)) {
- scope.form.groupby = undefined;
+ if (!includes(this.columnNames, this.form.groupby)) {
+ this.form.groupby = undefined;
}
}
+ };
+
+ const refreshSeries = () => {
+ const chartData = getChartData(this.data.rows, this.options);
+ const seriesNames = map(chartData, s => s.name);
+ const existing = keys(this.options.seriesOptions);
+ each(difference(seriesNames, existing), (name) => {
+ this.options.seriesOptions[name] = {
+ type: this.options.globalSeriesType,
+ yAxis: 0,
+ };
+ this.form.seriesList.push(name);
+ });
+ each(difference(existing, seriesNames), (name) => {
+ this.form.seriesList = without(this.form.seriesList, name);
+ delete this.options.seriesOptions[name];
+ });
- function refreshSeries() {
- const chartData = scope.queryResult.getChartData(scope.options.columnMapping);
- const seriesNames = map(chartData, i => i.name);
- const existing = keys(scope.options.seriesOptions);
- each(difference(seriesNames, existing), (name) => {
- scope.options.seriesOptions[name] = {
- type: scope.options.globalSeriesType,
- yAxis: 0,
- };
- scope.form.seriesList.push(name);
+ if (this.options.globalSeriesType === 'pie') {
+ const uniqueValuesNames = new Set();
+ each(chartData, (series) => {
+ each(series.data, (row) => {
+ uniqueValuesNames.add(row.x);
+ });
});
- each(difference(existing, seriesNames), (name) => {
- scope.form.seriesList = without(scope.form.seriesList, name);
- delete scope.options.seriesOptions[name];
+ const valuesNames = [];
+ uniqueValuesNames.forEach(v => valuesNames.push(v));
+
+ // initialize newly added values
+ const newValues = difference(valuesNames, keys(this.options.valuesOptions));
+ each(newValues, (name) => {
+ this.options.valuesOptions[name] = {};
+ this.form.valuesList.push(name);
});
-
- if (scope.options.globalSeriesType === 'pie') {
- const uniqueValuesNames = new Set();
- each(chartData, (series) => {
- each(series.data, (row) => {
- uniqueValuesNames.add(row.x);
- });
- });
- const valuesNames = [];
- uniqueValuesNames.forEach(v => valuesNames.push(v));
-
- // initialize newly added values
- const newValues = difference(valuesNames, keys(scope.options.valuesOptions));
- each(newValues, (name) => {
- scope.options.valuesOptions[name] = {};
- scope.form.valuesList.push(name);
- });
- // remove settings for values that are no longer available
- each(keys(scope.options.valuesOptions), (name) => {
- if (valuesNames.indexOf(name) === -1) {
- delete scope.options.valuesOptions[name];
- }
- });
- scope.form.valuesList = intersection(scope.form.valuesList, valuesNames);
- }
- }
-
- function setColumnRole(role, column) {
- scope.options.columnMapping[column] = role;
- }
-
- function unsetColumn(column) {
- setColumnRole('unused', column);
+ // remove settings for values that are no longer available
+ each(keys(this.options.valuesOptions), (name) => {
+ if (valuesNames.indexOf(name) === -1) {
+ delete this.options.valuesOptions[name];
+ }
+ });
+ this.form.valuesList = intersection(this.form.valuesList, valuesNames);
}
+ };
- refreshColumns();
-
- scope.$watch('options.columnMapping', () => {
- if (scope.queryResult.status === 'done') {
- refreshSeries();
- }
- }, true);
-
- scope.$watch(() => [scope.queryResult.getId(), scope.queryResult.status], (changed) => {
- if (!changed[0] || changed[1] !== 'done') {
- return;
- }
+ const setColumnRole = (role, column) => {
+ this.options.columnMapping[column] = role;
+ };
- refreshColumnsAndForm();
- refreshSeries();
- }, true);
+ const unsetColumn = column => setColumnRole('unused', column);
- scope.form = {
- yAxisColumns: [],
- seriesList: sortBy(keys(scope.options.seriesOptions), name => scope.options.seriesOptions[name].zIndex),
- valuesList: keys(scope.options.valuesOptions),
- };
+ refreshColumns();
- scope.$watchCollection('form.seriesList', (value) => {
- each(value, (name, index) => {
- scope.options.seriesOptions[name].zIndex = index;
- scope.options.seriesOptions[name].index = 0; // is this needed?
- });
- });
+ $scope.$watch('$ctrl.options.columnMapping', refreshSeries, true);
+ $scope.$watch('$ctrl.data', () => {
+ refreshColumnsAndForm();
+ refreshSeries();
+ });
- scope.$watchCollection('form.yAxisColumns', (value, old) => {
- each(old, unsetColumn);
- each(value, partial(setColumnRole, 'y'));
- });
-
- scope.$watch('form.xAxisColumn', (value, old) => {
- if (old !== undefined) {
- unsetColumn(old);
- }
- if (value !== undefined) { setColumnRole('x', value); }
- });
-
- scope.$watch('form.errorColumn', (value, old) => {
- if (old !== undefined) {
- unsetColumn(old);
- }
- if (value !== undefined) {
- setColumnRole('yError', value);
- }
+ $scope.$watchCollection('$ctrl.form.seriesList', (value) => {
+ each(value, (name, index) => {
+ this.options.seriesOptions[name].zIndex = index;
+ this.options.seriesOptions[name].index = 0; // is this needed?
});
+ });
- scope.$watch('form.sizeColumn', (value, old) => {
- if (old !== undefined) {
- unsetColumn(old);
- }
- if (value !== undefined) {
- setColumnRole('size', value);
- }
- });
+ $scope.$watchCollection('$ctrl.form.yAxisColumns', (value, old) => {
+ each(old, unsetColumn);
+ each(value, partial(setColumnRole, 'y'));
+ });
- scope.$watch('form.zValColumn', (value, old) => {
- if (old !== undefined) {
- unsetColumn(old);
- }
- if (value !== undefined) {
- setColumnRole('zVal', value);
- }
- });
+ $scope.$watch('$ctrl.form.xAxisColumn', (value, old) => {
+ if (old !== undefined) { unsetColumn(old); }
+ if (value !== undefined) { setColumnRole('x', value); }
+ });
- scope.$watch('form.groupby', (value, old) => {
- if (old !== undefined) {
- unsetColumn(old);
- }
- if (value !== undefined) {
- setColumnRole('series', value);
- }
- });
+ $scope.$watch('$ctrl.form.errorColumn', (value, old) => {
+ if (old !== undefined) { unsetColumn(old); }
+ if (value !== undefined) { setColumnRole('yError', value); }
+ });
- if (!has(scope.options, 'legend')) {
- scope.options.legend = { enabled: true };
- }
+ $scope.$watch('$ctrl.form.sizeColumn', (value, old) => {
+ if (old !== undefined) { unsetColumn(old); }
+ if (value !== undefined) { setColumnRole('size', value); }
+ });
- if (scope.columnNames) {
- each(scope.options.columnMapping, (value, key) => {
- if (scope.columnNames.length > 0 && !includes(scope.columnNames, key)) {
- return;
- }
- if (value === 'x') {
- scope.form.xAxisColumn = key;
- } else if (value === 'y') {
- scope.form.yAxisColumns.push(key);
- } else if (value === 'series') {
- scope.form.groupby = key;
- } else if (value === 'yError') {
- scope.form.errorColumn = key;
- } else if (value === 'size') {
- scope.form.sizeColumn = key;
- } else if (value === 'zVal') {
- scope.form.zValColumn = key;
- }
- });
- }
+ $scope.$watch('$ctrl.form.zValColumn', (value, old) => {
+ if (old !== undefined) { unsetColumn(old); }
+ if (value !== undefined) { setColumnRole('zVal', value); }
+ });
- function setOptionsDefaults() {
- if (scope.options) {
- // For existing visualization - set default options
- defaults(scope.options, extend({}, DEFAULT_OPTIONS, {
- showDataLabels: scope.options.globalSeriesType === 'pie',
- dateTimeFormat: clientConfig.dateTimeFormat,
- }));
- }
- }
- setOptionsDefaults();
- scope.$watch('options', setOptionsDefaults);
-
- scope.templateHint = `
-
Use special names to access additional properties:
-
{{ @@name }}
series name;
-
{{ @@x }}
x-value;
-
{{ @@y }}
y-value;
-
{{ @@yPercent }}
relative y-value;
-
{{ @@yError }}
y deviation;
-
{{ @@size }}
bubble size;
-
Also, all query result columns can be referenced using
- {{ column_name }}
syntax.
- `;
- },
- };
-}
+ $scope.$watch('$ctrl.form.groupby', (value, old) => {
+ if (old !== undefined) { unsetColumn(old); }
+ if (value !== undefined) { setColumnRole('series', value); }
+ });
-const ColorBox = {
- bindings: {
- color: '<',
+ $scope.$watch('$ctrl.options', (options) => {
+ this.onOptionsChange(options);
+ }, true);
+
+ this.templateHint = `
+
Use special names to access additional properties:
+
{{ @@name }}
series name;
+
{{ @@x }}
x-value;
+
{{ @@y }}
y-value;
+
{{ @@yPercent }}
relative y-value;
+
{{ @@yError }}
y deviation;
+
{{ @@size }}
bubble size;
+
Also, all query result columns can be referenced using
+ {{ column_name }}
syntax.
+ `;
},
- template: "
",
};
export default function init(ngModule) {
- ngModule.component('colorBox', ColorBox);
- ngModule.directive('chartRenderer', ChartRenderer);
- ngModule.directive('chartEditor', ChartEditor);
- ngModule.config((VisualizationProvider) => {
- const renderTemplate = '
';
- const editTemplate = '
';
-
- VisualizationProvider.registerVisualization({
+ ngModule.component('chartRenderer', ChartRenderer);
+ ngModule.component('chartEditor', ChartEditor);
+
+ ngModule.run(($injector) => {
+ registerVisualization({
type: 'CHART',
name: 'Chart',
- renderTemplate,
- editorTemplate: editTemplate,
- defaultOptions: DEFAULT_OPTIONS,
+ getOptions: options => merge({}, DEFAULT_OPTIONS, {
+ showDataLabels: options.globalSeriesType === 'pie',
+ dateTimeFormat: clientConfig.dateTimeFormat,
+ }, options),
+ Renderer: angular2react('chartRenderer', ChartRenderer, $injector),
+ Editor: angular2react('chartEditor', ChartEditor, $injector),
+
+ defaultColumns: 3,
+ defaultRows: 8,
+ minColumns: 1,
+ minRows: 5,
});
});
}
diff --git a/client/app/visualizations/chart/plotly/index.js b/client/app/visualizations/chart/plotly/index.js
index c2e7172e42..4d0a91d4d0 100644
--- a/client/app/visualizations/chart/plotly/index.js
+++ b/client/app/visualizations/chart/plotly/index.js
@@ -8,7 +8,6 @@ import box from 'plotly.js/lib/box';
import heatmap from 'plotly.js/lib/heatmap';
import {
- ColorPalette,
prepareData,
prepareLayout,
updateData,
@@ -134,7 +133,6 @@ const CustomPlotlyChart = clientConfig => ({
});
export default function init(ngModule) {
- ngModule.constant('ColorPalette', ColorPalette);
ngModule.directive('plotlyChart', PlotlyChart);
ngModule.directive('customPlotlyChart', CustomPlotlyChart);
}
diff --git a/client/app/visualizations/chart/plotly/utils.js b/client/app/visualizations/chart/plotly/utils.js
index fdd7982178..ff73a3810f 100644
--- a/client/app/visualizations/chart/plotly/utils.js
+++ b/client/app/visualizations/chart/plotly/utils.js
@@ -6,36 +6,7 @@ import moment from 'moment';
import d3 from 'd3';
import plotlyCleanNumber from 'plotly.js/src/lib/clean_number';
import { createFormatter, formatSimpleTemplate } from '@/lib/value-format';
-
-// The following colors will be used if you pick "Automatic" color.
-const BaseColors = {
- Blue: '#356AFF',
- Red: '#E92828',
- Green: '#3BD973',
- Purple: '#604FE9',
- Cyan: '#50F5ED',
- Orange: '#FB8D3D',
- 'Light Blue': '#799CFF',
- Lilac: '#B554FF',
- 'Light Green': '#8CFFB4',
- Brown: '#A55F2A',
- Black: '#000000',
- Gray: '#494949',
- Pink: '#FF7DE3',
- 'Dark Blue': '#002FB4',
-};
-
-// Additional colors for the user to choose from:
-export const ColorPalette = Object.assign({}, BaseColors, {
- 'Indian Red': '#981717',
- 'Green 2': '#17BF51',
- 'Green 3': '#049235',
- DarkTurquoise: '#00B6EB',
- 'Dark Violet': '#A58AFF',
- 'Pink 2': '#C63FA9',
-});
-
-const ColorPaletteArray = values(BaseColors);
+import { ColorPaletteArray } from '@/visualizations/ColorPalette';
function cleanNumber(value) {
return isUndefined(value) ? value : (plotlyCleanNumber(value) || 0.0);
diff --git a/client/app/visualizations/choropleth/choropleth-editor.html b/client/app/visualizations/choropleth/choropleth-editor.html
index ed6886bfe3..e237b9402f 100644
--- a/client/app/visualizations/choropleth/choropleth-editor.html
+++ b/client/app/visualizations/choropleth/choropleth-editor.html
@@ -1,29 +1,29 @@
-
+
@@ -32,8 +32,8 @@
@@ -46,7 +46,7 @@
popover-trigger="'click outsideClick'">
+ ng-model="$ctrl.options.valueFormat" ng-model-options="{ allowInvalid: true, debounce: 200 }">
@@ -54,22 +54,22 @@
+ ng-model="$ctrl.options.noValuePlaceholder" ng-model-options="{ allowInvalid: true, debounce: 200 }">