Grids and graphs

Example module: hellogrid.

A graph is not a separate mechanism: it’s a visualization of an existing grid. Everything therefore starts with the grid.

The basic mechanism: a repeated snapshot

A grid is only a repeated snapshot of its own property’s base elements — there is no API to “add a row with these values” directly:

getEltInt("mesures", "t")->setValue(t, false);
getEltFloat("mesures", "valeur")->setValue(v, false);
getProperty("mesures")->push();   // copie l'état courant des éléments en nouvelle ligne
  • clearGrid() resets the grid.
  • gridLimit (JSON key) caps the number of rows: beyond it, the oldest one is silently removed (FIFO).
"mesures": {
  "hasGrid": true,
  "showGrid": true,
  "showElts": false,
  "gridLimit": 30,
  "e": {
    "t": { "type": "int" },
    "valeur": { "type": "float" }
  }
}
  • showGrid: displays the grid itself.
  • showElts: displays the form for the base elements (the “draft” before push()). Usually hidden (false) when it’s the module that pushes the rows.

Generic CRUD, with no code at all

With permission:2 (read/write) on a grid-backed property, the client gets, for free, controls to create/modify/delete/reorder rows — entirely handled by the framework, with no code needed on the module side:

EventPropertyMulti method calledEffect
GCnewLine(valeurs)Adds a row
GUupdateLine(index, valeurs)Modifies an existing row
GDdeleteLine(index)Deletes a row
GFfetchLine(index)Copies the row’s values into the base elements — “load for editing”
GHswapLines(index, index-1)Moves a row up one rank
GBswapLines(index, index+1)Moves a row down one rank
GRclearGrid()Clears the grid

The typical client (the ostcore interface) already displays, for any grid-backed property not locked to read-only: an edit button (triggers GF), move up/down (GH/GB), delete (GD), and an add/edit form (GC/GU) — unconditionally, as soon as showGrid and the permission allow it. With showElts:true as well, clicking a row (outside the action buttons) also triggers GF and loads its values into the base-elements form. This is exactly what gridvisible already demonstrates in the example module — no extra code to write to benefit from all this.

Two clearly distinct uses

A grid can be pushed by the module (read-only on the client side, the server pushes rows as they come — a measurement history, for example) or managed by the client (read/write, the generic CRUD above — a list of tasks, targets, filters…). Both patterns coexist within the same mechanism, only the permission changes.

autoSelect / autoUpDown: reserved, not wired up yet

PropertyMulti also exposes autoSelect()/setAutoSelect() and autoUpDown()/setAutoUpDown() (JSON keys "autoselect"/"autoupdown"). Their intended role: letting the module take control of what should happen when the user selects a row or reorders one — not a client-side presentation matter, the client displays its grid controls the same way in every case.

Not implemented yet

These two settings are stored and read back, but nothing in the framework actually consults them yet — there is currently no entry point (virtual method, event) allowing a module to react to a row selection or reordering. Tracked in ost/ostserver#270.

A graph is a grid, visualized

graphType/graphParams require hasGrid:true — no grid, no graph.

XY: a curve, or a scatter plot

"courbe": {
  "hasGrid": true, "showGrid": false, "showElts": false,
  "gridLimit": 30,
  "graphType": "XY",
  "graphParams": { "X": "t", "Y": "valeur" }
}

X/Y name the grid elements to use as axes. Points are connected by a line by default — for a scatter plot with no ordering relationship (non-sequential measurements, for example), adding "scatter": true to graphParams disables the line, the points themselves are still displayed.

SDY: several series, on several axes

"graphType": "SDY",
"graphParams": {
  "S": "serie", "D": "date", "Y": "valeur",
  "graphColors": {
    "temp": { "R": 255, "G": 0, "B": 0 },
    "hum":  { "R": 0, "G": 0, "B": 255 }
  },
  "axis": {
    "gauche": { "pos": "left", "text": "Température" },
    "droite": { "pos": "right", "text": "Humidité" }
  },
  "mapping": { "temp": "gauche", "hum": "droite" }
}

A single grid, several named series interleaved — each row has its own series value (S), a timestamp (D) and a value (Y). To feed two series at each sample, push one row per series:

getEltString("historique", "serie")->setValue("temp");
getEltDateTime("historique", "date")->setValue(maintenant, false);
getEltFloat("historique", "valeur")->setValue(temperature, false);
getProperty("historique")->push();

getEltString("historique", "serie")->setValue("hum");
getEltDateTime("historique", "date")->setValue(maintenant, false);
getEltFloat("historique", "valeur")->setValue(humidite, false);
getProperty("historique")->push();

graphColors/axis/mapping let two series with very different scales avoid visually crushing each other, each on its own axis.