Widgets

The frontend detail view consists of sections displayed as tabs containing widgets. Specific nodes produce the output rendered by the frontend into sections and widgets. See Tutorial: Write your own nodes for an introduction to this.

Section nodes are decorated with @marv.node(Section) and push a dictionary with title and a list of widgets:

section = {'title': 'Section title', 'widgets': [widget, ...]}

Widget nodes are decorated with @marv.node(Widget) and push a dictionary with an optional title and one key corresponding to a widget_type (e.g. image) and value with data extracted from a dataset to be rendered by the widget (e.g. {'src': 'img/path'}.

widget = {'title': 'Widget title', widget_type: data}

Widgets are either rendered directly into a section or are used as part of another composite widget, e.g. an image in a gallery (see below).

Valid values for widget_type are given in the following sections. The title key is optional and omitted for brevity.

Image

image = {'image': {'src': img.relpath}}

Example: Image used inside gallery marv_robotics.detail.galleries()

Video

video = {'video': {'src': videofile.relpath}}

Example: marv_robotics.detail.video_section()

PDF (EE)

pdf = {'pdf': {'src': pdffile.relpath}}

Interactive Plots

There are two options for plotting:
import plotly.graph_objects as go

# plot into figure with plotly
fig = go.Figure(data=go.Scatter(y=distances))

# save plotly figure to file
plotfile = yield marv.make_file('distances.json')
Path(plotfile.path).write_text(fig.to_json())

# create plotly widget referencing file
yield marv.push({
    'title': 'Distance driven',
    'plotly': f'marv-partial:{plotfile.relpath}',
})

Trajectory

The trajectory widget renders a list of layers on top of each other.

{'zoom': {'min': -10, 'max': 30},
 'layers': [
     {'title': 'Vector floor map',
      'geojson': geojson_object1},
     {'title': 'Trajectory',
      'color': (0., 1., 0., 1.),
      'geojson': geojson_object2},
 ]
}

The zoom value defines the valid zoom range that will be enforced in the frontend. Each layer in the list is defined by its name that is displayed in the legend, an optional legend color, and its GeoJSON definition.

The geojson value conforms the official GeoJSON format specification, and adds a few styling extensions. For now the widget supports a subset of the GeoJSON standard. The widget expects a feature collection as the toplevel GeoJSON object and the supported geometries are LineString and Polygon.

{'feature_collection': {'features': [
 {'geometry': {'line_string': {'coordinates': coord_list}},
  'properties': {'coordinatesystem': 'WGS84',      # or `cartesian`
                 'color': (0., 1., 0., 1.),        # per geometry color
                 'colors': color_list,             # or per vertex color list
                 'fillcolor': (0., 1., 0., 1.),    # per geometry fillcolor
                 'fillcolors': fillcolor_list,     # or per vertex fillcolor list
                 'width': 4.,                      # line or polygon stroke width
                 'timestamps': timestamp_list,     # per vertex timestamp used for playback
                 'rotations': rotations_list,      # per vertex rotations if markers are used
                 'markervertices' marker_geometry, # rotation marker polygon (e.g. `[0, 0, -1, .3, -1, -.3]`)
                 }},
]}}

The properties object holds styling and animation information for the trajectory player widget. Properties should at least define one of the color values, apart from that all entries are optional. The default coordinatesystem is WGS84 which is used per default in the GeoJSON standard and in sensor_msgs/NavSatFix Message. The value cartesian allows the use of any Cartesian coordinate system.

Colors can be given either as a per geometry value or as a list of values for each vertex in the geometry.

The width value corresponds to the rendered line width in pixels. When the geometry is of type polygon and either of color or colors is set, then a stroke of width pixels is rendered.

EE only: The presence of a timestamps list enables the player functionality. This option works only with geometries of type LineString and should hold one value per geometry vertex. The widget assumes that the timestamps are in ascending order, as usually delivered by a GPS sensor.

EE only: The presence of a markervertices enables rendering of a marker at the current trajectory location during playback. The triangle size is not affected by zoom. If not set explicitly its rotation is calculated by the last significant heading from the coordinates.

EE only: The rotations list can be used to set the rotation of the marker at each coordinate. Each value is a scalar indicating the rotation around the z axis, e.g. obtained from an IMU. The rotation angles have to be given counter clock wise in radians, with zero pointing in the direction of the x axis.

Example: marv_robotics.detail.trajectory_section()

Table

Example: marv_robotics.detail.bagmeta_table()

Key/value

Example: marv_robotics.detail.summary_keyval()

Preformatted

Wraps data into an html <pre></pre> tag.

pre = {'pre': {'text': 'foo\nbar'}}

Custom

Renders custom widgets.

custom = {'custom': {'type': 'foo', 'data': json.dumps(data)}}

Create site/frontend/custom.js and restart your instance to customize widgets and formatters.

/*!
 * Copyright 2016 - 2018  Ternaris.
 * SPDX-License-Identifier: CC0-1.0
 */

window.marv_extensions = {
  formats: {
    // replace default datetime formatter, show times in UTC
    'datetime': function(date) { return new Date(date).toUTCString(); }
  },
  widgets: {
    // rowcount widget displays the number of rows in a table
    'rowcount': [
      /* insert callback, renders the data

        @function insert
        @param {HTMLElement} element The parent element
        @param {Object} data The data to be rendered
        @return {Object} state Any variable, if required by remove
      */
      function insert(element, data) {
        const doc = element.ownerDocument;
        const el = doc.createTextNode(data.rows.length + ' rows');
        element.appendChild(el);

        const state = { el };
        return state;
      },

      /* remove callback, clean up if necessary

        @function remove
        @param {Object} state The state object returned by insert
      */
      function remove(state) {
        state.el.remove();
      }
    ]
  }
};

Implement custom widgets as Web Components and style them independently of the rest of MARV inside their respective shadow DOM. This approach guarantees that changes in MARV or the customization will never interfere with each other. Each widget renders in a container with the CSS class .widget-${name} and each custom widget not implemented as a Web Component can use this class to style its contents. Additionally, custom widgets can make use of the CSS classes and variables defined by the default MARV stylesheet to achieve a native look and feel.

Custom CSS loads from site/frontend/custom.css and the /custom/ backend route serves additional files from site/frontend/custom/.