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

Reflect policy parameter changes in policy right sidebar and parameter-over-time chart #839

Merged
merged 6 commits into from
Nov 27, 2023
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
33 changes: 26 additions & 7 deletions src/api/parameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
21 changes: 20 additions & 1 deletion src/api/variables.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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],
Expand Down
11 changes: 3 additions & 8 deletions src/controls/InputField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -88,7 +83,7 @@ export default function InputField(props) {
setInputValue(e.target.value);
}}
value={inputValue}
placeholder={savedPlaceholder}
placeholder={placeholder}
/>
);
}
93 changes: 42 additions & 51 deletions src/pages/policy/input/ParameterEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,82 +22,73 @@ 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;

if (parameter.unit === "bool" || parameter.unit === "abolition") {
control = (
<div style={{ padding: 10 }}>
<Switch
checked={getParameterAtInstant(reformedParameter, startDate)}
onChange={(value) => {
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)}
/>
</div>
);
} else if (parameter.unit === "/1") {
let val = getParameterAtInstant(reformedParameter, startDate);
let val = getParameterAtInstant(parameter, startDate);
let valInPercentage = formatVariableValue(parameter, val);
control = (
<InputField
placeholder={valInPercentage}
pattern={"%"}
onChange={(value) => {
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 = (
<InputField
placeholder={getParameterAtInstant(reformedParameter, startDate)}
onChange={(value) => {
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))}
/>
);
}
Expand Down
10 changes: 6 additions & 4 deletions src/pages/policy/input/ParameterOverTime.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -65,6 +68,7 @@ export default function ParameterOverTime(props) {
type: "line",
line: {
shape: "hv",
dash: "dot",
},
marker: {
color: style.colors.GRAY,
Expand All @@ -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,
Expand Down
Loading