Compare commits

...

56 Commits

Author SHA1 Message Date
Pete Richards
7240b11ad1 Use edit context to support subobject editing 2017-12-04 13:35:55 -08:00
Deep Tailor
7982961dcd Merge branch 'master' of https://github.com/nasa/openmct into testathon-aries 2017-12-04 13:25:58 -08:00
Pete Richards
1e795695e3 Update Plot Options to match new Inspectors 2017-12-04 13:11:52 -08:00
Deep Tailor
395199b203 change theme to Espresso 2017-12-04 11:52:16 -08:00
Deep Tailor
1455ea588c Merge branch 'pete-plot-import' of https://github.com/nasa/openmct into testathon-aries 2017-12-04 11:48:37 -08:00
Pete Richards
14ca25b267 [Plot] new plots
* Remove Old Plot Code
* Remove Telemetry Panel Type
* UTCFormat.parse: passthrough numbers
* TelemetryAPI: default request arguments
* TelemetryAPI: fix enum formatting

pass value instead of trying to create datum
Set max precision to avoid errors
Install plot by default
Make sure plots are always plots
Set default yKey on series
Use new time API
Update hints
proper id formats
Only redraw on change
Set x axis on bounds change

while MCTChart can handle inserting points in the middle of the
chart series, this is not performant with a large insert volume.

So, when merging historical requests with existing data, do a batch
sort and uniq check up front and then reset the data such that MCTChart
only has to do appends.  This is significantly faster than doing
large volumes of inserts.

[CSS] Plot Styles

Moved .view-control out of tree.scss and
generalized as a control prior to implementing
it as the expand/collapse control in plot legends
for #584 and #618.

Major layout changes to enable flex approach for
legend and plot display area; table styling for
expanded legend; legend on top, bottom, right
and left support added but still a bunch to do.

Style and markup normalization for legend elements.

Significant mods to layout in Inspector while
editing, using new .compact-form class;
mod to ul.tree li to allow .compact-form layout
class within tree scope;

Significant layout cleanup, mainly for stacked plots
and expanded legend;
Config options hide/show controls fully ported from
Pete's original version;

New 12px crosshair glyph added;
'hover-value-enabled' markup and class refinements;

Generalized .t-alert-unsynced, moved to _icons.scss;
Moved .t-object-alert to proper location in plot markup;
Added new .t-stacked-plot class;

Moved location of padding for .hover-value-enabled
elements

Minor refactor to use explicit legend-collapsed and -expanded CSS
classes, replacing ng-show statements;
Removed ng-style that was setting column widths based on char
count of column name;

lobal rename of `compact-form` to `inspector-config`;
Applied same to plot-options-browse.html for tree items;

Renamed `plot-legend-on-*` selector;
Fixed layout for plot-legend-hidden, involved
returning `plot-wrapper-axis-and-display-area` to
use position: absolute;

Fixes #584
Fixes #618
Fixes #1590

Fix for renamed view-control classes

Fixes #1795
Also removed .has-children from plot-options html files;
2017-12-04 10:59:53 -08:00
Henry
a51b9bc63f [Examples] Memory leak in SWG 2017-12-04 10:42:36 -08:00
Deep Tailor
bfc49af9d3 Merge branch 'master' of https://github.com/nasa/openmct into testathon-aries 2017-12-01 11:51:23 -08:00
Deep Tailor
ff003c3dab Merge pull request #1822 from nasa/file-saver-1821
[Export] Add shim config for saveAs
2017-12-01 11:50:56 -08:00
Victor Woeltjen
de7c4d2ce3 [Export] Add shim config for saveAs
Fixes #1821 after regression introduced by changing dependency in #1808
2017-11-30 15:20:26 -08:00
Deep Tailor
d3a5b0a74c Merge branch 'subobject-selection-inspector' of https://github.com/nasa/openmct into testathon-aries 2017-11-30 14:01:14 -08:00
Pegah Sarram
5001872dc4 Fix tests 2017-11-30 13:49:22 -08:00
Pegah Sarram
585e5d2d9e [Fixed Position] Stop event propagation on click handlers in fixed position to avoid the event reaching the selection click handlers which caused issues with toolbar and selection." 2017-11-29 16:57:29 -08:00
Pegah Sarram
eb8cbbc542 Add tests for inspector controller and fix broken tests. Clean up code. 2017-11-29 10:37:26 -08:00
Charles Hacskaylo
83823fcb77 [Frontend] Fixed position items border width fixed
Fixes #1811
- Set to 1px;
2017-11-28 17:28:46 -08:00
Charles Hacskaylo
1b9710a2ce [Frontend] Tweaks to frame.no-frame layout
Fixes #1811
- Margin set to 0;
- Overflow set to hidden;
2017-11-28 17:06:32 -08:00
Charles Hacskaylo
84095cf9b4 [Frontend] Cleanups and tweaks
Fixes #1811
- Cleanups between frame, editor and selecting.scss;
- Hover and selected borders visually pumped up a bit;
- Solid borders on hover and selecting when browsing;
- Dashed borders for layouts when editing;
- Fixed cursor to only show move capability when
element is selected;
2017-11-28 16:53:12 -08:00
Aaron Doubek-Kraft
4b07930305 Summary Widgets (#1668)
* [ViewAPI] Update view API with more support

Update view provider to allow metadata definitions and to play
nicely with old style views.

Spec out some updates to ViewProviders and ViewRegistry to
support further use of views.

* [Summary Widgets] Add summary widgets

Add a summary widget domain object type

Implement basic interface and style configuration for rules

* [Summary Widgets] Implementation for Rules

Add rule configuration inputs, populated with domain objects, metadata,
and appropriate operations for a given type

* [Inputs] Add implementation for icon palette

Issue #1644

Wire up icon palette inputs to widget, and make icon class a persistable
property of a rule

* [Summary Widgets] Implementation for conditions

Support configuring and persisting multiple conditions per rule

Issue #1644

* [Summary Widgets] Generate Rule Descriptions

Dynamically update the rule description based on the current state
of the rules' conditions

* [Summary Widgets] 'Any/All Telemetry' in conditions

Add UI and implemenetion for evaluating any telemetry or all telemetry
in an individual condition. Add related unit tests.

* [Summary Widgets] Rule Reorders

Implement drag and drop rule reorders using the native HTML5 API

* [Summary Widget] Test Data

Issue #1644

Add user-configurable mock data to test rules. Modify evaluator to
gracefully handle uninitialzed test data points.

* [Summary Widgets] Edit Mode

Enable edit mode for summary widgets, and make configuration interface
visible only when the user has entered edit mode

Fix collision between widget palettes and other interfaces where
palettes would permanently hide other menus

* [Summary Widgets] UI for scripted conditions

* [Sumamry Widgets] Destroy
Implement destroy

* [Summary Widgets] Cleanup
Remove unnecessary persist calls in Rule.js.
Remove generateDescription from refreshConditions and add it after refreshConditions to initCondition and deleteCondition
Throw error when unsupported callback is passed in condition.js, return summary widget instance in plugin.js instead of wrapping in new object for view
Add request properties to telemetry request for providers that support it
Remove check for editing when persisting, in SummaryWidget.js
2017-11-28 13:23:15 -08:00
Pegah Sarram
6394588233 Fix broken tests 2017-11-27 13:59:36 -08:00
Pegah Sarram
97c8c2e0b2 Make reviewers' requested changes 2017-11-27 12:07:02 -08:00
Andrew Henry
5a49ac16b1 Merge pull request #1417 from ev1stensberg/URLIndicator
[BUILD] Adds URLIndicator
2017-11-24 10:17:04 -08:00
Even Stensberg
91b150c064 icon -> cssClass 2017-11-24 10:04:59 -08:00
Even Stensberg
9506d309b0 [TEST] Add Unit Test for URLIndicator
Adds the first test for the URLIndicator.

[DOCS] fix docs api indenting

fix linting

fix linting

fix docs
2017-11-24 10:04:59 -08:00
Even Stensberg
c9bd60f50e [BUILD] Adds URLIndicator along with documentation
Adds URLIndicator to the build, testable adding

`openmct.install(new openmct.plugins.URLIndicatorPlugin({
url: 'http://localhost:8080/',
icon: 'check',
interval: 15000,
label: 'Localhost'
}))`

to the openmct file. Also added Documentation about the plugin.
2017-11-24 10:04:59 -08:00
Pete Richards
92d69014fe Only show table options when editing 2017-11-22 13:56:11 -08:00
Deep Tailor
cf15ff5c07 [imagery] fix for issue #1799 (#1814)
* [imagery] fix for issue #1799
if there is no immediately new imagery incoming, forward to latest image in history on un-pause

* add test to check whether image is forwarded to latest image on un-pause
2017-11-22 11:10:53 -08:00
Victor Woeltjen
6bbdfcdfbe [Table] Retain rows in scope (#1813)
* [Table] Push rows in as they are added

...such that sorting does not cause real-time table rows to be lost.
Fixes #1738

* [Table] Test adding rows

* [Table] Fix code style in test

https://github.com/nasa/openmct/pull/1813#pullrequestreview-78277635
2017-11-22 11:05:03 -08:00
Pete Richards
06e93ff520 Merge pull request #1721 from nasa/persistence-issue-1593
[persistence] uncaught in promise error fix for issue #1593
2017-11-21 14:14:35 -08:00
Pegah Sarram
53cbfa2799 [Inspector] Make sure the right content is displayed based on whether a view provider exists or not. 2017-11-21 12:28:18 -08:00
Deep Tailor
550e7a15e6 [persistence] fix for issue #1593
prevent EditorCapability#finish from calling transactionservice#cancel when transactionService was not active - leading to console error everytime user would leave edit mode

Add tests for changes made and also to check for return type in either case (isActive or not)
2017-11-21 12:07:59 -08:00
Pegah Sarram
b8f5255c8a Bring back the change that made mct-init-select work 2017-11-21 10:46:53 -08:00
Pegah Sarram
b41ef21ff7 Fix checkstyle and lint errors 2017-11-21 10:03:06 -08:00
Pete Richards
1b972e04b3 MCTSelectable allows selection in initialization, use to select on navigation
[Frontend] Show grid in first nested layout, hide from deeper nesting. Only show grids when applicable to relative selection.
2017-11-20 15:34:12 -08:00
Charles Hacskaylo
2655482dd2 [Frontend] Mods to markup and CSS for sub-object selection 2017-11-20 15:33:58 -08:00
Pegah Sarram
76f3550e72 [API] Add inspector view registry to register inspector view providers and show a view in the inspector.
[API] Modify the selection API to register the click event and handle the event. The API will add a class to the selected object and the immediate parent of the selected object.

[Directive] Implemenet mct-selectable directive for making an element selectable.

[Layout] Update the layout controller to use the Selection API. Also, add double click gesture to allow drilling into a selected object.

Populate the Elements pool with contained elements of the selected object. Update toolbar and inspector to listen for the changes in selection.
2017-11-20 15:26:31 -08:00
Andrew Henry
71c54cd541 Merge pull request #1794 from nasa/open599b
[Timeline] Add resource graphs by drag-and-drop
2017-11-17 16:44:59 -08:00
Andrew Henry
e81b8e53dc Merge pull request #1797 from nasa/object-provider-fix
[API] get with keystring, fix transitional race condition
2017-11-17 09:00:00 -08:00
Pete Richards
84e6928f54 Merge pull request #1809 from nasa/file-saver-1808
[Build] Update file-saver dependency
2017-11-14 16:08:08 -08:00
Victor Woeltjen
ba688fe62c [Build] Update file-saver dependency
Fixes #1808
2017-11-14 14:27:43 -08:00
Pete Richards
c533e10352 [API] get with keystring, mark methods not implemented
Update Object API such that get supports calls with either a keystring or an
identifier.

As save and delete are not implemented and  have different calling signatures,
implement them as separate methods so they can be documented separately.
2017-11-01 12:16:06 -07:00
Victor Woeltjen
04f47b3db6 [Timeline] Test MCTResourceGraphDrop's dragleave handler 2017-10-30 12:44:51 -07:00
Victor Woeltjen
717fa5edf4 [Timeline] Test MCTResourceGraphDrop's drop handler 2017-10-30 12:42:18 -07:00
Victor Woeltjen
14f5f048fb [Timeline] Test MCTResourceGraphDrop's dragover handler 2017-10-30 12:40:08 -07:00
Victor Woeltjen
72929500d3 [Timeline] Begin adding spec for MCTResourceGraphDrop
...to follow up on PR #1195, which fixes #599
2017-10-30 12:25:59 -07:00
David Hudson
471adde923 [Swimlanes] Check for valid swimlane
Issue #599. Also switches class toggle from scope based to element
based.
2017-10-30 12:07:52 -07:00
David Hudson
6c5d5f3d00 [Swimlanes] Implement resource graph directive
Issue #599
2017-10-30 12:07:52 -07:00
David Hudson
2262fef29b [Swimlanes] Add resource graph drop directive
Issue #599
2017-10-30 12:07:49 -07:00
Victor Woeltjen
bda30f1475 Merge pull request #1787 from nasa/fixed-position-panels
[Fixed Position] Fixed position displays now show image URLs
2017-10-30 11:31:37 -07:00
Victor Woeltjen
bfec434369 Merge pull request #1793 from nasa/layout-regression-1790
[Layout] Fix regression in selecting object when it's dropped
2017-10-30 09:43:49 -07:00
Pegah Sarram
14df350994 [Layout] Fix regression...
...by clearing the selection only if the selected object is no longer in the compositon and there's no newly dropped object.

Fixes # 1790
2017-10-27 18:18:09 -07:00
Henry
80582f5e8d [Fixed Position] Do not just show range values, be more flexible to other telemetry types. Fixes #1740 2017-10-24 11:44:06 -07:00
Victor Woeltjen
7442768ced [List] Use standard format for modified/persisted times (#1737)
* [List] Use standard format for modified/persisted times

This provides consistency with other times and dates in the user interface,
and also provides a meaningful sort order due to the use of ISO formats for
standard date/time presentation. Fixes #1730.

* Remove unused dependency
2017-10-20 18:25:49 -07:00
Victor Woeltjen
77c7bdfdec [Timers] Follow timers from timelines (#1694)
* Squashed commit of the following:

commit f1dc1ce152e186da0d10c8e77d920ac0a76c9bc2
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 14:35:38 2017 -0700

    [Timers] Rewrite JSDoc for FollowTimerAction

    https://github.com/nasa/openmct/pull/1694/files#r137604769

commit 7ab0693cc983f8a04ac8ee9002f4d776b06a869a
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 14:27:53 2017 -0700

    [Timer] Expect domain objects from FollowIndicator test

commit ff89c0849d16ab451bfd2fddd9202cf36940f599
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 14:26:28 2017 -0700

    [Timer] Add JSDoc for new method

commit 2a0343352eca241dfc28a4aa0b3832e3e6928864
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 14:24:59 2017 -0700

    [Timeline] Update TOI tests

    ...to account for refactoring out of tick handling.

commit 01cbaafc72870fab4ada5894637ae5721214933d
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 14:17:25 2017 -0700

    [Timeline] Update dependencies for TOI test

commit 6bd5c378566362dce331e7c200dea87f0b08ecc6
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 14:15:21 2017 -0700

    [Timers] Update timerService tests with dependencies

commit b0793865c5131e17a58786ec356d67f2f2bba4c5
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 14:09:54 2017 -0700

    [Timers] Declare vars to satisfy JSHint

commit 9d2a63f7fe61dadf68255d795512ec55f532c533
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 14:07:12 2017 -0700

    [Timeline] Handle stopped timer

commit 30871270514730f3f2f12482075e5140bb97fa1f
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 13:59:08 2017 -0700

    [Timer] Tweak refactored timer logic

commit 53ad127ba7cf679377dc865301612a1d78399324
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 13:53:36 2017 -0700

    [Timer] Convert times from timerService

    ...to reduce resposibilities for TOI controller.

commit f8341133cf23df383b8f6e4815b88e0066ebd2bc
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 13:03:37 2017 -0700

    [Timeline] Factor out timer knowledge

commit aebd9e0ac223971b868b03343dbe4c61c6eb4849
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 12:34:58 2017 -0700

    [Timeline] Consistently use this

commit 48ac427a20c5c343aecdbd54b068d8691f7830b6
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 12:33:57 2017 -0700

    [Timeline] Remove unused tick binding/call

commit ea62f0a15ba4ab5de53213bbed14599eaf878d70
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 12:10:59 2017 -0700

    [Timeline] Retrieve timestamp on demand

commit f53bd04b5e343b22ea52b431785ade891577bb6a
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 12:07:55 2017 -0700

    [Timeline] Update clocks on bounds events

    https://github.com/nasa/openmct/pull/1694/files#r137603081

commit 51d8e376ee46aafa13cd9a969c6f03885e10dafb
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 11:40:33 2017 -0700

    [Timeline] Don't listen for non-existing tick events

commit 5cc40c488cec5e7453c2fe1dea5e5a4fa3509ecd
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 11:39:21 2017 -0700

    [Time] Revert Time API changes

    https://github.com/nasa/openmct/pull/1694/files#r137603081

commit c55c8bc627bf0a7f3cfd04b604b82d15ff469ab9
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 11:37:40 2017 -0700

    [Timeline] Finish testing TOI controller

commit af5cea5f2f172a309568d477dfdf11b8d45e74bb
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 11:06:47 2017 -0700

    [Timeline] Test TOI controller

commit ba64db68b132fa431e8ccdb533024bf2850f9712
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 10:06:41 2017 -0700

    [Timers] Test timerService

commit 247e663b326ec5b8145b832af9b26086204baea3
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 10:05:24 2017 -0700

    [Timers] Remove unused timerService method

commit 8d741ad5744e1b7deb669dbaa0f3d30e4eb5866e
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 09:59:32 2017 -0700

    [Timers] Remove unused timerService dependency

commit b59c8917bdef5ec3e54c8857d993d86547cfe177
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 09:58:10 2017 -0700

    [Timers] Remove unused timerService event

commit f15dd9827f835a814dc40a6201c90268a60ed64a
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 09:49:09 2017 -0700

    [Timers] Test timer-following indicator

commit 2501f11af8c0b2aed9ebf16ffd28c0003b2701c2
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 09:42:54 2017 -0700

    [Timer] Complete test coverage for FollowTimerAction

commit aa2be83fc15cd68ee6de4d9f8205dc2fcba8c35b
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 7 09:35:37 2017 -0700

    [Timers] Begin testing Follow Timer action

commit d9062e0b0ff351b141dcb646972053ec72292d53
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 16:45:18 2017 -0700

    [Timeline] Remove unused variables

commit 79ebe4dd2b2aefc1e83ea8142588ed0715b3c269
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 16:39:22 2017 -0700

    [Timeline] JSDoc for TOI controller

commit 330f6b465188555e8e59f4eaf8ce1875b5335846
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 16:30:58 2017 -0700

    [Timeline] Use different icon to follow time bounds

commit f0a3b628e6d1d843324085edd563b68997f5a215
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 16:30:46 2017 -0700

    [Timeline] Simplify TOI following initialization

commit e76f3d1d525e0d19845b4c5b457995e60c416ad0
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 16:27:07 2017 -0700

    [Timeline] Add toggle to follow time bounds

commit 8ec072c0a2a953c074e0c327430dd68f27894ffb
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 16:19:25 2017 -0700

    [Timeline] Follow TC bounds based on boolean

commit 206a26734dedc267af6d298a77658aa261ca4fea
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 15:37:12 2017 -0700

    [Timeline] Tune bounds following

commit 19563bdf53a036c7bf09c52924425a1902b243bb
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 15:19:19 2017 -0700

    [Timeline] Remove unused method

commit 293981ec55ad115d7bd90b92f5bd090df64bd7c2
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 15:18:59 2017 -0700

    [Timeline] Only update timestamp on tick

    Leave bounds-following to the bounds event

commit 9180e15971d2043f0999a16f0aa8794273bcfc74
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 14:43:01 2017 -0700

    [Time] Document tick event

commit c7b163dff0d94aaea86b76647501f21623b353e1
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 14:39:57 2017 -0700

    [Timeline] Stop listening on destroy

    ...from the TOI controller.

commit ca7def3cf98e1eaf6c3aeb16cf9fd79452c86bd0
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 14:32:40 2017 -0700

    [Timeline] Remove surplus watches

commit 367e7afa94ae1ed448e39f13be804c62e2bfcf00
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 14:30:14 2017 -0700

    [Timeline] Very deltas are valid before panning

commit 7ee94f316e90d046015266a2a9168e349ff73345
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 14:28:10 2017 -0700

    [Timeline] Scroll with TOI only while in view

commit 9d7bb431119b7bc6ddc86f0058718e4385478518
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 10:36:46 2017 -0700

    [Timeline] Utilize zoomController.bounds

commit f151b9e8adfd235c31e32bef5fddab835efa7c8f
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 10:35:57 2017 -0700

    [Timeline] Add methods to set zoom bounds

commit c3d0b9876ab79c18003838ed3315045c5fb2ddbb
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 10:32:08 2017 -0700

    [Timelines] Observe bounds changes

    ...to synchronize zoom with Time Conductor, #1688

commit 58adafc46f231b0fd92827d10c377131166ff39c
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 09:37:07 2017 -0700

    [Timers] JSDoc for TimerService

commit a325a8d5085bf1a4c9aa3ab20771308d4789765a
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 09:12:50 2017 -0700

    [Timeline] Re-tweak follow scroll calculations

    ...for visibility.

commit 41e4bf153607b081aaf92253fa2b21300e2f0ea7
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 09:03:45 2017 -0700

    [Timeline] Tweak follow scroll calculations

    ...for visibility.

commit 08a5b9f14ab629a310dc27a3771ca454f1187327
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 08:59:45 2017 -0700

    [Timeline] Replace debug output with scroll updates

commit 26585ecd61341b4ee89abd8ee866e705a02bbc9a
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 08:59:07 2017 -0700

    [Timeline] Move TOI to scrollable area

commit 654eda027c3c67a3a0ff33136109ca27d14762ba
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 08:56:07 2017 -0700

    [Timeline] Begin implementing TOI following

commit 552f67a11ce439be58ab7ca7884c46241a25adee
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Sep 6 08:55:51 2017 -0700

    [Timeline] At zoom-to-time method

    For use by time-of-interest controller, #1688

commit 37acbfd458740b2c3176875f83d37f0fdf57e727
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Aug 30 12:46:33 2017 -0700

    [Timeline] Remove other excess $apply calls

    ...although this should make us nervy about those callbacks being
    invoked in different ways.

commit 0e72847c9ba59f957efa2d412cb77c024afa9e63
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Aug 30 12:44:27 2017 -0700

    [Timeline] Remove $apply from $watch callback

    ...to avoid an infinite digest loop.

commit bade0fd9f60101d5b1b782cd28e608af493c9076
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Aug 30 12:42:18 2017 -0700

    [Timeline] Begin adding TOI line to template

commit f94034a3b4136f6b174155397084f8cdb22ce544
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Aug 30 12:11:25 2017 -0700

    [Timers] Add missing semicolon, satisfy JSHint

commit cb465b94011e7432cc7e4d9e815641f97dc61d7a
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Aug 30 12:08:45 2017 -0700

    [Time] Verify that tick event is emitted

commit 7c84a86a33ceb73ba6a06801374ea3f89793c450
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Aug 30 11:59:06 2017 -0700

    [Time] Emit tick events from Time API

    https://github.com/nasa/openmct/pull/1694

commit d319a783fcd882c03eb7d9a81fec33898016384e
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Wed Aug 30 11:56:28 2017 -0700

    [Timeline] Sketch in TOI controller

    ...to position/follow time-of-interest, relative to the active timer.

commit 2dbdb2627450039d69dbfd10eed2c100207e061a
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Tue Aug 29 12:57:47 2017 -0700

    [Timers] Use timerService

    ...to coordinate between action and indicator

commit f94a2358eaf0366bd4da2b44e69ccb62b153c5db
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Tue Aug 29 12:52:22 2017 -0700

    [Timers] Use TimerService from Follow Timer action

commit a720c2ec2cda4a300d26167f4717f0571bedcbfd
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Tue Aug 29 12:50:31 2017 -0700

    [Timers] Expose TimerService through bundle

commit e32bbc3e232d25f7c5dba98674781e4f263c4870
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Tue Aug 29 12:49:03 2017 -0700

    [Timers] Sketch in timer service

    ...which will keep track of the active timer used to interpret SET
    for Timelines.

commit a038c2b1d8fd34c2874fa8fc0421fa7ba53e11ab
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Tue Aug 29 12:41:05 2017 -0700

    [Timers] Register indicator

commit 0e93ae87a1cccc4f3a0636844625b64ccb77a7ae
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Tue Aug 29 12:39:21 2017 -0700

    [Timers] Skeleton for time following indicator

commit e806386891639740e9fe3d8641c2f60ab5a88eac
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Tue Aug 29 09:37:14 2017 -0700

    [Timers] Register the Follow Timer action

commit 008aa95932070459dcc6fa1d918a23dac8df7592
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Tue Aug 29 09:35:08 2017 -0700

    [Timers] Skeleton for Follow Timer action

    ...to synchronize the time conductor with a particular Timer. #1688

* [Timers] Remove unused variable to pass lint checks

* [Timers] Frontend updates for time-of-interest

Squashed commit of the following:

commit 370b910d36
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Sep 20 10:59:00 2017 -0700

    [Frontend] Fix in FollowIndicator.js

    Fixes #1688

commit 883d1feb32
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Sep 20 10:36:56 2017 -0700

    [Frontend] Styling and content on Follow indicator

    Fixes #1688

commit cff85fbbde
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Sep 20 10:09:19 2017 -0700

    [Frontend] Styling complete on Follow Line

    Fixes #1688

commit 563a86b69f
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Mon Sep 18 16:05:53 2017 -0700

    [Front-end] WIP Markup and CSS for Follow Line

    Fixes #1688
    Added line icon, style refinement;

commit fc49e5d023
Author: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Date:   Mon Sep 18 15:07:35 2017 -0700

    [Front-end] WIP Markup and CSS for Follow Line

    Fixes #1688
    Moved TimelineTOIController up 2 levels of markup hierarchy
    to allow Follow Lines, one in each split pane;
    Follow LInes markup and CSS in progress;

commit 8ec3c42291
Author: Charles Hacskaylo <charlesh88@gmail.com>
Date:   Wed Sep 13 16:46:14 2017 -0700

    [Frontend] WIP Timeline Follow Line

    Fixes #1688
    VERY WIP! Initial move of styles into classes;

* [Timeline] Follow up on front-end updates

Fixes #1688

Squashed commit of the following:

commit 817c7f31289b3e7631c3332d2192a68f21f50f9e
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 21 12:47:48 2017 -0700

    [Timeline] Initialize lastWidth

    ...to avoid clamping values before a width has actually been observed.

commit 5f7324c1cdb0cbef6385fbccac31b0404d216f95
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 21 12:21:11 2017 -0700

    [Timeline] Clamp right edge of zoom

    ...to avoid getting stuck in a weird scrolling state for large
    timer values.

commit 076aca112392e65835e7a01ac8e28780d24bfff1
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 21 12:02:23 2017 -0700

    [Timeline] Don't set scroll.x to negative values

    ...avoids mispositioning timer-following line,
    https://github.com/nasa/openmct/issues/1688#issuecomment-330373625

commit ac9bdb919df69fac65b297487131e2c41204ebeb
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Sep 21 11:32:49 2017 -0700

    [Timers] Loosen test expectation

    Resolves build failure https://circleci.com/gh/nasa/openmct/4181
    by reducing test specificity for indicator display name.

* [Timer] Handle mutations to followed timers

Fixes #1741

Squashed commit of the following:

commit 5fdd156dc9089baac2e975a85373146e0b788731
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Oct 5 12:18:06 2017 -0700

    [Timer] Test mutation observation

    ...to verify resolution of root cause for #1741

commit 348b193fd45fc457d4b56bc1ddb2249aab65afba
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Oct 5 12:15:05 2017 -0700

    [Timers] Update expected API usage in Follow Timer test

commit 7a584dd993d68c4c50a99ac66976420b5931893c
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Oct 5 12:12:11 2017 -0700

    [Timers] Update spec for timerService

    ...to account for use of openmct.objects

commit ad396a79f0bad9dfc5382745943dd34ddcee1bef
Author: Victor Woeltjen <victor.woeltjen@nasa.gov>
Date:   Thu Oct 5 12:10:25 2017 -0700

    [Timer] Observe timer mutations

    ...such that followed timer remains in sync with timer model,
    e.g. during navigation. Fixes #1741
2017-10-20 18:05:35 -07:00
Andrew Henry
07d9769966 Merge pull request #1786 from nasa/pass-row-as-structure-1785
[Controls] pass item as structure
2017-10-20 17:29:48 -07:00
Pete Richards
385b6177b2 [Controls] pass item as structure
Pass the item to child controls inside of a composite instead of
the row object.   Thus, options are correctly passed to children.

Fixes #1785.
2017-10-20 17:19:11 -07:00
Pete Richards
7f68d26433 Merge pull request #1642 from nasa/view-api-implementation
[ViewAPI] implement initial view API
2017-10-20 17:11:28 -07:00
257 changed files with 14749 additions and 8802 deletions

15
API.md
View File

@@ -879,6 +879,21 @@ openmct.install(openmct.plugins.CouchDB('http://localhost:9200'))
* `openmct.plugins.Espresso` and `openmct.plugins.Snow` are two different
themes (dark and light) available for Open MCT. Note that at least one
of these themes must be installed for Open MCT to appear correctly.
* `openmct.plugins.URLIndicatorPlugin` adds an indicator which shows the
availability of a URL with the following options:
- `url` : URL to indicate the status of
- `cssClass`: Icon to show in the status bar, defaults to `icon-database`, [list of all icons](https://nasa.github.io/openmct/style-guide/#/browse/styleguide:home?view=items)
- `interval`: Interval between checking the connection, defaults to `10000`
- `label` Name showing up as text in the status bar, defaults to url
```javascript
openmct.install(openmct.plugins.URLIndicatorPlugin({
url: 'http://google.com',
cssClass: 'check',
interval: 10000,
label: 'Google'
})
);
```
* `openmct.plugins.LocalStorage` provides persistence of user-created
objects in browser-local storage. This is particularly useful in
development environments.

View File

@@ -17,7 +17,7 @@
"screenfull": "^3.0.0",
"node-uuid": "^1.4.7",
"comma-separated-values": "^3.6.4",
"FileSaver.js": "^0.0.2",
"file-saver": "^1.3.3",
"zepto": "^1.1.6",
"eventemitter3": "^1.2.0",
"lodash": "3.10.1",

View File

@@ -30,7 +30,8 @@ define([
amplitude: 1,
period: 10,
offset: 0,
dataRateInHz: 1
dataRateInHz: 1,
phase: 0
};
function GeneratorProvider() {
@@ -50,16 +51,19 @@ define([
'amplitude',
'period',
'offset',
'dataRateInHz'
'dataRateInHz',
'phase',
];
request = request || {};
var workerRequest = {};
props.forEach(function (prop) {
if (domainObject.telemetry && domainObject.telemetry.hasOwnProperty(prop)) {
workerRequest[prop] = domainObject.telemetry[prop];
}
if (request.hasOwnProperty(prop)) {
if (request && request.hasOwnProperty(prop)) {
workerRequest[prop] = request[prop];
}
if (!workerRequest[prop]) {
@@ -67,7 +71,7 @@ define([
}
workerRequest[prop] = Number(workerRequest[prop]);
});
workerRequest.name = domainObject.name;
return workerRequest;
};

View File

@@ -0,0 +1,80 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
define([
], function (
) {
function StateGeneratorProvider() {
}
function pointForTimestamp(timestamp, duration, name) {
return {
name: name,
utc: Math.floor(timestamp / duration) * duration,
value: Math.floor(timestamp / duration) % 2
};
}
StateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) {
return domainObject.type === 'example.state-generator';
};
StateGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
var duration = domainObject.telemetry.duration * 1000;
var interval = setInterval(function () {
var now = Date.now();
callback(pointForTimestamp(now, duration, domainObject.name));
}, duration);
return function () {
clearInterval(interval);
};
};
StateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
return domainObject.type === 'example.state-generator';
};
StateGeneratorProvider.prototype.request = function (domainObject, options) {
var start = options.start;
var end = options.end;
var duration = domainObject.telemetry.duration * 1000;
if (options.strategy === 'latest' || options.size === 1) {
start = end;
}
var data = [];
while (start <= end && data.length < 5000) {
data.push(pointForTimestamp(start, duration, domainObject.name));
start += 5000;
}
return Promise.resolve(data);
};
return StateGeneratorProvider;
});

View File

@@ -44,9 +44,7 @@ define([
message = message.data;
var callback = this.callbacks[message.id];
if (callback) {
if (callback(message)) {
delete this.callbacks[message.id];
}
callback(message);
}
};
@@ -72,6 +70,7 @@ define([
deferred.resolve = resolve;
deferred.reject = reject;
});
var messageId;
function callback(message) {
if (message.error) {
@@ -79,33 +78,27 @@ define([
} else {
deferred.resolve(message.data);
}
return true;
delete this.callbacks[messageId];
}
this.dispatch('request', request, callback);
messageId = this.dispatch('request', request, callback.bind(this));
return promise;
};
WorkerInterface.prototype.subscribe = function (request, cb) {
var isCancelled = false;
var callback = function (message) {
if (isCancelled) {
return true;
}
function callback(message) {
cb(message.data);
};
var messageId = this.dispatch('subscribe', request, callback)
var messageId = this.dispatch('subscribe', request, callback);
return function () {
isCancelled = true;
this.dispatch('unsubscribe', {
id: messageId
});
delete this.callbacks[messageId];
}.bind(this);
};

View File

@@ -62,10 +62,11 @@
self.postMessage({
id: message.id,
data: {
name: data.name,
utc: nextStep,
yesterday: nextStep - 60*60*24*1000,
sin: sin(nextStep, data.period, data.amplitude, data.offset),
cos: cos(nextStep, data.period, data.amplitude, data.offset)
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase),
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase)
}
});
nextStep += step;
@@ -82,21 +83,22 @@
}
function onRequest(message) {
var data = message.data;
if (data.end == undefined) {
data.end = Date.now();
var request = message.data;
if (request.end == undefined) {
request.end = Date.now();
}
if (data.start == undefined){
data.start = data.end - FIFTEEN_MINUTES;
if (request.start == undefined){
request.start = request.end - FIFTEEN_MINUTES;
}
var now = Date.now();
var start = data.start;
var end = data.end > now ? now : data.end;
var amplitude = data.amplitude;
var period = data.period;
var offset = data.offset;
var dataRateInHz = data.dataRateInHz;
var start = request.start;
var end = request.end > now ? now : request.end;
var amplitude = request.amplitude;
var period = request.period;
var offset = request.offset;
var dataRateInHz = request.dataRateInHz;
var phase = request.phase;
var step = 1000 / dataRateInHz;
var nextStep = start - (start % step) + step;
@@ -105,10 +107,11 @@
for (; nextStep < end && data.length < 5000; nextStep += step) {
data.push({
name: request.name,
utc: nextStep,
yesterday: nextStep - 60*60*24*1000,
sin: sin(nextStep, period, amplitude, offset),
cos: cos(nextStep, period, amplitude, offset)
sin: sin(nextStep, period, amplitude, offset, phase),
cos: cos(nextStep, period, amplitude, offset, phase)
});
}
self.postMessage({
@@ -117,14 +120,14 @@
});
}
function cos(timestamp, period, amplitude, offset) {
function cos(timestamp, period, amplitude, offset, phase) {
return amplitude *
Math.cos(timestamp / period / 1000 * Math.PI * 2) + offset;
Math.cos(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
}
function sin(timestamp, period, amplitude, offset) {
function sin(timestamp, period, amplitude, offset, phase) {
return amplitude *
Math.sin(timestamp / period / 1000 * Math.PI * 2) + offset;
Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
}
function sendError(error, message) {

View File

@@ -23,10 +23,12 @@
define([
"./GeneratorProvider",
"./SinewaveLimitCapability"
"./SinewaveLimitCapability",
"./StateGeneratorProvider"
], function (
GeneratorProvider,
SinewaveLimitCapability
SinewaveLimitCapability,
StateGeneratorProvider
) {
var legacyExtensions = {
@@ -46,6 +48,75 @@ define([
openmct.legacyExtension(type, extension)
})
});
openmct.types.addType("example.state-generator", {
name: "State Generator",
description: "For development use. Generates test enumerated telemetry by cycling through a given set of states",
cssClass: "icon-telemetry",
creatable: true,
form: [
{
name: "State Duration (seconds)",
control: "textfield",
cssClass: "l-input-sm l-numeric",
key: "duration",
required: true,
property: [
"telemetry",
"duration"
],
pattern: "^\\d*(\\.\\d*)?$"
}
],
initialize: function (object) {
object.telemetry = {
duration: 5,
values: [
{
key: "name",
name: "Name"
},
{
key: "utc",
name: "Time",
format: "utc",
hints: {
domain: 1
}
},
{
key: "state",
source: "value",
name: "State",
format: "enum",
enumerations: [
{
value: 0,
string: "OFF"
},
{
value: 1,
string: "ON"
}
],
hints: {
range: 1
}
},
{
key: "value",
name: "Value",
hints: {
range: 2
}
}
]
}
}
});
openmct.telemetry.addProvider(new StateGeneratorProvider());
openmct.types.addType("generator", {
name: "Sine Wave Generator",
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
@@ -99,6 +170,18 @@ define([
"dataRateInHz"
],
pattern: "^\\d*(\\.\\d*)?$"
},
{
name: "Phase (radians)",
control: "textfield",
cssClass: "l-input-sm l-numeric",
key: "phase",
required: true,
property: [
"telemetry",
"phase"
],
pattern: "^\\d*(\\.\\d*)?$"
}
],
initialize: function (object) {
@@ -107,7 +190,12 @@ define([
amplitude: 1,
offset: 0,
dataRateInHz: 1,
phase: 0,
values: [
{
key: "name",
name: "Name"
},
{
key: "utc",
name: "Time",
@@ -142,6 +230,7 @@ define([
};
}
});
openmct.telemetry.addProvider(new GeneratorProvider());
};

View File

@@ -48,8 +48,9 @@ define([
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
];
function pointForTimestamp(timestamp) {
function pointForTimestamp(timestamp, name) {
return {
name: name,
utc: Math.floor(timestamp / 5000) * 5000,
url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
};
@@ -61,7 +62,7 @@ define([
},
subscribe: function (domainObject, callback) {
var interval = setInterval(function () {
callback(pointForTimestamp(Date.now()));
callback(pointForTimestamp(Date.now(), domainObject.name));
}, 5000);
return function (interval) {
@@ -79,8 +80,8 @@ define([
var start = options.start;
var end = options.end;
var data = [];
while (start < end && data.length < 5000) {
data.push(pointForTimestamp(start));
while (start <= end && data.length < 5000) {
data.push(pointForTimestamp(start, domainObject.name));
start += 5000;
}
return Promise.resolve(data);
@@ -93,7 +94,7 @@ define([
options.strategy === 'latest';
},
request: function (domainObject, options) {
return Promise.resolve([pointForTimestamp(Date.now())]);
return Promise.resolve([pointForTimestamp(Date.now(), domainObject.name)]);
}
};
@@ -109,6 +110,10 @@ define([
initialize: function (object) {
object.telemetry = {
values: [
{
name: 'Name',
key: 'name'
},
{
name: 'Time',
key: 'utc',

View File

@@ -121,7 +121,7 @@
<h2>Palettes</h2>
<div class="cols cols1-1">
<div class="col">
<p>Use a palette to provide color choices. Similar to context menus and dropdowns, palettes should be dismissed when a choice is made within them, or if the user clicks outside one.</p>
<p>Use a palette to provide color choices. Similar to context menus and dropdowns, palettes should be dismissed when a choice is made within them, or if the user clicks outside one. Selected palette choices should utilize the <code>selected</code> CSS class to visualize indicate that state.</p>
<p>Note that while this example uses static markup for illustrative purposes, don't do this - use a front-end framework with repeaters to build the color choices.</p>
</div>
<mct-example><div style="height: 220px" title="Ignore me, I'm just here to provide space for this example.">
@@ -129,9 +129,9 @@
<div class="s-button s-menu-button menu-element t-color-palette icon-paint-bucket" ng-controller="ClickAwayController as toggle">
<span class="l-click-area" ng-click="toggle.toggle()"></span>
<span class="color-swatch" style="background: rgb(255, 0, 0);"></span>
<div class="menu l-color-palette" ng-show="toggle.isActive()">
<div class="menu l-palette l-color-palette" ng-show="toggle.isActive()">
<div class="l-palette-row l-option-row">
<div class="l-palette-item s-palette-item " ng-click="ngModel[field] = 'transparent'"></div>
<div class="l-palette-item s-palette-item no-selection"></div>
<span class="l-palette-item-label">None</span>
</div>
<div class="l-palette-row">
@@ -147,7 +147,7 @@
<div class="l-palette-item s-palette-item" style="background: rgb(255, 255, 255);"></div>
</div>
<div class="l-palette-row">
<div class="l-palette-item s-palette-item" style="background: rgb(136, 32, 32);"></div>
<div class="l-palette-item s-palette-item selected" style="background: rgb(255, 0, 0);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(224, 64, 64);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(240, 160, 72);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(255, 248, 96);"></div>

View File

@@ -25,8 +25,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title></title>
<script src="bower_components/requirejs/require.js">
</script>
<script src="bower_components/requirejs/require.js"> </script>
<script>
var THIRTY_MINUTES = 30 * 60 * 1000;
@@ -50,7 +49,7 @@
name: "Fixed",
timeSystem: 'utc',
bounds: {
start: Date.now() - 30 * 60 * 1000,
start: Date.now() - THIRTY_MINUTES,
end: Date.now()
}
},
@@ -65,6 +64,7 @@
}
]
}));
openmct.install(openmct.plugins.SummaryWidget());
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
openmct.time.timeSystem('utc');
openmct.start();

View File

@@ -33,7 +33,7 @@ requirejs.config({
"moment": "bower_components/moment/moment",
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
"moment-timezone": "bower_components/moment-timezone/builds/moment-timezone-with-data",
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
"saveAs": "bower_components/file-saver/FileSaver.min",
"screenfull": "bower_components/screenfull/dist/screenfull.min",
"text": "bower_components/text/text",
"uuid": "bower_components/node-uuid/uuid",
@@ -66,6 +66,9 @@ requirejs.config({
"moment-duration-format": {
"deps": ["moment"]
},
"saveAs": {
"exports": "saveAs"
},
"screenfull": {
"exports": "screenfull"
},

View File

@@ -57,7 +57,12 @@
</div>
<mct-representation key="representation.selected.key"
mct-object="representation.selected.key && domainObject"
class="abs flex-elem grows object-holder-main scroll">
class="abs flex-elem grows object-holder-main scroll"
mct-selectable="{
item: domainObject.useCapability('adapter'),
oldItem: domainObject
}"
mct-init-select>
</mct-representation>
</div>
</div>

View File

@@ -19,12 +19,21 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div ng-controller="InspectorController">
<div ng-repeat="region in regions">
<div ng-controller="InspectorController as controller">
<mct-representation
key="region.content.key"
mct-object="domainObject"
key="'object-properties'"
mct-object="controller.selectedItem()"
ng-model="ngModel">
</mct-representation>
</div>
<div ng-if="!controller.hasProviderView()">
<mct-representation
key="inspectorKey"
mct-object="controller.selectedItem()"
ng-model="ngModel">
</mct-representation>
</div>
<div class='inspector-provider-view'>
</div>
</div>

View File

@@ -38,8 +38,6 @@
ng-class="{ last:($index + 1) === contextualParents.length }">
<mct-representation key="'label'"
mct-object="parent"
ng-model="ngModel"
ng-click="ngModel.selectedObject = parent"
class="location-item">
</mct-representation>
</span>
@@ -51,8 +49,6 @@
ng-class="{ last:($index + 1) === primaryParents.length }">
<mct-representation key="'label'"
mct-object="parent"
ng-model="ngModel"
ng-click="ngModel.selectedObject = parent"
class="location-item">
</mct-representation>
</span>

View File

@@ -20,7 +20,7 @@
at runtime from the About dialog for additional information.
-->
<div class="abs top-bar">
<div class="title">{{ngModel.title}}</div>
<div class="dialog-title">{{ngModel.title}}</div>
<div class="hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
</div>
<div class='abs editor'>

View File

@@ -1,11 +1,10 @@
<div class="l-message"
ng-class="'message-severity-' + ngModel.severity">
<div class="ui-symbol type-icon message-type"></div>
<div class="message-contents">
<div class="w-message-contents">
<div class="top-bar">
<div class="title">{{ngModel.title}}</div>
<div class="hint" ng-hide="ngModel.hint === undefined">{{ngModel.hint}}</div>
</div>
<div class="hint" ng-hide="ngModel.hint === undefined">{{ngModel.hint}}</div>
<div class="message-body">
<div class="message-action">
{{ngModel.actionText}}
@@ -25,8 +24,6 @@
ng-click="ngModel.primaryOption.callback()">
{{ngModel.primaryOption.label}}
</a>
</div>
</div>
</div>

View File

@@ -1,17 +1,17 @@
<mct-container key="overlay" class="t-message-list">
<div class="message-contents">
<div class="abs top-bar">
<div class="title">{{ngModel.dialog.title}}</div>
<mct-container key="overlay">
<div class="t-message-list">
<div class="top-bar">
<div class="dialog-title">{{ngModel.dialog.title}}</div>
<div class="hint">Displaying {{ngModel.dialog.messages.length}} message<span ng-show="ngModel.dialog.messages.length > 1 ||
ngModel.dialog.messages.length == 0">s</span>
</div>
</div>
<div class="abs message-body">
<div class="w-messages">
<mct-include
ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'"
key="'message'" ng-model="msg.model"></mct-include>
ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'"
key="'message'" ng-model="msg.model"></mct-include>
</div>
<div class="abs bottom-bar">
<div class="bottom-bar">
<a ng-repeat="dialogAction in ngModel.dialog.actions"
class="s-button major"
ng-click="dialogAction.action()">

View File

@@ -21,7 +21,7 @@
-->
<mct-container key="overlay">
<div class="abs top-bar">
<div class="title">{{ngModel.dialog.title}}</div>
<div class="dialog-title">{{ngModel.dialog.title}}</div>
<div class="hint">{{ngModel.dialog.hint}}</div>
</div>
<div class='abs editor'>

View File

@@ -121,7 +121,8 @@ define([
"key": "ElementsController",
"implementation": ElementsController,
"depends": [
"$scope"
"$scope",
"openmct"
]
},
{
@@ -299,9 +300,6 @@ define([
{
"key": "edit-elements",
"template": elementsTemplate,
"uses": [
"composition"
],
"gestures": [
"drop"
]
@@ -385,7 +383,10 @@ define([
]
},
{
"implementation": EditToolbarRepresenter
"implementation": EditToolbarRepresenter,
"depends": [
"openmct"
]
}
],
"constants": [

View File

@@ -61,7 +61,12 @@
<mct-representation key="representation.selected.key"
mct-object="representation.selected.key && domainObject"
class="abs flex-elem grows object-holder-main scroll"
toolbar="toolbar">
toolbar="toolbar"
mct-selectable="{
item: domainObject.useCapability('adapter'),
oldItem: domainObject
}"
mct-init-select>
</mct-representation>
</div><!--/ l-object-wrapper-inner -->
</div>

View File

@@ -25,7 +25,7 @@
ng-model="filterBy">
</mct-include>
<div class="flex-elem grows vscroll">
<ul class="tree">
<ul class="tree" ng-if="composition.length > 0">
<li ng-repeat="containedObject in composition | filter:searchElements">
<span class="tree-item">
<mct-representation
@@ -36,5 +36,6 @@
</span>
</li>
</ul>
<div ng-if="composition.length === 0">No contained elements</div>
</div>
</div>

View File

@@ -101,10 +101,15 @@ define(
*/
EditorCapability.prototype.finish = function () {
var domainObject = this.domainObject;
return this.transactionService.cancel().then(function () {
domainObject.getCapability("status").set("editing", false);
return domainObject;
});
if (this.transactionService.isActive()) {
return this.transactionService.cancel().then(function () {
domainObject.getCapability("status").set("editing", false);
return domainObject;
});
} else {
return Promise.resolve(domainObject);
}
};
/**

View File

@@ -29,7 +29,11 @@ define(
*
* @constructor
*/
function ElementsController($scope) {
function ElementsController($scope, openmct) {
this.scope = $scope;
this.scope.composition = [];
var self = this;
function filterBy(text) {
if (typeof text === 'undefined') {
return $scope.searchText;
@@ -47,10 +51,44 @@ define(
}
}
function setSelection(selection) {
self.scope.selection = selection;
self.refreshComposition(selection);
}
$scope.filterBy = filterBy;
$scope.searchElements = searchElements;
openmct.selection.on('change', setSelection);
setSelection(openmct.selection.get());
$scope.$on("$destroy", function () {
openmct.selection.off("change", setSelection);
});
}
/**
* Gets the composition for the selected object and populates the scope with it.
*
* @param selection the selection object
* @private
*/
ElementsController.prototype.refreshComposition = function (selection) {
if (!selection[0]) {
return;
}
var selectedObjectComposition = selection[0].context.oldItem.useCapability('composition');
if (selectedObjectComposition) {
selectedObjectComposition.then(function (composition) {
this.scope.composition = composition;
}.bind(this));
} else {
this.scope.composition = [];
}
};
return ElementsController;
}
);

View File

@@ -38,7 +38,7 @@ define(
* @constructor
* @implements {Representer}
*/
function EditToolbarRepresenter(scope, element, attrs) {
function EditToolbarRepresenter(openmct, scope, element, attrs) {
var self = this;
// Mark changes as ready to persist
@@ -109,6 +109,7 @@ define(
this.updateSelection = updateSelection;
this.toolbar = undefined;
this.toolbarObject = {};
this.openmct = openmct;
// If this representation exposes a toolbar, set up watches
// to synchronize with it.
@@ -146,7 +147,7 @@ define(
// Expose the toolbar object to the parent scope
initialize(definition);
// Create a selection scope
this.setSelection(new EditToolbarSelection());
this.setSelection(new EditToolbarSelection(this.openmct));
// Initialize toolbar to an empty selection
this.updateSelection([]);
};

View File

@@ -38,10 +38,18 @@ define(
* @memberof platform/commonUI/edit
* @constructor
*/
function EditToolbarSelection() {
function EditToolbarSelection(openmct) {
this.selection = [{}];
this.selecting = false;
this.selectedObj = undefined;
openmct.selection.on('change', function (selection) {
if (selection[0] && selection[0].context.toolbar) {
this.select(selection[0].context.toolbar);
} else {
this.deselect();
}
}.bind(this));
}
/**

View File

@@ -62,6 +62,7 @@ define(
);
mockTransactionService.commit.andReturn(fastPromise());
mockTransactionService.cancel.andReturn(fastPromise());
mockTransactionService.isActive = jasmine.createSpy('isActive');
mockStatusCapability = jasmine.createSpyObj(
"statusCapability",
@@ -141,6 +142,7 @@ define(
describe("finish", function () {
beforeEach(function () {
mockTransactionService.isActive.andReturn(true);
capability.edit();
capability.finish();
});
@@ -152,6 +154,23 @@ define(
});
});
describe("finish", function () {
beforeEach(function () {
mockTransactionService.isActive.andReturn(false);
capability.edit();
});
it("does not cancel transaction when transaction is not active", function () {
capability.finish();
expect(mockTransactionService.cancel).not.toHaveBeenCalled();
});
it("returns a promise", function () {
expect(capability.finish() instanceof Promise).toBe(true);
});
});
describe("dirty", function () {
var model = {};

View File

@@ -27,11 +27,23 @@ define(
describe("The Elements Pane controller", function () {
var mockScope,
mockOpenMCT,
mockSelection,
controller;
beforeEach(function () {
mockScope = jasmine.createSpy("$scope");
controller = new ElementsController(mockScope);
mockScope = jasmine.createSpyObj("$scope", ['$on']);
mockSelection = jasmine.createSpyObj("selection", [
'on',
'off',
'get'
]);
mockSelection.get.andReturn([]);
mockOpenMCT = {
selection: mockSelection
};
controller = new ElementsController(mockScope, mockOpenMCT);
});
function getModel(model) {

View File

@@ -29,7 +29,9 @@ define(
mockElement,
testAttrs,
mockUnwatch,
representer;
representer,
mockOpenMCT,
mockSelection;
beforeEach(function () {
mockScope = jasmine.createSpyObj(
@@ -46,7 +48,18 @@ define(
mockScope.$parent.$watchCollection.andReturn(mockUnwatch);
mockSelection = jasmine.createSpyObj("selection", [
'on',
'off',
'get'
]);
mockSelection.get.andReturn([]);
mockOpenMCT = {
selection: mockSelection
};
representer = new EditToolbarRepresenter(
mockOpenMCT,
mockScope,
mockElement,
testAttrs

View File

@@ -28,13 +28,25 @@ define(
var testProxy,
testElement,
otherElement,
selection;
selection,
mockSelection,
mockOpenMCT;
beforeEach(function () {
testProxy = { someKey: "some value" };
testElement = { someOtherKey: "some other value" };
otherElement = { yetAnotherKey: 42 };
selection = new EditToolbarSelection();
mockSelection = jasmine.createSpyObj("selection", [
// 'select',
'on',
'off',
'get'
]);
mockSelection.get.andReturn([]);
mockOpenMCT = {
selection: mockSelection
};
selection = new EditToolbarSelection(mockOpenMCT);
selection.proxy(testProxy);
});

View File

@@ -121,6 +121,9 @@ define([
};
UTCTimeFormat.prototype.parse = function (text) {
if (typeof text === 'number') {
return text;
}
return moment.utc(text, DATE_FORMATS).valueOf();
};

View File

@@ -41,6 +41,7 @@ define([
"./src/controllers/BannerController",
"./src/directives/MCTContainer",
"./src/directives/MCTDrag",
"./src/directives/MCTSelectable",
"./src/directives/MCTClickElsewhere",
"./src/directives/MCTResize",
"./src/directives/MCTPopup",
@@ -90,6 +91,7 @@ define([
BannerController,
MCTContainer,
MCTDrag,
MCTSelectable,
MCTClickElsewhere,
MCTResize,
MCTPopup,
@@ -328,6 +330,13 @@ define([
"$document"
]
},
{
"key": "mctSelectable",
"implementation": MCTSelectable,
"depends": [
"openmct"
]
},
{
"key": "mctClickElsewhere",
"implementation": MCTClickElsewhere,

View File

@@ -137,6 +137,11 @@
min-height: 0;
&.holder:not(:last-child) { margin-bottom: $interiorMarginLg; }
}
&.l-flex-accordion .flex-accordion-holder {
display: flex;
flex-direction: column;
//overflow: hidden !important;
}
.flex-container { @include flex-direction(column); }
}

View File

@@ -99,7 +99,7 @@ $plotXBarH: 32px;
$plotLegendH: 20px;
$plotSwatchD: 8px;
// 1: Top, 2: right, 3: bottom, 4: left
$plotDisplayArea: ($plotLegendH + $interiorMargin, 0, $plotXBarH, $plotYBarW);
$plotDisplayArea: (0, 0, $plotXBarH, $plotYBarW);
/* min plot height is based on user testing to find minimum useful height */
$plotMinH: 95px;
/*************** Bubbles */

View File

@@ -25,6 +25,7 @@
}
.l-fixed-position-item {
border-width: 1px;
position: absolute;
&.s-not-selected {
opacity: 0.8;

View File

@@ -40,7 +40,7 @@
* Use https://icomoon.io/app with icomoon-project-openmct-symbols-12px.json
* to generate font files
*/
font-family: 'symbolsfont 12px';
font-family: 'symbolsfont-12px';
src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.eot');
src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.eot?#iefix') format('embedded-opentype'),
url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.woff') format('woff'),
@@ -180,6 +180,20 @@ a.disabled {
@include ellipsize();
}
.no-selection {
// aka selection = "None". Used in palettes and their menu buttons.
$c: red; $s: 48%; $e: 52%;
@include background-image(linear-gradient(-45deg,
transparent $s - 5%,
$c $s,
$c $e,
transparent $e + 5%
));
background-repeat: no-repeat;
background-size: contain;
}
.scrolling,
.scroll {
overflow: auto;
@@ -234,6 +248,12 @@ a.disabled {
color: rgba(#fff, 0.2);
}
.comma-list span {
&:not(:first-child) {
&:before { content: ', '; }
}
}
.test-stripes {
@include bgDiagonalStripes();
}

View File

@@ -44,6 +44,12 @@
}
}
.t-alert-unsynced {
@extend .icon-alert-triangle;
color: $colorPausedBg;
}
.bar .ui-symbol {
display: inline-block;
}
@@ -81,18 +87,5 @@
@include transform(scale(0.3));
z-index: 2;
}
/* .t-item-icon-glyph {
&:after {
color: $colorIconLink;
content: '\e921'; //$glyph-icon-link;
height: auto; width: auto;
position: absolute;
left: 0; top: 0; right: 0; bottom: 20%;
@include transform-origin(bottom left);
@include transform(scale(0.3));
z-index: 2;
}
}*/
}
}

View File

@@ -53,6 +53,7 @@
.l-inspector-part {
box-sizing: border-box;
padding-right: $interiorMargin;
.tree .form {
margin-left: $treeVCW + $interiorMarginLg;
}
@@ -78,6 +79,7 @@
}
}
.form-row {
// To be replaced with .inspector-config, see below.
@include align-items(center);
border: none !important;
margin-bottom: 0 !important;
@@ -99,15 +101,12 @@
position: relative;
}
ul li {
margin-bottom: $interiorMarginLg;
}
em.t-inspector-part-header {
border-radius: $basicCr;
background-color: $colorInspectorSectionHeaderBg;
color: $colorInspectorSectionHeaderFg;
margin-bottom: $interiorMargin;
margin-top: $interiorMarginLg;
//margin-bottom: $interiorMargin;
padding: floor($formTBPad * .75) $formLRPad;
text-transform: uppercase;
}
@@ -201,3 +200,102 @@ mct-representation:not(.s-status-editing) .l-inspect {
pointer-events: inherit;
}
}
// NEW COMPACT FORM, FOR USE IN INSPECTOR
// ul > li > label, control
// Make a new UL for each form section
// Allow control-first, controls-below
.l-inspect .tree ul li,
.inspector-config ul li {
padding: 2px 0;
}
.inspector-config {
$labelW: 40%;
$minW: $labelW;
ul {
margin-bottom: $interiorMarginLg;
li {
@include display(flex);
@include flex-wrap(wrap);
@include align-items(center);
label,
.control {
@include display(flex);
min-width: $minW;
}
label {
line-height: inherit;
padding: $interiorMarginSm 0;
width: $labelW;
}
.control {
@include flex-grow(1);
}
&:not(.section-header) {
&:not(.connects-to-previous) {
//border-top: 1px solid $colorFormLines;
}
}
&.connects-to-previous {
padding-top: 0 !important;
}
&.section-header {
margin-top: $interiorMarginLg;
border-top: 1px solid $colorFormLines;
}
&.controls-first {
.control {
@include flex-grow(0);
margin-right: $interiorMargin;
min-width: 0;
order: 1;
width: auto;
}
label {
@include flex-grow(1);
order: 2;
width: auto;
}
}
&.controls-under {
display: block;
.control, label {
display: block;
width: auto;
}
ul li {
border-top: none !important;
padding: 0;
}
}
}
}
.form-error {
// Block element that visually flags an error and contains a message
background-color: $colorFormFieldErrorBg;
color: $colorFormFieldErrorFg;
border-radius: $basicCr;
display: block;
padding: 1px 6px;
&:before {
content: $glyph-icon-alert-triangle;
display: inline;
font-family: symbolsfont;
margin-right: $interiorMarginSm;
}
}
}
.tree .inspector-config {
margin-left: $treeVCW + $interiorMarginLg;
}

View File

@@ -37,7 +37,7 @@
/********************************* CONTROLS */
@import "controls/breadcrumb";
@import "controls/buttons";
@import "controls/color-palette";
@import "controls/palette";
@import "controls/controls";
@import "controls/lists";
@import "controls/menus";
@@ -70,6 +70,7 @@
@import "fixed-position";
@import "lists/tabular";
@import "plots/plots-main";
@import "plots/legend";
@import "iframe";
@import "views";
@import "items/item";
@@ -80,3 +81,4 @@
@import "autoflow";
@import "features/imagery";
@import "features/time-display";
@import "widgets";

View File

@@ -50,7 +50,6 @@
content:'';
font-family: symbolsfont;
font-size: 0.8em;
display: inline;
margin-right: $interiorMarginSm;
}
}

View File

@@ -5,6 +5,7 @@
}
.l-view-section {
//@include test(orange, 0.1);
@include absPosDefault(0);
h2 {
color: #fff;

View File

@@ -0,0 +1,306 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
/************************************************************* WIDGET OBJECT */
.l-summary-widget {
// Widget layout classes here
@include ellipsize();
display: inline-block;
text-align: center;
.widget-label:before {
// Widget icon
font-size: 0.9em;
margin-right: $interiorMarginSm;
}
}
.s-summary-widget {
// Widget style classes here
@include boxShdw($shdwBtns);
border-radius: $basicCr;
border-style: solid;
border-width: 1px;
box-sizing: border-box;
cursor: default;
font-size: 0.8rem;
padding: $interiorMarginLg $interiorMarginLg * 2;
&[href] {
cursor: pointer;
}
}
.widget-edit-holder {
// Hide edit area when in browse mode
display: none;
}
.widget-rule-header {
@extend .l-flex-row;
@include align-items(center);
margin-bottom: $interiorMargin;
> .flex-elem {
&:not(:first-child) {
margin-left: $interiorMargin;
}
}
}
.widget-rules-wrapper,
.widget-rule-content,
.w-widget-test-data-content {
@include trans-prop-nice($props: (height, min-height, opacity), $dur: 250ms);
min-height: 0;
height: 0;
opacity: 0;
overflow: hidden;
pointer-events: none;
}
.widget-rules-wrapper {
flex: 1 1 auto !important;
}
.widget-rule-content.expanded {
overflow: visible !important;
min-height: 50px;
height: auto;
opacity: 1;
pointer-events: inherit;
}
.w-widget-test-data-content {
.l-enable {
padding: $interiorMargin 0;
}
.w-widget-test-data-items {
max-height: 20vh;
overflow-y: scroll !important;
padding-right: $interiorMargin;
}
}
.l-widget-thumb-wrapper,
.l-compact-form label {
$ruleLabelW: 40%;
$ruleLabelMaxW: 150px;
@include display(flex);
max-width: $ruleLabelMaxW;
width: $ruleLabelW;
}
.t-message-widget-no-data {
display: none;
}
/********************************************************** EDITING A WIDGET */
.s-status-editing > mct-view > .w-summary-widget {
// Classes for editor layout while editing a widget
// This selector is ugly and brittle, but needed to prevent interface from showing when widget is in a layout
// being edited.
@include absPosDefault();
@extend .l-flex-col;
> .l-summary-widget {
// Main view of the summary widget
// Give some airspace and center the widget in the area
margin: 30px auto;
}
.widget-edit-holder {
display: flex; // Overrides `display: none` during Browse mode
.flex-accordion-holder {
// Needed because otherwise accordion elements "creep" when contents expand and contract
display: block !important;
}
&.expanded-widget-test-data {
.w-widget-test-data-content {
min-height: 50px;
height: auto;
opacity: 1;
pointer-events: inherit;
}
&:not(.expanded-widget-rules) {
// Test data is expanded and rules are collapsed
// Make text data take up all the vertical space
.flex-accordion-holder { display: flex; }
.widget-test-data {
flex-grow: 999999;
}
.w-widget-test-data-items {
max-height: inherit;
}
}
}
&.expanded-widget-rules {
.widget-rules-wrapper {
min-height: 50px;
height: auto;
opacity: 1;
pointer-events: inherit;
}
}
}
&.s-status-no-data {
.widget-edit-holder {
opacity: 0.3;
pointer-events: none;
}
.t-message-widget-no-data {
display: flex;
}
}
.l-compact-form {
// Overrides on .l-compact-form
ul {
&:last-child { margin: 0; }
li {
@include align-items(flex-start);
@include flex-wrap(nowrap);
line-height: 230%; // Provide enough space when controls wrap
padding: 2px 0;
&:not(.widget-rule-header) {
&:not(.connects-to-previous) {
border-top: 1px solid $colorFormLines;
}
}
&.connects-to-previous {
padding: $interiorMargin 0;
}
> label {
display: block; // Needed to align text to right
text-align: right;
}
}
}
&.s-widget-test-data-item {
// Single line of ul li label span, etc.
ul {
li {
border: none !important;
> label {
display: inline-block;
width: auto;
text-align: left;
}
}
}
}
}
}
.widget-edit-holder {
font-size: 0.8rem;
}
.widget-rules-wrapper {
// Wrapper area that holds n rules
box-sizing: border-box;
overflow-y: scroll;
padding-right: $interiorMargin;
}
.l-widget-rule,
.l-widget-test-data-item {
box-sizing: border-box;
margin-bottom: $interiorMarginSm;
padding: $interiorMargin $interiorMarginLg;
}
.l-widget-thumb-wrapper {
@extend .l-flex-row;
@include align-items(center);
> span { display: block; }
.grippy-holder,
.view-control {
margin-right: $interiorMargin;
width: 1em;
height: 1em;
}
.widget-thumb {
@include flex(1 1 auto);
width: 100%;
}
}
.rule-title {
@include flex(0 1 auto);
color: pullForward($colorBodyFg, 50%);
}
.rule-description {
@include flex(1 1 auto);
@include ellipsize();
color: pushBack($colorBodyFg, 20%);
}
.s-widget-rule,
.s-widget-test-data-item {
background-color: rgba($colorBodyFg, 0.1);
border-radius: $basicCr;
}
.widget-thumb {
@include ellipsize();
@extend .s-summary-widget;
@extend .l-summary-widget;
padding: $interiorMarginSm $interiorMargin;
}
// Hide and show elements in the rule-header on hover
.l-widget-rule,
.l-widget-test-data-item {
.grippy,
.l-rule-action-buttons-wrapper,
.l-condition-action-buttons-wrapper,
.l-widget-test-data-item-action-buttons-wrapper {
@include trans-prop-nice($props: opacity, $dur: 500ms);
opacity: 0;
}
&:hover {
.grippy,
.l-rule-action-buttons-wrapper,
.l-widget-test-data-item-action-buttons-wrapper {
@include trans-prop-nice($props: opacity, $dur: 0);
opacity: 1;
}
}
.l-rule-action-buttons-wrapper {
.t-delete {
margin-left: 10px;
}
}
.t-condition {
&:hover {
.l-condition-action-buttons-wrapper {
@include trans-prop-nice($props: opacity, $dur: 0);
opacity: 1;
}
}
}
}

View File

@@ -150,6 +150,26 @@
}
}
/******************************************************** VIEW CONTROLS */
// Expand/collapse > and v arrows, used in tree and plot legend
// Moved this over from a tree-only context 5/18/17
.view-control {
@extend .ui-symbol;
cursor: pointer;
height: 1em; width: 1em;
line-height: inherit;
&:before {
position: absolute;
@include trans-prop-nice(transform, 100ms);
content: $glyph-icon-arrow-right;
@include transform-origin(center);
}
&.expanded:before {
@include transform(rotate(90deg));
}
}
/******************************************************** CUSTOM CHECKBOXES */
label.checkbox.custom,
label.radio.custom {
@@ -261,7 +281,7 @@ input[type="number"] {
input[type="text"].lg { width: 100% !important; }
.l-input-med input[type="text"],
input[type="text"].med { width: 200px !important; }
input[type="text"].sm { width: 50px !important; }
input[type="text"].sm, input[type="number"].sm { width: 50px !important; }
.l-numeric input[type="text"],
input[type="text"].numeric { text-align: right; }
@@ -317,14 +337,10 @@ input[type="text"].s-input-inline,
.select {
@include btnSubtle($bg: $colorSelectBg);
@extend .icon-arrow-down; // Context arrow
@if $shdwBtns != none {
margin: 0 0 2px 0; // Needed to avoid dropshadow from being clipped by parent containers
}
display: inline-block;
padding: 0 $interiorMargin;
overflow: hidden;
position: relative;
line-height: $formInputH;
select {
@include appearance(none);
box-sizing: border-box;
@@ -340,11 +356,13 @@ input[type="text"].s-input-inline,
}
}
&:before {
pointer-events: none;
@include transform(translateY(-50%));
color: rgba($colorInvokeMenu, percentToDecimal($contrastInvokeMenuPercent));
display: block;
pointer-events: none;
position: absolute;
right: $interiorMargin; top: 0;
right: $interiorMargin;
top: 50%;
}
}
@@ -396,8 +414,7 @@ input[type="text"].s-input-inline,
.l-elem-wrapper {
mct-representation {
// Holds the context-available item
// Must have min-width to make flex work properly
// in Safari
// Must have min-width to make flex work properly in Safari
min-width: 0.7em;
}
}
@@ -563,7 +580,6 @@ input[type="text"].s-input-inline,
height: $h;
margin-top: 1 + floor($h/2) * -1;
@include btnSubtle(pullForward($colorBtnBg, 10%));
//border-radius: 50% !important;
}
@mixin sliderKnobRound() {
@@ -578,7 +594,6 @@ input[type="text"].s-input-inline,
input[type="range"] {
// HTML5 range inputs
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
background: transparent; /* Otherwise white in Chrome */
&:focus {
@@ -736,6 +751,30 @@ textarea {
}
}
.view-switcher,
.t-btn-view-large {
@include trans-prop-nice-fade($controlFadeMs);
}
.view-control {
@extend .icon-arrow-right;
cursor: pointer;
font-size: 0.75em;
&:before {
position: absolute;
@include trans-prop-nice(transform, 100ms);
@include transform-origin(center);
}
&.expanded:before {
@include transform(rotate(90deg));
}
}
.grippy {
@extend .icon-grippy;
cursor: move;
}
/******************************************************** BROWSER ELEMENTS */
body.desktop {
::-webkit-scrollbar {

View File

@@ -29,23 +29,27 @@
}
.icon {
font-size: 16px; //120%;
font-size: 16px;
}
.title-label {
margin-left: $interiorMarginSm;
}
.icon-swatch,
.color-swatch {
// Used in color menu buttons in toolbar
$d: 10px;
display: inline-block;
border: 1px solid rgba($colorBtnFg, 0.2);
height: $d;
width: $d;
height: $d; width: $d;
line-height: $d;
vertical-align: middle;
margin-left: $interiorMarginSm;
margin-top: -2px;
&:not(.no-selection) {
border-color: transparent;
}
}
&:after {

View File

@@ -19,7 +19,7 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/******************************************************************* STATUS BLOCK ELEMS */
@mixin statusBannerColors($bg, $fg: $colorStatusFg) {
$bgPb: 30%;
$bgPbD: 10%;
@@ -120,7 +120,11 @@
}
.status-indicator {
background: none !important;
margin-right: $interiorMarginSm;
&[class*='s-status']:before {
font-size: 1em;
}
}
.count {
@@ -136,7 +140,7 @@
}
}
/* Styles for messages and message banners */
/******************************************************************* MESSAGE BANNERS */
.message {
&.block {
border-radius: $basicCr;
@@ -192,7 +196,6 @@
padding: 0 $interiorMargin;
}
.close {
//@include test(red, 0.7);
cursor: pointer;
font-size: 7px;
width: 8px;
@@ -236,132 +239,147 @@
}
}
@mixin messageBlock($iconW: 32px) {
.type-icon.message-type {
/******************************************************************* MESSAGES */
/* Contexts:
In .t-message-list
In .overlay as a singleton
Inline in the view area
*/
// Archetypal message
.l-message {
$iconW: 32px;
@include display(flex);
@include flex-direction(row);
@include align-items(stretch);
padding: $interiorMarginLg;
&:before {
// Icon
@include flex(0 1 auto);
@include txtShdw($shdwStatusIc);
@extend .icon-bell;
color: $colorStatusDefault;
font-size: $iconW;
padding: 1px;
width: $iconW + 2;
margin-right: $interiorMarginLg;
}
.message-severity-info .type-icon.message-type {
&.message-severity-info:before {
@extend .icon-info;
color: $colorInfo;
}
.message-severity-alert .type-icon.message-type {
@extend .icon-bell;
&.message-severity-alert:before {
color: $colorWarningLo;
}
.message-severity-error .type-icon.message-type {
&.message-severity-error:before {
@extend .icon-alert-rect;
color: $colorWarningHi;
}
}
/* Paths:
t-dialog | t-dialog-sm > t-message-single | t-message-list > overlay > holder > contents > l-message >
message-type > (icon)
message-contents >
top-bar >
title
hint
editor >
(if displaying list of messages)
ul > li > l-message >
... same as above
bottom-bar
*/
.l-message {
.w-message-contents {
@include flex(1 1 auto);
@include display(flex);
@include flex-direction(row);
@include align-items(stretch);
.type-icon.message-type {
@include flex(0 1 auto);
position: relative;
}
.message-contents {
@include flex(1 1 auto);
margin-left: $overlayMargin;
position: relative;
@include flex-direction(column);
.top-bar,
> div,
> span {
//@include test(red);
margin-bottom: $interiorMargin;
}
.message-body {
@include flex(1 1 100%);
}
}
// Singleton in an overlay dialog
.t-message-single .l-message,
.t-message-single.l-message {
$iconW: 80px;
@include absPosDefault();
padding: 0;
&:before {
font-size: $iconW;
width: $iconW + 2;
}
.title {
font-size: 1.2em;
}
}
// Singleton inline in a view
.t-message-inline .l-message,
.t-message-inline.l-message {
border-radius: $controlCr;
&.message-severity-info { background-color: rgba($colorInfo, 0.3); }
&.message-severity-alert { background-color: rgba($colorWarningLo, 0.3); }
&.message-severity-error { background-color: rgba($colorWarningHi, 0.3); }
.w-message-contents.l-message-body-only {
.message-body {
margin-bottom: $interiorMarginLg * 2;
margin-top: $interiorMargin;
}
}
}
// In a list
.t-message-list {
@include absPosDefault();
@include display(flex);
@include flex-direction(column);
// Message as singleton
.t-message-single {
@include messageBlock(80px);
}
body.desktop .t-message-single {
.l-message,
.bottom-bar {
@include absPosDefault();
> div,
> span {
margin-bottom: $interiorMargin;
}
.bottom-bar {
top: auto;
height: $ovrFooterH;
.w-messages {
@include flex(1 1 100%);
overflow-y: auto;
padding-right: $interiorMargin;
}
// Each message
.l-message {
border-radius: $controlCr;
background: rgba($colorOvrFg, 0.1);
margin-bottom: $interiorMargin;
.hint,
.bottom-bar {
text-align: left;
}
}
}
@include phonePortrait {
.t-message-single {
.l-message {
@include flex-direction(column);
.message-contents { margin-left: 0; }
}
.type-icon.message-type {
.t-message-single .l-message,
.t-message-single.l-message {
@include flex-direction(column);
&:before {
margin-right: 0;
margin-bottom: $interiorMarginLg;
width: 100%;
text-align: center;
width: 100%;
}
.bottom-bar {
text-align: center !important;
}
}
}
// Messages in list
.t-message-list {
@include messageBlock(32px);
.message-contents {
.l-message {
border-radius: $controlCr;
background: rgba($colorOvrFg, 0.1);
margin-bottom: $interiorMargin;
padding: $interiorMarginLg;
.message-contents,
.bottom-bar {
position: relative;
}
.message-contents {
font-size: 0.9em;
margin-left: $interiorMarginLg;
.message-action { color: pushBack($colorOvrFg, 20%); }
.bottom-bar { text-align: left; }
}
.top-bar,
.message-body {
margin-bottom: $interiorMarginLg;
text-align: center;
.s-button {
display: block;
width: 100%;
}
}
}
}
body.desktop .t-message-list {
.message-contents .l-message { margin-right: $interiorMarginLg; }
.w-message-contents { padding-right: $interiorMargin; }
}
// Alert elements in views
@@ -380,10 +398,6 @@ body.desktop .t-message-list {
.object-header {
.t-object-alert {
display: inline;
&.t-alert-unsynced {
@extend .icon-alert-triangle;
color: $colorPausedBg;
}
}
}
}

View File

@@ -19,11 +19,10 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
.l-color-palette {
.l-palette {
$d: 16px;
$colorsPerRow: 10;
$m: 1;
$colorSelectedColor: #fff;
box-sizing: border-box;
padding: $interiorMargin !important;
@@ -33,46 +32,42 @@
line-height: $d;
width: ($d * $colorsPerRow) + ($m * $colorsPerRow);
&.l-option-row {
margin-bottom: $interiorMargin;
.s-palette-item {
border-color: $colorPaletteFg;
}
}
.l-palette-item {
box-sizing: border-box;
@include txtShdwSubtle(0.8);
@include trans-prop-nice-fade(0.25s);
border: 1px solid transparent;
color: $colorSelectedColor;
display: block;
float: left;
height: $d; width: $d;
line-height: $d * 0.9;
margin: 0 ($m * 1px) ($m * 1px) 0;
position: relative;
text-align: center;
&:before {
// Check mark for selected items
font-size: 0.8em;
}
}
.s-palette-item {
border: 1px solid transparent;
color: $colorPaletteFg;
text-shadow: $shdwPaletteFg;
@include trans-prop-nice-fade(0.25s);
&:hover {
@include trans-prop-nice-fade(0);
border-color: $colorSelectedColor !important;
border-color: $colorPaletteSelected !important;
}
&.selected {
border-color: $colorPaletteSelected;
box-shadow: $shdwPaletteSelected; //Needed to see selection rect on light colored swatches
}
}
.l-palette-item-label {
margin-left: $interiorMargin;
}
&.l-option-row {
margin-bottom: $interiorMargin;
.s-palette-item {
border-color: $colorBodyFg;
}
}
}
}

View File

@@ -80,23 +80,32 @@
// Editing Grids
.l-grid-holder {
display: block;
.l-grid {
&.l-grid-x { @include bgTicks($colorGridLines, 'x'); }
&.l-grid-y { @include bgTicks($colorGridLines, 'y'); }
}
}
// Prevent nested frames from showing their grids
.t-frame-outer .l-grid-holder { display: none !important; }
// Prevent nested elements from showing s-hover-border
.t-frame-outer .s-hover-border {
border: none !important;
// Display grid when selected or selection parent.
.s-selected .l-grid-holder,
.s-selected-parent .l-grid-holder {
display: block;
}
// Prevent nested frames from being selectable until we have proper sub-object editing
.t-frame-outer .t-frame-outer {
pointer-events: none;
// Display in nested frames...
.t-frame-outer {
// ...when drilled in or selection parent...
&.s-drilled-in, &.s-selected-parent {
.l-grid-holder {
display: block;
}
.t-frame-outer:not(.s-drilled-in) .l-grid-holder {
display: none;
}
}
// ...but hide otherwise.
.l-grid-holder {
display: none;
}
}
}

View File

@@ -20,7 +20,19 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
.section-header {
border-radius: $basicCr;
background: $colorFormSectionHeader;
color: lighten($colorBodyFg, 20%);
font-size: inherit;
margin: $interiorMargin 0;
padding: $formTBPad $formLRPad;
text-transform: uppercase;
.view-control {
display: inline-block;
margin-right: $interiorMargin;
width: 1em;
height: 1em;
}
}
.form {
@@ -41,15 +53,6 @@
}
}
.section-header {
border-radius: $basicCr;
background: $colorFormSectionHeader;
$c: lighten($colorBodyFg, 20%);
color: $c;
font-size: 0.8em;
padding: $formTBPad $formLRPad;
}
.form-row {
$m: $interiorMargin;
box-sizing: border-box;
@@ -57,9 +60,6 @@
margin-bottom: $interiorMarginLg * 2;
padding: $formTBPad 0;
position: relative;
//&ng-form {
// display: block;
//}
&.first {
border-top: none;
@@ -171,3 +171,106 @@
padding: $interiorMargin;
}
}
/**************************************************************************** COMPACT FORM */
// ul > li > label, control
// Make a new UL for each form section
// Allow control-first, controls-below
// TO-DO: migrate work in branch ch-plot-styling that users .inspector-config to use classes below instead
.l-compact-form .tree ul li,
.l-compact-form ul li {
padding: 2px 0;
}
.l-compact-form {
$labelW: 40%;
$minW: $labelW;
ul {
margin-bottom: $interiorMarginLg;
li {
@include display(flex);
@include flex-wrap(wrap);
@include align-items(center);
label,
.control {
@include display(flex);
}
label {
line-height: inherit;
width: $labelW;
}
.controls {
@include flex-grow(1);
margin-left: $interiorMargin;
input[type="text"],
input[type="search"],
input[type="number"],
.select {
height: $btnStdH;
line-height: $btnStdH;
vertical-align: middle;
}
.e-control {
// Individual form controls
&:not(:first-child) {
margin-left: $interiorMarginSm;
}
}
}
&.connects-to-previous {
padding-top: 0;
}
&.section-header {
margin-top: $interiorMarginLg;
border-top: 1px solid $colorFormLines;
}
&.controls-first {
.control {
@include flex-grow(0);
margin-right: $interiorMargin;
min-width: 0;
order: 1;
width: auto;
}
label {
@include flex-grow(1);
order: 2;
width: auto;
}
}
&.controls-under {
display: block;
.control, label {
display: block;
width: auto;
}
ul li {
border-top: none !important;
padding: 0;
}
}
}
}
.form-error {
// Block element that visually flags an error and contains a message
background-color: $colorFormFieldErrorBg;
color: $colorFormFieldErrorFg;
border-radius: $basicCr;
display: block;
padding: 1px 6px;
&:before {
content: $glyph-icon-alert-triangle;
display: inline;
font-family: symbolsfont;
margin-right: $interiorMarginSm;
}
}
}

View File

@@ -34,18 +34,7 @@ body.touch {
line-height: $mobileTreeItemH !important;
.view-control {
font-size: 1em;
margin-right: $interiorMargin;
width: ceil($mobileTreeItemH * 0.75);
&.has-children {
&:before {
content: $glyph-icon-arrow-down;
left: 50%;
@include transform(translateX(-50%) rotate(-90deg));
}
&.expanded:before {
@include transform(translateX(-50%) rotate(0deg));
}
}
width: ceil($mobileTreeItemH * 0.5);
}
.t-object-label {
line-height: inherit;

View File

@@ -79,6 +79,7 @@
// Dialog boxes, size constrained and centered in desktop/tablet
&.l-dialog {
font-size: 0.8rem;
.s-button {
&:not(.major) {
@include btnSubtle($bg: $colorOvrBtnBg, $bgHov: pullForward($colorOvrBtnBg, 10%), $fg: $colorOvrBtnFg, $fgHov: $colorOvrBtnFg, $ic: $colorOvrBtnFg, $icHov: $colorOvrBtnFg);
@@ -125,9 +126,9 @@
@include containerSubtle($colorOvrBg, $colorOvrFg);
}
.title {
.dialog-title {
@include ellipsize();
font-size: 1.2em;
font-size: 1.5em;
line-height: 120%;
margin-bottom: $interiorMargin;
}

View File

@@ -0,0 +1,208 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
.gl-plot {
.gl-plot-legend {
min-height: $plotLegendH;
.view-control {
font-size: 1em;
margin-right: $interiorMarginSm;
}
table {
table-layout: fixed;
tr {
display: table-row;
}
th,
td {
@include ellipsize(); // Note: this won't work if table-layout uses anything other than fixed.
display: table-cell;
padding: 1px 3px; // Tighter than standard tabular padding
}
}
&.hover-on-plot {
// User is hovering over the plot to get a value at a point
.hover-value-enabled {
background-color: $legendHoverValueBg;
border-radius: $smallCr;
padding: 0 $interiorMarginSm;
&:before {
opacity: 0.5;
}
&.cursor-hover,
.value-to-display-nearestTimestamp,
.value-to-display-nearestValue
{
@extend .icon-crosshair-12px;
&:before {
font-size: 9px;
}
}
&.value-to-display-min:before {
content: 'MIN ';
}
&.value-to-display-max:before {
content: 'MAX ';
}
}
}
}
&.plot-legend-collapsed .plot-wrapper-expanded-legend { display: none; }
&.plot-legend-expanded .plot-wrapper-collapsed-legend { display: none; }
/***************** GENERAL STYLES, ALL STATES */
.plot-legend-item {
// General styles for legend items, both expanded and collapsed legend states
.plot-series-color-swatch {
border-radius: $smallCr;
border: 1px solid $colorBodyBg;
display: inline-block;
height: $plotSwatchD;
width: $plotSwatchD;
}
.plot-series-name {
display: inline;
}
.plot-series-value {
@include ellipsize();
}
}
/***************** GENERAL STYLES, COLLAPSED */
&.plot-legend-collapsed {
// .plot-legend-item is a span of spans.
&.plot-legend-top .gl-plot-legend { margin-bottom: $interiorMargin; }
&.plot-legend-bottom .gl-plot-legend { margin-top: $interiorMargin; }
&.plot-legend-right .gl-plot-legend { margin-left: $interiorMargin; }
&.plot-legend-left .gl-plot-legend { margin-right: $interiorMargin; }
.plot-legend-item {
display: flex;
align-items: center;
&:not(:first-child) {
margin-left: $interiorMarginLg;
}
.plot-series-swatch-and-name,
.plot-series-value {
@include ellipsize();
flex: 1 1 auto;
}
.plot-series-swatch-and-name {
margin-right: $interiorMarginSm;
}
.plot-series-value {
text-align: left;
width: 170px;
}
}
}
/***************** GENERAL STYLES, EXPANDED */
&.plot-legend-expanded {
.gl-plot-legend {
max-height: 70%;
}
.plot-wrapper-expanded-legend {
overflow-y: auto;
}
&.plot-legend-top .gl-plot-legend {
margin-bottom: $interiorMargin;
}
&.plot-legend-bottom .gl-plot-legend {
margin-top: $interiorMargin;
}
}
/***************** TOP OR BOTTOM */
&.plot-legend-top,
&.plot-legend-bottom {
// General styles when legend is on the top or bottom
@extend .l-flex-col;
&.plot-legend-collapsed {
// COLLAPSED ON TOP OR BOTTOM
.plot-wrapper-collapsed-legend {
display: flex;
flex: 1 1 auto;
overflow: hidden;
}
}
}
/***************** EITHER SIDE */
&.plot-legend-left,
&.plot-legend-right {
@extend .l-flex-row;
// If the legend is expanded, use flex-col instead so that the legend gets the width it needs.
&.plot-legend-expanded {
// EXPANDED, ON EITHER SIDE
@extend .l-flex-col;
}
&.plot-legend-collapsed {
// COLLAPSED, ON EITHER SIDE
.gl-plot-legend {
max-height: inherit;
width: 25%;
}
.plot-wrapper-collapsed-legend {
display: flex;
flex-flow: column nowrap;
min-width: 0;
flex: 1 1 auto;
overflow-y: auto;
}
.plot-legend-item {
margin-bottom: 1px;
margin-left: 0;
flex-wrap: wrap;
.plot-series-swatch-and-name {
flex: 0 1 auto;
min-width: 20%;
}
.plot-series-value {
flex: 0 1 auto;
width: auto;
}
}
}
}
/***************** ON BOTTOM OR RIGHT */
&.plot-legend-right:not(.plot-legend-expanded),
&.plot-legend-bottom {
.gl-plot-legend {
order: 2;
}
.plot-wrapper-axis-and-display-area {
order: 1;
}
}
}

View File

@@ -20,10 +20,42 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
.abs.holder-plot {
// Fend off the scrollbar when less than min-height;
right: $interiorMargin;
right: $interiorMargin; // Fend off the scrollbar when less than min-height;
.t-object-alert.t-alert-unsynced {
display: none;
}
}
/********************************************* STACKED PLOT LAYOUT */
.t-plot-stacked {
.l-view-section {
// Make this a flex container
display: flex;
flex-flow: column nowrap;
.gl-plot.child-frame {
mct-plot {
display: flex;
flex: 1 1 auto;
height: 100%;
position: relative;
}
flex: 1 1 auto;
&:not(:first-child) {
margin-top: $interiorMargin;
}
}
}
.s-status-timeconductor-unsynced .holder-plot {
.t-object-alert.t-alert-unsynced {
display: block;
}
}
}
.gl-plot {
color: $colorPlotFg;
font-size: 0.7rem;
@@ -32,6 +64,19 @@
height: 100%;
min-height: $plotMinH;
/********************************************* AXIS AND DISPLAY AREA */
.plot-wrapper-axis-and-display-area {
margin-top: $interiorMargin; // Keep the top tick label from getting clipped
position: relative;
flex: 1 1 auto;
.t-object-alert {
position: absolute;
display: block;
font-size: 1.5em;
top: $interiorMarginSm; left: $interiorMarginSm;
}
}
.gl-plot-wrapper-display-area-and-x-axis {
// Holds the plot area and the X-axis only
position: absolute;
@@ -49,7 +94,6 @@
}
.gl-plot-axis-area.gl-plot-x {
//@include test(green);
top: auto;
right: 0;
bottom: 0;
@@ -63,7 +107,7 @@
.gl-plot-axis-area {
position: absolute;
&.gl-plot-y {
top: $plotLegendH + $interiorMargin;
top: nth($plotDisplayArea, 1);
right: auto;
bottom: nth($plotDisplayArea, 3);
left: 0;
@@ -158,17 +202,6 @@
}
}
.gl-plot-legend {
position: absolute;
top: 0;
right: 0;
bottom: auto;
left: 0;
height: $plotLegendH;
overflow-x: hidden;
overflow-y: auto;
}
/****************************** Limits and Out-of-Bounds data */
.l-limit-bar,
@@ -235,39 +268,6 @@
border: 1px solid $colorPlotAreaBorder;
}
.gl-plot-legend,
.legend {
.plot-legend-item,
.legend-item {
display: inline-block;
margin-right: $interiorMarginLg;
margin-bottom: $interiorMarginSm;
span {
vertical-align: middle;
}
.plot-color-swatch,
.color-swatch {
border-radius: 2px;
display: inline-block;
height: $plotSwatchD;
width: $plotSwatchD;
}
}
}
.gl-plot-legend {
.plot-legend-item {
border-radius: $smallCr;
line-height: 1.5em;
padding: 0px $itemPadLR;
.plot-color-swatch {
border: 1px solid $colorBodyBg;
height: $plotSwatchD + 1;
width: $plotSwatchD + 1;
}
}
}
.tick {
position: absolute;
border: 0 $colorPlotHash solid;

View File

@@ -23,7 +23,7 @@
ul.tree {
@include menuUlReset();
@include user-select(none);
li {
> li {
display: block;
position: relative;
}
@@ -52,21 +52,11 @@ ul.tree {
.view-control {
color: $colorItemTreeVC;
font-size: 0.75em;
margin-right: $interiorMargin;
height: 100%;
line-height: inherit;
width: $treeVCW;
&.has-children {
&:before {
position: absolute;
@include trans-prop-nice(transform, 100ms);
content: "\e904";
@include transform-origin(center);
}
&.expanded:before {
@include transform(rotate(90deg));
}
&:before { display: block; }
&.no-children {
&:before { display: none; }
}
}

View File

@@ -23,15 +23,14 @@
$ohH: $btnFrameH;
$bc: $colorInteriorBorder;
&.child-frame.panel {
border: 1px solid transparent;
z-index: 0; // Needed to prevent child-frame controls from showing through when another child-frame is above
&:not(.no-frame) {
background: $colorBodyBg;
border: 1px solid $bc;
&:hover {
border-color: lighten($bc, 10%);
}
border-color: $bc;
}
}
.object-browse-bar {
font-size: 0.75em;
height: $ohH;
@@ -44,7 +43,8 @@
&.t-object-type-timer,
&.t-object-type-clock,
&.t-object-type-hyperlink {
&.t-object-type-hyperlink,
&.t-object-type-summary-widget {
// Hide the right side buttons for objects where they don't make sense
// Note that this will hide the view Switcher button if applied
// to an object that has it.
@@ -91,9 +91,9 @@
&.no-frame {
background: transparent !important;
border: none !important;
border: none;
.object-browse-bar .right {
$m: 0; // $interiorMarginSm;
$m: 0;
background: rgba(black, 0.3);
border-radius: $basicCr;
padding: $interiorMarginSm;
@@ -103,7 +103,7 @@
}
&.t-frame-outer > .t-rep-frame {
&.contents {
$m: 2px;
$m: 0px;
top: $m;
right: $m;
bottom: $m;
@@ -114,6 +114,7 @@
display: none;
}
> .object-holder.abs {
overflow: hidden;
top: 0 !important;
}
}
@@ -125,14 +126,21 @@
pointer-events: none !important;
}
/********************************************************** OBJECT TYPES */
.t-object-type-hyperlink {
/********************************************************** OBJECT TYPES */
.t-object-type-hyperlink,
.t-object-type-summary-widget {
.object-holder {
overflow: hidden;
}
.w-summary-widget,
.l-summary-widget,
.l-hyperlink.s-button {
// When a hyperlink is a button in a frame, make it expand to fill out to the object-holder
// Some object types expand to the full size of the object-holder.
@extend .abs;
}
.l-summary-widget,
.l-hyperlink.s-button {
.label {
@include ellipsize();
@include transform(translateY(-50%));

View File

@@ -20,35 +20,51 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
.s-hover-border {
border: 1px dotted transparent;
&:hover {
border-color: rgba($colorSelectableSelectedPrimary, 0.5) !important;
}
}
.s-status-editing {
// Limit to editing mode until we have sub-object selection
// Limit to editing mode
$o: 0.5;
$oHover: 0.8;
$bc: $colorSelectableSelectedPrimary;
.s-hover-border {
// Show a border by default so user can see object bounds and empty objects
border: 1px dotted rgba($colorSelectableSelectedPrimary, 0.3) !important;
border-color: rgba($bc, $o) !important;
border-style: dotted !important;
&:hover {
border-color: rgba($colorSelectableSelectedPrimary, 0.7) !important;
border-color: rgba($bc, $oHover) !important;
}
&.t-object-type-layout {
border-style: dashed !important;
}
}
.s-selected > .s-hover-border,
.s-selected.s-hover-border {
// Styles for a selected object. Also used by legacy Fixed Position/Panel objects.
border-color: $colorSelectableSelectedPrimary !important;
@include boxShdwLarge();
// Show edit-corners if you got 'em
.edit-corner {
display: block;
&:hover {
background-color: rgba($colorKey, 1);
.s-selected {
&.s-moveable {
&:not(.s-drilled-in) {
cursor: move;
}
}
}
}
.s-selected > .s-moveable,
.s-selected.s-moveable {
cursor: move;
.s-selected > .s-hover-border,
.s-selected.s-hover-border {
// Styles for a selected object. Also used by legacy Fixed Position/Panel objects.
border-color: $colorSelectableSelectedPrimary !important;
@include boxShdwLarge();
// Show edit-corners if you got 'em
.edit-corner {
display: block;
&:hover {
background-color: rgba($colorKey, 1);
}
}
}
}

View File

@@ -40,7 +40,7 @@ define(
// Gets an array of the contextual parents/ancestors of the selected object
function getContextualPath() {
var currentObj = $scope.ngModel.selectedObject,
var currentObj = $scope.domainObject,
currentParent,
parents = [];
@@ -68,7 +68,7 @@ define(
// If this the the initial call of this recursive function
if (!current) {
current = $scope.ngModel.selectedObject;
current = $scope.domainObject;
$scope.primaryParents = [];
}
@@ -87,16 +87,16 @@ define(
// Gets the metadata for the selected object
function getMetadata() {
$scope.metadata = $scope.ngModel.selectedObject &&
$scope.ngModel.selectedObject.hasCapability('metadata') &&
$scope.ngModel.selectedObject.useCapability('metadata');
$scope.metadata = $scope.domainObject &&
$scope.domainObject.hasCapability('metadata') &&
$scope.domainObject.useCapability('metadata');
}
// Set scope variables when the selected object changes
$scope.$watch('ngModel.selectedObject', function () {
$scope.isLink = $scope.ngModel.selectedObject &&
$scope.ngModel.selectedObject.hasCapability('location') &&
$scope.ngModel.selectedObject.getCapability('location').isLink();
$scope.$watch('domainObject', function () {
$scope.isLink = $scope.domainObject &&
$scope.domainObject.hasCapability('location') &&
$scope.domainObject.getCapability('location').isLink();
if ($scope.isLink) {
getPrimaryPath();
@@ -109,7 +109,7 @@ define(
getMetadata();
});
var mutation = $scope.ngModel.selectedObject.getCapability('mutation');
var mutation = $scope.domainObject.getCapability('mutation');
var unlisten = mutation.listen(getMetadata);
$scope.$on('$destroy', unlisten);
}

View File

@@ -21,39 +21,40 @@
*****************************************************************************/
define(
["./SubPlot"],
function (SubPlot) {
[],
function () {
/**
* Utility factory; wraps the SubPlot constructor and adds
* in a reference to the telemetryFormatter, which will be
* used to represent telemetry values (timestamps or data
* values) as human-readable strings.
* @memberof platform/features/plot
* The mct-selectable directive allows selection functionality
* (click) to be attached to specific elements.
*
* @memberof platform/commonUI/general
* @constructor
*/
function SubPlotFactory(telemetryFormatter) {
this.telemetryFormatter = telemetryFormatter;
function MCTSelectable(openmct) {
// Link; install event handlers.
function link(scope, element, attrs) {
var removeSelectable = openmct.selection.selectable(
element[0],
scope.$eval(attrs.mctSelectable),
attrs.hasOwnProperty('mctInitSelect') && scope.$eval(attrs.mctInitSelect) !== false
);
scope.$on("$destroy", function () {
removeSelectable();
});
}
return {
// mct-selectable only makes sense as an attribute
restrict: "A",
// Link function, to install event handlers
link: link
};
}
/**
* Instantiate a new sub-plot.
* @param {DomainObject[]} telemetryObjects the domain objects
* which will be plotted in this sub-plot
* @param {PlotPanZoomStack} panZoomStack the stack of pan-zoom
* states which is applicable to this sub-plot
* @returns {SubPlot} the instantiated sub-plot
* @method
*/
SubPlotFactory.prototype.createSubPlot = function (telemetryObjects, panZoomStack) {
return new SubPlot(
telemetryObjects,
panZoomStack,
this.telemetryFormatter
);
};
return SubPlotFactory;
return MCTSelectable;
}
);

View File

@@ -83,9 +83,9 @@ define([
this.activeObject = domainObject;
if (domainObject && domainObject.hasCapability('composition')) {
$(this.toggleView.elements()).addClass('has-children');
$(this.toggleView.elements()).removeClass('no-children');
} else {
$(this.toggleView.elements()).removeClass('has-children');
$(this.toggleView.elements()).addClass('no-children');
}
if (domainObject && domainObject.hasCapability('status')) {

View File

@@ -41,16 +41,6 @@ define(
"$scope",
["$watch", "$on"]
);
mockScope.ngModel = {};
mockScope.ngModel.selectedObject = {
getCapability: function () {
return {
listen: function () {
return true;
}
};
}
};
mockObjectService = jasmine.createSpyObj(
"objectService",
@@ -77,22 +67,27 @@ define(
"location capability",
["isLink"]
);
mockDomainObject.getCapability.andCallFake(function (param) {
if (param === 'location') {
return mockLocationCapability;
} else if (param === 'context') {
return mockContextCapability;
} else if (param === 'mutation') {
return {
listen: function () {
return true;
}
};
}
});
mockScope.domainObject = mockDomainObject;
controller = new ObjectInspectorController(mockScope, mockObjectService);
// Change the selected object to trigger the watch call
mockScope.ngModel.selectedObject = mockDomainObject;
});
it("watches for changes to the selected object", function () {
expect(mockScope.$watch).toHaveBeenCalledWith('ngModel.selectedObject', jasmine.any(Function));
expect(mockScope.$watch).toHaveBeenCalledWith('domainObject', jasmine.any(Function));
});
it("looks for contextual parent objects", function () {

View File

@@ -38,7 +38,8 @@ define([
"implementation": InspectorController,
"depends": [
"$scope",
"policyService"
"openmct",
"$document"
]
}
],

View File

@@ -21,44 +21,69 @@
*****************************************************************************/
define(
['../../browse/src/InspectorRegion'],
function (InspectorRegion) {
[],
function () {
/**
* The InspectorController adds region data for a domain object's type
* to the scope.
* The InspectorController listens for the selection changes and adds the selection
* object to the scope.
*
* @constructor
*/
function InspectorController($scope, policyService) {
var domainObject = $scope.domainObject,
typeCapability = domainObject.getCapability('type'),
statusListener;
function InspectorController($scope, openmct, $document) {
var self = this;
self.$scope = $scope;
/**
* Filters region parts to only those allowed by region policies
* @param regions
* @returns {{}}
* Callback handler for the selection change event.
* Adds the selection object to the scope. If the selected item has an inspector view,
* it puts the key in the scope. If provider view exists, it shows the view.
*/
function filterRegions(inspector) {
//Dupe so we're not modifying the type definition.
return inspector.regions && inspector.regions.filter(function (region) {
return policyService.allow('region', region, domainObject);
});
function setSelection(selection) {
if (selection[0]) {
var view = openmct.inspectorViews.get(selection);
var container = $document[0].querySelectorAll('.inspector-provider-view')[0];
container.innerHTML = "";
if (view) {
self.providerView = true;
view.show(container);
} else {
self.providerView = false;
$scope.inspectorKey = selection[0].context.oldItem.getCapability("type").typeDef.inspector;
}
}
self.$scope.selection = selection;
}
function setRegions() {
$scope.regions = filterRegions(typeCapability.getDefinition().inspector || new InspectorRegion());
}
openmct.selection.on("change", setSelection);
setSelection(openmct.selection.get());
statusListener = domainObject.getCapability("status").listen(setRegions);
$scope.$on("$destroy", function () {
statusListener();
openmct.selection.off("change", setSelection);
});
setRegions();
}
/**
* Gets the selected item.
*
* @returns a domain object
*/
InspectorController.prototype.selectedItem = function () {
return this.$scope.selection[0].context.oldItem;
};
/**
* Checks if a provider view exists.
*
* @returns 'true' if provider view exists, 'false' otherwise
*/
InspectorController.prototype.hasProviderView = function () {
return this.providerView;
};
return InspectorController;
}
);

View File

@@ -27,82 +27,93 @@ define(
describe("The inspector controller ", function () {
var mockScope,
mockDomainObject,
mockTypeCapability,
mockTypeDefinition,
mockPolicyService,
mockStatusCapability,
capabilities = {},
controller;
mockOpenMCT,
mockSelection,
mockInspectorViews,
mockTypeDef,
controller,
container,
$document = [],
selectable = [];
beforeEach(function () {
mockTypeDefinition = {
inspector:
{
'regions': [
{'name': 'Part One'},
{'name': 'Part Two'}
]
}
mockTypeDef = {
typeDef: {
inspector: "some-key"
}
};
mockTypeCapability = jasmine.createSpyObj('typeCapability', [
'getDefinition'
]);
mockTypeCapability.getDefinition.andReturn(mockTypeDefinition);
capabilities.type = mockTypeCapability;
mockStatusCapability = jasmine.createSpyObj('statusCapability', [
'listen'
]);
capabilities.status = mockStatusCapability;
mockDomainObject = jasmine.createSpyObj('domainObject', [
'getCapability'
]);
mockDomainObject.getCapability.andCallFake(function (name) {
return capabilities[name];
});
mockPolicyService = jasmine.createSpyObj('policyService', [
'allow'
]);
mockDomainObject.getCapability.andReturn(mockTypeDef);
mockScope = jasmine.createSpyObj('$scope',
['$on']
['$on', 'selection']
);
mockScope.domainObject = mockDomainObject;
selectable[0] = {
context: {
oldItem: mockDomainObject
}
};
mockSelection = jasmine.createSpyObj("selection", [
'on',
'off',
'get'
]);
mockSelection.get.andReturn(selectable);
mockInspectorViews = jasmine.createSpyObj('inspectorViews', ['get']);
mockOpenMCT = {
selection: mockSelection,
inspectorViews: mockInspectorViews
};
container = jasmine.createSpy('container', ['innerHTML']);
$document[0] = jasmine.createSpyObj("$document", ['querySelectorAll']);
$document[0].querySelectorAll.andReturn([container]);
controller = new InspectorController(mockScope, mockOpenMCT, $document);
});
it("filters out regions disallowed by region policy", function () {
mockPolicyService.allow.andReturn(false);
controller = new InspectorController(mockScope, mockPolicyService);
expect(mockScope.regions.length).toBe(0);
it("listens for selection change event", function () {
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
expect(controller.selectedItem()).toEqual(mockDomainObject);
var mockItem = jasmine.createSpyObj('domainObject', [
'getCapability'
]);
mockItem.getCapability.andReturn(mockTypeDef);
selectable[0].context.oldItem = mockItem;
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(controller.selectedItem()).toEqual(mockItem);
});
it("does not filter out regions allowed by region policy", function () {
mockPolicyService.allow.andReturn(true);
controller = new InspectorController(mockScope, mockPolicyService);
expect(mockScope.regions.length).toBe(2);
it("cleans up on scope destroy", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
'$destroy',
jasmine.any(Function)
);
mockScope.$on.calls[0].args[1]();
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
it("Responds to status changes", function () {
mockPolicyService.allow.andReturn(true);
controller = new InspectorController(mockScope, mockPolicyService);
expect(mockScope.regions.length).toBe(2);
expect(mockStatusCapability.listen).toHaveBeenCalled();
mockPolicyService.allow.andReturn(false);
mockStatusCapability.listen.mostRecentCall.args[0]();
expect(mockScope.regions.length).toBe(0);
});
it("Unregisters status listener", function () {
var mockListener = jasmine.createSpy('listener');
mockStatusCapability.listen.andReturn(mockListener);
controller = new InspectorController(mockScope, mockPolicyService);
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function));
mockScope.$on.mostRecentCall.args[1]();
expect(mockListener).toHaveBeenCalled();
it("adds selection object to scope", function () {
expect(mockScope.selection).toEqual(selectable);
expect(controller.selectedItem()).toEqual(mockDomainObject);
});
});
}

View File

@@ -181,6 +181,8 @@ $colorPlotHash: $colorTick;
$stylePlotHash: dashed;
$colorPlotAreaBorder: $colorInteriorBorder;
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
$legendCollapsedNameMaxW: 50%;
$legendHoverValueBg: rgba($colorBodyFg, 0.1);
// Tree
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);
@@ -243,6 +245,12 @@ $colorCalCellSelectedBg: $colorItemTreeSelectedBg;
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
$colorCalCellInMonthBg: pushBack($colorMenuBg, 5%);
// Palettes
$colorPaletteFg: pullForward($colorMenuBg, 30%);
$colorPaletteSelected: #fff;
$shdwPaletteFg: black 0 0 2px;
$shdwPaletteSelected: inset 0 0 0 1px #000;
// About Screen
$colorAboutLink: #84b3ff;

View File

@@ -181,6 +181,8 @@ $colorPlotHash: $colorTick;
$stylePlotHash: dashed;
$colorPlotAreaBorder: $colorInteriorBorder;
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
$legendCollapsedNameMaxW: 50%;
$legendHoverValueBg: rgba($colorBodyFg, 0.2);
// Tree
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);
@@ -243,6 +245,12 @@ $colorCalCellSelectedBg: $colorItemTreeSelectedBg;
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
$colorCalCellInMonthBg: pullForward($colorMenuBg, 5%);
// Palettes
$colorPaletteFg: pullForward($colorMenuBg, 30%);
$colorPaletteSelected: #333;
$shdwPaletteFg: none;
$shdwPaletteSelected: inset 0 0 0 1px #fff;
// About Screen
$colorAboutLink: #84b3ff;

View File

@@ -23,10 +23,13 @@
define([
"moment-timezone",
"./src/indicators/ClockIndicator",
"./src/indicators/FollowIndicator",
"./src/services/TickerService",
"./src/services/TimerService",
"./src/controllers/ClockController",
"./src/controllers/TimerController",
"./src/controllers/RefreshingController",
"./src/actions/FollowTimerAction",
"./src/actions/StartTimerAction",
"./src/actions/RestartTimerAction",
"./src/actions/StopTimerAction",
@@ -37,10 +40,13 @@ define([
], function (
MomentTimezone,
ClockIndicator,
FollowIndicator,
TickerService,
TimerService,
ClockController,
TimerController,
RefreshingController,
FollowTimerAction,
StartTimerAction,
RestartTimerAction,
StopTimerAction,
@@ -80,6 +86,11 @@ define([
"CLOCK_INDICATOR_FORMAT"
],
"priority": "preferred"
},
{
"implementation": FollowIndicator,
"depends": ["timerService"],
"priority": "fallback"
}
],
"services": [
@@ -90,6 +101,11 @@ define([
"$timeout",
"now"
]
},
{
"key": "timerService",
"implementation": TimerService,
"depends": ["openmct"]
}
],
"controllers": [
@@ -134,6 +150,15 @@ define([
}
],
"actions": [
{
"key": "timer.follow",
"implementation": FollowTimerAction,
"depends": ["timerService"],
"category": "contextual",
"name": "Follow Timer",
"cssClass": "icon-clock",
"priority": "optional"
},
{
"key": "timer.start",
"implementation": StartTimerAction,

View File

@@ -0,0 +1,56 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, 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.
*****************************************************************************/
define(
[],
function () {
/**
* Designates a specific timer for following. Timelines, for example,
* use the actively followed timer to display a time-of-interest line
* and interpret time conductor bounds in the Timeline's relative
* time frame.
*
* @implements {Action}
* @memberof platform/features/clock
* @constructor
* @param {ActionContext} context the context for this action
*/
function FollowTimerAction(timerService, context) {
var domainObject =
context.domainObject &&
context.domainObject.useCapability('adapter');
this.perform =
timerService.setTimer.bind(timerService, domainObject);
}
FollowTimerAction.appliesTo = function (context) {
var model =
(context.domainObject && context.domainObject.getModel()) ||
{};
return model.type === 'timer';
};
return FollowTimerAction;
}
);

View File

@@ -0,0 +1,57 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, 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.
*****************************************************************************/
define(
['moment'],
function (moment) {
var NO_TIMER = "No timer being followed";
/**
* Indicator that displays the active timer, as well as its
* current state.
* @implements {Indicator}
* @memberof platform/features/clock
*/
function FollowIndicator(timerService) {
this.timerService = timerService;
}
FollowIndicator.prototype.getGlyphClass = function () {
return "";
};
FollowIndicator.prototype.getCssClass = function () {
return (this.timerService.getTimer()) ? "icon-timer s-status-ok" : "icon-timer";
};
FollowIndicator.prototype.getText = function () {
var timer = this.timerService.getTimer();
return (timer) ? 'Following timer ' + timer.getModel().name : NO_TIMER;
};
FollowIndicator.prototype.getDescription = function () {
return "";
};
return FollowIndicator;
}
);

View File

@@ -0,0 +1,113 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, 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.
*****************************************************************************/
define(['EventEmitter'], function (EventEmitter) {
/**
* Tracks the currently-followed Timer object. Used by
* timelines et al to synchronize to a particular timer.
*
* The TimerService emits `change` events when the active timer
* is changed.
*/
function TimerService(openmct) {
EventEmitter.apply(this);
this.time = openmct.time;
this.objects = openmct.objects;
}
TimerService.prototype = Object.create(EventEmitter.prototype);
/**
* Set (or clear, if `timer` is undefined) the currently active timer.
* @param {DomainObject} timer the new active timer
* @emits change
*/
TimerService.prototype.setTimer = function (timer) {
this.timer = timer;
this.emit('change');
if (this.stopObserving) {
this.stopObserving();
delete this.stopObserving;
}
if (timer) {
this.stopObserving =
this.objects.observe(timer, '*', this.setTimer.bind(this));
}
};
/**
* Get the currently active timer.
* @return {DomainObject} the active timer
* @emits change
*/
TimerService.prototype.getTimer = function () {
return this.timer;
};
/**
* Check if there is a currently active timer.
* @return {boolean} true if there is a timer
*/
TimerService.prototype.hasTimer = function () {
return !!this.timer;
};
/**
* Convert the provided timestamp to milliseconds relative to
* the active timer.
* @return {number} milliseconds since timer start
*/
TimerService.prototype.convert = function (timestamp) {
var clock = this.time.clock();
var canConvert = this.hasTimer() &&
!!clock &&
this.timer.timerState !== 'stopped';
if (!canConvert) {
return undefined;
}
var now = clock.currentValue();
var delta = this.timer.timerState === 'paused' ?
now - this.timer.pausedTime : 0;
var epoch = this.timer.timestamp;
return timestamp - epoch - delta;
};
/**
* Get the value of the active clock, adjusted to be relative to the active
* timer. If there is no clock or no active timer, this will return
* `undefined`.
* @return {number} milliseconds since the start of the active timer
*/
TimerService.prototype.now = function () {
var clock = this.time.clock();
return clock && this.convert(clock.currentValue());
};
return TimerService;
});

View File

@@ -0,0 +1,87 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, 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.
*****************************************************************************/
define([
"../../src/actions/FollowTimerAction"
], function (FollowTimerAction) {
var TIMER_SERVICE_METHODS =
['setTimer', 'getTimer', 'clearTimer', 'on', 'off'];
describe("The Follow Timer action", function () {
var testContext;
var testModel;
var testAdaptedObject;
beforeEach(function () {
testModel = {};
testContext = { domainObject: jasmine.createSpyObj('domainObject', [
'getModel',
'useCapability'
]) };
testAdaptedObject = { foo: 'bar' };
testContext.domainObject.getModel.andReturn(testModel);
testContext.domainObject.useCapability.andCallFake(function (c) {
return c === 'adapter' && testAdaptedObject;
});
});
it("is applicable to timers", function () {
testModel.type = "timer";
expect(FollowTimerAction.appliesTo(testContext)).toBe(true);
});
it("is inapplicable to non-timers", function () {
testModel.type = "folder";
expect(FollowTimerAction.appliesTo(testContext)).toBe(false);
});
describe("when instantiated", function () {
var mockTimerService;
var action;
beforeEach(function () {
mockTimerService = jasmine.createSpyObj(
'timerService',
TIMER_SERVICE_METHODS
);
action = new FollowTimerAction(mockTimerService, testContext);
});
it("does not interact with the timer service", function () {
TIMER_SERVICE_METHODS.forEach(function (method) {
expect(mockTimerService[method]).not.toHaveBeenCalled();
});
});
describe("and performed", function () {
beforeEach(function () {
action.perform();
});
it("sets the active timer", function () {
expect(mockTimerService.setTimer)
.toHaveBeenCalledWith(testAdaptedObject);
});
});
});
});
});

View File

@@ -0,0 +1,61 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, 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.
*****************************************************************************/
define(["../../src/indicators/FollowIndicator"], function (FollowIndicator) {
var TIMER_SERVICE_METHODS =
['setTimer', 'getTimer', 'clearTimer', 'on', 'off'];
describe("The timer-following indicator", function () {
var mockTimerService;
var indicator;
beforeEach(function () {
mockTimerService =
jasmine.createSpyObj('timerService', TIMER_SERVICE_METHODS);
indicator = new FollowIndicator(mockTimerService);
});
it("implements the Indicator interface", function () {
expect(indicator.getGlyphClass()).toEqual(jasmine.any(String));
expect(indicator.getCssClass()).toEqual(jasmine.any(String));
expect(indicator.getText()).toEqual(jasmine.any(String));
expect(indicator.getDescription()).toEqual(jasmine.any(String));
});
describe("when a timer is set", function () {
var testModel;
var mockDomainObject;
beforeEach(function () {
testModel = { name: "some timer!" };
mockDomainObject = jasmine.createSpyObj('timer', ['getModel']);
mockDomainObject.getModel.andReturn(testModel);
mockTimerService.getTimer.andReturn(mockDomainObject);
});
it("displays the timer's name", function () {
expect(indicator.getText().indexOf(testModel.name))
.not.toEqual(-1);
});
});
});
});

View File

@@ -0,0 +1,77 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, 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.
*****************************************************************************/
define([
'../../src/services/TimerService'
], function (TimerService) {
describe("TimerService", function () {
var callback;
var mockmct;
var timerService;
beforeEach(function () {
callback = jasmine.createSpy('callback');
mockmct = {
time: { clock: jasmine.createSpy('clock') },
objects: { observe: jasmine.createSpy('observe') }
};
timerService = new TimerService(mockmct);
timerService.on('change', callback);
});
it("initially emits no change events", function () {
expect(callback).not.toHaveBeenCalled();
});
it("reports no current timer", function () {
expect(timerService.getTimer()).toBeUndefined();
});
describe("setTimer", function () {
var testTimer;
beforeEach(function () {
testTimer = { name: "I am some timer; you are nobody." };
timerService.setTimer(testTimer);
});
it("emits a change event", function () {
expect(callback).toHaveBeenCalled();
});
it("reports the current timer", function () {
expect(timerService.getTimer()).toBe(testTimer);
});
it("observes changes to an object", function () {
var newTimer = { name: "I am another timer." };
expect(mockmct.objects.observe).toHaveBeenCalledWith(
testTimer,
'*',
jasmine.any(Function)
);
mockmct.objects.observe.mostRecentCall.args[2](newTimer);
expect(timerService.getTimer()).toBe(newTimer);
});
});
});
});

View File

@@ -255,6 +255,8 @@ define(
if (this.nextDatum) {
this.updateValues(this.nextDatum);
delete this.nextDatum;
} else {
this.updateValues(this.$scope.imageHistory[this.$scope.imageHistory.length - 1]);
}
this.autoScroll = true;
}

View File

@@ -183,6 +183,17 @@ define(
expect(controller.getImageUrl()).toEqual(newUrl);
});
it("forwards large image view to latest image in history on un-pause", function () {
$scope.imageHistory = [
{ utc: 1434600258122, url: 'some/url1', selected: false},
{ utc: 1434600258123, url: 'some/url2', selected: false}
];
controller.paused(true);
controller.paused(false);
expect(controller.getImageUrl()).toEqual(controller.getImageUrl($scope.imageHistory[1]));
});
it("subscribes to telemetry", function () {
expect(openmct.telemetry.subscribe).toHaveBeenCalledWith(
newDomainObject,
@@ -227,7 +238,7 @@ define(
expect(controller.updateHistory(mockDatum)).toBe(false);
});
describe("user clicks on imagery thumbnail", function () {
describe("when user clicks on imagery thumbnail", function () {
var mockDatum = { utc: 1434600258123, url: 'some/url', selected: false};
it("pauses and adds selected class to imagery thumbnail", function () {
@@ -248,6 +259,7 @@ define(
expect(controller.getTime()).toEqual(controller.timeFormat.format(mockDatum.utc));
});
});
});
it("initially shows an empty string for date/time", function () {

View File

@@ -260,7 +260,9 @@ define([
"key": "LayoutController",
"implementation": LayoutController,
"depends": [
"$scope"
"$scope",
"$element",
"openmct"
]
},
{
@@ -334,46 +336,6 @@ define([
"conversion": "number[]"
}
]
},
{
"key": "telemetry.panel",
"name": "Telemetry Panel",
"cssClass": "icon-telemetry-panel",
"description": "A panel for collecting telemetry elements.",
"priority": 899,
"delegates": [
"telemetry"
],
"features": "creation",
"contains": [
{
"has": "telemetry"
}
],
"model": {
"composition": []
},
"properties": [
{
"name": "Layout Grid",
"control": "composite",
"items": [
{
"name": "Horizontal grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
},
{
"name": "Vertical grid (px)",
"control": "textfield",
"cssClass": "l-input-sm l-numeric"
}
],
"pattern": "^(\\d*[1-9]\\d*)?$",
"property": "layoutGrid",
"conversion": "number[]"
}
]
}
]
}

View File

@@ -40,7 +40,7 @@
's-selected': controller.selected(element)
}"
ng-style="element.style"
ng-click="controller.select(element)">
ng-click="controller.select(element, $event)">
<mct-include key="element.template"
parameters="{ gridSize: controller.getGridSize() }"
ng-model="element">
@@ -53,14 +53,16 @@
mct-drag-down="controller.moveHandle().startDrag(controller.selected())"
mct-drag="controller.moveHandle().continueDrag(delta)"
mct-drag-up="controller.moveHandle().endDrag()"
ng-style="controller.selected().style">
ng-style="controller.selected().style"
ng-click="$event.stopPropagation()">
</div>
<div ng-repeat="handle in controller.handles()"
class="l-fixed-position-item-handle edit-corner"
ng-style="handle.style()"
mct-drag-down="handle.startDrag()"
mct-drag="handle.continueDrag(delta)"
mct-drag-up="handle.endDrag()">
mct-drag-up="handle.endDrag()"
ng-click="$event.stopPropagation()">
</div>
</span>

View File

@@ -19,7 +19,7 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="frame frame-template t-frame-inner abs t-object-type-{{ representation.selected.key }}">
<div class="frame frame-template t-frame-inner abs t-object-type-{{ domainObject.getModel().type }}">
<div class="abs object-browse-bar l-flex-row">
<div class="left flex-elem l-flex-row grows">
<mct-representation

View File

@@ -22,10 +22,12 @@
<div class="abs l-layout"
ng-controller="LayoutController as controller"
ng-click="controller.clearSelection()">
ng-click="controller.bypassSelection($event)">
<!-- Background grid -->
<div class="l-grid-holder" ng-click="controller.clearSelection()">
<div class="l-grid-holder"
ng-show="!controller.drilledIn"
ng-click="controller.bypassSelection($event)">
<div class="l-grid l-grid-x"
ng-if="!controller.getGridSize()[0] < 3"
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
@@ -34,10 +36,12 @@
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
</div>
<div class='abs frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border'
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-selected':controller.selected(childObject) }"
<div class="abs frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border {{childObject.getId() + '-' + $id}} t-object-type-{{ childObject.getModel().type }}"
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }"
ng-repeat="childObject in composition"
ng-click="controller.select($event, childObject.getId())"
ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)"
mct-selectable="controller.getContext(childObject, true)"
ng-dblclick="controller.drill($event, childObject)"
ng-style="controller.getFrameStyle(childObject.getId())">
<mct-representation key="'frame'"
@@ -45,7 +49,7 @@
mct-object="childObject">
</mct-representation>
<!-- Drag handles -->
<span class="abs t-edit-handle-holder s-hover-border" ng-if="controller.selected(childObject)">
<span class="abs t-edit-handle-holder" ng-if="controller.selected(childObject) && !controller.isDrilledIn(childObject)">
<span class="edit-handle edit-move"
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
mct-drag="controller.continueDrag(delta)"
@@ -73,7 +77,6 @@
mct-drag-up="controller.endDrag()">
</span>
</span>
</div>
</div>

View File

@@ -360,22 +360,47 @@ define(
*/
FixedController.prototype.updateView = function (telemetryObject, datum) {
var metadata = this.openmct.telemetry.getMetadata(telemetryObject);
var rangeMetadata = metadata.valuesForHints(['range'])[0];
var rangeKey = rangeMetadata.source || rangeMetadata.key;
var valueMetadata = metadata.value(rangeKey);
var telemetryKeyToDisplay = this.chooseTelemetryKeyToDisplay(metadata);
var formattedTelemetryValue = this.getFormattedTelemetryValueForKey(telemetryKeyToDisplay, datum, metadata);
var limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
var formatter = this.openmct.telemetry.getValueFormatter(valueMetadata);
var value = datum[valueMetadata.key];
var alarm = limitEvaluator && limitEvaluator.evaluate(datum, rangeKey);
var alarm = limitEvaluator && limitEvaluator.evaluate(datum, telemetryKeyToDisplay);
this.setDisplayedValue(
telemetryObject,
formatter.format(value),
formattedTelemetryValue,
alarm && alarm.cssClass
);
this.digest();
};
/**
* @private
*/
FixedController.prototype.getFormattedTelemetryValueForKey = function (telemetryKeyToDisplay, datum, metadata) {
var valueMetadata = metadata.value(telemetryKeyToDisplay);
var formatter = this.openmct.telemetry.getValueFormatter(valueMetadata);
return formatter.format(datum[valueMetadata.key]);
};
/**
* @private
*/
FixedController.prototype.chooseTelemetryKeyToDisplay = function (metadata) {
// If there is a range value, show that preferentially
var telemetryKeyToDisplay = metadata.valuesForHints(['range'])[0];
// If no range is defined, default to the highest priority non time-domain data.
if (telemetryKeyToDisplay === undefined) {
var valuesOrderedByPriority = metadata.values();
telemetryKeyToDisplay = valuesOrderedByPriority.filter(function (valueMetadata) {
return !(valueMetadata.hints.domain);
})[0];
}
return telemetryKeyToDisplay.source;
};
/**
* Request the last historical data point for the given domain objects
* @param {object[]} objects
@@ -388,7 +413,9 @@ define(
objects.forEach(function (object) {
self.openmct.telemetry.request(object, {start: bounds.start, end: bounds.end, size: 1})
.then(function (data) {
self.updateView(object, data[data.length - 1]);
if (data.length > 0) {
self.updateView(object, data[data.length - 1]);
}
});
});
return objects;
@@ -479,7 +506,11 @@ define(
* Set the active user selection in this view.
* @param element the element to select
*/
FixedController.prototype.select = function select(element) {
FixedController.prototype.select = function select(element, event) {
if (event) {
event.stopPropagation();
}
if (this.selection) {
// Update selection...
this.selection.select(element);

View File

@@ -27,9 +27,11 @@
*/
define(
[
'zepto',
'./LayoutDrag'
],
function (
$,
LayoutDrag
) {
@@ -50,10 +52,12 @@ define(
* @constructor
* @param {Scope} $scope the controller's Angular scope
*/
function LayoutController($scope) {
function LayoutController($scope, $element, openmct) {
var self = this,
callbackCount = 0;
this.$element = $element;
// Update grid size when it changed
function updateGridSize(layoutGrid) {
var oldSize = self.gridSize;
@@ -123,14 +127,11 @@ define(
self.layoutPanels(ids);
self.setFrames(ids);
// If there is a newly-dropped object, select it.
if (self.droppedIdToSelectAfterRefresh) {
self.select(null, self.droppedIdToSelectAfterRefresh);
delete self.droppedIdToSelectAfterRefresh;
}
if (composition.indexOf(self.selectedId) === -1) {
self.clearSelection();
if (self.selectedId &&
self.selectedId !== $scope.domainObject.getId() &&
composition.indexOf(self.selectedId) === -1) {
// Click triggers selection of layout parent.
self.$element[0].click();
}
}
});
@@ -162,22 +163,39 @@ define(
}
};
// Sets the selectable object in response to the selection change event.
function setSelection(selectable) {
var selection = selectable[0];
if (!selection) {
delete self.selectedId;
return;
}
self.selectedId = selection.context.oldItem.getId();
self.drilledIn = undefined;
self.selectable = selectable;
}
this.positions = {};
this.rawPositions = {};
this.gridSize = DEFAULT_GRID_SIZE;
this.$scope = $scope;
this.drilledIn = undefined;
this.openmct = openmct;
// Watch for changes to the grid size in the model
$scope.$watch("model.layoutGrid", updateGridSize);
$scope.$watch("selection", function (selection) {
this.selection = selection;
}.bind(this));
// Update composed objects on screen, and position panes
$scope.$watchCollection("model.composition", refreshComposition);
// Position panes where they are dropped
openmct.selection.on('change', setSelection);
$scope.$on("$destroy", function () {
openmct.selection.off("change", setSelection);
});
$scope.$on("mctDrop", handleDrop);
}
@@ -359,37 +377,14 @@ define(
};
/**
* Check if the object is currently selected.
* Checks if the object is currently selected.
*
* @param {string} obj the object to check for selection
* @returns {boolean} true if selected, otherwise false
*/
LayoutController.prototype.selected = function (obj) {
return !!this.selectedId && this.selectedId === obj.getId();
};
/**
* Set the active user selection in this view.
*
* @param event the mouse event
* @param {string} id the object id
*/
LayoutController.prototype.select = function (event, id) {
if (event) {
event.stopPropagation();
if (this.selection) {
event.preventDefault();
}
}
this.selectedId = id;
var selectedObj = {};
selectedObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id);
if (this.selection) {
this.selection.select(selectedObj);
}
var sobj = this.openmct.selection.get()[0];
return (sobj && sobj.context.oldItem.getId() === obj.getId()) ? true : false;
};
/**
@@ -398,7 +393,7 @@ define(
* @param {string} id the object id
* @private
*/
LayoutController.prototype.toggleFrame = function (id) {
LayoutController.prototype.toggleFrame = function (id, domainObject) {
var configuration = this.$scope.configuration;
if (!configuration.panels[id]) {
@@ -406,21 +401,75 @@ define(
}
this.frames[id] = configuration.panels[id].hasFrame = !this.frames[id];
this.select(undefined, id); // reselect so toolbar updates
var selection = this.openmct.selection.get();
selection[0].context.toolbar = this.getToolbar(id, domainObject);
this.openmct.selection.select(selection); // reselect so toolbar updates
};
/**
* Clear the current user selection.
* Gets the toolbar object for the given domain object.
*
* @param id the domain object id
* @param domainObject the domain object
* @returns {object}
* @private
*/
LayoutController.prototype.clearSelection = function () {
LayoutController.prototype.getToolbar = function (id, domainObject) {
var toolbarObj = {};
toolbarObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id, domainObject);
return toolbarObj;
};
/**
* Bypasses selection if drag is in progress.
*
* @param event the angular event object
*/
LayoutController.prototype.bypassSelection = function (event) {
if (this.dragInProgress) {
if (event) {
event.stopPropagation();
}
return;
}
};
/**
* Checks if the domain object is drilled in.
*
* @param domainObject the domain object
* @return true if the object is drilled in, false otherwise
*/
LayoutController.prototype.isDrilledIn = function (domainObject) {
return this.drilledIn === domainObject.getId();
};
/**
* Puts the given object in the drilled-in mode.
*
* @param event the angular event object
* @param domainObject the domain object
*/
LayoutController.prototype.drill = function (event, domainObject) {
if (event) {
event.stopPropagation();
}
if (!domainObject.getCapability('editor').inEditContext()) {
return;
}
if (this.selection) {
this.selection.deselect();
delete this.selectedId;
if (!domainObject.hasCapability('composition')) {
return;
}
// Disable since fixed position doesn't use the selection API yet
if (domainObject.getModel().type === 'telemetry.fixed') {
return;
}
this.drilledIn = domainObject.getId();
};
/**
@@ -442,6 +491,37 @@ define(
return this.gridSize;
};
/**
* Gets the selection context.
*
* @param domainObject the domain object
* @returns {object} the context object which includes
* item, oldItem and toolbar
*/
LayoutController.prototype.getContext = function (domainObject, toolbar) {
return {
item: domainObject.useCapability('adapter'),
oldItem: domainObject,
toolbar: toolbar ? this.getToolbar(domainObject.getId(), domainObject) : undefined
};
};
/**
* Selects a newly-dropped object.
*
* @param classSelector the css class selector
* @param domainObject the domain object
*/
LayoutController.prototype.selectIfNew = function (classSelector, domainObject) {
if (domainObject.getId() === this.droppedIdToSelectAfterRefresh) {
var selector = $(classSelector).selector;
setTimeout(function () {
$('.' + selector)[0].click();
delete this.droppedIdToSelectAfterRefresh;
}.bind(this), 0);
}
};
return LayoutController;
}
);

View File

@@ -178,7 +178,6 @@ define(
Promise.resolve(mockChildren)
);
mockScope.model = testModel;
mockScope.configuration = testConfiguration;
mockScope.selection = jasmine.createSpyObj(
@@ -194,7 +193,8 @@ define(
mockMetadata = jasmine.createSpyObj('mockMetadata', [
'valuesForHints',
'value'
'value',
'values'
]);
mockMetadata.value.andReturn({
key: 'value'
@@ -653,6 +653,39 @@ define(
});
});
it("selects an range value to display, if available", function () {
mockMetadata.valuesForHints.andReturn([
{
key: 'range',
source: 'range'
}
]);
var key = controller.chooseTelemetryKeyToDisplay(mockMetadata);
expect(key).toEqual('range');
});
it("selects the first non-domain value to display, if no range available", function () {
mockMetadata.valuesForHints.andReturn([]);
mockMetadata.values.andReturn([
{
key: 'domain',
source: 'domain',
hints: {
domain: 1
}
},
{
key: 'image',
source: 'image',
hints: {
image: 1
}
}
]);
var key = controller.chooseTelemetryKeyToDisplay(mockMetadata);
expect(key).toEqual('image');
});
it("reflects limit status", function () {
mockLimitEvaluator.evaluate.andReturn({cssClass: "alarm-a"});
controller.updateView(mockTelemetryObject, [{

View File

@@ -32,7 +32,11 @@ define(
controller,
mockCompositionCapability,
mockComposition,
mockCompositionObjects;
mockCompositionObjects,
mockOpenMCT,
mockSelection,
$element = [],
selectable = [];
function mockPromise(value) {
return {
@@ -62,17 +66,6 @@ define(
};
}
// Utility function to find a watch for a given expression
function findWatch(expr) {
var watch;
mockScope.$watch.calls.forEach(function (call) {
if (call.args[0] === expr) {
watch = call.args[1];
}
});
return watch;
}
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
@@ -88,7 +81,6 @@ define(
mockComposition = ["a", "b", "c"];
mockCompositionObjects = mockComposition.map(mockDomainObject);
testConfiguration = {
panels: {
a: {
@@ -103,21 +95,57 @@ define(
mockScope.domainObject = mockDomainObject("mockDomainObject");
mockScope.model = testModel;
mockScope.configuration = testConfiguration;
mockScope.selection = jasmine.createSpyObj(
'selection',
['select', 'get', 'selected', 'deselect']
selectable[0] = {
context: {
oldItem: mockDomainObject
}
};
mockSelection = jasmine.createSpyObj("selection", [
'select',
'on',
'off',
'get'
]);
mockSelection.get.andReturn(selectable);
mockOpenMCT = {
selection: mockSelection
};
$element[0] = jasmine.createSpyObj(
"$element",
['click']
);
spyOn(mockScope.domainObject, "useCapability").andCallThrough();
controller = new LayoutController(mockScope);
controller = new LayoutController(mockScope, $element, mockOpenMCT);
spyOn(controller, "layoutPanels").andCallThrough();
findWatch("selection")(mockScope.selection);
jasmine.Clock.useMock();
});
it("listens for selection change events", function () {
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
it("cleans up on scope destroy", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
'$destroy',
jasmine.any(Function)
);
mockScope.$on.calls[0].args[1]();
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
// Model changes will indicate that panel positions
// may have changed, for instance.
it("watches for changes to composition", function () {
@@ -320,67 +348,35 @@ define(
.not.toEqual(oldStyle);
});
it("allows panels to be selected", function () {
it("allows objects to be selected", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
selectable[0].context.oldItem = childObj;
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(controller.selected(childObj)).toBe(true);
});
it("prevents event bubbling while drag is in progress", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
controller.select(mockEvent, childObj.getId());
// Do a drag
controller.startDrag(childObj.getId(), [1, 1], [0, 0]);
controller.continueDrag([100, 100]);
controller.endDrag();
// Because mouse position could cause the parent object to be selected, this should be ignored.
controller.bypassSelection(mockEvent);
expect(mockEvent.stopPropagation).toHaveBeenCalled();
expect(controller.selected(childObj)).toBe(true);
});
it("allows selection to be cleared", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
controller.select(null, childObj.getId());
controller.clearSelection();
expect(controller.selected(childObj)).toBeFalsy();
});
it("prevents clearing selection while drag is in progress", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
var id = childObj.getId();
controller.select(mockEvent, id);
// Do a drag
controller.startDrag(id, [1, 1], [0, 0]);
controller.continueDrag([100, 100]);
controller.endDrag();
// Because mouse position could cause clearSelection to be called, this should be ignored.
controller.clearSelection();
expect(controller.selected(childObj)).toBe(true);
// Shoud be able to clear the selection after dragging is done.
// Shoud be able to select another object when dragging is done.
jasmine.Clock.tick(0);
controller.clearSelection();
mockEvent.stopPropagation.reset();
controller.bypassSelection(mockEvent);
expect(controller.selected(childObj)).toBe(false);
});
it("clears selection after moving/resizing", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
var id = childObj.getId();
controller.select(mockEvent, id);
// Do a drag
controller.startDrag(id, [1, 1], [0, 0]);
controller.continueDrag([100, 100]);
controller.endDrag();
jasmine.Clock.tick(0);
controller.clearSelection();
expect(controller.selected(childObj)).toBe(false);
expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
});
it("shows frames by default", function () {
@@ -398,41 +394,37 @@ define(
it("hides frame when selected object has frame ", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
controller.select(mockEvent, childObj.getId());
expect(mockScope.selection.select).toHaveBeenCalled();
var selectedObj = mockScope.selection.select.mostRecentCall.args[0];
selectable[0].context.oldItem = childObj;
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
var toolbarObj = controller.getToolbar(childObj.getId(), childObj);
expect(controller.hasFrame(childObj)).toBe(true);
expect(selectedObj.hideFrame).toBeDefined();
expect(selectedObj.hideFrame).toEqual(jasmine.any(Function));
expect(toolbarObj.hideFrame).toBeDefined();
expect(toolbarObj.hideFrame).toEqual(jasmine.any(Function));
});
it("shows frame when selected object has no frame", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[1];
controller.select(mockEvent, childObj.getId());
expect(mockScope.selection.select).toHaveBeenCalled();
var selectedObj = mockScope.selection.select.mostRecentCall.args[0];
selectable[0].context.oldItem = childObj;
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
var toolbarObj = controller.getToolbar(childObj.getId(), childObj);
expect(controller.hasFrame(childObj)).toBe(false);
expect(selectedObj.showFrame).toBeDefined();
expect(selectedObj.showFrame).toEqual(jasmine.any(Function));
expect(toolbarObj.showFrame).toBeDefined();
expect(toolbarObj.showFrame).toEqual(jasmine.any(Function));
});
it("deselects the object that is no longer in the composition", function () {
it("selects the parent object when selected object is removed", function () {
mockScope.$watchCollection.mostRecentCall.args[1]();
var childObj = mockCompositionObjects[0];
controller.select(mockEvent, childObj.getId());
selectable[0].context.oldItem = childObj;
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
var composition = ["b", "c"];
mockScope.$watchCollection.mostRecentCall.args[1](composition);
expect(controller.selected(childObj)).toBe(false);
expect($element[0].click).toHaveBeenCalled();
});
});

View File

@@ -49,7 +49,7 @@
{
"key": "ListViewController",
"implementation": ListViewController,
"depends": ["$scope"]
"depends": ["$scope", "formatService"]
}
],
"directives": [

View File

@@ -21,7 +21,7 @@
*****************************************************************************/
define(function () {
function ListViewController($scope) {
function ListViewController($scope, formatService) {
this.$scope = $scope;
$scope.orderByField = 'title';
$scope.reverseSort = false;
@@ -30,6 +30,8 @@ define(function () {
var unlisten = $scope.domainObject.getCapability('mutation')
.listen(this.updateView.bind(this));
this.utc = formatService.getFormat('utc');
$scope.$on('$destroy', function () {
unlisten();
});
@@ -50,17 +52,13 @@ define(function () {
icon: child.getCapability('type').getCssClass(),
title: child.getModel().name,
type: child.getCapability('type').getName(),
persisted: new Date(
child.getModel().persisted
).toUTCString(),
modified: new Date(
child.getModel().modified
).toUTCString(),
persisted: this.utc.format(child.getModel().persisted),
modified: this.utc.format(child.getModel().modified),
asDomainObject: child,
location: child.getCapability('location'),
action: child.getCapability('action')
};
});
}, this);
};
return ListViewController;

View File

@@ -31,7 +31,9 @@ define(
controller,
childModel,
typeCapability,
mutationCapability;
mutationCapability,
formatService;
beforeEach(function () {
unlistenFunc = jasmine.createSpy("unlisten");
@@ -41,6 +43,18 @@ define(
);
mutationCapability.listen.andReturn(unlistenFunc);
formatService = jasmine.createSpyObj(
"formatService",
["getFormat"]
);
formatService.getFormat.andReturn(jasmine.createSpyObj(
'utc',
["format"]
));
formatService.getFormat().format.andCallFake(function (v) {
return "formatted " + v;
});
typeCapability = jasmine.createSpyObj(
"typeCapability",
["getCssClass", "getName"]
@@ -94,20 +108,27 @@ define(
);
scope.domainObject = domainObject;
controller = new ListViewController(scope);
controller = new ListViewController(scope, formatService);
waitsFor(function () {
return scope.children;
});
});
it("uses the UTC time format", function () {
expect(formatService.getFormat).toHaveBeenCalledWith('utc');
});
it("updates the view", function () {
expect(scope.children[0]).toEqual(
{
icon: "icon-folder",
title: "Battery Charge Status",
type: "Folder",
persisted: "Wed, 07 Jun 2017 20:34:57 GMT",
modified: "Wed, 07 Jun 2017 20:34:57 GMT",
persisted: formatService.getFormat('utc')
.format(childModel.persisted),
modified: formatService.getFormat('utc')
.format(childModel.modified),
asDomainObject: childObject,
location: ''
}

View File

@@ -1,37 +0,0 @@
# Plot README
## Chart
The `mct-chart` directive is used to support drawing of simple charts. It is
present to support the Plot view, and its functionality is limited to the
functionality that is relevant for that view.
This directive is used at the element level and takes one attribute, `draw`
which is an Angular expression which will should evaluate to a drawing object.
This drawing object should contain the following properties:
* `dimensions`: The size, in logical coordinates, of the chart area. A
two-element array or numbers.
* `origin`: The position, in logical coordinates, of the lower-left corner of
the chart area. A two-element array or numbers.
* `lines`: An array of lines (e.g. as a plot line) to draw, where each line is
expressed as an object containing:
* `buffer`: A Float32Array containing points in the line, in logical
coordinates, in sequential x,y pairs.
* `color`: The color of the line, as a four-element RGBA array, where
each element is a number in the range of 0.0-1.0.
* `points`: The number of points in the line.
* `boxes`: An array of rectangles to draw in the chart area. Each is an object
containing:
* `start`: The first corner of the rectangle, as a two-element array of
numbers, in logical coordinates.
* `end`: The opposite corner of the rectangle, as a two-element array of
numbers, in logical coordinates. color : The color of the line, as a
four-element RGBA array, where each element is a number in the range of
0.0-1.0.
While `mct-chart` is intended to support plots specifically, it does perform
some useful management of canvas objects (e.g. choosing between WebGL and Canvas
2D APIs for drawing based on browser support) so its usage is recommended when
its supported drawing primitives are sufficient for other charting tasks.

View File

@@ -1,157 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
define([
"./src/MCTChart",
"./src/PlotController",
"./src/policies/PlotViewPolicy",
"./src/PlotOptionsController",
"./src/services/ExportImageService",
"text!./res/templates/plot.html",
"text!./res/templates/plot-options-browse.html",
'legacyRegistry'
], function (
MCTChart,
PlotController,
PlotViewPolicy,
PlotOptionsController,
exportImageService,
plotTemplate,
plotOptionsBrowseTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/features/plot", {
"name": "Plot view for telemetry",
"extensions": {
"views": [
{
"name": "Plot",
"key": "plot",
"cssClass": "icon-sine",
"template": plotTemplate,
"needs": [
"telemetry"
],
"priority": "preferred",
"delegation": true
}
],
"directives": [
{
"key": "mctChart",
"implementation": MCTChart,
"depends": [
"$interval",
"$log"
]
}
],
"controllers": [
{
"key": "PlotController",
"implementation": PlotController,
"depends": [
"$scope",
"$element",
"exportImageService",
"telemetryFormatter",
"telemetryHandler",
"throttle",
"PLOT_FIXED_DURATION",
"openmct"
]
},
{
"key": "PlotOptionsController",
"implementation": PlotOptionsController,
"depends": [
"$scope"
]
}
],
"services": [
{
"key": "exportImageService",
"implementation": exportImageService,
"depends": [
"$q",
"$timeout",
"$log",
"EXPORT_IMAGE_TIMEOUT"
]
}
],
"constants": [
{
"key": "PLOT_FIXED_DURATION",
"value": 900000,
"priority": "fallback",
"comment": "Fifteen minutes."
},
{
"key": "EXPORT_IMAGE_TIMEOUT",
"value": 500,
"priority": "fallback"
}
],
"policies": [
{
"category": "view",
"implementation": PlotViewPolicy,
"depends": [
"openmct"
]
}
],
"representations": [
{
"key": "plot-options-browse",
"template": plotOptionsBrowseTemplate
}
],
"licenses": [
{
"name": "FileSaver.js",
"version": "0.0.2",
"author": "Eli Grey",
"description": "File download initiator (for file exports)",
"website": "https://github.com/eligrey/FileSaver.js/",
"copyright": "Copyright © 2015 Eli Grey.",
"license": "license-mit",
"link": "https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md"
},
{
"name": "html2canvas",
"version": "0.4.1",
"author": "Niklas von Hertzen",
"description": "JavaScript HTML renderer",
"website": "https://github.com/niklasvh/html2canvas",
"copyright": "Copyright © 2012 Niklas von Hertzen.",
"license": "license-mit",
"link": "https://github.com/niklasvh/html2canvas/blob/master/LICENSE"
}
]
}
});
});

View File

@@ -1,70 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2017, 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.
-->
<div ng-controller="PlotOptionsController" class="flex-elem grows l-inspector-part">
<em class="t-inspector-part-header" title="Display properties for this object">Plot Options</em>
<mct-form
ng-model="configuration.plot.xAxis"
structure="xAxisForm"
name="xAxisFormState"
class="flex-elem l-flex-row no-margin">
</mct-form>
<mct-form
ng-model="configuration.plot.yAxis"
structure="yAxisForm"
name="yAxisFormState"
class="flex-elem l-flex-row no-margin">
</mct-form>
<div class="form">
<div class="section-header ng-binding ng-scope">
Plot Series
</div>
<ul class="first flex-elem grows vscroll">
<ul class="tree">
<li ng-repeat="child in children">
<span ng-controller="ToggleController as toggle">
<span ng-controller="TreeNodeController as treeNode">
<span class="tree-item menus-to-left">
<span
class='ui-symbol view-control flex-elem has-children'
ng-class="{ expanded: toggle.isActive() }"
ng-click="toggle.toggle(); treeNode.trackExpansion()">
</span>
<mct-representation
class="rep-object-label"
key="'label'"
mct-object="child">
</mct-representation>
</span>
</span>
<mct-form
ng-class="{hidden: !toggle.isActive()}"
ng-model="configuration.plot.series[$index]"
structure="plotSeriesForm"
name="plotOptionsState"
class="flex-elem l-flex-row">
</mct-form>
</span>
</li>
</ul>
</ul>
</div>
</div>

View File

@@ -1,165 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2017, 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.
-->
<span ng-controller="PlotController as plot"
class="abs holder holder-plot has-control-bar">
<div class="l-control-bar" ng-show="!plot.hideExportButtons">
<span class="l-btn-set">
<a class="s-button t-export labeled icon-download"
ng-click="plot.exportPNG()"
title="Export This View's Data as PNG">
PNG
</a>
<a class="s-button t-export labeled"
ng-click="plot.exportJPG()"
title="Export This View's Data as JPG">
JPG
</a>
</span>
</div>
<div class="l-view-section">
<div class="gl-plot"
ng-style="{ height: 100 / plot.getSubPlots().length + '%'}"
ng-repeat="subplot in plot.getSubPlots()">
<div class="gl-plot-legend">
<span class='plot-legend-item'
ng-repeat="telemetryObject in subplot.getTelemetryObjects()"
ng-class="plot.getLegendClass(telemetryObject)">
<span class='plot-color-swatch'
ng-style="{ 'background-color': plot.getColor($index) }">
</span>
<span class='title-label'>{{telemetryObject.getModel().name}}</span>
</span>
</div>
<div class="gl-plot-axis-area gl-plot-y">
<div class="gl-plot-label gl-plot-y-label">
{{axes[1].active.name}}
</div>
<div ng-repeat="tick in subplot.getRangeTicks()"
class="gl-plot-tick gl-plot-y-tick-label"
ng-style="{ bottom: (100 * $index / (subplot.getRangeTicks().length - 1)) + '%' }">
{{tick.label | reverse}}
</div>
<div class="gl-plot-y-options gl-plot-local-controls"
ng-if="axes[1].options.length > 1">
<div class='form-control shell select'>
<select class="form-control input shell"
ng-model="axes[1].active"
ng-options="option.name for option in axes[1].options">
</select>
</div>
</div>
</div>
<div class="gl-plot-wrapper-display-area-and-x-axis">
<mct-include key="'time-of-interest'"
class="l-toi-holder show-val"
ng-if="toiPerc"
ng-class="{ 'pinned': toiPinned, 'val-to-left': toiPerc > 80 }"
ng-style="{'left': toiPerc + '%'}"></mct-include>
<div class="gl-plot-coords"
ng-if="subplot.isHovering() && subplot.getHoverCoordinates()">
{{subplot.getHoverCoordinates()}}
</div>
<div class="gl-plot-display-area"
ng-mouseenter="subplot.isHovering(true);"
ng-mouseleave="subplot.isHovering(false)"
ng-class="{ loading: plot.isRequestPending() }">
<!-- Out-of-bounds data indicators -->
<!-- ng-show is temporarily hard-coded in next element -->
<div ng-show="false" class="l-oob-data l-oob-data-up"></div>
<div ng-show="false" class="l-oob-data l-oob-data-dwn"></div>
<div class="gl-plot-hash hash-v"
ng-repeat="tick in subplot.getDomainTicks()"
ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%', height: '100%' }"
ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)">
</div>
<div class="gl-plot-hash hash-h"
ng-repeat="tick in subplot.getRangeTicks()"
ng-style="{ bottom: (100 * $index / (subplot.getRangeTicks().length - 1)) + '%', width: '100%' }"
ng-show="$index > 0 && $index < (subplot.getRangeTicks().length - 1)">
</div>
<mct-chart draw="subplot.getDrawingObject()"
ng-if="subplot.getTelemetryObjects().length > 0"
ng-mousemove="subplot.hover($event)"
mct-drag="subplot.continueDrag($event)"
mct-drag-down="subplot.startDrag($event)"
mct-drag-up="subplot.endDrag($event); plot.update()">
</mct-chart>
<!-- TODO: Move into correct position; make part of group; infer from set of actions -->
<div class="l-local-controls gl-plot-local-controls t-plot-display-controls"
ng-if="$first">
<a class="s-button icon-arrow-left"
ng-click="plot.stepBackPanZoom()"
ng-show="plot.isZoomed()"
title="Restore previous pan/zoom">
</a>
<a class="s-button icon-arrows-out"
ng-click="plot.unzoom()"
ng-show="plot.isZoomed()"
title="Reset pan/zoom">
</a>
<div class="menu-element s-menu-button menus-to-left {{plot.getMode().cssClass}}"
ng-if="plot.getModeOptions().length > 1"
ng-controller="ClickAwayController as toggle">
<span class="l-click-area" ng-click="toggle.toggle()"></span>
<span>{{plot.getMode().name}}</span>
<div class="menu" ng-show="toggle.isActive()">
<ul>
<li ng-repeat="option in plot.getModeOptions()"
ng-click="plot.setMode(option); toggle.setState(false)"
class="{{option.cssClass}}">
{{option.name}}
</li>
</ul>
</div>
</div>
</div>
</div>
<div ng-if="$last" class="gl-plot-axis-area gl-plot-x">
<div ng-repeat="tick in subplot.getDomainTicks()"
class="gl-plot-tick gl-plot-x-tick-label"
ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)"
ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%' }">
{{tick.label | reverse}}
</div>
<div class="gl-plot-label gl-plot-x-label">
{{axes[0].active.name}}
</div>
<div class="gl-plot-x-options gl-plot-local-controls"
ng-if="axes[0].options.length > 1">
<div class='form-control shell select'>
<select class="form-control input shell"
ng-model="axes[0].active"
ng-options="option.name for option in axes[0].options">
</select>
</div>
</div>
</div>
</div>
</div>
</div>
</span>

View File

@@ -1,117 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
define(
[],
function () {
/**
* Create a new chart which uses Canvas's 2D API for rendering.
*
* @memberof platform/features/plot
* @constructor
* @implements {platform/features/plot.Chart}
* @param {CanvasElement} canvas the canvas object to render upon
* @throws {Error} an error is thrown if Canvas's 2D API is unavailable.
*/
function Canvas2DChart(canvas) {
this.canvas = canvas;
this.c2d = canvas.getContext('2d');
this.width = canvas.width;
this.height = canvas.height;
this.dimensions = [this.width, this.height];
this.origin = [0, 0];
if (!this.c2d) {
throw new Error("Canvas 2d API unavailable.");
}
}
// Convert from logical to physical x coordinates
Canvas2DChart.prototype.x = function (v) {
return ((v - this.origin[0]) / this.dimensions[0]) * this.width;
};
// Convert from logical to physical y coordinates
Canvas2DChart.prototype.y = function (v) {
return this.height -
((v - this.origin[1]) / this.dimensions[1]) * this.height;
};
// Set the color to be used for drawing operations
Canvas2DChart.prototype.setColor = function (color) {
var mappedColor = color.map(function (c, i) {
return i < 3 ? Math.floor(c * 255) : (c);
}).join(',');
this.c2d.strokeStyle = "rgba(" + mappedColor + ")";
this.c2d.fillStyle = "rgba(" + mappedColor + ")";
};
Canvas2DChart.prototype.clear = function () {
var canvas = this.canvas;
this.width = canvas.width;
this.height = canvas.height;
this.c2d.clearRect(0, 0, this.width, this.height);
};
Canvas2DChart.prototype.setDimensions = function (newDimensions, newOrigin) {
this.dimensions = newDimensions;
this.origin = newOrigin;
};
Canvas2DChart.prototype.drawLine = function (buf, color, points) {
var i;
this.setColor(color);
// Configure context to draw two-pixel-thick lines
this.c2d.lineWidth = 2;
// Start a new path...
if (buf.length > 1) {
this.c2d.beginPath();
this.c2d.moveTo(this.x(buf[0]), this.y(buf[1]));
}
// ...and add points to it...
for (i = 2; i < points * 2; i = i + 2) {
this.c2d.lineTo(this.x(buf[i]), this.y(buf[i + 1]));
}
// ...before finally drawing it.
this.c2d.stroke();
};
Canvas2DChart.prototype.drawSquare = function (min, max, color) {
var x1 = this.x(min[0]),
y1 = this.y(min[1]),
w = this.x(max[0]) - x1,
h = this.y(max[1]) - y1;
this.setColor(color);
this.c2d.fillRect(x1, y1, w, h);
};
return Canvas2DChart;
}
);

View File

@@ -1,160 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
/**
* Module defining GLPlot. Created by vwoeltje on 11/12/14.
*/
define(
[],
function () {
// WebGL shader sources (for drawing plain colors)
var FRAGMENT_SHADER = [
"precision mediump float;",
"uniform vec4 uColor;",
"void main(void) {",
"gl_FragColor = uColor;",
"}"
].join('\n'),
VERTEX_SHADER = [
"attribute vec2 aVertexPosition;",
"uniform vec2 uDimensions;",
"uniform vec2 uOrigin;",
"void main(void) {",
"gl_Position = vec4(2.0 * ((aVertexPosition - uOrigin) / uDimensions) - vec2(1,1), 0, 1);",
"}"
].join('\n');
/**
* Create a new chart which uses WebGL for rendering.
*
* @memberof platform/features/plot
* @constructor
* @implements {platform/features/plot.Chart}
* @param {CanvasElement} canvas the canvas object to render upon
* @throws {Error} an error is thrown if WebGL is unavailable.
*/
function GLChart(canvas) {
var gl = canvas.getContext("webgl", { preserveDrawingBuffer: true }) ||
canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true }),
vertexShader,
fragmentShader,
program,
aVertexPosition,
uColor,
uDimensions,
uOrigin;
// Ensure a context was actually available before proceeding
if (!gl) {
throw new Error("WebGL unavailable.");
}
// Initialize shaders
vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, VERTEX_SHADER);
gl.compileShader(vertexShader);
fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, FRAGMENT_SHADER);
gl.compileShader(fragmentShader);
// Assemble vertex/fragment shaders into programs
program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
// Get locations for attribs/uniforms from the
// shader programs (to pass values into shaders at draw-time)
aVertexPosition = gl.getAttribLocation(program, "aVertexPosition");
uColor = gl.getUniformLocation(program, "uColor");
uDimensions = gl.getUniformLocation(program, "uDimensions");
uOrigin = gl.getUniformLocation(program, "uOrigin");
gl.enableVertexAttribArray(aVertexPosition);
// Create a buffer to holds points which will be drawn
this.buffer = gl.createBuffer();
// Use a line width of 2.0 for legibility
gl.lineWidth(2.0);
// Enable blending, for smoothness
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
this.gl = gl;
this.aVertexPosition = aVertexPosition;
this.uColor = uColor;
this.uDimensions = uDimensions;
this.uOrigin = uOrigin;
}
// Utility function to handle drawing of a buffer;
// drawType will determine whether this is a box, line, etc.
GLChart.prototype.doDraw = function (drawType, buf, color, points) {
var gl = this.gl;
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.bufferData(gl.ARRAY_BUFFER, buf, gl.DYNAMIC_DRAW);
gl.vertexAttribPointer(this.aVertexPosition, 2, gl.FLOAT, false, 0, 0);
gl.uniform4fv(this.uColor, color);
gl.drawArrays(drawType, 0, points);
};
GLChart.prototype.clear = function () {
var gl = this.gl;
// Set the viewport size; note that we use the width/height
// that our WebGL context reports, which may be lower
// resolution than the canvas we requested.
gl.viewport(
0,
0,
gl.drawingBufferWidth,
gl.drawingBufferHeight
);
gl.clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT);
};
GLChart.prototype.setDimensions = function (dimensions, origin) {
var gl = this.gl;
if (dimensions && dimensions.length > 0 &&
origin && origin.length > 0) {
gl.uniform2fv(this.uDimensions, dimensions);
gl.uniform2fv(this.uOrigin, origin);
}
};
GLChart.prototype.drawLine = function (buf, color, points) {
this.doDraw(this.gl.LINE_STRIP, buf, color, points);
};
GLChart.prototype.drawSquare = function (min, max, color) {
this.doDraw(this.gl.TRIANGLE_FAN, new Float32Array(
min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]])
), color, 4);
};
return GLChart;
}
);

View File

@@ -1,250 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
/**
* Module defining MCTChart. Created by vwoeltje on 11/12/14.
*/
define(
["./GLChart", "./Canvas2DChart"],
function (GLChart, Canvas2DChart) {
var TEMPLATE = "<canvas style='position: absolute; background: none; width: 100%; height: 100%;'></canvas>";
/**
* The mct-chart directive provides a canvas element which can be
* drawn upon, to support Plot view and similar visualizations.
*
* This directive takes one attribute, "draw", which is an Angular
* expression which will be two-way bound to a drawing object. This
* drawing object should contain:
*
* * `dimensions`: An object describing the logical bounds of the
* drawable area, containing two fields:
* * `origin`: The position, in logical coordinates, of the
* lower-left corner of the chart area. A two-element array.
* * `dimensions`: A two-element array containing the width
* and height of the chart area, in logical coordinates.
* * `lines`: An array of lines to be drawn, where each line is
* expressed as an object containing:
* * `buffer`: A Float32Array containing points in the line,
* in logical coordinate, in sequential x/y pairs.
* * `color`: The color of the line, as a four-element RGBA
* array, where each element is in the range of 0.0-1.0
* * `points`: The number of points in the line.
* * `boxes`: An array of rectangles to draw in the chart area
* (used for marquee zoom). Each is an object containing:
* * `start`: The first corner of the rectangle (as a two-element
* array, logical coordinates)
* * `end`: The opposite corner of the rectangle (again, as a
* two-element array)
* * `color`: The color of the box, as a four-element RGBA
* array, where each element is in the range of 0.0-1.0
*
* @memberof platform/features/plot
* @constructor
*/
function MCTChart($interval, $log) {
// Get an underlying chart implementation
function getChart(Charts, canvas) {
// Try the first available option...
var Chart = Charts[0];
// This function recursively try-catches all options;
// if these all fail, issue a warning.
if (!Chart) {
$log.warn("Cannot initialize mct-chart.");
return undefined;
}
// Try first option; if it fails, try remaining options
try {
return new Chart(canvas);
} catch (e) {
$log.warn([
"Could not instantiate chart",
Chart.name,
";",
e.message
].join(" "));
return getChart(Charts.slice(1), canvas);
}
}
function linkChart(scope, element) {
var canvas = element.find("canvas")[0],
activeInterval,
chart;
// Handle drawing, based on contents of the "draw" object
// in scope
function doDraw(draw) {
// Ensure canvas context has same resolution
// as canvas element
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
// Clear previous contents
chart.clear();
// Nothing to draw if no draw object defined
if (!draw) {
return;
}
// Set logical boundaries for the chart
chart.setDimensions(
draw.dimensions || [1, 1],
draw.origin || [0, 0]
);
// Draw line segments
(draw.lines || []).forEach(function (line) {
chart.drawLine(
line.buffer,
line.color,
line.points
);
});
// Draw boxes (e.g. marquee zoom rect)
(draw.boxes || []).forEach(function (box) {
chart.drawSquare(
box.start,
box.end,
box.color
);
});
}
// Issue a drawing call, if-and-only-if canvas size
// has changed. This will be called on a timer, since
// there is no event to depend on.
function drawIfResized() {
if (canvas.width !== canvas.offsetWidth ||
canvas.height !== canvas.offsetHeight) {
doDraw(scope.draw);
scope.$apply();
}
}
// Stop watching for changes to size (scope destroyed)
function releaseInterval() {
if (activeInterval) {
$interval.cancel(activeInterval);
}
}
// Switch from WebGL to plain 2D if context is lost
function fallbackFromWebGL() {
element.html(TEMPLATE);
canvas = element.find("canvas")[0];
chart = getChart([Canvas2DChart], canvas);
if (chart) {
doDraw(scope.draw);
}
}
// Try to initialize a chart.
chart = getChart([GLChart, Canvas2DChart], canvas);
// If that failed, there's nothing more we can do here.
// (A warning will already have been issued)
if (!chart) {
return;
}
// WebGL is a bit of a special case; it may work, then fail
// later for various reasons, so we need to listen for this
// and fall back to plain canvas drawing when it occurs.
canvas.addEventListener("webglcontextlost", fallbackFromWebGL);
// Check for resize, on a timer
activeInterval = $interval(drawIfResized, 1000, 0, false);
// Watch "draw" for external changes to the set of
// things to be drawn.
scope.$watchCollection("draw", doDraw);
// Stop checking for resize when scope is destroyed
scope.$on("$destroy", releaseInterval);
}
return {
// Apply directive only to elements
restrict: "E",
// Template to use (a canvas element)
template: TEMPLATE,
// Link function; set up scope
link: linkChart,
// Initial, isolate scope for the directive
scope: { draw: "=" }
};
}
/**
* @interface platform/features/plot.Chart
* @private
*/
/**
* Clear the chart.
* @method platform/features/plot.Chart#clear
*/
/**
* Set the logical boundaries of the chart.
* @param {number[]} dimensions the horizontal and
* vertical dimensions of the chart
* @param {number[]} origin the horizontal/vertical
* origin of the chart
* @memberof platform/features/plot.Chart#setDimensions
*/
/**
* Draw the supplied buffer as a line strip (a sequence
* of line segments), in the chosen color.
* @param {Float32Array} buf the line strip to draw,
* in alternating x/y positions
* @param {number[]} color the color to use when drawing
* the line, as an RGBA color where each element
* is in the range of 0.0-1.0
* @param {number} points the number of points to draw
* @memberof platform/features/plot.Chart#drawLine
*/
/**
* Draw a rectangle extending from one corner to another,
* in the chosen color.
* @param {number[]} min the first corner of the rectangle
* @param {number[]} max the opposite corner
* @param {number[]} color the color to use when drawing
* the rectangle, as an RGBA color where each element
* is in the range of 0.0-1.0
* @memberof platform/features/plot.Chart#drawSquare
*/
return MCTChart;
}
);

View File

@@ -1,437 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
/**
* This bundle adds a "Plot" view for numeric telemetry data.
* @namespace platform/features/plot
*/
define(
[
"./elements/PlotUpdater",
"./elements/PlotPalette",
"./elements/PlotAxis",
"./elements/PlotLimitTracker",
"./elements/PlotTelemetryFormatter",
"./modes/PlotModeOptions",
"./SubPlotFactory"
],
function (
PlotUpdater,
PlotPalette,
PlotAxis,
PlotLimitTracker,
PlotTelemetryFormatter,
PlotModeOptions,
SubPlotFactory
) {
var AXIS_DEFAULTS = [
{ "name": "Time" },
{ "name": "Value" }
];
/**
* The PlotController is responsible for any computation/logic
* associated with displaying the plot view. Specifically, these
* responsibilities include:
*
* * Describing axes and labeling.
* * Handling user interactions.
* * Deciding what needs to be drawn in the chart area.
*
* @memberof platform/features/plot
* @constructor
*/
function PlotController(
$scope,
$element,
exportImageService,
telemetryFormatter,
telemetryHandler,
throttle,
PLOT_FIXED_DURATION,
openmct
) {
var self = this,
plotTelemetryFormatter =
new PlotTelemetryFormatter(telemetryFormatter),
subPlotFactory =
new SubPlotFactory(plotTelemetryFormatter),
cachedObjects = [],
updater,
lastBounds,
lastRange,
lastDomain,
handle;
var timeAPI = openmct.time;
// Populate the scope with axis information (specifically, options
// available for each axis.)
function setupAxes(metadatas) {
$scope.axes.forEach(function (axis) {
axis.updateMetadata(metadatas);
});
}
// Trigger an update of a specific subplot;
// used in a loop to update all subplots.
function updateSubplot(subplot) {
subplot.update();
}
// Set up available modes (stacked/overlaid), based on the
// set of telemetry objects in this plot view.
function setupModes(telemetryObjects) {
if (cachedObjects !== telemetryObjects) {
cachedObjects = telemetryObjects;
self.modeOptions = new PlotModeOptions(
telemetryObjects || [],
subPlotFactory
);
}
}
// Change the displayable bounds
function setBasePanZoom(bounds) {
var start = bounds.start,
end = bounds.end;
if (updater) {
updater.setDomainBounds(start, end);
self.update();
}
lastBounds = bounds;
}
// Reinstantiate the plot updater (e.g. because we have a
// new subscription.) This will clear the plot.
function recreateUpdater() {
var domain = $scope.axes[0].active.key,
range = $scope.axes[1].active.key,
duration = PLOT_FIXED_DURATION;
updater = new PlotUpdater(handle, domain, range, duration);
lastDomain = domain;
lastRange = range;
self.limitTracker = new PlotLimitTracker(handle, range);
// Keep any externally-provided bounds
if (lastBounds) {
setBasePanZoom(lastBounds);
}
}
function getUpdater() {
if (!updater) {
recreateUpdater();
}
return updater;
}
// Handle new telemetry data in this plot
function updateValues() {
self.pending = false;
if (handle) {
setupModes(handle.getTelemetryObjects());
setupAxes(handle.getMetadata());
getUpdater().update();
self.modeOptions.getModeHandler().plotTelemetry(updater);
self.limitTracker.update();
self.update();
}
}
// Display new historical data as it becomes available
function addHistoricalData(domainObject, series) {
self.pending = false;
getUpdater().addHistorical(domainObject, series);
self.modeOptions.getModeHandler().plotTelemetry(updater);
self.update();
}
// Issue a new request for historical telemetry
function requestTelemetry() {
if (handle) {
handle.request({}, addHistoricalData);
}
}
// Requery for data entirely
function replot() {
if (handle) {
updater = undefined;
requestTelemetry();
}
}
function changeTimeOfInterest(timeOfInterest) {
if (timeOfInterest !== undefined) {
var bounds = timeAPI.bounds();
var range = bounds.end - bounds.start;
$scope.toiPerc = ((timeOfInterest - bounds.start) / range) * 100;
$scope.toiPinned = true;
} else {
$scope.toiPerc = undefined;
$scope.toiPinned = false;
}
}
// Create a new subscription; telemetrySubscriber gets
// to do the meaningful work here.
function subscribe(domainObject) {
if (handle) {
handle.unsubscribe();
}
handle = domainObject && telemetryHandler.handle(
domainObject,
updateValues,
true // Lossless
);
replot();
changeTimeOfInterest(timeAPI.timeOfInterest());
timeAPI.on("timeOfInterest", changeTimeOfInterest);
}
// Release the current subscription (called when scope is destroyed)
function releaseSubscription() {
if (handle) {
handle.unsubscribe();
handle = undefined;
}
timeAPI.off("timeOfInterest", changeTimeOfInterest);
}
function requery() {
self.pending = true;
releaseSubscription();
subscribe($scope.domainObject);
}
function updateDomainFormat() {
var domainAxis = $scope.axes[0];
plotTelemetryFormatter
.setDomainFormat(domainAxis.active.format);
}
function domainRequery(newDomain) {
if (newDomain !== lastDomain) {
updateDomainFormat();
requery();
}
}
function rangeRequery(newRange) {
if (newRange !== lastRange) {
requery();
}
}
// Respond to a display bounds change (requery for data)
function changeDisplayBounds(event, bounds, follow) {
//'hack' for follow mode
if (follow === true) {
setBasePanZoom(bounds);
} else {
var domainAxis = $scope.axes[0];
if (bounds.domain) {
domainAxis.chooseOption(bounds.domain);
}
updateDomainFormat();
setBasePanZoom(bounds);
requery();
}
self.setUnsynchedStatus($scope.domainObject, follow && self.isZoomed());
changeTimeOfInterest(timeAPI.timeOfInterest());
}
this.modeOptions = new PlotModeOptions([], subPlotFactory);
this.updateValues = updateValues;
// Create a throttled update function
this.scheduleUpdate = throttle(function () {
self.modeOptions.getModeHandler().getSubPlots()
.forEach(updateSubplot);
});
self.pending = true;
self.$element = $element;
self.exportImageService = exportImageService;
// Initialize axes; will get repopulated when telemetry
// metadata becomes available.
$scope.axes = [
new PlotAxis("domains", [], AXIS_DEFAULTS[0]),
new PlotAxis("ranges", [], AXIS_DEFAULTS[1])
];
//Are some initialized bounds defined?
var bounds = timeAPI.bounds();
if (bounds &&
bounds.start !== undefined &&
bounds.end !== undefined) {
changeDisplayBounds(undefined, timeAPI.bounds(), timeAPI.clock() !== undefined);
}
// Watch for changes to the selected axis
$scope.$watch("axes[0].active.key", domainRequery);
$scope.$watch("axes[1].active.key", rangeRequery);
// Subscribe to telemetry when a domain object becomes available
$scope.$watch('domainObject', subscribe);
// Respond to external bounds changes
$scope.$on("telemetry:display:bounds", changeDisplayBounds);
// Unsubscribe when the plot is destroyed
$scope.$on("$destroy", releaseSubscription);
}
/**
* Get the color (as a style-friendly string) to use
* for plotting the trace at the specified index.
* @param {number} index the index of the trace
* @returns {string} the color, in #RRGGBB form
*/
PlotController.prototype.getColor = function (index) {
return PlotPalette.getStringColor(index);
};
/**
* Check if the plot is zoomed or panned out
* of its default state (to determine whether back/unzoom
* controls should be shown)
* @returns {boolean} true if not in default state
*/
PlotController.prototype.isZoomed = function () {
return this.modeOptions.getModeHandler().isZoomed();
};
/**
* Undo the most recent pan/zoom change and restore
* the prior state.
*/
PlotController.prototype.stepBackPanZoom = function () {
return this.modeOptions.getModeHandler().stepBackPanZoom();
};
/**
* Undo all pan/zoom changes and restore the initial state.
*/
PlotController.prototype.unzoom = function () {
return this.modeOptions.getModeHandler().unzoom();
};
/**
* Get the mode options (Stacked/Overlaid) that are applicable
* for this plot.
*/
PlotController.prototype.getModeOptions = function () {
return this.modeOptions.getModeOptions();
};
/**
* Get the current mode that is applicable to this plot. This
* will include key, name, and cssClass fields.
*/
PlotController.prototype.getMode = function () {
return this.modeOptions.getMode();
};
/**
* Set the mode which should be active in this plot.
* @param mode one of the mode options returned from
* getModeOptions()
*/
PlotController.prototype.setMode = function (mode) {
this.modeOptions.setMode(mode);
this.updateValues();
};
/**
* Get all individual plots contained within this Plot view.
* (Multiple may be contained when in Stacked mode).
* @returns {SubPlot[]} all subplots in this Plot view
*/
PlotController.prototype.getSubPlots = function () {
return this.modeOptions.getModeHandler().getSubPlots();
};
/**
* Get the CSS class to apply to the legend for this domain
* object; this will reflect limit state.
* @returns {string} the CSS class
*/
PlotController.prototype.getLegendClass = function (telemetryObject) {
return this.limitTracker &&
this.limitTracker.getLegendClass(telemetryObject);
};
/**
* Explicitly update all plots.
*/
PlotController.prototype.update = function () {
this.scheduleUpdate();
};
/**
* Check if a request is pending (to show the wait spinner)
*/
PlotController.prototype.isRequestPending = function () {
// Placeholder; this should reflect request state
// when requesting historical telemetry
return this.pending;
};
PlotController.prototype.setUnsynchedStatus = function (domainObject, status) {
if (domainObject.hasCapability('status')) {
domainObject.getCapability('status').set('timeconductor-unsynced', status);
}
};
/**
* Export the plot to PNG
*/
PlotController.prototype.exportPNG = function () {
var self = this;
self.hideExportButtons = true;
self.exportImageService.exportPNG(self.$element[0], "plot.png").finally(function () {
self.hideExportButtons = false;
});
};
/**
* Export the plot to JPG
*/
PlotController.prototype.exportJPG = function () {
var self = this;
self.hideExportButtons = true;
self.exportImageService.exportJPG(self.$element[0], "plot.jpg").finally(function () {
self.hideExportButtons = false;
});
};
return PlotController;
}
);

View File

@@ -1,195 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
define(
['./PlotOptionsForm'],
function (PlotOptionsForm) {
/**
* Notes on implementation of plot options
*
* Multiple y-axes will have to be handled with multiple forms as
* they will need to be stored on distinct model object
*
* Likewise plot series options per-child will need to be separate
* forms.
*/
/**
* The LayoutController is responsible for supporting the
* Layout view. It arranges frames according to saved configuration
* and provides methods for updating these based on mouse
* movement.
* @memberof platform/features/plot
* @constructor
* @param {Scope} $scope the controller's Angular scope
*/
function PlotOptionsController($scope) {
var self = this;
this.$scope = $scope;
this.domainObject = $scope.domainObject;
this.configuration = this.domainObject.getModel().configuration || {};
this.plotOptionsForm = new PlotOptionsForm();
this.composition = [];
this.watches = [];
/*
Listen for changes to the domain object and update the object's
children.
*/
this.mutationListener = this.domainObject.getCapability('mutation').listen(function (model) {
if (self.hasCompositionChanged(self.composition, model.composition)) {
self.updateChildren();
}
});
/*
Set form structures on scope
*/
$scope.plotSeriesForm = this.plotOptionsForm.plotSeriesForm;
$scope.xAxisForm = this.plotOptionsForm.xAxisForm;
$scope.yAxisForm = this.plotOptionsForm.yAxisForm;
$scope.$on("$destroy", function () {
//Clean up any listeners on destruction of controller
self.mutationListener();
});
this.defaultConfiguration();
this.updateChildren();
/*
* Setup a number of watches for changes to form values. On
* change, update the model configuration via mutation
*/
$scope.$watchCollection('configuration.plot.yAxis', function (newValue, oldValue) {
self.updateConfiguration(newValue, oldValue);
});
$scope.$watchCollection('configuration.plot.xAxis', function (newValue, oldValue) {
self.updateConfiguration(newValue, oldValue);
});
this.watchSeries();
}
/**
* Unregister all watches for series data (ie. the configuration for
* child objects)
* @private
*/
PlotOptionsController.prototype.clearSeriesWatches = function () {
this.watches.forEach(function (watch) {
watch();
});
this.watches = [];
};
/**
* Attach watches for each object in the plot's composition
* @private
*/
PlotOptionsController.prototype.watchSeries = function () {
var self = this;
this.clearSeriesWatches();
(self.$scope.children || []).forEach(function (child, index) {
self.watches.push(
self.$scope.$watchCollection(
'configuration.plot.series[' + index + ']',
function (newValue, oldValue) {
self.updateConfiguration(newValue, oldValue);
}
)
);
});
};
/**
* Determine whether the changes to the model that triggered a
* mutation event were purely compositional.
*
* @private
*/
PlotOptionsController.prototype.hasCompositionChanged = function (oldComposition, newComposition) {
// Framed slightly strangely, but the boolean logic is
// easier to follow for the unchanged case.
var isUnchanged = oldComposition === newComposition ||
(
oldComposition.length === newComposition.length &&
oldComposition.every(function (currentValue, index) {
return newComposition[index] && currentValue === newComposition[index];
})
);
return !isUnchanged;
};
/**
* Default the plot options model
*
* @private
*/
PlotOptionsController.prototype.defaultConfiguration = function () {
this.configuration.plot = this.configuration.plot || {};
this.configuration.plot.xAxis = this.configuration.plot.xAxis || {};
this.configuration.plot.yAxis = this.configuration.plot.yAxis || {}; // y-axes will be associative array keyed on axis key
this.configuration.plot.series = this.configuration.plot.series || []; // series will be associative array keyed on sub-object id
this.$scope.configuration = this.configuration;
};
/**
* When a child is added to, or removed from a plot, update the
* plot options model
* @private
*/
PlotOptionsController.prototype.updateChildren = function () {
var self = this;
this.domainObject.useCapability('composition').then(function (children) {
self.$scope.children = children;
self.composition = self.domainObject.getModel().composition;
children.forEach(function (child, index) {
self.configuration.plot.series[index] =
self.configuration.plot.series[index] || {'id': child.getId()};
});
self.watchSeries();
});
};
/**
* On changes to the form, update the configuration on the domain
* object
* @private
*/
PlotOptionsController.prototype.updateConfiguration = function () {
var self = this;
this.domainObject.useCapability('mutation', function (model) {
model.configuration = model.configuration || {};
model.configuration.plot = self.configuration.plot;
});
};
return PlotOptionsController;
}
);

View File

@@ -1,150 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
define(
[],
function () {
/**
* A class for encapsulating structure and behaviour of the plot
* options form
* @memberOf platform/features/plot
* @param topic
* @constructor
*/
function PlotOptionsForm() {
/*
Defined below are the form structures for the plot options.
*/
this.xAxisForm = {
'name': 'x-axis',
'sections': [{
'name': 'x-axis',
'rows': [
{
'name': 'Domain',
'control': 'select',
'key': 'key',
'options': [
{'name': 'SCET', 'value': 'scet'},
{'name': 'SCLK', 'value': 'sclk'},
{'name': 'LST', 'value': 'lst'}
]
}
]
}]};
this.yAxisForm = {
'name': 'y-axis',
'sections': [{
// Will need to be repeated for each y-axis, with a
// distinct name for each. Ideally the name of the axis
// itself.
'name': 'y-axis',
'rows': [
{
'name': 'Range',
'control': 'select',
'key': 'key',
'options': [
{'name': 'EU', 'value': 'eu'},
{'name': 'DN', 'value': 'dn'},
{'name': 'Status', 'value': 'status'}
]
},
{
'name': 'Autoscale',
'control': 'checkbox',
'key': 'autoscale'
},
{
'name': 'Min',
'control': 'textfield',
'key': 'min',
'pattern': '[0-9]',
'inputsize' : 'sm'
},
{
'name': 'Max',
'control': 'textfield',
'key': 'max',
'pattern': '[0-9]',
'inputsize' : 'sm'
}
]
}]
};
this.plotSeriesForm = {
'name': 'Series Options',
'sections': [
{
rows: [
{
'name': 'Color',
'control': 'color',
'key': 'color'
}]
},
{
'rows': [
{
'name': 'Markers',
'control': 'checkbox',
'key': 'markers',
'layout': 'control-first'
}
]
},
{
'rows': [
{
'name': 'No Line',
'control': 'radio',
'key': 'lineType',
'value': 'noLine',
'layout': 'control-first'
},
{
'name': 'Step Line',
'control': 'radio',
'key': 'lineType',
'value': 'stepLine',
'layout': 'control-first'
},
{
'name': 'Linear Line',
'control': 'radio',
'key': 'lineType',
'value': 'linearLine',
'layout': 'control-first'
}
]
}
]
};
}
return PlotOptionsForm;
}
);

View File

@@ -1,415 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
define(
[
'./elements/PlotPosition',
'./elements/PlotTickGenerator'
],
function (PlotPosition, PlotTickGenerator) {
var DOMAIN_TICKS = 5,
RANGE_TICKS = 7;
/**
* A SubPlot is an individual plot within a Plot View (which
* may contain multiple plots, specifically when in Stacked
* plot mode.)
* @memberof platform/features/plot
* @constructor
* @param {DomainObject[]} telemetryObjects the domain objects
* which will be plotted in this sub-plot
* @param {PlotPanZoomStack} panZoomStack the stack of pan-zoom
* states which is applicable to this sub-plot
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service; used to convert domain/range values
* from telemetry data sets to a human-readable form.
*/
function SubPlot(telemetryObjects, panZoomStack, telemetryFormatter) {
// We are used from a template often, so maintain
// state in local variables to allow for fast look-up,
// as is normal for controllers.
this.telemetryObjects = telemetryObjects;
this.domainTicks = [];
this.rangeTicks = [];
this.formatter = telemetryFormatter;
this.draw = {};
this.hovering = false;
this.panZoomStack = panZoomStack;
// Start with the right initial drawing bounds,
// tick marks
this.updateDrawingBounds();
this.updateTicks();
}
/**
* Tests whether this subplot has domain data to show for the current pan/zoom level. Absence of domain data
* implies that there is no range data displayed either
* @returns {boolean} true if domain data exists for the current pan/zoom level
*/
SubPlot.prototype.hasDomainData = function () {
return this.panZoomStack &&
this.panZoomStack.getDimensions()[0] > 0;
};
// Utility function for filtering out empty strings.
function isNonEmpty(v) {
return typeof v === 'string' && v !== "";
}
// Converts from pixel coordinates to domain-range,
// to interpret mouse gestures.
SubPlot.prototype.mousePositionToDomainRange = function (mousePosition) {
return new PlotPosition(
mousePosition.x,
mousePosition.y,
mousePosition.width,
mousePosition.height,
this.panZoomStack
).getPosition();
};
// Utility function to get the mouse position (in x,y
// pixel coordinates in the canvas area) from a mouse
// event object.
SubPlot.prototype.toMousePosition = function ($event) {
var bounds = this.subPlotBounds;
return {
x: $event.clientX - bounds.left,
y: $event.clientY - bounds.top,
width: bounds.width,
height: bounds.height
};
};
// Convert a domain-range position to a displayable
// position. This will subtract the domain offset, which
// is used to bias domain values to minimize loss-of-precision
// associated with conversion to a 32-bit floating point
// format (which is needed in the chart area itself, by WebGL.)
SubPlot.prototype.toDisplayable = function (position) {
return [position[0] - this.domainOffset, position[1]];
};
// Update the current hover coordinates
SubPlot.prototype.updateHoverCoordinates = function () {
var formatter = this.formatter;
// Utility, for map/forEach loops. Index 0 is domain,
// index 1 is range.
function formatValue(v, i) {
return i ?
formatter.formatRangeValue(v) :
formatter.formatDomainValue(v);
}
this.hoverCoordinates = this.mousePosition &&
this.mousePositionToDomainRange(this.mousePosition)
.map(formatValue)
.filter(isNonEmpty)
.join(", ");
};
// Update the drawable marquee area to reflect current
// mouse position (or don't show it at all, if no marquee
// zoom is in progress)
SubPlot.prototype.updateMarqueeBox = function () {
// Express this as a box in the draw object, which
// is passed to an mct-chart in the template for rendering.
this.draw.boxes = this.marqueeStart ?
[{
start: this.toDisplayable(
this.mousePositionToDomainRange(this.marqueeStart)
),
end: this.toDisplayable(
this.mousePositionToDomainRange(this.mousePosition)
),
color: [1, 1, 1, 0.5]
}] : undefined;
};
// Update the bounds (origin and dimensions) of the drawing area.
SubPlot.prototype.updateDrawingBounds = function () {
var panZoom = this.panZoomStack.getPanZoom();
// Communicate pan-zoom state from stack to the draw object
// which is passed to mct-chart in the template.
this.draw.dimensions = panZoom.dimensions;
this.draw.origin = [
panZoom.origin[0] - this.domainOffset,
panZoom.origin[1]
];
};
// Update tick marks in scope.
SubPlot.prototype.updateTicks = function () {
var tickGenerator =
new PlotTickGenerator(this.panZoomStack, this.formatter);
this.domainTicks =
tickGenerator.generateDomainTicks(DOMAIN_TICKS);
this.rangeTicks =
tickGenerator.generateRangeTicks(RANGE_TICKS);
};
SubPlot.prototype.updatePan = function () {
var start, current, delta, nextOrigin;
// Clear the previous panning pan-zoom state
this.panZoomStack.popPanZoom();
// Calculate what the new resulting pan-zoom should be
start = this.mousePositionToDomainRange(
this.panStart,
this.panZoomStack
);
current = this.mousePositionToDomainRange(
this.mousePosition,
this.panZoomStack
);
delta = [current[0] - start[0], current[1] - start[1]];
nextOrigin = [
this.panStartBounds.origin[0] - delta[0],
this.panStartBounds.origin[1] - delta[1]
];
// ...and push a new one at the current mouse position
this.panZoomStack
.pushPanZoom(nextOrigin, this.panStartBounds.dimensions);
};
/**
* Get the set of domain objects which are being
* represented in this sub-plot.
* @returns {DomainObject[]} the domain objects which
* will have data plotted in this sub-plot
*/
SubPlot.prototype.getTelemetryObjects = function () {
return this.telemetryObjects;
};
/**
* Get ticks mark information appropriate for using in the
* template for this sub-plot's domain axis, as prepared
* by the PlotTickGenerator.
* @returns {Array} tick marks for the domain axis
*/
SubPlot.prototype.getDomainTicks = function () {
return this.domainTicks;
};
/**
* Get ticks mark information appropriate for using in the
* template for this sub-plot's range axis, as prepared
* by the PlotTickGenerator.
* @returns {Array} tick marks for the range axis
*/
SubPlot.prototype.getRangeTicks = function () {
return this.rangeTicks;
};
/**
* Get the drawing object associated with this sub-plot;
* this object will be passed to the mct-chart in which
* this sub-plot's lines will be plotted, as its "draw"
* attribute, and should have the same internal format
* expected by that directive.
* @return {object} the drawing object
*/
SubPlot.prototype.getDrawingObject = function () {
return this.draw;
};
/**
* Get the coordinates (as displayable text) for the
* current mouse position.
* @returns {string[]} the displayable domain and range
* coordinates over which the mouse is hovered
*/
SubPlot.prototype.getHoverCoordinates = function () {
return this.hoverCoordinates;
};
/**
* Handle mouse movement over the chart area.
* @param $event the mouse event
* @memberof platform/features/plot.SubPlot#
*/
SubPlot.prototype.hover = function ($event) {
this.hovering = true;
this.subPlotBounds = $event.target.getBoundingClientRect();
this.mousePosition = this.toMousePosition($event);
//If there is a domain to display, show hover coordinates, otherwise hover coordinates are meaningless
if (this.hasDomainData()) {
this.updateHoverCoordinates();
}
if (this.marqueeStart) {
this.updateMarqueeBox();
}
if (this.panStart) {
this.updatePan();
this.updateDrawingBounds();
this.updateTicks();
}
};
/**
* Continue a previously-start pan or zoom gesture.
* @param $event the mouse event
* @memberof platform/features/plot.SubPlot#
*/
SubPlot.prototype.continueDrag = function ($event) {
this.mousePosition = this.toMousePosition($event);
if (this.marqueeStart) {
this.updateMarqueeBox();
}
if (this.panStart) {
this.updatePan();
this.updateDrawingBounds();
this.updateTicks();
}
};
/**
* Initiate a marquee zoom action.
* @param $event the mouse event
*/
SubPlot.prototype.startDrag = function ($event) {
this.subPlotBounds = $event.target.getBoundingClientRect();
this.mousePosition = this.toMousePosition($event);
// Treat any modifier key as a pan
if ($event.altKey || $event.shiftKey || $event.ctrlKey) {
// Start panning
this.panStart = this.mousePosition;
this.panStartBounds = this.panZoomStack.getPanZoom();
// We're starting a pan, so add this back as a
// state on the stack; it will get replaced
// during the pan.
this.panZoomStack.pushPanZoom(
this.panStartBounds.origin,
this.panStartBounds.dimensions
);
$event.preventDefault();
} else {
// Start marquee zooming
this.marqueeStart = this.mousePosition;
this.updateMarqueeBox();
}
};
/**
* Complete a marquee zoom action.
* @param $event the mouse event
*/
SubPlot.prototype.endDrag = function ($event) {
var self = this;
// Perform a marquee zoom.
function marqueeZoom(start, end) {
// Determine what boundary is described by the marquee,
// in domain-range values. Use the minima for origin, so that
// it doesn't matter what direction the user marqueed in.
var a = self.mousePositionToDomainRange(start),
b = self.mousePositionToDomainRange(end),
origin = [
Math.min(a[0], b[0]),
Math.min(a[1], b[1])
],
dimensions = [
Math.max(a[0], b[0]) - origin[0],
Math.max(a[1], b[1]) - origin[1]
];
// Proceed with zoom if zoom dimensions are non zeros
if (!(dimensions[0] === 0 && dimensions[1] === 0)) {
// Push the new state onto the pan-zoom stack
self.panZoomStack.pushPanZoom(origin, dimensions);
// Make sure tick marks reflect new bounds
self.updateTicks();
}
}
this.mousePosition = this.toMousePosition($event);
this.subPlotBounds = undefined;
if (this.marqueeStart) {
marqueeZoom(this.marqueeStart, this.mousePosition);
this.marqueeStart = undefined;
this.updateMarqueeBox();
this.updateDrawingBounds();
this.updateTicks();
}
if (this.panStart) {
// End panning
this.panStart = undefined;
this.panStartBounds = undefined;
}
};
/**
* Update the drawing bounds, marquee box, and
* tick marks for this subplot.
*/
SubPlot.prototype.update = function () {
this.updateDrawingBounds();
this.updateMarqueeBox();
this.updateTicks();
};
/**
* Set the domain offset associated with this sub-plot.
* A domain offset is subtracted from all domain
* before lines are drawn to avoid artifacts associated
* with the use of 32-bit floats when domain values
* are often timestamps (due to insufficient precision.)
* A SubPlot will be drawing boxes (for marquee zoom) in
* the same offset coordinate space, so it needs to know
* the value of this to position that marquee box
* correctly.
* @param {number} value the domain offset
*/
SubPlot.prototype.setDomainOffset = function (value) {
this.domainOffset = value;
};
/**
* When used with no argument, check whether or not the user
* is currently hovering over this subplot. When used with
* an argument, set that state.
* @param {boolean} [state] the new hovering state
* @returns {boolean} the hovering state
*/
SubPlot.prototype.isHovering = function (state) {
if (state !== undefined) {
this.hovering = state;
}
return this.hovering;
};
return SubPlot;
}
);

View File

@@ -1,134 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
define(
[],
function () {
/**
* A PlotAxis provides a template-ready set of options
* for the domain or range axis, sufficient to populate
* selectors.
*
* @memberof platform/features/plot
* @constructor
* @param {string} axisType the field in metadatas to
* look at for axis options; usually one of
* "domains" or "ranges"
* @param {object[]} metadatas metadata objects, as
* returned by the `getMetadata()` method of
* a `telemetry` capability.
* @param {object} defaultValue the value to use for the
* active state in the event that no options are
* found; should contain "name" and "key" at
* minimum.
*
*/
function PlotAxis(axisType, metadatas, defaultValue) {
this.axisType = axisType;
this.defaultValue = defaultValue;
this.optionKeys = {};
/**
* The currently chosen option for this axis. An
* initial value is provided; this will be updated
* directly form the plot template.
* @memberof platform/features/plot.PlotAxis#
*/
this.active = defaultValue;
/**
* The set of options applicable for this axis;
* an array of objects, where each object contains a
* "key" field and a "name" field (for machine- and
* human-readable names respectively)
* @memberof platform/features/plot.PlotAxis#
*/
this.options = [];
// Initialize options from metadata objects
this.updateMetadata(metadatas);
}
/**
* Update axis options to reflect current metadata.
* @param {TelemetryMetadata[]} metadata objects describing
* applicable telemetry
*/
PlotAxis.prototype.updateMetadata = function (metadatas) {
var axisType = this.axisType,
optionKeys = this.optionKeys,
newOptions = {},
toAdd = [];
function isValid(option) {
return option && optionKeys[option.key];
}
metadatas.forEach(function (m) {
(m[axisType] || []).forEach(function (option) {
var key = option.key;
if (!optionKeys[key] && !newOptions[key]) {
toAdd.push(option);
}
newOptions[key] = true;
});
});
optionKeys = this.optionKeys = newOptions;
// General approach here is to avoid changing object
// instances unless something has really changed, since
// Angular is watching; don't want to trigger extra digests.
if (!this.options.every(isValid)) {
this.options = this.options.filter(isValid);
}
if (toAdd.length > 0) {
this.options = this.options.concat(toAdd);
}
if (!isValid(this.active)) {
this.active = this.options[0] || this.defaultValue;
}
};
/**
* Change the domain/range selection for this axis. If the
* provided `key` is not recognized as an option, no change
* will occur.
* @param {string} key the identifier for the domain/range
*/
PlotAxis.prototype.chooseOption = function (key) {
var self = this;
this.options.forEach(function (option) {
if (option.key === key) {
self.active = option;
}
});
};
return PlotAxis;
}
);

View File

@@ -1,78 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
define(
[],
function () {
/**
* Tracks the limit state of telemetry objects being plotted.
* @memberof platform/features/plot
* @constructor
* @param {platform/telemetry.TelemetryHandle} handle the handle
* to telemetry access
* @param {string} range the key to use when looking up range values
*/
function PlotLimitTracker(handle, range) {
this.handle = handle;
this.range = range;
this.legendClasses = {};
}
/**
* Update limit states to reflect the latest data.
*/
PlotLimitTracker.prototype.update = function () {
var legendClasses = {},
range = this.range,
handle = this.handle;
function updateLimit(telemetryObject) {
var limit = telemetryObject.getCapability('limit'),
datum = handle.getDatum(telemetryObject);
if (limit && datum) {
legendClasses[telemetryObject.getId()] =
(limit.evaluate(datum, range) || {}).cssClass;
}
}
handle.getTelemetryObjects().forEach(updateLimit);
this.legendClasses = legendClasses;
};
/**
* Get the CSS class associated with any limit violations for this
* telemetry object.
* @param {DomainObject} domainObject the telemetry object to check
* @returns {string} the CSS class name, if any
*/
PlotLimitTracker.prototype.getLegendClass = function (domainObject) {
var id = domainObject && domainObject.getId();
return id && this.legendClasses[id];
};
return PlotLimitTracker;
}
);

View File

@@ -1,118 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
define(
['./PlotSeriesWindow'],
function (PlotSeriesWindow) {
/**
* Represents a single line or trace of a plot.
* @param {{PlotLineBuffer}} buffer the plot buffer
* @constructor
*/
function PlotLine(buffer) {
this.buffer = buffer;
}
/**
* Add a point to this plot line.
* @param {number} domainValue the domain value
* @param {number} rangeValue the range value
*/
PlotLine.prototype.addPoint = function (domainValue, rangeValue) {
var buffer = this.buffer,
index;
// Make sure we got real/useful values here...
if (domainValue !== undefined && rangeValue !== undefined) {
index = buffer.findInsertionIndex(domainValue);
// Already in the buffer? Skip insertion
if (index < 0) {
return;
}
// Insert the point
if (!buffer.insertPoint(domainValue, rangeValue, index)) {
// If insertion failed, trim from the beginning...
buffer.trim(1);
// ...and try again.
buffer.insertPoint(domainValue, rangeValue, index);
}
}
};
/**
* Add a series of telemetry data to this plot line.
* @param {TelemetrySeries} series the data series
* @param {string} [domain] the key indicating which domain
* to use when looking up data from this series
* @param {string} [range] the key indicating which range
* to use when looking up data from this series
*/
PlotLine.prototype.addSeries = function (series, domain, range) {
var buffer = this.buffer;
// Insert a time-windowed data series into the buffer
function insertSeriesWindow(seriesWindow) {
var count = seriesWindow.getPointCount();
function doInsert() {
var firstTimestamp = seriesWindow.getDomainValue(0),
lastTimestamp = seriesWindow.getDomainValue(count - 1),
startIndex = buffer.findInsertionIndex(firstTimestamp),
endIndex = buffer.findInsertionIndex(lastTimestamp);
// Does the whole series fit in between two adjacent indexes?
if ((startIndex === endIndex) && startIndex > -1) {
// Insert it in between
buffer.insert(seriesWindow, startIndex);
} else {
// Split it up, and add the two halves
seriesWindow.split().forEach(insertSeriesWindow);
}
}
// Only insert if there are points to insert
if (count > 0) {
doInsert();
}
}
// Should try to add via insertion if a
// clear insertion point is available;
// if not, should split and add each half.
// Insertion operation also needs to factor out
// redundant timestamps, for overlapping data
insertSeriesWindow(new PlotSeriesWindow(
series,
domain,
range,
0,
series.getPointCount()
));
};
return PlotLine;
}
);

View File

@@ -1,268 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
define(
[],
function () {
/**
* Contains the buffer used to draw a plot.
* @param {number} domainOffset number to subtract from domain values
* @param {number} initialSize initial buffer size
* @param {number} maxSize maximum buffer size
* @memberof platform/features/plot
* @constructor
*/
function PlotLineBuffer(domainOffset, initialSize, maxSize) {
this.buffer = new Float32Array(initialSize * 2);
this.rangeExtrema =
[Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY];
this.length = 0;
this.domainOffset = domainOffset;
this.initialSize = initialSize;
this.maxSize = maxSize;
}
// Binary search for an insertion index
PlotLineBuffer.prototype.binSearch = function (value, min, max) {
var mid = Math.floor((min + max) / 2),
found = this.buffer[mid * 2];
// On collisions, insert at same index
if (found === value) {
return mid;
}
// Otherwise, if we're down to a single index,
// we've found our insertion point
if (min >= max) {
// Compare the found timestamp with the search
// value to decide if we'll insert after or before.
return min + ((found < value) ? 1 : 0);
}
// Finally, do the recursive step
if (found < value) {
return this.binSearch(value, mid + 1, max);
} else {
return this.binSearch(value, min, mid - 1);
}
};
// Increase the size of the buffer
PlotLineBuffer.prototype.doubleBufferSize = function () {
var sz = Math.min(this.maxSize * 2, this.buffer.length * 2),
canDouble = sz > this.buffer.length,
doubled = canDouble && new Float32Array(sz);
if (canDouble) {
doubled.set(this.buffer); // Copy contents of original
this.buffer = doubled;
}
return canDouble;
};
// Decrease the size of the buffer
PlotLineBuffer.prototype.halveBufferSize = function () {
var sz = Math.max(this.initialSize * 2, this.buffer.length / 2),
canHalve = sz < this.buffer.length;
if (canHalve) {
this.buffer = new Float32Array(this.buffer.subarray(0, sz));
}
return canHalve;
};
// Set a value in the buffer
PlotLineBuffer.prototype.setValue = function (index, domainValue, rangeValue) {
this.buffer[index * 2] = domainValue - this.domainOffset;
this.buffer[index * 2 + 1] = rangeValue;
// Track min/max of range values (min/max for
// domain values can be read directly from buffer)
this.rangeExtrema[0] = Math.min(this.rangeExtrema[0], rangeValue);
this.rangeExtrema[1] = Math.max(this.rangeExtrema[1], rangeValue);
};
/**
* Get the WebGL-displayable buffer of points to plot.
* @returns {Float32Array} displayable buffer for this line
*/
PlotLineBuffer.prototype.getBuffer = function () {
return this.buffer;
};
/**
* Get the number of points stored in this buffer.
* @returns {number} the number of points stored
*/
PlotLineBuffer.prototype.getLength = function () {
return this.length;
};
/**
* Get the min/max range values that are currently in this
* buffer. Unlike range extrema, these will change as the
* buffer gets trimmed.
* @returns {number[]} min, max domain values
*/
PlotLineBuffer.prototype.getDomainExtrema = function () {
// Since these are ordered in the buffer, assume
// these are the values at the first and last index
return [
this.buffer[0] + this.domainOffset,
this.buffer[this.length * 2 - 2] + this.domainOffset
];
};
/**
* Get the min/max range values that have been observed for this
* buffer. Note that these values may have been trimmed out at
* some point.
* @returns {number[]} min, max range values
*/
PlotLineBuffer.prototype.getRangeExtrema = function () {
return this.rangeExtrema;
};
/**
* Remove values from this buffer.
* Normally, values are removed from the start
* of the buffer; a truthy value in the second argument
* will cause values to be removed from the end.
* @param {number} count number of values to remove
* @param {boolean} [fromEnd] true if the most recent
* values should be removed
*/
PlotLineBuffer.prototype.trim = function (count, fromEnd) {
// If we're removing values from the start...
if (!fromEnd) {
// ...do so by shifting buffer contents over
this.buffer.set(this.buffer.subarray(2 * count));
}
// Reduce used buffer size accordingly
this.length -= count;
// Finally, if less than half of the buffer is being
// used, free up some memory.
if (this.length < this.buffer.length / 4) {
this.halveBufferSize();
}
};
/**
* Insert data from the provided series at the specified
* index. If this would exceed the buffer's maximum capacity,
* this operation fails and the buffer is unchanged.
* @param {TelemetrySeries} series the series to insert
* @param {number} index the index at which to insert this
* series
* @returns {boolean} true if insertion succeeded; otherwise
* false
*/
PlotLineBuffer.prototype.insert = function (series, index) {
var sz = series.getPointCount(),
i;
// Don't allow append after the end; that doesn't make sense
index = Math.min(index, this.length);
// Resize if necessary
while (sz > ((this.buffer.length / 2) - this.length)) {
if (!this.doubleBufferSize()) {
// Can't make room for this, insertion fails
return false;
}
}
// Shift data over if necessary
if (index < this.length) {
this.buffer.set(
this.buffer.subarray(index * 2, this.length * 2),
(index + sz) * 2
);
}
// Insert data into the set
for (i = 0; i < sz; i += 1) {
this.setValue(
i + index,
series.getDomainValue(i),
series.getRangeValue(i)
);
}
// Increase the length
this.length += sz;
// Indicate that insertion was successful
return true;
};
/**
* Append a single data point.
* @memberof platform/features/plot.PlotLineBuffer#
*/
PlotLineBuffer.prototype.insertPoint = function (domainValue, rangeValue) {
// Ensure there is space for this point
if (this.length >= (this.buffer.length / 2)) {
if (!this.doubleBufferSize()) {
return false;
}
}
// Put the data in the buffer
this.setValue(this.length, domainValue, rangeValue);
// Update length
this.length += 1;
// Indicate that this was successful
return true;
};
/**
* Find an index for inserting data with this
* timestamp. The second argument indicates whether
* we are searching for insert-before or insert-after
* positions.
* Timestamps are meant to be unique, so if a collision
* occurs, this will return -1.
* @param {number} timestamp timestamp to insert
* @returns {number} the index for insertion (or -1)
*/
PlotLineBuffer.prototype.findInsertionIndex = function (timestamp) {
var value = timestamp - this.domainOffset;
// Handle empty buffer case and check for an
// append opportunity (which is most common case for
// real-time data so is optimized-for) before falling
// back to a binary search for the insertion point.
return (this.length < 1) ? 0 :
(value > this.buffer[this.length * 2 - 2]) ? this.length :
this.binSearch(value, 0, this.length - 1);
};
return PlotLineBuffer;
}
);

View File

@@ -1,133 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
/**
* Plot palette. Defines colors for various plot lines.
*/
define(
function () {
// Prepare different forms of the palette, since we wish to
// describe colors in several ways (as RGB 0-255, as
// RGB 0.0-1.0, or as stylesheet-appropriate #-prefixed colors).
var integerPalette = [
[0x20, 0xB2, 0xAA],
[0x9A, 0xCD, 0x32],
[0xFF, 0x8C, 0x00],
[0xD2, 0xB4, 0x8C],
[0x40, 0xE0, 0xD0],
[0x41, 0x69, 0xFF],
[0xFF, 0xD7, 0x00],
[0x6A, 0x5A, 0xCD],
[0xEE, 0x82, 0xEE],
[0xCC, 0x99, 0x66],
[0x99, 0xCC, 0xCC],
[0x66, 0xCC, 0x33],
[0xFF, 0xCC, 0x00],
[0xFF, 0x66, 0x33],
[0xCC, 0x66, 0xFF],
[0xFF, 0x00, 0x66],
[0xFF, 0xFF, 0x00],
[0x80, 0x00, 0x80],
[0x00, 0x86, 0x8B],
[0x00, 0x8A, 0x00],
[0xFF, 0x00, 0x00],
[0x00, 0x00, 0xFF],
[0xF5, 0xDE, 0xB3],
[0xBC, 0x8F, 0x8F],
[0x46, 0x82, 0xB4],
[0xFF, 0xAF, 0xAF],
[0x43, 0xCD, 0x80],
[0xCD, 0xC1, 0xC5],
[0xA0, 0x52, 0x2D],
[0x64, 0x95, 0xED]
], stringPalette = integerPalette.map(function (arr) {
// Convert to # notation for use in styles
return '#' + arr.map(function (c) {
return (c < 16 ? '0' : '') + c.toString(16);
}).join('');
}), floatPalette = integerPalette.map(function (arr) {
return arr.map(function (c) {
return c / 255.0;
}).concat([1]); // RGBA
});
/**
* PlotPalette allows a consistent set of colors to be retrieved
* by index, in various color formats. All PlotPalette methods are
* static, so there is no need for a constructor call; using
* this will simply return PlotPalette itself.
* @memberof platform/features/plot
* @constructor
*/
function PlotPalette() {
return PlotPalette;
}
/**
* Look up a color in the plot's palette, by index.
* This will be returned as a three element array of RGB
* values, as integers in the range of 0-255.
* @param {number} i the index of the color to look up
* @return {number[]} the color, as integer RGB values
*/
PlotPalette.getIntegerColor = function (i) {
return integerPalette[Math.floor(i) % integerPalette.length];
};
/**
* Look up a color in the plot's palette, by index.
* This will be returned as a three element array of RGB
* values, in the range of 0.0-1.0.
*
* This format is present specifically to support use with
* WebGL, which expects colors of that form.
*
* @param {number} i the index of the color to look up
* @return {number[]} the color, as floating-point RGB values
*/
PlotPalette.getFloatColor = function (i) {
return floatPalette[Math.floor(i) % floatPalette.length];
};
/**
* Look up a color in the plot's palette, by index.
* This will be returned as a string using #-prefixed
* six-digit RGB hex notation (e.g. #FF0000)
* See http://www.w3.org/TR/css3-color/#rgb-color.
*
* This format is useful for representing colors in in-line
* styles.
*
* @param {number} i the index of the color to look up
* @return {string} the color, as a style-friendly string
*/
PlotPalette.getStringColor = function (i) {
return stringPalette[Math.floor(i) % stringPalette.length];
};
return PlotPalette;
}
);

View File

@@ -1,141 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
define(
[],
function () {
/**
* The PlotPanZoomStack is responsible for maintaining the
* pan-zoom state of a plot (expressed as a boundary starting
* at an origin and extending to certain dimensions) in a
* stack, to support the back and unzoom buttons in plot controls.
*
* Dimensions and origins are here described each by two-element
* arrays, where the first element describes a value or quantity
* along the domain axis, and the second element describes the same
* along the range axis.
*
* @memberof platform/features/plot
* @constructor
* @param {number[]} origin the plot's origin, initially
* @param {number[]} dimensions the plot's dimensions, initially
*/
function PlotPanZoomStack(origin, dimensions) {
// Use constructor parameters as the stack's initial state
this.stack = [{ origin: origin, dimensions: dimensions }];
}
// Various functions which follow are simply wrappers for
// normal stack-like array methods, with the exception that
// they prevent undesired modification and enforce that this
// stack must remain non-empty.
// See JSDoc for specific methods below for more detail.
/**
* Get the current stack depth; that is, the number
* of items on the stack. A depth of one means that no
* panning or zooming relative to the base value has
* been applied.
* @returns {number} the depth of the stack
*/
PlotPanZoomStack.prototype.getDepth = function getDepth() {
return this.stack.length;
};
/**
* Push a new pan-zoom state onto the stack; this will
* become the active pan-zoom state.
* @param {number[]} origin the new origin
* @param {number[]} dimensions the new dimensions
*/
PlotPanZoomStack.prototype.pushPanZoom = function (origin, dimensions) {
this.stack.push({ origin: origin, dimensions: dimensions });
};
/**
* Pop a pan-zoom state from the stack. Whatever pan-zoom
* state was previously present will become current.
* If called when there is only one pan-zoom state on the
* stack, this acts as a no-op (that is, the lowest
* pan-zoom state on the stack cannot be popped, to ensure
* that some pan-zoom state is always available.)
*/
PlotPanZoomStack.prototype.popPanZoom = function popPanZoom() {
if (this.stack.length > 1) {
this.stack.pop();
}
};
/**
* Set the base pan-zoom state; that is, the state at the
* bottom of the stack. This allows the "unzoomed" state of
* a plot to be updated (e.g. as new data comes in) without
* interfering with the user's chosen zoom level.
* @param {number[]} origin the base origin
* @param {number[]} dimensions the base dimensions
* @memberof platform/features/plot.PlotPanZoomStack#
*/
PlotPanZoomStack.prototype.setBasePanZoom = function (origin, dimensions) {
this.stack[0] = { origin: origin, dimensions: dimensions };
};
/**
* Clear the pan-zoom stack down to its bottom element;
* in effect, pop all elements but the last, e.g. to remove
* any temporary user modifications to pan-zoom state.
*/
PlotPanZoomStack.prototype.clearPanZoom = function clearPanZoom() {
this.stack = [this.stack[0]];
};
/**
* Get the current pan-zoom state (the state at the top
* of the stack), expressed as an object with "origin" and
* "dimensions" fields.
* @returns {object} the current pan-zoom state
*/
PlotPanZoomStack.prototype.getPanZoom = function getPanZoom() {
return this.stack[this.stack.length - 1];
};
/**
* Get the current origin, as represented on the top of the
* stack.
* @returns {number[]} the current plot origin
*/
PlotPanZoomStack.prototype.getOrigin = function getOrigin() {
return this.getPanZoom().origin;
};
/**
* Get the current dimensions, as represented on the top of
* the stack.
* @returns {number[]} the current plot dimensions
*/
PlotPanZoomStack.prototype.getDimensions = function getDimensions() {
return this.getPanZoom().dimensions;
};
return PlotPanZoomStack;
}
);

Some files were not shown because too many files have changed in this diff Show More