mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Charts plugin (combining ui/color_maps and EuiUtils) (#55469)
* Combine ui/color_maps and EuiUtils into new Charts plugin * EuiUtils is now the theme service * ui/color_maps is now the colorMaps service * Fix all imports of each to pull from new Charts plugin * Add theme methods to both setup and start contracts * Move and jestify heatMapColors tests * Convert remaining js files to ts * Move vis/color to Charts plugin * Update missed visTypeVislib naming
This commit is contained in:
parent
22117abf49
commit
05ed2d63b5
107 changed files with 1454 additions and 953 deletions
|
@ -12,6 +12,7 @@
|
|||
"embeddableExamples": "examples/embeddable_examples",
|
||||
"share": "src/plugins/share",
|
||||
"home": "src/plugins/home",
|
||||
"charts": "src/plugins/charts",
|
||||
"esUi": "src/plugins/es_ui_shared",
|
||||
"devTools": "src/plugins/dev_tools",
|
||||
"expressions": "src/plugins/expressions",
|
||||
|
|
|
@ -32,10 +32,10 @@ import {
|
|||
} from 'src/plugins/data/public';
|
||||
import { createSavedSearchesLoader } from './saved_searches';
|
||||
import { DiscoverStartPlugins } from './plugin';
|
||||
import { EuiUtilsStart } from '../../../../../plugins/eui_utils/public';
|
||||
import { SharePluginStart } from '../../../../../plugins/share/public';
|
||||
import { SavedSearch } from './np_ready/types';
|
||||
import { DocViewsRegistry } from './np_ready/doc_views/doc_views_registry';
|
||||
import { ChartsPluginStart } from '../../../../../plugins/charts/public';
|
||||
|
||||
export interface DiscoverServices {
|
||||
addBasePath: (path: string) => string;
|
||||
|
@ -45,7 +45,7 @@ export interface DiscoverServices {
|
|||
data: DataPublicPluginStart;
|
||||
docLinks: DocLinksStart;
|
||||
docViewsRegistry: DocViewsRegistry;
|
||||
eui_utils: EuiUtilsStart;
|
||||
theme: ChartsPluginStart['theme'];
|
||||
filterManager: FilterManager;
|
||||
indexPatterns: IndexPatternsContract;
|
||||
inspector: unknown;
|
||||
|
@ -77,7 +77,7 @@ export async function buildServices(
|
|||
data: plugins.data,
|
||||
docLinks: core.docLinks,
|
||||
docViewsRegistry,
|
||||
eui_utils: plugins.eui_utils,
|
||||
theme: plugins.charts.theme,
|
||||
filterManager: plugins.data.query.filterManager,
|
||||
getSavedSearchById: async (id: string) => savedObjectService.get(id),
|
||||
getSavedSearchUrlById: async (id: string) => savedObjectService.urlFor(id),
|
||||
|
|
|
@ -114,13 +114,13 @@ export class DiscoverHistogram extends Component<DiscoverHistogramProps, Discove
|
|||
|
||||
private subscription?: Subscription;
|
||||
public state = {
|
||||
chartsTheme: getServices().eui_utils.getChartsThemeDefault(),
|
||||
chartsTheme: getServices().theme.chartsDefaultTheme,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.subscription = getServices()
|
||||
.eui_utils.getChartsTheme$()
|
||||
.subscribe((chartsTheme: EuiChartThemeType['theme']) => this.setState({ chartsTheme }));
|
||||
this.subscription = getServices().theme.chartsTheme$.subscribe(
|
||||
(chartsTheme: EuiChartThemeType['theme']) => this.setState({ chartsTheme })
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
|
|
@ -27,7 +27,7 @@ import { IEmbeddableStart, IEmbeddableSetup } from '../../../../../plugins/embed
|
|||
import { getInnerAngularModule, getInnerAngularModuleEmbeddable } from './get_inner_angular';
|
||||
import { setAngularModule, setServices } from './kibana_services';
|
||||
import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public';
|
||||
import { EuiUtilsStart } from '../../../../../plugins/eui_utils/public';
|
||||
import { ChartsPluginStart } from '../../../../../plugins/charts/public';
|
||||
import { buildServices } from './build_services';
|
||||
import { SharePluginStart } from '../../../../../plugins/share/public';
|
||||
import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public';
|
||||
|
@ -56,7 +56,7 @@ export interface DiscoverStartPlugins {
|
|||
uiActions: IUiActionsStart;
|
||||
embeddable: IEmbeddableStart;
|
||||
navigation: NavigationStart;
|
||||
eui_utils: EuiUtilsStart;
|
||||
charts: ChartsPluginStart;
|
||||
data: DataPublicPluginStart;
|
||||
share: SharePluginStart;
|
||||
inspector: any;
|
||||
|
|
|
@ -52,7 +52,6 @@ import './visualize/legacy';
|
|||
import './dashboard/legacy';
|
||||
import './management';
|
||||
import './dev_tools';
|
||||
import 'ui/color_maps';
|
||||
import 'ui/agg_response';
|
||||
import 'ui/agg_types';
|
||||
import { showAppRedirectNotification } from 'ui/notify';
|
||||
|
|
|
@ -23,11 +23,12 @@ import _ from 'lodash';
|
|||
import d3 from 'd3';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { KibanaMapLayer } from 'ui/vis/map/kibana_map_layer';
|
||||
import { truncatedColorMaps } from 'ui/color_maps';
|
||||
import * as topojson from 'topojson-client';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import * as colorUtil from 'ui/vis/map/color_util';
|
||||
|
||||
import { truncatedColorMaps } from '../../../../plugins/charts/public';
|
||||
|
||||
const EMPTY_STYLE = {
|
||||
weight: 1,
|
||||
opacity: 0.6,
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Schemas } from 'ui/vis/editors/default/schemas';
|
||||
import { truncatedColorSchemas as colorSchemas } from 'ui/color_maps';
|
||||
import { mapToLayerWithId } from './util';
|
||||
import { createRegionMapVisualization } from './region_map_visualization';
|
||||
import { Status } from '../../visualizations/public';
|
||||
import { RegionMapOptions } from './components/region_map_options';
|
||||
import { truncatedColorSchemas } from '../../../../plugins/charts/public';
|
||||
|
||||
// TODO: reference to TILE_MAP plugin should be removed
|
||||
import { ORIGIN } from '../../tile_map/common/origin';
|
||||
|
@ -60,7 +60,7 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
|||
editorConfig: {
|
||||
optionsTemplate: props => <RegionMapOptions {...props} serviceSettings={serviceSettings} />,
|
||||
collections: {
|
||||
colorSchemas,
|
||||
colorSchemas: truncatedColorSchemas,
|
||||
vectorLayers: [],
|
||||
tmsLayers: [],
|
||||
},
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import ChoroplethLayer from './choropleth_layer';
|
||||
import { truncatedColorMaps } from 'ui/color_maps';
|
||||
import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
|
||||
import { TileMapTooltipFormatter } from './tooltip_formatter';
|
||||
import { truncatedColorMaps } from '../../../../plugins/charts/public';
|
||||
|
||||
// TODO: reference to TILE_MAP plugin should be removed
|
||||
import { BaseMapsVisualizationProvider } from '../../tile_map/public/base_maps_visualization';
|
||||
|
|
|
@ -22,9 +22,10 @@ import _ from 'lodash';
|
|||
import d3 from 'd3';
|
||||
import $ from 'jquery';
|
||||
import { EventEmitter } from 'events';
|
||||
import { truncatedColorMaps } from 'ui/color_maps';
|
||||
import * as colorUtil from 'ui/vis/map/color_util';
|
||||
|
||||
import { truncatedColorMaps } from '../../../../../plugins/charts/public';
|
||||
|
||||
export class ScaledCirclesMarkers extends EventEmitter {
|
||||
constructor(
|
||||
featureCollection,
|
||||
|
@ -87,7 +88,7 @@ export class ScaledCirclesMarkers extends EventEmitter {
|
|||
|
||||
const quantizeDomain = min !== max ? [min, max] : d3.scale.quantize().domain();
|
||||
|
||||
this._legendColors = makeLegendColors(this._colorRamp);
|
||||
this._legendColors = this.getLegendColors();
|
||||
this._legendQuantizer = d3.scale
|
||||
.quantize()
|
||||
.domain(quantizeDomain)
|
||||
|
@ -222,11 +223,11 @@ export class ScaledCirclesMarkers extends EventEmitter {
|
|||
getBounds() {
|
||||
return this._leafletLayer.getBounds();
|
||||
}
|
||||
}
|
||||
|
||||
function makeLegendColors(colorRampKey) {
|
||||
const colorRamp = _.get(truncatedColorMaps[colorRampKey], 'value');
|
||||
return colorUtil.getLegendColors(colorRamp);
|
||||
getLegendColors() {
|
||||
const colorRamp = _.get(truncatedColorMaps[this._colorRamp], 'value');
|
||||
return colorUtil.getLegendColors(colorRamp);
|
||||
}
|
||||
}
|
||||
|
||||
function makeColorDarker(color) {
|
||||
|
|
|
@ -21,7 +21,6 @@ import React from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { Schemas } from 'ui/vis/editors/default/schemas';
|
||||
import { truncatedColorSchemas as colorSchemas } from 'ui/color_maps';
|
||||
import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson';
|
||||
|
||||
import { createTileMapVisualization } from './tile_map_visualization';
|
||||
|
@ -29,6 +28,7 @@ import { Status } from '../../visualizations/public';
|
|||
import { TileMapOptions } from './components/tile_map_options';
|
||||
import { MapTypes } from './map_types';
|
||||
import { supportsCssFilters } from './css_filters';
|
||||
import { truncatedColorSchemas } from '../../../../plugins/charts/public';
|
||||
|
||||
export function createTileMapTypeDefinition(dependencies) {
|
||||
const CoordinateMapsVisualization = createTileMapVisualization(dependencies);
|
||||
|
@ -63,7 +63,7 @@ export function createTileMapTypeDefinition(dependencies) {
|
|||
responseHandler: convertToGeoJson,
|
||||
editorConfig: {
|
||||
collections: {
|
||||
colorSchemas,
|
||||
colorSchemas: truncatedColorSchemas,
|
||||
legendPositions: [
|
||||
{
|
||||
value: 'bottomleft',
|
||||
|
|
|
@ -20,12 +20,12 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { MetricVisComponent } from './metric_vis_component';
|
||||
import { Vis } from '../legacy_imports';
|
||||
import { MetricVisComponent, MetricVisComponentProps } from './metric_vis_component';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
type Props = MetricVisComponent['props'];
|
||||
type Props = MetricVisComponentProps;
|
||||
|
||||
const baseVisData = {
|
||||
columns: [{ id: 'col-0', name: 'Count' }],
|
||||
|
|
|
@ -22,15 +22,16 @@ import React, { Component } from 'react';
|
|||
|
||||
import { isColorDark } from '@elastic/eui';
|
||||
|
||||
import { getHeatmapColors, getFormat, Vis } from '../legacy_imports';
|
||||
import { getFormat, Vis } from '../legacy_imports';
|
||||
import { MetricVisValue } from './metric_vis_value';
|
||||
import { fieldFormats } from '../../../../../plugins/data/public';
|
||||
import { Context } from '../metric_vis_fn';
|
||||
import { KibanaDatatable } from '../../../../../plugins/expressions/public';
|
||||
import { getHeatmapColors } from '../../../../../plugins/charts/public';
|
||||
import { VisParams, MetricVisMetric } from '../types';
|
||||
import { SchemaConfig } from '../../../visualizations/public';
|
||||
|
||||
interface MetricVisComponentProps {
|
||||
export interface MetricVisComponentProps {
|
||||
visParams: VisParams;
|
||||
visData: Context;
|
||||
vis: Vis;
|
||||
|
@ -50,7 +51,6 @@ export class MetricVisComponent extends Component<MetricVisComponentProps> {
|
|||
const to = isPercentageMode ? Math.round((100 * range.to) / max) : range.to;
|
||||
labels.push(`${from} - ${to}`);
|
||||
});
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import { plugin } from '.';
|
|||
const plugins: Readonly<MetricVisPluginSetupDependencies> = {
|
||||
expressions: npSetup.plugins.expressions,
|
||||
visualizations: visualizationsSetup,
|
||||
charts: npSetup.plugins.charts,
|
||||
};
|
||||
|
||||
const pluginInstance = plugin({} as PluginInitializerContext);
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
*/
|
||||
|
||||
export { Vis, VisParams } from 'ui/vis';
|
||||
export { vislibColorMaps, colorSchemas, ColorSchemas } from 'ui/color_maps';
|
||||
export { getHeatmapColors } from 'ui/color_maps';
|
||||
export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities';
|
||||
export { VisOptionsProps } from 'ui/vis/editors/default';
|
||||
// @ts-ignore
|
||||
|
|
|
@ -18,8 +18,7 @@
|
|||
*/
|
||||
|
||||
import { createMetricVisFn } from './metric_vis_fn';
|
||||
|
||||
// eslint-disable-next-line
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { vislibColorMaps, ColorSchemas } from './legacy_imports';
|
||||
import {
|
||||
ExpressionFunction,
|
||||
KibanaDatatable,
|
||||
|
@ -29,6 +28,7 @@ import {
|
|||
} from '../../../../plugins/expressions/public';
|
||||
import { ColorModes } from '../../vis_type_vislib/public';
|
||||
import { visType, DimensionsVisParam, VisParams } from './types';
|
||||
import { ColorSchemas, vislibColorMaps } from '../../../../plugins/charts/public';
|
||||
|
||||
export type Context = KibanaDatatable;
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import {
|
|||
setup as visualizationsSetup,
|
||||
start as visualizationsStart,
|
||||
} from '../../visualizations/public/np_ready/public/legacy';
|
||||
import { metricVisTypeDefinition } from './metric_vis_type';
|
||||
import { createMetricVisTypeDefinition } from './metric_vis_type';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
|
@ -37,7 +37,7 @@ describe('metric_vis - createMetricVisTypeDefinition', () => {
|
|||
let vis: Vis;
|
||||
|
||||
beforeAll(() => {
|
||||
visualizationsSetup.types.createReactVisualization(metricVisTypeDefinition);
|
||||
visualizationsSetup.types.createReactVisualization(createMetricVisTypeDefinition());
|
||||
(npStart.plugins.data.fieldFormats.getType as jest.Mock).mockImplementation(() => {
|
||||
return fieldFormats.UrlFormat;
|
||||
});
|
||||
|
|
|
@ -22,9 +22,10 @@ import { i18n } from '@kbn/i18n';
|
|||
import { MetricVisComponent } from './components/metric_vis_component';
|
||||
import { MetricVisOptions } from './components/metric_vis_options';
|
||||
import { ColorModes } from '../../vis_type_vislib/public';
|
||||
import { Schemas, AggGroupNames, colorSchemas, ColorSchemas } from './legacy_imports';
|
||||
import { Schemas, AggGroupNames } from './legacy_imports';
|
||||
import { ColorSchemas, colorSchemas } from '../../../../plugins/charts/public';
|
||||
|
||||
export const metricVisTypeDefinition = {
|
||||
export const createMetricVisTypeDefinition = () => ({
|
||||
name: 'metric',
|
||||
title: i18n.translate('visTypeMetric.metricTitle', { defaultMessage: 'Metric' }),
|
||||
icon: 'visMetric',
|
||||
|
@ -121,4 +122,4 @@ export const metricVisTypeDefinition = {
|
|||
},
|
||||
]),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -22,12 +22,14 @@ import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressio
|
|||
import { VisualizationsSetup } from '../../visualizations/public';
|
||||
|
||||
import { createMetricVisFn } from './metric_vis_fn';
|
||||
import { metricVisTypeDefinition } from './metric_vis_type';
|
||||
import { createMetricVisTypeDefinition } from './metric_vis_type';
|
||||
import { ChartsPluginSetup } from '../../../../plugins/charts/public';
|
||||
|
||||
/** @internal */
|
||||
export interface MetricVisPluginSetupDependencies {
|
||||
expressions: ReturnType<ExpressionsPublicPlugin['setup']>;
|
||||
visualizations: VisualizationsSetup;
|
||||
charts: ChartsPluginSetup;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -38,9 +40,12 @@ export class MetricVisPlugin implements Plugin<void, void> {
|
|||
this.initializerContext = initializerContext;
|
||||
}
|
||||
|
||||
public setup(core: CoreSetup, { expressions, visualizations }: MetricVisPluginSetupDependencies) {
|
||||
public setup(
|
||||
core: CoreSetup,
|
||||
{ expressions, visualizations, charts }: MetricVisPluginSetupDependencies
|
||||
) {
|
||||
expressions.registerFunction(createMetricVisFn);
|
||||
visualizations.types.createReactVisualization(metricVisTypeDefinition);
|
||||
visualizations.types.createReactVisualization(createMetricVisTypeDefinition());
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { ColorSchemas } from './legacy_imports';
|
||||
import { Range } from '../../../../plugins/expressions/public';
|
||||
import { SchemaConfig } from '../../visualizations/public';
|
||||
import { ColorModes, Labels, Style } from '../../vis_type_vislib/public';
|
||||
import { ColorSchemas } from '../../../../plugins/charts/public';
|
||||
|
||||
export const visType = 'metric';
|
||||
|
||||
|
|
|
@ -26,6 +26,14 @@ import { fromNode, delay } from 'bluebird';
|
|||
import { ImageComparator } from 'test_utils/image_comparator';
|
||||
import simpleloadPng from './simpleload.png';
|
||||
|
||||
// Replace with mock when converting to jest tests
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { seedColors } from '../../../../../../plugins/charts/public/services/colors/seed_colors';
|
||||
|
||||
const colors = {
|
||||
seedColors,
|
||||
};
|
||||
|
||||
describe('tag cloud tests', function() {
|
||||
const minValue = 1;
|
||||
const maxValue = 9;
|
||||
|
@ -124,7 +132,7 @@ describe('tag cloud tests', function() {
|
|||
)}`, function() {
|
||||
beforeEach(async function() {
|
||||
setupDOM();
|
||||
tagCloud = new TagCloud(domNode);
|
||||
tagCloud = new TagCloud(domNode, colors);
|
||||
tagCloud.setData(test.data);
|
||||
tagCloud.setOptions(test.options);
|
||||
await fromNode(cb => tagCloud.once('renderComplete', cb));
|
||||
|
@ -156,7 +164,7 @@ describe('tag cloud tests', function() {
|
|||
|
||||
//TagCloud takes at least 600ms to complete (due to d3 animation)
|
||||
//renderComplete should only notify at the last one
|
||||
tagCloud = new TagCloud(domNode);
|
||||
tagCloud = new TagCloud(domNode, colors);
|
||||
tagCloud.setData(baseTest.data);
|
||||
tagCloud.setOptions(baseTest.options);
|
||||
|
||||
|
@ -188,7 +196,7 @@ describe('tag cloud tests', function() {
|
|||
describe('should use the latest state before notifying (when modifying options multiple times)', function() {
|
||||
beforeEach(async function() {
|
||||
setupDOM();
|
||||
tagCloud = new TagCloud(domNode);
|
||||
tagCloud = new TagCloud(domNode, colors);
|
||||
tagCloud.setData(baseTest.data);
|
||||
tagCloud.setOptions(baseTest.options);
|
||||
tagCloud.setOptions(logScaleTest.options);
|
||||
|
@ -215,7 +223,7 @@ describe('tag cloud tests', function() {
|
|||
describe('should use the latest state before notifying (when modifying data multiple times)', function() {
|
||||
beforeEach(async function() {
|
||||
setupDOM();
|
||||
tagCloud = new TagCloud(domNode);
|
||||
tagCloud = new TagCloud(domNode, colors);
|
||||
tagCloud.setData(baseTest.data);
|
||||
tagCloud.setOptions(baseTest.options);
|
||||
tagCloud.setData(trimDataTest.data);
|
||||
|
@ -245,7 +253,7 @@ describe('tag cloud tests', function() {
|
|||
counter = 0;
|
||||
setupDOM();
|
||||
return new Promise((resolve, reject) => {
|
||||
tagCloud = new TagCloud(domNode);
|
||||
tagCloud = new TagCloud(domNode, colors);
|
||||
tagCloud.setData(baseTest.data);
|
||||
tagCloud.setOptions(baseTest.options);
|
||||
|
||||
|
@ -291,7 +299,7 @@ describe('tag cloud tests', function() {
|
|||
describe('should show correct data when state-updates are interleaved with resize event', function() {
|
||||
beforeEach(async function() {
|
||||
setupDOM();
|
||||
tagCloud = new TagCloud(domNode);
|
||||
tagCloud = new TagCloud(domNode, colors);
|
||||
tagCloud.setData(logScaleTest.data);
|
||||
tagCloud.setOptions(logScaleTest.options);
|
||||
|
||||
|
@ -329,7 +337,7 @@ describe('tag cloud tests', function() {
|
|||
setupDOM();
|
||||
domNode.style.width = '1px';
|
||||
domNode.style.height = '1px';
|
||||
tagCloud = new TagCloud(domNode);
|
||||
tagCloud = new TagCloud(domNode, colors);
|
||||
tagCloud.setData(baseTest.data);
|
||||
tagCloud.setOptions(baseTest.options);
|
||||
await fromNode(cb => tagCloud.once('renderComplete', cb));
|
||||
|
@ -355,7 +363,7 @@ describe('tag cloud tests', function() {
|
|||
domNode.style.width = '1px';
|
||||
domNode.style.height = '1px';
|
||||
|
||||
tagCloud = new TagCloud(domNode);
|
||||
tagCloud = new TagCloud(domNode, colors);
|
||||
tagCloud.setData(baseTest.data);
|
||||
tagCloud.setOptions(baseTest.options);
|
||||
await fromNode(cb => tagCloud.once('renderComplete', cb));
|
||||
|
@ -380,7 +388,7 @@ describe('tag cloud tests', function() {
|
|||
describe(`tags should no longer fit after making container smaller`, function() {
|
||||
beforeEach(async function() {
|
||||
setupDOM();
|
||||
tagCloud = new TagCloud(domNode);
|
||||
tagCloud = new TagCloud(domNode, colors);
|
||||
tagCloud.setData(baseTest.data);
|
||||
tagCloud.setOptions(baseTest.options);
|
||||
await fromNode(cb => tagCloud.once('renderComplete', cb));
|
||||
|
@ -412,7 +420,7 @@ describe('tag cloud tests', function() {
|
|||
});
|
||||
|
||||
it('should render simple image', async function() {
|
||||
tagCloud = new TagCloud(domNode);
|
||||
tagCloud = new TagCloud(domNode, colors);
|
||||
tagCloud.setData(baseTest.data);
|
||||
tagCloud.setOptions(baseTest.options);
|
||||
|
||||
|
|
|
@ -22,11 +22,15 @@ import ngMock from 'ng_mock';
|
|||
import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||
import { Vis } from 'ui/vis';
|
||||
import { ImageComparator } from 'test_utils/image_comparator';
|
||||
import { TagCloudVisualization } from '../tag_cloud_visualization';
|
||||
import { createTagCloudVisualization } from '../tag_cloud_visualization';
|
||||
import basicdrawPng from './basicdraw.png';
|
||||
import afterresizePng from './afterresize.png';
|
||||
import afterparamChange from './afterparamchange.png';
|
||||
|
||||
// Replace with mock when converting to jest tests
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { seedColors } from '../../../../../../plugins/charts/public/services/colors/seed_colors';
|
||||
|
||||
const THRESHOLD = 0.65;
|
||||
const PIXEL_DIFF = 64;
|
||||
|
||||
|
@ -55,6 +59,11 @@ describe('TagCloudVisualizationTest', function() {
|
|||
{ 'col-0': 'BR', 'col-1': 3 },
|
||||
],
|
||||
};
|
||||
const TagCloudVisualization = createTagCloudVisualization({
|
||||
colors: {
|
||||
seedColors,
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
import d3 from 'd3';
|
||||
import d3TagCloud from 'd3-cloud';
|
||||
import { seedColors } from 'ui/vis/components/color/seed_colors';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
const ORIENTATIONS = {
|
||||
|
@ -38,7 +37,7 @@ const D3_SCALING_FUNCTIONS = {
|
|||
};
|
||||
|
||||
export class TagCloud extends EventEmitter {
|
||||
constructor(domNode) {
|
||||
constructor(domNode, colors) {
|
||||
super();
|
||||
|
||||
//DOM
|
||||
|
@ -55,6 +54,7 @@ export class TagCloud extends EventEmitter {
|
|||
this._spiral = 'archimedean'; //layout shape
|
||||
this._timeInterval = 1000; //time allowed for layout algorithm
|
||||
this._padding = 5;
|
||||
this._seedColors = colors.seedColors;
|
||||
|
||||
//OPTIONS
|
||||
this._orientation = 'single';
|
||||
|
@ -208,7 +208,7 @@ export class TagCloud extends EventEmitter {
|
|||
enteringTags.style('font-style', this._fontStyle);
|
||||
enteringTags.style('font-weight', () => this._fontWeight);
|
||||
enteringTags.style('font-family', () => this._fontFamily);
|
||||
enteringTags.style('fill', getFill);
|
||||
enteringTags.style('fill', this.getFill.bind(this));
|
||||
enteringTags.attr('text-anchor', () => 'middle');
|
||||
enteringTags.attr('transform', affineTransform);
|
||||
enteringTags.attr('data-test-subj', getDisplayText);
|
||||
|
@ -369,6 +369,11 @@ export class TagCloud extends EventEmitter {
|
|||
};
|
||||
return debug;
|
||||
}
|
||||
|
||||
getFill(tag) {
|
||||
const colorScale = d3.scale.ordinal().range(this._seedColors);
|
||||
return colorScale(tag.text);
|
||||
}
|
||||
}
|
||||
|
||||
TagCloud.STATUS = { COMPLETE: 0, INCOMPLETE: 1 };
|
||||
|
@ -402,11 +407,6 @@ function getSizeInPixels(tag) {
|
|||
return `${tag.size}px`;
|
||||
}
|
||||
|
||||
const colorScale = d3.scale.ordinal().range(seedColors);
|
||||
function getFill(tag) {
|
||||
return colorScale(tag.text);
|
||||
}
|
||||
|
||||
function hashWithinRange(str, max) {
|
||||
str = JSON.stringify(str);
|
||||
let hash = 0;
|
||||
|
|
|
@ -31,130 +31,132 @@ import { FeedbackMessage } from './feedback_message';
|
|||
|
||||
const MAX_TAG_COUNT = 200;
|
||||
|
||||
export class TagCloudVisualization {
|
||||
constructor(node, vis) {
|
||||
this._containerNode = node;
|
||||
export function createTagCloudVisualization({ colors }) {
|
||||
return class TagCloudVisualization {
|
||||
constructor(node, vis) {
|
||||
this._containerNode = node;
|
||||
|
||||
const cloudRelativeContainer = document.createElement('div');
|
||||
cloudRelativeContainer.classList.add('tgcVis');
|
||||
cloudRelativeContainer.setAttribute('style', 'position: relative');
|
||||
const cloudContainer = document.createElement('div');
|
||||
cloudContainer.classList.add('tgcVis');
|
||||
cloudContainer.setAttribute('data-test-subj', 'tagCloudVisualization');
|
||||
this._containerNode.classList.add('visChart--vertical');
|
||||
cloudRelativeContainer.appendChild(cloudContainer);
|
||||
this._containerNode.appendChild(cloudRelativeContainer);
|
||||
const cloudRelativeContainer = document.createElement('div');
|
||||
cloudRelativeContainer.classList.add('tgcVis');
|
||||
cloudRelativeContainer.setAttribute('style', 'position: relative');
|
||||
const cloudContainer = document.createElement('div');
|
||||
cloudContainer.classList.add('tgcVis');
|
||||
cloudContainer.setAttribute('data-test-subj', 'tagCloudVisualization');
|
||||
this._containerNode.classList.add('visChart--vertical');
|
||||
cloudRelativeContainer.appendChild(cloudContainer);
|
||||
this._containerNode.appendChild(cloudRelativeContainer);
|
||||
|
||||
this._vis = vis;
|
||||
this._truncated = false;
|
||||
this._tagCloud = new TagCloud(cloudContainer);
|
||||
this._tagCloud.on('select', event => {
|
||||
if (!this._visParams.bucket) {
|
||||
this._vis = vis;
|
||||
this._truncated = false;
|
||||
this._tagCloud = new TagCloud(cloudContainer, colors);
|
||||
this._tagCloud.on('select', event => {
|
||||
if (!this._visParams.bucket) {
|
||||
return;
|
||||
}
|
||||
this._vis.API.events.filter({
|
||||
table: event.meta.data,
|
||||
column: 0,
|
||||
row: event.meta.rowIndex,
|
||||
});
|
||||
});
|
||||
this._renderComplete$ = Rx.fromEvent(this._tagCloud, 'renderComplete');
|
||||
|
||||
this._feedbackNode = document.createElement('div');
|
||||
this._containerNode.appendChild(this._feedbackNode);
|
||||
this._feedbackMessage = React.createRef();
|
||||
render(
|
||||
<I18nContext>
|
||||
<FeedbackMessage ref={this._feedbackMessage} />
|
||||
</I18nContext>,
|
||||
this._feedbackNode
|
||||
);
|
||||
|
||||
this._labelNode = document.createElement('div');
|
||||
this._containerNode.appendChild(this._labelNode);
|
||||
this._label = React.createRef();
|
||||
render(<Label ref={this._label} />, this._labelNode);
|
||||
}
|
||||
|
||||
async render(data, visParams, status) {
|
||||
if (!(status.resize || status.data || status.params)) return;
|
||||
|
||||
if (status.params || status.data) {
|
||||
this._updateParams(visParams);
|
||||
this._updateData(data);
|
||||
}
|
||||
|
||||
if (status.resize) {
|
||||
this._resize();
|
||||
}
|
||||
|
||||
await this._renderComplete$.pipe(take(1)).toPromise();
|
||||
|
||||
if (data.columns.length !== 2) {
|
||||
this._feedbackMessage.current.setState({
|
||||
shouldShowTruncate: false,
|
||||
shouldShowIncomplete: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this._vis.API.events.filter({
|
||||
table: event.meta.data,
|
||||
column: 0,
|
||||
row: event.meta.rowIndex,
|
||||
|
||||
this._label.current.setState({
|
||||
label: `${data.columns[0].name} - ${data.columns[1].name}`,
|
||||
shouldShowLabel: visParams.showLabel,
|
||||
});
|
||||
});
|
||||
this._renderComplete$ = Rx.fromEvent(this._tagCloud, 'renderComplete');
|
||||
|
||||
this._feedbackNode = document.createElement('div');
|
||||
this._containerNode.appendChild(this._feedbackNode);
|
||||
this._feedbackMessage = React.createRef();
|
||||
render(
|
||||
<I18nContext>
|
||||
<FeedbackMessage ref={this._feedbackMessage} />
|
||||
</I18nContext>,
|
||||
this._feedbackNode
|
||||
);
|
||||
|
||||
this._labelNode = document.createElement('div');
|
||||
this._containerNode.appendChild(this._labelNode);
|
||||
this._label = React.createRef();
|
||||
render(<Label ref={this._label} />, this._labelNode);
|
||||
}
|
||||
|
||||
async render(data, visParams, status) {
|
||||
if (!(status.resize || status.data || status.params)) return;
|
||||
|
||||
if (status.params || status.data) {
|
||||
this._updateParams(visParams);
|
||||
this._updateData(data);
|
||||
}
|
||||
|
||||
if (status.resize) {
|
||||
this._resize();
|
||||
}
|
||||
|
||||
await this._renderComplete$.pipe(take(1)).toPromise();
|
||||
|
||||
if (data.columns.length !== 2) {
|
||||
this._feedbackMessage.current.setState({
|
||||
shouldShowTruncate: false,
|
||||
shouldShowIncomplete: false,
|
||||
shouldShowTruncate: this._truncated,
|
||||
shouldShowIncomplete: this._tagCloud.getStatus() === TagCloud.STATUS.INCOMPLETE,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._label.current.setState({
|
||||
label: `${data.columns[0].name} - ${data.columns[1].name}`,
|
||||
shouldShowLabel: visParams.showLabel,
|
||||
});
|
||||
this._feedbackMessage.current.setState({
|
||||
shouldShowTruncate: this._truncated,
|
||||
shouldShowIncomplete: this._tagCloud.getStatus() === TagCloud.STATUS.INCOMPLETE,
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._tagCloud.destroy();
|
||||
unmountComponentAtNode(this._feedbackNode);
|
||||
unmountComponentAtNode(this._labelNode);
|
||||
}
|
||||
|
||||
_updateData(data) {
|
||||
if (!data || !data.rows.length) {
|
||||
this._tagCloud.setData([]);
|
||||
return;
|
||||
destroy() {
|
||||
this._tagCloud.destroy();
|
||||
unmountComponentAtNode(this._feedbackNode);
|
||||
unmountComponentAtNode(this._labelNode);
|
||||
}
|
||||
|
||||
const bucket = this._visParams.bucket;
|
||||
const metric = this._visParams.metric;
|
||||
const bucketFormatter = bucket ? getFormat(bucket.format) : null;
|
||||
const tagColumn = bucket ? data.columns[bucket.accessor].id : -1;
|
||||
const metricColumn = data.columns[metric.accessor].id;
|
||||
const tags = data.rows.map((row, rowIndex) => {
|
||||
const tag = row[tagColumn] === undefined ? 'all' : row[tagColumn];
|
||||
const metric = row[metricColumn];
|
||||
return {
|
||||
displayText: bucketFormatter ? bucketFormatter.convert(tag, 'text') : tag,
|
||||
rawText: tag,
|
||||
value: metric,
|
||||
meta: {
|
||||
data: data,
|
||||
rowIndex: rowIndex,
|
||||
},
|
||||
};
|
||||
});
|
||||
_updateData(data) {
|
||||
if (!data || !data.rows.length) {
|
||||
this._tagCloud.setData([]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tags.length > MAX_TAG_COUNT) {
|
||||
tags.length = MAX_TAG_COUNT;
|
||||
this._truncated = true;
|
||||
} else {
|
||||
this._truncated = false;
|
||||
const bucket = this._visParams.bucket;
|
||||
const metric = this._visParams.metric;
|
||||
const bucketFormatter = bucket ? getFormat(bucket.format) : null;
|
||||
const tagColumn = bucket ? data.columns[bucket.accessor].id : -1;
|
||||
const metricColumn = data.columns[metric.accessor].id;
|
||||
const tags = data.rows.map((row, rowIndex) => {
|
||||
const tag = row[tagColumn] === undefined ? 'all' : row[tagColumn];
|
||||
const metric = row[metricColumn];
|
||||
return {
|
||||
displayText: bucketFormatter ? bucketFormatter.convert(tag, 'text') : tag,
|
||||
rawText: tag,
|
||||
value: metric,
|
||||
meta: {
|
||||
data: data,
|
||||
rowIndex: rowIndex,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
if (tags.length > MAX_TAG_COUNT) {
|
||||
tags.length = MAX_TAG_COUNT;
|
||||
this._truncated = true;
|
||||
} else {
|
||||
this._truncated = false;
|
||||
}
|
||||
|
||||
this._tagCloud.setData(tags);
|
||||
}
|
||||
|
||||
this._tagCloud.setData(tags);
|
||||
}
|
||||
_updateParams(visParams) {
|
||||
this._visParams = visParams;
|
||||
this._tagCloud.setOptions(visParams);
|
||||
}
|
||||
|
||||
_updateParams(visParams) {
|
||||
this._visParams = visParams;
|
||||
this._tagCloud.setOptions(visParams);
|
||||
}
|
||||
|
||||
_resize() {
|
||||
this._tagCloud.resize();
|
||||
}
|
||||
_resize() {
|
||||
this._tagCloud.resize();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import { plugin } from '.';
|
|||
const plugins: Readonly<TagCloudPluginSetupDependencies> = {
|
||||
expressions: npSetup.plugins.expressions,
|
||||
visualizations: visualizationsSetup,
|
||||
charts: npSetup.plugins.charts,
|
||||
};
|
||||
|
||||
const pluginInstance = plugin({} as PluginInitializerContext);
|
||||
|
|
|
@ -20,14 +20,21 @@
|
|||
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public';
|
||||
import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public';
|
||||
import { VisualizationsSetup } from '../../visualizations/public';
|
||||
import { ChartsPluginSetup } from '../../../../plugins/charts/public';
|
||||
|
||||
import { createTagCloudFn } from './tag_cloud_fn';
|
||||
import { tagcloudVisDefinition } from './tag_cloud_type';
|
||||
import { createTagCloudVisTypeDefinition } from './tag_cloud_type';
|
||||
|
||||
/** @internal */
|
||||
export interface TagCloudPluginSetupDependencies {
|
||||
expressions: ReturnType<ExpressionsPublicPlugin['setup']>;
|
||||
visualizations: VisualizationsSetup;
|
||||
charts: ChartsPluginSetup;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface TagCloudVisDependencies {
|
||||
colors: ChartsPluginSetup['colors'];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -38,9 +45,17 @@ export class TagCloudPlugin implements Plugin<void, void> {
|
|||
this.initializerContext = initializerContext;
|
||||
}
|
||||
|
||||
public setup(core: CoreSetup, { expressions, visualizations }: TagCloudPluginSetupDependencies) {
|
||||
public setup(
|
||||
core: CoreSetup,
|
||||
{ expressions, visualizations, charts }: TagCloudPluginSetupDependencies
|
||||
) {
|
||||
const visualizationDependencies: TagCloudVisDependencies = {
|
||||
colors: charts.colors,
|
||||
};
|
||||
expressions.registerFunction(createTagCloudFn);
|
||||
visualizations.types.createBaseVisualization(tagcloudVisDefinition);
|
||||
visualizations.types.createBaseVisualization(
|
||||
createTagCloudVisTypeDefinition(visualizationDependencies)
|
||||
);
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {
|
||||
|
|
|
@ -25,9 +25,10 @@ import { Status } from '../../visualizations/public';
|
|||
import { TagCloudOptions } from './components/tag_cloud_options';
|
||||
|
||||
// @ts-ignore
|
||||
import { TagCloudVisualization } from './components/tag_cloud_visualization';
|
||||
import { createTagCloudVisualization } from './components/tag_cloud_visualization';
|
||||
import { TagCloudVisDependencies } from './plugin';
|
||||
|
||||
export const tagcloudVisDefinition = {
|
||||
export const createTagCloudVisTypeDefinition = (deps: TagCloudVisDependencies) => ({
|
||||
name: 'tagcloud',
|
||||
title: i18n.translate('visTypeTagCloud.vis.tagCloudTitle', { defaultMessage: 'Tag Cloud' }),
|
||||
icon: 'visTagCloud',
|
||||
|
@ -44,7 +45,7 @@ export const tagcloudVisDefinition = {
|
|||
},
|
||||
},
|
||||
requiresUpdateStatus: [Status.PARAMS, Status.RESIZE, Status.DATA],
|
||||
visualization: TagCloudVisualization,
|
||||
visualization: createTagCloudVisualization(deps),
|
||||
editorConfig: {
|
||||
collections: {
|
||||
scales: [
|
||||
|
@ -121,4 +122,4 @@ export const tagcloudVisDefinition = {
|
|||
]),
|
||||
},
|
||||
useCustomNoDataScreen: true,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ import { Legacy } from 'kibana';
|
|||
|
||||
import { LegacyPluginApi, LegacyPluginInitializer } from '../../types';
|
||||
|
||||
const kbnVislibVisTypesPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) =>
|
||||
const visTypeVislibPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) =>
|
||||
new Plugin({
|
||||
id: 'vis_type_vislib',
|
||||
require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter', 'data'],
|
||||
|
@ -41,4 +41,4 @@ const kbnVislibVisTypesPluginInitializer: LegacyPluginInitializer = ({ Plugin }:
|
|||
} as Legacy.PluginSpecOptions);
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default kbnVislibVisTypesPluginInitializer;
|
||||
export default visTypeVislibPluginInitializer;
|
||||
|
|
|
@ -38,9 +38,9 @@ import {
|
|||
} from './utils/collections';
|
||||
import { getAreaOptionTabs, countLabel } from './utils/common_config';
|
||||
import { createVislibVisController } from './vis_controller';
|
||||
import { KbnVislibVisTypesDependencies } from './plugin';
|
||||
import { VisTypeVislibDependencies } from './plugin';
|
||||
|
||||
export const createAreaVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({
|
||||
export const createAreaVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
|
||||
name: 'area',
|
||||
title: i18n.translate('visTypeVislib.area.areaTitle', { defaultMessage: 'Area' }),
|
||||
icon: 'visArea',
|
||||
|
|
|
@ -22,10 +22,11 @@ import { i18n } from '@kbn/i18n';
|
|||
import { EuiLink, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { VisOptionsProps, ColorSchema } from '../../legacy_imports';
|
||||
import { VisOptionsProps } from '../../legacy_imports';
|
||||
import { SelectOption } from './select';
|
||||
import { SwitchOption } from './switch';
|
||||
import { ColorSchemaVislibParams } from '../../types';
|
||||
import { ColorSchema } from '../../../../../../plugins/charts/public';
|
||||
|
||||
export type SetColorSchemaOptionsValue = <T extends keyof ColorSchemaVislibParams>(
|
||||
paramName: T,
|
||||
|
|
|
@ -22,12 +22,12 @@ import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { ColorSchemas } from '../../../legacy_imports';
|
||||
import { ColorRanges, ColorSchemaOptions, SwitchOption } from '../../common';
|
||||
import { GaugeOptionsInternalProps } from '.';
|
||||
import { ColorSchemaVislibParams } from '../../../types';
|
||||
import { Gauge } from '../../../gauge';
|
||||
import { SetColorRangeValue } from '../../common/color_ranges';
|
||||
import { ColorSchemas } from '../../../../../../../plugins/charts/public';
|
||||
|
||||
function RangesPanel({
|
||||
setGaugeValue,
|
||||
|
|
|
@ -19,12 +19,13 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { Schemas, AggGroupNames, ColorSchemas, RangeValues } from './legacy_imports';
|
||||
import { Schemas, AggGroupNames, RangeValues } from './legacy_imports';
|
||||
import { GaugeOptions } from './components/options';
|
||||
import { getGaugeCollections, Alignments, ColorModes, GaugeTypes } from './utils/collections';
|
||||
import { createVislibVisController } from './vis_controller';
|
||||
import { ColorSchemaVislibParams, Labels, Style } from './types';
|
||||
import { KbnVislibVisTypesDependencies } from './plugin';
|
||||
import { VisTypeVislibDependencies } from './plugin';
|
||||
import { ColorSchemas } from '../../../../plugins/charts/public';
|
||||
|
||||
export interface Gauge extends ColorSchemaVislibParams {
|
||||
backStyle: 'Full';
|
||||
|
@ -54,7 +55,7 @@ export interface GaugeVisParams {
|
|||
gauge: Gauge;
|
||||
}
|
||||
|
||||
export const createGaugeVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({
|
||||
export const createGaugeVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
|
||||
name: 'gauge',
|
||||
title: i18n.translate('visTypeVislib.gauge.gaugeTitle', { defaultMessage: 'Gauge' }),
|
||||
icon: 'visGauge',
|
||||
|
|
|
@ -19,13 +19,14 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { Schemas, AggGroupNames, ColorSchemas } from './legacy_imports';
|
||||
import { Schemas, AggGroupNames } from './legacy_imports';
|
||||
import { GaugeOptions } from './components/options';
|
||||
import { getGaugeCollections, GaugeTypes, ColorModes } from './utils/collections';
|
||||
import { createVislibVisController } from './vis_controller';
|
||||
import { KbnVislibVisTypesDependencies } from './plugin';
|
||||
import { VisTypeVislibDependencies } from './plugin';
|
||||
import { ColorSchemas } from '../../../../plugins/charts/public';
|
||||
|
||||
export const createGoalVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({
|
||||
export const createGoalVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
|
||||
name: 'goal',
|
||||
title: i18n.translate('visTypeVislib.goal.goalTitle', { defaultMessage: 'Goal' }),
|
||||
icon: 'visGoal',
|
||||
|
|
|
@ -19,13 +19,14 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { Schemas, AggGroupNames, ColorSchemas, RangeValues } from './legacy_imports';
|
||||
import { Schemas, AggGroupNames, RangeValues } from './legacy_imports';
|
||||
import { AxisTypes, getHeatmapCollections, Positions, ScaleTypes } from './utils/collections';
|
||||
import { HeatmapOptions } from './components/options';
|
||||
import { createVislibVisController } from './vis_controller';
|
||||
import { TimeMarker } from './vislib/visualizations/time_marker';
|
||||
import { CommonVislibParams, ColorSchemaVislibParams, ValueAxis } from './types';
|
||||
import { KbnVislibVisTypesDependencies } from './plugin';
|
||||
import { VisTypeVislibDependencies } from './plugin';
|
||||
import { ColorSchemas } from '../../../../plugins/charts/public';
|
||||
|
||||
export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaVislibParams {
|
||||
type: 'heatmap';
|
||||
|
@ -39,7 +40,7 @@ export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaVislibP
|
|||
times: TimeMarker[];
|
||||
}
|
||||
|
||||
export const createHeatmapVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({
|
||||
export const createHeatmapVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
|
||||
name: 'heatmap',
|
||||
title: i18n.translate('visTypeVislib.heatmap.heatmapTitle', { defaultMessage: 'Heat Map' }),
|
||||
icon: 'visHeatmap',
|
||||
|
|
|
@ -38,9 +38,9 @@ import {
|
|||
} from './utils/collections';
|
||||
import { getAreaOptionTabs, countLabel } from './utils/common_config';
|
||||
import { createVislibVisController } from './vis_controller';
|
||||
import { KbnVislibVisTypesDependencies } from './plugin';
|
||||
import { VisTypeVislibDependencies } from './plugin';
|
||||
|
||||
export const createHistogramVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({
|
||||
export const createHistogramVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
|
||||
name: 'histogram',
|
||||
title: i18n.translate('visTypeVislib.histogram.histogramTitle', {
|
||||
defaultMessage: 'Vertical Bar',
|
||||
|
|
|
@ -38,9 +38,9 @@ import {
|
|||
} from './utils/collections';
|
||||
import { getAreaOptionTabs, countLabel } from './utils/common_config';
|
||||
import { createVislibVisController } from './vis_controller';
|
||||
import { KbnVislibVisTypesDependencies } from './plugin';
|
||||
import { VisTypeVislibDependencies } from './plugin';
|
||||
|
||||
export const createHorizontalBarVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({
|
||||
export const createHorizontalBarVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
|
||||
name: 'horizontal_bar',
|
||||
title: i18n.translate('visTypeVislib.horizontalBar.horizontalBarTitle', {
|
||||
defaultMessage: 'Horizontal Bar',
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { PluginInitializerContext } from '../../../../core/public';
|
||||
import { KbnVislibVisTypesPlugin as Plugin } from './plugin';
|
||||
import { VisTypeVislibPlugin as Plugin } from './plugin';
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new Plugin(initializerContext);
|
||||
|
|
|
@ -20,28 +20,23 @@
|
|||
import { npSetup, npStart } from 'ui/new_platform';
|
||||
import { PluginInitializerContext } from 'kibana/public';
|
||||
|
||||
// @ts-ignore
|
||||
import { vislibColor } from 'ui/vis/components/color/color';
|
||||
|
||||
import { plugin } from '.';
|
||||
import {
|
||||
KbnVislibVisTypesPluginSetupDependencies,
|
||||
KbnVislibVisTypesPluginStartDependencies,
|
||||
VisTypeVislibPluginSetupDependencies,
|
||||
VisTypeVislibPluginStartDependencies,
|
||||
} from './plugin';
|
||||
import {
|
||||
setup as visualizationsSetup,
|
||||
start as visualizationsStart,
|
||||
} from '../../visualizations/public/np_ready/public/legacy';
|
||||
|
||||
const setupPlugins: Readonly<KbnVislibVisTypesPluginSetupDependencies> = {
|
||||
const setupPlugins: Readonly<VisTypeVislibPluginSetupDependencies> = {
|
||||
expressions: npSetup.plugins.expressions,
|
||||
visualizations: visualizationsSetup,
|
||||
__LEGACY: {
|
||||
vislibColor,
|
||||
},
|
||||
charts: npSetup.plugins.charts,
|
||||
};
|
||||
|
||||
const startPlugins: Readonly<KbnVislibVisTypesPluginStartDependencies> = {
|
||||
const startPlugins: Readonly<VisTypeVislibPluginStartDependencies> = {
|
||||
expressions: npStart.plugins.expressions,
|
||||
visualizations: visualizationsStart,
|
||||
};
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
export { AggGroupNames, VisOptionsProps } from 'ui/vis/editors/default';
|
||||
export { Schemas } from 'ui/vis/editors/default/schemas';
|
||||
export { RangeValues, RangesParamEditor } from 'ui/vis/editors/default/controls/ranges';
|
||||
export { ColorSchema, ColorSchemas, colorSchemas, getHeatmapColors } from 'ui/color_maps';
|
||||
export { AggConfig, Vis, VisParams } from 'ui/vis';
|
||||
export { AggType } from 'ui/agg_types';
|
||||
// @ts-ignore
|
||||
|
|
|
@ -38,9 +38,9 @@ import {
|
|||
} from './utils/collections';
|
||||
import { getAreaOptionTabs, countLabel } from './utils/common_config';
|
||||
import { createVislibVisController } from './vis_controller';
|
||||
import { KbnVislibVisTypesDependencies } from './plugin';
|
||||
import { VisTypeVislibDependencies } from './plugin';
|
||||
|
||||
export const createLineVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({
|
||||
export const createLineVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
|
||||
name: 'line',
|
||||
title: i18n.translate('visTypeVislib.line.lineTitle', { defaultMessage: 'Line' }),
|
||||
icon: 'visLine',
|
||||
|
|
|
@ -24,7 +24,7 @@ import { PieOptions } from './components/options';
|
|||
import { getPositions, Positions } from './utils/collections';
|
||||
import { createVislibVisController } from './vis_controller';
|
||||
import { CommonVislibParams } from './types';
|
||||
import { KbnVislibVisTypesDependencies } from './plugin';
|
||||
import { VisTypeVislibDependencies } from './plugin';
|
||||
|
||||
export interface PieVisParams extends CommonVislibParams {
|
||||
type: 'pie';
|
||||
|
@ -38,7 +38,7 @@ export interface PieVisParams extends CommonVislibParams {
|
|||
};
|
||||
}
|
||||
|
||||
export const createPieVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({
|
||||
export const createPieVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({
|
||||
name: 'pie',
|
||||
title: i18n.translate('visTypeVislib.pie.pieTitle', { defaultMessage: 'Pie' }),
|
||||
icon: 'visPie',
|
||||
|
|
|
@ -42,7 +42,7 @@ jest.mock('./vislib/response_handler', () => ({
|
|||
}));
|
||||
|
||||
describe('interpreter/functions#pie', () => {
|
||||
const fn = functionWrapper(createPieVisFn());
|
||||
const fn = functionWrapper(createPieVisFn);
|
||||
const context = {
|
||||
type: 'kibana_datatable',
|
||||
rows: [{ 'col-0-1': 0 }],
|
||||
|
|
|
@ -43,12 +43,7 @@ interface RenderValue {
|
|||
|
||||
type Return = Render<RenderValue>;
|
||||
|
||||
export const createPieVisFn = () => (): ExpressionFunction<
|
||||
typeof name,
|
||||
Context,
|
||||
Arguments,
|
||||
Return
|
||||
> => ({
|
||||
export const createPieVisFn = (): ExpressionFunction<typeof name, Context, Arguments, Return> => ({
|
||||
name: 'kibana_pie',
|
||||
type: 'render',
|
||||
context: {
|
||||
|
|
|
@ -26,7 +26,7 @@ import {
|
|||
|
||||
import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public';
|
||||
import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public';
|
||||
import { createKbnVislibVisTypesFn } from './vis_type_vislib_vis_fn';
|
||||
import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn';
|
||||
import { createPieVisFn } from './pie_fn';
|
||||
import {
|
||||
createHistogramVisTypeDefinition,
|
||||
|
@ -38,45 +38,43 @@ import {
|
|||
createGaugeVisTypeDefinition,
|
||||
createGoalVisTypeDefinition,
|
||||
} from './vis_type_vislib_vis_types';
|
||||
import { ChartsPluginSetup } from '../../../../plugins/charts/public';
|
||||
|
||||
type KbnVislibVisTypesCoreSetup = CoreSetup<KbnVislibVisTypesPluginStartDependencies>;
|
||||
|
||||
export interface LegacyDependencies {
|
||||
vislibColor: (colors: Array<string | number>, mappings: any) => (value: any) => any;
|
||||
export interface VisTypeVislibDependencies {
|
||||
uiSettings: IUiSettingsClient;
|
||||
charts: ChartsPluginSetup;
|
||||
}
|
||||
|
||||
export type KbnVislibVisTypesDependencies = LegacyDependencies & {
|
||||
uiSettings: IUiSettingsClient;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export interface KbnVislibVisTypesPluginSetupDependencies {
|
||||
export interface VisTypeVislibPluginSetupDependencies {
|
||||
expressions: ReturnType<ExpressionsPublicPlugin['setup']>;
|
||||
visualizations: VisualizationsSetup;
|
||||
__LEGACY: LegacyDependencies;
|
||||
charts: ChartsPluginSetup;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface KbnVislibVisTypesPluginStartDependencies {
|
||||
export interface VisTypeVislibPluginStartDependencies {
|
||||
expressions: ReturnType<ExpressionsPublicPlugin['start']>;
|
||||
visualizations: VisualizationsStart;
|
||||
}
|
||||
|
||||
type VisTypeVislibCoreSetup = CoreSetup<VisTypeVislibPluginStartDependencies>;
|
||||
|
||||
/** @internal */
|
||||
export class KbnVislibVisTypesPlugin implements Plugin<Promise<void>, void> {
|
||||
export class VisTypeVislibPlugin implements Plugin<Promise<void>, void> {
|
||||
constructor(public initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public async setup(
|
||||
core: KbnVislibVisTypesCoreSetup,
|
||||
{ expressions, visualizations, __LEGACY }: KbnVislibVisTypesPluginSetupDependencies
|
||||
core: VisTypeVislibCoreSetup,
|
||||
{ expressions, visualizations, charts }: VisTypeVislibPluginSetupDependencies
|
||||
) {
|
||||
const visualizationDependencies: Readonly<KbnVislibVisTypesDependencies> = {
|
||||
...__LEGACY,
|
||||
const visualizationDependencies: Readonly<VisTypeVislibDependencies> = {
|
||||
uiSettings: core.uiSettings,
|
||||
charts,
|
||||
};
|
||||
|
||||
expressions.registerFunction(createKbnVislibVisTypesFn());
|
||||
expressions.registerFunction(createPieVisFn());
|
||||
expressions.registerFunction(createVisTypeVislibVisFn);
|
||||
expressions.registerFunction(createPieVisFn);
|
||||
|
||||
[
|
||||
createHistogramVisTypeDefinition,
|
||||
|
@ -90,7 +88,7 @@ export class KbnVislibVisTypesPlugin implements Plugin<Promise<void>, void> {
|
|||
].forEach(vis => visualizations.types.createBaseVisualization(vis(visualizationDependencies)));
|
||||
}
|
||||
|
||||
public start(core: CoreStart, deps: KbnVislibVisTypesPluginStartDependencies) {
|
||||
public start(core: CoreStart, deps: VisTypeVislibPluginStartDependencies) {
|
||||
// nothing to do here
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { ColorSchemas } from './legacy_imports';
|
||||
import { TimeMarker } from './vislib/visualizations/time_marker';
|
||||
import {
|
||||
Positions,
|
||||
|
@ -30,6 +29,7 @@ import {
|
|||
ScaleTypes,
|
||||
ThresholdLineStyles,
|
||||
} from './utils/collections';
|
||||
import { ColorSchemas } from '../../../../plugins/charts/public';
|
||||
|
||||
export interface CommonVislibParams {
|
||||
addTooltip: boolean;
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { $Values } from '@kbn/utility-types';
|
||||
import { colorSchemas } from '../legacy_imports';
|
||||
|
||||
import { colorSchemas } from '../../../../../plugins/charts/public';
|
||||
|
||||
export const Positions = Object.freeze({
|
||||
RIGHT: 'right' as 'right',
|
||||
|
|
|
@ -24,7 +24,7 @@ import { Vis, VisParams } from './legacy_imports';
|
|||
// @ts-ignore
|
||||
import { Vis as Vislib } from './vislib/vis';
|
||||
import { Positions } from './utils/collections';
|
||||
import { KbnVislibVisTypesDependencies } from './plugin';
|
||||
import { VisTypeVislibDependencies } from './plugin';
|
||||
import { mountReactNode } from '../../../../core/public/utils';
|
||||
import { VisLegend, CUSTOM_LEGEND_VIS_TYPES } from './vislib/components/legend';
|
||||
|
||||
|
@ -35,7 +35,7 @@ const legendClassName = {
|
|||
right: 'visLib--legend-right',
|
||||
};
|
||||
|
||||
export const createVislibVisController = (deps: KbnVislibVisTypesDependencies) => {
|
||||
export const createVislibVisController = (deps: VisTypeVislibDependencies) => {
|
||||
return class VislibVisController {
|
||||
unmount: (() => void) | null = null;
|
||||
visParams?: VisParams;
|
||||
|
|
|
@ -45,7 +45,7 @@ interface RenderValue {
|
|||
|
||||
type Return = Render<RenderValue>;
|
||||
|
||||
export const createKbnVislibVisTypesFn = () => (): ExpressionFunction<
|
||||
export const createVisTypeVislibVisFn = (): ExpressionFunction<
|
||||
typeof name,
|
||||
Context,
|
||||
Arguments,
|
||||
|
|
|
@ -20,10 +20,14 @@
|
|||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
|
||||
import { vislibColor } from 'ui/vis/components/color/color';
|
||||
|
||||
import { Vis } from '../../../vis';
|
||||
|
||||
// TODO: Remove when converted to jest mocks
|
||||
import {
|
||||
ColorsService,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../../../../../plugins/charts/public/services';
|
||||
|
||||
const $visCanvas = $('<div>')
|
||||
.attr('id', 'vislib-vis-fixtures')
|
||||
.css({
|
||||
|
@ -57,9 +61,14 @@ afterEach(function() {
|
|||
|
||||
const getDeps = () => {
|
||||
const uiSettings = new Map();
|
||||
const colors = new ColorsService();
|
||||
colors.init(uiSettings);
|
||||
|
||||
return {
|
||||
uiSettings,
|
||||
vislibColor,
|
||||
charts: {
|
||||
colors,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -35,14 +35,16 @@ import { getFormat } from '../../legacy_imports';
|
|||
* @param attr {Object|*} Visualization options
|
||||
*/
|
||||
export class Data {
|
||||
constructor(data, uiState, vislibColor) {
|
||||
constructor(data, uiState, createColorLookupFunction) {
|
||||
this.uiState = uiState;
|
||||
this.vislibColor = vislibColor;
|
||||
this.createColorLookupFunction = createColorLookupFunction;
|
||||
this.data = this.copyDataObj(data);
|
||||
this.type = this.getDataType();
|
||||
this._cleanVisData();
|
||||
this.labels = this._getLabels(this.data);
|
||||
this.color = this.labels ? vislibColor(this.labels, uiState.get('vis.colors')) : undefined;
|
||||
this.color = this.labels
|
||||
? createColorLookupFunction(this.labels, uiState.get('vis.colors'))
|
||||
: undefined;
|
||||
this._normalizeOrdered();
|
||||
}
|
||||
|
||||
|
@ -473,7 +475,7 @@ export class Data {
|
|||
const defaultColors = this.uiState.get('vis.defaultColors');
|
||||
const overwriteColors = this.uiState.get('vis.colors');
|
||||
const colors = defaultColors ? _.defaults({}, overwriteColors, defaultColors) : overwriteColors;
|
||||
return this.vislibColor(this.getLabels(), colors);
|
||||
return this.createColorLookupFunction(this.getLabels(), colors);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -483,7 +485,7 @@ export class Data {
|
|||
* @returns {Function} Performs lookup on string and returns hex color
|
||||
*/
|
||||
getPieColorFunc() {
|
||||
return this.vislibColor(
|
||||
return this.createColorLookupFunction(
|
||||
this.pieNames(this.getVisData()).map(function(d) {
|
||||
return d.label;
|
||||
}),
|
||||
|
|
|
@ -35,8 +35,8 @@ const DEFAULT_VIS_CONFIG = {
|
|||
};
|
||||
|
||||
export class VisConfig {
|
||||
constructor(visConfigArgs, data, uiState, el, vislibColor) {
|
||||
this.data = new Data(data, uiState, vislibColor);
|
||||
constructor(visConfigArgs, data, uiState, el, createColorLookupFunction) {
|
||||
this.data = new Data(data, uiState, createColorLookupFunction);
|
||||
|
||||
const visType = visTypes[visConfigArgs.type];
|
||||
const typeDefaults = visType(visConfigArgs, this.data);
|
||||
|
|
|
@ -55,7 +55,7 @@ export class Vis extends EventEmitter {
|
|||
this.data,
|
||||
this.uiState,
|
||||
this.element,
|
||||
this.deps.vislibColor
|
||||
this.deps.charts.colors.createColorLookupFunction.bind(this.deps.charts.colors)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
import d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { getHeatmapColors } from '../../../legacy_imports';
|
||||
import { getHeatmapColors } from '../../../../../../../plugins/charts/public';
|
||||
|
||||
const arcAngles = {
|
||||
angleFactor: 0.75,
|
||||
|
|
|
@ -249,7 +249,7 @@ export class PointSeries extends Chart {
|
|||
if (!seriArgs.show) return;
|
||||
const SeriClass =
|
||||
seriTypes[seriArgs.type || self.handler.visConfig.get('chart.type')] || seriTypes.line;
|
||||
const series = new SeriClass(self.handler, svg, data.series[i], seriArgs, this.deps);
|
||||
const series = new SeriClass(self.handler, svg, data.series[i], seriArgs, self.deps);
|
||||
series.events = self.events;
|
||||
svg.call(series.draw());
|
||||
self.series.push(series);
|
||||
|
|
|
@ -23,7 +23,7 @@ import moment from 'moment';
|
|||
import { isColorDark } from '@elastic/eui';
|
||||
|
||||
import { PointSeries } from './_point_series';
|
||||
import { getHeatmapColors } from '../../../legacy_imports';
|
||||
import { getHeatmapColors } from '../../.../../../../../../../plugins/charts/public';
|
||||
|
||||
const defaults = {
|
||||
color: undefined, // todo
|
||||
|
@ -42,6 +42,7 @@ const defaults = {
|
|||
export class HeatmapChart extends PointSeries {
|
||||
constructor(handler, chartEl, chartData, seriesConfigArgs, deps) {
|
||||
super(handler, chartEl, chartData, seriesConfigArgs, deps);
|
||||
|
||||
this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults);
|
||||
|
||||
this.handler.visConfig.set('legend', {
|
||||
|
|
|
@ -27,11 +27,13 @@ import { inspectorPluginMock } from '../../../../../plugins/inspector/public/moc
|
|||
import { uiActionsPluginMock } from '../../../../../plugins/ui_actions/public/mocks';
|
||||
import { managementPluginMock } from '../../../../../plugins/management/public/mocks';
|
||||
import { usageCollectionPluginMock } from '../../../../../plugins/usage_collection/public/mocks';
|
||||
import { chartPluginMock } from '../../../../../plugins/charts/public/mocks';
|
||||
/* eslint-enable @kbn/eslint/no-restricted-paths */
|
||||
|
||||
export const pluginsMock = {
|
||||
createSetup: () => ({
|
||||
data: dataPluginMock.createSetupContract(),
|
||||
charts: chartPluginMock.createSetupContract(),
|
||||
navigation: navigationPluginMock.createSetupContract(),
|
||||
embeddable: embeddablePluginMock.createSetupContract(),
|
||||
inspector: inspectorPluginMock.createSetupContract(),
|
||||
|
@ -41,6 +43,7 @@ export const pluginsMock = {
|
|||
}),
|
||||
createStart: () => ({
|
||||
data: dataPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
navigation: navigationPluginMock.createStartContract(),
|
||||
embeddable: embeddablePluginMock.createStartContract(),
|
||||
inspector: inspectorPluginMock.createStartContract(),
|
||||
|
|
|
@ -141,6 +141,12 @@ export const npSetup = {
|
|||
update: sinon.fake(),
|
||||
},
|
||||
},
|
||||
charts: {
|
||||
theme: {
|
||||
chartsTheme$: mockObservable,
|
||||
useChartsTheme: sinon.fake(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -276,12 +282,21 @@ export const npStart = {
|
|||
featureCatalogue: {
|
||||
register: sinon.fake(),
|
||||
},
|
||||
environment: {
|
||||
get: sinon.fake(),
|
||||
},
|
||||
},
|
||||
navigation: {
|
||||
ui: {
|
||||
TopNavMenu: mockComponent,
|
||||
},
|
||||
},
|
||||
charts: {
|
||||
theme: {
|
||||
chartsTheme$: mockObservable,
|
||||
useChartsTheme: sinon.fake(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import {
|
|||
Setup as InspectorSetup,
|
||||
Start as InspectorStart,
|
||||
} from '../../../../plugins/inspector/public';
|
||||
import { EuiUtilsStart } from '../../../../plugins/eui_utils/public';
|
||||
import { ChartsPluginSetup, ChartsPluginStart } from '../../../../plugins/charts/public';
|
||||
import { DevToolsSetup, DevToolsStart } from '../../../../plugins/dev_tools/public';
|
||||
import { KibanaLegacySetup, KibanaLegacyStart } from '../../../../plugins/kibana_legacy/public';
|
||||
import { HomePublicPluginSetup, HomePublicPluginStart } from '../../../../plugins/home/public';
|
||||
|
@ -42,6 +42,7 @@ import {
|
|||
|
||||
export interface PluginsSetup {
|
||||
bfetch: BfetchPublicSetup;
|
||||
charts: ChartsPluginSetup;
|
||||
data: ReturnType<DataPlugin['setup']>;
|
||||
embeddable: IEmbeddableSetup;
|
||||
expressions: ReturnType<ExpressionsPlugin['setup']>;
|
||||
|
@ -57,9 +58,9 @@ export interface PluginsSetup {
|
|||
|
||||
export interface PluginsStart {
|
||||
bfetch: BfetchPublicStart;
|
||||
charts: ChartsPluginStart;
|
||||
data: ReturnType<DataPlugin['start']>;
|
||||
embeddable: IEmbeddableStart;
|
||||
eui_utils: EuiUtilsStart;
|
||||
expressions: ReturnType<ExpressionsPlugin['start']>;
|
||||
home: HomePublicPluginStart;
|
||||
inspector: InspectorStart;
|
||||
|
|
|
@ -1,343 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import ngMock from 'ng_mock';
|
||||
import _ from 'lodash';
|
||||
import d3 from 'd3';
|
||||
import chrome from '../../../chrome';
|
||||
import { seedColors } from '../../../vis/components/color/seed_colors';
|
||||
import { vislibColor as getColors } from '../../components/color/color';
|
||||
import { mappedColors } from '../../components/color/mapped_colors';
|
||||
import { createColorPalette } from '../../components/color/color_palette';
|
||||
|
||||
const config = chrome.getUiSettingsClient();
|
||||
|
||||
describe('Vislib Color Module Test Suite', function() {
|
||||
describe('Color (main)', function() {
|
||||
let previousConfig;
|
||||
const arr = ['good', 'better', 'best', 'never', 'let', 'it', 'rest'];
|
||||
const arrayOfNumbers = [1, 2, 3, 4, 5];
|
||||
const arrayOfUndefinedValues = [undefined, undefined, undefined];
|
||||
const arrayOfObjects = [{}, {}, {}];
|
||||
const arrayOfBooleans = [true, false, true];
|
||||
const arrayOfNullValues = [null, null, null];
|
||||
const emptyObject = {};
|
||||
const nullValue = null;
|
||||
let notAValue;
|
||||
let color;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(() => {
|
||||
previousConfig = config.get('visualization:colorMapping');
|
||||
config.set('visualization:colorMapping', {});
|
||||
color = getColors(arr, {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
config.set('visualization:colorMapping', previousConfig);
|
||||
});
|
||||
|
||||
it('should throw an error if input is not an array', function() {
|
||||
expect(function() {
|
||||
getColors(200);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function() {
|
||||
getColors('help');
|
||||
}).to.throwError();
|
||||
|
||||
expect(function() {
|
||||
getColors(true);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function() {
|
||||
getColors(notAValue);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function() {
|
||||
getColors(nullValue);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function() {
|
||||
getColors(emptyObject);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
describe('when array is not composed of numbers, strings, or undefined values', function() {
|
||||
it('should throw an error', function() {
|
||||
expect(function() {
|
||||
getColors(arrayOfObjects);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function() {
|
||||
getColors(arrayOfBooleans);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function() {
|
||||
getColors(arrayOfNullValues);
|
||||
}).to.throwError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when input is an array of strings, numbers, or undefined values', function() {
|
||||
it('should not throw an error', function() {
|
||||
expect(function() {
|
||||
getColors(arr);
|
||||
}).to.not.throwError();
|
||||
|
||||
expect(function() {
|
||||
getColors(arrayOfNumbers);
|
||||
}).to.not.throwError();
|
||||
|
||||
expect(function() {
|
||||
getColors(arrayOfUndefinedValues);
|
||||
}).to.not.throwError();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be a function', function() {
|
||||
expect(typeof getColors).to.be('function');
|
||||
});
|
||||
|
||||
it('should return a function', function() {
|
||||
expect(typeof color).to.be('function');
|
||||
});
|
||||
|
||||
it('should return the first hex color in the seed colors array', function() {
|
||||
expect(color(arr[0])).to.be(seedColors[0]);
|
||||
});
|
||||
|
||||
it('should return the value from the mapped colors', function() {
|
||||
expect(color(arr[1])).to.be(mappedColors.get(arr[1]));
|
||||
});
|
||||
|
||||
it('should return the value from the specified color mapping overrides', function() {
|
||||
const colorFn = getColors(arr, { good: 'red' });
|
||||
expect(colorFn('good')).to.be('red');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Seed Colors', function() {
|
||||
it('should return an array', function() {
|
||||
expect(seedColors instanceof Array).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Mapped Colors', () => {
|
||||
let previousConfig;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(() => {
|
||||
previousConfig = config.get('visualization:colorMapping');
|
||||
mappedColors.mapping = {};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
config.set('visualization:colorMapping', previousConfig);
|
||||
});
|
||||
|
||||
it('should properly map keys to unique colors', () => {
|
||||
config.set('visualization:colorMapping', {});
|
||||
|
||||
const arr = [1, 2, 3, 4, 5];
|
||||
mappedColors.mapKeys(arr);
|
||||
expect(
|
||||
_(mappedColors.mapping)
|
||||
.values()
|
||||
.uniq()
|
||||
.size()
|
||||
).to.be(arr.length);
|
||||
});
|
||||
|
||||
it('should not include colors used by the config', () => {
|
||||
const newConfig = { bar: seedColors[0] };
|
||||
config.set('visualization:colorMapping', newConfig);
|
||||
|
||||
const arr = ['foo', 'baz', 'qux'];
|
||||
mappedColors.mapKeys(arr);
|
||||
|
||||
const colorValues = _(mappedColors.mapping).values();
|
||||
expect(colorValues.contains(seedColors[0])).to.be(false);
|
||||
expect(colorValues.uniq().size()).to.be(arr.length);
|
||||
});
|
||||
|
||||
it('should create a unique array of colors even when config is set', () => {
|
||||
const newConfig = { bar: seedColors[0] };
|
||||
config.set('visualization:colorMapping', newConfig);
|
||||
|
||||
const arr = ['foo', 'bar', 'baz', 'qux'];
|
||||
mappedColors.mapKeys(arr);
|
||||
|
||||
const expectedSize = _(arr)
|
||||
.difference(_.keys(newConfig))
|
||||
.size();
|
||||
expect(
|
||||
_(mappedColors.mapping)
|
||||
.values()
|
||||
.uniq()
|
||||
.size()
|
||||
).to.be(expectedSize);
|
||||
expect(mappedColors.get(arr[0])).to.not.be(seedColors[0]);
|
||||
});
|
||||
|
||||
it('should treat different formats of colors as equal', () => {
|
||||
const color = d3.rgb(seedColors[0]);
|
||||
const rgb = `rgb(${color.r}, ${color.g}, ${color.b})`;
|
||||
const newConfig = { bar: rgb };
|
||||
config.set('visualization:colorMapping', newConfig);
|
||||
|
||||
const arr = ['foo', 'bar', 'baz', 'qux'];
|
||||
mappedColors.mapKeys(arr);
|
||||
|
||||
const expectedSize = _(arr)
|
||||
.difference(_.keys(newConfig))
|
||||
.size();
|
||||
expect(
|
||||
_(mappedColors.mapping)
|
||||
.values()
|
||||
.uniq()
|
||||
.size()
|
||||
).to.be(expectedSize);
|
||||
expect(mappedColors.get(arr[0])).to.not.be(seedColors[0]);
|
||||
expect(mappedColors.get('bar')).to.be(seedColors[0]);
|
||||
});
|
||||
|
||||
it('should have a flush method that moves the current map to the old map', function() {
|
||||
const arr = [1, 2, 3, 4, 5];
|
||||
mappedColors.mapKeys(arr);
|
||||
expect(_.keys(mappedColors.mapping).length).to.be(5);
|
||||
expect(_.keys(mappedColors.oldMap).length).to.be(0);
|
||||
|
||||
mappedColors.flush();
|
||||
|
||||
expect(_.keys(mappedColors.oldMap).length).to.be(5);
|
||||
expect(_.keys(mappedColors.mapping).length).to.be(0);
|
||||
|
||||
mappedColors.flush();
|
||||
|
||||
expect(_.keys(mappedColors.oldMap).length).to.be(0);
|
||||
expect(_.keys(mappedColors.mapping).length).to.be(0);
|
||||
});
|
||||
|
||||
it('should use colors in the oldMap if they are available', function() {
|
||||
const arr = [1, 2, 3, 4, 5];
|
||||
mappedColors.mapKeys(arr);
|
||||
expect(_.keys(mappedColors.mapping).length).to.be(5);
|
||||
expect(_.keys(mappedColors.oldMap).length).to.be(0);
|
||||
|
||||
mappedColors.flush();
|
||||
|
||||
mappedColors.mapKeys([3, 4, 5]);
|
||||
expect(_.keys(mappedColors.oldMap).length).to.be(5);
|
||||
expect(_.keys(mappedColors.mapping).length).to.be(3);
|
||||
|
||||
expect(mappedColors.mapping[1]).to.be(undefined);
|
||||
expect(mappedColors.mapping[2]).to.be(undefined);
|
||||
expect(mappedColors.mapping[3]).to.equal(mappedColors.oldMap[3]);
|
||||
expect(mappedColors.mapping[4]).to.equal(mappedColors.oldMap[4]);
|
||||
expect(mappedColors.mapping[5]).to.equal(mappedColors.oldMap[5]);
|
||||
});
|
||||
|
||||
it('should have a purge method that clears both maps', function() {
|
||||
const arr = [1, 2, 3, 4, 5];
|
||||
mappedColors.mapKeys(arr);
|
||||
mappedColors.flush();
|
||||
mappedColors.mapKeys(arr);
|
||||
|
||||
expect(_.keys(mappedColors.mapping).length).to.be(5);
|
||||
expect(_.keys(mappedColors.oldMap).length).to.be(5);
|
||||
|
||||
mappedColors.purge();
|
||||
|
||||
expect(_.keys(mappedColors.mapping).length).to.be(0);
|
||||
expect(_.keys(mappedColors.oldMap).length).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Color Palette', function() {
|
||||
const num1 = 45;
|
||||
const num2 = 72;
|
||||
const num3 = 90;
|
||||
const string = 'Welcome';
|
||||
const bool = true;
|
||||
const nullValue = null;
|
||||
const emptyArr = [];
|
||||
const emptyObject = {};
|
||||
let notAValue;
|
||||
let colorPalette;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(
|
||||
ngMock.inject(function() {
|
||||
colorPalette = createColorPalette(num1);
|
||||
})
|
||||
);
|
||||
|
||||
it('should throw an error if input is not a number', function() {
|
||||
expect(function() {
|
||||
createColorPalette(string);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function() {
|
||||
createColorPalette(bool);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function() {
|
||||
createColorPalette(nullValue);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function() {
|
||||
createColorPalette(emptyArr);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function() {
|
||||
createColorPalette(emptyObject);
|
||||
}).to.throwError();
|
||||
|
||||
expect(function() {
|
||||
createColorPalette(notAValue);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should be a function', function() {
|
||||
expect(typeof createColorPalette).to.be('function');
|
||||
});
|
||||
|
||||
it('should return an array', function() {
|
||||
expect(colorPalette instanceof Array).to.be(true);
|
||||
});
|
||||
|
||||
it('should return an array of the same length as the input', function() {
|
||||
expect(colorPalette.length).to.be(num1);
|
||||
});
|
||||
|
||||
it('should return the seed color array when input length is 72', function() {
|
||||
expect(createColorPalette(num2)[71]).to.be(seedColors[71]);
|
||||
});
|
||||
|
||||
it('should return an array of the same length as the input when input is greater than 72', function() {
|
||||
expect(createColorPalette(num3).length).to.be(num3);
|
||||
});
|
||||
|
||||
it('should create new darker colors when input is greater than 72', function() {
|
||||
expect(createColorPalette(num3)[72]).not.to.equal(seedColors[0]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { mappedColors } from './mapped_colors';
|
||||
|
||||
/*
|
||||
* Accepts an array of strings or numbers that are used to create a
|
||||
* a lookup table that associates the values (key) with a hex color (value).
|
||||
* Returns a function that accepts a value (i.e. a string or number)
|
||||
* and returns a hex color associated with that value.
|
||||
*/
|
||||
|
||||
export function vislibColor(arrayOfStringsOrNumbers, colorMapping) {
|
||||
colorMapping = colorMapping || {};
|
||||
if (!Array.isArray(arrayOfStringsOrNumbers)) {
|
||||
throw new Error('ColorUtil expects an array');
|
||||
}
|
||||
|
||||
arrayOfStringsOrNumbers.forEach(function(val) {
|
||||
if (!_.isString(val) && !_.isNumber(val) && !_.isUndefined(val)) {
|
||||
throw new TypeError('ColorUtil expects an array of strings, numbers, or undefined values');
|
||||
}
|
||||
});
|
||||
|
||||
mappedColors.mapKeys(arrayOfStringsOrNumbers);
|
||||
|
||||
return function(value) {
|
||||
return colorMapping[value] || mappedColors.get(value);
|
||||
};
|
||||
}
|
97
src/plugins/charts/README.md
Normal file
97
src/plugins/charts/README.md
Normal file
|
@ -0,0 +1,97 @@
|
|||
# Charts
|
||||
|
||||
The Charts plugin is a way to create easier integration of shared colors, themes, types and other utilities across all Kibana charts and visualizations.
|
||||
|
||||
## Static methods
|
||||
|
||||
### `vislibColorMaps`
|
||||
|
||||
Color mappings related to vislib visualizations
|
||||
|
||||
### `truncatedColorMaps`
|
||||
|
||||
Color mappings subset of `vislibColorMaps`
|
||||
|
||||
### `colorSchemas`
|
||||
|
||||
Color mappings in `value`/`text` form
|
||||
|
||||
### `getHeatmapColors`
|
||||
|
||||
Funciton to retrive heatmap related colors based on `value` and `colorSchemaName`
|
||||
|
||||
### `truncatedColorSchemas`
|
||||
|
||||
Truncated color mappings in `value`/`text` form
|
||||
|
||||
## Theme
|
||||
|
||||
the `theme` service offers utilities to interact with theme of kibana. EUI provides a light and dark theme object to work with Elastic-Charts. However, every instance of a Chart would need to pass down this the correctly EUI theme depending on Kibana's light or dark mode. There are several ways you can use the `theme` service to get the correct theme.
|
||||
|
||||
> The current theme (light or dark) of Kibana is typically taken into account for the functions below.
|
||||
|
||||
### `useChartsTheme`
|
||||
|
||||
The simple fetching of the correct EUI theme; a **React hook**.
|
||||
|
||||
```js
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { Chart, Settings } from '@elastic/charts';
|
||||
|
||||
export const YourComponent = () => (
|
||||
<Chart>
|
||||
<Settings theme={npStart.plugins.charts.theme.useChartsTheme()} />
|
||||
</Chart>
|
||||
);
|
||||
```
|
||||
|
||||
### `chartsTheme$`
|
||||
|
||||
An **observable** of the current charts theme. Use this implementation for more flexible updates to the chart theme without full page refreshes.
|
||||
|
||||
```tsx
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { EuiChartThemeType } from '@elastic/eui/src/themes/charts/themes';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Chart, Settings } from '@elastic/charts';
|
||||
|
||||
interface YourComponentProps {};
|
||||
|
||||
interface YourComponentState {
|
||||
chartsTheme: EuiChartThemeType['theme'];
|
||||
}
|
||||
|
||||
export class YourComponent extends Component<YourComponentProps, YourComponentState> {
|
||||
private subscription?: Subscription;
|
||||
public state = {
|
||||
chartsTheme: npStart.plugins.charts.theme.chartsDefaultTheme,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.subscription = npStart.plugins.charts.theme
|
||||
.chartsTheme$
|
||||
.subscribe(chartsTheme => this.setState({ chartsTheme }));
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
this.subscription = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { chartsTheme } = this.state;
|
||||
|
||||
return (
|
||||
<Chart>
|
||||
<Settings theme={chartsTheme} />
|
||||
</Chart>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `chartsDefaultTheme`
|
||||
|
||||
Returns default charts theme (i.e. light).
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"id": "eui_utils",
|
||||
"id": "charts",
|
||||
"version": "kibana",
|
||||
"server": false,
|
||||
"ui": true
|
27
src/plugins/charts/public/index.ts
Normal file
27
src/plugins/charts/public/index.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { ChartsPlugin } from './plugin';
|
||||
|
||||
export const plugin = () => new ChartsPlugin();
|
||||
|
||||
export type ChartsPluginSetup = ReturnType<ChartsPlugin['setup']>;
|
||||
export type ChartsPluginStart = ReturnType<ChartsPlugin['start']>;
|
||||
|
||||
export * from './static';
|
42
src/plugins/charts/public/mocks.ts
Normal file
42
src/plugins/charts/public/mocks.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { ChartsPlugin } from './plugin';
|
||||
import { themeServiceMock } from './services/theme/mock';
|
||||
import { colorsServiceMock } from './services/colors/mock';
|
||||
|
||||
export type Setup = jest.Mocked<ReturnType<ChartsPlugin['setup']>>;
|
||||
export type Start = jest.Mocked<ReturnType<ChartsPlugin['start']>>;
|
||||
|
||||
const createSetupContract = (): Setup => ({
|
||||
colors: colorsServiceMock,
|
||||
theme: themeServiceMock,
|
||||
});
|
||||
|
||||
const createStartContract = (): Start => ({
|
||||
colors: colorsServiceMock,
|
||||
theme: themeServiceMock,
|
||||
});
|
||||
|
||||
export { colorMapsMock } from './static/color_maps/mock';
|
||||
|
||||
export const chartPluginMock = {
|
||||
createSetupContract,
|
||||
createStartContract,
|
||||
};
|
57
src/plugins/charts/public/plugin.ts
Normal file
57
src/plugins/charts/public/plugin.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Plugin, CoreSetup } from 'kibana/public';
|
||||
|
||||
import { ThemeService, ColorsService } from './services';
|
||||
|
||||
export type Theme = Omit<ThemeService, 'init'>;
|
||||
export type Color = Omit<ColorsService, 'init'>;
|
||||
|
||||
/** @public */
|
||||
export interface ChartsPluginSetup {
|
||||
colors: Color;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type ChartsPluginStart = ChartsPluginSetup;
|
||||
|
||||
/** @public */
|
||||
export class ChartsPlugin implements Plugin<ChartsPluginSetup, ChartsPluginStart> {
|
||||
private readonly themeService = new ThemeService();
|
||||
private readonly colorsService = new ColorsService();
|
||||
|
||||
public setup({ uiSettings }: CoreSetup): ChartsPluginSetup {
|
||||
this.themeService.init(uiSettings);
|
||||
this.colorsService.init(uiSettings);
|
||||
|
||||
return {
|
||||
colors: this.colorsService,
|
||||
theme: this.themeService,
|
||||
};
|
||||
}
|
||||
|
||||
public start(): ChartsPluginStart {
|
||||
return {
|
||||
colors: this.colorsService,
|
||||
theme: this.themeService,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -19,18 +19,13 @@
|
|||
|
||||
import d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import { seedColors } from './seed_colors';
|
||||
|
||||
/*
|
||||
* Generates an array of hex colors the length of the input number.
|
||||
* If the number is greater than the length of seed colors available,
|
||||
* new colors are generated up to the value of the input number.
|
||||
*/
|
||||
import { seedColors } from './seed_colors';
|
||||
|
||||
const offset = 300; // Hue offset to start at
|
||||
|
||||
const fraction = function(goal) {
|
||||
const walkTree = function(numerator, denominator, bytes) {
|
||||
const fraction = function(goal: number) {
|
||||
const walkTree = (numerator: number, denominator: number, bytes: number[]): number => {
|
||||
if (bytes.length) {
|
||||
return walkTree(numerator * 2 + (bytes.pop() ? 1 : -1), denominator * 2, bytes);
|
||||
} else {
|
||||
|
@ -49,13 +44,17 @@ const fraction = function(goal) {
|
|||
return walkTree(1, 2, b);
|
||||
};
|
||||
|
||||
export function createColorPalette(num) {
|
||||
/**
|
||||
* Generates an array of hex colors the length of the input number.
|
||||
* If the number is greater than the length of seed colors available,
|
||||
* new colors are generated up to the value of the input number.
|
||||
*/
|
||||
export function createColorPalette(num?: any): string[] {
|
||||
if (!_.isNumber(num)) {
|
||||
throw new TypeError('ColorPaletteUtilService expects a number');
|
||||
}
|
||||
|
||||
const colors = seedColors;
|
||||
|
||||
const seedLength = seedColors.length;
|
||||
|
||||
_.times(num - seedLength, function(i) {
|
140
src/plugins/charts/public/services/colors/colors.test.ts
Normal file
140
src/plugins/charts/public/services/colors/colors.test.ts
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { coreMock } from '../../../../../core/public/mocks';
|
||||
import { seedColors } from './seed_colors';
|
||||
import { ColorsService } from './colors';
|
||||
|
||||
// Local state for config
|
||||
const config = new Map<string, any>();
|
||||
|
||||
describe('Vislib Color Service', () => {
|
||||
const colors = new ColorsService();
|
||||
const mockUiSettings = coreMock.createSetup().uiSettings;
|
||||
mockUiSettings.get.mockImplementation(a => config.get(a));
|
||||
mockUiSettings.set.mockImplementation((...a) => config.set(...a) as any);
|
||||
colors.init(mockUiSettings);
|
||||
|
||||
let color: any;
|
||||
let previousConfig: any;
|
||||
|
||||
const arr = ['good', 'better', 'best', 'never', 'let', 'it', 'rest'];
|
||||
const arrayOfNumbers = [1, 2, 3, 4, 5];
|
||||
const arrayOfUndefinedValues = [undefined, undefined, undefined];
|
||||
const arrayOfObjects = [{}, {}, {}];
|
||||
const arrayOfBooleans = [true, false, true];
|
||||
const arrayOfNullValues = [null, null, null];
|
||||
const emptyObject = {};
|
||||
const nullValue = null;
|
||||
|
||||
beforeEach(() => {
|
||||
previousConfig = config.get('visualization:colorMapping');
|
||||
config.set('visualization:colorMapping', {});
|
||||
color = colors.createColorLookupFunction(arr, {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
config.set('visualization:colorMapping', previousConfig);
|
||||
});
|
||||
|
||||
it('should throw error if not initialized', () => {
|
||||
const colorsBad = new ColorsService();
|
||||
|
||||
expect(() => colorsBad.createColorLookupFunction(arr, {})).toThrowError();
|
||||
});
|
||||
|
||||
it('should throw an error if input is not an array', () => {
|
||||
expect(() => {
|
||||
colors.createColorLookupFunction(200);
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
colors.createColorLookupFunction('help');
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
colors.createColorLookupFunction(true);
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
colors.createColorLookupFunction();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
colors.createColorLookupFunction(nullValue);
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
colors.createColorLookupFunction(emptyObject);
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
describe('when array is not composed of numbers, strings, or undefined values', () => {
|
||||
it('should throw an error', () => {
|
||||
expect(() => {
|
||||
colors.createColorLookupFunction(arrayOfObjects);
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
colors.createColorLookupFunction(arrayOfBooleans);
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
colors.createColorLookupFunction(arrayOfNullValues);
|
||||
}).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when input is an array of strings, numbers, or undefined values', () => {
|
||||
it('should not throw an error', () => {
|
||||
expect(() => {
|
||||
colors.createColorLookupFunction(arr);
|
||||
}).not.toThrowError();
|
||||
|
||||
expect(() => {
|
||||
colors.createColorLookupFunction(arrayOfNumbers);
|
||||
}).not.toThrowError();
|
||||
|
||||
expect(() => {
|
||||
colors.createColorLookupFunction(arrayOfUndefinedValues);
|
||||
}).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be a function', () => {
|
||||
expect(typeof colors.createColorLookupFunction).toBe('function');
|
||||
});
|
||||
|
||||
it('should return a function', () => {
|
||||
expect(typeof color).toBe('function');
|
||||
});
|
||||
|
||||
it('should return the first hex color in the seed colors array', () => {
|
||||
expect(color(arr[0])).toBe(seedColors[0]);
|
||||
});
|
||||
|
||||
it('should return the value from the mapped colors', () => {
|
||||
expect(color(arr[1])).toBe(colors.mappedColors.get(arr[1]));
|
||||
});
|
||||
|
||||
it('should return the value from the specified color mapping overrides', () => {
|
||||
const colorFn = colors.createColorLookupFunction(arr, { good: 'red' });
|
||||
expect(colorFn('good')).toBe('red');
|
||||
});
|
||||
});
|
74
src/plugins/charts/public/services/colors/colors.ts
Normal file
74
src/plugins/charts/public/services/colors/colors.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { CoreSetup } from 'kibana/public';
|
||||
|
||||
import { MappedColors } from './mapped_colors';
|
||||
import { seedColors } from './seed_colors';
|
||||
|
||||
/**
|
||||
* Accepts an array of strings or numbers that are used to create a
|
||||
* a lookup table that associates the values (key) with a hex color (value).
|
||||
* Returns a function that accepts a value (i.e. a string or number)
|
||||
* and returns a hex color associated with that value.
|
||||
*/
|
||||
export class ColorsService {
|
||||
private _mappedColors?: MappedColors;
|
||||
|
||||
public readonly seedColors = seedColors;
|
||||
|
||||
public get mappedColors() {
|
||||
if (!this._mappedColors) {
|
||||
throw new Error('ColorService not yet initialized');
|
||||
}
|
||||
|
||||
return this._mappedColors;
|
||||
}
|
||||
|
||||
init(uiSettings: CoreSetup['uiSettings']) {
|
||||
this._mappedColors = new MappedColors(uiSettings);
|
||||
}
|
||||
|
||||
createColorLookupFunction(
|
||||
arrayOfStringsOrNumbers?: any,
|
||||
colorMapping: Partial<Record<string, string>> = {}
|
||||
) {
|
||||
if (!Array.isArray(arrayOfStringsOrNumbers)) {
|
||||
throw new Error(
|
||||
`createColorLookupFunction expects an array but recived: ${typeof arrayOfStringsOrNumbers}`
|
||||
);
|
||||
}
|
||||
|
||||
arrayOfStringsOrNumbers.forEach(function(val) {
|
||||
if (!_.isString(val) && !_.isNumber(val) && !_.isUndefined(val)) {
|
||||
throw new TypeError(
|
||||
'createColorLookupFunction expects an array of strings, numbers, or undefined values'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this.mappedColors.mapKeys(arrayOfStringsOrNumbers);
|
||||
|
||||
return (value: string) => {
|
||||
return colorMapping[value] || this.mappedColors.get(value);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { seedColors } from './seed_colors';
|
||||
import { createColorPalette } from './color_palette';
|
||||
|
||||
describe('Color Palette', () => {
|
||||
const num1 = 45;
|
||||
const num2 = 72;
|
||||
const num3 = 90;
|
||||
const string = 'Welcome';
|
||||
const bool = true;
|
||||
const nullValue = null;
|
||||
const emptyArr: [] = [];
|
||||
const emptyObject = {};
|
||||
let colorPalette: string[];
|
||||
|
||||
beforeEach(() => {
|
||||
colorPalette = createColorPalette(num1);
|
||||
});
|
||||
|
||||
it('should throw an error if input is not a number', () => {
|
||||
expect(() => {
|
||||
createColorPalette(string);
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
createColorPalette(bool);
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
createColorPalette(nullValue);
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
createColorPalette(emptyArr);
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
createColorPalette(emptyObject);
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
createColorPalette();
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should be a function', () => {
|
||||
expect(typeof createColorPalette).toBe('function');
|
||||
});
|
||||
|
||||
it('should return an array', () => {
|
||||
expect(colorPalette).toBeInstanceOf(Array);
|
||||
});
|
||||
|
||||
it('should return an array of the same length as the input', () => {
|
||||
expect(colorPalette.length).toBe(num1);
|
||||
});
|
||||
|
||||
it('should return the seed color array when input length is 72', () => {
|
||||
expect(createColorPalette(num2)[71]).toBe(seedColors[71]);
|
||||
});
|
||||
|
||||
it('should return an array of the same length as the input when input is greater than 72', () => {
|
||||
expect(createColorPalette(num3).length).toBe(num3);
|
||||
});
|
||||
|
||||
it('should create new darker colors when input is greater than 72', () => {
|
||||
expect(createColorPalette(num3)[72]).not.toEqual(seedColors[0]);
|
||||
});
|
||||
});
|
|
@ -17,7 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { EuiUtils } from './eui_utils';
|
||||
|
||||
export const plugin = () => new EuiUtils();
|
||||
export type EuiUtilsStart = ReturnType<EuiUtils['start']>;
|
||||
export { ColorsService } from './colors';
|
163
src/plugins/charts/public/services/colors/mapped_colors.test.ts
Normal file
163
src/plugins/charts/public/services/colors/mapped_colors.test.ts
Normal file
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import d3 from 'd3';
|
||||
|
||||
import { coreMock } from '../../../../../core/public/mocks';
|
||||
import { seedColors } from './seed_colors';
|
||||
import { MappedColors } from './mapped_colors';
|
||||
|
||||
// Local state for config
|
||||
const config = new Map<string, any>();
|
||||
|
||||
describe('Mapped Colors', () => {
|
||||
const mockUiSettings = coreMock.createSetup().uiSettings;
|
||||
mockUiSettings.get.mockImplementation(a => config.get(a));
|
||||
mockUiSettings.set.mockImplementation((...a) => config.set(...a) as any);
|
||||
|
||||
const mappedColors = new MappedColors(mockUiSettings);
|
||||
let previousConfig: any;
|
||||
|
||||
beforeEach(() => {
|
||||
previousConfig = config.get('visualization:colorMapping');
|
||||
mappedColors.purge();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
config.set('visualization:colorMapping', previousConfig);
|
||||
});
|
||||
|
||||
it('should properly map keys to unique colors', () => {
|
||||
config.set('visualization:colorMapping', {});
|
||||
|
||||
const arr = [1, 2, 3, 4, 5];
|
||||
mappedColors.mapKeys(arr);
|
||||
expect(
|
||||
_(mappedColors.mapping)
|
||||
.values()
|
||||
.uniq()
|
||||
.size()
|
||||
).toBe(arr.length);
|
||||
});
|
||||
|
||||
it('should not include colors used by the config', () => {
|
||||
const newConfig = { bar: seedColors[0] };
|
||||
config.set('visualization:colorMapping', newConfig);
|
||||
|
||||
const arr = ['foo', 'baz', 'qux'];
|
||||
mappedColors.mapKeys(arr);
|
||||
|
||||
const colorValues = _(mappedColors.mapping).values();
|
||||
expect(colorValues.contains(seedColors[0])).toBe(false);
|
||||
expect(colorValues.uniq().size()).toBe(arr.length);
|
||||
});
|
||||
|
||||
it('should create a unique array of colors even when config is set', () => {
|
||||
const newConfig = { bar: seedColors[0] };
|
||||
config.set('visualization:colorMapping', newConfig);
|
||||
|
||||
const arr = ['foo', 'bar', 'baz', 'qux'];
|
||||
mappedColors.mapKeys(arr);
|
||||
|
||||
const expectedSize = _(arr)
|
||||
.difference(_.keys(newConfig))
|
||||
.size();
|
||||
expect(
|
||||
_(mappedColors.mapping)
|
||||
.values()
|
||||
.uniq()
|
||||
.size()
|
||||
).toBe(expectedSize);
|
||||
expect(mappedColors.get(arr[0])).not.toBe(seedColors[0]);
|
||||
});
|
||||
|
||||
it('should treat different formats of colors as equal', () => {
|
||||
const color = d3.rgb(seedColors[0]);
|
||||
const rgb = `rgb(${color.r}, ${color.g}, ${color.b})`;
|
||||
const newConfig = { bar: rgb };
|
||||
config.set('visualization:colorMapping', newConfig);
|
||||
|
||||
const arr = ['foo', 'bar', 'baz', 'qux'];
|
||||
mappedColors.mapKeys(arr);
|
||||
|
||||
const expectedSize = _(arr)
|
||||
.difference(_.keys(newConfig))
|
||||
.size();
|
||||
expect(
|
||||
_(mappedColors.mapping)
|
||||
.values()
|
||||
.uniq()
|
||||
.size()
|
||||
).toBe(expectedSize);
|
||||
expect(mappedColors.get(arr[0])).not.toBe(seedColors[0]);
|
||||
expect(mappedColors.get('bar')).toBe(seedColors[0]);
|
||||
});
|
||||
|
||||
it('should have a flush method that moves the current map to the old map', function() {
|
||||
const arr = [1, 2, 3, 4, 5];
|
||||
mappedColors.mapKeys(arr);
|
||||
expect(_.keys(mappedColors.mapping).length).toBe(5);
|
||||
expect(_.keys(mappedColors.oldMap).length).toBe(0);
|
||||
|
||||
mappedColors.flush();
|
||||
|
||||
expect(_.keys(mappedColors.oldMap).length).toBe(5);
|
||||
expect(_.keys(mappedColors.mapping).length).toBe(0);
|
||||
|
||||
mappedColors.flush();
|
||||
|
||||
expect(_.keys(mappedColors.oldMap).length).toBe(0);
|
||||
expect(_.keys(mappedColors.mapping).length).toBe(0);
|
||||
});
|
||||
|
||||
it('should use colors in the oldMap if they are available', function() {
|
||||
const arr = [1, 2, 3, 4, 5];
|
||||
mappedColors.mapKeys(arr);
|
||||
expect(_.keys(mappedColors.mapping).length).toBe(5);
|
||||
expect(_.keys(mappedColors.oldMap).length).toBe(0);
|
||||
|
||||
mappedColors.flush();
|
||||
|
||||
mappedColors.mapKeys([3, 4, 5]);
|
||||
expect(_.keys(mappedColors.oldMap).length).toBe(5);
|
||||
expect(_.keys(mappedColors.mapping).length).toBe(3);
|
||||
|
||||
expect(mappedColors.mapping[1]).toBe(undefined);
|
||||
expect(mappedColors.mapping[2]).toBe(undefined);
|
||||
expect(mappedColors.mapping[3]).toEqual(mappedColors.oldMap[3]);
|
||||
expect(mappedColors.mapping[4]).toEqual(mappedColors.oldMap[4]);
|
||||
expect(mappedColors.mapping[5]).toEqual(mappedColors.oldMap[5]);
|
||||
});
|
||||
|
||||
it('should have a purge method that clears both maps', function() {
|
||||
const arr = [1, 2, 3, 4, 5];
|
||||
mappedColors.mapKeys(arr);
|
||||
mappedColors.flush();
|
||||
mappedColors.mapKeys(arr);
|
||||
|
||||
expect(_.keys(mappedColors.mapping).length).toBe(5);
|
||||
expect(_.keys(mappedColors.oldMap).length).toBe(5);
|
||||
|
||||
mappedColors.purge();
|
||||
|
||||
expect(_.keys(mappedColors.mapping).length).toBe(0);
|
||||
expect(_.keys(mappedColors.oldMap).length).toBe(0);
|
||||
});
|
||||
});
|
|
@ -19,64 +19,75 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import d3 from 'd3';
|
||||
import chrome from '../../../chrome';
|
||||
|
||||
import { CoreSetup } from 'kibana/public';
|
||||
|
||||
import { createColorPalette } from './color_palette';
|
||||
|
||||
const standardizeColor = color => d3.rgb(color).toString();
|
||||
const standardizeColor = (color: string) => d3.rgb(color).toString();
|
||||
|
||||
const config = chrome.getUiSettingsClient();
|
||||
|
||||
function getConfigColorMapping() {
|
||||
return _.mapValues(config.get('visualization:colorMapping'), standardizeColor);
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Maintains a lookup table that associates the value (key) with a hex color (value)
|
||||
* across the visualizations.
|
||||
* Provides functions to interact with the lookup table
|
||||
*/
|
||||
class MappedColors {
|
||||
constructor() {
|
||||
this.oldMap = {};
|
||||
this.mapping = {};
|
||||
export class MappedColors {
|
||||
private _oldMap: any;
|
||||
private _mapping: any;
|
||||
|
||||
constructor(private uiSettings: CoreSetup['uiSettings']) {
|
||||
this._oldMap = {};
|
||||
this._mapping = {};
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return getConfigColorMapping()[key] || this.mapping[key];
|
||||
private getConfigColorMapping() {
|
||||
return _.mapValues(this.uiSettings.get('visualization:colorMapping'), standardizeColor);
|
||||
}
|
||||
|
||||
public get oldMap(): any {
|
||||
return this._oldMap;
|
||||
}
|
||||
|
||||
public get mapping(): any {
|
||||
return this._mapping;
|
||||
}
|
||||
|
||||
get(key: string | number) {
|
||||
return this.getConfigColorMapping()[key] || this._mapping[key];
|
||||
}
|
||||
|
||||
flush() {
|
||||
this.oldMap = _.clone(this.mapping);
|
||||
this.mapping = {};
|
||||
this._oldMap = _.clone(this._mapping);
|
||||
this._mapping = {};
|
||||
}
|
||||
|
||||
purge() {
|
||||
this.oldMap = {};
|
||||
this.mapping = {};
|
||||
this._oldMap = {};
|
||||
this._mapping = {};
|
||||
}
|
||||
|
||||
mapKeys(keys) {
|
||||
const configMapping = getConfigColorMapping();
|
||||
mapKeys(keys: Array<string | number>) {
|
||||
const configMapping = this.getConfigColorMapping();
|
||||
const configColors = _.values(configMapping);
|
||||
const oldColors = _.values(this.oldMap);
|
||||
const oldColors = _.values(this._oldMap);
|
||||
|
||||
const keysToMap = [];
|
||||
const keysToMap: Array<string | number> = [];
|
||||
_.each(keys, key => {
|
||||
// If this key is mapped in the config, it's unnecessary to have it mapped here
|
||||
if (configMapping[key]) delete this.mapping[key];
|
||||
if (configMapping[key]) delete this._mapping[key];
|
||||
|
||||
// If this key is mapped to a color used by the config color mapping, we need to remap it
|
||||
if (_.contains(configColors, this.mapping[key])) keysToMap.push(key);
|
||||
if (_.contains(configColors, this._mapping[key])) keysToMap.push(key);
|
||||
|
||||
// if key exist in oldMap, move it to mapping
|
||||
if (this.oldMap[key]) this.mapping[key] = this.oldMap[key];
|
||||
if (this._oldMap[key]) this._mapping[key] = this._oldMap[key];
|
||||
|
||||
// If this key isn't mapped, we need to map it
|
||||
if (this.get(key) == null) keysToMap.push(key);
|
||||
});
|
||||
|
||||
// Generate a color palette big enough that all new keys can have unique color values
|
||||
const allColors = _(this.mapping)
|
||||
const allColors = _(this._mapping)
|
||||
.values()
|
||||
.union(configColors)
|
||||
.union(oldColors)
|
||||
|
@ -88,8 +99,6 @@ class MappedColors {
|
|||
newColors = newColors.concat(_.sample(allColors, keysToMap.length - newColors.length));
|
||||
}
|
||||
|
||||
_.merge(this.mapping, _.zipObject(keysToMap, newColors));
|
||||
_.merge(this._mapping, _.zipObject(keysToMap, newColors));
|
||||
}
|
||||
}
|
||||
|
||||
export const mappedColors = new MappedColors();
|
28
src/plugins/charts/public/services/colors/mock.ts
Normal file
28
src/plugins/charts/public/services/colors/mock.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { ColorsService } from './colors';
|
||||
import { coreMock } from '../../../../../core/public/mocks';
|
||||
|
||||
const colors = new ColorsService();
|
||||
colors.init(coreMock.createSetup().uiSettings);
|
||||
|
||||
export const colorsServiceMock: ColorsService = {
|
||||
createColorLookupFunction: jest.fn(colors.createColorLookupFunction),
|
||||
} as any;
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { seedColors } from './seed_colors';
|
||||
|
||||
describe('Seed Colors', function() {
|
||||
it('should return an array', function() {
|
||||
expect(seedColors).toBeInstanceOf(Array);
|
||||
});
|
||||
});
|
|
@ -17,13 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
/**
|
||||
* Using a random color generator presented awful colors and unpredictable color schemes.
|
||||
* So we needed to come up with a color scheme of our own that creates consistent, pleasing color patterns.
|
||||
* The order allows us to guarantee that 1st, 2nd, 3rd, etc values always get the same color.
|
||||
*/
|
||||
|
||||
export const seedColors = [
|
||||
export const seedColors: string[] = [
|
||||
'#00a69b',
|
||||
'#57c17b',
|
||||
'#6f87d8',
|
|
@ -17,8 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export * from './color_maps';
|
||||
// @ts-ignore
|
||||
export { getHeatmapColors } from './heatmap_color';
|
||||
// @ts-ignore
|
||||
export * from './truncated_color_maps';
|
||||
export { ColorsService } from './colors';
|
||||
export { ThemeService } from './theme';
|
20
src/plugins/charts/public/services/theme/index.ts
Normal file
20
src/plugins/charts/public/services/theme/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { ThemeService } from './theme';
|
29
src/plugins/charts/public/services/theme/mock.ts
Normal file
29
src/plugins/charts/public/services/theme/mock.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme';
|
||||
import { ThemeService } from './theme';
|
||||
|
||||
export const themeServiceMock: ThemeService = {
|
||||
chartsTheme$: jest.fn(() => ({
|
||||
subsribe: jest.fn(),
|
||||
})),
|
||||
chartsDefaultTheme: EUI_CHARTS_THEME_LIGHT.theme,
|
||||
useChartsTheme: jest.fn(),
|
||||
} as any;
|
|
@ -18,49 +18,53 @@
|
|||
*/
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
|
||||
import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme';
|
||||
import { EuiUtils } from './eui_utils';
|
||||
import { coreMock } from '../../../core/public/mocks';
|
||||
import { take } from 'rxjs/operators';
|
||||
const startMock = coreMock.createStart();
|
||||
|
||||
describe('EuiUtils', () => {
|
||||
describe('getChartsTheme()', () => {
|
||||
import { ThemeService } from './theme';
|
||||
import { coreMock } from '../../../../../core/public/mocks';
|
||||
|
||||
const { uiSettings: setupMockUiSettings } = coreMock.createSetup();
|
||||
|
||||
describe('ThemeService', () => {
|
||||
describe('chartsTheme$', () => {
|
||||
it('should throw error if service has not been initialized', () => {
|
||||
const themeService = new ThemeService();
|
||||
expect(() => themeService.chartsTheme$).toThrowError();
|
||||
});
|
||||
it('returns the light theme when not in dark mode', async () => {
|
||||
startMock.uiSettings.get$.mockReturnValue(new BehaviorSubject(false));
|
||||
setupMockUiSettings.get$.mockReturnValue(new BehaviorSubject(false));
|
||||
const themeService = new ThemeService();
|
||||
themeService.init(setupMockUiSettings);
|
||||
|
||||
expect(
|
||||
await new EuiUtils()
|
||||
.start(startMock)
|
||||
.getChartsTheme$()
|
||||
.pipe(take(1))
|
||||
.toPromise()
|
||||
).toEqual(EUI_CHARTS_THEME_LIGHT.theme);
|
||||
expect(await themeService.chartsTheme$.pipe(take(1)).toPromise()).toEqual(
|
||||
EUI_CHARTS_THEME_LIGHT.theme
|
||||
);
|
||||
});
|
||||
|
||||
describe('in dark mode', () => {
|
||||
it(`returns the dark theme`, async () => {
|
||||
// Fake dark theme turned returning true
|
||||
startMock.uiSettings.get$.mockReturnValue(new BehaviorSubject(true));
|
||||
setupMockUiSettings.get$.mockReturnValue(new BehaviorSubject(true));
|
||||
const themeService = new ThemeService();
|
||||
themeService.init(setupMockUiSettings);
|
||||
|
||||
expect(
|
||||
await new EuiUtils()
|
||||
.start(startMock)
|
||||
.getChartsTheme$()
|
||||
.pipe(take(1))
|
||||
.toPromise()
|
||||
).toEqual(EUI_CHARTS_THEME_DARK.theme);
|
||||
expect(await themeService.chartsTheme$.pipe(take(1)).toPromise()).toEqual(
|
||||
EUI_CHARTS_THEME_DARK.theme
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useChartsTheme()', () => {
|
||||
describe('useChartsTheme', () => {
|
||||
it('updates when the uiSettings change', () => {
|
||||
const darkMode$ = new BehaviorSubject(false);
|
||||
startMock.uiSettings.get$.mockReturnValue(darkMode$);
|
||||
const { useChartsTheme } = new EuiUtils().start(startMock);
|
||||
setupMockUiSettings.get$.mockReturnValue(darkMode$);
|
||||
const themeService = new ThemeService();
|
||||
themeService.init(setupMockUiSettings);
|
||||
const { useChartsTheme } = themeService;
|
||||
|
||||
const { result } = renderHook(() => useChartsTheme());
|
||||
expect(result.current).toBe(EUI_CHARTS_THEME_LIGHT.theme);
|
63
src/plugins/charts/public/services/theme/theme.ts
Normal file
63
src/plugins/charts/public/services/theme/theme.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { CoreSetup } from 'kibana/public';
|
||||
import { RecursivePartial, Theme } from '@elastic/charts';
|
||||
import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme';
|
||||
|
||||
export class ThemeService {
|
||||
private _chartsTheme$?: Observable<RecursivePartial<Theme>>;
|
||||
|
||||
/** Returns default charts theme */
|
||||
public readonly chartsDefaultTheme = EUI_CHARTS_THEME_LIGHT.theme;
|
||||
|
||||
/** An observable of the current charts theme */
|
||||
public get chartsTheme$(): Observable<RecursivePartial<Theme>> {
|
||||
if (!this._chartsTheme$) {
|
||||
throw new Error('ThemeService not initialized');
|
||||
}
|
||||
|
||||
return this._chartsTheme$;
|
||||
}
|
||||
|
||||
/** A React hook for consuming the charts theme */
|
||||
public useChartsTheme = () => {
|
||||
const [value, update] = useState(this.chartsDefaultTheme);
|
||||
|
||||
useEffect(() => {
|
||||
const s = this.chartsTheme$.subscribe(update);
|
||||
return () => s.unsubscribe();
|
||||
}, []);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
/** initialize service with uiSettings */
|
||||
public init(uiSettings: CoreSetup['uiSettings']) {
|
||||
this._chartsTheme$ = uiSettings
|
||||
.get$('theme:darkMode')
|
||||
.pipe(
|
||||
map(darkMode => (darkMode ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme))
|
||||
);
|
||||
}
|
||||
}
|
|
@ -33,11 +33,21 @@ export interface ColorSchema {
|
|||
text: string;
|
||||
}
|
||||
|
||||
export const vislibColorMaps = {
|
||||
export interface RawColorSchema {
|
||||
id: ColorSchemas;
|
||||
label: string;
|
||||
value: Array<[number, number[]]>;
|
||||
}
|
||||
|
||||
export interface ColorMap {
|
||||
[key: string]: RawColorSchema;
|
||||
}
|
||||
|
||||
export const vislibColorMaps: ColorMap = {
|
||||
// Sequential
|
||||
[ColorSchemas.Blues]: {
|
||||
id: ColorSchemas.Blues,
|
||||
label: i18n.translate('common.ui.vislib.colormaps.bluesText', {
|
||||
label: i18n.translate('charts.colormaps.bluesText', {
|
||||
defaultMessage: 'Blues',
|
||||
}),
|
||||
value: [
|
||||
|
@ -557,7 +567,7 @@ export const vislibColorMaps = {
|
|||
},
|
||||
[ColorSchemas.Greens]: {
|
||||
id: ColorSchemas.Greens,
|
||||
label: i18n.translate('common.ui.vislib.colormaps.greensText', {
|
||||
label: i18n.translate('charts.colormaps.greensText', {
|
||||
defaultMessage: 'Greens',
|
||||
}),
|
||||
value: [
|
||||
|
@ -1077,7 +1087,7 @@ export const vislibColorMaps = {
|
|||
},
|
||||
[ColorSchemas.Greys]: {
|
||||
id: ColorSchemas.Greys,
|
||||
label: i18n.translate('common.ui.vislib.colormaps.greysText', {
|
||||
label: i18n.translate('charts.colormaps.greysText', {
|
||||
defaultMessage: 'Greys',
|
||||
}),
|
||||
value: [
|
||||
|
@ -1597,7 +1607,7 @@ export const vislibColorMaps = {
|
|||
},
|
||||
[ColorSchemas.Reds]: {
|
||||
id: ColorSchemas.Reds,
|
||||
label: i18n.translate('common.ui.vislib.colormaps.redsText', {
|
||||
label: i18n.translate('charts.colormaps.redsText', {
|
||||
defaultMessage: 'Reds',
|
||||
}),
|
||||
value: [
|
||||
|
@ -2117,7 +2127,7 @@ export const vislibColorMaps = {
|
|||
},
|
||||
[ColorSchemas.YellowToRed]: {
|
||||
id: ColorSchemas.YellowToRed,
|
||||
label: i18n.translate('common.ui.vislib.colormaps.yellowToRedText', {
|
||||
label: i18n.translate('charts.colormaps.yellowToRedText', {
|
||||
defaultMessage: 'Yellow to Red',
|
||||
}),
|
||||
value: [
|
||||
|
@ -2638,7 +2648,7 @@ export const vislibColorMaps = {
|
|||
|
||||
[ColorSchemas.GreenToRed]: {
|
||||
id: ColorSchemas.GreenToRed,
|
||||
label: i18n.translate('common.ui.vislib.colormaps.greenToRedText', {
|
||||
label: i18n.translate('charts.colormaps.greenToRedText', {
|
||||
defaultMessage: 'Green to Red',
|
||||
}),
|
||||
value: [
|
||||
|
@ -3158,7 +3168,7 @@ export const vislibColorMaps = {
|
|||
},
|
||||
};
|
||||
|
||||
export const colorSchemas = Object.values(vislibColorMaps).map(({ id, label }) => ({
|
||||
export const colorSchemas: ColorSchema[] = Object.values(vislibColorMaps).map(({ id, label }) => ({
|
||||
value: id,
|
||||
text: label,
|
||||
}));
|
|
@ -17,76 +17,83 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { getHeatmapColors } from './heatmap_color';
|
||||
|
||||
import { getHeatmapColors } from '../../../legacy_imports';
|
||||
|
||||
describe('Vislib Heatmap Color Module Test Suite', function() {
|
||||
describe('Vislib Heatmap Color Module Test Suite', () => {
|
||||
const emptyObject = {};
|
||||
const nullValue = null;
|
||||
let notAValue;
|
||||
|
||||
it('should throw an error if schema is invalid', function() {
|
||||
expect(function() {
|
||||
it('should throw an error if schema is invalid', () => {
|
||||
expect(() => {
|
||||
getHeatmapColors(4, 'invalid schema');
|
||||
}).to.throwError();
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should throw an error if input is not a number', function() {
|
||||
expect(function() {
|
||||
it('should throw an error if input is not a number', () => {
|
||||
expect(() => {
|
||||
getHeatmapColors([200], 'Greens');
|
||||
}).to.throwError();
|
||||
}).toThrowError();
|
||||
|
||||
expect(function() {
|
||||
expect(() => {
|
||||
getHeatmapColors('help', 'Greens');
|
||||
}).to.throwError();
|
||||
}).toThrowError();
|
||||
|
||||
expect(function() {
|
||||
expect(() => {
|
||||
getHeatmapColors(true, 'Greens');
|
||||
}).to.throwError();
|
||||
}).toThrowError();
|
||||
|
||||
expect(function() {
|
||||
getHeatmapColors(notAValue, 'Greens');
|
||||
}).to.throwError();
|
||||
expect(() => {
|
||||
getHeatmapColors(undefined, 'Greens');
|
||||
}).toThrowError();
|
||||
|
||||
expect(function() {
|
||||
expect(() => {
|
||||
getHeatmapColors(nullValue, 'Greens');
|
||||
}).to.throwError();
|
||||
}).toThrowError();
|
||||
|
||||
expect(function() {
|
||||
expect(() => {
|
||||
getHeatmapColors(emptyObject, 'Greens');
|
||||
}).to.throwError();
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should throw an error if input is less than 0', function() {
|
||||
expect(function() {
|
||||
it('should throw an error if input is less than 0', () => {
|
||||
expect(() => {
|
||||
getHeatmapColors(-2, 'Greens');
|
||||
}).to.throwError();
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should throw an error if input is greater than 1', function() {
|
||||
expect(function() {
|
||||
it('should throw an error if input is greater than 1', () => {
|
||||
expect(() => {
|
||||
getHeatmapColors(2, 'Greens');
|
||||
}).to.throwError();
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should be a function', function() {
|
||||
expect(typeof getHeatmapColors).to.be('function');
|
||||
it('should be a function', () => {
|
||||
expect(typeof getHeatmapColors).toBe('function');
|
||||
});
|
||||
|
||||
it('should return a color for 10 numbers from 0 to 1', function() {
|
||||
it('should return a color for 10 numbers from 0 to 1', () => {
|
||||
const colorRegex = /^rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)$/;
|
||||
const schema = 'Greens';
|
||||
for (let i = 0; i < 10; i++) {
|
||||
expect(getHeatmapColors(i / 10, schema)).to.match(colorRegex);
|
||||
expect(getHeatmapColors(i / 10, schema)).toMatch(colorRegex);
|
||||
}
|
||||
});
|
||||
|
||||
describe('drawColormap function', () => {
|
||||
const canvasElement = {
|
||||
getContext: jest.fn(() => ({
|
||||
fillStyle: null,
|
||||
fillRect: jest.fn(),
|
||||
})),
|
||||
};
|
||||
beforeEach(() => {
|
||||
jest.spyOn(document, 'createElement').mockImplementation(() => canvasElement as any);
|
||||
});
|
||||
|
||||
it('should return canvas element', () => {
|
||||
const response = getHeatmapColors.prototype.drawColormap('Greens');
|
||||
expect(typeof response).to.equal('object');
|
||||
expect(response instanceof window.HTMLElement).to.equal(true);
|
||||
expect(typeof response).toEqual('object');
|
||||
expect(response).toBe(canvasElement);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -18,9 +18,10 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { vislibColorMaps } from './color_maps';
|
||||
|
||||
function enforceBounds(x) {
|
||||
import { vislibColorMaps, RawColorSchema } from './color_maps';
|
||||
|
||||
function enforceBounds(x: number) {
|
||||
if (x < 0) {
|
||||
return 0;
|
||||
} else if (x > 1) {
|
||||
|
@ -30,12 +31,12 @@ function enforceBounds(x) {
|
|||
}
|
||||
}
|
||||
|
||||
function interpolateLinearly(x, values) {
|
||||
function interpolateLinearly(x: number, values: RawColorSchema['value']) {
|
||||
// Split values into four lists
|
||||
const xValues = [];
|
||||
const rValues = [];
|
||||
const gValues = [];
|
||||
const bValues = [];
|
||||
const xValues: number[] = [];
|
||||
const rValues: number[] = [];
|
||||
const gValues: number[] = [];
|
||||
const bValues: number[] = [];
|
||||
values.forEach(value => {
|
||||
xValues.push(value[0]);
|
||||
rValues.push(value[1][0]);
|
||||
|
@ -54,11 +55,12 @@ function interpolateLinearly(x, values) {
|
|||
return [enforceBounds(r), enforceBounds(g), enforceBounds(b)];
|
||||
}
|
||||
|
||||
export function getHeatmapColors(value, colorSchemaName) {
|
||||
export function getHeatmapColors(value: any, colorSchemaName: string) {
|
||||
if (!_.isNumber(value) || value < 0 || value > 1) {
|
||||
throw new Error('heatmap_color expects a number from 0 to 1 as first parameter');
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const colorSchema = vislibColorMaps[colorSchemaName].value;
|
||||
if (!colorSchema) {
|
||||
throw new Error('invalid colorSchemaName provided');
|
||||
|
@ -71,11 +73,16 @@ export function getHeatmapColors(value, colorSchemaName) {
|
|||
return `rgb(${r},${g},${b})`;
|
||||
}
|
||||
|
||||
function drawColormap(colorSchema, width = 100, height = 10) {
|
||||
function drawColormap(colorSchema: string, width = 100, height = 10) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
if (ctx === null) {
|
||||
throw new Error('no HeatmapColors canvas context found');
|
||||
}
|
||||
|
||||
for (let i = 0; i <= width; i++) {
|
||||
ctx.fillStyle = getHeatmapColors(i / width, colorSchema);
|
||||
ctx.fillRect(i, 0, 1, height);
|
29
src/plugins/charts/public/static/color_maps/index.ts
Normal file
29
src/plugins/charts/public/static/color_maps/index.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
ColorSchemas,
|
||||
ColorSchema,
|
||||
RawColorSchema,
|
||||
ColorMap,
|
||||
vislibColorMaps,
|
||||
colorSchemas,
|
||||
} from './color_maps';
|
||||
export { getHeatmapColors } from './heatmap_color';
|
||||
export { truncatedColorMaps, truncatedColorSchemas } from './truncated_color_maps';
|
31
src/plugins/charts/public/static/color_maps/mock.ts
Normal file
31
src/plugins/charts/public/static/color_maps/mock.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { colorSchemas, vislibColorMaps } from './color_maps';
|
||||
import { getHeatmapColors } from './heatmap_color';
|
||||
import { truncatedColorMaps, truncatedColorSchemas } from './truncated_color_maps';
|
||||
|
||||
// Note: Using actual values due to existing test dependencies
|
||||
export const colorMapsMock = {
|
||||
getHeatmapColors: jest.fn(getHeatmapColors),
|
||||
vislibColorMaps,
|
||||
colorSchemas,
|
||||
truncatedColorMaps,
|
||||
truncatedColorSchemas,
|
||||
} as any;
|
|
@ -17,22 +17,26 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { vislibColorMaps } from './color_maps';
|
||||
import { vislibColorMaps, ColorMap, ColorSchema } from './color_maps';
|
||||
|
||||
export const truncatedColorMaps = {};
|
||||
export const truncatedColorMaps: ColorMap = {};
|
||||
|
||||
const colormaps = vislibColorMaps;
|
||||
const colormaps: ColorMap = vislibColorMaps;
|
||||
for (const key in colormaps) {
|
||||
if (colormaps.hasOwnProperty(key)) {
|
||||
// slice off lightest colors
|
||||
// @ts-ignore
|
||||
const color = colormaps[key];
|
||||
truncatedColorMaps[key] = {
|
||||
...colormaps[key],
|
||||
value: colormaps[key].value.slice(Math.floor(colormaps[key].value.length / 4)),
|
||||
...color,
|
||||
value: color.value.slice(Math.floor(color.value.length / 4)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const truncatedColorSchemas = Object.values(truncatedColorMaps).map(({ id, label }) => ({
|
||||
value: id,
|
||||
text: label,
|
||||
}));
|
||||
export const truncatedColorSchemas: ColorSchema[] = Object.values(truncatedColorMaps).map(
|
||||
({ id, label }) => ({
|
||||
value: id,
|
||||
text: label,
|
||||
})
|
||||
);
|
|
@ -17,4 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { vislibColor } from './color';
|
||||
export * from './color_maps';
|
|
@ -1,67 +0,0 @@
|
|||
# EuiUtils
|
||||
|
||||
The EuiUtils plugin is a way to create easier integration of EUI colors, themes, and other utilities with Kibana. They usually take into account the current theme (light or dark) of Kibana and return the correct object that was asked for.
|
||||
|
||||
## EUI plus Elastic-Charts
|
||||
|
||||
EUI provides a light and dark theme object to work with Elastic-Charts. However, every instance of a Chart would need to pass down this the correctly EUI theme depending on Kibana's light or dark mode. There are several ways you can use EuiUtils to grab the correct theme.
|
||||
|
||||
### `useChartsTheme`
|
||||
|
||||
The simple fetching of the correct EUI theme; a **React hook**.
|
||||
|
||||
```js
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { Chart, Settings } from '@elastic/charts';
|
||||
|
||||
export const YourComponent = () => (
|
||||
<Chart>
|
||||
<Settings theme={npStart.plugins.eui_utils.useChartsTheme()} />
|
||||
</Chart>
|
||||
);
|
||||
```
|
||||
|
||||
### `getChartsTheme$`
|
||||
|
||||
An **observable** of the current charts theme. Use this implementation for more flexible updates to the chart theme without full page refreshes.
|
||||
|
||||
```ts
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { EuiChartThemeType } from '@elastic/eui/src/themes/charts/themes';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Chart, Settings } from '@elastic/charts';
|
||||
|
||||
interface YourComponentState {
|
||||
chartsTheme: EuiChartThemeType['theme'];
|
||||
}
|
||||
|
||||
export class YourComponent extends Component<YourComponentProps, YourComponentState> {
|
||||
private subscription?: Subscription;
|
||||
public state = {
|
||||
chartsTheme: npStart.plugins.eui_utils.getChartsThemeDefault(),
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.subscription = npStart.plugins.eui_utils
|
||||
.getChartsTheme$()
|
||||
.subscribe(chartsTheme => this.setState({ chartsTheme }));
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
this.subscription = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { chartsTheme } = this.state;
|
||||
|
||||
return (
|
||||
<Chart>
|
||||
<Settings theme={chartsTheme} />
|
||||
</Chart>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { CoreStart, CoreSetup } from 'src/core/public';
|
||||
import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
export class EuiUtils {
|
||||
public setup(core: CoreSetup) {}
|
||||
public start(core: CoreStart) {
|
||||
const getChartsThemeDefault = () => EUI_CHARTS_THEME_LIGHT.theme;
|
||||
|
||||
const getChartsTheme$ = () => {
|
||||
return core.uiSettings
|
||||
.get$('theme:darkMode')
|
||||
.pipe(
|
||||
map(darkMode => (darkMode ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme))
|
||||
);
|
||||
};
|
||||
|
||||
const useChartsTheme = () => {
|
||||
const [value, update] = useState(getChartsThemeDefault());
|
||||
|
||||
useEffect(() => {
|
||||
const s = getChartsTheme$().subscribe(update);
|
||||
return () => s.unsubscribe();
|
||||
}, []);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
return {
|
||||
/** The default charts theme */
|
||||
getChartsThemeDefault,
|
||||
|
||||
/** An observable of the current charts theme */
|
||||
getChartsTheme$,
|
||||
|
||||
/** A React hook for consuming the charts theme */
|
||||
useChartsTheme,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -30,7 +30,6 @@ import 'uiExports/shareContextMenuExtensions';
|
|||
import _ from 'lodash';
|
||||
import 'ui/autoload/all';
|
||||
import 'ui/kbn_top_nav';
|
||||
import 'ui/color_maps';
|
||||
import 'ui/agg_response';
|
||||
import 'ui/agg_types';
|
||||
import 'leaflet';
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
|
||||
import { InnerJoin } from './inner_join';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
jest.mock('ui/vis/editors/default/schemas', () => {
|
||||
class MockSchemas {}
|
||||
return {
|
||||
Schemas: MockSchemas,
|
||||
};
|
||||
});
|
||||
jest.mock('../../kibana_services', () => {});
|
||||
jest.mock('ui/agg_types', () => {});
|
||||
jest.mock('ui/timefilter', () => {});
|
||||
jest.mock('../vector_layer', () => {});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { EMSFileSource } from './ems_file_source';
|
||||
|
||||
jest.mock('../../../kibana_services', () => {});
|
||||
jest.mock('ui/new_platform');
|
||||
jest.mock('../../vector_layer', () => {});
|
||||
|
||||
function makeEMSFileSource(tooltipProperties) {
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
import { ESTermSource, extractPropertiesMap } from './es_term_source';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
jest.mock('../vector_layer', () => {});
|
||||
jest.mock('ui/vis/editors/default/schemas', () => ({
|
||||
Schemas: function() {},
|
||||
}));
|
||||
jest.mock('../../kibana_services', () => {});
|
||||
jest.mock('ui/agg_types', () => {});
|
||||
jest.mock('ui/timefilter', () => {});
|
||||
|
||||
|
|
|
@ -5,14 +5,16 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { vislibColorMaps } from 'ui/color_maps';
|
||||
import { getLegendColors, getColor } from 'ui/vis/map/color_util';
|
||||
import { ColorGradient } from './components/color_gradient';
|
||||
import { euiPaletteColorBlind } from '@elastic/eui/lib/services';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import chroma from 'chroma-js';
|
||||
|
||||
import { euiPaletteColorBlind } from '@elastic/eui/lib/services';
|
||||
|
||||
import { getLegendColors, getColor } from 'ui/vis/map/color_util';
|
||||
|
||||
import { ColorGradient } from './components/color_gradient';
|
||||
import { COLOR_PALETTE_MAX_SIZE } from '../../../common/constants';
|
||||
import { vislibColorMaps } from '../../../../../../../src/plugins/charts/public';
|
||||
|
||||
const GRADIENT_INTERVALS = 8;
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ import {
|
|||
getRGBColorRangeStrings,
|
||||
} from './color_utils';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
describe('COLOR_GRADIENTS', () => {
|
||||
it('Should contain EuiSuperSelect options list of color ramps', () => {
|
||||
expect(COLOR_GRADIENTS.length).toBe(6);
|
||||
|
|
|
@ -9,6 +9,8 @@ import { shallow } from 'enzyme';
|
|||
|
||||
import { HeatmapStyleEditor } from './heatmap_style_editor';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
describe('HeatmapStyleEditor', () => {
|
||||
test('is rendered', () => {
|
||||
const component = shallow(
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
jest.mock('../components/vector_style_editor', () => ({
|
||||
VectorStyleEditor: () => {
|
||||
return <div>mockVectorStyleEditor</div>;
|
||||
|
|
|
@ -9,6 +9,8 @@ import { DataRequest } from '../../util/data_request';
|
|||
import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types';
|
||||
import { FIELD_ORIGIN } from '../../../../common/constants';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
class MockField {
|
||||
constructor({ fieldName }) {
|
||||
this._fieldName = fieldName;
|
||||
|
|
|
@ -100,7 +100,7 @@ export const ThresholdVisualization: React.FunctionComponent<Props> = ({ alert }
|
|||
const [error, setError] = useState<undefined | any>(undefined);
|
||||
const [visualizationData, setVisualizationData] = useState<Record<string, any>>([]);
|
||||
|
||||
const chartsTheme = npStart.plugins.eui_utils.useChartsTheme();
|
||||
const chartsTheme = npStart.plugins.charts.theme.useChartsTheme();
|
||||
const {
|
||||
index,
|
||||
timeField,
|
||||
|
|
|
@ -506,12 +506,12 @@
|
|||
"common.ui.vis.editors.sidebar.tabs.optionsLabel": "オプション",
|
||||
"common.ui.vis.kibanaMap.leaflet.fitDataBoundsAriaLabel": "データバウンドを合わせる",
|
||||
"common.ui.vis.kibanaMap.zoomWarning": "ズームレベルが最大に達しました。完全にズームインするには、Elasticsearch と Kibana の {defaultDistribution} にアップグレードしてください。{ems} でより多くのズームレベルが利用できます。または、独自のマップサーバーを構成できます。詳細は { wms } または { configSettings} をご覧ください。",
|
||||
"common.ui.vislib.colormaps.bluesText": "青",
|
||||
"common.ui.vislib.colormaps.greensText": "緑",
|
||||
"common.ui.vislib.colormaps.greenToRedText": "緑から赤",
|
||||
"common.ui.vislib.colormaps.greysText": "グレー",
|
||||
"common.ui.vislib.colormaps.redsText": "赤",
|
||||
"common.ui.vislib.colormaps.yellowToRedText": "黄色から赤",
|
||||
"charts.colormaps.bluesText": "青",
|
||||
"charts.colormaps.greensText": "緑",
|
||||
"charts.colormaps.greenToRedText": "緑から赤",
|
||||
"charts.colormaps.greysText": "グレー",
|
||||
"charts.colormaps.redsText": "赤",
|
||||
"charts.colormaps.yellowToRedText": "黄色から赤",
|
||||
"common.ui.visualize.queryGeohashBounds.unableToGetBoundErrorTitle": "バウンドを取得できませんでした",
|
||||
"console.autocomplete.addMethodMetaText": "メソド",
|
||||
"console.consoleDisplayName": "コンソール",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue