Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nasa/openmct.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRukmini Bose <rukmini.bose15@gmail.com>2022-05-20 03:54:30 +0300
committerRukmini Bose <rukmini.bose15@gmail.com>2022-05-20 03:54:30 +0300
commita181faff4edb35d1fda93d629f459fd2847bcfb5 (patch)
treea0b540acb346b24f785b262e82115d663c0c26da
parent10c26ac39ab858a0fbce7f708c6664965a89f9cf (diff)
parent6521b888d6f52322bf63cad91a855c6a66994024 (diff)
Address merge conflictoperator-status-merge
-rw-r--r--e2e/playwright-ci.config.js1
-rw-r--r--e2e/playwright-local.config.js1
-rw-r--r--e2e/tests/branding.e2e.spec.js2
-rw-r--r--e2e/tests/plugins/ExportAsJSON/exportAsJson.e2e.spec.js2
-rw-r--r--e2e/tests/plugins/ImportAsJSON/importAsJson.e2e.spec.js2
-rw-r--r--e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js128
-rw-r--r--e2e/tests/plugins/plot/logPlot.e2e.spec.js2
-rw-r--r--e2e/tests/plugins/timeConductor/timeConductor.e2e.spec.js3
-rw-r--r--package.json6
-rw-r--r--src/MCT.js3
-rw-r--r--src/api/forms/components/controls/FileInput.vue18
-rw-r--r--src/plugins/charts/bar/BarGraphCompositionPolicy.js (renamed from src/plugins/charts/BarGraphCompositionPolicy.js)0
-rw-r--r--src/plugins/charts/bar/BarGraphConstants.js (renamed from src/plugins/charts/BarGraphConstants.js)0
-rw-r--r--src/plugins/charts/bar/BarGraphPlot.vue (renamed from src/plugins/charts/BarGraphPlot.vue)0
-rw-r--r--src/plugins/charts/bar/BarGraphView.vue (renamed from src/plugins/charts/BarGraphView.vue)17
-rw-r--r--src/plugins/charts/bar/BarGraphViewProvider.js (renamed from src/plugins/charts/BarGraphViewProvider.js)0
-rw-r--r--src/plugins/charts/bar/inspector/BarGraphInspectorViewProvider.js (renamed from src/plugins/charts/inspector/BarGraphInspectorViewProvider.js)0
-rw-r--r--src/plugins/charts/bar/inspector/BarGraphOptions.vue (renamed from src/plugins/charts/inspector/BarGraphOptions.vue)0
-rw-r--r--src/plugins/charts/bar/inspector/SeriesOptions.vue (renamed from src/plugins/charts/inspector/SeriesOptions.vue)0
-rw-r--r--src/plugins/charts/bar/plugin.js (renamed from src/plugins/charts/plugin.js)0
-rw-r--r--src/plugins/charts/bar/pluginSpec.js (renamed from src/plugins/charts/pluginSpec.js)0
-rw-r--r--src/plugins/charts/scatter/ScatterPlotCompositionPolicy.js57
-rw-r--r--src/plugins/charts/scatter/ScatterPlotForm.vue146
-rw-r--r--src/plugins/charts/scatter/ScatterPlotView.vue346
-rw-r--r--src/plugins/charts/scatter/ScatterPlotViewProvider.js79
-rw-r--r--src/plugins/charts/scatter/ScatterPlotWithUnderlay.vue393
-rw-r--r--src/plugins/charts/scatter/inspector/PlotOptions.vue64
-rw-r--r--src/plugins/charts/scatter/inspector/PlotOptionsBrowse.vue153
-rw-r--r--src/plugins/charts/scatter/inspector/PlotOptionsEdit.vue262
-rw-r--r--src/plugins/charts/scatter/inspector/ScatterPlotInspectorViewProvider.js48
-rw-r--r--src/plugins/charts/scatter/plugin.js127
-rw-r--r--src/plugins/charts/scatter/pluginSpec.js421
-rw-r--r--src/plugins/charts/scatter/scatterPlotConstants.js4
-rw-r--r--src/plugins/imagery/components/ImageryView.vue2
-rw-r--r--src/plugins/imagery/mixins/imageryData.js3
-rw-r--r--src/plugins/notebook/components/NotebookEntry.vue16
-rw-r--r--src/plugins/plan/Plan.vue4
-rw-r--r--src/plugins/plan/util.js2
-rw-r--r--src/plugins/plugins.js9
-rw-r--r--src/plugins/timeline/TimelineViewLayout.vue4
-rw-r--r--src/plugins/timelist/Timelist.vue4
-rwxr-xr-xsrc/styles/_constants.scss59
-rwxr-xr-xsrc/styles/_glyphs.scss5
-rw-r--r--src/styles/_legacy-plots.scss6
-rw-r--r--src/styles/fonts/Open MCT Symbols 16px.json32
-rw-r--r--src/styles/fonts/Open-MCT-Symbols-16px.svg2
-rw-r--r--src/styles/notebook.scss33
-rw-r--r--src/ui/color/ColorSwatch.vue16
-rw-r--r--src/ui/components/ObjectView.vue1
-rw-r--r--src/ui/inspector/inspector.scss6
-rw-r--r--src/ui/layout/mct-tree.vue61
51 files changed, 2445 insertions, 105 deletions
diff --git a/e2e/playwright-ci.config.js b/e2e/playwright-ci.config.js
index 8f241a8cd..92b55f17d 100644
--- a/e2e/playwright-ci.config.js
+++ b/e2e/playwright-ci.config.js
@@ -2,6 +2,7 @@
// playwright.config.js
// @ts-check
+// eslint-disable-next-line no-unused-vars
const { devices } = require('@playwright/test');
/** @type {import('@playwright/test').PlaywrightTestConfig} */
diff --git a/e2e/playwright-local.config.js b/e2e/playwright-local.config.js
index f5b2bdb5c..fe3145f31 100644
--- a/e2e/playwright-local.config.js
+++ b/e2e/playwright-local.config.js
@@ -2,6 +2,7 @@
// playwright.config.js
// @ts-check
+// eslint-disable-next-line no-unused-vars
const { devices } = require('@playwright/test');
/** @type {import('@playwright/test').PlaywrightTestConfig} */
diff --git a/e2e/tests/branding.e2e.spec.js b/e2e/tests/branding.e2e.spec.js
index f86cc23b2..dc6b94be2 100644
--- a/e2e/tests/branding.e2e.spec.js
+++ b/e2e/tests/branding.e2e.spec.js
@@ -58,6 +58,6 @@ test.describe('Branding tests', () => {
page.waitForEvent('popup'),
page.locator('text=click here for third party licensing information').click()
]);
- expect(page2.waitForURL('**\/licenses**')).toBeTruthy();
+ expect(page2.waitForURL('**/licenses**')).toBeTruthy();
});
});
diff --git a/e2e/tests/plugins/ExportAsJSON/exportAsJson.e2e.spec.js b/e2e/tests/plugins/ExportAsJSON/exportAsJson.e2e.spec.js
index f2095b351..6da6c0287 100644
--- a/e2e/tests/plugins/ExportAsJSON/exportAsJson.e2e.spec.js
+++ b/e2e/tests/plugins/ExportAsJSON/exportAsJson.e2e.spec.js
@@ -25,6 +25,8 @@ This test suite is dedicated to tests which verify the basic operations surround
*/
const { test } = require('../../../fixtures.js');
+// FIXME: Remove this eslint exception once tests are implemented
+// eslint-disable-next-line no-unused-vars
const { expect } = require('@playwright/test');
test.describe('ExportAsJSON', () => {
diff --git a/e2e/tests/plugins/ImportAsJSON/importAsJson.e2e.spec.js b/e2e/tests/plugins/ImportAsJSON/importAsJson.e2e.spec.js
index 8b61fdf23..6469e8f15 100644
--- a/e2e/tests/plugins/ImportAsJSON/importAsJson.e2e.spec.js
+++ b/e2e/tests/plugins/ImportAsJSON/importAsJson.e2e.spec.js
@@ -25,6 +25,8 @@ This test suite is dedicated to tests which verify the basic operations surround
*/
const { test } = require('../../../fixtures.js');
+// FIXME: Remove this eslint exception once tests are implemented
+// eslint-disable-next-line no-unused-vars
const { expect } = require('@playwright/test');
test.describe('ExportAsJSON', () => {
diff --git a/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js b/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js
index 8fcafb068..ef31628c6 100644
--- a/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js
+++ b/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js
@@ -225,21 +225,123 @@ test.describe('Example Imagery', () => {
return expect(pausePlayButton).not.toHaveClass(/is-paused/);
});
- //test.fixme('Can use Mouse Wheel to zoom in and out of previous image');
- //test.fixme('Can zoom into the latest image and the real-time/fixed-time imagery will pause');
- //test.fixme('Can zoom into a previous image from thumbstrip in real-time or fixed-time');
- //test.fixme('Clicking on the left arrow should pause the imagery and go to previous image');
- //test.fixme('If the imagery view is in pause mode, it should not be updated when new images come in');
- //test.fixme('If the imagery view is not in pause mode, it should be updated when new images come in');
});
-test.describe('Example Imagery in Display layout', () => {
- test.fixme('Can use Mouse Wheel to zoom in and out of previous image');
- test.fixme('Can use alt+drag to move around image once zoomed in');
- test.fixme('Can zoom into the latest image and the real-time/fixed-time imagery will pause');
- test.fixme('Clicking on the left arrow should pause the imagery and go to previous image');
- test.fixme('If the imagery view is in pause mode, it should not be updated when new images come in');
- test.fixme('If the imagery view is not in pause mode, it should be updated when new images come in');
+// The following test case will cover these scenarios
+// ('Can use Mouse Wheel to zoom in and out of previous image');
+// ('Can use alt+drag to move around image once zoomed in');
+// ('Clicking on the left arrow should pause the imagery and go to previous image');
+// ('If the imagery view is in pause mode, it should not be updated when new images come in');
+// ('If the imagery view is not in pause mode, it should be updated when new images come in');
+const backgroundImageSelector = '.c-imagery__main-image__background-image';
+test('Example Imagery in Display layout', async ({ page }) => {
+ // Go to baseURL
+ await page.goto('/', { waitUntil: 'networkidle' });
+
+ // Click the Create button
+ await page.click('button:has-text("Create")');
+
+ // Click text=Example Imagery
+ await page.click('text=Example Imagery');
+
+ // Clear and set Image load delay (milliseconds)
+ await page.click('input[type="number"]', {clickCount: 3});
+ await page.type('input[type="number"]', "20");
+
+ // Click text=OK
+ await Promise.all([
+ page.waitForNavigation({waitUntil: 'networkidle'}),
+ page.click('text=OK'),
+ //Wait for Save Banner to appear
+ page.waitForSelector('.c-message-banner__message')
+ ]);
+ // Wait until Save Banner is gone
+ await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
+ await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery');
+ const bgImageLocator = await page.locator(backgroundImageSelector);
+ await bgImageLocator.hover();
+
+ // Click previous image button
+ const previousImageButton = await page.locator('.c-nav--prev');
+ await previousImageButton.click();
+
+ // Verify previous image
+ const selectedImage = page.locator('.selected');
+ await expect(selectedImage).toBeVisible();
+
+ // Zoom in
+ const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
+ await bgImageLocator.hover();
+ const deltaYStep = 100; // equivalent to 1x zoom
+ await page.mouse.wheel(0, deltaYStep * 2);
+ const zoomedBoundingBox = await bgImageLocator.boundingBox();
+ const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
+ const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
+
+ // Wait for zoom animation to finish
+ await bgImageLocator.hover();
+ const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
+ expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
+ expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
+
+ // Center the mouse pointer
+ await page.mouse.move(imageCenterX, imageCenterY);
+
+ // Pan Imagery Hints
+ console.log('process.platform is ' + process.platform);
+ const expectedAltText = process.platform === 'linux' ? 'Ctrl+Alt drag to pan' : 'Alt drag to pan';
+ const imageryHintsText = await page.locator('.c-imagery__hints').innerText();
+ expect(expectedAltText).toEqual(imageryHintsText);
+
+ // Click next image button
+ const nextImageButton = await page.locator('.c-nav--next');
+ await nextImageButton.click();
+
+ // Click fixed timespan button
+ await page.locator('.c-button__label >> text=Fixed Timespan').click();
+
+ // Click local clock
+ await page.locator('.icon-clock >> text=Local Clock').click();
+
+ // Zoom in on next image
+ await bgImageLocator.hover();
+ await page.mouse.wheel(0, deltaYStep * 2);
+
+ // Wait for zoom animation to finish
+ await bgImageLocator.hover();
+ const imageNextMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
+ expect(imageNextMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
+ expect(imageNextMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
+
+ // Click previous image button
+ await previousImageButton.click();
+
+ // Verify previous image
+ await expect(selectedImage).toBeVisible();
+
+ // Wait 20ms to verify no new image has come in
+ await page.waitForTimeout(21);
+
+ //Get background-image url from background-image css prop
+ const backgroundImage = await page.locator('.c-imagery__main-image__background-image');
+ let backgroundImageUrl = await backgroundImage.evaluate((el) => {
+ return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
+ });
+ let backgroundImageUrl1 = backgroundImageUrl.slice(1, -1); //forgive me, padre
+ console.log('backgroundImageUrl1 ' + backgroundImageUrl1);
+
+ // sleep 21ms
+ await page.waitForTimeout(21);
+
+ // Verify next image has updated
+ let backgroundImageUrlNext = await backgroundImage.evaluate((el) => {
+ return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
+ });
+ let backgroundImageUrl2 = backgroundImageUrlNext.slice(1, -1); //forgive me, padre
+ console.log('backgroundImageUrl2 ' + backgroundImageUrl2);
+
+ // Expect backgroundImageUrl2 to be greater then backgroundImageUrl1
+ expect(backgroundImageUrl2 >= backgroundImageUrl1);
});
test.describe('Example Imagery in Flexible layout', () => {
diff --git a/e2e/tests/plugins/plot/logPlot.e2e.spec.js b/e2e/tests/plugins/plot/logPlot.e2e.spec.js
index 1e040c402..2dfd704d4 100644
--- a/e2e/tests/plugins/plot/logPlot.e2e.spec.js
+++ b/e2e/tests/plugins/plot/logPlot.e2e.spec.js
@@ -242,6 +242,8 @@ async function saveOverlayPlot(page) {
/**
* @param {import('@playwright/test').Page} page
*/
+// FIXME: Remove this eslint exception once implemented
+// eslint-disable-next-line no-unused-vars
async function testLogPlotPixels(page) {
const pixelsMatch = await page.evaluate(async () => {
// TODO get canvas pixels at a few locations to make sure they're the correct color, to test that the plot comes out as expected.
diff --git a/e2e/tests/plugins/timeConductor/timeConductor.e2e.spec.js b/e2e/tests/plugins/timeConductor/timeConductor.e2e.spec.js
index 7d68c7409..8ca1285aa 100644
--- a/e2e/tests/plugins/timeConductor/timeConductor.e2e.spec.js
+++ b/e2e/tests/plugins/timeConductor/timeConductor.e2e.spec.js
@@ -76,9 +76,6 @@ test.describe('Time conductor input fields real-time mode', () => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
- // Set realtime "local clock" mode offsets
- const timeInputs = page.locator('input.c-input--datetime');
-
// Click fixed timespan button
await page.locator('.c-button__label >> text=Fixed Timespan').click();
diff --git a/package.json b/package.json
index 74a9b21a1..0497345d3 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,7 @@
"git-rev-sync": "3.0.2",
"html2canvas": "1.4.1",
"imports-loader": "0.8.0",
- "jasmine-core": "4.0.1",
+ "jasmine-core": "4.1.1",
"jsdoc": "3.5.5",
"karma": "6.3.18",
"karma-chrome-launcher": "3.1.1",
@@ -82,8 +82,8 @@
"clean": "rm -rf ./dist ./node_modules ./package-lock.json",
"clean-test-lint": "npm run clean; npm install; npm run test; npm run lint",
"start": "node app.js",
- "lint": "eslint example src --ext .js,.vue openmct.js",
- "lint:fix": "eslint example src --ext .js,.vue openmct.js --fix",
+ "lint": "eslint example src e2e --ext .js,.vue openmct.js",
+ "lint:fix": "eslint example src e2e --ext .js,.vue openmct.js --fix",
"build:prod": "cross-env webpack --config webpack.prod.js",
"build:dev": "webpack --config webpack.dev.js",
"build:coverage": "webpack --config webpack.coverage.js",
diff --git a/src/MCT.js b/src/MCT.js
index 00624460d..3ea51a1d4 100644
--- a/src/MCT.js
+++ b/src/MCT.js
@@ -242,7 +242,8 @@ define([
// Plugins that are installed by default
this.install(this.plugins.Plot());
- this.install(this.plugins.Chart());
+ this.install(this.plugins.ScatterPlot());
+ this.install(this.plugins.BarChart());
this.install(this.plugins.TelemetryTable.default());
this.install(PreviewPlugin.default());
this.install(LicensesPlugin.default());
diff --git a/src/api/forms/components/controls/FileInput.vue b/src/api/forms/components/controls/FileInput.vue
index f1b10f8f3..ef9a6a3b8 100644
--- a/src/api/forms/components/controls/FileInput.vue
+++ b/src/api/forms/components/controls/FileInput.vue
@@ -40,6 +40,12 @@
>
{{ name }}
</button>
+ <button
+ v-if="removable"
+ class="c-button icon-trash"
+ title="Remove file"
+ @click="removeFile"
+ ></button>
</span>
</span>
</template>
@@ -63,6 +69,9 @@ export default {
const fileInfo = this.fileInfo || this.model.value;
return fileInfo && fileInfo.name || this.model.text;
+ },
+ removable() {
+ return (this.fileInfo || this.model.value) && this.model.removable;
}
},
mounted() {
@@ -97,6 +106,15 @@ export default {
},
selectFile() {
this.$refs.fileInput.click();
+ },
+ removeFile() {
+ this.model.value = undefined;
+ this.fileInfo = undefined;
+ const data = {
+ model: this.model,
+ value: undefined
+ };
+ this.$emit('onChange', data);
}
}
};
diff --git a/src/plugins/charts/BarGraphCompositionPolicy.js b/src/plugins/charts/bar/BarGraphCompositionPolicy.js
index 7da0a7fc7..7da0a7fc7 100644
--- a/src/plugins/charts/BarGraphCompositionPolicy.js
+++ b/src/plugins/charts/bar/BarGraphCompositionPolicy.js
diff --git a/src/plugins/charts/BarGraphConstants.js b/src/plugins/charts/bar/BarGraphConstants.js
index bdd88a7ce..bdd88a7ce 100644
--- a/src/plugins/charts/BarGraphConstants.js
+++ b/src/plugins/charts/bar/BarGraphConstants.js
diff --git a/src/plugins/charts/BarGraphPlot.vue b/src/plugins/charts/bar/BarGraphPlot.vue
index ad5894afc..ad5894afc 100644
--- a/src/plugins/charts/BarGraphPlot.vue
+++ b/src/plugins/charts/bar/BarGraphPlot.vue
diff --git a/src/plugins/charts/BarGraphView.vue b/src/plugins/charts/bar/BarGraphView.vue
index dbb07b4a7..02ec8f205 100644
--- a/src/plugins/charts/BarGraphView.vue
+++ b/src/plugins/charts/bar/BarGraphView.vue
@@ -40,6 +40,14 @@ export default {
BarGraph
},
inject: ['openmct', 'domainObject', 'path'],
+ props: {
+ options: {
+ type: Object,
+ default() {
+ return {};
+ }
+ }
+ },
data() {
this.telemetryObjects = {};
this.telemetryObjectFormats = {};
@@ -247,7 +255,7 @@ export default {
}
});
- const trace = {
+ let trace = {
key,
name: telemetryObject.name,
x: xValues,
@@ -255,13 +263,18 @@ export default {
text: yValues.map(String),
xAxisMetadata: axisMetadata.xAxisMetadata,
yAxisMetadata: axisMetadata.yAxisMetadata,
- type: 'bar',
+ type: this.options.type ? this.options.type : 'bar',
marker: {
color: this.domainObject.configuration.barStyles.series[key].color
},
hoverinfo: 'skip'
};
+ if (this.options.type) {
+ trace.mode = 'markers';
+ trace.hoverinfo = 'x+y';
+ }
+
this.addTrace(trace, key);
},
isDataInTimeRange(datum, key) {
diff --git a/src/plugins/charts/BarGraphViewProvider.js b/src/plugins/charts/bar/BarGraphViewProvider.js
index 87ccf8e6e..87ccf8e6e 100644
--- a/src/plugins/charts/BarGraphViewProvider.js
+++ b/src/plugins/charts/bar/BarGraphViewProvider.js
diff --git a/src/plugins/charts/inspector/BarGraphInspectorViewProvider.js b/src/plugins/charts/bar/inspector/BarGraphInspectorViewProvider.js
index 0028ea40d..0028ea40d 100644
--- a/src/plugins/charts/inspector/BarGraphInspectorViewProvider.js
+++ b/src/plugins/charts/bar/inspector/BarGraphInspectorViewProvider.js
diff --git a/src/plugins/charts/inspector/BarGraphOptions.vue b/src/plugins/charts/bar/inspector/BarGraphOptions.vue
index a17fbc28b..a17fbc28b 100644
--- a/src/plugins/charts/inspector/BarGraphOptions.vue
+++ b/src/plugins/charts/bar/inspector/BarGraphOptions.vue
diff --git a/src/plugins/charts/inspector/SeriesOptions.vue b/src/plugins/charts/bar/inspector/SeriesOptions.vue
index 29c1f9cc3..29c1f9cc3 100644
--- a/src/plugins/charts/inspector/SeriesOptions.vue
+++ b/src/plugins/charts/bar/inspector/SeriesOptions.vue
diff --git a/src/plugins/charts/plugin.js b/src/plugins/charts/bar/plugin.js
index 7a15d1cb6..7a15d1cb6 100644
--- a/src/plugins/charts/plugin.js
+++ b/src/plugins/charts/bar/plugin.js
diff --git a/src/plugins/charts/pluginSpec.js b/src/plugins/charts/bar/pluginSpec.js
index 56e3577e2..56e3577e2 100644
--- a/src/plugins/charts/pluginSpec.js
+++ b/src/plugins/charts/bar/pluginSpec.js
diff --git a/src/plugins/charts/scatter/ScatterPlotCompositionPolicy.js b/src/plugins/charts/scatter/ScatterPlotCompositionPolicy.js
new file mode 100644
index 000000000..710fb3402
--- /dev/null
+++ b/src/plugins/charts/scatter/ScatterPlotCompositionPolicy.js
@@ -0,0 +1,57 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+import { SCATTER_PLOT_KEY } from './scatterPlotConstants';
+
+export default function ScatterPlotCompositionPolicy(openmct) {
+ function hasRange(metadata) {
+ const rangeValues = metadata.valuesForHints(['range']).map((value) => {
+ return value.source;
+ });
+
+ const uniqueRangeValues = new Set(rangeValues);
+
+ return uniqueRangeValues && uniqueRangeValues.size > 1;
+ }
+
+ function hasScatterPlotTelemetry(domainObject) {
+ if (!openmct.telemetry.isTelemetryObject(domainObject)) {
+ return false;
+ }
+
+ let metadata = openmct.telemetry.getMetadata(domainObject);
+
+ return metadata.values().length > 0 && hasRange(metadata);
+ }
+
+ return {
+ allow: function (parent, child) {
+ if (parent.type === SCATTER_PLOT_KEY) {
+ if ((child.type === 'conditionSet') || (!hasScatterPlotTelemetry(child))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ };
+}
diff --git a/src/plugins/charts/scatter/ScatterPlotForm.vue b/src/plugins/charts/scatter/ScatterPlotForm.vue
new file mode 100644
index 000000000..adef666dc
--- /dev/null
+++ b/src/plugins/charts/scatter/ScatterPlotForm.vue
@@ -0,0 +1,146 @@
+/*****************************************************************************
+* Open MCT, Copyright (c) 2014-2022, United States Government
+* as represented by the Administrator of the National Aeronautics and Space
+* Administration. All rights reserved.
+*
+* Open MCT is licensed under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+* http://www.apache.org/licenses/LICENSE-2.0.
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+* License for the specific language governing permissions and limitations
+* under the License.
+*
+* Open MCT includes source code licensed under additional open source
+* licenses. See the Open Source Licenses file (LICENSES.md) included with
+* this source code distribution or the Licensing information page available
+* at runtime from the About dialog for additional information.
+*****************************************************************************/
+
+<template>
+<span class="form-control">
+ <span
+ class="field control"
+ :class="model.cssClass"
+ >
+ <div
+ class="c-form--sub-grid"
+ >
+ <div class="c-form__row">
+ <span
+ class="req-indicator"
+ :class="{'req': isRequired}"
+ >
+ </span>
+ <label>Minimum X axis value</label>
+ <input
+ ref="domainMin"
+ v-model.number="domainMin"
+ data-field-name="domainMin"
+ type="number"
+ @input="onChange('domainMin')"
+ >
+ </div>
+
+ <div class="c-form__row">
+ <span
+ class="req-indicator"
+ :class="{'req': isRequired}"
+ >
+ </span>
+ <label>Maximum X axis value</label>
+ <input
+ ref="domainMax"
+ v-model.number="domainMax"
+ data-field-name="domainMax"
+ type="number"
+ @input="onChange('domainMax')"
+ >
+ </div>
+
+ <div class="c-form__row">
+ <span
+ class="req-indicator"
+ :class="{'req': isRequired}"
+ >
+ </span>
+ <label>Minimum Y axis value</label>
+ <input
+ ref="rangeMin"
+ v-model.number="rangeMin"
+ data-field-name="rangeMin"
+ type="number"
+ @input="onChange('rangeMin')"
+ >
+ </div>
+
+ <div class="c-form__row">
+ <span
+ class="req-indicator"
+ :class="{'req': isRequired}"
+ >
+ </span>
+ <label>Maximum Y axis value</label>
+ <input
+ ref="rangeMax"
+ v-model.number="rangeMax"
+ data-field-name="rangeMax"
+ type="number"
+ @input="onChange('rangeMax')"
+ >
+ </div>
+ </div>
+ </span>
+</span>
+</template>
+
+<script>
+
+export default {
+ props: {
+ model: {
+ type: Object,
+ required: true
+ }
+ },
+ data() {
+ return {
+ rangeMax: this.model.value.rangeMax,
+ rangeMin: this.model.value.rangeMin,
+ domainMax: this.model.value.domainMax,
+ domainMin: this.model.value.domainMin
+ };
+ },
+ computed: {
+ isRequired() {
+ return [this.rangeMax, this.rangeMin, this.domainMin, this.domainMax].some(value => value !== undefined && value !== '');
+ }
+ },
+ methods: {
+ onChange(property) {
+ if (this[property] === '') {
+ this[property] = undefined;
+ }
+
+ const data = {
+ model: this.model,
+ value: {
+ rangeMax: this.rangeMax,
+ rangeMin: this.rangeMin,
+ domainMax: this.domainMax,
+ domainMin: this.domainMin
+ }
+ };
+
+ if (property) {
+ this.model.validate(data);
+ }
+
+ this.$emit('onChange', data);
+ }
+ }
+};
+</script>
diff --git a/src/plugins/charts/scatter/ScatterPlotView.vue b/src/plugins/charts/scatter/ScatterPlotView.vue
new file mode 100644
index 000000000..f6a69228e
--- /dev/null
+++ b/src/plugins/charts/scatter/ScatterPlotView.vue
@@ -0,0 +1,346 @@
+<!--
+ Open MCT, Copyright (c) 2014-2021, United States Government
+ as represented by the Administrator of the National Aeronautics and Space
+ Administration. All rights reserved.
+
+ Open MCT is licensed under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations
+ under the License.
+
+ Open MCT includes source code licensed under additional open source
+ licenses. See the Open Source Licenses file (LICENSES.md) included with
+ this source code distribution or the Licensing information page available
+ at runtime from the About dialog for additional information.
+-->
+
+<template>
+<ScatterPlotWithUnderlay
+ class="c-plot c-scatter-chart-view"
+ :data="trace"
+ :plot-axis-title="plotAxisTitle"
+ @subscribe="subscribeToAll"
+ @unsubscribe="removeAllSubscriptions"
+/>
+</template>
+
+<script>
+import ScatterPlotWithUnderlay from './ScatterPlotWithUnderlay.vue';
+import _ from 'lodash';
+
+export default {
+ components: {
+ ScatterPlotWithUnderlay
+ },
+ inject: ['openmct', 'domainObject', 'path'],
+ data() {
+ this.telemetryObjects = {};
+ this.telemetryObjectFormats = {};
+ this.valuesByTimestamp = {};
+ this.subscriptions = [];
+
+ return {
+ trace: []
+ };
+ },
+ computed: {
+ plotAxisTitle() {
+ const { xAxisMetadata = {}, yAxisMetadata = {} } = this.trace[0] || {};
+ const xAxisUnit = xAxisMetadata.units ? `(${xAxisMetadata.units})` : '';
+ const yAxisUnit = yAxisMetadata.units ? `(${yAxisMetadata.units})` : '';
+
+ return {
+ xAxisTitle: `${xAxisMetadata.name || ''} ${xAxisUnit}`,
+ yAxisTitle: `${yAxisMetadata.name || ''} ${yAxisUnit}`
+ };
+ }
+ },
+ mounted() {
+ this.setTimeContext();
+ this.loadComposition();
+ this.reloadTelemetry = this.reloadTelemetry.bind(this);
+ this.reloadTelemetry = _.debounce(this.reloadTelemetry, 500);
+ this.unobserve = this.openmct.objects.observe(this.domainObject, 'configuration.axes', this.reloadTelemetry);
+ this.unobserveUnderlayRanges = this.openmct.objects.observe(this.domainObject, 'configuration.ranges', this.reloadTelemetry);
+ },
+ beforeDestroy() {
+ this.stopFollowingTimeContext();
+
+ if (!this.composition) {
+ return;
+ }
+
+ this.removeAllSubscriptions();
+
+ this.composition.off('add', this.addToComposition);
+ this.composition.off('remove', this.removeTelemetryObject);
+ if (this.unobserve) {
+ this.unobserve();
+ }
+
+ if (this.unobserveUnderlayRanges) {
+ this.unobserveUnderlayRanges();
+ }
+ },
+ methods: {
+ setTimeContext() {
+ this.stopFollowingTimeContext();
+
+ this.timeContext = this.openmct.time.getContextForView(this.path);
+ this.followTimeContext();
+
+ },
+ followTimeContext() {
+ this.timeContext.on('bounds', this.reloadTelemetry);
+ },
+ stopFollowingTimeContext() {
+ if (this.timeContext) {
+ this.timeContext.off('bounds', this.reloadTelemetry);
+ }
+ },
+ addToComposition(telemetryObject) {
+ if (Object.values(this.telemetryObjects).length > 0) {
+ this.confirmRemoval(telemetryObject);
+ } else {
+ this.addTelemetryObject(telemetryObject);
+ }
+ },
+ removeFromComposition(telemetryObject) {
+ let composition = this.domainObject.composition.filter(id =>
+ !this.openmct.objects.areIdsEqual(id, telemetryObject.identifier)
+ );
+
+ this.openmct.objects.mutate(this.domainObject, 'composition', composition);
+ },
+ addTelemetryObject(telemetryObject) {
+ // grab information we need from the added telmetry object
+ const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
+ this.telemetryObjects[key] = telemetryObject;
+ const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
+ this.telemetryObjectFormats[key] = this.openmct.telemetry.getFormatMap(metadata);
+ this.getDataForTelemetry(key);
+ },
+ confirmRemoval(telemetryObject) {
+ const dialog = this.openmct.overlays.dialog({
+ iconClass: 'alert',
+ message: 'This action will replace the current telemetry source. Do you want to continue?',
+ buttons: [
+ {
+ label: 'Ok',
+ emphasis: true,
+ callback: () => {
+ const oldTelemetryObject = Object.values(this.telemetryObjects)[0];
+ this.removeFromComposition(oldTelemetryObject);
+ this.removeTelemetryObject(oldTelemetryObject.identifier);
+ this.valuesByTimestamp = {};
+ this.addTelemetryObject(telemetryObject);
+ dialog.dismiss();
+ }
+ },
+ {
+ label: 'Cancel',
+ callback: () => {
+ this.removeFromComposition(telemetryObject);
+ dialog.dismiss();
+ }
+ }
+ ]
+ });
+ },
+ getTelemetryProcessor(keyString) {
+ return (telemetry) => {
+ //Check that telemetry object has not been removed since telemetry was requested.
+ const telemetryObject = this.telemetryObjects[keyString];
+ if (!telemetryObject) {
+ return;
+ }
+
+ telemetry.forEach(datum => {
+ this.addDataToGraph(telemetryObject, datum);
+ });
+ this.updateTrace(telemetryObject);
+ };
+ },
+ getAxisMetadata(telemetryObject) {
+ const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
+ if (!metadata) {
+ return {};
+ }
+
+ return metadata.valuesForHints(['range']);
+ },
+ loadComposition() {
+ this.composition = this.openmct.composition.get(this.domainObject);
+ this.composition.on('add', this.addToComposition);
+ this.composition.on('remove', this.removeTelemetryObject);
+ this.composition.load();
+ },
+ reloadTelemetry() {
+ this.valuesByTimestamp = {};
+
+ Object.keys(this.telemetryObjects).forEach(key => {
+ this.getDataForTelemetry(key);
+ });
+ },
+ getDataForTelemetry(key) {
+ const telemetryObject = this.telemetryObjects[key];
+ if (!telemetryObject) {
+ return;
+ }
+
+ const telemetryProcessor = this.getTelemetryProcessor(key);
+ const options = this.getOptions();
+ this.openmct.telemetry.request(telemetryObject, options).then(telemetryProcessor);
+ this.subscribeToObject(telemetryObject);
+ },
+ removeTelemetryObject(identifier) {
+ const key = this.openmct.objects.makeKeyString(identifier);
+ if (this.telemetryObjects[key]) {
+ delete this.telemetryObjects[key];
+ }
+
+ if (this.telemetryObjectFormats && this.telemetryObjectFormats[key]) {
+ delete this.telemetryObjectFormats[key];
+ }
+
+ this.removeSubscription(key);
+ },
+ addDataToGraph(telemetryObject, data) {
+ const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
+
+ if (data.message) {
+ this.openmct.notifications.alert(data.message);
+ }
+
+ if (!this.domainObject.configuration.axes.xKey || !this.domainObject.configuration.axes.yKey) {
+ return;
+ }
+
+ const timestamp = this.getTimestampForDatum(data, key, telemetryObject);
+ let valueForTimestamp = this.valuesByTimestamp[timestamp] || {};
+
+ //populate x values
+ let metadataKey = this.domainObject.configuration.axes.xKey;
+ if (data[metadataKey] !== undefined) {
+ valueForTimestamp.x = this.format(key, metadataKey, data);
+ }
+
+ metadataKey = this.domainObject.configuration.axes.yKey;
+ if (data[metadataKey] !== undefined) {
+ valueForTimestamp.y = this.format(key, metadataKey, data);
+ }
+
+ this.valuesByTimestamp[timestamp] = valueForTimestamp;
+ },
+ updateTrace(telemetryObject) {
+ const xAndyValues = Object.values(this.valuesByTimestamp);
+ const xValues = xAndyValues.map(value => value.x);
+ const yValues = xAndyValues.map(value => value.y);
+ const axisMetadata = this.getAxisMetadata(telemetryObject);
+ const xAxisMetadata = axisMetadata.find(metadata => metadata.source === this.domainObject.configuration.axes.xKey);
+ let yAxisMetadata = {};
+ if (this.domainObject.configuration.axes.yKey) {
+ yAxisMetadata = axisMetadata.find(metadata => metadata.source === this.domainObject.configuration.axes.yKey);
+ }
+
+ let trace = {
+ key: this.openmct.objects.makeKeyString(this.domainObject.identifier),
+ name: this.domainObject.name,
+ x: xValues,
+ y: yValues,
+ text: yValues.map(String),
+ xAxisMetadata: xAxisMetadata,
+ yAxisMetadata: yAxisMetadata,
+ type: 'scatter',
+ mode: 'markers',
+ marker: {
+ color: this.domainObject.configuration.styles.color
+ },
+ hoverinfo: 'x+y'
+ };
+
+ if (this.domainObject.configuration.ranges !== undefined && this.domainObject.configuration.ranges.domainMin !== undefined && this.domainObject.configuration.ranges.domainMax !== undefined) {
+ trace.xaxis = {
+ min: this.domainObject.configuration.ranges.domainMin,
+ max: this.domainObject.configuration.ranges.domainMax
+ };
+ }
+
+ if (this.domainObject.configuration.ranges !== undefined && this.domainObject.configuration.ranges.rangeMin !== undefined && this.domainObject.configuration.ranges.rangeMax !== undefined) {
+ trace.yaxis = {
+ min: this.domainObject.configuration.ranges.rangeMin,
+ max: this.domainObject.configuration.ranges.rangeMax
+ };
+ }
+
+ this.trace = [trace];
+ },
+ getTimestampForDatum(datum, key, telemetryObject) {
+ const timeSystemKey = this.timeContext.timeSystem().key;
+ const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
+ let metadataValue = metadata.value(timeSystemKey) || { format: timeSystemKey };
+
+ return this.parse(key, metadataValue.source, datum);
+ },
+ format(telemetryObjectKey, metadataKey, data) {
+ const formats = this.telemetryObjectFormats[telemetryObjectKey];
+
+ return formats[metadataKey].format(data);
+ },
+ parse(telemetryObjectKey, metadataKey, datum) {
+ if (!datum) {
+ return;
+ }
+
+ const formats = this.telemetryObjectFormats[telemetryObjectKey];
+
+ return formats[metadataKey].parse(datum);
+ },
+ getOptions() {
+ const { start, end } = this.timeContext.bounds();
+
+ return {
+ end,
+ start
+ };
+ },
+ subscribeToObject(telemetryObject) {
+ const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
+
+ this.removeSubscription(key);
+
+ const options = this.getOptions();
+ const unsubscribe = this.openmct.telemetry.subscribe(telemetryObject,
+ data => this.addDataToGraph(telemetryObject, data)
+ , options);
+
+ this.subscriptions.push({
+ key,
+ unsubscribe
+ });
+ },
+ subscribeToAll() {
+ const telemetryObjects = Object.values(this.telemetryObjects);
+ telemetryObjects.forEach(this.subscribeToObject);
+ },
+ removeAllSubscriptions() {
+ this.subscriptions.forEach(subscription => subscription.unsubscribe());
+ this.subscriptions = [];
+ },
+ removeSubscription(key) {
+ const found = this.subscriptions.findIndex(subscription => subscription.key === key);
+ if (found > -1) {
+ this.subscriptions[found].unsubscribe();
+ this.subscriptions.splice(found, 1);
+ }
+ }
+ }
+};
+
+</script>
diff --git a/src/plugins/charts/scatter/ScatterPlotViewProvider.js b/src/plugins/charts/scatter/ScatterPlotViewProvider.js
new file mode 100644
index 000000000..338d2eb3e
--- /dev/null
+++ b/src/plugins/charts/scatter/ScatterPlotViewProvider.js
@@ -0,0 +1,79 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2021, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+import ScatterPlotView from './ScatterPlotView.vue';
+import { SCATTER_PLOT_KEY, SCATTER_PLOT_VIEW, TIME_STRIP_KEY } from './scatterPlotConstants.js';
+import Vue from 'vue';
+
+export default function ScatterPlotViewProvider(openmct) {
+ function isCompactView(objectPath) {
+ let isChildOfTimeStrip = objectPath.find(object => object.type === TIME_STRIP_KEY);
+
+ return isChildOfTimeStrip && !openmct.router.isNavigatedObject(objectPath);
+ }
+
+ return {
+ key: SCATTER_PLOT_VIEW,
+ name: 'Scatter Plot',
+ cssClass: 'icon-telemetry',
+ canView(domainObject, objectPath) {
+ return domainObject && domainObject.type === SCATTER_PLOT_KEY;
+ },
+
+ canEdit(domainObject, objectPath) {
+ return domainObject && domainObject.type === SCATTER_PLOT_KEY;
+ },
+
+ view: function (domainObject, objectPath) {
+ let component;
+
+ return {
+ show: function (element) {
+ let isCompact = isCompactView(objectPath);
+ component = new Vue({
+ el: element,
+ components: {
+ ScatterPlotView
+ },
+ provide: {
+ openmct,
+ domainObject,
+ path: objectPath
+ },
+ data() {
+ return {
+ options: {
+ compact: isCompact
+ }
+ };
+ },
+ template: '<scatter-plot-view :options="options"></scatter-plot-view>'
+ });
+ },
+ destroy: function () {
+ component.$destroy();
+ component = undefined;
+ }
+ };
+ }
+ };
+}
diff --git a/src/plugins/charts/scatter/ScatterPlotWithUnderlay.vue b/src/plugins/charts/scatter/ScatterPlotWithUnderlay.vue
new file mode 100644
index 000000000..796a252ac
--- /dev/null
+++ b/src/plugins/charts/scatter/ScatterPlotWithUnderlay.vue
@@ -0,0 +1,393 @@
+<template>
+<div
+ ref="plotWrapper"
+ class="has-local-controls"
+ :class="{ 's-unsynced' : isZoomed }"
+>
+ <div
+ v-if="isZoomed"
+ class="l-state-indicators"
+ >
+ <span
+ class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
+ title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."
+ ></span>
+ </div>
+ <div
+ ref="plot"
+ class="c-scatter-chart"
+ ></div>
+ <div
+ ref="localControl"
+ class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover"
+ >
+ <button
+ v-if="data.length"
+ class="c-button icon-reset"
+ :disabled="!isZoomed"
+ title="Reset pan/zoom"
+ @click="reset()"
+ >
+ </button>
+ </div>
+</div>
+</template>
+<script>
+import Plotly from 'plotly-basic';
+
+const MULTI_AXES_X_PADDING_PERCENT = {
+ LEFT: 8,
+ RIGHT: 94
+};
+
+import { getValidatedData } from "@/plugins/plan/util";
+
+const PATH_COLORS = ['blue', 'red', 'green'];
+const MARKER_COLOR = 'white';
+
+export default {
+ inject: ['openmct', 'domainObject'],
+ props: {
+ data: {
+ type: Array,
+ default() {
+ return [];
+ }
+ },
+ plotAxisTitle: {
+ type: Object,
+ default() {
+ return {};
+ }
+ }
+ },
+ data() {
+ return {
+ isZoomed: false,
+ yAxisRange: {
+ min: '',
+ max: ''
+ },
+ xAxisRange: {
+ min: '',
+ max: ''
+ }
+ };
+ },
+ watch: {
+ data: {
+ immediate: false,
+ handler: 'updateData'
+ }
+ },
+ mounted() {
+ this.getUnderlayPlotData();
+
+ Plotly.newPlot(this.$refs.plot, Array.from(this.data.concat(this.getShapes(this.shapesData))), this.getLayout(), {
+ responsive: true,
+ displayModeBar: false
+ });
+ this.registerListeners();
+
+ this.$refs.plot.on('plotly_relayout', this.zoom);
+ },
+ beforeDestroy() {
+ if (this.$refs.plot && this.$refs.plot.off) {
+ this.$refs.plot.off('plotly_relayout', this.zoom);
+ }
+
+ if (this.plotResizeObserver) {
+ this.plotResizeObserver.unobserve(this.$refs.plotWrapper);
+ clearTimeout(this.resizeTimer);
+ }
+
+ if (this.unlistenUnderlay) {
+ this.unlistenUnderlay();
+ }
+
+ if (this.unlistenUnderlayRanges) {
+ this.unlistenUnderlayRanges();
+ }
+
+ if (this.unobserveColorChanges) {
+ this.unobserveColorChanges();
+ }
+ },
+ methods: {
+ getUnderlayPlotData() {
+ if (this.domainObject.selectFile) {
+ this.shapesData = getValidatedData(this.domainObject);
+ } else {
+ this.shapesData = [];
+ }
+ },
+ observeForUnderlayPlotChanges() {
+ this.getUnderlayPlotData();
+ this.updateData();
+ },
+ getAxisMinMax() {
+ if (!this.data.length) {
+ return;
+ }
+
+ // For now, use x and y axes min, max values only if an underlay is available
+ if (this.shapesData.length && this.data[0].xaxis) {
+ this.xAxisRange = this.data[0].xaxis;
+ }
+
+ if (this.shapesData.length && this.data[0].yaxis) {
+ this.yAxisRange = this.data[0].yaxis;
+ }
+ },
+ getLayout() {
+ this.getAxisMinMax();
+
+ const yAxesMeta = this.getYAxisMeta();
+ const primaryYaxis = this.getYaxisLayout(yAxesMeta['1']);
+ const xAxisDomain = this.getXAxisDomain(yAxesMeta);
+
+ const shapes = this.shapesData.map((shapeData, index) => {
+ if (!shapeData.x || !shapeData.y
+ || !shapeData.x.length || !shapeData.y.length
+ || shapeData.x.length !== shapeData.y.length) {
+ return "";
+ }
+
+ let path = `M ${shapeData.x[0]},${shapeData.y[0]}`;
+ shapeData.x.forEach((point, shapeIndex) => {
+ if (shapeIndex > 0) {
+ path = `${path} L${point},${shapeData.y[shapeIndex]}`;
+ }
+ });
+
+ return {
+ path,
+ type: 'path',
+ line: {
+ color: PATH_COLORS[index]
+ },
+ opacity: 0.5
+ };
+ });
+
+ return {
+ autosize: true,
+ showlegend: false,
+ textposition: 'auto',
+ font: {
+ family: 'Helvetica Neue, Helvetica, Arial, sans-serif',
+ size: '12px',
+ color: '#666'
+ },
+ xaxis: {
+ domain: xAxisDomain,
+ range: [this.xAxisRange.min, this.xAxisRange.max],
+ title: this.plotAxisTitle.xAxisTitle,
+ automargin: true
+ },
+ yaxis: primaryYaxis,
+ margin: {
+ l: 5,
+ r: 5,
+ t: 5,
+ b: 0
+ },
+ paper_bgcolor: 'transparent',
+ plot_bgcolor: 'transparent',
+ shapes,
+ layer: 'below'
+ };
+ },
+ getYAxisMeta() {
+ const yAxisMeta = {};
+
+ this.data.forEach(datum => {
+ const yAxisMetadata = datum.yAxisMetadata;
+ const range = '1';
+ const side = 'left';
+ const name = yAxisMetadata.name;
+ const unit = yAxisMetadata.units;
+
+ yAxisMeta[range] = {
+ range,
+ side,
+ name,
+ unit
+ };
+ });
+
+ return yAxisMeta;
+ },
+ getXAxisDomain(yAxisMeta) {
+ let leftPaddingPerc = 0;
+ let rightPaddingPerc = 100;
+ let rightSide = yAxisMeta && Object.values(yAxisMeta).filter((axisMeta => axisMeta.side === 'right'));
+ let leftSide = yAxisMeta && Object.values(yAxisMeta).filter((axisMeta => axisMeta.side === 'left'));
+ if (yAxisMeta && rightSide.length > 1) {
+ rightPaddingPerc = MULTI_AXES_X_PADDING_PERCENT.RIGHT;
+ }
+
+ if (yAxisMeta && leftSide.length > 1) {
+ leftPaddingPerc = MULTI_AXES_X_PADDING_PERCENT.LEFT;
+ }
+
+ return [leftPaddingPerc / 100, rightPaddingPerc / 100];
+ },
+ getYaxisLayout(yAxisMeta) {
+ if (!yAxisMeta) {
+ return {};
+ }
+
+ const { name, range, side = 'left', unit } = yAxisMeta;
+ const title = `${name} ${unit ? '(' + unit + ')' : ''}`;
+ const yaxis = {
+ automargin: true,
+ title
+ };
+
+ let yRange = this.yAxisRange;
+ if (range === '1') {
+ yaxis.range = [yRange.min, yRange.max];
+
+ return yaxis;
+ }
+
+ yaxis.range = [yRange.min, yRange.max];
+ yaxis.anchor = side.toLowerCase() === 'left'
+ ? 'free'
+ : 'x';
+ yaxis.showline = side.toLowerCase() === 'left';
+ yaxis.side = side.toLowerCase();
+ yaxis.overlaying = 'y';
+ yaxis.position = 0.01;
+
+ return yaxis;
+ },
+ registerListeners() {
+ this.unobserveColorChanges = this.openmct.objects.observe(this.domainObject, 'configuration.styles.color', this.updateColors);
+ this.unlistenUnderlay = this.openmct.objects.observe(this.domainObject, 'selectFile', this.observeForUnderlayPlotChanges);
+ this.unlistenUnderlayRanges = this.openmct.objects.observe(this.domainObject, 'configuration.ranges', this.updateData);
+ this.resizeTimer = false;
+ if (window.ResizeObserver) {
+ this.plotResizeObserver = new ResizeObserver(() => {
+ // debounce and trigger window resize so that plotly can resize the plot
+ clearTimeout(this.resizeTimer);
+ this.resizeTimer = setTimeout(() => {
+ window.dispatchEvent(new Event('resize'));
+ }, 250);
+ });
+ this.plotResizeObserver.observe(this.$refs.plotWrapper);
+ }
+ },
+ updateColors() {
+ const colors = [];
+ const indices = [];
+ this.data.forEach((item, index) => {
+ const colorExists = this.domainObject.configuration.styles.color;
+ indices.push(index);
+ if (colorExists) {
+ colors.push(this.domainObject.configuration.styles.color);
+ } else {
+ colors.push(item.marker.color);
+ }
+ });
+ const plotUpdate = {
+ 'marker.color': colors
+ };
+
+ Plotly.restyle(this.$refs.plot, plotUpdate, indices);
+ },
+ reset() {
+ this.isZoomed = false;
+
+ this.updatePlot();
+ this.$emit('subscribe');
+ },
+ updateData() {
+ this.updatePlot();
+ },
+ updateLocalControlPosition() {
+ const localControl = this.$refs.localControl;
+ localControl.style.display = 'none';
+
+ const plot = this.$refs.plot;
+ const bgLayer = this.$el.querySelector('.bglayer');
+
+ const plotBoundingRect = plot.getBoundingClientRect();
+ const bgLayerBoundingRect = bgLayer.getBoundingClientRect();
+
+ const top = bgLayerBoundingRect.top - plotBoundingRect.top + 5;
+ const left = bgLayerBoundingRect.left - plotBoundingRect.left + 5;
+
+ localControl.style.top = `${top}px`;
+ localControl.style.left = `${left}px`;
+ localControl.style.display = 'block';
+ },
+ updatePlot() {
+ if (!this.$refs || !this.$refs.plot || this.isZoomed) {
+ return;
+ }
+
+ Plotly.react(this.$refs.plot, Array.from(this.data.concat(this.getShapes(this.shapesData))), this.getLayout());
+ },
+ zoom(eventData) {
+ const autorange = eventData['xaxis.autorange'];
+ const { autosize } = eventData;
+
+ if (autosize || autorange) {
+ return;
+ }
+
+ this.isZoomed = true;
+ this.$emit('unsubscribe');
+ },
+ getShapes() {
+ let markerData = {
+ x: [],
+ y: []
+ };
+ const shapes = this.shapesData.map((shapeData, index) => {
+ if (!shapeData.x || !shapeData.y
+ || !shapeData.x.length || !shapeData.y.length
+ || shapeData.x.length !== shapeData.y.length) {
+ return "";
+ }
+
+ let text = [];
+ shapeData.x.forEach((point) => {
+ text.push(`${parseFloat(point).toPrecision(2)}`);
+ });
+
+ markerData.x = markerData.x.concat(shapeData.x);
+ markerData.y = markerData.y.concat(shapeData.y);
+
+ return {
+ x: shapeData.x,
+ y: shapeData.y,
+ mode: 'text',
+ text,
+ textfont: {
+ family: 'Helvetica Neue, Helvetica, Arial, sans-serif',
+ size: '12px',
+ color: PATH_COLORS[index]
+ },
+ opacity: 0.5
+ };
+ });
+
+ shapes.push({
+ x: markerData.x,
+ y: markerData.y,
+ mode: "markers",
+ marker: {
+ size: 6,
+ color: MARKER_COLOR
+ }
+ });
+
+ return shapes;
+ }
+ }
+};
+</script>
+
diff --git a/src/plugins/charts/scatter/inspector/PlotOptions.vue b/src/plugins/charts/scatter/inspector/PlotOptions.vue
new file mode 100644
index 000000000..a72fcb8c9
--- /dev/null
+++ b/src/plugins/charts/scatter/inspector/PlotOptions.vue
@@ -0,0 +1,64 @@
+<!--
+ Open MCT, Copyright (c) 2014-2022, United States Government
+ as represented by the Administrator of the National Aeronautics and Space
+ Administration. All rights reserved.
+
+ Open MCT is licensed under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations
+ under the License.
+
+ Open MCT includes source code licensed under additional open source
+ licenses. See the Open Source Licenses file (LICENSES.md) included with
+ this source code distribution or the Licensing information page available
+ at runtime from the About dialog for additional information.
+-->
+<template>
+<div>
+ <div v-if="canEdit">
+ <plot-options-edit />
+ </div>
+ <div v-else>
+ <plot-options-browse />
+ </div>
+</div>
+</template>
+
+<script>
+import PlotOptionsBrowse from "./PlotOptionsBrowse.vue";
+import PlotOptionsEdit from "./PlotOptionsEdit.vue";
+export default {
+ components: {
+ PlotOptionsBrowse,
+ PlotOptionsEdit
+ },
+ inject: ['openmct', 'domainObject'],
+ data() {
+ return {
+ isEditing: this.openmct.editor.isEditing()
+ };
+ },
+ computed: {
+ canEdit() {
+ return this.isEditing && !this.domainObject.locked;
+ }
+ },
+ mounted() {
+ this.openmct.editor.on('isEditing', this.setEditState);
+ },
+ beforeDestroy() {
+ this.openmct.editor.off('isEditing', this.setEditState);
+ },
+ methods: {
+ setEditState(isEditing) {
+ this.isEditing = isEditing;
+ }
+ }
+};
+</script>
diff --git a/src/plugins/charts/scatter/inspector/PlotOptionsBrowse.vue b/src/plugins/charts/scatter/inspector/PlotOptionsBrowse.vue
new file mode 100644
index 000000000..c7af21973
--- /dev/null
+++ b/src/plugins/charts/scatter/inspector/PlotOptionsBrowse.vue
@@ -0,0 +1,153 @@
+<!--
+ Open MCT, Copyright (c) 2014-2022, United States Government
+ as represented by the Administrator of the National Aeronautics and Space
+ Administration. All rights reserved.
+
+ Open MCT is licensed under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations
+ under the License.
+
+ Open MCT includes source code licensed under additional open source
+ licenses. See the Open Source Licenses file (LICENSES.md) included with
+ this source code distribution or the Licensing information page available
+ at runtime from the About dialog for additional information.
+-->
+<template>
+<div class="js-plot-options-browse grid-properties">
+ <ul class="l-inspector-part">
+ <h2 title="Object view settings">Settings</h2>
+ <li class="grid-row">
+ <div
+ class="grid-cell label"
+ title="X axis selection"
+ >X Axis</div>
+ <div class="grid-cell value">{{ xKeyLabel }}</div>
+ </li>
+ <li class="grid-row">
+ <div
+ class="grid-cell label"
+ title="Y axis selection"
+ >Y Axis</div>
+ <div class="grid-cell value">{{ yKeyLabel }}</div>
+ </li>
+ <ColorSwatch
+ :current-color="currentColor"
+ edit-title="Manually set the color for this plot"
+ view-title="The marker color for this plot"
+ short-label="Color"
+ />
+ </ul>
+</div>
+</template>
+
+<script>
+import ColorSwatch from "../../../../ui/color/ColorSwatch.vue";
+import Color from "../../../../ui/color/Color";
+import ColorPalette from "../../../../ui/color/ColorPalette";
+
+export default {
+ components: { ColorSwatch },
+ inject: ['openmct', 'domainObject'],
+ data() {
+ return {
+ xKeyLabel: '',
+ yKeyLabel: '',
+ currentColor: undefined
+ };
+ },
+ mounted() {
+ this.plotSeries = [];
+ this.colorPalette = new ColorPalette();
+ this.initColor();
+ this.composition = this.openmct.composition.get(this.domainObject);
+ this.registerListeners();
+ this.composition.load();
+ },
+ beforeDestroy() {
+ this.stopListening();
+ },
+ methods: {
+ initColor() {
+ // this is called before the plot is initialized
+ if (!this.domainObject.configuration.styles || !this.domainObject.configuration.styles.color) {
+ const color = this.colorPalette.getNextColor().asHexString();
+ this.domainObject.configuration.styles = {
+ color
+ };
+ }
+
+ this.currentColor = this.domainObject.configuration.styles.color;
+ const colorObject = Color.fromHexString(this.currentColor);
+
+ this.colorPalette.remove(colorObject);
+ },
+ registerListeners() {
+ this.composition.on('add', this.addSeries);
+ this.composition.on('remove', this.removeSeries);
+ this.unobserve = this.openmct.objects.observe(this.domainObject, 'configuration.axes', this.setAxesLabels);
+ },
+ stopListening() {
+ this.composition.off('add', this.addSeries);
+ this.composition.off('remove', this.removeSeries);
+ if (this.unobserve) {
+ this.unobserve();
+ }
+ },
+ addSeries(series, index) {
+ this.$set(this.plotSeries, this.plotSeries.length, series);
+ this.setAxesLabels();
+ },
+ removeSeries(series) {
+ const index = this.plotSeries.findIndex(plotSeries => this.openmct.objects.areIdsEqual(series.identifier, plotSeries.identifier));
+ if (index !== undefined) {
+ this.$delete(this.plotSeries, index);
+ this.setAxesLabels();
+ }
+ },
+ setAxesLabels() {
+ let xKeyOptions = [];
+ let yKeyOptions = [];
+ if (this.plotSeries.length <= 0) {
+ return;
+ }
+
+ const series = this.plotSeries[0];
+ const metadataValues = this.openmct.telemetry.getMetadata(series).valuesForHints(['range']);
+
+ metadataValues.forEach((metadataValue) => {
+ xKeyOptions.push({
+ name: metadataValue.name || metadataValue.key,
+ value: metadataValue.source || metadataValue.key
+ });
+ yKeyOptions.push({
+ name: metadataValue.name || metadataValue.key,
+ value: metadataValue.source || metadataValue.key
+ });
+ });
+ let xKeyOptionIndex;
+ let yKeyOptionIndex;
+
+ if (this.domainObject.configuration.axes.xKey) {
+ xKeyOptionIndex = xKeyOptions.findIndex(option => option.value === this.domainObject.configuration.axes.xKey);
+ if (xKeyOptionIndex > -1) {
+ this.xKeyLabel = xKeyOptions[xKeyOptionIndex].name;
+ }
+ }
+
+ if (metadataValues.length > 1 && this.domainObject.configuration.axes.yKey) {
+ yKeyOptionIndex = yKeyOptions.findIndex(option => option.value === this.domainObject.configuration.axes.yKey);
+ if (yKeyOptionIndex > -1) {
+ this.yKeyLabel = yKeyOptions[yKeyOptionIndex].name;
+ }
+ }
+ }
+ }
+};
+</script>
diff --git a/src/plugins/charts/scatter/inspector/PlotOptionsEdit.vue b/src/plugins/charts/scatter/inspector/PlotOptionsEdit.vue
new file mode 100644
index 000000000..6781a2777
--- /dev/null
+++ b/src/plugins/charts/scatter/inspector/PlotOptionsEdit.vue
@@ -0,0 +1,262 @@
+<!--
+ Open MCT, Copyright (c) 2014-2022, United States Government
+ as represented by the Administrator of the National Aeronautics and Space
+ Administration. All rights reserved.
+
+ Open MCT is licensed under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations
+ under the License.
+
+ Open MCT includes source code licensed under additional open source
+ licenses. See the Open Source Licenses file (LICENSES.md) included with
+ this source code distribution or the Licensing information page available
+ at runtime from the About dialog for additional information.
+-->
+<template>
+<div class="js-plot-options-edit grid-properties">
+ <ul class="l-inspector-part">
+ <h2 title="Object view settings">Settings</h2>
+ <li class="grid-row">
+ <div
+ class="grid-cell label"
+ title="X axis selection."
+ >X Axis</div>
+ <div class="grid-cell value">
+ <select
+ v-model="xKey"
+ @change="updateForm('xKey')"
+ >
+ <option
+ v-for="option in xKeyOptions"
+ :key="`xKey-${option.value}`"
+ :value="option.value"
+ :selected="option.value == xKey"
+ >
+ {{ option.name }}
+ </option>
+ </select>
+ </div>
+ </li>
+ <li class="grid-row">
+ <div
+ class="grid-cell label"
+ title="Y axis selection."
+ >Y Axis</div>
+ <div class="grid-cell value">
+ <select
+ v-model="yKey"
+ @change="updateForm('yKey')"
+ >
+ <option
+ v-for="option in yKeyOptions"
+ :key="`yKey-${option.value}`"
+ :value="option.value"
+ :selected="option.value == yKey"
+ >
+ {{ option.name }}
+ </option>
+ </select>
+ </div>
+ </li>
+ <ColorSwatch
+ :current-color="currentColor"
+ title="Manually set the line and marker color for this plot."
+ edit-title="Manually set the line and marker color for this plot."
+ view-title="The line and marker color for this plot."
+ short-label="Color"
+ @colorSet="setColor"
+ />
+ </ul>
+</div>
+</template>
+<script>
+import Color from "../../../../ui/color/Color";
+import ColorPalette from "../../../../ui/color/ColorPalette";
+import ColorSwatch from "../../../../ui/color/ColorSwatch.vue";
+
+export default {
+ components: { ColorSwatch },
+ inject: ['openmct', 'domainObject'],
+ data() {
+ return {
+ xKey: undefined,
+ yKey: undefined,
+ xKeyOptions: [],
+ yKeyOptions: [],
+ currentColor: undefined
+ };
+ },
+ mounted() {
+ this.plotSeries = [];
+ this.colorPalette = new ColorPalette();
+ this.initColor();
+ this.composition = this.openmct.composition.get(this.domainObject);
+ this.registerListeners();
+ this.composition.load();
+ },
+ beforeDestroy() {
+ this.stopListening();
+ },
+ methods: {
+ initColor() {
+ // this is called before the plot is initialized
+ if (!this.domainObject.configuration.styles || !this.domainObject.configuration.styles.color) {
+ const color = this.colorPalette.getNextColor().asHexString();
+ this.domainObject.configuration.styles = {
+ color
+ };
+ }
+
+ this.currentColor = this.domainObject.configuration.styles.color;
+ const colorObject = Color.fromHexString(this.currentColor);
+
+ this.colorPalette.remove(colorObject);
+ },
+ setColor(chosenColor) {
+ this.currentColor = chosenColor.asHexString();
+ this.openmct.objects.mutate(
+ this.domainObject,
+ `configuration.styles.color`,
+ this.currentColor
+ );
+ },
+ registerListeners() {
+ this.composition.on('add', this.addSeries);
+ this.composition.on('remove', this.removeSeries);
+ this.unobserve = this.openmct.objects.observe(this.domainObject, 'configuration.axes', this.setupOptions);
+ },
+ stopListening() {
+ this.composition.off('add', this.addSeries);
+ this.composition.off('remove', this.removeSeries);
+ if (this.unobserve) {
+ this.unobserve();
+ }
+ },
+ addSeries(series, index) {
+ this.$set(this.plotSeries, this.plotSeries.length, series);
+ this.setupOptions();
+ },
+ removeSeries(seriesIdentifier) {
+ const index = this.plotSeries.findIndex(plotSeries => this.openmct.objects.areIdsEqual(seriesIdentifier, plotSeries.identifier));
+ if (index >= 0) {
+ this.$delete(this.plotSeries, index);
+ this.setupOptions();
+ }
+ },
+ setupOptions() {
+ this.xKeyOptions = [];
+ this.yKeyOptions = [];
+ if (this.plotSeries.length <= 0) {
+ return;
+ }
+
+ let update = false;
+ const series = this.plotSeries[0];
+ const metadataValues = this.openmct.telemetry.getMetadata(series).valuesForHints(['range']);
+ metadataValues.forEach((metadataValue) => {
+ this.xKeyOptions.push({
+ name: metadataValue.name || metadataValue.key,
+ value: metadataValue.source || metadataValue.key
+ });
+ this.yKeyOptions.push({
+ name: metadataValue.name || metadataValue.key,
+ value: metadataValue.source || metadataValue.key
+ });
+ });
+
+ let xKeyOptionIndex;
+ let yKeyOptionIndex;
+
+ if (this.domainObject.configuration.axes.xKey) {
+ xKeyOptionIndex = this.xKeyOptions.findIndex(option => option.value === this.domainObject.configuration.axes.xKey);
+ if (xKeyOptionIndex > -1) {
+ this.xKey = this.xKeyOptions[xKeyOptionIndex].value;
+ } else {
+ this.xKey = undefined;
+ }
+ }
+
+ if (this.xKey === undefined) {
+ update = true;
+ xKeyOptionIndex = 0;
+ this.xKey = this.xKeyOptions[xKeyOptionIndex].value;
+ }
+
+ if (metadataValues.length > 1) {
+ if (this.domainObject.configuration.axes.yKey) {
+ yKeyOptionIndex = this.yKeyOptions.findIndex(option => option.value === this.domainObject.configuration.axes.yKey);
+ if (yKeyOptionIndex > -1 && yKeyOptionIndex !== xKeyOptionIndex) {
+ this.yKey = this.yKeyOptions[yKeyOptionIndex].value;
+ } else {
+ this.yKey = undefined;
+ }
+ }
+
+ if (this.yKey === undefined) {
+ update = true;
+ yKeyOptionIndex = this.yKeyOptions.findIndex((option, index) => index !== xKeyOptionIndex);
+ this.yKey = this.yKeyOptions[yKeyOptionIndex].value;
+ }
+
+ this.yKeyOptions = this.yKeyOptions.map((option, index) => {
+ if (index === xKeyOptionIndex) {
+ option.name = `${option.name} (swap)`;
+ option.swap = yKeyOptionIndex;
+ } else {
+ option.name = option.name.replace(' (swap)', '');
+ option.swap = undefined;
+ }
+
+ return option;
+ });
+ }
+
+ this.xKeyOptions = this.xKeyOptions.map((option, index) => {
+ if (index === yKeyOptionIndex) {
+ option.name = `${option.name} (swap)`;
+ option.swap = xKeyOptionIndex;
+ } else {
+ option.name = option.name.replace(' (swap)', '');
+ option.swap = undefined;
+ }
+
+ return option;
+ });
+
+ if (update === true) {
+ this.saveConfiguration();
+ }
+ },
+ updateForm(property) {
+ if (property === 'xKey') {
+ const xKeyOption = this.xKeyOptions.find(option => option.value === this.xKey);
+ if (xKeyOption.swap !== undefined) {
+ //swap
+ this.yKey = this.xKeyOptions[xKeyOption.swap].value;
+ }
+ } else if (property === 'yKey') {
+ const yKeyOption = this.yKeyOptions.find(option => option.value === this.yKey);
+ if (yKeyOption.swap !== undefined) {
+ //swap
+ this.xKey = this.yKeyOptions[yKeyOption.swap].value;
+ }
+ }
+
+ this.saveConfiguration();
+ },
+ saveConfiguration() {
+ this.openmct.objects.mutate(this.domainObject, `configuration.axes`, {
+ xKey: this.xKey,
+ yKey: this.yKey
+ });
+ }
+ }
+};
+</script>
diff --git a/src/plugins/charts/scatter/inspector/ScatterPlotInspectorViewProvider.js b/src/plugins/charts/scatter/inspector/ScatterPlotInspectorViewProvider.js
new file mode 100644
index 000000000..54487dfe3
--- /dev/null
+++ b/src/plugins/charts/scatter/inspector/ScatterPlotInspectorViewProvider.js
@@ -0,0 +1,48 @@
+import { SCATTER_PLOT_INSPECTOR_KEY, SCATTER_PLOT_KEY } from '../scatterPlotConstants';
+import Vue from 'vue';
+import PlotOptions from "./PlotOptions.vue";
+
+export default function ScatterPlotInspectorViewProvider(openmct) {
+ return {
+ key: SCATTER_PLOT_INSPECTOR_KEY,
+ name: 'Bar Graph Inspector View',
+ canView: function (selection) {
+ if (selection.length === 0 || selection[0].length === 0) {
+ return false;
+ }
+
+ let object = selection[0][0].context.item;
+
+ return object
+ && object.type === SCATTER_PLOT_KEY;
+ },
+ view: function (selection) {
+ let component;
+
+ return {
+ show: function (element) {
+ component = new Vue({
+ el: element,
+ components: {
+ PlotOptions
+ },
+ provide: {
+ openmct,
+ domainObject: selection[0][0].context.item
+ },
+ template: '<plot-options></plot-options>'
+ });
+ },
+ destroy: function () {
+ if (component) {
+ component.$destroy();
+ component = undefined;
+ }
+ }
+ };
+ },
+ priority: function () {
+ return 1;
+ }
+ };
+}
diff --git a/src/plugins/charts/scatter/plugin.js b/src/plugins/charts/scatter/plugin.js
new file mode 100644
index 000000000..600c2970f
--- /dev/null
+++ b/src/plugins/charts/scatter/plugin.js
@@ -0,0 +1,127 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2022, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+import { SCATTER_PLOT_KEY } from './scatterPlotConstants.js';
+import ScatterPlotViewProvider from './ScatterPlotViewProvider';
+import ScatterPlotInspectorViewProvider from './inspector/ScatterPlotInspectorViewProvider';
+import ScatterPlotCompositionPolicy from './ScatterPlotCompositionPolicy';
+import Vue from "vue";
+import ScatterPlotForm from "./ScatterPlotForm.vue";
+
+export default function () {
+ return function install(openmct) {
+ openmct.forms.addNewFormControl('scatter-plot-form-control', getScatterPlotFormControl(openmct));
+
+ openmct.types.addType(SCATTER_PLOT_KEY, {
+ key: SCATTER_PLOT_KEY,
+ name: "Scatter Plot",
+ cssClass: "icon-plot-scatter",
+ description: "View data as a scatter plot.",
+ creatable: true,
+ initialize: function (domainObject) {
+ domainObject.composition = [];
+ domainObject.configuration = {
+ styles: {},
+ axes: {},
+ ranges: {}
+ };
+ },
+ form: [
+ {
+ name: 'Underlay data (JSON file)',
+ key: 'selectFile',
+ control: 'file-input',
+ text: 'Select File...',
+ type: 'application/json',
+ removable: true,
+ hideFromInspector: true,
+ property: [
+ "selectFile"
+ ]
+ },
+ {
+ name: "Underlay ranges",
+ control: "scatter-plot-form-control",
+ cssClass: "l-input",
+ key: "scatterPlotForm",
+ required: false,
+ hideFromInspector: false,
+ property: [
+ "configuration",
+ "ranges"
+ ],
+ validate: ({ value }, callback) => {
+ const { rangeMin, rangeMax, domainMin, domainMax } = value;
+ const valid = {
+ rangeMin,
+ rangeMax,
+ domainMin,
+ domainMax
+ };
+
+ if (callback) {
+ callback(valid);
+ }
+
+ const values = Object.values(valid);
+ const hasAllValues = values.every(rangeValue => rangeValue !== undefined);
+ const hasNoValues = values.every(rangeValue => rangeValue === undefined);
+
+ return hasAllValues || hasNoValues;
+ }
+ }
+ ],
+ priority: 891
+ });
+
+ openmct.objectViews.addProvider(new ScatterPlotViewProvider(openmct));
+
+ openmct.inspectorViews.addProvider(new ScatterPlotInspectorViewProvider(openmct));
+
+ openmct.composition.addPolicy(new ScatterPlotCompositionPolicy(openmct).allow);
+ };
+
+ function getScatterPlotFormControl(openmct) {
+ return {
+ show(element, model, onChange) {
+ const rowComponent = new Vue({
+ el: element,
+ components: {
+ ScatterPlotForm
+ },
+ provide: {
+ openmct
+ },
+ data() {
+ return {
+ model,
+ onChange
+ };
+ },
+ template: `<scatter-plot-form :model="model" @onChange="onChange"></scatter-plot-form>`
+ });
+
+ return rowComponent;
+ }
+ };
+ }
+}
+
diff --git a/src/plugins/charts/scatter/pluginSpec.js b/src/plugins/charts/scatter/pluginSpec.js
new file mode 100644
index 000000000..2eb17c7a4
--- /dev/null
+++ b/src/plugins/charts/scatter/pluginSpec.js
@@ -0,0 +1,421 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2022, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+import {createOpenMct, resetApplicationState} from "utils/testing";
+import Vue from "vue";
+import ScatterPlotPlugin from "./plugin";
+import ScatterPlot from './ScatterPlotView.vue';
+import EventEmitter from "EventEmitter";
+import { SCATTER_PLOT_VIEW, SCATTER_PLOT_KEY } from './scatterPlotConstants';
+
+describe("the plugin", function () {
+ let element;
+ let child;
+ let openmct;
+ let telemetryPromise;
+ let telemetryPromiseResolve;
+ let mockObjectPath;
+
+ beforeEach((done) => {
+ mockObjectPath = [
+ {
+ name: 'mock folder',
+ type: 'fake-folder',
+ identifier: {
+ key: 'mock-folder',
+ namespace: ''
+ }
+ }
+ ];
+ const testTelemetry = [
+ {
+ 'utc': 1,
+ 'some-key': 'some-value 1',
+ 'some-other-key': 'some-other-value 1'
+ },
+ {
+ 'utc': 2,
+ 'some-key': 'some-value 2',
+ 'some-other-key': 'some-other-value 2'
+ },
+ {
+ 'utc': 3,
+ 'some-key': 'some-value 3',
+ 'some-other-key': 'some-other-value 3'
+ }
+ ];
+
+ openmct = createOpenMct();
+
+ telemetryPromise = new Promise((resolve) => {
+ telemetryPromiseResolve = resolve;
+ });
+
+ spyOn(openmct.telemetry, 'request').and.callFake(() => {
+ telemetryPromiseResolve(testTelemetry);
+
+ return telemetryPromise;
+ });
+
+ openmct.install(new ScatterPlotPlugin());
+
+ element = document.createElement("div");
+ element.style.width = "640px";
+ element.style.height = "480px";
+ child = document.createElement("div");
+ child.style.width = "640px";
+ child.style.height = "480px";
+ element.appendChild(child);
+ document.body.appendChild(element);
+
+ spyOn(window, 'ResizeObserver').and.returnValue({
+ observe() {},
+ unobserve() {},
+ disconnect() {}
+ });
+
+ openmct.time.timeSystem("utc", {
+ start: 0,
+ end: 4
+ });
+
+ openmct.types.addType("test-object", {
+ creatable: true
+ });
+
+ openmct.on("start", done);
+ openmct.startHeadless();
+ });
+
+ afterEach((done) => {
+ openmct.time.timeSystem('utc', {
+ start: 0,
+ end: 1
+ });
+ resetApplicationState(openmct).then(done).catch(done);
+ });
+
+ describe("The scatter plot view", () => {
+ let testDomainObject;
+ let scatterPlotObject;
+ // eslint-disable-next-line no-unused-vars
+ let component;
+ let mockComposition;
+
+ beforeEach(async () => {
+ scatterPlotObject = {
+ identifier: {
+ namespace: "",
+ key: "test-plot"
+ },
+ type: "telemetry.plot.scatter-plot",
+ name: "Test Scatter Plot",
+ configuration: {
+ axes: {},
+ styles: {}
+ }
+ };
+
+ testDomainObject = {
+ identifier: {
+ namespace: "",
+ key: "test-object"
+ },
+ type: "test-object",
+ name: "Test Object",
+ telemetry: {
+ values: [{
+ key: "utc",
+ format: "utc",
+ name: "Time",
+ hints: {
+ domain: 1
+ }
+ }, {
+ key: "some-key",
+ name: "Some attribute",
+ hints: {
+ range: 1
+ }
+ }, {
+ key: "some-other-key",
+ name: "Another attribute",
+ hints: {
+ range: 2
+ }
+ }]
+ }
+ };
+
+ mockComposition = new EventEmitter();
+ mockComposition.load = () => {
+ mockComposition.emit('add', testDomainObject);
+
+ return [testDomainObject];
+ };
+
+ spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
+
+ let viewContainer = document.createElement("div");
+ child.append(viewContainer);
+ component = new Vue({
+ el: viewContainer,
+ components: {
+ ScatterPlot
+ },
+ provide: {
+ openmct: openmct,
+ domainObject: scatterPlotObject,
+ composition: openmct.composition.get(scatterPlotObject)
+ },
+ template: "<ScatterPlot></ScatterPlot>"
+ });
+
+ await Vue.nextTick();
+ });
+
+ it("provides a scatter plot view", () => {
+ const applicableViews = openmct.objectViews.get(scatterPlotObject, mockObjectPath);
+ const plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === SCATTER_PLOT_VIEW);
+ expect(plotViewProvider).toBeDefined();
+ });
+
+ it("Renders plotly scatter plot", () => {
+ let scatterPlotElement = element.querySelectorAll(".plotly");
+ expect(scatterPlotElement.length).toBe(1);
+ });
+ });
+
+ describe("the scatter plot objects", () => {
+ const mockObject = {
+ name: 'A very nice scatter plot',
+ key: SCATTER_PLOT_KEY,
+ creatable: true
+ };
+
+ it('defines a scatter plot object type with the correct key', () => {
+ const objectDef = openmct.types.get(SCATTER_PLOT_KEY).definition;
+ expect(objectDef.key).toEqual(mockObject.key);
+ });
+
+ it('is creatable', () => {
+ const objectDef = openmct.types.get(SCATTER_PLOT_KEY).definition;
+ expect(objectDef.creatable).toEqual(mockObject.creatable);
+ });
+ });
+
+ describe("The scatter plot composition policy", () => {
+ it("allows composition for telemetry that contain at least 2 ranges", () => {
+ const parent = {
+ "composition": [],
+ "configuration": {
+ axes: {},
+ styles: {}
+ },
+ "name": "Some Scatter Plot",
+ "type": "telemetry.plot.scatter-plot",
+ "location": "mine",
+ "modified": 1631005183584,
+ "persisted": 1631005183502,
+ "identifier": {
+ "namespace": "",
+ "key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9"
+ }
+ };
+ const testTelemetryObject = {
+ identifier: {
+ namespace: "",
+ key: "test-object"
+ },
+ type: "test-object",
+ name: "Test Object",
+ telemetry: {
+ values: [{
+ key: "some-key",
+ name: "Some attribute",
+ hints: {
+ domain: 1
+ }
+ }, {
+ key: "some-other-key",
+ name: "Another attribute",
+ hints: {
+ range: 1
+ }
+ }, {
+ key: "some-other-key2",
+ name: "Another attribute2",
+ hints: {
+ range: 2
+ }
+ }]
+ }
+ };
+ const composition = openmct.composition.get(parent);
+ expect(() => {
+ composition.add(testTelemetryObject);
+ }).not.toThrow();
+ expect(parent.composition.length).toBe(1);
+ });
+
+ it("disallows composition for telemetry that don't contain at least 2 range hints", () => {
+ const parent = {
+ "composition": [],
+ "configuration": {
+ axes: {},
+ styles: {}
+ },
+ "name": "Some Scatter Plot",
+ "type": "telemetry.plot.scatter-plot",
+ "location": "mine",
+ "modified": 1631005183584,
+ "persisted": 1631005183502,
+ "identifier": {
+ "namespace": "",
+ "key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9"
+ }
+ };
+ const testTelemetryObject = {
+ identifier: {
+ namespace: "",
+ key: "test-object"
+ },
+ type: "test-object",
+ name: "Test Object",
+ telemetry: {
+ values: [{
+ key: "some-key",
+ name: "Some attribute",
+ hints: {
+ domain: 1
+ }
+ }, {
+ key: "some-other-key",
+ name: "Another attribute",
+ hints: {
+ range: 1
+ }
+ }]
+ }
+ };
+ const composition = openmct.composition.get(parent);
+ expect(() => {
+ composition.add(testTelemetryObject);
+ }).toThrow();
+ expect(parent.composition.length).toBe(0);
+ });
+ });
+ describe('the inspector view', () => {
+ let mockComposition;
+ let testDomainObject;
+ let selection;
+ let plotInspectorView;
+ let viewContainer;
+ let optionsElement;
+ beforeEach(async () => {
+ testDomainObject = {
+ identifier: {
+ namespace: "",
+ key: "test-object"
+ },
+ type: "test-object",
+ name: "Test Object",
+ telemetry: {
+ values: [{
+ key: "utc",
+ format: "utc",
+ name: "Time",
+ hints: {
+ domain: 1
+ }
+ }, {
+ key: "some-key",
+ name: "Some attribute",
+ hints: {
+ range: 1
+ }
+ }, {
+ key: "some-other-key",
+ name: "Another attribute",
+ hints: {
+ range: 2
+ }
+ }]
+ }
+ };
+
+ selection = [
+ [
+ {
+ context: {
+ item: {
+ id: "test-object",
+ identifier: {
+ key: "test-object",
+ namespace: ''
+ },
+ type: "telemetry.plot.scatter-plot",
+ configuration: {
+ axes: {},
+ styles: {
+ }
+ },
+ composition: [
+ {
+ key: '~Some~foo.scatter'
+ }
+ ]
+ }
+ }
+ }
+ ]
+ ];
+
+ mockComposition = new EventEmitter();
+ mockComposition.load = () => {
+ mockComposition.emit('add', testDomainObject);
+
+ return [testDomainObject];
+ };
+
+ spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
+
+ viewContainer = document.createElement('div');
+ child.append(viewContainer);
+
+ const applicableViews = openmct.inspectorViews.get(selection);
+ plotInspectorView = applicableViews[0];
+ plotInspectorView.show(viewContainer);
+
+ await Vue.nextTick();
+ optionsElement = element.querySelector('.c-scatter-plot-options');
+ });
+
+ afterEach(() => {
+ plotInspectorView.destroy();
+ });
+
+ it('it renders the options', () => {
+ expect(optionsElement).toBeDefined();
+ });
+ });
+});
diff --git a/src/plugins/charts/scatter/scatterPlotConstants.js b/src/plugins/charts/scatter/scatterPlotConstants.js
new file mode 100644
index 000000000..e458be37c
--- /dev/null
+++ b/src/plugins/charts/scatter/scatterPlotConstants.js
@@ -0,0 +1,4 @@
+export const SCATTER_PLOT_VIEW = 'scatter-plot.view';
+export const SCATTER_PLOT_KEY = 'telemetry.plot.scatter-plot';
+export const SCATTER_PLOT_INSPECTOR_KEY = 'telemetry.plot.scatter-plot.inspector';
+export const TIME_STRIP_KEY = 'time-strip';
diff --git a/src/plugins/imagery/components/ImageryView.vue b/src/plugins/imagery/components/ImageryView.vue
index 2d0cce2e6..ad63b623d 100644
--- a/src/plugins/imagery/components/ImageryView.vue
+++ b/src/plugins/imagery/components/ImageryView.vue
@@ -55,7 +55,7 @@
<div
v-if="zoomFactor > 1"
class="c-imagery__hints"
- >{{formatImageAltText}}</div>
+ >{{ formatImageAltText }}</div>
<div
ref="focusedImageWrapper"
class="image-wrapper"
diff --git a/src/plugins/imagery/mixins/imageryData.js b/src/plugins/imagery/mixins/imageryData.js
index 933df01c1..64137d46d 100644
--- a/src/plugins/imagery/mixins/imageryData.js
+++ b/src/plugins/imagery/mixins/imageryData.js
@@ -46,7 +46,6 @@ export default {
// kickoff
this.subscribe();
- this.requestHistory();
},
beforeDestroy() {
if (this.unsubscribe) {
@@ -169,8 +168,6 @@ export default {
// splice array to encourage garbage collection
this.imageHistory.splice(0, this.imageHistory.length);
- // requesting history effectively clears imageHistory array
- return this.requestHistory();
},
timeSystemChange() {
this.timeSystem = this.timeContext.timeSystem();
diff --git a/src/plugins/notebook/components/NotebookEntry.vue b/src/plugins/notebook/components/NotebookEntry.vue
index 9261063a0..6f45cd389 100644
--- a/src/plugins/notebook/components/NotebookEntry.vue
+++ b/src/plugins/notebook/components/NotebookEntry.vue
@@ -28,12 +28,16 @@
@drop.prevent="dropOnEntry"
>
<div class="c-ne__time-and-content">
- <div class="c-ne__time">
- <template v-if="entry.createdBy">
- <span class="c-icon icon-person">{{ entry.createdBy }}</span>
- </template>
- <span>{{ createdOnDate }}</span>
- <span>{{ createdOnTime }}</span>
+ <div class="c-ne__time-and-creator">
+ <span class="c-ne__created-date">{{ createdOnDate }}</span>
+ <span class="c-ne__created-time">{{ createdOnTime }}</span>
+
+ <span
+ v-if="entry.createdBy"
+ class="c-ne__creator"
+ >
+ <span class="icon-person"></span> {{ entry.createdBy }}
+ </span>
</div>
<div class="c-ne__content">
<template v-if="readOnly && result">
diff --git a/src/plugins/plan/Plan.vue b/src/plugins/plan/Plan.vue
index 1f34e97a6..89ad45bdd 100644
--- a/src/plugins/plan/Plan.vue
+++ b/src/plugins/plan/Plan.vue
@@ -49,7 +49,7 @@
import * as d3Scale from 'd3-scale';
import TimelineAxis from "../../ui/components/TimeSystemAxis.vue";
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
-import { getValidatedPlan } from "./util";
+import { getValidatedData } from "./util";
import Vue from "vue";
const PADDING = 1;
@@ -161,7 +161,7 @@ export default {
return clientWidth - 200;
},
getPlanData(domainObject) {
- this.planData = getValidatedPlan(domainObject);
+ this.planData = getValidatedData(domainObject);
},
updateViewBounds(bounds) {
if (bounds) {
diff --git a/src/plugins/plan/util.js b/src/plugins/plan/util.js
index cf119412f..c5e3cc6b8 100644
--- a/src/plugins/plan/util.js
+++ b/src/plugins/plan/util.js
@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
-export function getValidatedPlan(domainObject) {
+export function getValidatedData(domainObject) {
let sourceMap = domainObject.sourceMap;
let body = domainObject.selectFile.body;
let json = {};
diff --git a/src/plugins/plugins.js b/src/plugins/plugins.js
index 5ccafda4c..e53ac6843 100644
--- a/src/plugins/plugins.js
+++ b/src/plugins/plugins.js
@@ -37,7 +37,8 @@ define([
'./URLIndicatorPlugin/URLIndicatorPlugin',
'./telemetryMean/plugin',
'./plot/plugin',
- './charts/plugin',
+ './charts/bar/plugin',
+ './charts/scatter/plugin',
'./telemetryTable/plugin',
'./staticRootPlugin/plugin',
'./notebook/plugin',
@@ -97,7 +98,8 @@ define([
URLIndicatorPlugin,
TelemetryMean,
PlotPlugin,
- ChartPlugin,
+ BarChartPlugin,
+ ScatterPlotPlugin,
TelemetryTablePlugin,
StaticRootPlugin,
Notebook,
@@ -174,7 +176,8 @@ define([
plugins.ImageryPlugin = ImageryPlugin;
plugins.Plot = PlotPlugin.default;
- plugins.Chart = ChartPlugin.default;
+ plugins.BarChart = BarChartPlugin.default;
+ plugins.ScatterPlot = ScatterPlotPlugin.default;
plugins.TelemetryTable = TelemetryTablePlugin;
plugins.SummaryWidget = SummaryWidget;
diff --git a/src/plugins/timeline/TimelineViewLayout.vue b/src/plugins/timeline/TimelineViewLayout.vue
index b1764ec1a..cc327b22a 100644
--- a/src/plugins/timeline/TimelineViewLayout.vue
+++ b/src/plugins/timeline/TimelineViewLayout.vue
@@ -61,7 +61,7 @@
import TimelineObjectView from './TimelineObjectView.vue';
import TimelineAxis from '../../ui/components/TimeSystemAxis.vue';
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
-import { getValidatedPlan } from "../plan/util";
+import { getValidatedData } from "../plan/util";
const unknownObjectType = {
definition: {
@@ -110,7 +110,7 @@ export default {
let objectPath = [domainObject].concat(this.objectPath.slice());
let rowCount = 0;
if (domainObject.type === 'plan') {
- rowCount = Object.keys(getValidatedPlan(domainObject)).length;
+ rowCount = Object.keys(getValidatedData(domainObject)).length;
}
let height = domainObject.type === 'telemetry.plot.stacked' ? `${domainObject.composition.length * 100}px` : '100px';
diff --git a/src/plugins/timelist/Timelist.vue b/src/plugins/timelist/Timelist.vue
index 517822cd6..188c95800 100644
--- a/src/plugins/timelist/Timelist.vue
+++ b/src/plugins/timelist/Timelist.vue
@@ -35,7 +35,7 @@
</template>
<script>
-import {getValidatedPlan} from "../plan/util";
+import {getValidatedData} from "../plan/util";
import ListView from '../../ui/components/List/ListView.vue';
import {getPreciseDuration} from "../../utils/duration";
import ticker from 'utils/clock/Ticker';
@@ -164,7 +164,7 @@ export default {
}
},
getPlanData(domainObject) {
- this.planData = getValidatedPlan(domainObject);
+ this.planData = getValidatedData(domainObject);
},
setViewBounds() {
const pastEventsIndex = this.domainObject.configuration.pastEventsIndex;
diff --git a/src/styles/_constants.scss b/src/styles/_constants.scss
index d2a3235ed..8b4994756 100755
--- a/src/styles/_constants.scss
+++ b/src/styles/_constants.scss
@@ -272,6 +272,7 @@ $glyph-icon-map: '\eb2d';
$glyph-icon-plan: '\eb2e';
$glyph-icon-timelist: '\eb2f';
$glyph-icon-notebook-shift-log: '\eb31';
+$glyph-icon-plot-scatter: '\eb30';
/************************** GLYPHS AS DATA URI */
// Only objects have been converted, for use in Create menu and folder views
@@ -279,44 +280,41 @@ $bg-icon-alert-rect: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://
$bg-icon-alert-triangle: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M499.1 424.4L287.8 54.6c-17.5-30.6-46-30.6-63.5 0L12.9 424.4C-4.6 455 9.9 480 45.1 480h421.7c35.3 0 49.8-25 32.3-55.6zM288 448h-64v-64h64v64zm10.9-192L280 352h-48l-18.9-96V128H299v128z' fill='%23000000'/%3e%3c/svg%3e");
$bg-icon-bell: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cg fill='%23000000'%3e%3cpath d='M256 512c53 0 96-43 96-96H160c0 53 43 96 96 96zM448 224v-32C448 86 362 0 256 0S64 86 64 192v32c0 35.3-28.7 64-64 64v64h512v-64c-35.3 0-64-28.7-64-64z'/%3e%3c/g%3e%3c/svg%3e");
$bg-icon-info: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256 256-114.6 256-256S397.4 0 256 0zm0 64c35.3 0 64 28.7 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zm96 352H160v-64h32V224h128v128h32v64z' fill='%23000000'/%3e%3c/svg%3e");
-$bg-icon-activity: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M288 32H160l160 160H174.872C152.74 153.742 111.377 128 64 128H0v256h64c47.377 0 88.74-25.742 110.872-64H320L160 480h128l224-224L288 32z'/%3e%3c/svg%3e");
-$bg-icon-activity-mode: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 0C148.6 0 56.6 66.2 18.6 160H64c28.4 0 54 12.4 71.5 32H256l-96-96h128l160 160-160 160H160l96-96H135.5C118 339.6 92.4 352 64 352H18.6c38 93.8 129.9 160 237.4 160 141.4 0 256-114.6 256-256S397.4 0 256 0z'/%3e%3c/svg%3e");
-$bg-icon-autoflow-tabular: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M96 0C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h32V0H96zM192 0h128v512H192zM416 0h-32v352h128V96c0-52.8-43.2-96-96-96z'/%3e%3c/svg%3e");
$bg-icon-plus: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M480,192H320V32A32.1,32.1,0,0,0,288,0H224a32.1,32.1,0,0,0-32,32V192H32A32.1,32.1,0,0,0,0,224v64a32.1,32.1,0,0,0,32,32H192V480a32.1,32.1,0,0,0,32,32h64a32.1,32.1,0,0,0,32-32V320H480a32.1,32.1,0,0,0,32-32V224A32.1,32.1,0,0,0,480,192Z' transform='translate(0)'/%3e%3c/svg%3e");
$bg-icon-grippy-ew: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M416 0v512h-64V0zM288 0v512h-64V0zM160 0v512H96V0z'/%3e%3c/svg%3e");
$bg-icon-chain-links: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M479.2 32.8C457.3 10.9 428.7 0 400 0c-28.7 0-57.3 10.9-79.2 32.8l-64 64c-37 37-42.7 93.5-17 136.5l-6.4 6.4C215.7 229.3 195.9 224 176 224c-28.7 0-57.3 10.9-79.2 32.8l-64 64c-43.7 43.7-43.7 114.7 0 158.4C54.7 501.1 83.3 512 112 512c28.7 0 57.3-10.9 79.2-32.8l64-64c37-37 42.7-93.5 17-136.5l6.4-6.4c17.6 10.5 37.5 15.8 57.3 15.8 28.7 0 57.3-10.9 79.2-32.8l64-64c43.8-43.8 43.8-114.8.1-158.5zM209.9 369.9l-64 64c-9 9.1-21.1 14.1-33.9 14.1-12.8 0-24.9-5-33.9-14.1-18.7-18.7-18.7-49.2 0-67.9l64-64c9.1-9.1 21.1-14.1 33.9-14.1 2.8 0 5.6.3 8.4.7l-27.8 27.8c-5.2 5.2-8.1 12.1-8.1 19.4s2.9 14.3 8.1 19.4c5.2 5.2 12.1 8.1 19.4 8.1s14.3-2.9 19.4-8.1l27.8-27.8c2.7 15.2-1.8 31.1-13.3 42.5zm224-224l-64 64c-9 9.1-21.1 14.1-33.9 14.1-2.8 0-5.6-.3-8.4-.7l27.8-27.8c5.2-5.2 8.1-12.1 8.1-19.4s-2.9-14.3-8.1-19.4c-5.2-5.2-12.1-8.1-19.4-8.1s-14.3 2.9-19.4 8.1l-27.8 27.8c-2.6-14.9 1.8-30.8 13.3-42.3l64-64C375.1 69 387.2 64 400 64s24.9 5 33.9 14.1c18.8 18.7 18.8 49.1 0 67.8z'/%3e%3c/svg%3e");
-$bg-icon-clock: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256 256-114.6 256-256S397.4 0 256 0zm135 345c-6.4 11.1-18.3 18-31.2 18-6.3 0-12.5-1.7-18-4.8l-110.9-64-.1-.1c-.4-.2-.8-.5-1.2-.7l-.4-.3-.9-.6-.6-.5-.6-.5-.9-.7-.3-.3c-.4-.3-.7-.6-1.1-.9-2.5-2.3-4.7-5-6.5-7.9-.1-.2-.3-.5-.4-.7s-.3-.5-.4-.7c-1.6-3-2.9-6.2-3.6-9.6v-.1c-.1-.5-.2-.9-.3-1.4 0-.1 0-.3-.1-.4-.1-.3-.1-.7-.1-1.1s-.1-.5-.1-.8 0-.5-.1-.8-.1-.8-.1-1.1v-.5-1.4V81c0-19.9 16.1-36 36-36s36 16.1 36 36v161.2l92.9 53.6c17.1 10 22.9 32 13 49.2z'/%3e%3c/svg%3e");
-$bg-icon-database: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 256C114.615 256 0 213.019 0 160v256c0 53.019 114.615 96 256 96s256-42.981 256-96V160c0 53.019-114.615 96-256 96z'/%3e%3cellipse fill='%23000000' cx='256' cy='96' rx='256' ry='96'/%3e%3c/svg%3e");
-$bg-icon-database-query: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M341.76 409.643C316.369 423.871 287.118 432 256 432c-97.047 0-176-78.953-176-176S158.953 80 256 80s176 78.953 176 176c0 31.118-8.129 60.369-22.357 85.76l95.846 95.846C509.747 430.661 512 423.429 512 416V96c0-53.019-114.615-96-256-96S0 42.981 0 96v320c0 53.019 114.615 96 256 96 63.055 0 120.774-8.554 165.388-22.73l-79.628-79.627z'/%3e%3cpath fill='%23000000' d='M176 256c0 44.112 35.888 80 80 80s80-35.888 80-80-35.888-80-80-80-80 35.888-80 80z'/%3e%3c/svg%3e");
-$bg-icon-dataset: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 96H288l-54.6-54.6-18.7-18.7C202.2 10.2 177.6 0 160 0H32C14.4 0 0 14.4 0 32v192c0-35.2 28.8-64 64-64h384c35.2 0 64 28.8 64 64v-64c0-35.2-28.8-64-64-64zM448 224H64c-35.2 0-64 28.8-64 64v160c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64zM160 448H96V288h64v160zm128 0h-64V288h64v160zm128 0h-64V288h64v160z'/%3e%3c/svg%3e");
-$bg-icon-datatable: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 256C114.6 256 0 213 0 160v256c0 53 114.6 96 256 96s256-43 256-96V160c0 53-114.6 96-256 96zm192 31.5v128c-18.3 7.8-39.9 14.4-64 19.7v-128c24.1-5.3 45.7-11.9 64-19.7zm-320 19.7v128c-24.1-5.2-45.7-11.9-64-19.7v-128c18.3 7.8 39.9 14.4 64 19.7zM192 445V317c20.5 2 41.9 3 64 3s43.5-1.1 64-3v128c-20.5 2-41.9 3-64 3s-43.5-1.1-64-3z'/%3e%3cellipse fill='%23000000' cx='256' cy='96' rx='256' ry='96'/%3e%3c/svg%3e");
-$bg-icon-dictionary: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M416 320c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96v160l-64-32-64 32V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96v-96c0 52.8-43.2 96-96 96H96v-96h320z'/%3e%3c/svg%3e");
-$bg-icon-folder: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 96H288l-54.6-54.6-18.7-18.7C202.2 10.2 177.6 0 160 0H32C14.4 0 0 14.4 0 32v192c0-35.2 28.8-64 64-64h384c35.2 0 64 28.8 64 64v-64c0-35.2-28.8-64-64-64zM448 224H64c-35.2 0-64 28.8-64 64v160c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64z'/%3e%3c/svg%3e");
-$bg-icon-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 0H64C28.8 0 0 28.8 0 64v384c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V64c0-35.2-28.8-64-64-64zm0 448H64V64h384v384z'/%3e%3cpath fill='%23000000' d='M160 128l-64 64v224h320V256l-64-64-64 64z'/%3e%3c/svg%3e");
-$bg-icon-layout: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M224 0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h128V0zM416 0H288v288.832h224V96c0-52.8-43.2-96-96-96zM288 512h128c52.8 0 96-43.2 96-96v-64.832H288V512z'/%3e%3c/svg%3e");
-$bg-icon-object: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='none' d='M256 96L76.8 208 256 320l179.2-112z'/%3e%3cpath fill='%23000000' d='M256 512l256-160V160L255.99 0 0 160v192l256 160zm0-416l179.2 112L256 320 76.8 208 256 96z'/%3e%3c/svg%3e");
-$bg-icon-object-unknown: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M255-1L-1 159v192l256 160 256-160V159L255-1zm37.7 430.6c-10.6 10.4-23 15.4-38 15.4-15.6 0-28.1-4.9-38.1-14.8-10-10-14.8-22.4-14.8-38.1 0-15.2 5.1-27.6 15.5-38.1s22.6-15.6 37.4-15.6c14.8 0 27.1 5.2 37.8 16 10.7 10.8 15.9 23.2 15.9 38-.1 14.5-5.4 27-15.7 37.2zm26.4-156.3c-11.8 5.9-18.7 11-21.7 16.2-1.8 3.1-3 7.4-3.7 13.4v20.5H213v-22.1c0-20.1 2.2-34.9 6.5-44 4-8.6 11.3-15.1 22.4-20l17.4-7.7c16-7.1 24.1-17.6 24.1-31.4 0-8-3-15.2-8.6-20.9-5.6-5.6-12.8-8.6-20.8-8.6-12 0-27.2 5-31.4 28.7l-1.1 6.1H148l.7-8.1c2-22.3 8.5-41.2 19.4-56.1 9.8-13.5 22.8-24.3 38.5-32.3 15.7-8 32.3-12 49.1-12 30.3 0 55.1 9.7 75.7 29.8 20.6 20 30.6 44 30.6 73.6 0 35.4-14.4 60.7-42.9 74.9z'/%3e%3c/svg%3e");
-$bg-icon-packet: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='none' d='M256 96L76.8 208 256 320l179.2-112z'/%3e%3cpath fill='%23000000' d='M256 0L0 160v256c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V160L256 0zm0 96l179.2 112L256 320 76.8 208 256 96z'/%3e%3c/svg%3e");
-$bg-icon-page: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M352 256c-52.8 0-96-43.2-96-96V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V256H352z'/%3e%3cpath fill='%23000000' d='M384 192h128L320 0v128c0 35.2 28.8 64 64 64z'/%3e%3c/svg%3e");
-$bg-icon-plot-overlay: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M415 0H97C43.65 0 0 43.65 0 97v203.41c7.09 9.32 12.83 14.17 16 15.42 7.14-2.81 27.22-23.77 46.48-73C83.71 188.64 120.64 124 176 124c26.2 0 50.71 14.58 72.85 43.34 18.67 24.25 32.42 54.46 40.67 75.54 19.26 49.19 39.34 70.15 46.48 73 7.14-2.81 27.22-23.77 46.48-73C403.71 188.64 440.64 124 496 124a69.55 69.55 0 0 1 16 1.87V97c0-53.35-43.65-97-97-97z'/%3e%3cpath fill='%23000000' d='M496 196.17c-7.14 2.81-27.22 23.76-46.48 73C428.29 323.36 391.36 388 336 388c-26.2 0-50.71-14.58-72.85-43.34-18.67-24.25-32.42-54.46-40.67-75.54-19.26-49.19-39.34-70.15-46.48-73-7.14 2.81-27.22 23.76-46.48 73C108.29 323.36 71.36 388 16 388a69.56 69.56 0 0 1-16-1.87V415c0 53.35 43.65 97 97 97h318c53.35 0 97-43.65 97-97V211.59c-7.09-9.32-12.83-14.17-16-15.42z'/%3e%3c/svg%3e");
-$bg-icon-plot-stacked: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M44.8 156c12.49 0 24.48-13.26 42.76-35.09 22.71-27.14 51-60.91 98-60.91 22.32 0 43.31 7.73 62.4 23 14.34 11.45 25.58 25.21 36.46 38.53C303.63 145 314 156 326.4 156H512V97c0-53.35-43.65-97-97-97H97C43.65 0 0 43.65 0 97v59h44.8z'/%3e%3cpath fill='%23000000' d='M264.75 205.2c-14.12-11.32-25.26-25-36-38.14C211 145.32 199.37 132 185.6 132c-12.53 0-24.54 13.27-42.83 35.12-22.7 27.12-51 60.88-98 60.88H0v56h185.6c22 0 42.77 7.67 61.65 22.8 14.12 11.32 25.26 25 36 38.14C301 366.68 312.63 380 326.4 380c12.53 0 24.54-13.27 42.83-35.12 22.7-27.12 51-60.88 98-60.88H512v-56H326.4c-22.03 0-42.77-7.67-61.65-22.8z'/%3e%3cpath fill='%23000000' d='M467.2 356c-12.49 0-24.48 13.26-42.76 35.09-22.71 27.14-51 60.91-98 60.91-22.32 0-43.31-7.73-62.4-23-14.34-11.45-25.58-25.21-36.46-38.53C208.37 367 198 356 185.6 356H0v59c0 53.35 43.65 97 97 97h318c53.35 0 97-43.65 97-97v-59h-44.8z'/%3e%3c/svg%3e");
-$bg-icon-session: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M317.8 262.2c3.3 2.1 6.6 4.3 9.6 6.8l60.2 48.2c14.8 11.9 41.9 11.9 56.7 0l67.6-54c.1-2.4.1-4.7.1-7.1 0-26.1-3.9-51.2-11.1-74.9L423.5 243c-29.1 23.3-70.1 29.6-105.7 19.2zM124.3 317.1l60.2-48.2c29-23.2 70-29.6 105.6-19.2-3.3-2.1-6.6-4.3-9.6-6.8l-60.2-48.2c-14.8-11.9-41.9-11.9-56.7 0L103.5 243c-20 16-45.7 24-71.5 24-10.8 0-21.5-1.4-31.9-4.2v.8c2.5 1.7 5 3.4 7.3 5.3l60.2 48.2c14.9 11.9 41.9 11.9 56.7 0z'/%3e%3cpath fill='%23000000' d='M60.3 189.1l60.2-48.2c40.1-32.1 102.8-32.1 142.9 0l60.2 48.2c14.8 11.9 41.9 11.9 56.7 0l90.5-72.4C425.2 46.5 346 0 256 0 136.7 0 36.4 81.6 8 192.1c15.4 8.8 38.9 7.8 52.3-3zM344.5 371l-60.2-48.2c-14.8-11.9-41.9-11.9-56.7 0L167.5 371c-20 16-45.7 24-71.5 24-23.9 0-47.7-6.9-67.1-20.7C71.7 456.1 157.3 512 256 512s184.3-55.9 227.1-137.7c-40.2 28.7-99.9 27.6-138.6-3.3z'/%3e%3c/svg%3e");
-$bg-icon-tabular: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 0H64C28.8 0 0 28.8 0 64v384c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V64c0-35.2-28.8-64-64-64zM320 224H192v-96h128v96zm-128 32h128v96H192v-96zm-32 96H32v-96h128v96zm0-224v96H32v-96h128zM64 480c-8.5 0-16.5-3.3-22.6-9.4S32 456.5 32 448v-64h128v96H64zm128 0v-96h128v96H192zm288-32c0 8.5-3.3 16.5-9.4 22.6S456.5 480 448 480h-96v-96h128v64zm0-96H352v-96h128v96zm0-128H352v-96h128v96z'/%3e%3c/svg%3e");
-$bg-icon-tabular-lad: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 0H64C28.7.1.1 28.7 0 64v384c.1 35.3 28.7 63.9 64 64h384c35.3-.1 63.9-28.7 64-64V64c-.1-35.3-28.7-63.9-64-64zM32 128h128v96H32v-96zm0 128h128v96H32v-96zm32 224c-17.6-.1-31.9-14.4-32-32v-64h128v96H64zm128 0v-96h128v96H192zm288-32c-.1 17.6-14.4 31.9-32 32h-96v-96h128v64zm0-192v96H192v-96h32v-32h-32v-96h288v96h-32v32h32z'/%3e%3cpath fill='%23000000' d='M391.2 273.7L336 246.1V160c0-8.8-7.2-16-16-16s-16 7.2-16 16v105.9l72.8 36.4c7.9 4 17.5.8 21.5-7.2 4-7.8.8-17.5-7.1-21.4z'/%3e%3c/svg%3e");
-$bg-icon-tabular-lad-set: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M64 384V96c-35.3.1-63.9 28.7-64 64v288c.1 35.3 28.7 63.9 64 64h288c35.3-.1 63.9-28.7 64-64H128c-35.3-.1-63.9-28.7-64-64z'/%3e%3cpath fill='%23000000' d='M448 0H160c-35.3.1-63.9 28.7-64 64v288c.1 35.3 28.7 63.9 64 64h288c35.3-.1 63.9-28.7 64-64V64c-.1-35.3-28.7-63.9-64-64zM128 96h96v64h-96V96zm0 96h96v96h-96v-96zm32 192c-17.6-.1-31.9-14.4-32-32v-32h96v64h-64zm96 0v-64h96v64h-96zm224-32c-.1 17.6-14.4 31.9-32 32h-64v-64h96v32zm0-64H256V96h224v192z'/%3e%3cpath fill='%23000000' d='M416 240c8.8 0 16-7.2 16-16 0-6.9-4.4-13-10.9-15.2L384 196.5V144c0-8.8-7.2-16-16-16s-16 7.2-16 16v75.5l58.9 19.6c1.7.6 3.4.9 5.1.9z'/%3e%3c/svg%3e");
-$bg-icon-tabular-realtime: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M0 64v384c0 35.2 28.8 64 64 64h288c35.2 0 64-28.8 64-64V340c-19.8 7.8-41.4 12-64 12-35.4 0-68.4-10.5-96-28.6V352h-96v-96h35.3c-5.2-10.1-9.4-20.8-12.6-32H160v-96h22.7C203.6 54.2 271.6 0 352 0H64C28.8 0 0 28.8 0 64zm288 320h96v64c0 8.5-3.3 16.5-9.4 22.6S360.5 480 352 480h-64v-96zm-160 96H64c-8.5 0-16.5-3.3-22.6-9.4S32 456.5 32 448v-64h96v96zm0-128H32v-96h96v96zm32 32h96v96h-96v-96zm-32-160H32v-96h96v96z'/%3e%3cpath fill='%23000000' d='M192 160c0 88.4 71.6 160 160 160s160-71.6 160-160S440.4 0 352 0 192 71.6 192 160zm49.7 39.8L227 187.5c-1.4-6.4-2.3-12.9-2.7-19.6 15.1-.1 30.1-5 41.9-14.8l39.6-33c7.5-6.2 21.1-6.2 28.6 0l39.6 33c2.8 2.3 5.7 4.3 8.8 6.1-23-11.7-52.7-9.2-72.8 7.5l-39.6 33c-7.6 6.3-21.2 6.3-28.7.1zM352 288c-36.7 0-69.7-15.4-93-40.1 14.2-.6 28.1-5.5 39.2-14.7l39.6-33c7.5-6.2 21.1-6.2 28.6 0l39.6 33c11 9.2 25 14.1 39.2 14.7-23.5 24.7-56.5 40.1-93.2 40.1zm125.9-151.3c1.4 7.5 2.1 15.3 2.1 23.3 0 9.4-1 18.6-3 27.5l-14.7 12.3c-7.5 6.2-21.1 6.2-28.6 0l-39.6-33c-2.8-2.3-5.7-4.3-8.8-6.1 23 11.7 52.7 9.2 72.8-7.5l19.8-16.5zM352 32c46.4 0 87.1 24.7 109.5 61.7l-31.2 26c-7.5 6.2-21.1 6.2-28.6 0l-39.6-33c-23.6-19.7-60.6-19.7-84.3 0l-39.6 33c-2.5 2.1-5.7 3.5-9.1 4.2C244.7 70.8 293.8 32 352 32z'/%3e%3c/svg%3e");
-$bg-icon-tabular-scrolling: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M32 0C14.4 0 0 14.4 0 32v96h224V0H32zM512 128V32c0-17.6-14.4-32-32-32H288v128h224zM0 192v96c0 17.6 14.4 32 32 32h192V192H0zM480 320c17.6 0 32-14.4 32-32v-96H288v128h192zM256 512L128 384h256z'/%3e%3c/svg%3e");
-$bg-icon-telemetry: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M16 315.83c7.14-2.81 27.22-23.77 46.48-73C83.71 188.64 120.64 124 176 124c26.2 0 50.71 14.58 72.85 43.34 18.67 24.25 32.42 54.46 40.67 75.54 19.26 49.19 39.34 70.15 46.48 73 7.14-2.81 27.22-23.77 46.48-73 18.7-47.75 49.57-103.57 94.47-116.23A255.87 255.87 0 0 0 256 0C114.62 0 0 114.62 0 256a257.18 257.18 0 0 0 5 50.52c4.77 5.39 8.61 8.37 11 9.31z'/%3e%3cpath fill='%23000000' d='M496 196.17c-7.14 2.81-27.22 23.76-46.48 73C428.29 323.36 391.36 388 336 388c-26.2 0-50.71-14.58-72.85-43.34-18.67-24.25-32.42-54.46-40.67-75.54-19.26-49.19-39.34-70.15-46.48-73-7.14 2.81-27.22 23.76-46.48 73-18.7 47.75-49.57 103.57-94.47 116.23A255.87 255.87 0 0 0 256 512c141.38 0 256-114.62 256-256a257.18 257.18 0 0 0-5-50.52c-4.77-5.39-8.61-8.37-11-9.31z'/%3e%3c/svg%3e");
+$bg-icon-clock: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M288 32H160l160 160H174.872C152.74 153.742 111.377 128 64 128H0v256h64c47.377 0 88.74-25.742 110.872-64H320L160 480h128l224-224L288 32z'/%3e%3c/svg%3e");
+$bg-icon-database: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 0C148.6 0 56.6 66.2 18.6 160H64c28.4 0 54 12.4 71.5 32H256l-96-96h128l160 160-160 160H160l96-96H135.5C118 339.6 92.4 352 64 352H18.6c38 93.8 129.9 160 237.4 160 141.4 0 256-114.6 256-256S397.4 0 256 0z'/%3e%3c/svg%3e");
+$bg-icon-database-query: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M96 0C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h32V0H96zM192 0h128v512H192zM416 0h-32v352h128V96c0-52.8-43.2-96-96-96z'/%3e%3c/svg%3e");
+$bg-icon-dataset: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256 256-114.6 256-256S397.4 0 256 0zm135 345c-6.4 11.1-18.3 18-31.2 18-6.3 0-12.5-1.7-18-4.8l-110.9-64-.1-.1c-.4-.2-.8-.5-1.2-.7l-.4-.3-.9-.6-.6-.5-.6-.5-.9-.7-.3-.3c-.4-.3-.7-.6-1.1-.9-2.5-2.3-4.7-5-6.5-7.9-.1-.2-.3-.5-.4-.7s-.3-.5-.4-.7c-1.6-3-2.9-6.2-3.6-9.6v-.1c-.1-.5-.2-.9-.3-1.4 0-.1 0-.3-.1-.4-.1-.3-.1-.7-.1-1.1s-.1-.5-.1-.8 0-.5-.1-.8-.1-.8-.1-1.1v-.5-1.4V81c0-19.9 16.1-36 36-36s36 16.1 36 36v161.2l92.9 53.6c17.1 10 22.9 32 13 49.2z'/%3e%3c/svg%3e");
+$bg-icon-datatable: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 256C114.615 256 0 213.019 0 160v256c0 53.019 114.615 96 256 96s256-42.981 256-96V160c0 53.019-114.615 96-256 96z'/%3e%3cellipse fill='%23000000' cx='256' cy='96' rx='256' ry='96'/%3e%3c/svg%3e");
+$bg-icon-dictionary: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M341.76 409.643C316.369 423.871 287.118 432 256 432c-97.047 0-176-78.953-176-176S158.953 80 256 80s176 78.953 176 176c0 31.118-8.129 60.369-22.357 85.76l95.846 95.846C509.747 430.661 512 423.429 512 416V96c0-53.019-114.615-96-256-96S0 42.981 0 96v320c0 53.019 114.615 96 256 96 63.055 0 120.774-8.554 165.388-22.73l-79.628-79.627z'/%3e%3cpath fill='%23000000' d='M176 256c0 44.112 35.888 80 80 80s80-35.888 80-80-35.888-80-80-80-80 35.888-80 80z'/%3e%3c/svg%3e");
+$bg-icon-folder: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 96H288l-54.6-54.6-18.7-18.7C202.2 10.2 177.6 0 160 0H32C14.4 0 0 14.4 0 32v192c0-35.2 28.8-64 64-64h384c35.2 0 64 28.8 64 64v-64c0-35.2-28.8-64-64-64zM448 224H64c-35.2 0-64 28.8-64 64v160c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64zM160 448H96V288h64v160zm128 0h-64V288h64v160zm128 0h-64V288h64v160z'/%3e%3c/svg%3e");
+$bg-icon-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 256C114.6 256 0 213 0 160v256c0 53 114.6 96 256 96s256-43 256-96V160c0 53-114.6 96-256 96zm192 31.5v128c-18.3 7.8-39.9 14.4-64 19.7v-128c24.1-5.3 45.7-11.9 64-19.7zm-320 19.7v128c-24.1-5.2-45.7-11.9-64-19.7v-128c18.3 7.8 39.9 14.4 64 19.7zM192 445V317c20.5 2 41.9 3 64 3s43.5-1.1 64-3v128c-20.5 2-41.9 3-64 3s-43.5-1.1-64-3z'/%3e%3cellipse fill='%23000000' cx='256' cy='96' rx='256' ry='96'/%3e%3c/svg%3e");
+$bg-icon-layout: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M416 320c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96v160l-64-32-64 32V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96v-96c0 52.8-43.2 96-96 96H96v-96h320z'/%3e%3c/svg%3e");
+$bg-icon-object: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 96H288l-54.6-54.6-18.7-18.7C202.2 10.2 177.6 0 160 0H32C14.4 0 0 14.4 0 32v192c0-35.2 28.8-64 64-64h384c35.2 0 64 28.8 64 64v-64c0-35.2-28.8-64-64-64zM448 224H64c-35.2 0-64 28.8-64 64v160c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64z'/%3e%3c/svg%3e");
+$bg-icon-object-unknown: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 0H64C28.8 0 0 28.8 0 64v384c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V64c0-35.2-28.8-64-64-64zm0 448H64V64h384v384z'/%3e%3cpath fill='%23000000' d='M160 128l-64 64v224h320V256l-64-64-64 64z'/%3e%3c/svg%3e");
+$bg-icon-packet: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M224 0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h128V0zM416 0H288v288.832h224V96c0-52.8-43.2-96-96-96zM288 512h128c52.8 0 96-43.2 96-96v-64.832H288V512z'/%3e%3c/svg%3e");
+$bg-icon-page: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='none' d='M256 96L76.8 208 256 320l179.2-112z'/%3e%3cpath fill='%23000000' d='M256 512l256-160V160L255.99 0 0 160v192l256 160zm0-416l179.2 112L256 320 76.8 208 256 96z'/%3e%3c/svg%3e");
+$bg-icon-plot-overlay: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M255-1L-1 159v192l256 160 256-160V159L255-1zm37.7 430.6c-10.6 10.4-23 15.4-38 15.4-15.6 0-28.1-4.9-38.1-14.8-10-10-14.8-22.4-14.8-38.1 0-15.2 5.1-27.6 15.5-38.1s22.6-15.6 37.4-15.6c14.8 0 27.1 5.2 37.8 16 10.7 10.8 15.9 23.2 15.9 38-.1 14.5-5.4 27-15.7 37.2zm26.4-156.3c-11.8 5.9-18.7 11-21.7 16.2-1.8 3.1-3 7.4-3.7 13.4v20.5H213v-22.1c0-20.1 2.2-34.9 6.5-44 4-8.6 11.3-15.1 22.4-20l17.4-7.7c16-7.1 24.1-17.6 24.1-31.4 0-8-3-15.2-8.6-20.9-5.6-5.6-12.8-8.6-20.8-8.6-12 0-27.2 5-31.4 28.7l-1.1 6.1H148l.7-8.1c2-22.3 8.5-41.2 19.4-56.1 9.8-13.5 22.8-24.3 38.5-32.3 15.7-8 32.3-12 49.1-12 30.3 0 55.1 9.7 75.7 29.8 20.6 20 30.6 44 30.6 73.6 0 35.4-14.4 60.7-42.9 74.9z'/%3e%3c/svg%3e");
+$bg-icon-plot-stacked: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='none' d='M256 96L76.8 208 256 320l179.2-112z'/%3e%3cpath fill='%23000000' d='M256 0L0 160v256c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V160L256 0zm0 96l179.2 112L256 320 76.8 208 256 96z'/%3e%3c/svg%3e");
+$bg-icon-session: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M352 256c-52.8 0-96-43.2-96-96V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V256H352z'/%3e%3cpath fill='%23000000' d='M384 192h128L320 0v128c0 35.2 28.8 64 64 64z'/%3e%3c/svg%3e");
+$bg-icon-tabular: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M415 0H97C43.65 0 0 43.65 0 97v203.41c7.09 9.32 12.83 14.17 16 15.42 7.14-2.81 27.22-23.77 46.48-73C83.71 188.64 120.64 124 176 124c26.2 0 50.71 14.58 72.85 43.34 18.67 24.25 32.42 54.46 40.67 75.54 19.26 49.19 39.34 70.15 46.48 73 7.14-2.81 27.22-23.77 46.48-73C403.71 188.64 440.64 124 496 124a69.55 69.55 0 0 1 16 1.87V97c0-53.35-43.65-97-97-97z'/%3e%3cpath fill='%23000000' d='M496 196.17c-7.14 2.81-27.22 23.76-46.48 73C428.29 323.36 391.36 388 336 388c-26.2 0-50.71-14.58-72.85-43.34-18.67-24.25-32.42-54.46-40.67-75.54-19.26-49.19-39.34-70.15-46.48-73-7.14 2.81-27.22 23.76-46.48 73C108.29 323.36 71.36 388 16 388a69.56 69.56 0 0 1-16-1.87V415c0 53.35 43.65 97 97 97h318c53.35 0 97-43.65 97-97V211.59c-7.09-9.32-12.83-14.17-16-15.42z'/%3e%3c/svg%3e");
+$bg-icon-tabular-lad: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M44.8 156c12.49 0 24.48-13.26 42.76-35.09 22.71-27.14 51-60.91 98-60.91 22.32 0 43.31 7.73 62.4 23 14.34 11.45 25.58 25.21 36.46 38.53C303.63 145 314 156 326.4 156H512V97c0-53.35-43.65-97-97-97H97C43.65 0 0 43.65 0 97v59h44.8z'/%3e%3cpath fill='%23000000' d='M264.75 205.2c-14.12-11.32-25.26-25-36-38.14C211 145.32 199.37 132 185.6 132c-12.53 0-24.54 13.27-42.83 35.12-22.7 27.12-51 60.88-98 60.88H0v56h185.6c22 0 42.77 7.67 61.65 22.8 14.12 11.32 25.26 25 36 38.14C301 366.68 312.63 380 326.4 380c12.53 0 24.54-13.27 42.83-35.12 22.7-27.12 51-60.88 98-60.88H512v-56H326.4c-22.03 0-42.77-7.67-61.65-22.8z'/%3e%3cpath fill='%23000000' d='M467.2 356c-12.49 0-24.48 13.26-42.76 35.09-22.71 27.14-51 60.91-98 60.91-22.32 0-43.31-7.73-62.4-23-14.34-11.45-25.58-25.21-36.46-38.53C208.37 367 198 356 185.6 356H0v59c0 53.35 43.65 97 97 97h318c53.35 0 97-43.65 97-97v-59h-44.8z'/%3e%3c/svg%3e");
+$bg-icon-tabular-lad-set: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M317.8 262.2c3.3 2.1 6.6 4.3 9.6 6.8l60.2 48.2c14.8 11.9 41.9 11.9 56.7 0l67.6-54c.1-2.4.1-4.7.1-7.1 0-26.1-3.9-51.2-11.1-74.9L423.5 243c-29.1 23.3-70.1 29.6-105.7 19.2zM124.3 317.1l60.2-48.2c29-23.2 70-29.6 105.6-19.2-3.3-2.1-6.6-4.3-9.6-6.8l-60.2-48.2c-14.8-11.9-41.9-11.9-56.7 0L103.5 243c-20 16-45.7 24-71.5 24-10.8 0-21.5-1.4-31.9-4.2v.8c2.5 1.7 5 3.4 7.3 5.3l60.2 48.2c14.9 11.9 41.9 11.9 56.7 0z'/%3e%3cpath fill='%23000000' d='M60.3 189.1l60.2-48.2c40.1-32.1 102.8-32.1 142.9 0l60.2 48.2c14.8 11.9 41.9 11.9 56.7 0l90.5-72.4C425.2 46.5 346 0 256 0 136.7 0 36.4 81.6 8 192.1c15.4 8.8 38.9 7.8 52.3-3zM344.5 371l-60.2-48.2c-14.8-11.9-41.9-11.9-56.7 0L167.5 371c-20 16-45.7 24-71.5 24-23.9 0-47.7-6.9-67.1-20.7C71.7 456.1 157.3 512 256 512s184.3-55.9 227.1-137.7c-40.2 28.7-99.9 27.6-138.6-3.3z'/%3e%3c/svg%3e");
+$bg-icon-tabular-realtime: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 0H64C28.8 0 0 28.8 0 64v384c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V64c0-35.2-28.8-64-64-64zM320 224H192v-96h128v96zm-128 32h128v96H192v-96zm-32 96H32v-96h128v96zm0-224v96H32v-96h128zM64 480c-8.5 0-16.5-3.3-22.6-9.4S32 456.5 32 448v-64h128v96H64zm128 0v-96h128v96H192zm288-32c0 8.5-3.3 16.5-9.4 22.6S456.5 480 448 480h-96v-96h128v64zm0-96H352v-96h128v96zm0-128H352v-96h128v96z'/%3e%3c/svg%3e");
+$bg-icon-tabular-scrolling: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 0H64C28.7.1.1 28.7 0 64v384c.1 35.3 28.7 63.9 64 64h384c35.3-.1 63.9-28.7 64-64V64c-.1-35.3-28.7-63.9-64-64zM32 128h128v96H32v-96zm0 128h128v96H32v-96zm32 224c-17.6-.1-31.9-14.4-32-32v-64h128v96H64zm128 0v-96h128v96H192zm288-32c-.1 17.6-14.4 31.9-32 32h-96v-96h128v64zm0-192v96H192v-96h32v-32h-32v-96h288v96h-32v32h32z'/%3e%3cpath fill='%23000000' d='M391.2 273.7L336 246.1V160c0-8.8-7.2-16-16-16s-16 7.2-16 16v105.9l72.8 36.4c7.9 4 17.5.8 21.5-7.2 4-7.8.8-17.5-7.1-21.4z'/%3e%3c/svg%3e");
+$bg-icon-telemetry: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M64 384V96c-35.3.1-63.9 28.7-64 64v288c.1 35.3 28.7 63.9 64 64h288c35.3-.1 63.9-28.7 64-64H128c-35.3-.1-63.9-28.7-64-64z'/%3e%3cpath fill='%23000000' d='M448 0H160c-35.3.1-63.9 28.7-64 64v288c.1 35.3 28.7 63.9 64 64h288c35.3-.1 63.9-28.7 64-64V64c-.1-35.3-28.7-63.9-64-64zM128 96h96v64h-96V96zm0 96h96v96h-96v-96zm32 192c-17.6-.1-31.9-14.4-32-32v-32h96v64h-64zm96 0v-64h96v64h-96zm224-32c-.1 17.6-14.4 31.9-32 32h-64v-64h96v32zm0-64H256V96h224v192z'/%3e%3cpath fill='%23000000' d='M416 240c8.8 0 16-7.2 16-16 0-6.9-4.4-13-10.9-15.2L384 196.5V144c0-8.8-7.2-16-16-16s-16 7.2-16 16v75.5l58.9 19.6c1.7.6 3.4.9 5.1.9z'/%3e%3c/svg%3e");
$bg-icon-timeline: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M416 0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96ZM64 160V96h128v64Zm64 64h192v64H128Zm320 192H224v-64h224Zm0-128h-64v-64h64Zm0-128H256V96h192Z'/%3e%3c/svg%3e");
-$bg-icon-timer: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M288 73.3V32.01a32 32 0 0 0-32-32h-64a32 32 0 0 0-32 32V73.3C67.48 100.84 0 186.54 0 288.01c0 123.71 100.29 224 224 224s224-100.29 224-224c0-101.48-67.5-187.2-160-214.71zm-54 224.71l-131.88 105.5A167.4 167.4 0 0 1 56 288.01c0-92.64 75.36-168 168-168 3.36 0 6.69.11 10 .31v177.69z'/%3e%3c/svg%3e");
-$bg-icon-topic: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M227.18 238.32l43.15-43.15a25.18 25.18 0 0 1 35.36 0l43.15 43.15a94.42 94.42 0 0 0 35.18 22.25V174.5l-28.82-28.82a95.11 95.11 0 0 0-134.35 0l-43.15 43.15a25.18 25.18 0 0 1-35.36 0L128 174.5v86.07a95.11 95.11 0 0 0 99.18-22.25z'/%3e%3cpath fill='%23000000' d='M252.82 273.68l-43.15 43.15a25.18 25.18 0 0 1-35.36 0l-43.15-43.15c-1-1-2.1-2-3.18-3v98.68a95.11 95.11 0 0 0 131.18-3l43.15-43.15a25.18 25.18 0 0 1 35.36 0l43.15 43.15c1 1 2.1 2 3.18 3v-98.68a95.11 95.11 0 0 0-131.18 3z'/%3e%3cpath fill='%23000000' d='M416 0h-64v96h63.83l.17.17v319.66l-.17.17H352v96h64c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96zM160 416H96.17l-.17-.17V96.17l.17-.17H160V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h64v-96z'/%3e%3c/svg%3e");
+$bg-icon-timer: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M32 0C14.4 0 0 14.4 0 32v96h224V0H32zM512 128V32c0-17.6-14.4-32-32-32H288v128h224zM0 192v96c0 17.6 14.4 32 32 32h192V192H0zM480 320c17.6 0 32-14.4 32-32v-96H288v128h192zM256 512L128 384h256z'/%3e%3c/svg%3e");
+$bg-icon-topic: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M16 315.83c7.14-2.81 27.22-23.77 46.48-73C83.71 188.64 120.64 124 176 124c26.2 0 50.71 14.58 72.85 43.34 18.67 24.25 32.42 54.46 40.67 75.54 19.26 49.19 39.34 70.15 46.48 73 7.14-2.81 27.22-23.77 46.48-73 18.7-47.75 49.57-103.57 94.47-116.23A255.87 255.87 0 0 0 256 0C114.62 0 0 114.62 0 256a257.18 257.18 0 0 0 5 50.52c4.77 5.39 8.61 8.37 11 9.31z'/%3e%3cpath fill='%23000000' d='M496 196.17c-7.14 2.81-27.22 23.76-46.48 73C428.29 323.36 391.36 388 336 388c-26.2 0-50.71-14.58-72.85-43.34-18.67-24.25-32.42-54.46-40.67-75.54-19.26-49.19-39.34-70.15-46.48-73-7.14 2.81-27.22 23.76-46.48 73-18.7 47.75-49.57 103.57-94.47 116.23A255.87 255.87 0 0 0 256 512c141.38 0 256-114.62 256-256a257.18 257.18 0 0 0-5-50.52c-4.77-5.39-8.61-8.37-11-9.31z'/%3e%3c/svg%3e");
$bg-icon-box-with-dashed-lines: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M0 192h64v128H0zM64 64.11l.11-.11H160V0H64A64.19 64.19 0 0 0 0 64v96h64V64.11zM64 447.89V352H0v96a64.19 64.19 0 0 0 64 64h96v-64H64.11zM192 0h128v64H192zM448 447.89l-.11.11H352v64h96a64.19 64.19 0 0 0 64-64v-96h-64v95.89zM448 0h-96v64h95.89l.11.11V160h64V64a64.19 64.19 0 0 0-64-64zM448 192h64v128h-64zM192 448h128v64H192zM128 128h256v256H128z'/%3e%3c/svg%3e");
-$bg-icon-summary-widget: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 0H64C28.8 0 0 28.8 0 64v384c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V64c0-35.2-28.8-64-64-64zm-24.1 305.2l-41.3 71.6-94.8-65.8 9.6 115h-82.7l9.6-115-94.8 65.8-41.3-71.6L192.5 256 88.1 206.8l41.3-71.6 94.8 65.8-9.6-115h82.7l-9.6 115 94.8-65.8 41.3 71.6L319.5 256l104.4 49.2z'/%3e%3c/svg%3e");
-$bg-icon-notebook: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 55.4c0-39.9-27.7-63.7-61.5-52.7L0 128h448V55.4zM448 160H0v288c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64zm-32 256H224V256h192v160z'/%3e%3c/svg%3e");
+$bg-icon-summary-widget: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M128 128h192v64H128zM192 224h192v64H192zM160 320h192v64H160z'/%3e%3cpath fill='%23000000' d='M416 0h-64v96h63.8c.1 0 .1.1.2.2v319.7c0 .1-.1.1-.2.2H352v96h64c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96zM96 415.8V96.2c0-.1.1-.1.2-.2H160V0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h64v-96H96.2c-.1 0-.2-.1-.2-.2z'/%3e%3c/svg%3e");
+$bg-icon-notebook: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M288 73.3V32.01a32 32 0 0 0-32-32h-64a32 32 0 0 0-32 32V73.3C67.48 100.84 0 186.54 0 288.01c0 123.71 100.29 224 224 224s224-100.29 224-224c0-101.48-67.5-187.2-160-214.71zm-54 224.71l-131.88 105.5A167.4 167.4 0 0 1 56 288.01c0-92.64 75.36-168 168-168 3.36 0 6.69.11 10 .31v177.69z'/%3e%3c/svg%3e");
$bg-icon-tabs-view: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M0 448a64.2 64.2 0 0 0 64 64h384a64.2 64.2 0 0 0 64-64V144H256L230.9 31.2C227.1 14.1 209.6 0 192 0H64A64.2 64.2 0 0 0 0 64zm416-64H96V256h320z'/%3e%3cpath d='M240 0c17.6 0 35.1 14.1 38.9 31.2l18 80.8H512V64a64.2 64.2 0 0 0-64-64z'/%3e%3c/svg%3e");
$bg-icon-flexible-layout: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M0 416c0 52.8 43.2 96 96 96h32V224H0zM0 96v64h128V0H96C43.2 0 0 43.2 0 96zM384 512h32c52.8 0 96-43.2 96-96v-64H384zM192 0h128v512H192zM416 0h-32v288h128V96c0-52.8-43.2-96-96-96z'/%3e%3c/svg%3e");
$bg-icon-generator-telemetry: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M76 236.9c5.4-2.1 20.4-17.8 34.9-54.7C126.8 141.5 154.5 93 196 93c19.7 0 38 10.9 54.6 32.5 14 18.2 24.4 40.8 30.5 56.7 14.5 36.9 29.5 52.6 34.9 54.7 5.4-2.1 20.4-17.8 34.9-54.7S388 104.5 421.7 95A192 192 0 0 0 256 0C150 0 64 86 64 192a197.2 197.2 0 0 0 3.7 37.9c3.6 4 6.5 6.3 8.3 7zM442.3 238.5A192.9 192.9 0 0 0 448 192a197.2 197.2 0 0 0-3.7-37.9c-3.6-4-6.5-6.3-8.3-7-5.4 2.1-20.4 17.8-34.9 54.7-10.9 27.9-27.3 59.5-50 76.6z'/%3e%3cpath d='M256 320l67.5-29.5a60.3 60.3 0 0 1-7.5.5c-19.7 0-38-10.9-54.6-32.5-14-18.2-24.4-40.8-30.5-56.7-14.5-36.9-29.5-52.6-34.9-54.7-5.4 2.1-20.4 17.8-34.9 54.7-8.2 21.1-19.6 44.2-34.4 61.6z'/%3e%3cpath d='M512 240L256 352 0 240v160l256 112 256-112V240z'/%3e%3c/svg%3e");
-$bg-icon-generator-events: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M160 96h192v32H160zM160 224h192v32H160zM160 160h160v32H160z'/%3e%3cpath d='M128 64.1h256V264l64-28V64a64.2 64.2 0 0 0-64-64H128a64.2 64.2 0 0 0-64 64v172l64 28zM329.1 288H182.9l73.1 32 73.1-32z'/%3e%3cpath d='M256 352L0 240v160l256 112 256-112V240L256 352z'/%3e%3c/svg%3e");
+$bg-icon-generator-events: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M160 96h192v32H160zM160 224h192v32H160zM160 160h160v32H160z'/%3e%3cpath d='M128 64.1h256V264l64-28V64a64.2 64.2 0 0 0-64-64H128a64.2 64.2 0 0 0-64 64v172l64 28zM329.1 288H182.9l73.1 32 73.1-32z'/%3e%3cpath d='M256 352L0 240v160l256 112 256-112V240L256 352z'/%3e%3c/svg%3e");
$bg-icon-gauge: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M256 0C114.6 0 0 114.6 0 256c0 113.2 73.5 209.2 175.3 243L304 256v251.5C422.4 485 512 381 512 256 512 114.6 397.4 0 256 0zm121.4 263.9a159.8 159.8 0 0 0-242.8 0l-73-62.5c4.3-5 8.7-9.8 13.4-14.4a255.9 255.9 0 0 1 362 0c4.7 4.6 9.1 9.4 13.4 14.4z'/%3e%3c/svg%3e");
$bg-icon-spectra: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M384 352H128l51.2-89.6L0 288v127c0 53.3 43.7 97 97 97h318c53.4 0 97-43.7 97-97v-31l-162.9-93.1zM415 0H97C43.7 0 0 43.6 0 97v159l200-30.1 56-97.9 54.9 96H512V97a97.2 97.2 0 00-97-97zM512 320v-32l-192-32 192 64z'/%3e%3c/svg%3e");
$bg-icon-spectra-telemetry: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 128l54.9 96H510C494.3 97.7 386.5 0 256 0 114.6 0 0 114.6 0 256l200-30.1zM384 352H128l51.2-89.6L2 287.7C17.6 414.1 125.4 512 256 512c100.8 0 188-58.3 229.8-143l-136.7-78.1zM320 256l192 64v-32l-192-32z'/%3e%3c/svg%3e");
@@ -328,3 +326,4 @@ $bg-icon-map: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.
$bg-icon-plan: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cg data-name='Layer 2'%3e%3cg data-name='Layer 1'%3e%3cpath fill='%23000000' d='M128 96V64a64.19 64.19 0 0 1 64-64h128a64.19 64.19 0 0 1 64 64v32Z'/%3e%3cpath fill='%23000000' d='M416 64v64H96V64c-52.8 0-96 43.2-96 96v256c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V160c0-52.8-43.2-96-96-96ZM64 288v-64h128v64Zm256 128H128v-64h192Zm128 0h-64v-64h64Zm0-128H256v-64h192Z'/%3e%3c/g%3e%3c/g%3e%3c/svg%3e");
$bg-icon-timelist: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cg data-name='Layer 2'%3e%3cpath d='M448 0H64A64.19 64.19 0 0 0 0 64v384a64.19 64.19 0 0 0 64 64h384a64.19 64.19 0 0 0 64-64V64a64.19 64.19 0 0 0-64-64ZM213.47 266.73a24 24 0 0 1-32.2 10.74L104 238.83V128a24 24 0 0 1 48 0v81.17l50.73 25.36a24 24 0 0 1 10.74 32.2ZM448 448H288v-64h160Zm0-96H288v-64h160Zm0-96H288v-64h160Zm0-96H288V96h160Z' data-name='Layer 1'/%3e%3c/g%3e%3c/svg%3e");
$bg-icon-notebook-shift-log: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M448 55.36c0-39.95-27.69-63.66-61.54-52.68L0 128h448V55.36ZM448 160H0v288c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64ZM128 416H64v-64h64v64Zm0-96H64v-64h64v64Zm320 96H192v-64h256v64Zm0-96H192v-64h256v64Z'/%3e%3c/svg%3e");
+$bg-icon-plot-scatter: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cg data-name='Layer 2'%3e%3cpath d='M96 0C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96ZM64 176a48 48 0 1 1 48 48 48 48 0 0 1-48-48Zm80 240a48 48 0 1 1 48-48 48 48 0 0 1-48 48Zm128-96a48 48 0 1 1 48-48 48 48 0 0 1-48 48Zm0-160a48 48 0 1 1 48-48 48 48 0 0 1-48 48Zm128 256a48 48 0 1 1 48-48 48 48 0 0 1-48 48Z' data-name='Layer 1'/%3e%3c/g%3e%3c/svg%3e");
diff --git a/src/styles/_glyphs.scss b/src/styles/_glyphs.scss
index 9cb9af252..80b43eb3d 100755
--- a/src/styles/_glyphs.scss
+++ b/src/styles/_glyphs.scss
@@ -203,6 +203,7 @@
.icon-plan { @include glyphBefore($glyph-icon-plan); }
.icon-timelist { @include glyphBefore($glyph-icon-timelist); }
.icon-notebook-shift-log { @include glyphBefore($glyph-icon-notebook-shift-log); }
+.icon-plot-scatter { @include glyphBefore($glyph-icon-plot-scatter); }
/************************** 12 PX CLASSES */
// TODO: sync with 16px redo as of 10/25/18
@@ -218,9 +219,6 @@
.bg-icon-alert-triangle { @include glyphBg($bg-icon-alert-triangle); }
.bg-icon-bell { @include glyphBg($bg-icon-bell); }
.bg-icon-info { @include glyphBg($bg-icon-info); }
-.bg-icon-activity { @include glyphBg($bg-icon-activity); }
-.bg-icon-activity-mode { @include glyphBg($bg-icon-activity-mode); }
-.bg-icon-autoflow-tabular { @include glyphBg($bg-icon-autoflow-tabular); }
.bg-icon-plus { @include glyphBg($bg-icon-plus); }
.bg-icon-grippy-ew { @include glyphBg($bg-icon-grippy-ew); }
.bg-icon-chain-links { @include glyphBg($bg-icon-chain-links); }
@@ -267,3 +265,4 @@
.bg-icon-plan { @include glyphBg($bg-icon-plan); }
.bg-icon-timelist { @include glyphBg($bg-icon-timelist); }
.bg-icon-notebook-shift-log { @include glyphBg($bg-icon-notebook-shift-log); }
+.bg-icon-plot-scatter { @include glyphBg($bg-icon-plot-scatter); }
diff --git a/src/styles/_legacy-plots.scss b/src/styles/_legacy-plots.scss
index 8b6e60a89..4ec0529e0 100644
--- a/src/styles/_legacy-plots.scss
+++ b/src/styles/_legacy-plots.scss
@@ -749,6 +749,12 @@ mct-plot {
overflow: hidden;
}
+/***************** SCATTER PLOTS */
+.c-scatter-chart {
+ flex: 1 1 auto;
+ overflow: hidden;
+}
+
/***************** CURSOR GUIDES */
[class*='c-cursor-guide'] {
box-shadow: $shdwCursorGuide;
diff --git a/src/styles/fonts/Open MCT Symbols 16px.json b/src/styles/fonts/Open MCT Symbols 16px.json
index a780f0f89..2270ee40a 100644
--- a/src/styles/fonts/Open MCT Symbols 16px.json
+++ b/src/styles/fonts/Open MCT Symbols 16px.json
@@ -1309,6 +1309,7 @@
"name": "icon-timelist",
"prevSize": 16,
"code": 60207,
+<<<<<<< HEAD
"tempChar": ""
},
{
@@ -1318,6 +1319,17 @@
"prevSize": 16,
"code": 60209,
"tempChar": ""
+=======
+ "tempChar": ""
+ },
+ {
+ "order": 205,
+ "id": 176,
+ "name": "icon-plot-scatter",
+ "prevSize": 16,
+ "code": 60208,
+ "tempChar": ""
+>>>>>>> master
}
],
"id": 0,
@@ -4228,6 +4240,7 @@
}
},
{
+<<<<<<< HEAD
"id": 184,
"paths": [
"M896 110.72c0-79.9-55.38-127.32-123.080-105.36l-772.92 250.64h896v-145.28z",
@@ -4235,17 +4248,31 @@
],
"attrs": [
{},
+=======
+ "id": 176,
+ "paths": [
+ "M192 0c-105.6 0-192 86.4-192 192v640c0 105.6 86.4 192 192 192h640c105.6 0 192-86.4 192-192v-640c0-105.6-86.4-192-192-192zM128 352c0-53.019 42.981-96 96-96s96 42.981 96 96c0 53.019-42.981 96-96 96v0c-53.019 0-96-42.981-96-96v0zM288 832c-53.019 0-96-42.981-96-96s42.981-96 96-96c53.019 0 96 42.981 96 96v0c0 53.019-42.981 96-96 96v0zM544 640c-53.019 0-96-42.981-96-96s42.981-96 96-96c53.019 0 96 42.981 96 96v0c0 53.019-42.981 96-96 96v0zM544 320c-53.019 0-96-42.981-96-96s42.981-96 96-96c53.019 0 96 42.981 96 96v0c0 53.019-42.981 96-96 96v0zM800 832c-53.019 0-96-42.981-96-96s42.981-96 96-96c53.019 0 96 42.981 96 96v0c0 53.019-42.981 96-96 96v0z"
+ ],
+ "attrs": [
+>>>>>>> master
{}
],
"isMulticolor": false,
"isMulticolor2": false,
"grid": 16,
"tags": [
+<<<<<<< HEAD
"icon-notebook-restricted"
],
"colorPermutations": {
"12552552551": [
{},
+=======
+ "icon-plot-scatter"
+ ],
+ "colorPermutations": {
+ "12552552551": [
+>>>>>>> master
{}
]
}
@@ -4283,11 +4310,12 @@
"fontFamily": "Open-MCT-Symbols-16px",
"majorVersion": 5,
"minorVersion": 1,
- "designer": "Charles Hacskaylo"
+ "designer": "Charles Hacskaylo",
+ "description": "Change to 5% baseline height"
},
"metrics": {
"emSize": 1024,
- "baseline": 20,
+ "baseline": 10,
"whitespace": 0
},
"embed": false,
diff --git a/src/styles/fonts/Open-MCT-Symbols-16px.svg b/src/styles/fonts/Open-MCT-Symbols-16px.svg
index 2d1edfd5a..31157d5c9 100644
--- a/src/styles/fonts/Open-MCT-Symbols-16px.svg
+++ b/src/styles/fonts/Open-MCT-Symbols-16px.svg
@@ -4,7 +4,7 @@
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="Open-MCT-Symbols-16px" horiz-adv-x="1024">
-<font-face units-per-em="1024" ascent="819.2" descent="-204.8" />
+<font-face units-per-em="1024" ascent="921.6" descent="-102.4" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="0" d="" />
<glyph unicode="&#xe900;" glyph-name="icon-alert-rect-v2" d="M896 832h-768c-70.6-0.2-127.8-57.4-128-128v-768c0.2-70.6 57.4-127.8 128-128h768c70.6 0.2 127.8 57.4 128 128v768c-0.2 70.6-57.4 127.8-128 128zM576-64h-128v128h128v-128zM597.8 320l-37.8-192h-96l-37.8 192v384h171.8v-384z" />
diff --git a/src/styles/notebook.scss b/src/styles/notebook.scss
index 44ddd5fc6..ca410100d 100644
--- a/src/styles/notebook.scss
+++ b/src/styles/notebook.scss
@@ -243,29 +243,40 @@
display: flex;
padding: $interiorMarginSm $interiorMarginSm $interiorMarginSm $interiorMargin;
- &__time,
&__text,
&__local-controls {
padding-top: $p;
padding-bottom: $p;
}
- &__time,
+ &__creator,
&__embed__time {
opacity: 0.7;
}
+ &__time-and-creator,
+ &__input {
+ padding: $p;
+ }
+
+ &__creator [class*='icon'] {
+ font-size: 0.95em;
+ }
+
&__time-and-content {
- display: flex;
+ display: block;
flex: 1 1 auto;
- flex-wrap: wrap;
+
+ > * + * {
+ margin-top: $interiorMarginSm;
+ }
+
+ [class*='created-'] {
+ color: pullForward($colorBodyFg, 20%);
+ }
}
&__time {
- flex: 0 1 auto;
- min-width: 130px;
- margin-right: $interiorMarginLg;
-
* {
white-space: nowrap;
}
@@ -294,12 +305,12 @@
&__input {
// Appended to __text element when Notebook is not in readOnly mode
@include inlineInput;
- padding-left: $inputTextPLeftRight;
- padding-right: $inputTextPLeftRight;
+ padding-left: $p;
+ padding-right: $p;
@include hover {
&:not(:focus) {
- background: rgba($colorBodyFg, 0.1);
+ background: rgba($colorBodyFg, 0.2);
}
}
diff --git a/src/ui/color/ColorSwatch.vue b/src/ui/color/ColorSwatch.vue
index c07291f5d..464851b49 100644
--- a/src/ui/color/ColorSwatch.vue
+++ b/src/ui/color/ColorSwatch.vue
@@ -20,11 +20,8 @@
at runtime from the About dialog for additional information.
-->
<template>
-<div class="u-contents">
- <div
- v-if="canEdit"
- class="grid-row"
- >
+<div class="grid-row">
+ <template v-if="canEdit">
<div
class="grid-cell label"
:title="editTitle"
@@ -63,11 +60,8 @@
</div>
</div>
</div>
- </div>
- <div
- v-else
- class="grid-row"
- >
+ </template>
+ <template v-else>
<div
class="grid-cell label"
:title="viewTitle"
@@ -81,7 +75,7 @@
>
</span>
</div>
- </div>
+ </template>
</div>
</template>
diff --git a/src/ui/components/ObjectView.vue b/src/ui/components/ObjectView.vue
index fbc4d433f..2976f7dc7 100644
--- a/src/ui/components/ObjectView.vue
+++ b/src/ui/components/ObjectView.vue
@@ -28,6 +28,7 @@ const SupportedViewTypes = [
'plot-stacked',
'plot-overlay',
'bar-graph.view',
+ 'scatter-plot.view',
'time-strip.view'
];
export default {
diff --git a/src/ui/inspector/inspector.scss b/src/ui/inspector/inspector.scss
index 93dc6a95d..26d1410bf 100644
--- a/src/ui/inspector/inspector.scss
+++ b/src/ui/inspector/inspector.scss
@@ -197,17 +197,17 @@
}
}
- li.grid-row + li.grid-row {
+ .grid-row + .grid-row {
> * {
border-top: 1px solid $colorInspectorSectionHeaderBg;
}
}
- li.grid-row .label {
+ .grid-row .label {
color: $colorInspectorPropName;
}
- li.grid-row .value {
+ .grid-row .value {
color: $colorInspectorPropVal;
word-break: break-all;
&:first-child {
diff --git a/src/ui/layout/mct-tree.vue b/src/ui/layout/mct-tree.vue
index d78a7b8a1..da074f98b 100644
--- a/src/ui/layout/mct-tree.vue
+++ b/src/ui/layout/mct-tree.vue
@@ -174,7 +174,8 @@ export default {
itemOffset: 0,
activeSearch: false,
mainTreeTopMargin: undefined,
- selectedItem: {}
+ selectedItem: {},
+ observers: {}
};
},
computed: {
@@ -275,6 +276,8 @@ export default {
if (this.treeResizeObserver) {
this.treeResizeObserver.disconnect();
}
+
+ this.destroyObservers(this.observers);
},
methods: {
async initialize() {
@@ -549,6 +552,8 @@ export default {
}
return composition.map((object) => {
+ this.addTreeItemObserver(object, parentObjectPath);
+
return this.buildTreeItem(object, parentObjectPath);
});
},
@@ -565,6 +570,41 @@ export default {
navigationPath
};
},
+ addTreeItemObserver(domainObject, parentObjectPath) {
+ if (this.observers[domainObject.identifier.key]) {
+ this.observers[domainObject.identifier.key]();
+ }
+
+ this.observers[domainObject.identifier.key] = this.openmct.objects.observe(
+ domainObject,
+ 'name',
+ this.updateTreeItems.bind(this, parentObjectPath)
+ );
+ },
+ async updateTreeItems(parentObjectPath) {
+ let children;
+
+ if (parentObjectPath.length) {
+ const parentItem = this.treeItems.find(item => item.objectPath === parentObjectPath);
+ const descendants = this.getChildrenInTreeFor(parentItem, true);
+ const parentIndex = this.treeItems.map(e => e.object).indexOf(parentObjectPath[0]);
+
+ children = await this.loadAndBuildTreeItemsFor(parentItem.object, parentItem.objectPath);
+
+ this.treeItems.splice(parentIndex + 1, descendants.length, ...children);
+ } else {
+ const root = await this.openmct.objects.get('ROOT');
+ children = await this.loadAndBuildTreeItemsFor(root, []);
+
+ this.treeItems = [...children];
+ }
+
+ for (let item of children) {
+ if (this.isTreeItemOpen(item)) {
+ this.openTreeItem(item);
+ }
+ }
+ },
buildNavigationPath(objectPath) {
return '/browse/' + [...objectPath].reverse()
.map((object) => this.openmct.objects.makeKeyString(object.identifier))
@@ -577,6 +617,8 @@ export default {
const descendants = this.getChildrenInTreeFor(parentItem, true);
const directDescendants = this.getChildrenInTreeFor(parentItem);
+ this.addTreeItemObserver(domainObject, parentItem.objectPath);
+
if (directDescendants.length === 0) {
this.addItemToTreeAfter(newItem, parentItem);
@@ -611,6 +653,7 @@ export default {
let removeItem = directDescendants.find(item => item.id === removeKeyString);
this.removeItemFromTree(removeItem);
+ this.removeItemFromObservers(removeItem);
};
},
removeCompositionListenerFor(navigationPath) {
@@ -632,6 +675,13 @@ export default {
const removeIndex = this.getTreeItemIndex(item.navigationPath);
this.treeItems.splice(removeIndex, 1);
},
+ removeItemFromObservers(item) {
+ if (this.observers[item.id]) {
+ this.observers[item.id]();
+
+ delete this.observers[item.id];
+ }
+ },
addItemToTreeBefore(addItem, beforeItem) {
const addIndex = this.getTreeItemIndex(beforeItem.navigationPath);
@@ -863,6 +913,15 @@ export default {
},
handleTreeResize() {
this.calculateHeights();
+ },
+ destroyObservers(observers) {
+ Object.entries(observers).forEach(([keyString, unobserve]) => {
+ if (typeof unobserve === 'function') {
+ unobserve();
+ }
+
+ delete observers[keyString];
+ });
}
}
};