From 2e09d7f75e29a94a349408dadad58336659752d6 Mon Sep 17 00:00:00 2001 From: Matteo V Date: Thu, 5 Oct 2023 10:15:47 +0200 Subject: [PATCH] fix #9071 add advanced options to counter widgets (#9536) * #9071 add advanced options to coutner widgets * fix retrocompatibility issue with uom --- web/client/components/charts/WidgetChart.jsx | 6 +- .../widgets/builder/wizard/CounterWizard.jsx | 22 +++- .../wizard/common/ChartAdvancedOptions.jsx | 42 +++---- .../wizard/common/CounterAdvancedOptions.jsx | 45 +++++++ .../widgets/builder/wizard/common/Format.jsx | 53 +++++++++ .../widgets/builder/wizard/common/Formula.jsx | 31 +++++ .../wizard/common/WPSWidgetOptions.jsx | 40 +++++-- .../components/widgets/widget/CounterView.jsx | 111 ++++++++++++++---- .../plugins/widgetbuilder/CounterBuilder.jsx | 5 +- 9 files changed, 291 insertions(+), 64 deletions(-) create mode 100644 web/client/components/widgets/builder/wizard/common/CounterAdvancedOptions.jsx create mode 100644 web/client/components/widgets/builder/wizard/common/Format.jsx create mode 100644 web/client/components/widgets/builder/wizard/common/Formula.jsx diff --git a/web/client/components/charts/WidgetChart.jsx b/web/client/components/charts/WidgetChart.jsx index 147d0efad9..2cbd796016 100644 --- a/web/client/components/charts/WidgetChart.jsx +++ b/web/client/components/charts/WidgetChart.jsx @@ -527,7 +527,11 @@ export const toPlotly = (props) => { * @prop {number} [xAxisOpts.nTicks] max number of ticks. Can be used to force to display all labels, instead of skipping. * @prop {number} [xAxisAngle] the angle, in degrees, of xAxisAngle. * @prop {object|boolean} [yAxis=true] if false, hide the yAxis. true by default. (should contain future options for yAxis) - * @prop {object} [yAxisOpts] options for yAxis: `type`, `tickPrefix`, `tickPostfix`, `format`, `formula` + * @prop {object} [counterOpts] options for counter widgets that manipulates the value: `tickPrefix`, `tickPostfix`, `format`, `formula`) + * @prop {string} [counterOpts.format] format. See {@link https://d3-wiki.readthedocs.io/zh_CN/master/Formatting/} + * @prop {string} [counterOpts.tickPrefix] the prefix. + * @prop {string} [counterOpts.tickSuffix] the suffix. + * @prop {object} [yAxisOpts] options for yAxis: `type`, `tickPrefix`, `tickPostfix`, `format`, `formula`) * @prop {string} [yAxisOpts.type] determine the type of the y axis of `date`, `-` (automatic), `log`, `linear`, `category`, `date`. * @prop {string} [yAxisOpts.format] format for y axis value. See {@link https://d3-wiki.readthedocs.io/zh_CN/master/Formatting/} * @prop {string} [yAxisOpts.tickPrefix] the prefix on y value diff --git a/web/client/components/widgets/builder/wizard/CounterWizard.jsx b/web/client/components/widgets/builder/wizard/CounterWizard.jsx index 2389b93b8a..9c08e39c7e 100644 --- a/web/client/components/widgets/builder/wizard/CounterWizard.jsx +++ b/web/client/components/widgets/builder/wizard/CounterWizard.jsx @@ -9,6 +9,7 @@ import {isNil} from 'lodash'; import React from 'react'; import { compose, lifecycle } from 'recompose'; +import PropTypes from 'prop-types'; import loadingEnhancer from '../../../misc/enhancers/loadingState'; import {wizardHandlers} from '../../../misc/wizard/enhancers'; @@ -70,7 +71,14 @@ const Wizard = wizardHandlers(WizardContainer); const Preview = enhancePreview(Counter); -const CounterPreview = ({ data = {}, layer, dependencies = {}, valid, setValid = () => { }, hasAggregateProcess }) => +const CounterPreview = ({ + data = {}, + layer, + dependencies = {}, + valid, + setValid = () => {}, + hasAggregateProcess +}) => !isCounterOptionsValid(data.options, { hasAggregateProcess }) ? { }, onFinish = () => { }, setP setValid={v => setValid(v && isCounterOptionsValid(data.options, { hasAggregateProcess }))} />} /> )); + +CounterPreview.propTypes = { + data: PropTypes.object, + dependencies: PropTypes.object, + hasAggregateProcess: PropTypes.bool, + layer: PropTypes.object, + setValid: PropTypes.func, + valid: PropTypes.bool, + value: PropTypes.string +}; diff --git a/web/client/components/widgets/builder/wizard/common/ChartAdvancedOptions.jsx b/web/client/components/widgets/builder/wizard/common/ChartAdvancedOptions.jsx index 10c95cab1b..3a30d115bc 100644 --- a/web/client/components/widgets/builder/wizard/common/ChartAdvancedOptions.jsx +++ b/web/client/components/widgets/builder/wizard/common/ChartAdvancedOptions.jsx @@ -9,15 +9,14 @@ import React, { useState } from 'react'; import { isNil } from 'lodash'; import Select from 'react-select'; import { Col, FormGroup, FormControl, ControlLabel } from 'react-bootstrap'; +import PropTypes from 'prop-types'; import Message from '../../../../I18N/Message'; -import HTML from '../../../../I18N/HTML'; import Slider from '../../../../misc/Slider'; import InfoPopover from '../../../widget/InfoPopover'; -import DisposablePopover from '../../../../misc/popover/DisposablePopover'; -import FormulaInput from './FormulaInput'; - +import Format from './Format'; +import Formula from './Formula'; import SwitchPanel from '../../../../misc/switch/SwitchPanel'; import SwitchButton from '../../../../misc/switch/SwitchButton'; @@ -60,7 +59,7 @@ function Header({}) { ); } -export default function ChartAdvancedOptions({ +function ChartAdvancedOptions({ classificationAttribute, data, onChange = () => {} @@ -132,29 +131,8 @@ export default function ChartAdvancedOptions({ onChange={(val) => { onChange("yAxis", !val); }} /> - - - - - - - onChange("yAxisOpts.tickPrefix", e.target.value)} /> - - - - - - - } text={} /> - onChange("yAxisOpts.format", e.target.value)} /> - - - - onChange("yAxisOpts.tickSuffix", e.target.value)} /> - - - onChange("formula", e.target.value)} /> - + + {/* X AXIS */} @@ -240,3 +218,11 @@ export default function ChartAdvancedOptions({ ); } + +ChartAdvancedOptions.propTypes = { + classificationAttribute: PropTypes.string, // [ ] verify is a string + data: PropTypes.object, + onChange: PropTypes.func +}; + +export default ChartAdvancedOptions; diff --git a/web/client/components/widgets/builder/wizard/common/CounterAdvancedOptions.jsx b/web/client/components/widgets/builder/wizard/common/CounterAdvancedOptions.jsx new file mode 100644 index 0000000000..a32aed3d77 --- /dev/null +++ b/web/client/components/widgets/builder/wizard/common/CounterAdvancedOptions.jsx @@ -0,0 +1,45 @@ +/* + * Copyright 2020, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +import React from 'react'; +import { FormGroup } from 'react-bootstrap'; +import PropTypes from 'prop-types'; + +import Message from '../../../../I18N/Message'; +import Format from './Format'; +import Formula from './Formula'; +import SwitchPanel from '../../../../misc/switch/SwitchPanel'; + +function Header({}) { + return ( + + ); +} + +function CounterAdvancedOptions({ + data, + onChange = () => {} +}) { + return (} + collapsible + expanded={data.panel} + onSwitch={(val) => { onChange("panel", val); }} + > + + + + + ); +} + +CounterAdvancedOptions.propTypes = { + data: PropTypes.object, + onChange: PropTypes.func +}; + +export default CounterAdvancedOptions; diff --git a/web/client/components/widgets/builder/wizard/common/Format.jsx b/web/client/components/widgets/builder/wizard/common/Format.jsx new file mode 100644 index 0000000000..84ac0cec07 --- /dev/null +++ b/web/client/components/widgets/builder/wizard/common/Format.jsx @@ -0,0 +1,53 @@ +/* + * Copyright 2023, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +import React from 'react'; +import { Col, FormControl, ControlLabel } from 'react-bootstrap'; +import PropTypes from 'prop-types'; + +import Message from '../../../../I18N/Message'; +import HTML from '../../../../I18N/HTML'; +import DisposablePopover from '../../../../misc/popover/DisposablePopover'; + +const Format = ({ + data, + onChange = () => {}, + prefix = "yAxisOpts" +}) => { + + return ( + <> + + + + + + + onChange(prefix + ".tickPrefix", e.target.value)} /> + + + + + + + } text={} /> + onChange(prefix + ".format", e.target.value)} /> + + + + onChange(prefix + ".tickSuffix", e.target.value)} /> + + + ); +}; +Format.propTypes = { + data: PropTypes.object, + onChange: PropTypes.func, + prefix: PropTypes.string +}; + +export default Format; diff --git a/web/client/components/widgets/builder/wizard/common/Formula.jsx b/web/client/components/widgets/builder/wizard/common/Formula.jsx new file mode 100644 index 0000000000..fb9e8b8eb2 --- /dev/null +++ b/web/client/components/widgets/builder/wizard/common/Formula.jsx @@ -0,0 +1,31 @@ +/* + * Copyright 2023, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Col } from 'react-bootstrap'; + +import FormulaInput from './FormulaInput'; + + +const Formula = ({ + data, + onChange = () => {} +}) => { + return ( + + onChange("formula", e.target.value)} /> + + ); +}; + +Formula.propTypes = { + data: PropTypes.object, + onChange: PropTypes.func +}; + +export default Formula; diff --git a/web/client/components/widgets/builder/wizard/common/WPSWidgetOptions.jsx b/web/client/components/widgets/builder/wizard/common/WPSWidgetOptions.jsx index cbd8ca4eba..015e455035 100644 --- a/web/client/components/widgets/builder/wizard/common/WPSWidgetOptions.jsx +++ b/web/client/components/widgets/builder/wizard/common/WPSWidgetOptions.jsx @@ -8,8 +8,12 @@ import React, {useEffect, useState} from 'react'; import { head, get} from 'lodash'; import { Row, Col, Form, FormGroup, FormControl, ControlLabel, Glyphicon, OverlayTrigger, Tooltip } from 'react-bootstrap'; -import Message from '../../../../I18N/Message'; +import PropTypes from 'prop-types'; import Select from 'react-select'; +import classNames from 'classnames'; +import uuid from 'uuid'; + +import Message from '../../../../I18N/Message'; import ColorRamp from '../../../../styleeditor/ColorRamp'; import { generateRandomHexColor } from '../../../../../utils/ColorUtils'; import Button from '../../../../misc/Button'; @@ -17,10 +21,9 @@ import ConfirmModal from '../../../../../components/resources/modals/ConfirmModa import StepHeader from '../../../../misc/wizard/StepHeader'; import SwitchButton from '../../../../misc/switch/SwitchButton'; import ChartAdvancedOptions from './ChartAdvancedOptions'; +import CounterAdvancedOptions from './CounterAdvancedOptions'; import ColorClassModal from '../chart/ColorClassModal'; import { defaultColorGenerator } from '../../../../charts/WidgetChart'; -import classNames from 'classnames'; -import uuid from 'uuid'; const DEFAULT_CUSTOM_COLOR_OPTIONS = { base: 190, @@ -101,7 +104,7 @@ const formatAutoColorOptions = (classification, attributeType) => ( )) ); -export default ({ +const WPSWidgetOptions = ({ hasAggregateProcess, data = { options: {}, autoColorOptions: {} }, onChange = () => { }, @@ -116,7 +119,8 @@ export default ({ }, aggregationOptions = [], sampleChart, - layer }) => { + layer +}) => { const [showModal, setShowModal] = useState(false); const [showConfirmModal, setShowConfirmModal] = useState(false); @@ -346,12 +350,34 @@ export default ({ : null} {formOptions.advancedOptions && data.widgetType === "chart" && (data.type === "bar" || data.type === "line") - ? + ? + : null} + {formOptions.advancedOptions && data.widgetType === "counter" + ? : null} - ); }; + +WPSWidgetOptions.propTypes = { + aggregationOptions: PropTypes.array, + data: PropTypes.object, + formOptions: PropTypes.object, + hasAggregateProcess: PropTypes.bool, + layer: PropTypes.object, + onChange: PropTypes.func, + options: PropTypes.array, + sampleChart: PropTypes.node, + showTitle: PropTypes.bool +}; +export default WPSWidgetOptions; diff --git a/web/client/components/widgets/widget/CounterView.jsx b/web/client/components/widgets/widget/CounterView.jsx index ad30dd2dc4..200aab4147 100644 --- a/web/client/components/widgets/widget/CounterView.jsx +++ b/web/client/components/widgets/widget/CounterView.jsx @@ -6,46 +6,107 @@ * LICENSE file in the root directory of this source tree. */ +import { compose } from 'recompose'; +import { get } from 'lodash'; +import { Textfit } from 'react-textfit'; +import PropTypes from 'prop-types'; +import {format} from 'd3-format'; +import React from 'react'; import loadingStateFactory from '../../misc/enhancers/loadingState'; - -const loadingState = loadingStateFactory(); import errorChartState from '../enhancers/errorChartState'; import emptyChartState from '../enhancers/emptyChartState'; -import FormatNumber from '../../I18N/Number'; -import { compose } from 'recompose'; -import { get } from 'lodash'; -import { Textfit } from 'react-textfit'; -const Counter = ({ value = "", uom = "", ...props } = {}) => ( - {uom} -); + +import { parseExpression } from '../../../utils/ExpressionUtils'; + +const loadingState = loadingStateFactory(); +const processFormula = (v, formula = "") => { + const value = v; + try { + return parseExpression(formula, {value}); + } catch { + // if error (e.g. null values), return the value itself + return v; + } +}; +const Counter = ({ + value = "", + uom = "", + formula = "", + counterOpts = { + tickPrefix: "", + tickSuffix: "" + }, + ...props +} = {}) =>{ + let val = value; + if (formula) { + val = processFormula(value, formula); + } + if (counterOpts?.format) { + try { + const formatter = format(counterOpts.format); + val = formatter(val); + } catch (e) { + console.error(e); + } + } + return ( + + {counterOpts?.tickPrefix ? counterOpts.tickPrefix : null} + + + {val} + + + {counterOpts?.tickSuffix || uom} + + ); +}; const enhanceCounter = compose( loadingState, errorChartState, emptyChartState ); -import React from 'react'; -export default enhanceCounter(({ series = [], data = [], options = {}, style = { - width: "100%", - height: "100%", - transform: "translate(-50%, -50%)", - position: "absolute", - display: "inline", - padding: "1%", - top: "50%", - left: "50%" -}} ) => { +const CounterView = enhanceCounter(({ + series = [], + data = [], + options = {}, + style = { + width: "100%", + height: "100%", + transform: "translate(-50%, -50%)", + position: "absolute", + display: "inline", + padding: "1%", + top: "50%", + left: "50%" + }, + counterOpts, + formula +}) => { const renderCounter = ({ dataKey } = {}, i) => (); return (
{series.map(renderCounter)}
); }); + +Counter.propTypes = { + uom: PropTypes.string, + value: PropTypes.string, + formula: PropTypes.string, + counterOpts: PropTypes.object +}; +export default CounterView; diff --git a/web/client/plugins/widgetbuilder/CounterBuilder.jsx b/web/client/plugins/widgetbuilder/CounterBuilder.jsx index eec10ba5a8..62acc82c29 100644 --- a/web/client/plugins/widgetbuilder/CounterBuilder.jsx +++ b/web/client/plugins/widgetbuilder/CounterBuilder.jsx @@ -89,8 +89,9 @@ export default chooseLayerEnhancer(({ enabled, onClose = () => { }, exitButton, > {enabled ? : null} ));