Curves
A curve is a named list of points. It describes one quantity against another, for example level against volume in a tank, or flow against headloss through a valve. A pattern says how something varies with time. A curve says how something varies with whatever the network does.
The curves library holds the three kinds an asset can point at: Tank volume, Valve and Headloss. The head and efficiency curves of a pump are curves too, but they live in their own library and do not appear here. See Pump library.
Opening the library
Section titled “Opening the library”To open the library, press Operational data in the toolbar, then Curves. The three rows that take a curve carry Open curves… as well:
- Volume curve on a tank, once Tank geometry is set to Volume curve. See Tanks.
- Headloss curve on a GPV. A GPV has no Setting row at all.
- Curve on a PCV, next to its Setting. See Valves.
Open curves… opens the library on the matching section, and you do not lose the asset behind it.

The three kinds
Section titled “The three kinds”Each section takes two columns. Where a quantity has a unit, the caption gives the unit of the project. Each section also has its own rule about the order of the points.
| Section | Columns | The rule |
|---|---|---|
| Tank volume | level, volume | both columns strictly increasing |
| Valve | % Open, % Full flow | both columns strictly increasing |
| Headloss | flow, headloss | flow strictly increasing, headloss free |
A Tank volume curve needs both columns increasing because EPANET reads it backwards as well as forwards. EPANET works a level out from a volume, and it can only do this if each volume belongs to exactly one level.
A Headloss curve has no rule on its second column. The headloss is free to fall as well as rise.
A Valve curve carries no units on either column. It is also the one kind that a single point is enough for. EPANET interpolates between that point and the fully open point at 100, 100.
To add a curve, press the button on its section: Add tank volume curve, Add valve curve or Add headloss curve. Then name the curve. The menu on each curve has Rename, Duplicate and Delete.
Building a curve
Section titled “Building a curve”When you select a curve, the app opens a numbered point table with a graph under it. You type the values in both columns. Add point at the foot of the table adds a row. The menu on each row has Delete, Insert row above and Insert row below. An insert copies the row you called it on, which is usually a better start than a pair of zeroes.
When you click a point on the graph, the app selects its row in the table. When you select a row, the app marks the point on the graph. At the bottom of the table, a row with zero in both columns is dropped when you save. You thus do not have to delete a row that you added and left empty.

When a curve is not valid
Section titled “When a curve is not valid”The dialog looks at the order of the points as you type, and it says so in three places at once. It marks the wrong cells in the table. It draws the wrong points as orange triangles on the graph. Between the two, a banner headed Invalid curve appears with what is wrong.
- The curve requires more than one point. This message comes from a Tank volume or Headloss curve with a single row. The curve needs a second point.
- Level values must be in ascending order., Volume values must be in ascending order. and Flow values must be in ascending order. Each message names the column that is out of order. A Tank volume curve with its levels and its volumes both the wrong way gets both messages.
For as long as the curve is invalid, its entry in the list carries a warning triangle. You can thus see a problem without opening every curve in the project.

Uncategorized curves
Section titled “Uncategorized curves”When the project holds curves with no kind, a fourth heading appears: Uncategorized. An import produces these curves. The library lists them and you can delete them. It shows their points under plain X and Y headings, and you cannot edit them. Until the app knows the kind of a curve, it has no rule for the order of the points and no units for the captions.
The menu on an uncategorized curve offers Set as Tank volume, Set as Valve and Set as Headloss. When you choose one, the app files the curve in that section. It opens the points for editing under the right column headings. It also makes the curve available to the rows that take that kind.
Deleting a curve
Section titled “Deleting a curve”The library does not delete a curve that is in use, and it says Cannot delete a curve that is in use. Free the curve first: point the tank or the valve that holds it at another curve, or at none. The delete then goes through.
Saving, and what cannot be edited
Section titled “Saving, and what cannot be edited”Save stays disabled until something changes, and the whole edit lands as one step in the undo history. When you press Cancel with unsaved work, the app offers Discard changes or Keep editing.
During the playback of a run, the library opens read-only. When a scenario exists, it also opens read-only on Main. Inside a scenario, you can edit the library. The edit belongs to that scenario and does not change Main. See Scenario work.
Importing curves from a file
Section titled “Importing curves from a file”Import, in the toolbar of the dialog, reads a .csv or .xlsx file and
merges its curves into the library. The file has a header row and four
columns: Curve name, Type, Axis and Values. A curve takes two rows, one
with X in the axis column and one with Y. The values run along each row
from the fourth column, one per cell. The two rows must hold the same number of
values. On the second row of a pair, you can leave the name and the type blank.
Export writes a file in this shape. Thus the shortest way to a correct file
is to export one and edit it.
Type is the section name as the library shows it: Tank volume, Valve or
Headloss. The match ignores case. A blank or unrecognized type puts the curve
in Uncategorized, and you file it from there.
The import matches each curve by name against the library. The match ignores case.
- A name the library does not hold becomes a new curve.
- A name it holds, with the same points and kind, stays as it is. The import counts it as identical.
- A name it holds, with other points, replaces the points of that curve. The curve keeps its identity, and the tank or valve that points at it keeps pointing. A blank type in the file does not remove the kind from a curve. A type in the file gives a kind to an uncategorized one.
- A name it holds under a different kind becomes a second curve with a generated label. An import never overwrites a curve with one of another kind.
The dialog looks at an imported curve the same way as a typed one. A curve with its points out of order carries the same warning triangle in the list.
The dialog then clears the selection and shows a result card in its place. The card reads Curves imported, followed by the counts, for example 3 added, 1 updated, 2 identical, 4 not modified. Only the counts that are not zero appear. The last count is the number of curves in this library that the import left as they were. When the file changed nothing, the card reads Nothing was imported instead. The card stays until you dismiss it or select a curve.
The app skips rows that it cannot use. The card then reads Found issues while importing. The counts gain the number of curves ignored, and an Issues entry sits under them. When you open it, you see one line per problem, with the rows where it happened:

| Message | What it means |
|---|---|
| Missing curve name. Row ignored | a row with no name, and no named row above it to take one from |
| Axis must be X or Y. Curve ignored | an axis cell that is blank or says something else |
| Invalid value. Curve ignored | a value cell that is not a number |
| Missing value. Curve ignored | an empty cell between two values |
| Axis given twice for the same curve. Row ignored | two X rows or two Y rows under one name (the app keeps the first) |
| Curve has no matching X and Y rows. Curve ignored | a name with only one of its two rows |
| X and Y rows give different types. Curve ignored | the two rows of a pair name different kinds |
| X and Y rows have different lengths. Curve ignored | more values on one row of the pair than on the other |
| Curve belongs to the Pump library. Ignored | a head or efficiency curve, which this dialog does not take |
A head or efficiency curve counts as ignored here, even when its rows are correct. Import it from the pump library instead.
When more than half of the rows are unusable, the app decides that this is not a curves file and imports nothing. It says This does not look like a curves file. Expected columns are Curve name, Type, Axis and Values. Curves that belong to the pump library do not count against the file. A file with another extension reports Only .csv and .xlsx files can be imported. A file the app cannot open reports Failed to read file.
An import changes the dialog, not the model. Until you press Save, nothing reaches the tanks or the valves. The whole import then lands as one step in the undo history. Cancel leaves the library as it was. A read-only library still offers Export, but Import is disabled.
Exporting
Section titled “Exporting”Export writes the library as Comma-Separated Values (.csv) or
Microsoft Excel spreadsheet (.xlsx). The app names the file after the
project, with -Curves on the end. The file holds the tank volume, valve and
headloss curves and the uncategorized ones, two rows each. It holds nothing
from the pump library. This is how a set of curves moves between models. You
export them from the model where you built them, then import the file into the
next model. To move every curve, export from both dialogs and import both
files.
What is saved, and what the run sees
Section titled “What is saved, and what the run sees”Curves are part of the model, and the app writes them into the project file with their kinds. See Projects.
Through an INP, curves behave like this:
- An export writes every curve in the library, used or not. A comment before
each curve names its kind:
;VOLUME:,;VALVE:or;HEADLOSS:. That comment lets a file epanet-js wrote come back with its sections intact. - A run gives the engine only the curves that something references.
- An import takes the kind from the first thing that uses the curve. A curve used in two different ways is imported twice, and the second copy gets a generated label. For a curve that nothing uses, the comment above its first point decides. A file written by another tool usually has no such comment, and those curves arrive Uncategorized.
See Opening an existing model and Import and export.
- Tanks — Tank geometry, and what happens when you attach a volume curve.
- Valves — which valve types need a curve.
- Pump library — head and efficiency curves, which live apart from these.