Compare commits

...

29 Commits

Author SHA1 Message Date
David Tsay
a6ac54383d fix bounds to use for timespans on pan axis 2019-12-12 12:02:31 -08:00
David Tsay
490f25add8 change tick to timespan to avoid confusion 2019-12-12 11:58:12 -08:00
David Tsay
694255db6b remove unused function calls 2019-12-04 11:29:10 -08:00
David Tsay
6910ae0a2b axis visual tuning 2019-11-26 14:59:29 -08:00
David Tsay
b3bf7f2db1 add presets
code cleanup
2019-11-26 14:49:11 -08:00
David Tsay
348ba9085b Merge branch 'topic-core-refactor' into feature/time-conductor-axis/zoom-slider 2019-11-26 11:46:34 -08:00
David Tsay
9d6a5e2e17 clean up refactoring 2019-11-26 11:46:02 -08:00
David Tsay
5cb0e2e885 conductor history functionality complete 2019-11-26 11:30:39 -08:00
David Tsay
ee277f3547 reset axis height after prototyping 2019-11-26 11:30:24 -08:00
David Tsay
bf77d240c7 WIP persistence works 2019-11-25 16:37:47 -08:00
David Tsay
684a3d2807 WIP save history to and pull history from local storage 2019-11-25 10:28:50 -08:00
David Tsay
27e01ef13f WIP conductor history 2019-11-21 16:35:15 -08:00
David Tsay
4a2b1640e9 set global bounds before emitting change event 2019-11-21 10:31:56 -08:00
David Tsay
38cb92b203 WIP prototyping conductor history 2019-11-18 15:32:16 -08:00
David Tsay
0792ae0ae4 just use d3-brush instead of entire d3 package 2019-11-15 15:47:06 -08:00
David Tsay
d462f5d763 Merge branch 'topic-core-refactor' into feature/time-conductor-axis/zoom-slider 2019-11-15 15:09:48 -08:00
David Tsay
cb8e97dc1c resize brush on window resize 2019-11-13 15:07:07 -08:00
David Tsay
aed3c19edd linting 2019-11-13 14:00:17 -08:00
David Tsay
a19c5cfd52 disable cursor for local time and enable for fixed time 2019-11-13 13:59:13 -08:00
David Tsay
03e5241041 pass isFixed as props so we can watch for change from parent 2019-11-13 13:40:37 -08:00
David Tsay
29ed251685 zoom axis sets start and end times 2019-11-13 11:59:50 -08:00
David Tsay
60433a12b8 linting 2019-11-12 17:03:30 -08:00
David Tsay
69b8dd6c37 make isTimeFixed check reusable 2019-11-12 15:48:09 -08:00
David Tsay
e6c78c1826 add LocalTimeSystem 2019-11-12 13:15:10 -08:00
David Tsay
a1657817dc make LocalTimeSystem UTCBased (Earth based) 2019-11-12 12:05:45 -08:00
David Tsay
29538e6e78 revert selection to times 2019-11-08 16:15:11 -08:00
David Tsay
68e9152d6a pan and zoom now co-exist 2019-11-08 15:58:16 -08:00
David Tsay
02b2b47411 require alt pressed for grab handle. display only 2019-11-07 16:46:56 -08:00
David Tsay
70624c2c5c basic brush prototype visible 2019-11-07 13:22:52 -08:00
7 changed files with 315 additions and 32 deletions

View File

@@ -48,6 +48,7 @@
openmct.install(openmct.plugins.Generator());
openmct.install(openmct.plugins.ExampleImagery());
openmct.install(openmct.plugins.UTCTimeSystem());
openmct.install(openmct.plugins.LocalTimeSystem());
openmct.install(openmct.plugins.AutoflowView({
type: "telemetry.panel"
}));
@@ -72,7 +73,24 @@
start: - THIRTY_MINUTES,
end: FIVE_MINUTES
}
}
},
{
name: "Fixed",
timeSystem: 'local',
bounds: {
start: Date.now() - THIRTY_MINUTES,
end: Date.now()
}
},
{
name: "Realtime",
timeSystem: 'local',
clock: 'local',
clockOffsets: {
start: - THIRTY_MINUTES,
end: FIVE_MINUTES
}
},
]
}));
openmct.install(openmct.plugins.SummaryWidget());

View File

@@ -14,6 +14,7 @@
"css-loader": "^1.0.0",
"d3-array": "1.2.x",
"d3-axis": "1.0.x",
"d3-brush": "^1.1.3",
"d3-collection": "1.0.x",
"d3-color": "1.0.x",
"d3-format": "1.2.x",

View File

@@ -41,7 +41,7 @@ define([], function () {
this.timeFormat = 'local-format';
this.durationFormat = 'duration';
this.isUTCBased = false;
this.isUTCBased = true;
}
return LocalTimeSystem;

View File

@@ -88,15 +88,24 @@
</div>
<conductor-axis
class="c-conductor__ticks"
:bounds="rawBounds"
@panAxis="setViewFromBounds"></conductor-axis>
class="c-conductor__ticks"
:bounds="rawBounds"
:isFixed="isFixed"
@panAxis="setViewFromBounds"
@zoomAxis="setViewFromBounds"
></conductor-axis>
</div>
<div class="c-conductor__controls">
<!-- Mode, time system menu buttons and duration slider -->
<ConductorMode class="c-conductor__mode-select"></ConductorMode>
<ConductorTimeSystem class="c-conductor__time-system-select"></ConductorTimeSystem>
<ConductorHistory
v-if="isFixed"
class="c-conductor__history-select"
:bounds="bounds"
:time-system="timeSystem"
@select-timespan="setViewFromBounds"
></ConductorHistory>
</div>
<input type="submit" class="invisible">
</form>
@@ -294,6 +303,7 @@ import ConductorTimeSystem from './ConductorTimeSystem.vue';
import DatePicker from './DatePicker.vue';
import ConductorAxis from './ConductorAxis.vue';
import ConductorModeIcon from './ConductorModeIcon.vue';
import ConductorHistory from './ConductorHistory.vue'
const DEFAULT_DURATION_FORMATTER = 'duration';
const SECONDS = 1000;
@@ -309,7 +319,8 @@ export default {
ConductorTimeSystem,
DatePicker,
ConductorAxis,
ConductorModeIcon
ConductorModeIcon,
ConductorHistory
},
data() {
let bounds = this.openmct.time.bounds();
@@ -319,6 +330,7 @@ export default {
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
return {
timeSystem: timeSystem,
timeFormatter: timeFormatter,
durationFormatter: durationFormatter,
offsets: {
@@ -329,6 +341,10 @@ export default {
start: timeFormatter.format(bounds.start),
end: timeFormatter.format(bounds.end)
},
bounds: {
start: bounds.start,
end: bounds.end
},
rawBounds: {
start: bounds.start,
end: bounds.end
@@ -340,10 +356,10 @@ export default {
},
methods: {
setTimeSystem(timeSystem) {
this.timeSystem = timeSystem
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
this.durationFormatter = this.getFormatter(
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
this.isUTCBased = timeSystem.isUTCBased;
},
setOffsetsFromView($event) {
@@ -492,11 +508,14 @@ export default {
this.validateAllBounds();
this.submitForm();
},
setNewBounds(bounds) {
this.bounds.start = bounds.start;
this.bounds.end = bounds.end;
}
},
mounted() {
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
this.openmct.time.on('bounds', this.setViewFromBounds);
this.openmct.time.on('bounds', this.setNewBounds);
this.openmct.time.on('timeSystem', this.setTimeSystem);
this.openmct.time.on('clock', this.setViewFromClock);
this.openmct.time.on('clockOffsets', this.setViewFromOffsets)

View File

@@ -22,7 +22,8 @@
<template>
<div class="c-conductor-axis"
ref="axisHolder"
@mousedown="dragStart($event)">
@mousedown="dragStart($event)"
>
</div>
</template>
@@ -44,7 +45,7 @@
text-rendering: geometricPrecision;
width: 100%;
height: 100%;
> g {
> g.axis {
// Overall Tick holder
transform: translateY($tickYPos);
path {
@@ -97,6 +98,7 @@
transition: $transIn;
}
}
}
.is-realtime-mode & {
@@ -115,6 +117,7 @@
import * as d3Selection from 'd3-selection';
import * as d3Axis from 'd3-axis';
import * as d3Scale from 'd3-scale';
import * as d3Brush from 'd3-brush';
import utcMultiTimeFormat from './utcMultiTimeFormat.js';
const PADDING = 1;
@@ -126,7 +129,13 @@ const PIXELS_PER_TICK_WIDE = 200;
export default {
inject: ['openmct'],
props: {
bounds: Object
bounds: Object,
isFixed: Boolean
},
data() {
return {
altPressed: false
}
},
methods: {
setScale() {
@@ -171,9 +180,8 @@ export default {
},
getActiveFormatter() {
let timeSystem = this.openmct.time.timeSystem();
let isFixed = this.openmct.time.clock() === undefined;
if (isFixed) {
if (this.isFixed) {
return this.getFormatter(timeSystem.timeFormat);
} else {
return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
@@ -185,8 +193,7 @@ export default {
}).formatter;
},
dragStart($event){
let isFixed = this.openmct.time.clock() === undefined;
if (isFixed){
if (this.isFixed){
this.dragStartX = $event.clientX;
document.addEventListener('mousemove', this.drag);
@@ -198,18 +205,20 @@ export default {
drag($event) {
if (!this.dragging){
this.dragging = true;
requestAnimationFrame(()=>{
let deltaX = $event.clientX - this.dragStartX;
let percX = deltaX / this.width;
let bounds = this.openmct.time.bounds();
let deltaTime = bounds.end - bounds.start;
let newStart = bounds.start - percX * deltaTime;
this.$emit('panAxis',{
start: newStart,
end: newStart + deltaTime
});
this.dragging = false;
})
if (this.altPressed) {
requestAnimationFrame(()=>{
let deltaX = $event.clientX - this.dragStartX;
let percX = deltaX / this.width;
let bounds = this.openmct.time.bounds();
let deltaTime = bounds.end - bounds.start;
let newStart = bounds.start - percX * deltaTime;
this.$emit('panAxis',{
start: newStart,
end: newStart + deltaTime
});
})
}
this.dragging = false;
} else {
console.log('Rejected drag due to RAF cap');
}
@@ -225,6 +234,55 @@ export default {
if (this.$refs.axisHolder.clientWidth !== this.width) {
this.width = this.$refs.axisHolder.clientWidth;
this.setScale();
this.destroyBrush();
this.initBrush();
this.createBrush();
}
},
initBrush() {
// brush extent y coordinate starts at -1 to prevent underlying layer cursor to show
this.brush = d3Brush.brushX()
.extent([[0, -1], [this.width, this.height]])
.on("end", this.brushEnd);
},
createBrush() {
if (!this.isFixed) {
return;
}
this.svg = this.svg || d3Selection.select('svg');
if (!this.svg) {
return;
}
this.svg.append("g")
.attr("class", "brush")
.call(this.brush);
},
brushEnd () {
const selection = d3Selection.event.selection;
if (!d3Selection.event.sourceEvent || !selection) {
return;
}
// SMELL
const [x0, x1] = selection.map(d => this.xScale.invert(d).getTime());
this.openmct.time.bounds({
start: x0,
end: x1
});
this.$emit('zoomAxis', {
start: x0,
end: x1
});
// clear brush
d3Selection.select('g.brush').call(this.brush.move, null);
},
destroyBrush() {
const brush = d3Selection.select('g.brush')
if (brush){
brush.remove();
}
}
},
@@ -234,22 +292,40 @@ export default {
this.setScale();
},
deep: true
},
isFixed: function(value) {
value ? this.createBrush() : this.destroyBrush();
}
},
created() {
document.addEventListener('keydown', (e) => {
if (e.key === 'Alt') {
this.altPressed = true;
this.destroyBrush();
}
});
document.addEventListener('keyup', (e) => {
if (e.key === 'Alt') {
this.altPressed = false;
this.createBrush();
}
});
},
mounted() {
let axisHolder = this.$refs.axisHolder;
let height = axisHolder.offsetHeight;
this.height = axisHolder.offsetHeight;
let vis = d3Selection.select(axisHolder)
.append("svg:svg")
.attr("width", "100%")
.attr("height", height);
.attr("height", this.height);
this.width = this.$refs.axisHolder.clientWidth;
this.xAxis = d3Axis.axisTop();
this.dragging = false;
// draw x axis with labels. CSS is used to position them.
this.axisElement = vis.append("g");
this.axisElement = vis.append("g")
.attr("class", "axis");
this.setViewFromTimeSystem(this.openmct.time.timeSystem());
this.setScale();
@@ -257,6 +333,9 @@ export default {
//Respond to changes in conductor
this.openmct.time.on("timeSystem", this.setViewFromTimeSystem);
setInterval(this.resize, RESIZE_POLL_INTERVAL);
this.initBrush();
this.createBrush();
},
destroyed() {
}

View File

@@ -0,0 +1,161 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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="c-ctrl-wrapper c-ctrl-wrapper--menus-up">
<button class="c-button--menu c-history-button icon-history"
@click.prevent="toggle">
<span class="c-button__label">History</span>
</button>
<div class="c-menu" v-if="open">
<ul
v-if="isUTCBased"
>
<li @click="selectHours(24)">Last 24 hours</li>
<li @click="selectHours(2)">Last 2 hours</li>
<li @click="selectHours(1)">Last hour</li>
</ul>
<ul>
<li
v-for="(timespan, index) in historyForCurrentTimeSystem"
:key="index"
@click="selectTimespan(timespan)"
>
{{ formatTime(timespan.start) }} - {{ formatTime(timespan.end) }}
</li>
</ul>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
</style>
<script>
import toggleMixin from '../../ui/mixins/toggle-mixin';
import utcMultiTimeFormat from './utcMultiTimeFormat.js';
import _ from 'lodash'
const LOCAL_STORAGE_HISTORY_MAX_RECORDS = 20;
const LOCAL_STORAGE_HISTORY_KEY = 'tcHistory';
export default {
inject: ['openmct'],
mixins: [toggleMixin],
props: {
bounds: {
type: Object,
required: true
},
timeSystem: {
type: Object,
required: true
}
},
data() {
return {
history: {} // contains arrays of timespans {start, end}, array key is time system key
}
},
computed: {
isUTCBased() {
return this.timeSystem.isUTCBased;
},
historyForCurrentTimeSystem() {
return this.history[this.timeSystem.key]
}
},
methods: {
getHistoryFromLocalStorage() {
if (localStorage.getItem(LOCAL_STORAGE_HISTORY_KEY)) {
this.history = JSON.parse(localStorage.getItem(LOCAL_STORAGE_HISTORY_KEY))
} else {
this.history = {};
this.persistHistoryToLocalStorage();
}
},
persistHistoryToLocalStorage() {
localStorage.setItem(LOCAL_STORAGE_HISTORY_KEY, JSON.stringify(this.history));
},
addTimespan() {
const key = this.timeSystem.key;
let [...currentHistory] = this.history[key] || [];
const timespan = {
start: this.bounds.start,
end: this.bounds.end,
};
// when choosing an existing entry, remove it and add it back as latest entry
currentHistory = currentHistory.filter((entry) => {
return !_.isEqual(timespan, entry);
});
if (currentHistory.length >= LOCAL_STORAGE_HISTORY_MAX_RECORDS) {
currentHistory.shift();
}
currentHistory.push(timespan);
this.history[key] = currentHistory;
},
selectTimespan(timespan) {
this.$emit('select-timespan', timespan);
},
selectHours(hours) {
const now = Date.now();
this.selectTimespan({
start: now - hours * 60 * 60 * 1000,
end: now
});
},
formatTime(time) {
const formatter = this.openmct.telemetry.getValueFormatter({
format: this.timeSystem.timeFormat
}).formatter;
return formatter.format(time);
}
},
watch: {
bounds: {
handler() {
this.addTimespan();
},
deep: true
},
timeSystem: {
handler() {
this.addTimespan();
},
deep: true
},
history: {
handler() {
this.persistHistoryToLocalStorage();
},
deep: true
}
},
mounted() {
this.getHistoryFromLocalStorage();
}
}
</script>

View File

@@ -432,6 +432,11 @@ select {
content: ''; // Add this element so that menu items without an icon still indent properly
}
}
ul {
&:not(:last-child) {
border-bottom: 1px solid $colorInteriorBorder;
}
}
}
.c-menu {