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

Migrate Choropleth visualization to React #4313

Merged
merged 10 commits into from
Nov 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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