diff --git a/docs/introduction/examples/index.md b/docs/introduction/examples/index.md new file mode 100644 index 0000000..60562c6 --- /dev/null +++ b/docs/introduction/examples/index.md @@ -0,0 +1,217 @@ +# Examples + +### Step through the hydraulic simulation + +Use the openH - initH - runH - nextH - closeH series of functions to step through the simulation one hydraulic time step at a time. + +[Run this example on CodeSandbox](https://codesandbox.io/embed/long-flower-npqiq?expanddevtools=1&fontsize=14&hidenavigation=1&module=%2Fsrc%2Findex.js&theme=dark) + +```js +const { Project, Workspace } = require("epanet-js"); +var fs = require("fs"); + +const net1 = fs.readFileSync("net1.inp"); + +const ws = new Workspace(); +const model = new Project(ws); + +ws.writeFile("net1.inp", net1); + +model.open("net1.inp", "report.rpt", "out.bin"); + +const n11Index = model.getNodeIndex("11"); + +model.openH(); +model.initH(11); + +let tStep = Infinity; +do { + const cTime = model.runH(); + const pressure = model.getNodeValue(n11Index, 11); + console.log( + `Current Time: - ${cTime}, Node 11 Pressure: ${pressure.toFixed(2)}` + ); + + tStep = model.nextH(); +} while (tStep > 0); + +model.saveH(); +model.closeH(); +``` + +### New model builder API + +Allows networks to be built completely from function calls instead of from an input file. + +```js +import { Project, Workspace } from "epanet-js"; + +const ws = new Workspace(); +const model = new Project(ws); + +model.init("report.rpt", "out.bin", 0, 0); + +const n1Index = model.addNode("N1", NodeType.Junction); +const n2Index = model.addNode("N2", NodeType.Junction); +model.setJunctionData(n1Index, 700, 0, ""); +model.setJunctionData(n2Index, 400, 0, ""); + +const l1Index = model.addLink("L1", LinkType.Pipe, "N1", "N2"); +``` + +### Fire Flow Analysis - React Example + +A more complex example that uses React. + +Select a node within a network, run an extended period simulation and conduct a fire flow test. Results displayed in a graph for pressure and the fire flow. + +placeholder + +[Run this example on CodeSandbox](https://codesandbox.io/embed/musing-jang-sqhk1?fontsize=14&hidenavigation=1&module=%2Fsrc%2FApp.js&theme=dark) + +```jsx +import React, { useState } from "react"; +import { VictoryChart, VictoryLine, VictoryAxis, VictoryTheme } from "victory"; +import { + Project, + Workspace, + InitHydOption, + NodeProperty, + CountType, + NodeType, +} from "epanet-js"; +var fs = require("fs"); + +const net1 = fs.readFileSync("net1.inp"); + +const ws = new Workspace(); +const model = new Project(ws); + +ws.writeFile("net1.inp", net1); +model.open("net1.inp", "report.rpt", "out.bin"); + +function getNodes() { + const nodeCount = model.getCount(CountType.NodeCount); + let nodeIds = []; + for (let i = 1; i < nodeCount; i++) { + if (model.getNodeType(i) === NodeType.Junction) { + const nodeId = model.getNodeId(i); + nodeIds.push(nodeId); + } + } + return nodeIds; +} + +function runSim(nodeId) { + const fireFlowNodeIndex = model.getNodeIndex(nodeId); + const flows = [100, 250, 500, 750, 1000]; + + model.openH(); + model.initH(InitHydOption.NoSave); + + let tStep = Infinity; + let flowAndPressure = []; + let fireResults = []; + do { + const cTime = model.runH(); + const pressure = model.getNodeValue( + fireFlowNodeIndex, + NodeProperty.Pressure + ); + + flowAndPressure.push({ x: cTime / 60 / 60, y: pressure }); + if (cTime === 46800) { + fireResults = fireFlowAnalysis(model, fireFlowNodeIndex, flows, pressure); + model.runH(); // Rerun original snapshot + } + + tStep = model.nextH(); + } while (tStep > 0); + + model.closeH(); + + return { + flowAndPressure, + fireResults, + }; +} + +function fireFlowAnalysis(model, index, flows, zeroPressure) { + const acc = [{ x: 0, y: zeroPressure }]; + + const orgBaseFlow = model.getNodeValue(index, NodeProperty.BaseDemand); + + const pressures = flows.reduce((acc, flow) => { + model.setNodeValue(index, NodeProperty.BaseDemand, orgBaseFlow + flow); + model.runH(); + const pressure = model.getNodeValue(index, NodeProperty.Pressure); + return acc.concat([{ x: flow, y: pressure }]); + }, acc); + + model.setNodeValue(index, NodeProperty.BaseDemand, orgBaseFlow); + return pressures; +} + +function SmallChart(props) { + return ( + + + + + + ); +} + +export default function App() { + const [nodeId, setNodeId] = useState("11"); + + function handleSelectChange(e) { + setNodeId(e.target.value); + } + + const nodeIds = getNodes(); + const { flowAndPressure, fireResults } = runSim(nodeId); + + return ( +
+

epanet-js React Fire Flow Example

+

Select a node to test

+ +

Fireflow at Node {nodeId}

+ + +

Pressure at Node {nodeId}

+ +
+ ); +} +``` diff --git a/docs/introduction/examples/vite.md b/docs/introduction/examples/vite.md new file mode 100644 index 0000000..2c1c32d --- /dev/null +++ b/docs/introduction/examples/vite.md @@ -0,0 +1,363 @@ +Below is a step-by-step tutorial to help you get started with **epanet-js** in a new **React + Vite + TypeScript** application. By the end, you’ll have a small web app that lets you: + +- Load an EPANET `.inp` file from your computer. +- Run a hydraulic simulation in the browser using **epanet-js**. +- Display the generated report in a read-only text area. + +This tutorial assumes: + +- You have some familiarity with React and TypeScript. +- You know basic npm commands, and have Node.js and npm installed. + +We’ll build this incrementally in four steps, starting from a fresh React + Vite + TypeScript scaffold. + +--- + +### Step 1: Create a New React + Vite + TypeScript App + +First, create and run your new Vite project with React and TypeScript: + +```bash +npm create vite@latest my-first-epanet-js-app -- --template react-ts +cd my-first-epanet-js-app +npm install epanet-js +npm run dev +``` + +Open [http://localhost:5173](http://localhost:5173) in your browser. You’ll see the default Vite + React starter page. + +Open the project in your code editor: + +```bash +code . +``` + +**Replace `src/App.tsx` with a simple scaffold:** +We’ll start with a very basic UI to verify our setup. Remove all existing code in `src/App.tsx` and replace it with this minimal code: + +```react +import React from "react"; + +function App() { + return ( +
+

EPANET-js React Example

+

This is the start of our EPANET-js app.

+

+ In later steps, we’ll load an INP file, run a simulation, and show the + report here. +

+
+ ); +} + +export default App; +``` + +**Check your app:** +Go back to the browser and ensure it’s still running on [http://localhost:5173](http://localhost:5173). You should see the new heading and text. That’s it for step one! We have our scaffold in place. + +--- + +### Step 2: Handling the INP File + +Now, let’s let users select an EPANET `.inp` file from their computer. When they pick a file, we’ll read it and just show the file’s text in a "report" area, proving we can handle file input. + +**Update `src/App.tsx` to add file selection:** + +```react +import React, { useState } from "react"; + +function App() { + const [inpContent, setInpContent] = useState(""); + + function handleFileChange(event: React.ChangeEvent) { + const file = event.target.files?.[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (e) => { + const text = e.target?.result as string; + setInpContent(text); + }; + reader.readAsText(file); + } + + return ( +
+

EPANET-js React Example

+

Select an INP file to display its content:

+ + + +

File Content

+