Skip to main content

UI Views

Users can create custom planning views for different sub-systems (e.g. science, engineering, thermal, etc.), where only data (e.g. activities and resources) for those sub-systems are visualized. This is done through custom JSON configuration files (or directly via the UI). The format of a UI View is the subject of this document.

See the UI view JSON schema specification for the complete set of view object properties and types.

View Schema​

This is the main type interface for the planning UI view:

type ViewActivityTable = {
columnDefs: ColDef[];
columnStates: ColumnState[];
id: number;
};

type ViewIFrame = {
id: number;
src: string;
title: string;
};

type ViewDefinition = {
plan: {
activityTables: ViewActivityTable[];
iFrames: ViewIFrame[];
layout: Grid;
timelines: Timeline[];
};
};

For example, here is a JSON object that implements an empty ViewDefinition interface:

{
"plan": {
"activityTables": [],
"iFrames": [],
"layout": {},
"timelines": []
}
}

Layout (Grid)​

A planning UI view consists of a layout which describes the different visible components and how they are arranged in re-sizeable rows and columns. The layout follows the following Grid type definitions:

type GridComponent = {
activityTableId?: number;
componentName: string;
gridName?: string;
iFrameId?: number;
id: number;
props?: any;
timelineId?: number;
type: 'component';
};

type GridColumns = {
columnSizes: string;
columns: Grid[];
gridName?: string;
id: number;
type: 'columns';
};

type GridGutter = {
gridName?: string;
id: number;
track: number;
type: 'gutter';
};

type GridRows = {
gridName?: string;
id: number;
rowSizes: string;
rows: Grid[];
type: 'rows';
};

type Grid = GridColumns | GridComponent | GridGutter | GridRows;

For example, here is a grid layout definition in JSON:

{
"columnSizes": "1fr 3px 2fr 3px 1fr",
"columns": [
{ "componentName": "ActivityFormPanel", "id": 1, "type": "component" },
{ "id": 2, "track": 1, "type": "gutter" },
{
"id": 3,
"rowSizes": "70% 3px 1fr",
"rows": [
{
"componentName": "TimelinePanel",
"id": 4,
"timelineId": 0,
"type": "component"
},
{ "id": 5, "track": 1, "type": "gutter" },
{
"activityTableId": 0,
"componentName": "ActivityTablePanel",
"id": 6,
"type": "component"
}
],
"type": "rows"
},
{ "id": 7, "track": 3, "type": "gutter" },
{ "componentName": "ActivityTypesPanel", "id": 8, "type": "component" }
],
"gridName": "Activities",
"id": 0,
"type": "columns"
}

Timeline​

The timelines section allows you to specify a list of timeline visualizations which display time-ordered data (i.e. activities or resources). Here is the interface of a timeline:

interface Timeline {
id: number;
marginLeft: number;
marginRight: number;
rows: Row[];
verticalGuides: VerticalGuide[];
}

To visualize data in a timeline you need to add row objects to the rows array. A row is a layered visualization of time-ordered data. Each layer of a row is specified as an object of the layers array. The types for a Row, Layer, and DiscreteOptions are as follows:

type Row = {
autoAdjustHeight: boolean;
discreteOptions: DiscreteOptions;
expanded: boolean;
height: number;
horizontalGuides: HorizontalGuide[];
id: number;
layers: Layer[];
name: string;
yAxes: Axis[];
};

type Layer {
chartType: 'activity' | 'line' | 'x-range' | 'external-event';
filter: {
activity?: ActivityLayerFilter;
externalEvent?: ExternalEventLayerFilter;
resource?: ResourceLayerFilter;
};
id: number;
name: string;
yAxisId: number | null;
}

type DiscreteOptions = {
// Activity-Layer-specific Options
activityOptions?: {
// Whether or not to display only directives, only spans, or both in the row
composition: 'directives' | 'spans' | 'both';

// If 'directive' the activities are grouped starting with directive types, if 'flat' activities are grouped by type regardless of hierarchy
hierarchyMode: 'directive' | 'flat';
};

// Describes the primary method in which external events are visualized within this row
displayMode: 'grouped' | 'compact';

// External-Event-Layer-specific Options
externalEventOptions?: {
// Determines whether to group the External Events by their event type, or their external source
groupBy: 'event_type_name' | 'source_key';
};

// Height of subrows
height: number;

// Item text label behavior
labelVisibility: 'on' | 'off' | 'auto';
};

Here is a JSON object that creates a single row with one activity layer that renders all activity types. Activity layer filtering and customization is discussed in depth on the Timeline Editing page.

{
"autoAdjustHeight": true,
"height": 200,
"horizontalGuides": [],
"id": 0,
"layers": [
{
"activityColor": "#283593",
"chartType": "activity",
"id": 0,
"yAxisId": null
}
],
"yAxes": []
}

For data that has y-values (for example resource data), you can specify a y-axis and link a layer to it by ID. Here are the interfaces for Axis and Label:

interface Axis {
color: string;
id: number;
label: Label;
scaleDomain: (number | null)[];
tickCount: number | null;
}

interface Label {
color?: string;
text: string;
}

Y-axes are specified in the row separately from layers so we can specify multi-way relationships between axes and layers. For example you could have many layers corresponding to a single row axis.

Here is the JSON for creating a row with two overlaid resource layers. The first layer shows only resources with the name peel, and uses the y-axis with ID 1. The second layer shows only resources with the name fruit, and uses the y-axis with the ID 2.

{
"autoAdjustHeight": false,
"height": 100,
"horizontalGuides": [],
"id": 1,
"layers": [
{
"chartType": "line",
"filter": { "resource": { "names": ["peel"] } },
"id": 1,
"lineColor": "#283593",
"lineWidth": 1,
"pointRadius": 2,
"yAxisId": 1
},
{
"chartType": "line",
"filter": { "resource": { "names": ["fruit"] } },
"id": 2,
"lineColor": "#ffcd69",
"lineWidth": 1,
"pointRadius": 2,
"yAxisId": 2
}
],
"yAxes": [
{
"color": "#000000",
"id": 1,
"label": { "text": "peel" },
"scaleDomain": [0, 4],
"tickCount": 5
},
{
"color": "#000000",
"id": 2,
"label": { "text": "fruit" },
"scaleDomain": [-10, 4],
"tickCount": 5
}
]
}

Activity Tables​

Here is an example activityTables view JSON definition. The columnDefs and columnStates follow the schemas of the ag-grid ColDef and ColumnState respectively.

{
"activityTables": [
{
"columnDefs": [
{
"field": "id",
"filter": "agTextColumnFilter",
"headerName": "ID",
"sortable": true,
"resizable": true
},
{
"field": "type",
"filter": "agTextColumnFilter",
"headerName": "Type",
"sortable": true,
"resizable": true
},
{
"field": "start_time",
"filter": "agTextColumnFilter",
"headerName": "Start Time",
"sortable": true,
"resizable": true
},
{
"field": "duration",
"filter": "agTextColumnFilter",
"headerName": "Duration",
"sortable": true,
"resizable": true
}
],
"columnStates": [],
"id": 0
}
]
}

To use the ActivityTablePanel you need to add an ActivityTablePanel component to the grid layout and connect it via the activityTableId. For example:

{
"activityTableId": 0,
"componentName": "ActivityTablePanel",
"id": 2,
"type": "component"
}

Notice how we connect the grid component activityTableId with the id of the definition in the activityTables array.

IFrames​

An IFrame component allows you to embed another application in the PlanDev UI. Here is an example iFrames view JSON definition:

{
"iFrames": [
{
"id": 0,
"src": "https://eyes.nasa.gov/apps/solar-system",
"title": "NASA-Eyes-Solar-System"
}
]
}

To use the IFrame you need to add an IFramePanel component to the grid layout and connect it via the iFrameId. For example:

{ "componentName": "IFramePanel", "iFrameId": 0, "id": 1, "type": "component" }

Notice how we connect the grid component iFrameId with the id of the definition in the iFrames array.

GraphQL Queries​

The following GraphQL queries can be used to programmatically operate on UI views.

Create Single View​

mutation {
insert_view_one(
object: {
definition: { plan: { activityTables: [], iFrames: [], layout: {}, timelines: [] } }
name: "My First View"
owner: "system"
}
) {
id
}
}

Create Multiple Views​

mutation {
insert_view(
objects: [
{
definition: { plan: { activityTables: [], iFrames: [], layout: {}, timelines: [] } }
name: "First View"
owner: "system"
}
{
definition: { plan: { activityTables: [], iFrames: [], layout: {}, timelines: [] } }
name: "Second View"
owner: "system"
}
]
) {
returning {
id
}
}
}

Get All Views​

query {
view {
created_at
definition
id
name
owner
updated_at
}
}

Get Single View by ID​

query {
view_by_pk(id: 1) {
created_at
definition
id
name
owner
updated_at
}
}

Delete Single View by ID​

mutation {
delete_view_by_pk(id: 1) {
id
}
}

Delete All Views in the Database​

mutation {
delete_view(where: {}) {
affected_rows
}
}

Update Definition and Name of a Single View by ID​

mutation {
update_view_by_pk(pk_columns: { id: 1 }, _set: { definition: { plan: {} }, name: "New Name" }) {
id
}
}