Skip to content

Commit

Permalink
Add option to graph nested serializable types (#31)
Browse files Browse the repository at this point in the history
* saba-ja: added support for graphing serilizables

* saba-ja: code format

* lestarch: adding in missspelling break

Co-authored-by: M Starch <LeStarch@googlemail.com>
  • Loading branch information
saba-ja and LeStarch authored Nov 30, 2021
1 parent c567e49 commit 6907b9c
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 38 deletions.
1 change: 1 addition & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ xcode
xhtml
xhttp
xl
xls
xlsx
xml
xy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export let chart_wrapper_template = `
<div class="col-md-6 mb-1">
<button class="btn btn-block" :class="{'btn-secondary': !this.siblings.in_sync, 'btn-success': siblings.in_sync}" v-on:click="siblings.in_sync = !siblings.in_sync">
<i class="fas fa-thumbtack"></i>
<span class="d-md-none d-lg-inline">Lock Timescales</span>
<span class="d-md-none d-lg-inline">Lock Scale</span>
</button>
</div>
</div>
Expand Down
112 changes: 77 additions & 35 deletions src/fprime_gds/flask/static/addons/chart-display/addon.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,27 @@
*
* Visualize selected telemetry channels using time series charts. This is done in realtime. Time-shifted signals
* will need to be panned into focus.
*
*
* @author saba-ja
*/
import {generate_chart_config} from "./config.js";
import {chart_wrapper_template, chart_display_template} from "./addon-templates.js";
import { _datastore } from '../../js/datastore.js';
import {_loader} from "../../js/loader.js";
import {SiblingSet} from './sibling.js';
import {timeToDate} from "../../js/vue-support/utils.js"
import { generate_chart_config } from "./config.js";
import {
chart_wrapper_template,
chart_display_template,
} from "./addon-templates.js";
import { _datastore } from "../../js/datastore.js";
import { _loader } from "../../js/loader.js";
import { SiblingSet } from "./sibling.js";
import { timeToDate } from "../../js/vue-support/utils.js";

import "./vendor/chart.js";
import "./vendor/chartjs-adapter-luxon.min.js";
import "./vendor/hammer.min.js";
import { flatten } from "./modified-vendor/flat.js";

import './vendor/chart.js';
import './vendor/chartjs-adapter-luxon.min.js';
import './vendor/hammer.min.js';
// Note: these are modified versions of the original plugin files
import './modified-vendor/chartjs-plugin-zoom.js';
import './modified-vendor/chartjs-plugin-streaming.js';
import "./modified-vendor/chartjs-plugin-zoom.js";
import "./modified-vendor/chartjs-plugin-streaming.js";

/**
* Wrapper component to allow user add multiple charts to the same page. This component handles the functions for
Expand All @@ -30,8 +35,8 @@ Vue.component("chart-wrapper", {
counter: 1,
locked: false,
isHelpActive: false,
wrappers: [{"id": 0}], // Starts with a single chart
siblings: new SiblingSet()
wrappers: [{ id: 0 }], // Starts with a single chart
siblings: new SiblingSet(),
};
},
template: chart_wrapper_template,
Expand All @@ -40,17 +45,17 @@ Vue.component("chart-wrapper", {
* Add new chart handling the Chart+ button.
*/
addChart(type) {
this.wrappers.push({'id': this.counter});
this.wrappers.push({ id: this.counter });
this.counter += 1;
},
/**
* Remove chart with the given id for handling the X button on a chart wrapper
*/
deleteChart(id) {
const index = this.wrappers.findIndex(f => f.id === id);
this.wrappers.splice(index,1);
const index = this.wrappers.findIndex((f) => f.id === id);
this.wrappers.splice(index, 1);
},
}
},
});

/**
Expand All @@ -60,15 +65,25 @@ Vue.component("chart-display", {
template: chart_display_template,
props: ["id", "siblings"],
data: function () {
let names = Object.values(_loader.endpoints["channel-dict"].data).map((value) => {return value.full_name});
const names_list = Object.values(
_loader.endpoints["channel-dict"].data
).map((value) => {
return flatten(value.type_obj, {
maxDepth: 20,
prefix: value.full_name,
});
});

const names = names_list.flat();

return {
channelNames: names,
selected: null,
oldSelected: null,

isCollapsed: false,
pause: false,

chart: null,
};
},
Expand Down Expand Up @@ -96,8 +111,11 @@ Vue.component("chart-display", {
config.options.scales.x.realtime.onRefresh = this.siblings.sync;
this.showControlBtns = true;
try {
this.chart = new Chart(this.$el.querySelector("#ds-line-chart"), config);
} catch(err) {
this.chart = new Chart(
this.$el.querySelector("#ds-line-chart"),
config
);
} catch (err) {
// Todo. This currently suppresses the following bug error
// See ChartJs bug report https://github.com/chartjs/Chart.js/issues/9368
}
Expand All @@ -119,7 +137,9 @@ Vue.component("chart-display", {
return;
}
_datastore.deregisterChannelConsumer(this);
this.chart.data.datasets.forEach((dataset) => {dataset.data = [];});
this.chart.data.datasets.forEach((dataset) => {
dataset.data = [];
});
this.chart.destroy();
this.siblings.remove(this.chart);
this.chart = null;
Expand All @@ -131,7 +151,7 @@ Vue.component("chart-display", {
*/
emitDeleteChart(id) {
this.destroy();
this.$emit('delete-chart', id);
this.$emit("delete-chart", id);
},
/**
* Callback to handle new channels being pushed at this object.
Expand All @@ -141,32 +161,54 @@ Vue.component("chart-display", {
if (this.selected == null || this.chart == null) {
return;
}
let name = this.selected;
// Get channel name assuming the string is in component.channel format.
let channel_full_name = this.selected
.split(".")
.slice(0, 2)
.join(".");
let serial_path = this.selected.split(".").slice(2).join(".");

// Filter channels down to the graphed channel
let new_channels = channels.filter((channel) => {
return channel.template.full_name === name
return channel.template.full_name === channel_full_name;
});
// Convert to chart JS format
new_channels = new_channels.map(
(channel) => {
return {x: timeToDate(channel.time), y: channel.val}

// Get channel value
function getValue(ch_obj, path_str) {
// If serializable path exist parse and return its value
if (path_str) {
let keys = "val_obj." + serial_path + ".value";
return keys
.split(".")
.reduce((o, k) => (o || {})[k], ch_obj);
} else {
// otherwise assume simple object
return ch_obj.val;
}
);
}

// Convert to chart JS format
new_channels = new_channels.map((channel) => {
return {
x: timeToDate(channel.time),
y: getValue(channel, serial_path),
};
});

// Graph and update
this.chart.data.datasets[0].data.push(...new_channels);
this.chart.update('quiet');
}
this.chart.update("quiet");
},
},
/**
* Watch for new selection of channel and re-register the chart
*/
watch: {
selected: function() {
selected: function () {
if (this.selected !== this.oldSelected) {
this.oldSelected = this.selected;
this.registerChart();
}
},
}
},
});
4 changes: 2 additions & 2 deletions src/fprime_gds/flask/static/addons/chart-display/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ export let ticks_config = {
export let realtime_config = {
// Initial display width (ms): 1 min
duration: 60000,
// Total data history (ms): 4 min
ttl: 4 * 60 * 1000,
// Total data history (ms): 60 min
ttl: 60 * 60 * 1000,
// Initial chart delay (ms): 0
delay: 0,
// Drawing framerate (ms): 30 Hz
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* BSD 3-Clause "New" or "Revised" License
* Source: https://github.com/hughsk/flat
* Copyright (c) 2014, Hugh Kennedy
The module has been modified to support F Prime GDS tasks
*/

function isBuffer(obj) {
return (
obj &&
obj.constructor &&
typeof obj.constructor.isBuffer === "function" &&
obj.constructor.isBuffer(obj)
);
}

function keyIdentity(key) {
return key;
}

/**
*
* @param {json} target : channel object
* @param {json} opts : config options
* @returns list of flatten paths
*/
function flatten(target, opts) {
opts = opts || {};
// Default supported F Prime types
const supportedTypes =
["U8", "U16", "U32", "U64", "I8", "I16", "I32", "I64", "F32", "F64"] ||
opts.supportedTypes;

const prefix = opts.prefix || "";
const delimiter = opts.delimiter || ".";
const maxDepth = opts.maxDepth;
const transformKey = opts.transformKey || keyIdentity;
const output = {};

function step(object, prev, currentDepth) {
currentDepth = currentDepth || 1;

Object.keys(object).forEach(function (key) {
const value = object[key];
const isarray = opts.safe && Array.isArray(value);
const type = Object.prototype.toString.call(value);
const isbuffer = isBuffer(value);
const isobject =
type === "[object Object]" || type === "[object Array]";

const newKey = prev
? prev + delimiter + transformKey(key)
: transformKey(key);

if (
!isarray &&
!isbuffer &&
isobject &&
Object.keys(value).length &&
(!opts.maxDepth || currentDepth < maxDepth)
) {
return step(value, newKey, currentDepth + 1);
}

if (supportedTypes.indexOf(value) !== -1) {
let keyId = "";
if (prefix) {
keyId = keyId.concat(prefix, delimiter, newKey);
} else {
keyId = newKey;
}
output[keyId] = value;
}
});
}

step(target);

// Get unique parent of each path
const output_set = new Set();
for (const [key, value] of Object.entries(output)) {
output_set.add(key.split(delimiter).slice(0, -1).join(delimiter));
}

// Sort and return a list of flatten json paths
let output_list = Array.from(output_set).sort();
return output_list;
}

export { flatten };

0 comments on commit 6907b9c

Please sign in to comment.