Compare commits

..

6 Commits

@ -25,7 +25,7 @@ h2 {
line-height: 48px; line-height: 48px;
font-size: 28px; font-size: 28px;
margin: 0; margin: 0;
padding: 12px; padding: 12px 24px;
} }
.HeaderContent h2 { .HeaderContent h2 {

@ -36,7 +36,7 @@ test("generateTableColumns", () => {
headerSort: false, headerSort: false,
headerWordWrap: true, headerWordWrap: true,
hozAlign: "left", hozAlign: "left",
minWidth: 75, minWidth: 80,
resizable: false, resizable: false,
title: "1", title: "1",
}); });

@ -5,7 +5,7 @@
*/ */
.SiteHeaderButton { .SiteHeaderButton {
padding: 6px 36px; padding: 6px 24px;
margin: 0 0 0 8px; margin: 0 0 0 8px;
line-height: 24px; line-height: 24px;
border: var(--box-border); border: var(--box-border);

@ -12,7 +12,11 @@ import {
} from "tabulator-tables"; } from "tabulator-tables";
import { ValidationError } from "../../../core/Data/validate"; import { ValidationError } from "../../../core/Data/validate";
import Papa from "papaparse"; import Papa from "papaparse";
import { parseBool, parseNumber } from "../../../core/Operations/commonOps"; import {
parseBool,
parseNullableNumber,
parseNumber,
} from "../../../core/Operations/commonOps";
import { UnitCommitmentScenario } from "../../../core/Data/types"; import { UnitCommitmentScenario } from "../../../core/Data/types";
export interface ColumnSpec { export interface ColumnSpec {
@ -243,6 +247,12 @@ export const parseCsv = (
data[name][spec.title] = val; data[name][spec.title] = val;
break; break;
} }
case "number?": {
const [val, err] = parseNullableNumber(row[spec.title]);
if (err) return [null, { message: err.message + rowRef }];
data[name][spec.title] = val;
break;
}
case "busRef": case "busRef":
const busName = row[spec.title]; const busName = row[spec.title];
if (!(busName in scenario.Buses)) { if (!(busName in scenario.Buses)) {
@ -359,6 +369,7 @@ const DataTable = (props: DataTableProps) => {
const tableContainerRef = useRef<HTMLDivElement | null>(null); const tableContainerRef = useRef<HTMLDivElement | null>(null);
const tableRef = useRef<Tabulator | null>(null); const tableRef = useRef<Tabulator | null>(null);
const [isTableBuilt, setTableBuilt] = useState<Boolean>(false); const [isTableBuilt, setTableBuilt] = useState<Boolean>(false);
const [activeCell, setActiveCell] = useState<CellComponent | null>(null);
useEffect(() => { useEffect(() => {
const onCellEdited = (cell: CellComponent) => { const onCellEdited = (cell: CellComponent) => {
@ -396,6 +407,7 @@ const DataTable = (props: DataTableProps) => {
const height = computeTableHeight(data); const height = computeTableHeight(data);
if (tableRef.current === null) { if (tableRef.current === null) {
console.log("new Tabulator");
tableRef.current = new Tabulator(tableContainerRef.current, { tableRef.current = new Tabulator(tableContainerRef.current, {
layout: "fitColumns", layout: "fitColumns",
data: data, data: data,
@ -411,13 +423,31 @@ const DataTable = (props: DataTableProps) => {
const newColumns = columns; const newColumns = columns;
const newData = data; const newData = data;
const oldRows = tableRef.current.getRows(); const oldRows = tableRef.current.getRows();
const activeRowPosition = activeCell?.getRow().getPosition() as number;
const activeField = activeCell?.getField();
// Update data // Update data
tableRef.current.replaceData(newData).then(() => {}); tableRef.current.replaceData(newData).then(() => {});
// Restore active cell selection
if (activeCell) {
tableRef.current
?.getRowFromPosition(activeRowPosition!!)
?.getCell(activeField!!)
?.edit();
}
// Update columns // Update columns
if (newColumns.length !== tableRef.current.getColumns().length) { let newColCount = 0;
newColumns.forEach((col) => {
if (col.columns) newColCount += col.columns.length;
else newColCount += 1;
});
if (newColCount !== tableRef.current.getColumns().length) {
tableRef.current.setColumns(newColumns); tableRef.current.setColumns(newColumns);
const rows = tableRef.current!.getRows()!;
const firstRow = rows[0];
if (firstRow) firstRow.scrollTo().then((r) => {});
} }
// Update height // Update height
@ -435,15 +465,32 @@ const DataTable = (props: DataTableProps) => {
}, 10); }, 10);
} }
// Update callbacks // Remove old callbacks
tableRef.current.off("cellEdited"); tableRef.current.off("cellEdited");
tableRef.current.off("cellEditing");
tableRef.current.off("cellEditCancelled");
// Set new callbacks
tableRef.current.on("cellEditing", (cell) => {
console.log("cellEditing", cell);
setActiveCell(cell);
});
tableRef.current.on("cellEditCancelled", (cell) => {
setActiveCell(null);
});
tableRef.current.on("cellEdited", (cell) => { tableRef.current.on("cellEdited", (cell) => {
onCellEdited(cell); onCellEdited(cell);
}); });
} }
}, [props, isTableBuilt]); }, [props, isTableBuilt]);
return <div className="tableContainer" ref={tableContainerRef} />; return (
<div className="tableWrapper">
<div ref={tableContainerRef} />
</div>
);
}; };
export default DataTable; export default DataTable;

@ -4,17 +4,21 @@
* Released under the modified BSD license. See COPYING.md for more details. * Released under the modified BSD license. See COPYING.md for more details.
*/ */
.FormWrapper {
margin: 0 auto;
max-width: var(--site-max-width);
}
.Form { .Form {
background-color: var(--contrast-0); background-color: var(--contrast-0);
border: var(--box-border); border: var(--box-border);
border-radius: var(--border-radius); border-radius: var(--border-radius);
box-shadow: var(--box-shadow); box-shadow: var(--box-shadow);
min-height: 48px; min-height: 48px;
margin: 0 auto; margin: 0 12px;
min-width: var(--site-min-width); min-width: var(--site-min-width);
max-width: var(--site-max-width);
max-height: 500px; max-height: 500px;
padding: 12px 0; padding: 12px;
} }
.FormRow { .FormRow {

@ -8,7 +8,11 @@ import { ReactNode } from "react";
import styles from "./Form.module.css"; import styles from "./Form.module.css";
function Form({ children }: { children: ReactNode }) { function Form({ children }: { children: ReactNode }) {
return <div className={styles.Form}>{children}</div>; return (
<div className={styles.FormWrapper}>
<div className={styles.Form}>{children}</div>
</div>
);
} }
export default Form; export default Form;

@ -4,16 +4,20 @@
* Released under the modified BSD license. See COPYING.md for more details. * Released under the modified BSD license. See COPYING.md for more details.
*/ */
.tableWrapper {
margin: 0 auto;
max-width: var(--site-max-width);
}
.tabulator { .tabulator {
background-color: var(--contrast-0); background-color: var(--contrast-0);
border: var(--box-border) !important; border: var(--box-border) !important;
border-radius: var(--border-radius); border-radius: var(--border-radius);
box-shadow: var(--box-shadow); box-shadow: var(--box-shadow);
min-height: 48px; min-height: 48px;
margin: 0 auto;
min-width: var(--site-min-width); min-width: var(--site-min-width);
max-width: var(--site-max-width);
padding: 0; padding: 0;
margin: 0 12px;
} }
.tabulator .tabulator-header { .tabulator .tabulator-header {

@ -6,7 +6,7 @@
import formStyles from "./Form.module.css"; import formStyles from "./Form.module.css";
import HelpButton from "../Buttons/HelpButton"; import HelpButton from "../Buttons/HelpButton";
import React, { useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { ValidationError } from "../../../core/Data/validate"; import { ValidationError } from "../../../core/Data/validate";
interface TextInputRowProps { interface TextInputRowProps {
@ -21,6 +21,13 @@ function TextInputRow(props: TextInputRowProps) {
const [savedValue, setSavedValue] = useState(props.initialValue); const [savedValue, setSavedValue] = useState(props.initialValue);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.value = props.initialValue;
}
setSavedValue(props.initialValue);
}, [props.initialValue]);
const onBlur = (event: React.FocusEvent<HTMLInputElement>) => { const onBlur = (event: React.FocusEvent<HTMLInputElement>) => {
const newValue = event.target.value; const newValue = event.target.value;
if (newValue === savedValue) return; if (newValue === savedValue) return;
@ -29,8 +36,8 @@ function TextInputRow(props: TextInputRowProps) {
inputRef.current!.value = savedValue; inputRef.current!.value = savedValue;
return; return;
} }
setSavedValue(newValue);
}; };
return ( return (
<div className={formStyles.FormRow}> <div className={formStyles.FormRow}>
<label> <label>

@ -13,7 +13,7 @@
.SectionHeader h1 { .SectionHeader h1 {
margin: 0; margin: 0;
padding: 0 12px; padding: 0 24px;
font-size: 16px; font-size: 16px;
line-height: 64px; line-height: 64px;
} }
@ -21,4 +21,5 @@
.SectionButtonsContainer { .SectionButtonsContainer {
float: right; float: right;
height: 64px; height: 64px;
margin-right: 12px;
} }

@ -29,11 +29,23 @@ export const changeTimeHorizon = (
Object.values(newScenario.Buses).forEach((bus) => { Object.values(newScenario.Buses).forEach((bus) => {
bus["Load (MW)"] = bus["Load (MW)"].slice(0, newT); bus["Load (MW)"] = bus["Load (MW)"].slice(0, newT);
}); });
Object.values(newScenario.Generators).forEach((generator) => {
if (generator.Type === "Profiled") {
generator["Minimum power (MW)"] = generator["Minimum power (MW)"].slice(0, newT);
generator["Maximum power (MW)"] = generator["Maximum power (MW)"].slice(0, newT);
}
});
} else { } else {
const padding = Array(newT - oldT).fill(0); const padding = Array(newT - oldT).fill(0);
Object.values(newScenario.Buses).forEach((bus) => { Object.values(newScenario.Buses).forEach((bus) => {
bus["Load (MW)"] = bus["Load (MW)"].concat(padding); bus["Load (MW)"] = bus["Load (MW)"].concat(padding);
}); });
Object.values(newScenario.Generators).forEach((generator) => {
if (generator.Type === "Profiled") {
generator["Minimum power (MW)"] = generator["Minimum power (MW)"].concat(padding);
generator["Maximum power (MW)"] = generator["Maximum power (MW)"].concat(padding);
}
});
} }
return [newScenario, null]; return [newScenario, null];
}; };
@ -110,6 +122,40 @@ export const changeTimeStep = (
}; };
} }
const newGenerators: { [name: string]: any } = {};
for (const generatorName in scenario.Generators) {
const generator = scenario.Generators[generatorName]!;
if (generator.Type === "Profiled") {
// Build data_y for minimum power
const minPower = generator["Minimum power (MW)"];
const minData_y = Array(oldT + 1).fill(0);
for (let i = 0; i < oldT; i++) minData_y[i] = minPower[i];
minData_y[oldT] = minData_y[0];
// Build data_y for maximum power
const maxPower = generator["Maximum power (MW)"];
const maxData_y = Array(oldT + 1).fill(0);
for (let i = 0; i < oldT; i++) maxData_y[i] = maxPower[i];
maxData_y[oldT] = maxData_y[0];
// Run interpolation for both
const newMinPower = Array(newT).fill(0);
const newMaxPower = Array(newT).fill(0);
for (let i = 0; i < newT; i++) {
newMinPower[i] = evaluatePwlFunction(data_x, minData_y, newTimeStep * i);
newMaxPower[i] = evaluatePwlFunction(data_x, maxData_y, newTimeStep * i);
}
newGenerators[generatorName] = {
...generator,
"Minimum power (MW)": newMinPower,
"Maximum power (MW)": newMaxPower,
};
} else {
newGenerators[generatorName] = generator;
}
}
return [ return [
{ {
...scenario, ...scenario,
@ -118,6 +164,7 @@ export const changeTimeStep = (
"Time step (min)": newTimeStep, "Time step (min)": newTimeStep,
}, },
Buses: newBuses, Buses: newBuses,
Generators: newGenerators,
}, },
null, null,
]; ];

Loading…
Cancel
Save