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 interfaces for a Row
, Layer
, and ActivityOptions
are as follows:
interface Row {
activityOptions?: ActivityOptions;
autoAdjustHeight: boolean;
expanded: boolean;
height: number;
horizontalGuides: HorizontalGuide[];
id: number;
layers: Layer[];
name: string;
yAxes: Axis[];
}
interface Layer {
chartType: 'activity' | 'line' | 'x-range';
filter: {
activity?: ActivityLayerFilter;
resource?: ResourceLayerFilter;
};
id: number;
yAxisId: number | null;
}
type ActivityOptions = {
// Height of activity subrows
activityHeight: number;
// Whether or not to display only directives, only spans, or both in the row
composition: 'directives' | 'spans' | 'both';
// Describes the primary method in which activities are visualized within this row
displayMode: 'grouped' | 'compact';
// If 'directive' the activities are grouped starting with directive types, if 'flat' activities are grouped by type regardless of hierarchy
hierarchyMode: 'directive' | 'flat';
// Activity text label behavior
labelVisibility: 'on' | 'off' | 'auto';
};
Here is a JSON object that creates a single row with one activity layer.
{
"autoAdjustHeight": true,
"height": 200,
"horizontalGuides": [],
"id": 0,
"layers": [
{
"activityColor": "#283593",
"activityHeight": 20,
"chartType": "activity",
"filter": { "activity": { "types": ["BiteBanana", "PickBanana"] } },
"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
.
{
"activityOptions": {
"activityHeight": 16,
"composition": "both",
"displayMode": "grouped",
"hierarchyMode": "flat",
"labelVisibility": "auto"
},
"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 Aerie 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
}
}