Skip to content

Commit

Permalink
Migrate Choropleth visualization to React (#4313)
Browse files Browse the repository at this point in the history
* Migrate Choropleth to React: skeleton

* Migrate Choropleth to React: Editor - skeleton

* Choropleth Editor: Bounds tab

* Choropleth Editor: Colors tab

* Choropleth Editor: Format tab

* Choropleth Editor: General tab

* Some refinements

* Migrate Choropleth to React: Renderer

* Refine code

* CR1
  • Loading branch information
kravets-levko authored and arikfr committed Nov 14, 2019
1 parent ef56e4e commit 1a95904
Show file tree
Hide file tree
Showing 21 changed files with 986 additions and 714 deletions.
28 changes: 0 additions & 28 deletions client/app/assets/less/inc/visualizations/map.less
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,4 @@
height: 100%;
z-index: 0;
}

.map-custom-control.leaflet-bar {
background: #fff;
padding: 10px;
margin: 10px;
position: absolute;
z-index: 1;

&.top-left {
left: 0;
top: 0;
}

&.top-right {
right: 0;
top: 0;
}

&.bottom-left {
left: 0;
bottom: 0;
}

&.bottom-right {
right: 0;
bottom: 0;
}
}
}
11 changes: 11 additions & 0 deletions client/app/lib/hooks/useMemoWithDeepCompare.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { isEqual } from 'lodash';
import { useMemo, useRef } from 'react';

export default function useMemoWithDeepCompare(create, inputs) {
const valueRef = useRef();
const value = useMemo(create, inputs);
if (!isEqual(value, valueRef.current)) {
valueRef.current = value;
}
return valueRef.current;
}
8 changes: 8 additions & 0 deletions client/app/visualizations/choropleth/ColorPalette.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { extend } from 'lodash';
import ColorPalette from '@/visualizations/ColorPalette';

export default extend({
White: '#ffffff',
Black: '#000000',
'Light Gray': '#dddddd',
}, ColorPalette);
80 changes: 80 additions & 0 deletions client/app/visualizations/choropleth/Editor/BoundsSettings.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { isFinite, cloneDeep } from 'lodash';
import React, { useState, useEffect, useCallback } from 'react';
import { useDebouncedCallback } from 'use-debounce';
import InputNumber from 'antd/lib/input-number';
import * as Grid from 'antd/lib/grid';
import { EditorPropTypes } from '@/visualizations';

export default function BoundsSettings({ options, onOptionsChange }) {
// Bounds may be changed in editor or on preview (by drag/zoom map).
// Changes from preview does not come frequently (only when user release mouse button),
// but changes from editor should be debounced.
// Therefore this component has intermediate state to hold immediate user input,
// which is updated from `options.bounds` and by inputs immediately on user input,
// but `onOptionsChange` event is debounced and uses last value from internal state.

const [bounds, setBounds] = useState(options.bounds);
const [onOptionsChangeDebounced] = useDebouncedCallback(onOptionsChange, 200);

useEffect(() => {
setBounds(options.bounds);
}, [options.bounds]);

const updateBounds = useCallback((i, j, v) => {
v = parseFloat(v); // InputNumber may emit `null` and empty strings instead of numbers
if (isFinite(v)) {
const newBounds = cloneDeep(bounds);
newBounds[i][j] = v;
setBounds(newBounds);
onOptionsChangeDebounced({ bounds: newBounds });
}
}, [bounds]);

return (
<React.Fragment>
<div className="m-b-15">
<label htmlFor="choropleth-editor-bounds-ne">North-East latitude and longitude</label>
<Grid.Row gutter={15}>
<Grid.Col span={12}>
<InputNumber
id="choropleth-editor-bounds-ne"
className="w-100"
value={bounds[1][0]}
onChange={value => updateBounds(1, 0, value)}
/>
</Grid.Col>
<Grid.Col span={12}>
<InputNumber
className="w-100"
value={bounds[1][1]}
onChange={value => updateBounds(1, 1, value)}
/>
</Grid.Col>
</Grid.Row>
</div>

<div className="m-b-15">
<label htmlFor="choropleth-editor-bounds-sw">South-West latitude and longitude</label>
<Grid.Row gutter={15}>
<Grid.Col span={12}>
<InputNumber
id="choropleth-editor-bounds-sw"
className="w-100"
value={bounds[0][0]}
onChange={value => updateBounds(0, 0, value)}
/>
</Grid.Col>
<Grid.Col span={12}>
<InputNumber
className="w-100"
value={bounds[0][1]}
onChange={value => updateBounds(0, 1, value)}
/>
</Grid.Col>
</Grid.Row>
</div>
</React.Fragment>
);
}

BoundsSettings.propTypes = EditorPropTypes;
132 changes: 132 additions & 0 deletions client/app/visualizations/choropleth/Editor/ColorsSettings.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React from 'react';
import { useDebouncedCallback } from 'use-debounce';
import Select from 'antd/lib/select';
import InputNumber from 'antd/lib/input-number';
import * as Grid from 'antd/lib/grid';
import ColorPicker from '@/components/ColorPicker';
import { EditorPropTypes } from '@/visualizations';
import ColorPalette from '../ColorPalette';

export default function ColorsSettings({ options, onOptionsChange }) {
const [onOptionsChangeDebounced] = useDebouncedCallback(onOptionsChange, 200);

return (
<React.Fragment>
<Grid.Row type="flex" align="middle" className="m-b-15">
<Grid.Col span={12}>
<label htmlFor="choropleth-editor-clustering-mode">Clustering mode</label>
</Grid.Col>
<Grid.Col span={12}>
<Select
id="choropleth-editor-clustering-mode"
className="w-100"
defaultValue={options.clusteringMode}
onChange={clusteringMode => onOptionsChange({ clusteringMode })}
>
<Select.Option value="q">quantile</Select.Option>
<Select.Option value="e">equidistant</Select.Option>
<Select.Option value="k">k-means</Select.Option>
</Select>
</Grid.Col>
</Grid.Row>

<Grid.Row type="flex" align="middle" className="m-b-15">
<Grid.Col span={12}>
<label htmlFor="choropleth-editor-color-steps">Steps</label>
</Grid.Col>
<Grid.Col span={12}>
<InputNumber
id="choropleth-editor-color-steps"
className="w-100"
min={3}
max={11}
defaultValue={options.steps}
onChange={steps => onOptionsChangeDebounced({ steps })}
/>
</Grid.Col>
</Grid.Row>

<Grid.Row type="flex" align="middle" className="m-b-15">
<Grid.Col span={12}>
<label htmlFor="choropleth-editor-color-min">Min Color</label>
</Grid.Col>
<Grid.Col span={12}>
<ColorPicker
id="choropleth-editor-color-min"
interactive
presetColors={ColorPalette}
placement="topRight"
color={options.colors.min}
onChange={min => onOptionsChange({ colors: { min } })}
/>
</Grid.Col>
</Grid.Row>

<Grid.Row type="flex" align="middle" className="m-b-15">
<Grid.Col span={12}>
<label htmlFor="choropleth-editor-color-max">Max Color</label>
</Grid.Col>
<Grid.Col span={12}>
<ColorPicker
id="choropleth-editor-color-max"
interactive
presetColors={ColorPalette}
placement="topRight"
color={options.colors.max}
onChange={max => onOptionsChange({ colors: { max } })}
/>
</Grid.Col>
</Grid.Row>

<Grid.Row type="flex" align="middle" className="m-b-15">
<Grid.Col span={12}>
<label htmlFor="choropleth-editor-color-no-value">No value color</label>
</Grid.Col>
<Grid.Col span={12}>
<ColorPicker
id="choropleth-editor-color-no-value"
interactive
presetColors={ColorPalette}
placement="topRight"
color={options.colors.noValue}
onChange={noValue => onOptionsChange({ colors: { noValue } })}
/>
</Grid.Col>
</Grid.Row>

<Grid.Row type="flex" align="middle" className="m-b-15">
<Grid.Col span={12}>
<label htmlFor="choropleth-editor-color-background">Background color</label>
</Grid.Col>
<Grid.Col span={12}>
<ColorPicker
id="choropleth-editor-color-background"
interactive
presetColors={ColorPalette}
placement="topRight"
color={options.colors.background}
onChange={background => onOptionsChange({ colors: { background } })}
/>
</Grid.Col>
</Grid.Row>

<Grid.Row type="flex" align="middle" className="m-b-15">
<Grid.Col span={12}>
<label htmlFor="choropleth-editor-color-borders">Borders color</label>
</Grid.Col>
<Grid.Col span={12}>
<ColorPicker
id="choropleth-editor-color-borders"
interactive
presetColors={ColorPalette}
placement="topRight"
color={options.colors.borders}
onChange={borders => onOptionsChange({ colors: { borders } })}
/>
</Grid.Col>
</Grid.Row>
</React.Fragment>
);
}

ColorsSettings.propTypes = EditorPropTypes;
Loading

0 comments on commit 1a95904

Please sign in to comment.