EcoInterfaces
How to install the display library, build a DisplayInput, render a display, load the stylesheet and pick the right export.
This page is the guide to the display library @ecosimlab/ecodisplays. It
covers the installation, the input contract, the exports and the stylesheet.
What the library is for stands on the tool page.
Every statement here comes from the repository ecodisplays, branch main
(commit 94b20a3), package version 1.0.2, newest tag v1.0.2. Where the README
and the code disagree, the code counts, and this page names the difference.
The library draws displays. It does not talk to a simulator, a message broker, or a network. You bring the data.
Prerequisites
- A bundler for SolidJS. The package ships compiled JSX and ES modules. A
Vite project with
vite-plugin-solidis the tested case. - Three peer dependencies:
solid-js1.9,d37 andlodash4.17. The package declares them inpeerDependenciesand installs none of them. - One copy of
solid-jsper page. Two copies break the reactivity. If the library and your app resolve different copies, force one copy in your bundler. - Node or Bun with npm, bun or a compatible package manager for the
installation. The package declares no
enginesfield.
Installation
The package is not in a registry. Install it from its public GitLab repository,
pinned to one tag. The built dist/ is part of the repository, so the
installation runs no build. You need no token and no .npmrc.
With npm:
npm install gitlab:ecosimlab/ecodisplays#v1.0.2
With bun, as a dependency entry:
"dependencies": {
"@ecosimlab/ecodisplays": "git+https://gitlab.com/ecosimlab/ecodisplays.git#v1.0.2"
}
Pin one exact tag. Never pin a branch.
The README still names
v0.5.0in both install examples. Thepackage.jsonofmaincarries version 1.0.2, andv1.0.2is the newest tag. The version in the code counts.
Quickstart
The smallest complete example: one display, one input, one stylesheet.
import { createSignal } from "solid-js"
import { AP6DefaultDisplay, displayInput } from "@ecosimlab/ecodisplays"
import "@ecosimlab/ecodisplays/styles.css"
export function Screen() {
const [input] = createSignal(
displayInput({ hasSimulationData: true, speedKmh: 48, gear: "D" }),
)
return (
<div style={{ width: "640px", height: "400px" }}>
<AP6DefaultDisplay input={input()} fit="contain" />
</div>
)
}
Every display takes the same two props:
| Prop | Meaning |
|---|---|
input |
the current DisplayInput |
fit |
how the display fills its parent box: "contain" (the default) fits the whole design into the box, "width" lets the width lead |
InputTransparenzDisplay takes one more prop, vehiclesAheadMode, and it has
no default. Set it to "all" or to "closest". The same display takes the
optional prop signsMode, default "list", which sets where the traffic signs
stand.
Package exports
The package.json declares four entry points.
| Entry point | What it carries |
|---|---|
@ecosimlab/ecodisplays |
the 20 displays, DisplayInput, displayInput, defaultDisplayInput, VehicleProfile, InputPanel, DisplayFrame, the design sizes and the pure calculations |
@ecosimlab/ecodisplays/input |
the input contract alone: DisplayInput, displayInput, defaultDisplayInput, INPUT_FIELDS, deriveDisplayGate, VehicleProfile. It loads neither SolidJS nor the DOM. |
@ecosimlab/ecodisplays/styles.css |
the stylesheet as a file |
@ecosimlab/ecodisplays/styles |
the same stylesheet as the strings css and fontFaceCss |
Use ./input in a parser or a test that must run in plain Node or Bun.
The input contract
DisplayInput is one flat object with 31 fields. The library checks no value at
runtime. The type is the contract, so check the data at your own boundary.
Build an input with displayInput(partial). It fills every field you leave out
from defaultDisplayInput:
import { type DisplayInput, displayInput } from "@ecosimlab/ecodisplays/input"
const input: DisplayInput = displayInput({
hasSimulationData: true,
hasEnergyData: true,
timeS: 12.5,
wheelspeedMps: 13.3,
batterySocPercent: 72,
})
The unit is part of the name
Every field name ends in its unit.
| Suffix | Meaning |
|---|---|
Percent |
0 to 100 |
S |
seconds |
M |
metres |
Mps |
metres per second |
Kmh |
kilometres per hour |
W, Kw, Kwh |
watt, kilowatt, kilowatt hours |
The fields
| Field | Unit | Default | Meaning |
|---|---|---|---|
hasSimulationData |
flag | false |
false until a valid message arrived. The consumer draws the “no data” state. |
hasEnergyData |
flag | false |
false if the tick carries no vehicle model data |
timeS |
s | 0 |
time since the start of the drive. A lower value than before starts a new drive and resets the accumulators. |
distanceM |
m | 0 |
absolute distance driven |
wheelspeedMps |
m/s | 0 |
wheel speed, unsmoothed. The basis of the calculations. |
speedKmh |
km/h | 0 |
smoothed speed for the display |
gear |
— | "P" |
"P", "R", "N", "D" or a gear number |
batteryEnergyKwh |
kWh | 0 |
energy in the battery. Consumption and range come from its decrease. |
batterySocPercent |
% | 0 |
state of charge |
pBatteryKw |
kW | 0 |
current battery power. Negative during recuperation. |
pAeroW, pRollW, pUpW, pPowertrainLossW, pHydraulicW |
W | 0 |
the power shares: drag, rolling resistance, gradient, drivetrain loss, hydraulic brake |
optimalSpeedKmh |
km/h | 0 |
the speed the optimiser recommends. 0 means “no recommendation”. |
optimalPowerW |
W | 0 |
the battery power the optimiser recommends |
referenceSpeedLine |
list | [] |
reference speed from the offline optimisation. Empty means “no reference”. |
mpcSpeedLine |
list | [] |
speed forecast of the MPC over the distance |
mpcPowerLine, mpcPowerLineTime |
list | [] |
power forecast of the MPC, over the distance and over time |
mpcStateMachineState |
— | null |
state of the MPC state machine. null shows nothing. |
vehiclesAhead |
list | [] |
the vehicles ahead in the own lane, sorted by distance |
speedLimitKmh |
km/h | 0 |
the speed limit in force. 0 means “unknown”. |
nextSpeedLimitKmh |
km/h | null |
the limit of the next sign ahead |
nextSpeedLimitDistanceM |
m | null |
the distance to the next speed limit sign |
trafficLightsAhead |
list | [] |
traffic lights and stop signs ahead, sorted by distance |
stopDistanceBrakeM, stopDistanceStrongRecuperationM, stopDistanceLightRecuperationM, stopDistanceCoastingM |
m | null |
the four stop points of the kinetic beam, one per way to stop |
INPUT_FIELDS carries the same table in code: unit, range and step per field.
Read it to build a control panel, or as documentation of a plausible value.
The displays
The export names come from the work packages of the project, not from the function. The column “shows” says what to expect.
| Family | Export | Shows |
|---|---|---|
AP4 |
AP4ConsumptionDisplay |
a full-screen road with the consumption of the last 150 m as a trace |
AP4 |
AP4VelocityDisplay |
the same road, with the driven speed against the line of the optimiser |
AP5 |
AP5BaselineDisplay |
speed, gear, state of charge and consumption, plus a tall power bar |
AP5 |
AP5BoxDisplay |
grey boxes in the sizes of the AP5 parts: a layout template, and the only display without input |
AP5 |
AP5ComperatorDisplay |
the power bar panel, plus the energy score as a bar |
AP5 |
AP5DecisionDisplay |
the power bar panel, with a marker for the recommended power |
AP5 |
AP5DecisionSpeedDisplay |
a speed bar with a marker for the current and one for the recommended speed |
AP5 |
AP5InputDisplay |
the power bar split into gradient, drag, rolling resistance, drivetrain loss and brake |
AP5 |
AP5SpeedometerDisplay |
speed and gear in front of a large arc |
AP6 |
AP6DefaultDisplay |
speed and gear alone, in the size of their content |
AP6 |
AP6StatusQuoDisplay |
a speed panel with a two-part bar, filled by the share of the maximum power |
AP6 |
AP6VariationDisplay |
the same two-part bar, filled by the consumption per distance |
LEM |
LEMDisplay |
power against recommended power over 150 m back and ahead |
LEM |
LEMTimeDisplay |
the same power chart over 20 seconds back and ahead |
LEM |
LEMVelocityTimeDisplay |
speed over 20 seconds back and ahead, with forecast and reference line |
LEM |
LEMDecisionDisplay |
an AP5 panel with the target speed in colour, next to a power bar with the brake share |
InputTransparenz |
InputTransparenzDisplay |
the road ahead with vehicles, the next speed sign and the next light |
EnergyScore |
EnergyScoreBarDisplay |
a small panel over the video: speed, gear and charge, next to a green efficiency bar |
EnergyScore |
EnergyScoreGlyphDisplay |
the same small panel, with a smiley instead of the bar |
EnergyScore |
MinimalGaugeDisplay |
speed and gear only, drawn small |
Two names are not what you would guess. LEMDecisionDisplay is an AP5 panel
with an LEM name, and it reads the AP5 fields. MinimalGaugeDisplay sits in the
energy score family, but it calculates like AP6.
Vehicle profile
The displays scale the input against a vehicle. Every display whose calculation
depends on those numbers takes the optional prop vehicle. Without the prop,
defaultVehicleProfile applies, with the values of the simulator car.
import { AP6DefaultDisplay, defaultVehicleProfile } from "@ecosimlab/ecodisplays"
const smallCar = { ...defaultVehicleProfile, maxPowerKw: 80, batteryCapacityKwh: 22 }
| Field | Unit | What it scales |
|---|---|---|
maxPowerKw |
kW | the power bar |
batteryCapacityKwh |
kWh | the state of charge in percent (LEM) |
rangeAtFullBatteryKm |
km | the range shown by LEM |
consumptionMaxKwh, consumptionMinKwh |
kWh | the top and the bottom of the consumption bar |
defaultConsumptionKwhPer100Km |
kWh/100 km | the stand-in range while the measured consumption is still 0 |
scorePowerMaxKw |
kW | how hard a deviation from the recommendation hits the energy score |
The AP4 displays draw the raw trace and take no profile.
Styles
The package ships its CSS twice. The text is the same in both.
As a file, if your build bundles CSS:
import "@ecosimlab/ecodisplays/styles.css"
As a string, if it does not — a single-file bundle, for example:
import { css, fontFaceCss } from "@ecosimlab/ecodisplays/styles"
const style = document.createElement("style")
style.textContent = css
document.head.append(style)
If you render a display in a shadow root, add fontFaceCss to the document as
well. The browser reads @font-face from the document only, never from a shadow
root. Without that step the displays fall back to a system font.
The stylesheet holds no global CSS reset. It leaves the page around it alone. If your page needs a reset, load one yourself.
The font is Inter, under the SIL Open Font License 1.1. The font files ship with the package as woff2, embedded in the CSS as data URIs. Nothing loads from the network at runtime.
Development and tests
Clone the repository and install with bun. The scripts of package.json:
| Command | What it does |
|---|---|
bun run dev |
starts the showcase: every display with a made-up drive, one page per display |
bun run build |
rewrites dist/ |
bun run test |
runs the tests with bun test --conditions browser |
bun run typecheck |
runs tsc --noEmit over the source and the tests |
bun run lint |
runs Biome over src/ |
bun run check |
builds again and fails if dist/ does not match the source, or if the version does not match the tag |
A release is a commit that carries the new version, the matching dist/ and a
tag. There is no publish step and no registry.
Licence and citation
MIT. The file LICENSE carries the text, AUTHORS names the people who wrote
the code, and ASSETS.md lists every image and font with its source and
licence. Cite the library at version 1.0.2, tag v1.0.2.
Open questions
- The README names
v0.5.0in its install examples, and the newest tag isv1.0.2. The README is out of date at that place.- The consumers
videosimlab-cockpit-replayandvideosimlab-templatepinv1.0.1, one tag behind the library.- The prop
kineticBeamofInputTransparenzDisplaydraws the four stop points on the road. The source calls it an experiment, not yet a study condition. It is off by default.