mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[TileMap][RegionMap] Implement custom renderers and expression builders (#84775)
* Convert to typescript * Move related files directly into plugin * Implement toExpressionAst * Remove build_pipeline dedicated fn * Async import converter * Create a custom renderer * Remove ExprVis instance usage in maps visualizations * Use uiState updates * Create wrapper component * Update rendering * Create region map expression renderer * Remove resize subscription * Fix custom visualization expression * Update interpreter functional tests * Use types from geojson Co-authored-by: Alexey Antonov <alexwizp@gmail.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
1c38f908cd
commit
fbfc50920b
53 changed files with 841 additions and 387 deletions
|
@ -110,7 +110,7 @@ export class ExpressionRenderHandler {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render = async (value: any, uiState: any = {}) => {
|
render = async (value: any, uiState?: any) => {
|
||||||
if (!value || typeof value !== 'object') {
|
if (!value || typeof value !== 'object') {
|
||||||
return this.handleRenderError(new Error('invalid data provided to the expression renderer'));
|
return this.handleRenderError(new Error('invalid data provided to the expression renderer'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { TmsLayer } from '../../index';
|
import { TmsLayer } from '..';
|
||||||
import { MapTypes } from './map_types';
|
|
||||||
|
|
||||||
export interface WMSOptions {
|
export interface WMSOptions {
|
||||||
selectedTmsLayer?: TmsLayer;
|
selectedTmsLayer?: TmsLayer;
|
||||||
|
@ -33,15 +32,3 @@ export interface WMSOptions {
|
||||||
styles?: string;
|
styles?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TileMapVisParams {
|
|
||||||
colorSchema: string;
|
|
||||||
mapType: MapTypes;
|
|
||||||
isDesaturated: boolean;
|
|
||||||
addTooltip: boolean;
|
|
||||||
heatClusterSize: number;
|
|
||||||
legendPosition: 'bottomright' | 'bottomleft' | 'topright' | 'topleft';
|
|
||||||
mapZoom: number;
|
|
||||||
mapCenter: [number, number];
|
|
||||||
wms: WMSOptions;
|
|
||||||
}
|
|
|
@ -22,7 +22,7 @@ import { EuiLink, EuiSpacer, EuiText, EuiScreenReaderOnly } from '@elastic/eui';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
|
|
||||||
import { TextInputOption } from '../../../vis_default_editor/public';
|
import { TextInputOption } from '../../../vis_default_editor/public';
|
||||||
import { WMSOptions } from '../common/types/external_basemap_types';
|
import { WMSOptions } from '../common/types';
|
||||||
|
|
||||||
interface WmsInternalOptions {
|
interface WmsInternalOptions {
|
||||||
wms: WMSOptions;
|
wms: WMSOptions;
|
||||||
|
|
|
@ -23,20 +23,19 @@ import { i18n } from '@kbn/i18n';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import { TmsLayer } from '../index';
|
import { TmsLayer } from '../index';
|
||||||
import { Vis } from '../../../visualizations/public';
|
import { Vis } from '../../../visualizations/public';
|
||||||
import { RegionMapVisParams } from '../common/types/region_map_types';
|
|
||||||
import { SelectOption, SwitchOption } from '../../../vis_default_editor/public';
|
import { SelectOption, SwitchOption } from '../../../vis_default_editor/public';
|
||||||
import { WmsInternalOptions } from './wms_internal_options';
|
import { WmsInternalOptions } from './wms_internal_options';
|
||||||
import { WMSOptions, TileMapVisParams } from '../common/types/external_basemap_types';
|
import { WMSOptions } from '../common/types';
|
||||||
|
|
||||||
interface Props {
|
interface Props<K> {
|
||||||
stateParams: TileMapVisParams | RegionMapVisParams;
|
stateParams: K;
|
||||||
setValue: (title: 'wms', options: WMSOptions) => void;
|
setValue: (title: 'wms', options: WMSOptions) => void;
|
||||||
vis: Vis;
|
vis: Vis;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapLayerForOption = ({ id }: TmsLayer) => ({ text: id, value: id });
|
const mapLayerForOption = ({ id }: TmsLayer) => ({ text: id, value: id });
|
||||||
|
|
||||||
function WmsOptions({ stateParams, setValue, vis }: Props) {
|
function WmsOptions<K extends { wms: WMSOptions }>({ stateParams, setValue, vis }: Props<K>) {
|
||||||
const { wms } = stateParams;
|
const { wms } = stateParams;
|
||||||
const { tmsLayers } = vis.type.editorConfig.collections;
|
const { tmsLayers } = vis.type.editorConfig.collections;
|
||||||
const tmsLayerOptions = useMemo(() => tmsLayers.map(mapLayerForOption), [tmsLayers]);
|
const tmsLayerOptions = useMemo(() => tmsLayers.map(mapLayerForOption), [tmsLayers]);
|
||||||
|
|
|
@ -17,17 +17,12 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
import { PluginInitializerContext } from 'kibana/public';
|
import { PluginInitializerContext } from 'kibana/public';
|
||||||
import { MapsLegacyPlugin } from './plugin';
|
import { MapsLegacyPlugin } from './plugin';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as colorUtil from './map/color_util';
|
import * as colorUtil from './map/color_util';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { KibanaMapLayer } from './map/kibana_map_layer';
|
import { KibanaMapLayer } from './map/kibana_map_layer';
|
||||||
// @ts-ignore
|
|
||||||
import { convertToGeoJson } from './map/convert_to_geojson';
|
|
||||||
// @ts-ignore
|
|
||||||
import { getPrecision, geoContains } from './map/decode_geo_hash';
|
|
||||||
import {
|
import {
|
||||||
VectorLayer,
|
VectorLayer,
|
||||||
FileLayerField,
|
FileLayerField,
|
||||||
|
@ -46,10 +41,7 @@ export function plugin(initializerContext: PluginInitializerContext) {
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export {
|
export {
|
||||||
getPrecision,
|
|
||||||
geoContains,
|
|
||||||
colorUtil,
|
colorUtil,
|
||||||
convertToGeoJson,
|
|
||||||
IServiceSettings,
|
IServiceSettings,
|
||||||
KibanaMapLayer,
|
KibanaMapLayer,
|
||||||
VectorLayer,
|
VectorLayer,
|
||||||
|
|
|
@ -34,8 +34,9 @@ export function BaseMapsVisualizationProvider() {
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
return class BaseMapsVisualization {
|
return class BaseMapsVisualization {
|
||||||
constructor(element, vis) {
|
constructor(element, handlers, initialVisParams) {
|
||||||
this.vis = vis;
|
this.handlers = handlers;
|
||||||
|
this._params = initialVisParams;
|
||||||
this._container = element;
|
this._container = element;
|
||||||
this._kibanaMap = null;
|
this._kibanaMap = null;
|
||||||
this._chartData = null; //reference to data currently on the map.
|
this._chartData = null; //reference to data currently on the map.
|
||||||
|
@ -61,25 +62,31 @@ export function BaseMapsVisualizationProvider() {
|
||||||
* @param status
|
* @param status
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
async render(esResponse, visParams) {
|
async render(esResponse = this._esResponse, visParams = this._params) {
|
||||||
|
await this._mapIsLoaded;
|
||||||
|
|
||||||
if (!this._kibanaMap) {
|
if (!this._kibanaMap) {
|
||||||
//the visualization has been destroyed;
|
//the visualization has been destroyed;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this._mapIsLoaded;
|
this.resize();
|
||||||
this._kibanaMap.resize();
|
|
||||||
this._params = visParams;
|
this._params = visParams;
|
||||||
await this._updateParams();
|
await this._updateParams();
|
||||||
|
|
||||||
if (this._hasESResponseChanged(esResponse)) {
|
if (this._hasESResponseChanged(esResponse)) {
|
||||||
|
this._esResponse = esResponse;
|
||||||
await this._updateData(esResponse);
|
await this._updateData(esResponse);
|
||||||
}
|
}
|
||||||
this._kibanaMap.useUiStateFromVisualization(this.vis);
|
this._kibanaMap.useUiStateFromVisualization(this.handlers.uiState);
|
||||||
|
|
||||||
await this._whenBaseLayerIsLoaded();
|
await this._whenBaseLayerIsLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resize() {
|
||||||
|
this._kibanaMap?.resize();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of a kibana-map with a single baselayer and assigns it to the this._kibanaMap property.
|
* Creates an instance of a kibana-map with a single baselayer and assigns it to the this._kibanaMap property.
|
||||||
* Clients can override this method to customize the initialization.
|
* Clients can override this method to customize the initialization.
|
||||||
|
@ -87,11 +94,11 @@ export function BaseMapsVisualizationProvider() {
|
||||||
*/
|
*/
|
||||||
async _makeKibanaMap() {
|
async _makeKibanaMap() {
|
||||||
const options = {};
|
const options = {};
|
||||||
const uiState = this.vis.getUiState();
|
const zoomFromUiState = parseInt(this.handlers.uiState?.get('mapZoom'));
|
||||||
const zoomFromUiState = parseInt(uiState.get('mapZoom'));
|
const centerFromUIState = this.handlers.uiState?.get('mapCenter');
|
||||||
const centerFromUIState = uiState.get('mapCenter');
|
const { mapZoom, mapCenter } = this._getMapsParams();
|
||||||
options.zoom = !isNaN(zoomFromUiState) ? zoomFromUiState : this.vis.params.mapZoom;
|
options.zoom = !isNaN(zoomFromUiState) ? zoomFromUiState : mapZoom;
|
||||||
options.center = centerFromUIState ? centerFromUIState : this.vis.params.mapCenter;
|
options.center = centerFromUIState ? centerFromUIState : mapCenter;
|
||||||
|
|
||||||
const modules = await lazyLoadMapsLegacyModules();
|
const modules = await lazyLoadMapsLegacyModules();
|
||||||
this._kibanaMap = new modules.KibanaMap(this._container, options);
|
this._kibanaMap = new modules.KibanaMap(this._container, options);
|
||||||
|
@ -100,7 +107,7 @@ export function BaseMapsVisualizationProvider() {
|
||||||
|
|
||||||
this._kibanaMap.addLegendControl();
|
this._kibanaMap.addLegendControl();
|
||||||
this._kibanaMap.addFitControl();
|
this._kibanaMap.addFitControl();
|
||||||
this._kibanaMap.persistUiStateForVisualization(this.vis);
|
this._kibanaMap.persistUiStateForVisualization(this.handlers.uiState);
|
||||||
|
|
||||||
this._kibanaMap.on('baseLayer:loaded', () => {
|
this._kibanaMap.on('baseLayer:loaded', () => {
|
||||||
this._baseLayerDirty = false;
|
this._baseLayerDirty = false;
|
||||||
|
@ -212,7 +219,7 @@ export function BaseMapsVisualizationProvider() {
|
||||||
}
|
}
|
||||||
|
|
||||||
_hasESResponseChanged(data) {
|
_hasESResponseChanged(data) {
|
||||||
return this._chartData !== data;
|
return this._esResponse !== data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -223,15 +230,11 @@ export function BaseMapsVisualizationProvider() {
|
||||||
await this._updateBaseLayer();
|
await this._updateBaseLayer();
|
||||||
this._kibanaMap.setLegendPosition(mapParams.legendPosition);
|
this._kibanaMap.setLegendPosition(mapParams.legendPosition);
|
||||||
this._kibanaMap.setShowTooltip(mapParams.addTooltip);
|
this._kibanaMap.setShowTooltip(mapParams.addTooltip);
|
||||||
this._kibanaMap.useUiStateFromVisualization(this.vis);
|
this._kibanaMap.useUiStateFromVisualization(this.handlers.uiState);
|
||||||
}
|
}
|
||||||
|
|
||||||
_getMapsParams() {
|
_getMapsParams() {
|
||||||
return {
|
return this._params;
|
||||||
...this.vis.type.visConfig.defaults,
|
|
||||||
type: this.vis.type.name,
|
|
||||||
...this._params,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_whenBaseLayerIsLoaded() {
|
_whenBaseLayerIsLoaded() {
|
||||||
|
|
27
src/plugins/maps_legacy/public/map/geohash_columns.test.ts
Normal file
27
src/plugins/maps_legacy/public/map/geohash_columns.test.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 { geohashColumns } from './geohash_columns';
|
||||||
|
|
||||||
|
test('geohashColumns', () => {
|
||||||
|
expect(geohashColumns(1)).toBe(8);
|
||||||
|
expect(geohashColumns(2)).toBe(8 * 4);
|
||||||
|
expect(geohashColumns(3)).toBe(8 * 4 * 8);
|
||||||
|
expect(geohashColumns(4)).toBe(8 * 4 * 8 * 4);
|
||||||
|
});
|
38
src/plugins/maps_legacy/public/map/geohash_columns.ts
Normal file
38
src/plugins/maps_legacy/public/map/geohash_columns.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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 function geohashColumns(precision: number): number {
|
||||||
|
return geohashCells(precision, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of geohash cells for a given precision
|
||||||
|
*
|
||||||
|
* @param {number} precision the geohash precision (1<=precision<=12).
|
||||||
|
* @param {number} axis constant for the axis 0=lengthwise (ie. columns, along longitude), 1=heightwise (ie. rows, along latitude).
|
||||||
|
* @returns {number} Number of geohash cells (rows or columns) at that precision
|
||||||
|
*/
|
||||||
|
function geohashCells(precision: number, axis: number) {
|
||||||
|
let cells = 1;
|
||||||
|
for (let i = 1; i <= precision; i += 1) {
|
||||||
|
/* On odd precisions, rows divide by 4 and columns by 8. Vice-versa on even precisions */
|
||||||
|
cells *= i % 2 === axis ? 4 : 8;
|
||||||
|
}
|
||||||
|
return cells;
|
||||||
|
}
|
|
@ -672,14 +672,13 @@ export class KibanaMap extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
persistUiStateForVisualization(visualization) {
|
persistUiStateForVisualization(uiState) {
|
||||||
function persistMapStateInUiState() {
|
function persistMapStateInUiState() {
|
||||||
const uiState = visualization.getUiState();
|
|
||||||
const centerFromUIState = uiState.get('mapCenter');
|
const centerFromUIState = uiState.get('mapCenter');
|
||||||
const zoomFromUiState = parseInt(uiState.get('mapZoom'));
|
const zoomFromUiState = parseInt(uiState.get('mapZoom'));
|
||||||
|
|
||||||
if (isNaN(zoomFromUiState) || this.getZoomLevel() !== zoomFromUiState) {
|
if (isNaN(zoomFromUiState) || this.getZoomLevel() !== zoomFromUiState) {
|
||||||
visualization.uiStateVal('mapZoom', this.getZoomLevel());
|
uiState.set('mapZoom', this.getZoomLevel());
|
||||||
}
|
}
|
||||||
const centerFromMap = this.getCenter();
|
const centerFromMap = this.getCenter();
|
||||||
if (
|
if (
|
||||||
|
@ -687,24 +686,17 @@ export class KibanaMap extends EventEmitter {
|
||||||
centerFromMap.lon !== centerFromUIState[1] ||
|
centerFromMap.lon !== centerFromUIState[1] ||
|
||||||
centerFromMap.lat !== centerFromUIState[0]
|
centerFromMap.lat !== centerFromUIState[0]
|
||||||
) {
|
) {
|
||||||
visualization.uiStateVal('mapCenter', [centerFromMap.lat, centerFromMap.lon]);
|
uiState.set('mapCenter', [centerFromMap.lat, centerFromMap.lon]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._leafletMap.on('resize', () => {
|
|
||||||
visualization.sessionState.mapBounds = this.getBounds();
|
|
||||||
});
|
|
||||||
this._leafletMap.on('load', () => {
|
|
||||||
visualization.sessionState.mapBounds = this.getBounds();
|
|
||||||
});
|
|
||||||
this.on('dragend', persistMapStateInUiState);
|
this.on('dragend', persistMapStateInUiState);
|
||||||
this.on('zoomend', persistMapStateInUiState);
|
this.on('zoomend', persistMapStateInUiState);
|
||||||
}
|
}
|
||||||
|
|
||||||
useUiStateFromVisualization(visualization) {
|
useUiStateFromVisualization(uiState) {
|
||||||
const uiState = visualization.getUiState();
|
const zoomFromUiState = parseInt(uiState?.get('mapZoom'));
|
||||||
const zoomFromUiState = parseInt(uiState.get('mapZoom'));
|
const centerFromUIState = uiState?.get('mapCenter');
|
||||||
const centerFromUIState = uiState.get('mapCenter');
|
|
||||||
if (!isNaN(zoomFromUiState)) {
|
if (!isNaN(zoomFromUiState)) {
|
||||||
this.setZoomLevel(zoomFromUiState);
|
this.setZoomLevel(zoomFromUiState);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { getUiSettings } from '../kibana_services';
|
import { getUiSettings } from '../kibana_services';
|
||||||
import { geohashColumns } from './decode_geo_hash';
|
import { geohashColumns } from './geohash_columns';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the number of geohash columns (world-wide) for a given precision
|
* Get the number of geohash columns (world-wide) for a given precision
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { geohashColumns } from './decode_geo_hash';
|
import { geohashColumns } from './geohash_columns';
|
||||||
|
|
||||||
const defaultMaxPrecision = 12;
|
const defaultMaxPrecision = 12;
|
||||||
const minGeoHashPixels = 16;
|
const minGeoHashPixels = 16;
|
||||||
|
|
|
@ -2,12 +2,9 @@
|
||||||
|
|
||||||
exports[`interpreter/functions#regionmap returns an object with the correct structure 1`] = `
|
exports[`interpreter/functions#regionmap returns an object with the correct structure 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"as": "visualization",
|
"as": "region_map_vis",
|
||||||
"type": "render",
|
"type": "render",
|
||||||
"value": Object {
|
"value": Object {
|
||||||
"params": Object {
|
|
||||||
"listenOnChange": true,
|
|
||||||
},
|
|
||||||
"visConfig": Object {
|
"visConfig": Object {
|
||||||
"addTooltip": true,
|
"addTooltip": true,
|
||||||
"colorSchema": "Yellow to Red",
|
"colorSchema": "Yellow to Red",
|
29
src/plugins/region_map/public/components/index.tsx
Normal file
29
src/plugins/region_map/public/components/index.tsx
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 React, { lazy } from 'react';
|
||||||
|
import { IServiceSettings } from 'src/plugins/maps_legacy/public';
|
||||||
|
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
|
||||||
|
import { RegionMapVisParams } from '../region_map_types';
|
||||||
|
|
||||||
|
const RegionMapOptions = lazy(() => import('./region_map_options'));
|
||||||
|
|
||||||
|
export const createRegionMapOptions = (getServiceSettings: () => Promise<IServiceSettings>) => (
|
||||||
|
props: VisOptionsProps<RegionMapVisParams>
|
||||||
|
) => <RegionMapOptions {...props} getServiceSettings={getServiceSettings} />;
|
|
@ -24,7 +24,8 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
|
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
|
||||||
import { FileLayerField, VectorLayer, IServiceSettings } from '../../../maps_legacy/public';
|
import { FileLayerField, VectorLayer, IServiceSettings } from '../../../maps_legacy/public';
|
||||||
import { SelectOption, SwitchOption, NumberInputOption } from '../../../vis_default_editor/public';
|
import { SelectOption, SwitchOption, NumberInputOption } from '../../../vis_default_editor/public';
|
||||||
import { RegionMapVisParams, WmsOptions } from '../../../maps_legacy/public';
|
import { WmsOptions } from '../../../maps_legacy/public';
|
||||||
|
import { RegionMapVisParams } from '../region_map_types';
|
||||||
|
|
||||||
const mapLayerForOption = ({ layerId, name }: VectorLayer) => ({
|
const mapLayerForOption = ({ layerId, name }: VectorLayer) => ({
|
||||||
text: name,
|
text: name,
|
||||||
|
@ -212,4 +213,6 @@ function RegionMapOptions(props: RegionMapOptionsProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { RegionMapOptions };
|
// default export required for React.Lazy
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export { RegionMapOptions as default };
|
||||||
|
|
|
@ -44,9 +44,10 @@ import { RegionMapsConfigType } from './index';
|
||||||
import { MapsLegacyConfig } from '../../maps_legacy/config';
|
import { MapsLegacyConfig } from '../../maps_legacy/config';
|
||||||
import { KibanaLegacyStart } from '../../kibana_legacy/public';
|
import { KibanaLegacyStart } from '../../kibana_legacy/public';
|
||||||
import { SharePluginStart } from '../../share/public';
|
import { SharePluginStart } from '../../share/public';
|
||||||
|
import { getRegionMapRenderer } from './region_map_renderer';
|
||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
interface RegionMapVisualizationDependencies {
|
export interface RegionMapVisualizationDependencies {
|
||||||
uiSettings: IUiSettingsClient;
|
uiSettings: IUiSettingsClient;
|
||||||
regionmapsConfig: RegionMapsConfig;
|
regionmapsConfig: RegionMapsConfig;
|
||||||
getServiceSettings: () => Promise<IServiceSettings>;
|
getServiceSettings: () => Promise<IServiceSettings>;
|
||||||
|
@ -107,6 +108,7 @@ export class RegionMapPlugin implements Plugin<RegionMapPluginSetup, RegionMapPl
|
||||||
};
|
};
|
||||||
|
|
||||||
expressions.registerFunction(createRegionMapFn);
|
expressions.registerFunction(createRegionMapFn);
|
||||||
|
expressions.registerRenderer(getRegionMapRenderer(visualizationDependencies));
|
||||||
|
|
||||||
visualizations.createBaseVisualization(
|
visualizations.createBaseVisualization(
|
||||||
createRegionMapTypeDefinition(visualizationDependencies)
|
createRegionMapTypeDefinition(visualizationDependencies)
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// eslint-disable-next-line
|
|
||||||
import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils';
|
import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils';
|
||||||
import { createRegionMapFn } from './region_map_fn';
|
import { createRegionMapFn } from './region_map_fn';
|
||||||
|
|
||||||
|
@ -57,11 +56,7 @@ describe('interpreter/functions#regionmap', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
it('returns an object with the correct structure', () => {
|
it('returns an object with the correct structure', () => {
|
||||||
const actual = fn(
|
const actual = fn(context, { visConfig: JSON.stringify(visConfig) });
|
||||||
context,
|
|
||||||
{ visConfig: JSON.stringify(visConfig) },
|
|
||||||
{ logDatatable: jest.fn() }
|
|
||||||
);
|
|
||||||
expect(actual).toMatchSnapshot();
|
expect(actual).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -19,7 +19,27 @@
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
export const createRegionMapFn = () => ({
|
import type { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public';
|
||||||
|
import { RegionMapVisConfig } from './region_map_types';
|
||||||
|
|
||||||
|
interface Arguments {
|
||||||
|
visConfig: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegionMapVisRenderValue {
|
||||||
|
visData: Datatable;
|
||||||
|
visType: 'region_map';
|
||||||
|
visConfig: RegionMapVisConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RegionMapExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||||
|
'regionmap',
|
||||||
|
Datatable,
|
||||||
|
Arguments,
|
||||||
|
Render<RegionMapVisRenderValue>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const createRegionMapFn = (): RegionMapExpressionFunctionDefinition => ({
|
||||||
name: 'regionmap',
|
name: 'regionmap',
|
||||||
type: 'render',
|
type: 'render',
|
||||||
context: {
|
context: {
|
||||||
|
@ -32,24 +52,22 @@ export const createRegionMapFn = () => ({
|
||||||
visConfig: {
|
visConfig: {
|
||||||
types: ['string', 'null'],
|
types: ['string', 'null'],
|
||||||
default: '"{}"',
|
default: '"{}"',
|
||||||
|
help: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fn(context, args, handlers) {
|
fn(context, args, handlers) {
|
||||||
const visConfig = JSON.parse(args.visConfig);
|
const visConfig = args.visConfig && JSON.parse(args.visConfig);
|
||||||
|
|
||||||
if (handlers?.inspectorAdapters?.tables) {
|
if (handlers?.inspectorAdapters?.tables) {
|
||||||
handlers.inspectorAdapters.tables.logDatatable('default', context);
|
handlers.inspectorAdapters.tables.logDatatable('default', context);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: 'render',
|
type: 'render',
|
||||||
as: 'visualization',
|
as: 'region_map_vis',
|
||||||
value: {
|
value: {
|
||||||
visData: context,
|
visData: context,
|
||||||
visType: 'region_map',
|
visType: 'region_map',
|
||||||
visConfig,
|
visConfig,
|
||||||
params: {
|
|
||||||
listenOnChange: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
52
src/plugins/region_map/public/region_map_renderer.tsx
Normal file
52
src/plugins/region_map/public/region_map_renderer.tsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* 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 React, { lazy } from 'react';
|
||||||
|
import { render, unmountComponentAtNode } from 'react-dom';
|
||||||
|
|
||||||
|
import { ExpressionRenderDefinition } from 'src/plugins/expressions';
|
||||||
|
import { VisualizationContainer } from '../../visualizations/public';
|
||||||
|
import { RegionMapVisualizationDependencies } from './plugin';
|
||||||
|
import { RegionMapVisRenderValue } from './region_map_fn';
|
||||||
|
|
||||||
|
const RegionMapVisualization = lazy(() => import('./region_map_visualization_component'));
|
||||||
|
|
||||||
|
export const getRegionMapRenderer: (
|
||||||
|
deps: RegionMapVisualizationDependencies
|
||||||
|
) => ExpressionRenderDefinition<RegionMapVisRenderValue> = (deps) => ({
|
||||||
|
name: 'region_map_vis',
|
||||||
|
reuseDomNode: true,
|
||||||
|
render: async (domNode, { visConfig, visData }, handlers) => {
|
||||||
|
handlers.onDestroy(() => {
|
||||||
|
unmountComponentAtNode(domNode);
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<VisualizationContainer handlers={handlers}>
|
||||||
|
<RegionMapVisualization
|
||||||
|
deps={deps}
|
||||||
|
handlers={handlers}
|
||||||
|
visConfig={visConfig}
|
||||||
|
visData={visData}
|
||||||
|
/>
|
||||||
|
</VisualizationContainer>,
|
||||||
|
domNode
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -16,19 +16,24 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { mapToLayerWithId } from './util';
|
|
||||||
import { createRegionMapVisualization } from './region_map_visualization';
|
import { BaseVisTypeOptions } from '../../visualizations/public';
|
||||||
import { RegionMapOptions } from './components/region_map_options';
|
|
||||||
import { truncatedColorSchemas } from '../../charts/public';
|
import { truncatedColorSchemas } from '../../charts/public';
|
||||||
import { ORIGIN } from '../../maps_legacy/public';
|
import { ORIGIN } from '../../maps_legacy/public';
|
||||||
|
|
||||||
import { getDeprecationMessage } from './get_deprecation_message';
|
import { getDeprecationMessage } from './get_deprecation_message';
|
||||||
|
import { RegionMapVisualizationDependencies } from './plugin';
|
||||||
|
import { createRegionMapOptions } from './components';
|
||||||
|
import { toExpressionAst } from './to_ast';
|
||||||
|
import { RegionMapVisParams } from './region_map_types';
|
||||||
|
import { mapToLayerWithId } from './util';
|
||||||
|
|
||||||
export function createRegionMapTypeDefinition(dependencies) {
|
export function createRegionMapTypeDefinition({
|
||||||
const { uiSettings, regionmapsConfig, getServiceSettings } = dependencies;
|
uiSettings,
|
||||||
const visualization = createRegionMapVisualization(dependencies);
|
regionmapsConfig,
|
||||||
|
getServiceSettings,
|
||||||
|
}: RegionMapVisualizationDependencies): BaseVisTypeOptions<RegionMapVisParams> {
|
||||||
return {
|
return {
|
||||||
name: 'region_map',
|
name: 'region_map',
|
||||||
getInfoMessage: getDeprecationMessage,
|
getInfoMessage: getDeprecationMessage,
|
||||||
|
@ -50,14 +55,11 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
||||||
mapZoom: 2,
|
mapZoom: 2,
|
||||||
mapCenter: [0, 0],
|
mapCenter: [0, 0],
|
||||||
outlineWeight: 1,
|
outlineWeight: 1,
|
||||||
showAllShapes: true, //still under consideration
|
showAllShapes: true, // still under consideration
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
visualization,
|
|
||||||
editorConfig: {
|
editorConfig: {
|
||||||
optionsTemplate: (props) => (
|
optionsTemplate: createRegionMapOptions(getServiceSettings),
|
||||||
<RegionMapOptions {...props} getServiceSettings={getServiceSettings} />
|
|
||||||
),
|
|
||||||
collections: {
|
collections: {
|
||||||
colorSchemas: truncatedColorSchemas,
|
colorSchemas: truncatedColorSchemas,
|
||||||
vectorLayers: [],
|
vectorLayers: [],
|
||||||
|
@ -99,6 +101,7 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
toExpressionAst,
|
||||||
setup: async (vis) => {
|
setup: async (vis) => {
|
||||||
const serviceSettings = await getServiceSettings();
|
const serviceSettings = await getServiceSettings();
|
||||||
const tmsLayers = await serviceSettings.getTMSServices();
|
const tmsLayers = await serviceSettings.getTMSServices();
|
||||||
|
@ -111,7 +114,7 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
||||||
mapToLayerWithId.bind(null, ORIGIN.KIBANA_YML)
|
mapToLayerWithId.bind(null, ORIGIN.KIBANA_YML)
|
||||||
);
|
);
|
||||||
let selectedLayer = vectorLayers[0];
|
let selectedLayer = vectorLayers[0];
|
||||||
let selectedJoinField = selectedLayer ? selectedLayer.fields[0] : null;
|
let selectedJoinField = selectedLayer ? selectedLayer.fields[0] : undefined;
|
||||||
if (regionmapsConfig.includeElasticMapsService) {
|
if (regionmapsConfig.includeElasticMapsService) {
|
||||||
const layers = await serviceSettings.getFileLayers();
|
const layers = await serviceSettings.getFileLayers();
|
||||||
const newLayers = layers
|
const newLayers = layers
|
||||||
|
@ -132,7 +135,7 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
||||||
vis.type.editorConfig.collections.vectorLayers = [...vectorLayers, ...newLayers];
|
vis.type.editorConfig.collections.vectorLayers = [...vectorLayers, ...newLayers];
|
||||||
|
|
||||||
[selectedLayer] = vis.type.editorConfig.collections.vectorLayers;
|
[selectedLayer] = vis.type.editorConfig.collections.vectorLayers;
|
||||||
selectedJoinField = selectedLayer ? selectedLayer.fields[0] : null;
|
selectedJoinField = selectedLayer ? selectedLayer.fields[0] : undefined;
|
||||||
|
|
||||||
if (selectedLayer && !vis.params.selectedLayer && selectedLayer.isEMS) {
|
if (selectedLayer && !vis.params.selectedLayer && selectedLayer.isEMS) {
|
||||||
vis.params.emsHotLink = await serviceSettings.getEMSHotLink(selectedLayer);
|
vis.params.emsHotLink = await serviceSettings.getEMSHotLink(selectedLayer);
|
|
@ -17,8 +17,8 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { VectorLayer, FileLayerField } from '../../index';
|
import { SchemaConfig } from 'src/plugins/visualizations/public';
|
||||||
import { WMSOptions } from './external_basemap_types';
|
import { VectorLayer, FileLayerField, WMSOptions } from '../../maps_legacy/public/index';
|
||||||
|
|
||||||
export interface RegionMapVisParams {
|
export interface RegionMapVisParams {
|
||||||
readonly addTooltip: true;
|
readonly addTooltip: true;
|
||||||
|
@ -34,3 +34,8 @@ export interface RegionMapVisParams {
|
||||||
selectedJoinField?: FileLayerField;
|
selectedJoinField?: FileLayerField;
|
||||||
wms: WMSOptions;
|
wms: WMSOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RegionMapVisConfig extends RegionMapVisParams {
|
||||||
|
metric: SchemaConfig;
|
||||||
|
bucket?: SchemaConfig;
|
||||||
|
}
|
|
@ -30,9 +30,8 @@ export function createRegionMapVisualization({
|
||||||
getServiceSettings,
|
getServiceSettings,
|
||||||
}) {
|
}) {
|
||||||
return class RegionMapsVisualization extends BaseMapsVisualization {
|
return class RegionMapsVisualization extends BaseMapsVisualization {
|
||||||
constructor(container, vis) {
|
constructor(container, handlers, initialVisParams) {
|
||||||
super(container, vis);
|
super(container, handlers, initialVisParams);
|
||||||
this._vis = this.vis;
|
|
||||||
this._choroplethLayer = null;
|
this._choroplethLayer = null;
|
||||||
this._tooltipFormatter = mapTooltipProvider(container, tooltipFormatter);
|
this._tooltipFormatter = mapTooltipProvider(container, tooltipFormatter);
|
||||||
}
|
}
|
||||||
|
@ -88,7 +87,7 @@ export function createRegionMapVisualization({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._kibanaMap.useUiStateFromVisualization(this._vis);
|
this._kibanaMap.useUiStateFromVisualization(this.handlers.uiState);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _loadConfig(fileLayerConfig) {
|
async _loadConfig(fileLayerConfig) {
|
||||||
|
@ -201,11 +200,18 @@ export function createRegionMapVisualization({
|
||||||
this._choroplethLayer.on('select', (event) => {
|
this._choroplethLayer.on('select', (event) => {
|
||||||
const { rows, columns } = this._chartData;
|
const { rows, columns } = this._chartData;
|
||||||
const rowIndex = rows.findIndex((row) => row[columns[0].id] === event);
|
const rowIndex = rows.findIndex((row) => row[columns[0].id] === event);
|
||||||
this._vis.API.events.filter({
|
this.handlers.event({
|
||||||
table: this._chartData,
|
name: 'filterBucket',
|
||||||
column: 0,
|
data: {
|
||||||
row: rowIndex,
|
data: [
|
||||||
value: event,
|
{
|
||||||
|
table: this._chartData,
|
||||||
|
column: 0,
|
||||||
|
row: rowIndex,
|
||||||
|
value: event,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
.rgmChart__wrapper, .rgmChart {
|
||||||
|
flex: 1 1 0;
|
||||||
|
display: flex;
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* 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 React, { useEffect, useMemo, useRef } from 'react';
|
||||||
|
import { EuiResizeObserver } from '@elastic/eui';
|
||||||
|
import { throttle } from 'lodash';
|
||||||
|
|
||||||
|
import { IInterpreterRenderHandlers, Datatable } from 'src/plugins/expressions';
|
||||||
|
import { PersistedState } from 'src/plugins/visualizations/public';
|
||||||
|
import { RegionMapVisualizationDependencies } from './plugin';
|
||||||
|
import { RegionMapVisConfig } from './region_map_types';
|
||||||
|
// @ts-expect-error
|
||||||
|
import { createRegionMapVisualization } from './region_map_visualization';
|
||||||
|
|
||||||
|
import './region_map_visualization.scss';
|
||||||
|
|
||||||
|
interface RegionMapVisController {
|
||||||
|
render(visData?: Datatable, visConfig?: RegionMapVisConfig): Promise<void>;
|
||||||
|
resize(): void;
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TileMapVisualizationProps {
|
||||||
|
deps: RegionMapVisualizationDependencies;
|
||||||
|
handlers: IInterpreterRenderHandlers;
|
||||||
|
visData: Datatable;
|
||||||
|
visConfig: RegionMapVisConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RegionMapVisualization = ({
|
||||||
|
deps,
|
||||||
|
handlers,
|
||||||
|
visData,
|
||||||
|
visConfig,
|
||||||
|
}: TileMapVisualizationProps) => {
|
||||||
|
const chartDiv = useRef<HTMLDivElement>(null);
|
||||||
|
const visController = useRef<RegionMapVisController | null>(null);
|
||||||
|
const isFirstRender = useRef(true);
|
||||||
|
const uiState = handlers.uiState as PersistedState | undefined;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (chartDiv.current && isFirstRender.current) {
|
||||||
|
isFirstRender.current = false;
|
||||||
|
const Controller = createRegionMapVisualization(deps);
|
||||||
|
visController.current = new Controller(chartDiv.current, handlers, visConfig);
|
||||||
|
}
|
||||||
|
}, [deps, handlers, visConfig, visData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
visController.current?.render(visData, visConfig).then(handlers.done);
|
||||||
|
}, [visData, visConfig, handlers.done]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onUiStateChange = () => {
|
||||||
|
visController.current?.render().then(handlers.done);
|
||||||
|
};
|
||||||
|
|
||||||
|
uiState?.on('change', onUiStateChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
uiState?.off('change', onUiStateChange);
|
||||||
|
};
|
||||||
|
}, [uiState, handlers.done]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
visController.current?.destroy();
|
||||||
|
visController.current = null;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const updateChartSize = useMemo(() => throttle(() => visController.current?.resize(), 300), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiResizeObserver onResize={updateChartSize}>
|
||||||
|
{(resizeRef) => (
|
||||||
|
<div className="rgmChart__wrapper" ref={resizeRef}>
|
||||||
|
<div className="rgmChart" ref={chartDiv} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</EuiResizeObserver>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// default export required for React.Lazy
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export { RegionMapVisualization as default };
|
59
src/plugins/region_map/public/to_ast.ts
Normal file
59
src/plugins/region_map/public/to_ast.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
EsaggsExpressionFunctionDefinition,
|
||||||
|
IndexPatternLoadExpressionFunctionDefinition,
|
||||||
|
} from '../../data/public';
|
||||||
|
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
|
||||||
|
import { getVisSchemas, Vis, BuildPipelineParams } from '../../visualizations/public';
|
||||||
|
import { RegionMapExpressionFunctionDefinition } from './region_map_fn';
|
||||||
|
import { RegionMapVisConfig, RegionMapVisParams } from './region_map_types';
|
||||||
|
|
||||||
|
export const toExpressionAst = (vis: Vis<RegionMapVisParams>, params: BuildPipelineParams) => {
|
||||||
|
const esaggs = buildExpressionFunction<EsaggsExpressionFunctionDefinition>('esaggs', {
|
||||||
|
index: buildExpression([
|
||||||
|
buildExpressionFunction<IndexPatternLoadExpressionFunctionDefinition>('indexPatternLoad', {
|
||||||
|
id: vis.data.indexPattern!.id!,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
metricsAtAllLevels: false,
|
||||||
|
partialRows: false,
|
||||||
|
aggs: vis.data.aggs!.aggs.map((agg) => buildExpression(agg.toExpressionAst())),
|
||||||
|
});
|
||||||
|
|
||||||
|
const schemas = getVisSchemas(vis, params);
|
||||||
|
|
||||||
|
const visConfig: RegionMapVisConfig = {
|
||||||
|
...vis.params,
|
||||||
|
metric: schemas.metric[0],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (schemas.segment) {
|
||||||
|
visConfig.bucket = schemas.segment[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const regionmap = buildExpressionFunction<RegionMapExpressionFunctionDefinition>('regionmap', {
|
||||||
|
visConfig: JSON.stringify(visConfig),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ast = buildExpression([esaggs, regionmap]);
|
||||||
|
|
||||||
|
return ast.toAst();
|
||||||
|
};
|
|
@ -2,12 +2,9 @@
|
||||||
|
|
||||||
exports[`interpreter/functions#tilemap returns an object with the correct structure 1`] = `
|
exports[`interpreter/functions#tilemap returns an object with the correct structure 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"as": "visualization",
|
"as": "tile_map_vis",
|
||||||
"type": "render",
|
"type": "render",
|
||||||
"value": Object {
|
"value": Object {
|
||||||
"params": Object {
|
|
||||||
"listenOnChange": true,
|
|
||||||
},
|
|
||||||
"visConfig": Object {
|
"visConfig": Object {
|
||||||
"addTooltip": true,
|
"addTooltip": true,
|
||||||
"colorSchema": "Yellow to Red",
|
"colorSchema": "Yellow to Red",
|
|
@ -1,15 +0,0 @@
|
||||||
// SASSTODO: Does this selector exist today?
|
|
||||||
.tilemap {
|
|
||||||
margin-bottom: 6px;
|
|
||||||
border: $euiBorderThin;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Visualizations have some padding by default but tilemaps look nice flush against the edge to maximize viewing
|
|
||||||
* space.
|
|
||||||
*/
|
|
||||||
// SASSTODO: Does this selector exist today?
|
|
||||||
.tile_map {
|
|
||||||
padding: 0; /* 1. */
|
|
||||||
}
|
|
25
src/plugins/tile_map/public/components/index.tsx
Normal file
25
src/plugins/tile_map/public/components/index.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* 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 React, { lazy } from 'react';
|
||||||
|
import type { TileMapOptionsProps } from './tile_map_options';
|
||||||
|
|
||||||
|
const TileMapOptions = lazy(() => import('./tile_map_options'));
|
||||||
|
|
||||||
|
export const TileMapOptionsLazy = (props: TileMapOptionsProps) => <TileMapOptions {...props} />;
|
|
@ -28,7 +28,9 @@ import {
|
||||||
SwitchOption,
|
SwitchOption,
|
||||||
RangeOption,
|
RangeOption,
|
||||||
} from '../../../vis_default_editor/public';
|
} from '../../../vis_default_editor/public';
|
||||||
import { WmsOptions, TileMapVisParams, MapTypes } from '../../../maps_legacy/public';
|
import { WmsOptions } from '../../../maps_legacy/public';
|
||||||
|
import { TileMapVisParams } from '../types';
|
||||||
|
import { MapTypes } from '../utils/map_types';
|
||||||
|
|
||||||
export type TileMapOptionsProps = VisOptionsProps<TileMapVisParams>;
|
export type TileMapOptionsProps = VisOptionsProps<TileMapVisParams>;
|
||||||
|
|
||||||
|
@ -102,4 +104,6 @@ function TileMapOptions(props: TileMapOptionsProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { TileMapOptions };
|
// default export required for React.Lazy
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export { TileMapOptions as default };
|
||||||
|
|
|
@ -19,11 +19,12 @@
|
||||||
|
|
||||||
import { min, isEqual } from 'lodash';
|
import { min, isEqual } from 'lodash';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { KibanaMapLayer, MapTypes } from '../../maps_legacy/public';
|
import { KibanaMapLayer } from '../../maps_legacy/public';
|
||||||
import { HeatmapMarkers } from './markers/heatmap';
|
import { HeatmapMarkers } from './markers/heatmap';
|
||||||
import { ScaledCirclesMarkers } from './markers/scaled_circles';
|
import { ScaledCirclesMarkers } from './markers/scaled_circles';
|
||||||
import { ShadedCirclesMarkers } from './markers/shaded_circles';
|
import { ShadedCirclesMarkers } from './markers/shaded_circles';
|
||||||
import { GeohashGridMarkers } from './markers/geohash_grid';
|
import { GeohashGridMarkers } from './markers/geohash_grid';
|
||||||
|
import { MapTypes } from './utils/map_types';
|
||||||
|
|
||||||
export class GeohashLayer extends KibanaMapLayer {
|
export class GeohashLayer extends KibanaMapLayer {
|
||||||
constructor(featureCollection, featureCollectionMetaData, options, zoom, kibanaMap, leaflet) {
|
constructor(featureCollection, featureCollectionMetaData, options, zoom, kibanaMap, leaflet) {
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
// Prefix all styles with "tlm" to avoid conflicts.
|
|
||||||
// Examples
|
|
||||||
// tlmChart
|
|
||||||
// tlmChart__legend
|
|
||||||
// tlmChart__legend--small
|
|
||||||
// tlmChart__legend-isLoading
|
|
||||||
|
|
||||||
@import 'tile_map';
|
|
|
@ -25,13 +25,6 @@ import {
|
||||||
} from 'kibana/public';
|
} from 'kibana/public';
|
||||||
import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public';
|
import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public';
|
||||||
import { VisualizationsSetup } from '../../visualizations/public';
|
import { VisualizationsSetup } from '../../visualizations/public';
|
||||||
// TODO: Determine why visualizations don't populate without this
|
|
||||||
import 'angular-sanitize';
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
import { createTileMapFn } from './tile_map_fn';
|
|
||||||
// @ts-ignore
|
|
||||||
import { createTileMapTypeDefinition } from './tile_map_type';
|
|
||||||
import { IServiceSettings, MapsLegacyPluginSetup } from '../../maps_legacy/public';
|
import { IServiceSettings, MapsLegacyPluginSetup } from '../../maps_legacy/public';
|
||||||
import { DataPublicPluginStart } from '../../data/public';
|
import { DataPublicPluginStart } from '../../data/public';
|
||||||
import {
|
import {
|
||||||
|
@ -44,12 +37,16 @@ import {
|
||||||
import { KibanaLegacyStart } from '../../kibana_legacy/public';
|
import { KibanaLegacyStart } from '../../kibana_legacy/public';
|
||||||
import { SharePluginStart } from '../../share/public';
|
import { SharePluginStart } from '../../share/public';
|
||||||
|
|
||||||
|
import { createTileMapFn } from './tile_map_fn';
|
||||||
|
import { createTileMapTypeDefinition } from './tile_map_type';
|
||||||
|
import { getTileMapRenderer } from './tile_map_renderer';
|
||||||
|
|
||||||
export interface TileMapConfigType {
|
export interface TileMapConfigType {
|
||||||
tilemap: any;
|
tilemap: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
interface TileMapVisualizationDependencies {
|
export interface TileMapVisualizationDependencies {
|
||||||
uiSettings: IUiSettingsClient;
|
uiSettings: IUiSettingsClient;
|
||||||
getZoomPrecision: any;
|
getZoomPrecision: any;
|
||||||
getPrecision: any;
|
getPrecision: any;
|
||||||
|
@ -98,7 +95,8 @@ export class TileMapPlugin implements Plugin<TileMapPluginSetup, TileMapPluginSt
|
||||||
getServiceSettings,
|
getServiceSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
expressions.registerFunction(() => createTileMapFn(visualizationDependencies));
|
expressions.registerFunction(createTileMapFn);
|
||||||
|
expressions.registerRenderer(getTileMapRenderer(visualizationDependencies));
|
||||||
|
|
||||||
visualizations.createBaseVisualization(createTileMapTypeDefinition(visualizationDependencies));
|
visualizations.createBaseVisualization(createTileMapTypeDefinition(visualizationDependencies));
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,10 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// eslint-disable-next-line
|
|
||||||
import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils';
|
import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils';
|
||||||
import { createTileMapFn } from './tile_map_fn';
|
import { createTileMapFn } from './tile_map_fn';
|
||||||
|
|
||||||
jest.mock('../../maps_legacy/public', () => ({
|
jest.mock('./utils', () => ({
|
||||||
convertToGeoJson: jest.fn().mockReturnValue({
|
convertToGeoJson: jest.fn().mockReturnValue({
|
||||||
featureCollection: {
|
featureCollection: {
|
||||||
type: 'FeatureCollection',
|
type: 'FeatureCollection',
|
||||||
|
@ -36,7 +35,7 @@ jest.mock('../../maps_legacy/public', () => ({
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { convertToGeoJson } from '../../maps_legacy/public';
|
import { convertToGeoJson } from './utils';
|
||||||
|
|
||||||
describe('interpreter/functions#tilemap', () => {
|
describe('interpreter/functions#tilemap', () => {
|
||||||
const fn = functionWrapper(createTileMapFn());
|
const fn = functionWrapper(createTileMapFn());
|
||||||
|
@ -79,18 +78,14 @@ describe('interpreter/functions#tilemap', () => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns an object with the correct structure', () => {
|
it('returns an object with the correct structure', async () => {
|
||||||
const actual = fn(
|
const actual = await fn(context, { visConfig: JSON.stringify(visConfig) });
|
||||||
context,
|
|
||||||
{ visConfig: JSON.stringify(visConfig) },
|
|
||||||
{ logDatatable: jest.fn() }
|
|
||||||
);
|
|
||||||
expect(actual).toMatchSnapshot();
|
expect(actual).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls response handler with correct values', () => {
|
it('calls response handler with correct values', async () => {
|
||||||
const { geohash, metric, geocentroid } = visConfig.dimensions;
|
const { geohash, metric, geocentroid } = visConfig.dimensions;
|
||||||
fn(context, { visConfig: JSON.stringify(visConfig) }, { logDatatable: jest.fn() });
|
await fn(context, { visConfig: JSON.stringify(visConfig) });
|
||||||
expect(convertToGeoJson).toHaveBeenCalledTimes(1);
|
expect(convertToGeoJson).toHaveBeenCalledTimes(1);
|
||||||
expect(convertToGeoJson).toHaveBeenCalledWith(context, {
|
expect(convertToGeoJson).toHaveBeenCalledWith(context, {
|
||||||
geohash,
|
geohash,
|
|
@ -16,10 +16,30 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { convertToGeoJson } from '../../maps_legacy/public';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
export const createTileMapFn = () => ({
|
import type { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public';
|
||||||
|
import { TileMapVisConfig, TileMapVisData } from './types';
|
||||||
|
|
||||||
|
interface Arguments {
|
||||||
|
visConfig: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TileMapVisRenderValue {
|
||||||
|
visData: TileMapVisData;
|
||||||
|
visType: 'tile_map';
|
||||||
|
visConfig: TileMapVisConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TileMapExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||||
|
'tilemap',
|
||||||
|
Datatable,
|
||||||
|
Arguments,
|
||||||
|
Promise<Render<TileMapVisRenderValue>>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const createTileMapFn = (): TileMapExpressionFunctionDefinition => ({
|
||||||
name: 'tilemap',
|
name: 'tilemap',
|
||||||
type: 'render',
|
type: 'render',
|
||||||
context: {
|
context: {
|
||||||
|
@ -32,34 +52,30 @@ export const createTileMapFn = () => ({
|
||||||
visConfig: {
|
visConfig: {
|
||||||
types: ['string', 'null'],
|
types: ['string', 'null'],
|
||||||
default: '"{}"',
|
default: '"{}"',
|
||||||
|
help: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fn(context, args, handlers) {
|
async fn(context, args, handlers) {
|
||||||
const visConfig = JSON.parse(args.visConfig);
|
const visConfig = args.visConfig && JSON.parse(args.visConfig);
|
||||||
const { geohash, metric, geocentroid } = visConfig.dimensions;
|
const { geohash, metric, geocentroid } = visConfig.dimensions;
|
||||||
|
|
||||||
|
const { convertToGeoJson } = await import('./utils');
|
||||||
const convertedData = convertToGeoJson(context, {
|
const convertedData = convertToGeoJson(context, {
|
||||||
geohash,
|
geohash,
|
||||||
metric,
|
metric,
|
||||||
geocentroid,
|
geocentroid,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (geohash && geohash.accessor) {
|
|
||||||
convertedData.meta.geohash = context.columns[geohash.accessor].meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handlers?.inspectorAdapters?.tables) {
|
if (handlers?.inspectorAdapters?.tables) {
|
||||||
handlers.inspectorAdapters.tables.logDatatable('default', context);
|
handlers.inspectorAdapters.tables.logDatatable('default', context);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: 'render',
|
type: 'render',
|
||||||
as: 'visualization',
|
as: 'tile_map_vis',
|
||||||
value: {
|
value: {
|
||||||
visData: convertedData,
|
visData: convertedData,
|
||||||
visType: 'tile_map',
|
visType: 'tile_map',
|
||||||
visConfig,
|
visConfig,
|
||||||
params: {
|
|
||||||
listenOnChange: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
52
src/plugins/tile_map/public/tile_map_renderer.tsx
Normal file
52
src/plugins/tile_map/public/tile_map_renderer.tsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* 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 React, { lazy } from 'react';
|
||||||
|
import { render, unmountComponentAtNode } from 'react-dom';
|
||||||
|
|
||||||
|
import { ExpressionRenderDefinition } from 'src/plugins/expressions';
|
||||||
|
import { VisualizationContainer } from '../../visualizations/public';
|
||||||
|
import { TileMapVisualizationDependencies } from './plugin';
|
||||||
|
import { TileMapVisRenderValue } from './tile_map_fn';
|
||||||
|
|
||||||
|
const TileMapVisualization = lazy(() => import('./tile_map_visualization_component'));
|
||||||
|
|
||||||
|
export const getTileMapRenderer: (
|
||||||
|
deps: TileMapVisualizationDependencies
|
||||||
|
) => ExpressionRenderDefinition<TileMapVisRenderValue> = (deps) => ({
|
||||||
|
name: 'tile_map_vis',
|
||||||
|
reuseDomNode: true,
|
||||||
|
render: async (domNode, { visConfig, visData }, handlers) => {
|
||||||
|
handlers.onDestroy(() => {
|
||||||
|
unmountComponentAtNode(domNode);
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<VisualizationContainer handlers={handlers}>
|
||||||
|
<TileMapVisualization
|
||||||
|
deps={deps}
|
||||||
|
handlers={handlers}
|
||||||
|
visData={visData}
|
||||||
|
visConfig={visConfig}
|
||||||
|
/>
|
||||||
|
</VisualizationContainer>,
|
||||||
|
domNode
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -17,17 +17,22 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { convertToGeoJson, MapTypes } from '../../maps_legacy/public';
|
import { BaseVisTypeOptions } from 'src/plugins/visualizations/public';
|
||||||
import { createTileMapVisualization } from './tile_map_visualization';
|
|
||||||
import { TileMapOptions } from './components/tile_map_options';
|
|
||||||
import { supportsCssFilters } from './css_filters';
|
|
||||||
import { truncatedColorSchemas } from '../../charts/public';
|
import { truncatedColorSchemas } from '../../charts/public';
|
||||||
import { getDeprecationMessage } from './get_deprecation_message';
|
|
||||||
|
|
||||||
export function createTileMapTypeDefinition(dependencies) {
|
// @ts-expect-error
|
||||||
const CoordinateMapsVisualization = createTileMapVisualization(dependencies);
|
import { supportsCssFilters } from './css_filters';
|
||||||
|
import { TileMapOptionsLazy } from './components';
|
||||||
|
import { getDeprecationMessage } from './get_deprecation_message';
|
||||||
|
import { TileMapVisualizationDependencies } from './plugin';
|
||||||
|
import { toExpressionAst } from './to_ast';
|
||||||
|
import { TileMapVisParams } from './types';
|
||||||
|
import { MapTypes } from './utils/map_types';
|
||||||
|
|
||||||
|
export function createTileMapTypeDefinition(
|
||||||
|
dependencies: TileMapVisualizationDependencies
|
||||||
|
): BaseVisTypeOptions<TileMapVisParams> {
|
||||||
const { uiSettings, getServiceSettings } = dependencies;
|
const { uiSettings, getServiceSettings } = dependencies;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -54,8 +59,7 @@ export function createTileMapTypeDefinition(dependencies) {
|
||||||
wms: uiSettings.get('visualization:tileMap:WMSdefaults'),
|
wms: uiSettings.get('visualization:tileMap:WMSdefaults'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
visualization: CoordinateMapsVisualization,
|
toExpressionAst,
|
||||||
responseHandler: convertToGeoJson,
|
|
||||||
editorConfig: {
|
editorConfig: {
|
||||||
collections: {
|
collections: {
|
||||||
colorSchemas: truncatedColorSchemas,
|
colorSchemas: truncatedColorSchemas,
|
||||||
|
@ -113,7 +117,7 @@ export function createTileMapTypeDefinition(dependencies) {
|
||||||
],
|
],
|
||||||
tmsLayers: [],
|
tmsLayers: [],
|
||||||
},
|
},
|
||||||
optionsTemplate: (props) => <TileMapOptions {...props} />,
|
optionsTemplate: TileMapOptionsLazy,
|
||||||
schemas: [
|
schemas: [
|
||||||
{
|
{
|
||||||
group: 'metrics',
|
group: 'metrics',
|
|
@ -19,12 +19,9 @@
|
||||||
|
|
||||||
import { get, round } from 'lodash';
|
import { get, round } from 'lodash';
|
||||||
import { getFormatService, getQueryService, getKibanaLegacy } from './services';
|
import { getFormatService, getQueryService, getKibanaLegacy } from './services';
|
||||||
import {
|
import { mapTooltipProvider, lazyLoadMapsLegacyModules } from '../../maps_legacy/public';
|
||||||
geoContains,
|
|
||||||
mapTooltipProvider,
|
|
||||||
lazyLoadMapsLegacyModules,
|
|
||||||
} from '../../maps_legacy/public';
|
|
||||||
import { tooltipFormatter } from './tooltip_formatter';
|
import { tooltipFormatter } from './tooltip_formatter';
|
||||||
|
import { geoContains } from './utils';
|
||||||
|
|
||||||
function scaleBounds(bounds) {
|
function scaleBounds(bounds) {
|
||||||
const scale = 0.5; // scale bounds by 50%
|
const scale = 0.5; // scale bounds by 50%
|
||||||
|
@ -57,8 +54,8 @@ export const createTileMapVisualization = (dependencies) => {
|
||||||
const { getZoomPrecision, getPrecision, BaseMapsVisualization } = dependencies;
|
const { getZoomPrecision, getPrecision, BaseMapsVisualization } = dependencies;
|
||||||
|
|
||||||
return class CoordinateMapsVisualization extends BaseMapsVisualization {
|
return class CoordinateMapsVisualization extends BaseMapsVisualization {
|
||||||
constructor(element, vis) {
|
constructor(element, handlers, initialVisParams) {
|
||||||
super(element, vis);
|
super(element, handlers, initialVisParams);
|
||||||
|
|
||||||
this._geohashLayer = null;
|
this._geohashLayer = null;
|
||||||
this._tooltipFormatter = mapTooltipProvider(element, tooltipFormatter);
|
this._tooltipFormatter = mapTooltipProvider(element, tooltipFormatter);
|
||||||
|
@ -84,10 +81,10 @@ export const createTileMapVisualization = (dependencies) => {
|
||||||
// todo: autoPrecision should be vis parameter, not aggConfig one
|
// todo: autoPrecision should be vis parameter, not aggConfig one
|
||||||
const zoomPrecision = getZoomPrecision();
|
const zoomPrecision = getZoomPrecision();
|
||||||
updateVarsObject.data.precision = geohashAgg.sourceParams.params.autoPrecision
|
updateVarsObject.data.precision = geohashAgg.sourceParams.params.autoPrecision
|
||||||
? zoomPrecision[this.vis.getUiState().get('mapZoom')]
|
? zoomPrecision[this.handlers.uiState.get('mapZoom')]
|
||||||
: getPrecision(geohashAgg.sourceParams.params.precision);
|
: getPrecision(geohashAgg.sourceParams.params.precision);
|
||||||
|
|
||||||
this.vis.eventsSubject.next(updateVarsObject);
|
this.handlers.event(updateVarsObject);
|
||||||
};
|
};
|
||||||
|
|
||||||
async render(esResponse, visParams) {
|
async render(esResponse, visParams) {
|
||||||
|
@ -96,13 +93,12 @@ export const createTileMapVisualization = (dependencies) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _makeKibanaMap() {
|
async _makeKibanaMap() {
|
||||||
await super._makeKibanaMap();
|
await super._makeKibanaMap(this._params);
|
||||||
|
|
||||||
let previousPrecision = this._kibanaMap.getGeohashPrecision();
|
let previousPrecision = this._kibanaMap.getGeohashPrecision();
|
||||||
let precisionChange = false;
|
let precisionChange = false;
|
||||||
|
|
||||||
const uiState = this.vis.getUiState();
|
this.handlers.uiState.on('change', (prop) => {
|
||||||
uiState.on('change', (prop) => {
|
|
||||||
if (prop === 'mapZoom' || prop === 'mapCenter') {
|
if (prop === 'mapZoom' || prop === 'mapCenter') {
|
||||||
this.updateGeohashAgg();
|
this.updateGeohashAgg();
|
||||||
}
|
}
|
||||||
|
@ -250,8 +246,6 @@ export const createTileMapVisualization = (dependencies) => {
|
||||||
|
|
||||||
const { filterManager } = getQueryService();
|
const { filterManager } = getQueryService();
|
||||||
filterManager.addFilters([filter]);
|
filterManager.addFilters([filter]);
|
||||||
|
|
||||||
this.vis.updateState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_getGeoHashAgg() {
|
_getGeoHashAgg() {
|
||||||
|
|
4
src/plugins/tile_map/public/tile_map_visualization.scss
Normal file
4
src/plugins/tile_map/public/tile_map_visualization.scss
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.tlmChart__wrapper, .tlmChart {
|
||||||
|
flex: 1 1 0;
|
||||||
|
display: flex;
|
||||||
|
}
|
103
src/plugins/tile_map/public/tile_map_visualization_component.tsx
Normal file
103
src/plugins/tile_map/public/tile_map_visualization_component.tsx
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* 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 React, { useEffect, useMemo, useRef } from 'react';
|
||||||
|
import { EuiResizeObserver } from '@elastic/eui';
|
||||||
|
import { throttle } from 'lodash';
|
||||||
|
|
||||||
|
import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
|
||||||
|
import { PersistedState } from 'src/plugins/visualizations/public';
|
||||||
|
import { TileMapVisualizationDependencies } from './plugin';
|
||||||
|
import { TileMapVisConfig, TileMapVisData } from './types';
|
||||||
|
// @ts-expect-error
|
||||||
|
import { createTileMapVisualization } from './tile_map_visualization';
|
||||||
|
|
||||||
|
import './tile_map_visualization.scss';
|
||||||
|
|
||||||
|
interface TileMapVisController {
|
||||||
|
render(visData?: TileMapVisData, visConfig?: TileMapVisConfig): Promise<void>;
|
||||||
|
resize(): void;
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TileMapVisualizationProps {
|
||||||
|
deps: TileMapVisualizationDependencies;
|
||||||
|
handlers: IInterpreterRenderHandlers;
|
||||||
|
visData: TileMapVisData;
|
||||||
|
visConfig: TileMapVisConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TileMapVisualization = ({
|
||||||
|
deps,
|
||||||
|
handlers,
|
||||||
|
visData,
|
||||||
|
visConfig,
|
||||||
|
}: TileMapVisualizationProps) => {
|
||||||
|
const chartDiv = useRef<HTMLDivElement>(null);
|
||||||
|
const visController = useRef<TileMapVisController | null>(null);
|
||||||
|
const isFirstRender = useRef(true);
|
||||||
|
const uiState = handlers.uiState as PersistedState;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (chartDiv.current && isFirstRender.current) {
|
||||||
|
isFirstRender.current = false;
|
||||||
|
const Controller = createTileMapVisualization(deps);
|
||||||
|
visController.current = new Controller(chartDiv.current, handlers, visConfig);
|
||||||
|
}
|
||||||
|
}, [deps, handlers, visConfig, visData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
visController.current?.render(visData, visConfig).then(handlers.done);
|
||||||
|
}, [visData, visConfig, handlers.done]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onUiStateChange = () => {
|
||||||
|
visController.current?.render().then(handlers.done);
|
||||||
|
};
|
||||||
|
|
||||||
|
uiState.on('change', onUiStateChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
uiState.off('change', onUiStateChange);
|
||||||
|
};
|
||||||
|
}, [uiState, handlers.done]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
visController.current?.destroy();
|
||||||
|
visController.current = null;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const updateChartSize = useMemo(() => throttle(() => visController.current?.resize(), 300), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiResizeObserver onResize={updateChartSize}>
|
||||||
|
{(resizeRef) => (
|
||||||
|
<div className="tlmChart__wrapper" ref={resizeRef}>
|
||||||
|
<div className="tlmChart" ref={chartDiv} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</EuiResizeObserver>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// default export required for React.Lazy
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export { TileMapVisualization as default };
|
59
src/plugins/tile_map/public/to_ast.ts
Normal file
59
src/plugins/tile_map/public/to_ast.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
EsaggsExpressionFunctionDefinition,
|
||||||
|
IndexPatternLoadExpressionFunctionDefinition,
|
||||||
|
} from '../../data/public';
|
||||||
|
import { buildExpression, buildExpressionFunction } from '../../expressions/public';
|
||||||
|
import { getVisSchemas, Vis, BuildPipelineParams } from '../../visualizations/public';
|
||||||
|
import { TileMapExpressionFunctionDefinition } from './tile_map_fn';
|
||||||
|
import { TileMapVisConfig, TileMapVisParams } from './types';
|
||||||
|
|
||||||
|
export const toExpressionAst = (vis: Vis<TileMapVisParams>, params: BuildPipelineParams) => {
|
||||||
|
const esaggs = buildExpressionFunction<EsaggsExpressionFunctionDefinition>('esaggs', {
|
||||||
|
index: buildExpression([
|
||||||
|
buildExpressionFunction<IndexPatternLoadExpressionFunctionDefinition>('indexPatternLoad', {
|
||||||
|
id: vis.data.indexPattern!.id!,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
metricsAtAllLevels: false,
|
||||||
|
partialRows: false,
|
||||||
|
aggs: vis.data.aggs!.aggs.map((agg) => buildExpression(agg.toExpressionAst())),
|
||||||
|
});
|
||||||
|
|
||||||
|
const schemas = getVisSchemas(vis, params);
|
||||||
|
|
||||||
|
const visConfig: TileMapVisConfig = {
|
||||||
|
...vis.params,
|
||||||
|
dimensions: {
|
||||||
|
metric: schemas.metric[0],
|
||||||
|
geohash: schemas.segment ? schemas.segment[0] : null,
|
||||||
|
geocentroid: schemas.geo_centroid ? schemas.geo_centroid[0] : null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const tilemap = buildExpressionFunction<TileMapExpressionFunctionDefinition>('tilemap', {
|
||||||
|
visConfig: JSON.stringify(visConfig),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ast = buildExpression([esaggs, tilemap]);
|
||||||
|
|
||||||
|
return ast.toAst();
|
||||||
|
};
|
57
src/plugins/tile_map/public/types.ts
Normal file
57
src/plugins/tile_map/public/types.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 { FeatureCollection } from 'geojson';
|
||||||
|
import type { SchemaConfig } from 'src/plugins/visualizations/public';
|
||||||
|
import type { DatatableColumnMeta } from 'src/plugins/expressions';
|
||||||
|
import type { WMSOptions } from 'src/plugins/maps_legacy/public';
|
||||||
|
import type { MapTypes } from './utils/map_types';
|
||||||
|
|
||||||
|
export interface TileMapVisData {
|
||||||
|
featureCollection: FeatureCollection;
|
||||||
|
meta: {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
geohash?: DatatableColumnMeta;
|
||||||
|
geohashPrecision: number | undefined;
|
||||||
|
geohashGridDimensionsAtEquator: [number, number] | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TileMapVisDimensions {
|
||||||
|
metric: SchemaConfig;
|
||||||
|
geohash: SchemaConfig | null;
|
||||||
|
geocentroid: SchemaConfig | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TileMapVisParams {
|
||||||
|
colorSchema: string;
|
||||||
|
mapType: MapTypes;
|
||||||
|
isDesaturated: boolean;
|
||||||
|
addTooltip: boolean;
|
||||||
|
heatClusterSize: number;
|
||||||
|
legendPosition: 'bottomright' | 'bottomleft' | 'topright' | 'topleft';
|
||||||
|
mapZoom: number;
|
||||||
|
mapCenter: [number, number];
|
||||||
|
wms: WMSOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TileMapVisConfig extends TileMapVisParams {
|
||||||
|
dimensions: TileMapVisDimensions;
|
||||||
|
}
|
|
@ -17,11 +17,17 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Feature } from 'geojson';
|
||||||
|
import type { Datatable } from '../../../expressions/public';
|
||||||
|
import type { TileMapVisDimensions, TileMapVisData } from '../types';
|
||||||
import { decodeGeoHash } from './decode_geo_hash';
|
import { decodeGeoHash } from './decode_geo_hash';
|
||||||
import { gridDimensions } from './grid_dimensions';
|
import { gridDimensions } from './grid_dimensions';
|
||||||
|
|
||||||
export function convertToGeoJson(tabifiedResponse, { geohash, geocentroid, metric }) {
|
export function convertToGeoJson(
|
||||||
let features;
|
tabifiedResponse: Datatable,
|
||||||
|
{ geohash, geocentroid, metric }: TileMapVisDimensions
|
||||||
|
): TileMapVisData {
|
||||||
|
let features: Feature[];
|
||||||
let min = Infinity;
|
let min = Infinity;
|
||||||
let max = -Infinity;
|
let max = -Infinity;
|
||||||
|
|
||||||
|
@ -41,7 +47,7 @@ export function convertToGeoJson(tabifiedResponse, { geohash, geocentroid, metri
|
||||||
if (!geohashValue) return false;
|
if (!geohashValue) return false;
|
||||||
const geohashLocation = decodeGeoHash(geohashValue);
|
const geohashLocation = decodeGeoHash(geohashValue);
|
||||||
|
|
||||||
let pointCoordinates;
|
let pointCoordinates: number[];
|
||||||
if (geocentroidColumn) {
|
if (geocentroidColumn) {
|
||||||
const location = row[geocentroidColumn.id];
|
const location = row[geocentroidColumn.id];
|
||||||
pointCoordinates = [location.lon, location.lat];
|
pointCoordinates = [location.lon, location.lat];
|
||||||
|
@ -58,7 +64,7 @@ export function convertToGeoJson(tabifiedResponse, { geohash, geocentroid, metri
|
||||||
|
|
||||||
const centerLatLng = [geohashLocation.latitude[2], geohashLocation.longitude[2]];
|
const centerLatLng = [geohashLocation.latitude[2], geohashLocation.longitude[2]];
|
||||||
|
|
||||||
if (geohash.params.useGeocentroid) {
|
if (geohash?.params.useGeocentroid) {
|
||||||
// see https://github.com/elastic/elasticsearch/issues/24694 for why clampGrid is used
|
// see https://github.com/elastic/elasticsearch/issues/24694 for why clampGrid is used
|
||||||
pointCoordinates[0] = clampGrid(
|
pointCoordinates[0] = clampGrid(
|
||||||
pointCoordinates[0],
|
pointCoordinates[0],
|
||||||
|
@ -86,35 +92,41 @@ export function convertToGeoJson(tabifiedResponse, { geohash, geocentroid, metri
|
||||||
geohash: geohashValue,
|
geohash: geohashValue,
|
||||||
geohash_meta: {
|
geohash_meta: {
|
||||||
center: centerLatLng,
|
center: centerLatLng,
|
||||||
rectangle: rectangle,
|
rectangle,
|
||||||
},
|
},
|
||||||
value: value,
|
value,
|
||||||
},
|
},
|
||||||
};
|
} as Feature;
|
||||||
})
|
})
|
||||||
.filter((row) => row);
|
.filter((row): row is Feature => !!row);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
features = [];
|
features = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const featureCollection = {
|
const convertedData: TileMapVisData = {
|
||||||
type: 'FeatureCollection',
|
featureCollection: {
|
||||||
features: features,
|
type: 'FeatureCollection',
|
||||||
};
|
features,
|
||||||
|
},
|
||||||
return {
|
|
||||||
featureCollection: featureCollection,
|
|
||||||
meta: {
|
meta: {
|
||||||
min: min,
|
min,
|
||||||
max: max,
|
max,
|
||||||
geohashPrecision: geohash && geohash.params.precision,
|
geohashPrecision: geohash?.params.precision,
|
||||||
geohashGridDimensionsAtEquator: geohash && gridDimensions(geohash.params.precision),
|
geohashGridDimensionsAtEquator: geohash?.params.precision
|
||||||
|
? gridDimensions(geohash.params.precision)
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (geohash && geohash.accessor) {
|
||||||
|
convertedData.meta.geohash = tabifiedResponse.columns[geohash.accessor].meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clampGrid(val, min, max) {
|
function clampGrid(val: number, min: number, max: number) {
|
||||||
if (val > max) val = max;
|
if (val > max) val = max;
|
||||||
else if (val < min) val = min;
|
else if (val < min) val = min;
|
||||||
return val;
|
return val;
|
|
@ -17,14 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { geohashColumns, decodeGeoHash } from './decode_geo_hash';
|
import { decodeGeoHash } from './decode_geo_hash';
|
||||||
|
|
||||||
test('geohashColumns', () => {
|
|
||||||
expect(geohashColumns(1)).toBe(8);
|
|
||||||
expect(geohashColumns(2)).toBe(8 * 4);
|
|
||||||
expect(geohashColumns(3)).toBe(8 * 4 * 8);
|
|
||||||
expect(geohashColumns(4)).toBe(8 * 4 * 8 * 4);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('decodeGeoHash', () => {
|
test('decodeGeoHash', () => {
|
||||||
expect(decodeGeoHash('drm3btev3e86')).toEqual({
|
expect(decodeGeoHash('drm3btev3e86')).toEqual({
|
|
@ -55,10 +55,11 @@ export function decodeGeoHash(geohash: string): DecodedGeoHash {
|
||||||
});
|
});
|
||||||
lat[2] = (lat[0] + lat[1]) / 2;
|
lat[2] = (lat[0] + lat[1]) / 2;
|
||||||
lon[2] = (lon[0] + lon[1]) / 2;
|
lon[2] = (lon[0] + lon[1]) / 2;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
latitude: lat,
|
latitude: lat,
|
||||||
longitude: lon,
|
longitude: lon,
|
||||||
} as DecodedGeoHash;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function refineInterval(interval: number[], cd: number, mask: number) {
|
function refineInterval(interval: number[], cd: number, mask: number) {
|
||||||
|
@ -69,26 +70,6 @@ function refineInterval(interval: number[], cd: number, mask: number) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function geohashColumns(precision: number): number {
|
|
||||||
return geohashCells(precision, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the number of geohash cells for a given precision
|
|
||||||
*
|
|
||||||
* @param {number} precision the geohash precision (1<=precision<=12).
|
|
||||||
* @param {number} axis constant for the axis 0=lengthwise (ie. columns, along longitude), 1=heightwise (ie. rows, along latitude).
|
|
||||||
* @returns {number} Number of geohash cells (rows or columns) at that precision
|
|
||||||
*/
|
|
||||||
function geohashCells(precision: number, axis: number) {
|
|
||||||
let cells = 1;
|
|
||||||
for (let i = 1; i <= precision; i += 1) {
|
|
||||||
/* On odd precisions, rows divide by 4 and columns by 8. Vice-versa on even precisions */
|
|
||||||
cells *= i % 2 === axis ? 4 : 8;
|
|
||||||
}
|
|
||||||
return cells;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GeoBoundingBoxCoordinate {
|
interface GeoBoundingBoxCoordinate {
|
||||||
lat: number;
|
lat: number;
|
||||||
lon: number;
|
lon: number;
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
// geohash precision mapping of geohash grid cell dimensions (width x height, in meters) at equator.
|
// geohash precision mapping of geohash grid cell dimensions (width x height, in meters) at equator.
|
||||||
// https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator
|
// https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator
|
||||||
const gridAtEquator = {
|
const gridAtEquator: { [key: number]: [number, number] } = {
|
||||||
1: [5009400, 4992600],
|
1: [5009400, 4992600],
|
||||||
2: [1252300, 624100],
|
2: [1252300, 624100],
|
||||||
3: [156500, 156000],
|
3: [156500, 156000],
|
||||||
|
@ -34,6 +34,6 @@ const gridAtEquator = {
|
||||||
12: [0.037, 0.019],
|
12: [0.037, 0.019],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function gridDimensions(precision) {
|
export function gridDimensions(precision: number) {
|
||||||
return gridAtEquator[precision];
|
return gridAtEquator[precision];
|
||||||
}
|
}
|
|
@ -17,10 +17,5 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
export { convertToGeoJson } from './convert_to_geojson';
|
||||||
* Use * syntax so that these exports do not break when internal
|
export { geoContains } from './decode_geo_hash';
|
||||||
* types are stripped.
|
|
||||||
*/
|
|
||||||
export * from './external_basemap_types';
|
|
||||||
export * from './map_types';
|
|
||||||
export * from './region_map_types';
|
|
|
@ -1,9 +1,3 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`visualize loader pipeline helpers: build pipeline buildPipeline calls toExpression on vis_type if it exists 1`] = `"kibana | kibana_context | test"`;
|
exports[`visualize loader pipeline helpers: build pipeline buildPipeline calls toExpression on vis_type if it exists 1`] = `"kibana | kibana_context | test"`;
|
||||||
|
|
||||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function with buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"bucket\\":1}' "`;
|
|
||||||
|
|
||||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function without buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}}' "`;
|
|
||||||
|
|
||||||
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tile_map function 1`] = `"tilemap visConfig='{\\"metric\\":{},\\"dimensions\\":{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"geohash\\":1,\\"geocentroid\\":3}}' "`;
|
|
||||||
|
|
|
@ -17,14 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { prepareJson, prepareString, buildPipeline } from './build_pipeline';
|
||||||
prepareJson,
|
|
||||||
prepareString,
|
|
||||||
buildPipelineVisFunction,
|
|
||||||
buildPipeline,
|
|
||||||
SchemaConfig,
|
|
||||||
Schemas,
|
|
||||||
} from './build_pipeline';
|
|
||||||
import { Vis } from '..';
|
import { Vis } from '..';
|
||||||
import { dataPluginMock } from '../../../../plugins/data/public/mocks';
|
import { dataPluginMock } from '../../../../plugins/data/public/mocks';
|
||||||
import { parseExpression } from '../../../expressions/common';
|
import { parseExpression } from '../../../expressions/common';
|
||||||
|
@ -74,53 +67,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('buildPipelineVisFunction', () => {
|
|
||||||
let schemaConfig: SchemaConfig;
|
|
||||||
let schemasDef: Schemas;
|
|
||||||
let uiState: any;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
schemaConfig = {
|
|
||||||
accessor: 0,
|
|
||||||
label: '',
|
|
||||||
format: {},
|
|
||||||
params: {},
|
|
||||||
aggType: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
schemasDef = { metric: [schemaConfig] };
|
|
||||||
uiState = {};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handles region_map function', () => {
|
|
||||||
it('without buckets', () => {
|
|
||||||
const params = { metric: {} };
|
|
||||||
const actual = buildPipelineVisFunction.region_map(params, schemasDef, uiState);
|
|
||||||
expect(actual).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('with buckets', () => {
|
|
||||||
const schemas = {
|
|
||||||
...schemasDef,
|
|
||||||
segment: [1, 2],
|
|
||||||
};
|
|
||||||
const actual = buildPipelineVisFunction.region_map({}, schemas, uiState);
|
|
||||||
expect(actual).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles tile_map function', () => {
|
|
||||||
const params = { metric: {} };
|
|
||||||
const schemas = {
|
|
||||||
...schemasDef,
|
|
||||||
segment: [1, 2],
|
|
||||||
geo_centroid: [3, 4],
|
|
||||||
};
|
|
||||||
const actual = buildPipelineVisFunction.tile_map(params, schemas, uiState);
|
|
||||||
expect(actual).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('buildPipeline', () => {
|
describe('buildPipeline', () => {
|
||||||
const dataStart = dataPluginMock.createStartContract();
|
const dataStart = dataPluginMock.createStartContract();
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,7 @@ import {
|
||||||
SerializedFieldFormat,
|
SerializedFieldFormat,
|
||||||
} from '../../../../plugins/expressions/public';
|
} from '../../../../plugins/expressions/public';
|
||||||
import { IAggConfig, search, TimefilterContract } from '../../../../plugins/data/public';
|
import { IAggConfig, search, TimefilterContract } from '../../../../plugins/data/public';
|
||||||
|
import { Vis } from '../types';
|
||||||
import { Vis, VisParams } from '../types';
|
|
||||||
|
|
||||||
const { isDateHistogramBucketAggConfig } = search.aggs;
|
const { isDateHistogramBucketAggConfig } = search.aggs;
|
||||||
|
|
||||||
interface SchemaConfigParams {
|
interface SchemaConfigParams {
|
||||||
|
@ -55,25 +53,6 @@ export interface Schemas {
|
||||||
// catch all for schema name
|
// catch all for schema name
|
||||||
[key: string]: any[] | undefined;
|
[key: string]: any[] | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
type BuildVisFunction = (
|
|
||||||
params: VisParams,
|
|
||||||
schemas: Schemas,
|
|
||||||
uiState: any,
|
|
||||||
meta?: { savedObjectId?: string }
|
|
||||||
) => string;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
type buildVisConfigFunction = (schemas: Schemas, visParams?: VisParams) => VisParams;
|
|
||||||
|
|
||||||
interface BuildPipelineVisFunction {
|
|
||||||
[key: string]: BuildVisFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BuildVisConfigFunction {
|
|
||||||
[key: string]: buildVisConfigFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BuildPipelineParams {
|
export interface BuildPipelineParams {
|
||||||
timefilter: TimefilterContract;
|
timefilter: TimefilterContract;
|
||||||
timeRange?: any;
|
timeRange?: any;
|
||||||
|
@ -224,43 +203,6 @@ export const prepareDimension = (variable: string, data: any) => {
|
||||||
return expr;
|
return expr;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildPipelineVisFunction: BuildPipelineVisFunction = {
|
|
||||||
region_map: (params, schemas) => {
|
|
||||||
const visConfig = {
|
|
||||||
...params,
|
|
||||||
...buildVisConfig.region_map(schemas),
|
|
||||||
};
|
|
||||||
return `regionmap ${prepareJson('visConfig', visConfig)}`;
|
|
||||||
},
|
|
||||||
tile_map: (params, schemas) => {
|
|
||||||
const visConfig = {
|
|
||||||
...params,
|
|
||||||
...buildVisConfig.tile_map(schemas),
|
|
||||||
};
|
|
||||||
return `tilemap ${prepareJson('visConfig', visConfig)}`;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildVisConfig: BuildVisConfigFunction = {
|
|
||||||
region_map: (schemas) => {
|
|
||||||
const visConfig = {} as any;
|
|
||||||
visConfig.metric = schemas.metric[0];
|
|
||||||
if (schemas.segment) {
|
|
||||||
visConfig.bucket = schemas.segment[0];
|
|
||||||
}
|
|
||||||
return visConfig;
|
|
||||||
},
|
|
||||||
tile_map: (schemas) => {
|
|
||||||
const visConfig = {} as any;
|
|
||||||
visConfig.dimensions = {
|
|
||||||
metric: schemas.metric[0],
|
|
||||||
geohash: schemas.segment ? schemas.segment[0] : null,
|
|
||||||
geocentroid: schemas.geo_centroid ? schemas.geo_centroid[0] : null,
|
|
||||||
};
|
|
||||||
return visConfig;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildPipeline = async (vis: Vis, params: BuildPipelineParams) => {
|
export const buildPipeline = async (vis: Vis, params: BuildPipelineParams) => {
|
||||||
const { indexPattern, searchSource } = vis.data;
|
const { indexPattern, searchSource } = vis.data;
|
||||||
const query = searchSource!.getField('query');
|
const query = searchSource!.getField('query');
|
||||||
|
@ -299,17 +241,8 @@ export const buildPipeline = async (vis: Vis, params: BuildPipelineParams) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
pipeline += `| `;
|
pipeline += `| `;
|
||||||
}
|
|
||||||
|
|
||||||
const schemas = getSchemas(vis, params);
|
|
||||||
|
|
||||||
if (buildPipelineVisFunction[vis.type.name]) {
|
|
||||||
pipeline += buildPipelineVisFunction[vis.type.name](
|
|
||||||
{ title, ...vis.params },
|
|
||||||
schemas,
|
|
||||||
uiState
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
|
const schemas = getSchemas(vis, params);
|
||||||
const visConfig = { ...vis.params };
|
const visConfig = { ...vis.params };
|
||||||
visConfig.dimensions = schemas;
|
visConfig.dimensions = schemas;
|
||||||
visConfig.title = title;
|
visConfig.title = title;
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 10 KiB |
|
@ -1 +1 @@
|
||||||
{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0},"metric":{"accessor":1,"format":{"id":"number"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"region_map"}}
|
{"as":"region_map_vis","type":"render","value":{"visConfig":{"addTooltip":true,"bucket":{"accessor":0},"colorSchema":"Yellow to Red","isDisplayWarning":true,"legendPosition":"bottomright","mapCenter":[0,0],"mapZoom":2,"metric":{"accessor":1,"format":{"id":"number"}},"outlineWeight":1,"selectedJoinField":{},"selectedLayer":{},"showAllShapes":true,"wms":{}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"region_map"}}
|
|
@ -1 +1 @@
|
||||||
{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0},"metric":{"accessor":1,"format":{"id":"number"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"region_map"}}
|
{"as":"region_map_vis","type":"render","value":{"visConfig":{"addTooltip":true,"bucket":{"accessor":0},"colorSchema":"Yellow to Red","isDisplayWarning":true,"legendPosition":"bottomright","mapCenter":[0,0],"mapZoom":2,"metric":{"accessor":1,"format":{"id":"number"}},"outlineWeight":1,"selectedJoinField":{},"selectedLayer":{},"showAllShapes":true,"wms":{}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"region_map"}}
|
|
@ -110,7 +110,7 @@ export default function ({
|
||||||
await expectExpression('partial_test_2', metricExpr, context).toMatchSnapshot()
|
await expectExpression('partial_test_2', metricExpr, context).toMatchSnapshot()
|
||||||
).toMatchScreenshot();
|
).toMatchScreenshot();
|
||||||
|
|
||||||
const regionMapExpr = `regionmap visConfig='{"metric":{"accessor":1,"format":{"id":"number"}},"bucket":{"accessor":0}}'`;
|
const regionMapExpr = `regionmap visConfig='{"metric":{"accessor":1,"format":{"id":"number"}},"bucket":{"accessor":0},"legendPosition":"bottomright","addTooltip":true,"colorSchema":"Yellow to Red","isDisplayWarning":true,"wms":{},"mapZoom":2,"mapCenter":[0,0],"outlineWeight":1,"showAllShapes":true,"selectedLayer":{},"selectedJoinField":{}}'`;
|
||||||
await (
|
await (
|
||||||
await expectExpression('partial_test_3', regionMapExpr, context).toMatchSnapshot()
|
await expectExpression('partial_test_3', regionMapExpr, context).toMatchSnapshot()
|
||||||
).toMatchScreenshot();
|
).toMatchScreenshot();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue