diff --git a/src/api/parameters.js b/src/api/parameters.js index 3c62a7ddd..43e42348d 100644 --- a/src/api/parameters.js +++ b/src/api/parameters.js @@ -101,19 +101,38 @@ export function getReformedParameter(parameter, reforms) { if (!parameterValues) { return null; } - const parameterValuesInOrder = Object.keys(parameterValues).sort(); + const sortedKeys = Object.keys(parameterValues).sort(); const reform = reforms[parameter.parameter]; if (reform) { for (const [timePeriod, value] of Object.entries(reform)) { const [startDate, endDate] = timePeriod.split("."); - // Delete all values in the time period - for (const timePeriod of parameterValuesInOrder) { - if (timePeriod >= startDate && timePeriod <= endDate) { - delete parameterValues[timePeriod]; + const i1 = sortedKeys.findIndex( + (k) => k >= startDate && parameterValues[k] !== value, + ); + const i2 = sortedKeys.findLastIndex( + (k) => endDate >= k && parameterValues[k] !== value, + ); + if (i1 !== -1 && i2 !== -1) { + // cache the last value + let c = parameterValues[sortedKeys[i2]]; + // delete all values in the time period + for (let i = i1; i <= i2; i++) { + delete parameterValues[sortedKeys[i]]; } + // add the new values + parameterValues[startDate] = value; + parameterValues[endDate] = c; + sortedKeys.splice(i1, i2 - i1 + 1, startDate, endDate); + } else if (i1 !== -1) { + parameterValues[startDate] = value; + sortedKeys.splice(i1, 0, startDate); + // TODO: it is unclear what the value in the range [endDate, + // sortedKeys[0]] should be. + } else if (i2 !== -1) { + parameterValues[startDate] = value; + parameterValues[endDate] = parameterValues[sortedKeys[i2]]; + sortedKeys.splice(i2, 0, startDate, endDate); } - // Add the new value - parameterValues[startDate] = value; } } return newParameter; diff --git a/src/api/variables.js b/src/api/variables.js index 3f0ea64e7..cc081f23f 100644 --- a/src/api/variables.js +++ b/src/api/variables.js @@ -247,7 +247,7 @@ export function getPlotlyAxisFormat( precisionOverride, valueType, ) { - // Possible units: currency-GBP, currency-USD, /1 + // Possible units: currency-GBP, currency-USD, /1, date // If values (an array) is passed, we need to calculate the // appropriate number of decimal places to use. let precision; @@ -299,6 +299,25 @@ export function getPlotlyAxisFormat( ]), }), }; + } else if (unit === "date") { + let minYear, maxYear; + if (values.length) { + minYear = values.reduce(function (a, b) { + return a <= b ? a : b; + }); + maxYear = values.reduce(function (a, b) { + return a >= b ? a : b; + }); + minYear = Number(minYear.match(/\d+/)[0]); + maxYear = Number(maxYear.match(/\d+/)[0]); + } else { + const currentYear = new Date().getFullYear(); + minYear = currentYear; + maxYear = currentYear; + } + return { + range: sortRange([minYear - 5 + "-01-01", maxYear + 5 + "-12-31"]), + }; } else if (valueType === "bool") { return { tickvals: [0, 1], diff --git a/src/controls/InputField.jsx b/src/controls/InputField.jsx index f1315cee3..fe3717ef4 100644 --- a/src/controls/InputField.jsx +++ b/src/controls/InputField.jsx @@ -15,24 +15,19 @@ export default function InputField(props) { value, placeholder, } = props; - //Saving placeholder as a state variable so it doesn't change if user types a value and deletes it - const [savedPlaceholder, setPlaceholder] = useState(placeholder); const [inputValue, setInputValue] = useState(value ? value : ""); const [searchParams] = useSearchParams(); const focus = searchParams.get("focus") || ""; const mobile = useMobile(); const re = /^[0-9\b]*[.]?[0-9\b]*?$/; const onInput = (e) => { - let value = e.target.value; - if (value !== "") { - onChange(value); - } + let value = e.target.value === "" ? placeholder : e.target.value; + onChange(value); }; //clears input field and resets placeholder if focus changes for use case of editing policy parameter useEffect(() => { if (!value) { setInputValue(""); - setPlaceholder(props.placeholder); } }, [focus]); return ( @@ -88,7 +83,7 @@ export default function InputField(props) { setInputValue(e.target.value); }} value={inputValue} - placeholder={savedPlaceholder} + placeholder={placeholder} /> ); } diff --git a/src/pages/policy/input/ParameterEditor.jsx b/src/pages/policy/input/ParameterEditor.jsx index 3f0d59020..01cc30232 100644 --- a/src/pages/policy/input/ParameterEditor.jsx +++ b/src/pages/policy/input/ParameterEditor.jsx @@ -22,16 +22,46 @@ export default function ParameterEditor(props) { const [searchParams, setSearchParams] = useSearchParams(); const parameter = metadata.parameters[parameterName]; - const reformedParameter = getReformedParameter(parameter, policy.reform.data); - const currentYear = new Date().getFullYear(); const [startDate, setStartDate] = useState(currentYear + "-01-01"); const [endDate, setEndDate] = useState(currentYear + 5 + "-12-31"); - // eslint-disable-next-line no-unused-vars - const [_, setValue] = useState( - getParameterAtInstant(reformedParameter, startDate), - ); + // change reform data using value and current startDate and endDate + function newReforms(reforms, value) { + let newReforms = { ...policy.reform.data }; + newReforms[parameterName] = { + ...newReforms[parameterName], + [`${startDate}.${endDate}`]: value, + }; + const values = getReformedParameter(parameter, newReforms).values; + let diff = {}, + ret = {}; + for (const key of Object.keys(values)) { + diff[key] = values[key] - getParameterAtInstant(parameter, key); + } + const keys = Object.keys(diff).sort(); + for (let i = 0; i < keys.length - 1; i++) { + const k1 = keys[i], + k2 = keys[i + 1]; + if (diff[k1] !== 0) { + ret[`${k1}.${k2}`] = values[k1]; + } + } + newReforms = { ...policy.reform.data }; + newReforms[parameterName] = ret; + return newReforms; + } + + function onChange(value) { + getNewPolicyId( + metadata.countryId, + newReforms(policy.reform.data, value), + ).then((newPolicyId) => { + let newSearch = copySearchParams(searchParams); + newSearch.set("reform", newPolicyId); + setSearchParams(newSearch); + }); + } let control; @@ -39,65 +69,26 @@ export default function ParameterEditor(props) { control = (
{ - let newPolicy = { ...policy.reform.data }; - newPolicy[parameterName] = { - ...newPolicy[parameterName], - [`${startDate}.${endDate}`]: !!value, - }; - setValue(value); - getNewPolicyId(metadata.countryId, newPolicy).then( - (newPolicyId) => { - let newSearch = copySearchParams(searchParams); - newSearch.set("reform", newPolicyId); - setSearchParams(newSearch); - }, - ); - }} + defaultChecked={getParameterAtInstant(parameter, startDate)} + onChange={(value) => onChange(!!value)} />
); } else if (parameter.unit === "/1") { - let val = getParameterAtInstant(reformedParameter, startDate); + let val = getParameterAtInstant(parameter, startDate); let valInPercentage = formatVariableValue(parameter, val); control = ( { - let newPolicy = { ...policy.reform.data }; - value = parseFloat(value); - newPolicy[parameterName] = { - ...newPolicy[parameterName], - [`${startDate}.${endDate}`]: value / 100, - }; - setValue(value); - getNewPolicyId(metadata.countryId, newPolicy).then((newPolicyId) => { - let newSearch = copySearchParams(searchParams); - newSearch.set("reform", newPolicyId); - setSearchParams(newSearch); - }); - }} + onChange={(value) => onChange(parseFloat(value) / 100)} /> ); } else { control = ( { - let newPolicy = { ...policy.reform.data }; - newPolicy[parameterName] = { - ...newPolicy[parameterName], - [`${startDate}.${endDate}`]: value, - }; - setValue(value); - getNewPolicyId(metadata.countryId, newPolicy).then((newPolicyId) => { - let newSearch = copySearchParams(searchParams); - newSearch.set("reform", newPolicyId); - setSearchParams(newSearch); - }); - }} + placeholder={getParameterAtInstant(parameter, startDate)} + onChange={(value) => onChange(Number(value))} /> ); } diff --git a/src/pages/policy/input/ParameterOverTime.jsx b/src/pages/policy/input/ParameterOverTime.jsx index 11de1df6f..c5dfd6750 100644 --- a/src/pages/policy/input/ParameterOverTime.jsx +++ b/src/pages/policy/input/ParameterOverTime.jsx @@ -43,9 +43,12 @@ export default function ParameterOverTime(props) { reformedY.push(reformedY[reformedY.length - 1]); } + let xForRange = reformedX ? x.concat(reformedX) : x; + xForRange = xForRange.filter((e) => e !== "0000-01-01" && e !== "2099-12-31"); + let xAxisFormat = getPlotlyAxisFormat("date", xForRange); let yAxisFormat = getPlotlyAxisFormat( parameter.unit, - Object.values(parameter.values), + reformedY ? y.concat(reformedY) : y, ); let yAxisTickVals; let yAxisTickLabels; @@ -65,6 +68,7 @@ export default function ParameterOverTime(props) { type: "line", line: { shape: "hv", + dash: "dot", }, marker: { color: style.colors.GRAY, @@ -87,9 +91,7 @@ export default function ParameterOverTime(props) { .reverse() .filter((x) => x)} layout={{ - xaxis: { - range: ["2000-01-01", "2025-01-01"], - }, + xaxis: xAxisFormat, yaxis: { ...yAxisFormat, tickvals: yAxisTickVals || null,