Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Maps] Support custom icons in maps #113144

Merged
merged 73 commits into from
Mar 30, 2022
Merged
Show file tree
Hide file tree
Changes from 66 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
00d4404
Initial work on custom icon support in maps
nickpeihl Sep 27, 2021
74d8cf0
Persist custom icons to saved object
nickpeihl Sep 27, 2021
fa799ea
Support static SVG custom icons
nickpeihl Sep 28, 2021
1b8fd8f
Merge branch 'master' of https://github.com/elastic/kibana into maps-…
nickpeihl Sep 29, 2021
f98296a
Move custom icons to top of select list
nickpeihl Sep 29, 2021
43478f5
Better handling for switching custom icons
nickpeihl Sep 29, 2021
4d178c9
Fix label for custom icons
nickpeihl Sep 29, 2021
1adb5ff
Increase resolution of SDF icons
nickpeihl Sep 29, 2021
0e8595f
Fix icon in select form
nickpeihl Sep 30, 2021
a7b5703
Add custom icons to legend
nickpeihl Sep 30, 2021
e664dbc
Support custom icons in dynamic icon form
nickpeihl Oct 1, 2021
70663e1
Store custom icons in map rather than layer
nickpeihl Oct 5, 2021
a911f6d
Fix dynamic icons in breaked legend
nickpeihl Oct 5, 2021
df34d94
Merge branch 'master' of https://github.com/elastic/kibana into maps-…
nickpeihl Oct 11, 2021
2a6fee1
Merge branch 'master' of https://github.com/elastic/kibana into maps-…
nickpeihl Oct 12, 2021
e485048
Preview custom icon in modal
nickpeihl Oct 13, 2021
f67a07f
Convert IconPreview to class component
nickpeihl Oct 13, 2021
794ca84
Merge branch 'master' of https://github.com/elastic/kibana into maps-…
nickpeihl Oct 21, 2021
3127e05
Merge branch 'main' of https://github.com/elastic/kibana into maps-cu…
nickpeihl Nov 4, 2021
397c17e
Merge branch 'main' of https://github.com/elastic/kibana into maps-cu…
nickpeihl Nov 4, 2021
7c03cc8
Allow fine-tuning for SDF icons
nickpeihl Nov 11, 2021
66d9330
Merge branch 'main' of https://github.com/elastic/kibana into maps-cu…
nickpeihl Nov 16, 2021
f6e7f1f
Merge branch 'main' of https://github.com/elastic/kibana into maps-cu…
nickpeihl Jan 5, 2022
605eab5
Better custom icon handling
nickpeihl Feb 4, 2022
3092934
Add Custom Icons to Map Settings panel
nickpeihl Feb 9, 2022
ca6d4cb
Propagate icon changes to map
nickpeihl Feb 9, 2022
178621a
Merge branch 'main' of https://github.com/elastic/kibana into maps-cu…
nickpeihl Feb 9, 2022
ff0b37d
Add documentation
nickpeihl Feb 10, 2022
ab0dda5
Add empty state for custom icons in map settings
nickpeihl Feb 14, 2022
d26cd04
Render custom icons at higher resolution
nickpeihl Feb 14, 2022
4f78947
Merge branch 'main' of https://github.com/elastic/kibana into maps-cu…
nickpeihl Feb 15, 2022
7b9fa80
Merge branch 'main' of https://github.com/elastic/kibana into maps-cu…
nickpeihl Feb 24, 2022
e1dc51e
Add and fix unit tests
nickpeihl Feb 28, 2022
a66b113
Merge branch 'main' of https://github.com/elastic/kibana into maps-cu…
nickpeihl Feb 28, 2022
1800930
Rename util function
nickpeihl Mar 2, 2022
354728d
Add unit tests
nickpeihl Mar 2, 2022
ab63623
clean up cruft
nickpeihl Mar 2, 2022
b8c8a8d
Custom icons panel test
nickpeihl Mar 2, 2022
9c58d32
Add validation to custom icon modal
nickpeihl Mar 3, 2022
b4c7dbb
Add visual regression test
nickpeihl Mar 3, 2022
3b03940
Merge branch 'main' of https://github.com/elastic/kibana into maps-cu…
nickpeihl Mar 3, 2022
86f1790
lint
nickpeihl Mar 3, 2022
57b0f0e
Update snapshot
nickpeihl Mar 4, 2022
e739756
Merge branch 'main' of https://github.com/elastic/kibana into maps-cu…
nickpeihl Mar 4, 2022
1577928
Merge branch 'main' into maps-custom-icons
kibanamachine Mar 7, 2022
eeaa986
Make custom icons panel more consistent with Maps
nickpeihl Mar 10, 2022
8a79797
Icon modal review feedback
nickpeihl Mar 15, 2022
ca959e8
Only store custom icon svgs in mapStateJSON
nickpeihl Mar 23, 2022
fdfd708
Do not delete custom icons in use by layers
nickpeihl Mar 23, 2022
aa45814
Merge branch 'main' of https://github.com/elastic/kibana into maps-cu…
nickpeihl Mar 23, 2022
2775b05
Merge branch 'maps-custom-icons' of https://github.com/nickpeihl/kiba…
nickpeihl Mar 23, 2022
0063399
Icon select feedback
nickpeihl Mar 23, 2022
81150de
Fix missing icons in BreakedLegend
nickpeihl Mar 23, 2022
4059eea
lint fixes
nickpeihl Mar 23, 2022
61017e6
Add `iconSource` property to `IconStop`
nickpeihl Mar 24, 2022
c89ae36
Encode SVGs in mapStateJSON as base64
nickpeihl Mar 24, 2022
a36af1d
Remove unused module
nickpeihl Mar 24, 2022
e62fd9a
review feedback
nickpeihl Mar 28, 2022
afc6b31
Fix tests
nickpeihl Mar 28, 2022
c6f40ed
Merge branch 'main' of https://github.com/elastic/kibana into maps-cu…
nickpeihl Mar 28, 2022
11b0046
Review feedback
nickpeihl Mar 28, 2022
cd0b17c
Review feedback
nickpeihl Mar 28, 2022
e2887ae
review feedback
nickpeihl Mar 29, 2022
e087b00
fix typo in functional test
nickpeihl Mar 29, 2022
b9973fc
Pass CustomIcon[] array to style classes
nickpeihl Mar 29, 2022
cd301b2
Remove custom icon demo visual regression test
nickpeihl Mar 29, 2022
09b3311
Review feedback
nickpeihl Mar 29, 2022
ad559b7
Merge branch 'main' into maps-custom-icons
nickpeihl Mar 29, 2022
bd48b2b
Review feedback
nickpeihl Mar 29, 2022
e822e0b
Merge branch 'maps-custom-icons' of https://github.com/nickpeihl/kiba…
nickpeihl Mar 29, 2022
827c7c2
Review feedback
nickpeihl Mar 29, 2022
bdc6279
Merge branch 'main' into maps-custom-icons
nickpeihl Mar 30, 2022
38f6dd0
Merge branch 'main' into maps-custom-icons
nickpeihl Mar 30, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/maps/vector-style-properties.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ Available icons
[role="screenshot"]
image::maps/images/maki-icons.png[]

Custom Icons

You may also use your own SVG icon to style Point features in your map. In Layer Settings click the **Add custom icon** button from the *Icon* dropdown list to upload your icon to the map. For best results, your SVG icon should be monochrome and have limited details. Dynamic styling in **Elastic Maps** requires rendering SVG icons as PNGs using a https://en.wikipedia.org/wiki/Signed_distance_function[Signed Distance Function]. As a result, sharp corners and intricate details may not render correctly. You may be able to tweak the **Alpha threshold** and **Radius** when adding or editing the icon for better results.

Custom icons can be added, edited, or deleted in Map settings.


[float]
[[polygon-style-properties]]
Expand Down
11 changes: 11 additions & 0 deletions x-pack/plugins/maps/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,17 @@ export enum LABEL_BORDER_SIZES {
}

export const DEFAULT_ICON = 'marker';
export const DEFAULT_CUSTOM_ICON_CUTOFF = 0.25;
export const DEFAULT_CUSTOM_ICON_RADIUS = 0.25;
export const CUSTOM_ICON_SIZE = 64;
export const CUSTOM_ICON_PREFIX_SDF = '__kbn__custom_icon_sdf__';
export const MAKI_ICON_SIZE = 16;
export const HALF_MAKI_ICON_SIZE = MAKI_ICON_SIZE / 2;

export enum ICON_SOURCE {
CUSTOM = 'CUSTOM',
MAKI = 'MAKI',
}

export enum VECTOR_STYLES {
SYMBOLIZE_AS = 'symbolizeAs',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import {
COLOR_MAP_TYPE,
FIELD_ORIGIN,
ICON_SOURCE,
LABEL_BORDER_SIZES,
SYMBOLIZE_AS_TYPES,
VECTOR_STYLES,
Expand Down Expand Up @@ -60,6 +61,7 @@ export type CategoryColorStop = {
export type IconStop = {
stop: string | null;
icon: string;
iconSource?: ICON_SOURCE;
};

export type ColorDynamicOptions = {
Expand Down Expand Up @@ -108,6 +110,9 @@ export type IconDynamicOptions = {

export type IconStaticOptions = {
value: string; // icon id
label?: string;
svg?: string;
iconSource?: ICON_SOURCE;
};

export type IconStylePropertyDescriptor =
Expand Down Expand Up @@ -178,6 +183,14 @@ export type SizeStylePropertyDescriptor =
options: SizeDynamicOptions;
};

export type CustomIcon = {
symbolId: string;
svg: string; // svg string
label: string; // user given label
cutoff: number;
radius: number;
};

export type VectorStylePropertiesDescriptor = {
[VECTOR_STYLES.SYMBOLIZE_AS]: SymbolizeAsStylePropertyDescriptor;
[VECTOR_STYLES.FILL_COLOR]: ColorStylePropertyDescriptor;
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/maps/public/actions/layer_actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ describe('layer_actions', () => {
return true;
};

// eslint-disable-next-line @typescript-eslint/no-var-requires
require('../selectors/map_selectors').getCustomIcons = () => {
return [];
};

// eslint-disable-next-line @typescript-eslint/no-var-requires
require('../selectors/map_selectors').createLayerInstance = () => {
return {
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/maps/public/actions/layer_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Query } from 'src/plugins/data/public';
import { MapStoreState } from '../reducers/store';
import {
createLayerInstance,
getCustomIcons,
getEditState,
getLayerById,
getLayerList,
Expand Down Expand Up @@ -174,8 +175,7 @@ export function addLayer(layerDescriptor: LayerDescriptor) {
layer: layerDescriptor,
});
dispatch(syncDataForLayerId(layerDescriptor.id, false));

const layer = createLayerInstance(layerDescriptor);
const layer = createLayerInstance(layerDescriptor, getCustomIcons(getState()));
const features = await layer.getLicensedFeatures();
features.forEach(notifyLicensedFeatureUsage);
};
Expand Down
58 changes: 55 additions & 3 deletions x-pack/plugins/maps/public/actions/map_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ import turfBooleanContains from '@turf/boolean-contains';
import { Filter } from '@kbn/es-query';
import { Query, TimeRange } from 'src/plugins/data/public';
import { Geometry, Position } from 'geojson';
import { asyncForEach } from '@kbn/std';
import { DRAW_MODE, DRAW_SHAPE } from '../../common/constants';
import { asyncForEach, asyncMap } from '@kbn/std';
import { DRAW_MODE, DRAW_SHAPE, LAYER_STYLE_TYPE } from '../../common/constants';
import type { MapExtentState, MapViewContext } from '../reducers/map/types';
import { MapStoreState } from '../reducers/store';
import { IVectorStyle } from '../classes/styles/vector/vector_style';
import {
getDataFilters,
getFilters,
Expand Down Expand Up @@ -60,7 +61,13 @@ import {
} from './data_request_actions';
import { addLayer, addLayerWithoutDataSync } from './layer_actions';
import { MapSettings } from '../reducers/map';
import { DrawState, MapCenterAndZoom, MapExtent, Timeslice } from '../../common/descriptor_types';
import {
CustomIcon,
DrawState,
MapCenterAndZoom,
MapExtent,
Timeslice,
} from '../../common/descriptor_types';
import { INITIAL_LOCATION } from '../../common/constants';
import { updateTooltipStateForLayer } from './tooltip_actions';
import { isVectorLayer, IVectorLayer } from '../classes/layers/vector_layer';
Expand Down Expand Up @@ -108,6 +115,51 @@ export function updateMapSetting(
};
}

export function updateCustomIcons(customIcons: CustomIcon[]) {
return {
type: UPDATE_MAP_SETTING,
settingKey: 'customIcons',
settingValue: customIcons.map((icon) => {
return { ...icon, svg: Buffer.from(icon.svg).toString('base64') };
}),
};
}

export function deleteCustomIcon(value: string) {
return async (
dispatch: ThunkDispatch<MapStoreState, void, AnyAction>,
getState: () => MapStoreState
) => {
const layersContainingCustomIcon = getLayerList(getState()).filter((layer) => {
const style = layer.getCurrentStyle();
if (!style || style.getType() !== LAYER_STYLE_TYPE.VECTOR) {
return false;
}
return (style as IVectorStyle).isUsingCustomIcon(value);
});

if (layersContainingCustomIcon.length > 0) {
const layerList = await asyncMap(layersContainingCustomIcon, async (layer) => {
return await layer.getDisplayName();
});
getToasts().addWarning(
i18n.translate('xpack.maps.mapActions.deleteCustomIconWarning', {
defaultMessage: `Unable to delete icon. The icon is in use by the {count, plural, one {layer} other {layers}}: {layerNames}`,
values: {
count: layerList.length,
layerNames: layerList.join(', '),
},
})
);
} else {
const newIcons = getState().map.settings.customIcons.filter(
({ symbolId }) => symbolId !== value
);
dispatch(updateMapSetting('customIcons', newIcons));
}
};
}

export function mapReady() {
return (
dispatch: ThunkDispatch<MapStoreState, void, AnyAction>,
Expand Down
9 changes: 6 additions & 3 deletions x-pack/plugins/maps/public/classes/layers/layer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
import { copyPersistentState } from '../../reducers/copy_persistent_state';
import {
Attribution,
CustomIcon,
LayerDescriptor,
MapExtent,
StyleDescriptor,
Expand Down Expand Up @@ -92,7 +93,8 @@ export interface ILayer {
isVisible(): boolean;
cloneDescriptor(): Promise<LayerDescriptor>;
renderStyleEditor(
onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void
onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void,
onCustomIconChange: (customIcons: CustomIcon[]) => void
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to onCustomIconsChange so its the same as in implementation and shows up in search results

): ReactElement<any> | null;
getInFlightRequestTokens(): symbol[];
getPrevRequestToken(dataId: string): symbol | undefined;
Expand Down Expand Up @@ -431,13 +433,14 @@ export class AbstractLayer implements ILayer {
}

renderStyleEditor(
onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void
onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void,
onCustomIconsChange: (customIcons: CustomIcon[]) => void
): ReactElement<any> | null {
const style = this.getStyleForEditing();
if (!style) {
return null;
}
return style.renderEditor(onStyleDescriptorChange);
return style.renderEditor(onStyleDescriptorChange, onCustomIconsChange);
}

getIndexPatternIds(): string[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { BlendedVectorLayer } from './blended_vector_layer';
import { ESSearchSource } from '../../../sources/es_search_source';
import {
AbstractESSourceDescriptor,
CustomIcon,
ESGeoGridSourceDescriptor,
} from '../../../../../common/descriptor_types';

Expand All @@ -23,6 +24,8 @@ jest.mock('../../../../kibana_services', () => {

const mapColors: string[] = [];

const customIcons: CustomIcon[] = [];

const notClusteredDataRequest = {
data: { isSyncClustered: false },
dataId: 'ACTIVE_COUNT_DATA_ID',
Expand Down Expand Up @@ -51,6 +54,7 @@ describe('getSource', () => {
},
mapColors
),
customIcons,
});

const source = blendedVectorLayer.getSource();
Expand All @@ -72,6 +76,7 @@ describe('getSource', () => {
},
mapColors
),
customIcons,
});

const source = blendedVectorLayer.getSource();
Expand Down Expand Up @@ -112,6 +117,7 @@ describe('getSource', () => {
},
mapColors
),
customIcons,
});

const source = blendedVectorLayer.getSource();
Expand All @@ -132,6 +138,7 @@ describe('cloneDescriptor', () => {
},
mapColors
),
customIcons,
});

const clonedLayerDescriptor = await blendedVectorLayer.cloneDescriptor();
Expand All @@ -151,6 +158,7 @@ describe('cloneDescriptor', () => {
},
mapColors
),
customIcons,
});

const clonedLayerDescriptor = await blendedVectorLayer.cloneDescriptor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { ISource } from '../../../sources/source';
import { DataRequestContext } from '../../../../actions';
import { DataRequestAbortError } from '../../../util/data_request';
import {
CustomIcon,
VectorStyleDescriptor,
SizeDynamicOptions,
DynamicStylePropertyOptions,
Expand Down Expand Up @@ -171,6 +172,7 @@ export interface BlendedVectorLayerArguments {
chartsPaletteServiceGetColor?: (value: string) => string | null;
source: IVectorSource;
layerDescriptor: VectorLayerDescriptor;
customIcons: CustomIcon[];
}

export class BlendedVectorLayer extends GeoJsonVectorLayer implements IVectorLayer {
Expand Down Expand Up @@ -207,6 +209,7 @@ export class BlendedVectorLayer extends GeoJsonVectorLayer implements IVectorLay
clusterStyleDescriptor,
this._clusterSource,
this,
options.customIcons,
options.chartsPaletteServiceGetColor
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function createLayer(
sourceDescriptor,
};
const layerDescriptor = MvtVectorLayer.createDescriptor(defaultLayerOptions);
return new MvtVectorLayer({ layerDescriptor, source: mvtSource });
return new MvtVectorLayer({ layerDescriptor, source: mvtSource, customIcons: [] });
}

describe('visiblity', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ export class MvtVectorLayer extends AbstractVectorLayer {

readonly _source: IMvtVectorSource;

constructor({ layerDescriptor, source }: VectorLayerArguments) {
super({ layerDescriptor, source });
constructor({ layerDescriptor, source, customIcons }: VectorLayerArguments) {
super({ layerDescriptor, source, customIcons });
this._source = source as IMvtVectorSource;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ describe('cloneDescriptor', () => {
const layer = new AbstractVectorLayer({
layerDescriptor,
source: new MockSource() as unknown as IVectorSource,
customIcons: [],
});
const clonedDescriptor = await layer.cloneDescriptor();
const clonedStyleProps = (clonedDescriptor.style as VectorStyleDescriptor).properties;
Expand Down Expand Up @@ -123,6 +124,7 @@ describe('cloneDescriptor', () => {
const layer = new AbstractVectorLayer({
layerDescriptor,
source: new MockSource() as unknown as IVectorSource,
customIcons: [],
});
const clonedDescriptor = await layer.cloneDescriptor();
const clonedStyleProps = (clonedDescriptor.style as VectorStyleDescriptor).properties;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
} from '../../util/mb_filter_expressions';
import {
AggDescriptor,
CustomIcon,
DynamicStylePropertyOptions,
DataFilters,
ESTermSourceDescriptor,
Expand Down Expand Up @@ -70,6 +71,7 @@ export interface VectorLayerArguments {
source: IVectorSource;
joins?: InnerJoin[];
layerDescriptor: VectorLayerDescriptor;
customIcons: CustomIcon[];
chartsPaletteServiceGetColor?: (value: string) => string | null;
}

Expand Down Expand Up @@ -133,6 +135,7 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer {
layerDescriptor,
source,
joins = [],
customIcons,
chartsPaletteServiceGetColor,
}: VectorLayerArguments) {
super({
Expand All @@ -144,6 +147,7 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer {
layerDescriptor.style,
source,
this,
customIcons,
chartsPaletteServiceGetColor
);
}
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/maps/public/classes/styles/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
@import 'vector/components/style_prop_editor';
@import 'vector/components/color/color_stops';
@import 'vector/components/symbol/icon_select';
@import 'vector/components/symbol/icon_preview';
@import 'vector/components/symbol/custom_icon_modal';
@import 'vector/components/legend/category';
@import 'vector/components/legend/vector_legend';
5 changes: 3 additions & 2 deletions x-pack/plugins/maps/public/classes/styles/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
*/

import { ReactElement } from 'react';
import { StyleDescriptor } from '../../../common/descriptor_types';
import { CustomIcon, StyleDescriptor } from '../../../common/descriptor_types';

export interface IStyle {
getType(): string;
renderEditor(
onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void
onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void,
onCustomIconsChange: (customIcons: CustomIcon[]) => void
): ReactElement<any> | null;
}
Loading