Skip to content

Dynamic diagram

Fernando Dodino edited this page Oct 5, 2023 · 15 revisions

Dependencies

Third-party components

Dynamic diagram uses

but in order to work offline, preinstall script downloads it inside this folder

- public
  - diagram
    - lib

So, if you do a npm install or npm i this process will occur automatically. For further explanation or if you need to upgrade the current version of these libraries, please see download-libs.sh.

Diagrama Dinámico - dependencias drawio

Fonts

We use Inter font inside the dynamic diagram page, but instead of using a CDN we have manually downloaded a WOFF which is a light version of the font. This font file is located in a special font folder:

image

This file is < 17 kb size, so it's not .gitignored, but we want to keep a minimum amount of typefaces (a.k.a. fonts) used. For now, just one is enough.

If you plan to change the typeface, keep in mind these resources:

  • 5 steps to faster web fonts
  • Google Font helper, allows you to convert a google font into a woff file (collecting only required fonts of a specific typeface, remember that bold/italic/regular is a font and Inter/Verdana/Noto/Ubuntu is the typeface name)

Running everything locally

This strategy allows us to have all dependencies in local files inside the server: you don't need Internet connection at all in order to run wollok-ts-cli and open a dynamic diagram.

Diagrama Dinámico - local 2

All files in public are accessible via localhost:3000 web server.

Interaction between REPL and Dynamic Diagram

Every time we start the REPL, we have

  • a client process (the REPL)
  • and a server process (Node web server, hosting index.html with dynamic pages)

The communication from client to server is achieved via socket.io, emitting these events:

  • initDiagram: dynamic diagram started for first time. The server will set the dark/light mode, and eventually any other configuration.
  • updateDiagram: any time there is a change in Wollok VM. We call updateDiagram 1. on start, 2. on any REPL command executed.

Diagram Generator

updateDiagram event is associated with getDataDiagram function in diagramGenerator.ts file.

This function gets the current frame for the interpreter, and

  • filters objects that doesn't belong to imported definitions for the current file
  • filters objects like true, false, self, that are required by Wollok VM
  • and traverses the whole graph between the objects, avoid duplicating already created nodes

This includes root references from REPL and wkos and their references.

Imported definitions: dynamic diagram vs. VM

Lets recap on "filters objects that doesn't belong to imported definitions for the current file". If we have the following example:

// file example1.wlk
import example2.*
import example3.*

object pepita { }

// file example2.wlk
object raul { }

// file example3.wlk
object veronica { } 

// file example4.wlk
object r2d2 { }

Inside Wollok VM, we will have: pepita, raul, veronica, and example4.r2d2

That said, in REPL console for example1.wlk trying to get a reference to r2d2 will result in a reference error while example4.r2d2 will work. We decided to filter example4.r2d2 in the dynamic diagram to avoid showing references that could be misleading or confusing. If we generate the diagram for example1 we'll only show pepita, raul & veronica.

Diagram generator's output

A node will return

  • id: unique identifier
  • label: the shorter representation we can have for an Object. For example, dates will show 10/22/2023, while strings will show "pepita", etc.
  • type: literal for wollok built-in objects, object for custom objects, null (specific for null references) or REPL.
  • fontsize: the font size based on label width (the longer the label the smaller the font size)
  • mode: light / dark -> not defined by diagram generator, but by the diagram.js file (see above explanation)

An edge will return

  • id: unique identifier
  • source and target: ids of the referenced nodes
  • style: dotted for collections, solid for the rest
  • width: thicker for REPL references, normal for the rest
  • label: the name of the reference (like energia), except for set elements
  • fontsize: based on label width

Diagram.js

Goals achieved by diagram.js file:

  • Define styles for nodes & edges (dark & light mode)
  • Reload the diagram any time we receive the updateDiagram event
  • Change from dark mode to light mode or viceversa

Styles definition

During initialization, Cytoscape offers a css-ish style definition. We configure

  • literal nodes
  • object nodes
  • repl nodes (not visible, via opacity = 0)
  • null nodes
  • edges

We have a special definition for dark mode:

selector: `node[type = "literal"]`,
...
selector: `node[type = "literal"][mode = "dark"]`, // cytoscape way of selecting nodes with type and mode

Why not having class definitions in diagram.css? Because it makes difficult to customize, based on the data the diagram generator produces. For example, the edge style uses label, width and style definitions:

  const edgeStyle = {
    ...fontFace,
    label: "data(label)",
    width: "data(width)",        
    "line-color": "#000000",
    "line-style": "data(style)", // dotted or solid
    ...
}

Reuse of styles are done via constants:

    style: [
      {
        selector: "node",
        style: nodeStyle,
      },
      {
        selector: `node[mode = "dark"]`,
        style: {
          ...nodeStyle,
          "line-color": "#000000",
          "background-color": "#4F709C",
          "border-color": "#6F8FC0",
          color: "#FFFFFF",
        },
      },

Reload diagram

This function

  • removes old nodes (they were garbage collected by last run). For example if we do
const list = ["pepe", "palala"]

and then we do

list.remove(0)

We will have 2 references: list and the first element "palala": "pepe" will no longer exist.

  • after that, we must take care of new objects.
    • if "Pin Objects" configuration is disabled (default behavior), this redraws the whole graph. It takes some time (less than 200ms up to 100 objects), but nodes are smoothly arranged (no collision).
    • if "Pin Objects" configuration is enabled. Performance will be nice (especially for > 100 objects), although new nodes will appear overriding existing ones. Once you move a node it will remain in its position.

Changing dark mode to light mode and viceversa

Whenever a user clicks on the Dark Mode/Light Mode toggle button, we call changeElementsMode function that simply

  • adds the mode for each element: currentElements.forEach(element => { element.data.mode = isDarkMode() ? 'dark' : 'light' })
  • and reloads the diagram