+
onOptionsChange({ colors: { borders } })}
/>
+
diff --git a/client/app/visualizations/map/Editor/GeneralSettings.jsx b/client/app/visualizations/map/Editor/GeneralSettings.jsx
new file mode 100644
index 0000000000..d24a1641df
--- /dev/null
+++ b/client/app/visualizations/map/Editor/GeneralSettings.jsx
@@ -0,0 +1,71 @@
+import { isNil, map, filter, difference } from 'lodash';
+import React, { useMemo } from 'react';
+import Select from 'antd/lib/select';
+import { EditorPropTypes } from '@/visualizations';
+
+function getColumns(column, unusedColumns) {
+ return filter(
+ [column, ...unusedColumns],
+ v => !isNil(v),
+ );
+}
+
+export default function GeneralSettings({ options, data, onOptionsChange }) {
+ const unusedColumns = useMemo(
+ () => difference(map(data.columns, c => c.name), [options.latColName, options.lonColName, options.classify]),
+ [data, options.latColName, options.lonColName, options.classify],
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+GeneralSettings.propTypes = EditorPropTypes;
diff --git a/client/app/visualizations/map/Editor/GroupsSettings.jsx b/client/app/visualizations/map/Editor/GroupsSettings.jsx
new file mode 100644
index 0000000000..7e4c03f0fd
--- /dev/null
+++ b/client/app/visualizations/map/Editor/GroupsSettings.jsx
@@ -0,0 +1,67 @@
+import { map } from 'lodash';
+import React, { useMemo, useCallback } from 'react';
+import Table from 'antd/lib/table';
+import ColorPicker from '@/components/ColorPicker';
+import { EditorPropTypes } from '@/visualizations';
+import ColorPalette from '@/visualizations/ColorPalette';
+
+import prepareData from '../prepareData';
+
+export default function GroupsSettings({ options, data, onOptionsChange }) {
+ const groups = useMemo(() => map(
+ prepareData(data, options),
+ ({ name }) => ({ name, color: (options.groups[name] || {}).color || null }),
+ ), [data, options]);
+
+ const colors = useMemo(() => ({
+ Automatic: null,
+ ...ColorPalette,
+ }), []);
+
+ const updateGroupOption = useCallback((name, prop, value) => {
+ onOptionsChange({
+ groups: {
+ [name]: {
+ [prop]: value,
+ },
+ },
+ });
+ }, [onOptionsChange]);
+
+ const columns = [
+ {
+ title: 'Group',
+ dataIndex: 'name',
+ },
+ {
+ title: 'Color',
+ dataIndex: 'color',
+ width: '1%',
+ render: (unused, item) => (
+
+ updateGroupOption(item.name, 'color', value)}
+ />
+
+
+ ),
+ },
+ ];
+
+ return (
+
+ );
+}
+
+GroupsSettings.propTypes = EditorPropTypes;
diff --git a/client/app/visualizations/map/Editor/StyleSettings.jsx b/client/app/visualizations/map/Editor/StyleSettings.jsx
new file mode 100644
index 0000000000..97aa6c797a
--- /dev/null
+++ b/client/app/visualizations/map/Editor/StyleSettings.jsx
@@ -0,0 +1,277 @@
+import { isNil, map } from 'lodash';
+import React, { useMemo } from 'react';
+import { useDebouncedCallback } from 'use-debounce';
+import Select from 'antd/lib/select';
+import Input from 'antd/lib/input';
+import Checkbox from 'antd/lib/checkbox';
+import Popover from 'antd/lib/popover';
+import Icon from 'antd/lib/icon';
+import Typography from 'antd/lib/typography';
+import * as Grid from 'antd/lib/grid';
+import ColorPicker from '@/components/ColorPicker';
+import { EditorPropTypes } from '@/visualizations';
+import ColorPalette from '@/visualizations/ColorPalette';
+
+const mapTiles = [
+ {
+ name: 'OpenStreetMap',
+ url: '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+ },
+ {
+ name: 'OpenStreetMap BW',
+ url: '//{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png',
+ },
+ {
+ name: 'OpenStreetMap DE',
+ url: '//{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png',
+ },
+ {
+ name: 'OpenStreetMap FR',
+ url: '//{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png',
+ },
+ {
+ name: 'OpenStreetMap Hot',
+ url: '//{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
+ },
+ {
+ name: 'Thunderforest',
+ url: '//{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png',
+ },
+ {
+ name: 'Thunderforest Spinal',
+ url: '//{s}.tile.thunderforest.com/spinal-map/{z}/{x}/{y}.png',
+ },
+ {
+ name: 'OpenMapSurfer',
+ url: '//korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}',
+ },
+ {
+ name: 'Stamen Toner',
+ url: '//stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.png',
+ },
+ {
+ name: 'Stamen Toner Background',
+ url: '//stamen-tiles-{s}.a.ssl.fastly.net/toner-background/{z}/{x}/{y}.png',
+ },
+ {
+ name: 'Stamen Toner Lite',
+ url: '//stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.png',
+ },
+ {
+ name: 'OpenTopoMap',
+ url: '//{s}.tile.opentopomap.org/{z}/{x}/{y}.png',
+ },
+];
+
+const CustomColorPalette = {
+ White: '#ffffff',
+ ...ColorPalette,
+};
+
+function getCustomIconOptionFields(iconShape) {
+ switch (iconShape) {
+ case 'doughnut':
+ return { showIcon: false, showBackgroundColor: true, showBorderColor: true };
+ case 'circle-dot':
+ case 'rectangle-dot':
+ return { showIcon: false, showBackgroundColor: false, showBorderColor: true };
+ default:
+ return { showIcon: true, showBackgroundColor: true, showBorderColor: true };
+ }
+}
+
+export default function StyleSettings({ options, onOptionsChange }) {
+ const [debouncedOnOptionsChange] = useDebouncedCallback(onOptionsChange, 200);
+
+ const { showIcon, showBackgroundColor, showBorderColor } = useMemo(
+ () => getCustomIconOptionFields(options.iconShape),
+ [options.iconShape],
+ );
+
+ const isCustomMarkersStyleAllowed = isNil(options.classify);
+
+ return (
+
+
+
+
+
+
+ Markers
+
+
+
+
+
+
+
+
+
+ {isCustomMarkersStyleAllowed && options.customizeMarkers && (
+
+
+
+
+
+
+
+
+
+
+ {showIcon && (
+
+
+
+ )}
+ >
+
+
+
+
+