mirror of
https://github.com/apache/superset.git
synced 2026-04-25 02:55:07 +00:00
feat(plugin): add plugin-chart-cartodiagram (#25869)
Co-authored-by: Jakob Miksch <jakob@meggsimum.de>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 { SupersetTheme } from '@superset-ui/core';
|
||||
import { ChartConfig, ChartConfigFeature } from '../types';
|
||||
import ChartWrapper from '../components/ChartWrapper';
|
||||
|
||||
/**
|
||||
* Create a chart component for a location.
|
||||
*
|
||||
* @param chartVizType The superset visualization type
|
||||
* @param chartConfigs The chart configurations
|
||||
* @param chartWidth The chart width
|
||||
* @param chartHeight The chart height
|
||||
* @param chartTheme The chart theme
|
||||
* @returns The chart as React component
|
||||
*/
|
||||
export const createChartComponent = (
|
||||
chartVizType: string,
|
||||
chartConfig: ChartConfigFeature,
|
||||
chartWidth: number,
|
||||
chartHeight: number,
|
||||
chartTheme: SupersetTheme,
|
||||
) => (
|
||||
<ChartWrapper
|
||||
vizType={chartVizType}
|
||||
chartConfig={chartConfig}
|
||||
width={chartWidth}
|
||||
height={chartHeight}
|
||||
theme={chartTheme}
|
||||
/>
|
||||
);
|
||||
|
||||
/**
|
||||
* Simplifies a chart configuration by removing
|
||||
* non-serializable properties.
|
||||
*
|
||||
* @param config The chart configuration to simplify.
|
||||
* @returns The simplified chart configuration.
|
||||
*/
|
||||
export const simplifyConfig = (config: ChartConfig) => {
|
||||
const simplifiedConfig: ChartConfig = {
|
||||
type: config.type,
|
||||
features: config.features.map(f => ({
|
||||
type: f.type,
|
||||
geometry: f.geometry,
|
||||
properties: Object.keys(f.properties)
|
||||
.filter(k => k !== 'refs')
|
||||
.reduce((prev, cur) => ({ ...prev, [cur]: f.properties[cur] }), {}),
|
||||
})),
|
||||
};
|
||||
return simplifiedConfig;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if two chart configurations are equal (deep equality).
|
||||
*
|
||||
* @param configA The first chart config for comparison.
|
||||
* @param configB The second chart config for comparison.
|
||||
* @returns True, if configurations are equal. False otherwise.
|
||||
*/
|
||||
export const isChartConfigEqual = (
|
||||
configA: ChartConfig,
|
||||
configB: ChartConfig,
|
||||
) => {
|
||||
const simplifiedConfigA = simplifyConfig(configA);
|
||||
const simplifiedConfigB = simplifyConfig(configB);
|
||||
return (
|
||||
JSON.stringify(simplifiedConfigA) === JSON.stringify(simplifiedConfigB)
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 { t } from '@superset-ui/core';
|
||||
import { SelectValue } from 'antd/lib/select';
|
||||
import { ControlPanelConfig } from '@superset-ui/chart-controls';
|
||||
|
||||
/**
|
||||
* Get the layer configuration object from the control panel.
|
||||
*
|
||||
* @param controlPanel The control panel
|
||||
* @returns The layer configuration object or undefined if not found
|
||||
*/
|
||||
export const getLayerConfig = (controlPanel: ControlPanelConfig) => {
|
||||
let layerConfig: any;
|
||||
controlPanel.controlPanelSections.forEach(section => {
|
||||
if (!section) {
|
||||
return;
|
||||
}
|
||||
const { controlSetRows } = section;
|
||||
controlSetRows.forEach((row: any[]) => {
|
||||
const configObject = row[0] as any;
|
||||
if (configObject && configObject.name === 'layer_configs') {
|
||||
layerConfig = configObject;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return layerConfig;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutates response of chart request into select options.
|
||||
*
|
||||
* If a currently selected value is not included in the response,
|
||||
* it will be added explicitly, in order to prevent antd from creating
|
||||
* a non-user-friendly select option.
|
||||
*
|
||||
* @param response Response json from resolved http request.
|
||||
* @param value The currently selected value of the select input.
|
||||
* @returns The list of options for the select input.
|
||||
*/
|
||||
export const selectedChartMutator = (
|
||||
response: Record<string, any>,
|
||||
value: SelectValue | undefined,
|
||||
) => {
|
||||
if (!response?.result) {
|
||||
if (value && typeof value === 'string') {
|
||||
return [
|
||||
{
|
||||
label: JSON.parse(value).slice_name,
|
||||
value,
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
const data: Record<string, any> = [];
|
||||
if (value && typeof value === 'string') {
|
||||
const parsedValue = JSON.parse(value);
|
||||
let itemFound = false;
|
||||
response.result.forEach((config: any) => {
|
||||
const configString = JSON.stringify(config);
|
||||
const sameId = config.id === parsedValue.id;
|
||||
const isUpdated = configString !== value;
|
||||
const label = config.slice_name;
|
||||
|
||||
if (sameId) {
|
||||
itemFound = true;
|
||||
}
|
||||
if (!sameId || !isUpdated) {
|
||||
data.push({
|
||||
value: configString,
|
||||
label,
|
||||
});
|
||||
} else {
|
||||
data.push({
|
||||
value: configString,
|
||||
label: (
|
||||
<span>
|
||||
<i>({t('updated')}) </i>
|
||||
{label}
|
||||
</span>
|
||||
),
|
||||
});
|
||||
data.push({
|
||||
value,
|
||||
label,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!itemFound) {
|
||||
data.push({
|
||||
value,
|
||||
label: parsedValue.slice_name,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
response.result.forEach((config: any) => {
|
||||
const configString = JSON.stringify(config);
|
||||
const label = config.slice_name;
|
||||
|
||||
data.push({
|
||||
value: configString,
|
||||
label,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Util for geometry related operations.
|
||||
*/
|
||||
|
||||
import GeoJSON from 'ol/format/GeoJSON';
|
||||
import Feature from 'ol/Feature';
|
||||
import { Point as OlPoint } from 'ol/geom';
|
||||
import VectorSource from 'ol/source/Vector';
|
||||
import { Point as GeoJsonPoint } from 'geojson';
|
||||
|
||||
/**
|
||||
* Extracts the coordinate from a Point GeoJSON in the current map projection.
|
||||
*
|
||||
* @param geoJsonPoint The GeoJSON string for the point
|
||||
*
|
||||
* @returns The coordinate
|
||||
*/
|
||||
export const getProjectedCoordinateFromPointGeoJson = (
|
||||
geoJsonPoint: GeoJsonPoint,
|
||||
) => {
|
||||
const geom: OlPoint = new GeoJSON().readGeometry(geoJsonPoint, {
|
||||
// TODO: adapt to map projection
|
||||
featureProjection: 'EPSG:3857',
|
||||
}) as OlPoint;
|
||||
return geom.getCoordinates();
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the extent for an array of features.
|
||||
*
|
||||
* @param features An Array of OpenLayers features
|
||||
* @returns The OpenLayers extent or undefined
|
||||
*/
|
||||
export const getExtentFromFeatures = (features: Feature[]) => {
|
||||
if (features.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const source = new VectorSource();
|
||||
source.addFeatures(features);
|
||||
return source.getExtent();
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Util for layer related operations.
|
||||
*/
|
||||
|
||||
import OlParser from 'geostyler-openlayers-parser';
|
||||
import TileLayer from 'ol/layer/Tile';
|
||||
import TileWMS from 'ol/source/TileWMS';
|
||||
import { bbox as bboxStrategy } from 'ol/loadingstrategy';
|
||||
import VectorLayer from 'ol/layer/Vector';
|
||||
import VectorSource from 'ol/source/Vector';
|
||||
import XyzSource from 'ol/source/XYZ';
|
||||
import GeoJSON from 'ol/format/GeoJSON';
|
||||
import { WmsLayerConf, WfsLayerConf, LayerConf, XyzLayerConf } from '../types';
|
||||
import { isWfsLayerConf, isWmsLayerConf, isXyzLayerConf } from '../typeguards';
|
||||
import { isVersionBelow } from './serviceUtil';
|
||||
|
||||
/**
|
||||
* Create a WMS layer.
|
||||
*
|
||||
* @param wmsLayerConf The layer configuration
|
||||
*
|
||||
* @returns The created WMS layer
|
||||
*/
|
||||
export const createWmsLayer = (wmsLayerConf: WmsLayerConf) => {
|
||||
const { url, layersParam, version, attribution } = wmsLayerConf;
|
||||
return new TileLayer({
|
||||
source: new TileWMS({
|
||||
url,
|
||||
params: {
|
||||
LAYERS: layersParam,
|
||||
VERSION: version,
|
||||
},
|
||||
attributions: attribution,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a XYZ layer.
|
||||
*
|
||||
* @param xyzLayerConf The layer configuration
|
||||
*
|
||||
* @returns The created XYZ layer
|
||||
*/
|
||||
export const createXyzLayer = (xyzLayerConf: XyzLayerConf) => {
|
||||
const { url, attribution } = xyzLayerConf;
|
||||
return new TileLayer({
|
||||
source: new XyzSource({
|
||||
url,
|
||||
attributions: attribution,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a WFS layer.
|
||||
*
|
||||
* @param wfsLayerConf The layer configuration
|
||||
*
|
||||
* @returns The created WFS layer
|
||||
*/
|
||||
export const createWfsLayer = async (wfsLayerConf: WfsLayerConf) => {
|
||||
const {
|
||||
url,
|
||||
typeName,
|
||||
maxFeatures,
|
||||
version = '1.1.0',
|
||||
style,
|
||||
attribution,
|
||||
} = wfsLayerConf;
|
||||
|
||||
const wfsSource = new VectorSource({
|
||||
format: new GeoJSON(),
|
||||
attributions: attribution,
|
||||
url: extent => {
|
||||
const requestUrl = new URL(url);
|
||||
const params = requestUrl.searchParams;
|
||||
params.append('service', 'wfs');
|
||||
params.append('request', 'GetFeature');
|
||||
params.append('outputFormat', 'application/json');
|
||||
// TODO: make CRS configurable or take it from Ol Map
|
||||
params.append('srsName', 'EPSG:3857');
|
||||
params.append('version', version);
|
||||
|
||||
let typeNameQuery = 'typeNames';
|
||||
if (isVersionBelow(version, '2.0.0', 'WFS')) {
|
||||
typeNameQuery = 'typeName';
|
||||
}
|
||||
params.append(typeNameQuery, typeName);
|
||||
|
||||
params.append('bbox', extent.join(','));
|
||||
if (maxFeatures) {
|
||||
let maxFeaturesQuery = 'count';
|
||||
if (isVersionBelow(version, '2.0.0', 'WFS')) {
|
||||
maxFeaturesQuery = 'maxFeatures';
|
||||
}
|
||||
params.append(maxFeaturesQuery, maxFeatures.toString());
|
||||
}
|
||||
|
||||
return requestUrl.toString();
|
||||
},
|
||||
strategy: bboxStrategy,
|
||||
});
|
||||
|
||||
let writeStyleResult;
|
||||
if (style) {
|
||||
const olParser = new OlParser();
|
||||
writeStyleResult = await olParser.writeStyle(style);
|
||||
if (writeStyleResult.errors) {
|
||||
console.warn('Could not create ol-style', writeStyleResult.errors);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return new VectorLayer({
|
||||
source: wfsSource,
|
||||
// @ts-ignore
|
||||
style: writeStyleResult?.output,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a layer instance with the provided configuration.
|
||||
*
|
||||
* @param layerConf The layer configuration
|
||||
*
|
||||
* @returns The created layer
|
||||
*/
|
||||
export const createLayer = async (layerConf: LayerConf) => {
|
||||
let layer;
|
||||
if (isWmsLayerConf(layerConf)) {
|
||||
layer = createWmsLayer(layerConf);
|
||||
} else if (isWfsLayerConf(layerConf)) {
|
||||
layer = await createWfsLayer(layerConf);
|
||||
} else if (isXyzLayerConf(layerConf)) {
|
||||
layer = createXyzLayer(layerConf);
|
||||
} else {
|
||||
console.warn('Provided layerconfig is not recognized');
|
||||
}
|
||||
return layer;
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Util for map related operations.
|
||||
*/
|
||||
import { Map } from 'ol';
|
||||
import GeoJSON from 'ol/format/GeoJSON';
|
||||
import { ChartConfig } from '../types';
|
||||
import { getExtentFromFeatures } from './geometryUtil';
|
||||
|
||||
// default map extent of world if no features are found
|
||||
// TODO: move to generic config file or plugin configuration
|
||||
// TODO: adapt to CRS other than Web Mercator
|
||||
const defaultExtent = [-16000000, -7279000, 20500000, 11000000];
|
||||
|
||||
/**
|
||||
* Fits map to the spatial extent of provided charts.
|
||||
*
|
||||
* @param olMap The OpenLayers map
|
||||
* @param chartConfigs The chart configuration
|
||||
*/
|
||||
export const fitMapToCharts = (olMap: Map, chartConfigs: ChartConfig) => {
|
||||
const view = olMap.getView();
|
||||
const features = new GeoJSON().readFeatures(chartConfigs, {
|
||||
// TODO: adapt to map projection
|
||||
featureProjection: 'EPSG:3857',
|
||||
});
|
||||
|
||||
const extent = getExtentFromFeatures(features) || defaultExtent;
|
||||
|
||||
view.fit(extent, {
|
||||
// tested for a desktop size monitor
|
||||
size: [250, 250],
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the available versions of WFS and WMS.
|
||||
*
|
||||
* @returns the versions
|
||||
*/
|
||||
export const getServiceVersions = () => ({
|
||||
WMS: ['1.3.0', '1.1.1'],
|
||||
WFS: ['2.0.2', '2.0.0', '1.1.0'],
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks if a given version is below the comparer version.
|
||||
*
|
||||
* @param version The version to check.
|
||||
* @param below The version to compare to.
|
||||
* @param serviceType The service type.
|
||||
* @returns True, if the version is below comparer version. False, otherwise.
|
||||
*/
|
||||
export const isVersionBelow = (
|
||||
version: string,
|
||||
below: string,
|
||||
serviceType: 'WFS' | 'WMS',
|
||||
) => {
|
||||
const versions = getServiceVersions()[serviceType];
|
||||
// versions is ordered from newest to oldest, so we invert the order
|
||||
// to improve the readability of this function.
|
||||
versions.reverse();
|
||||
const versionIdx = versions.indexOf(version);
|
||||
if (versionIdx === -1) {
|
||||
// TODO: consider throwing an error instead
|
||||
return false;
|
||||
}
|
||||
const belowIdx = versions.indexOf(below);
|
||||
if (belowIdx === -1) {
|
||||
// TODO: consider throwing an error instead
|
||||
return false;
|
||||
}
|
||||
|
||||
return versionIdx < belowIdx;
|
||||
};
|
||||
@@ -0,0 +1,340 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 {
|
||||
ChartProps,
|
||||
convertKeysToCamelCase,
|
||||
DataRecord,
|
||||
} from '@superset-ui/core';
|
||||
import { isObject } from 'lodash';
|
||||
import {
|
||||
LocationConfigMapping,
|
||||
SelectedChartConfig,
|
||||
ChartConfig,
|
||||
ChartConfigFeature,
|
||||
} from '../types';
|
||||
|
||||
const COLUMN_SEPARATOR = ', ';
|
||||
|
||||
/**
|
||||
* Get the indices of columns where the title is a geojson.
|
||||
*
|
||||
* @param columns List of column names.
|
||||
* @returns List of indices containing geojsonColumns.
|
||||
*/
|
||||
export const getGeojsonColumns = (columns: string[]) =>
|
||||
columns.reduce((prev, current, idx) => {
|
||||
let parsedColName;
|
||||
try {
|
||||
parsedColName = JSON.parse(current);
|
||||
} catch {
|
||||
parsedColName = undefined;
|
||||
}
|
||||
if (!parsedColName || !isObject(parsedColName)) {
|
||||
return [...prev];
|
||||
}
|
||||
if (!('type' in parsedColName) || !('coordinates' in parsedColName)) {
|
||||
return [...prev];
|
||||
}
|
||||
return [...prev, idx];
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Create a column name ignoring provided indices.
|
||||
*
|
||||
* @param columns List of column names.
|
||||
* @param ignoreIdx List of indices to ignore.
|
||||
* @returns Column name.
|
||||
*/
|
||||
export const createColumnName = (columns: string[], ignoreIdx: number[]) =>
|
||||
columns.filter((l, idx) => !ignoreIdx.includes(idx)).join(COLUMN_SEPARATOR);
|
||||
|
||||
/**
|
||||
* Group data by location for data providing a generic
|
||||
* x-axis.
|
||||
*
|
||||
* @param data The data to group.
|
||||
* @param params The data params.
|
||||
* @returns Data grouped by location.
|
||||
*/
|
||||
export const groupByLocationGenericX = (
|
||||
data: DataRecord[],
|
||||
params: SelectedChartConfig['params'],
|
||||
queryData: any,
|
||||
) => {
|
||||
const locations: LocationConfigMapping = {};
|
||||
if (!data) {
|
||||
return locations;
|
||||
}
|
||||
data.forEach(d => {
|
||||
Object.keys(d)
|
||||
.filter(k => k !== params.x_axis)
|
||||
.forEach(k => {
|
||||
const labelMap: string[] = queryData.label_map?.[k];
|
||||
|
||||
if (!labelMap) {
|
||||
console.log(
|
||||
'Cannot extract location from queryData. label_map not defined',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const geojsonCols = getGeojsonColumns(labelMap);
|
||||
|
||||
if (geojsonCols.length > 1) {
|
||||
// TODO what should we do, if there is more than one geom column?
|
||||
console.log(
|
||||
'More than one geometry column detected. Using first found.',
|
||||
);
|
||||
}
|
||||
const location = labelMap[geojsonCols[0]];
|
||||
const filter = geojsonCols.length ? [geojsonCols[0]] : [];
|
||||
const leftOverKey = createColumnName(labelMap, filter);
|
||||
|
||||
if (!Object.keys(locations).includes(location)) {
|
||||
locations[location] = [];
|
||||
}
|
||||
|
||||
let dataAtX = locations[location].find(
|
||||
i => i[params.x_axis] === d[params.x_axis],
|
||||
);
|
||||
|
||||
if (!dataAtX) {
|
||||
dataAtX = {
|
||||
// add the x_axis value explicitly, since we
|
||||
// filtered it out for the rest of the computation.
|
||||
[params.x_axis]: d[params.x_axis],
|
||||
};
|
||||
locations[location].push(dataAtX);
|
||||
}
|
||||
dataAtX[leftOverKey] = d[k];
|
||||
});
|
||||
});
|
||||
|
||||
return locations;
|
||||
};
|
||||
|
||||
/**
|
||||
* Group data by location.
|
||||
*
|
||||
* @param data The incoming dataset
|
||||
* @param geomColumn The name of the geometry column
|
||||
* @returns The grouped data
|
||||
*/
|
||||
export const groupByLocation = (data: DataRecord[], geomColumn: string) => {
|
||||
const locations: LocationConfigMapping = {};
|
||||
|
||||
data.forEach(d => {
|
||||
const loc = d[geomColumn] as string;
|
||||
if (!loc) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Object.keys(locations).includes(loc)) {
|
||||
locations[loc] = [];
|
||||
}
|
||||
|
||||
const newData = {
|
||||
...d,
|
||||
};
|
||||
delete newData[geomColumn];
|
||||
|
||||
locations[loc].push(newData);
|
||||
});
|
||||
|
||||
return locations;
|
||||
};
|
||||
|
||||
/**
|
||||
* Strips the geom from colnames and coltypes.
|
||||
*
|
||||
* @param queryData The querydata.
|
||||
* @param geomColumn Name of the geom column.
|
||||
* @returns colnames and coltypes without the geom.
|
||||
*/
|
||||
export const stripGeomFromColnamesAndTypes = (
|
||||
queryData: any,
|
||||
geomColumn: string,
|
||||
) => {
|
||||
const newColnames: string[] = [];
|
||||
const newColtypes: number[] = [];
|
||||
queryData.colnames?.forEach((colname: string, idx: number) => {
|
||||
if (colname === geomColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = colname.split(COLUMN_SEPARATOR);
|
||||
const geojsonColumns = getGeojsonColumns(parts);
|
||||
const filter = geojsonColumns.length ? [geojsonColumns[0]] : [];
|
||||
|
||||
const newColname = createColumnName(parts, filter);
|
||||
if (newColnames.includes(newColname)) {
|
||||
return;
|
||||
}
|
||||
newColnames.push(newColname);
|
||||
newColtypes.push(queryData.coltypes[idx]);
|
||||
});
|
||||
|
||||
return {
|
||||
colnames: newColnames,
|
||||
coltypes: newColtypes,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Strips the geom from labelMap.
|
||||
*
|
||||
* @param queryData The querydata.
|
||||
* @param geomColumn Name of the geom column.
|
||||
* @returns labelMap without the geom column.
|
||||
*/
|
||||
export const stripGeomColumnFromLabelMap = (
|
||||
labelMap: { [key: string]: string[] },
|
||||
geomColumn: string,
|
||||
) => {
|
||||
const newLabelMap = {};
|
||||
Object.entries(labelMap).forEach(([key, value]) => {
|
||||
if (key === geomColumn) {
|
||||
return;
|
||||
}
|
||||
const geojsonCols = getGeojsonColumns(value);
|
||||
const filter = geojsonCols.length ? [geojsonCols[0]] : [];
|
||||
const columnName = createColumnName(value, filter);
|
||||
const restItems = value.filter((v, idx) => !geojsonCols.includes(idx));
|
||||
newLabelMap[columnName] = restItems;
|
||||
});
|
||||
return newLabelMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Strip occurrences of the geom column from the query data.
|
||||
*
|
||||
* @param queryDataClone The query data
|
||||
* @param geomColumn The name of the geom column
|
||||
* @returns query data without geom column.
|
||||
*/
|
||||
export const stripGeomColumnFromQueryData = (
|
||||
queryData: any,
|
||||
geomColumn: string,
|
||||
) => {
|
||||
const queryDataClone = {
|
||||
...structuredClone(queryData),
|
||||
...stripGeomFromColnamesAndTypes(queryData, geomColumn),
|
||||
};
|
||||
if (queryDataClone.label_map) {
|
||||
queryDataClone.label_map = stripGeomColumnFromLabelMap(
|
||||
queryData.label_map,
|
||||
geomColumn,
|
||||
);
|
||||
}
|
||||
return queryDataClone;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the chart configurations depending on the referenced Superset chart.
|
||||
*
|
||||
* @param selectedChart The configuration of the referenced Superset chart
|
||||
* @param geomColumn The name of the geometry column
|
||||
* @param chartProps The properties provided within this OL plugin
|
||||
* @param chartTransformer The transformer function
|
||||
* @returns The chart configurations
|
||||
*/
|
||||
export const getChartConfigs = (
|
||||
selectedChart: SelectedChartConfig,
|
||||
geomColumn: string,
|
||||
chartProps: ChartProps,
|
||||
chartTransformer: any,
|
||||
) => {
|
||||
const chartFormDataSnake = selectedChart.params;
|
||||
const chartFormData = convertKeysToCamelCase(chartFormDataSnake);
|
||||
|
||||
const baseConfig = {
|
||||
...chartProps,
|
||||
// We overwrite width and height, which are not needed
|
||||
// here, but leads to unnecessary updating of the UI.
|
||||
width: null,
|
||||
height: null,
|
||||
formData: chartFormData,
|
||||
rawFormData: chartFormDataSnake,
|
||||
datasource: {},
|
||||
};
|
||||
|
||||
const { queriesData } = chartProps;
|
||||
const [queryData] = queriesData;
|
||||
|
||||
const data = queryData.data as DataRecord[];
|
||||
let dataByLocation: LocationConfigMapping;
|
||||
|
||||
const chartConfigs: ChartConfig = {
|
||||
type: 'FeatureCollection',
|
||||
features: [],
|
||||
};
|
||||
|
||||
if (!data) {
|
||||
return chartConfigs;
|
||||
}
|
||||
|
||||
if ('x_axis' in selectedChart.params) {
|
||||
dataByLocation = groupByLocationGenericX(
|
||||
data,
|
||||
selectedChart.params,
|
||||
queryData,
|
||||
);
|
||||
} else {
|
||||
dataByLocation = groupByLocation(data, geomColumn);
|
||||
}
|
||||
|
||||
const strippedQueryData = stripGeomColumnFromQueryData(queryData, geomColumn);
|
||||
|
||||
Object.keys(dataByLocation).forEach(location => {
|
||||
const config = {
|
||||
...baseConfig,
|
||||
queriesData: [
|
||||
{
|
||||
...strippedQueryData,
|
||||
data: dataByLocation[location],
|
||||
},
|
||||
],
|
||||
};
|
||||
const transformedProps = chartTransformer(config);
|
||||
|
||||
const feature: ChartConfigFeature = {
|
||||
type: 'Feature',
|
||||
geometry: JSON.parse(location),
|
||||
properties: {
|
||||
...transformedProps,
|
||||
},
|
||||
};
|
||||
|
||||
chartConfigs.features.push(feature);
|
||||
});
|
||||
return chartConfigs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the same chart configuration with parsed values for of the stringified "params" object.
|
||||
*
|
||||
* @param selectedChart Incoming chart configuration
|
||||
* @returns Chart configuration with parsed values for "params"
|
||||
*/
|
||||
export const parseSelectedChart = (selectedChart: string) => {
|
||||
const selectedChartParsed = JSON.parse(selectedChart);
|
||||
selectedChartParsed.params = JSON.parse(selectedChartParsed.params);
|
||||
return selectedChartParsed;
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 const MAX_ZOOM_LEVEL = 28;
|
||||
export const MIN_ZOOM_LEVEL = 0;
|
||||
Reference in New Issue
Block a user