Compare commits
	
		
			42 Commits
		
	
	
		
			include-e2
			...
			log-plots-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 9f1e38bacd | ||
|   | de8240e189 | ||
|   | c369b7332e | ||
|   | 504939d64a | ||
|   | 11c8a39758 | ||
|   | ff8708b269 | ||
|   | b6c74e784e | ||
|   | 224d20fba9 | ||
|   | 68c044fa1e | ||
|   | a6d86d470f | ||
|   | 6a01ce0c2d | ||
|   | 064fa80fdc | ||
|   | 08e84c9ad3 | ||
|   | f5d4e75c52 | ||
|   | 5f816179d6 | ||
|   | 817f8411f1 | ||
|   | 6a77fe6f03 | ||
|   | e7727bc9ad | ||
|   | 116aee9c5f | ||
|   | f7a0c030fa | ||
|   | c87c9f48fd | ||
|   | 0d6de7dfdb | ||
|   | f05e895e3a | ||
|   | 429ca484ed | ||
|   | 56a2e63600 | ||
|   | e6c2a118f7 | ||
|   | c917914183 | ||
|   | fb1d6c0187 | ||
|   | 3b42490883 | ||
|   | 5b756d3588 | ||
|   | 63e8fb53f8 | ||
|   | 72aea12f68 | ||
|   | c9d96565fa | ||
|   | fe447a0d4c | ||
|   | d21adc6f69 | ||
|   | 607acf9626 | ||
|   | 4251274174 | ||
|   | ffaeea3d31 | ||
|   | 84693b008e | ||
|   | 896571e20e | ||
|   | 4ff150caf4 | ||
|   | ddf45a18b0 | 
| @@ -22,7 +22,7 @@ | ||||
|     "cross-env": "7.0.3", | ||||
|     "css-loader": "4.0.0", | ||||
|     "d3-axis": "1.0.x", | ||||
|     "d3-scale": "1.0.x", | ||||
|     "d3-scale": "3.3.0", | ||||
|     "d3-selection": "1.3.x", | ||||
|     "eslint": "8.11.0", | ||||
|     "eslint-plugin-compat": "4.0.2", | ||||
|   | ||||
| @@ -30,8 +30,8 @@ | ||||
|         class="gl-plot-tick-wrapper" | ||||
|     > | ||||
|         <div | ||||
|             v-for="tick in ticks" | ||||
|             :key="tick.value" | ||||
|             v-for="(tick, i) in ticks" | ||||
|             :key="i" | ||||
|             class="gl-plot-tick gl-plot-x-tick-label" | ||||
|             :style="{ | ||||
|                 left: (100 * (tick.value - min) / interval) + '%' | ||||
| @@ -46,8 +46,8 @@ | ||||
|         class="gl-plot-tick-wrapper" | ||||
|     > | ||||
|         <div | ||||
|             v-for="tick in ticks" | ||||
|             :key="tick.value" | ||||
|             v-for="(tick, i) in ticks" | ||||
|             :key="i" | ||||
|             class="gl-plot-tick gl-plot-y-tick-label" | ||||
|             :style="{ top: (100 * (max - tick.value) / interval) + '%' }" | ||||
|             :title="tick.fullText || tick.text" | ||||
| @@ -59,8 +59,8 @@ | ||||
|     <!-- grid lines follow --> | ||||
|     <template v-if="position === 'right'"> | ||||
|         <div | ||||
|             v-for="tick in ticks" | ||||
|             :key="tick.value" | ||||
|             v-for="(tick, i) in ticks" | ||||
|             :key="i" | ||||
|             class="gl-plot-hash hash-v" | ||||
|             :style="{ | ||||
|                 right: (100 * (max - tick.value) / interval) + '%', | ||||
| @@ -71,8 +71,8 @@ | ||||
|     </template> | ||||
|     <template v-if="position === 'bottom'"> | ||||
|         <div | ||||
|             v-for="tick in ticks" | ||||
|             :key="tick.value" | ||||
|             v-for="(tick, i) in ticks" | ||||
|             :key="i" | ||||
|             class="gl-plot-hash hash-h" | ||||
|             :style="{ bottom: (100 * (tick.value - min) / interval) + '%', width: '100%' }" | ||||
|         > | ||||
| @@ -83,7 +83,7 @@ | ||||
|  | ||||
| <script> | ||||
| import eventHelpers from "./lib/eventHelpers"; | ||||
| import { ticks, getFormattedTicks } from "./tickUtils"; | ||||
| import { ticks, getLogTicks, getFormattedTicks } from "./tickUtils"; | ||||
| import configStore from "./configuration/ConfigStore"; | ||||
|  | ||||
| export default { | ||||
| @@ -96,6 +96,13 @@ export default { | ||||
|             }, | ||||
|             required: true | ||||
|         }, | ||||
|         // Make it a prop, then later we can allow user to change it via UI input | ||||
|         tickCount: { | ||||
|             type: Number, | ||||
|             default() { | ||||
|                 return 6; | ||||
|             } | ||||
|         }, | ||||
|         position: { | ||||
|             required: true, | ||||
|             type: String, | ||||
| @@ -118,7 +125,6 @@ export default { | ||||
|  | ||||
|         this.axis = this.getAxisFromConfig(); | ||||
|  | ||||
|         this.tickCount = 4; | ||||
|         this.tickUpdate = false; | ||||
|         this.listenTo(this.axis, 'change:displayRange', this.updateTicks, this); | ||||
|         this.listenTo(this.axis, 'change:format', this.updateTicks, this); | ||||
| @@ -184,7 +190,12 @@ export default { | ||||
|                 }, this); | ||||
|             } | ||||
|  | ||||
|             return ticks(range.min, range.max, number); | ||||
|             if (this.axisType === 'yAxis' && this.axis.get('logMode')) { | ||||
|                 return getLogTicks(range.min, range.max, number, 4); | ||||
|                 // return getLogTicks2(range.min, range.max, number); | ||||
|             } else { | ||||
|                 return ticks(range.min, range.max, number); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         updateTicksForceRegeneration() { | ||||
|   | ||||
| @@ -52,23 +52,33 @@ | ||||
|     </select> | ||||
|  | ||||
|     <mct-ticks | ||||
|         v-if="logMode === false" | ||||
|         :axis-type="'yAxis'" | ||||
|         class="gl-plot-ticks" | ||||
|         :position="'top'" | ||||
|         @plotTickWidth="onTickWidthChange" | ||||
|     /> | ||||
|     <d3-axis | ||||
|         v-if="bounds && logMode === true" | ||||
|         class="gl-plot-ticks" | ||||
|         :bounds="bounds" | ||||
|         :content-height="height" | ||||
|     /> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import MctTicks from "../MctTicks.vue"; | ||||
| import D3Axis from "../../../ui/components/d3-ticks/D3Axis.vue"; | ||||
| import configStore from "../configuration/ConfigStore"; | ||||
| import eventHelpers from "../lib/eventHelpers"; | ||||
|  | ||||
| export default { | ||||
|     components: { | ||||
|         MctTicks | ||||
|         MctTicks, | ||||
|         D3Axis | ||||
|     }, | ||||
|     inject: ['openmct', 'domainObject'], | ||||
|     inject: ['openmct', 'domainObject', 'path'], | ||||
|     props: { | ||||
|         singleSeries: { | ||||
|             type: Boolean, | ||||
| @@ -92,15 +102,53 @@ export default { | ||||
|     data() { | ||||
|         return { | ||||
|             yAxisLabel: 'none', | ||||
|             loaded: false | ||||
|             loaded: false, | ||||
|             bounds: undefined, | ||||
|             height: undefined, | ||||
|             logMode: false | ||||
|         }; | ||||
|     }, | ||||
|     mounted() { | ||||
|         eventHelpers.extend(this); | ||||
|         this.parentElement = this.$el.parentElement; | ||||
|         this.yAxis = this.getYAxisFromConfig(); | ||||
|         this.logMode = this.yAxis.get('logMode'); | ||||
|         this.setBoundsAndOptions(); | ||||
|         this.setDimensions = this.setDimensions.bind(this); | ||||
|         this.setDimensions(); | ||||
|         this.loaded = true; | ||||
|         this.setUpYAxisOptions(); | ||||
|         this.listenTo(this.yAxis, 'change:displayRange', this.setBounds); | ||||
|         this.listenTo(this.yAxis, 'change:logMode', this.changeToLogTicks); | ||||
|         this.plotResizeObserver = new ResizeObserver(this.setDimensions); | ||||
|         this.plotResizeObserver.observe(this.parentElement); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         if (this.plotResizeObserver) { | ||||
|             this.plotResizeObserver.unobserve(this.parentElement); | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         setDimensions() { | ||||
|             //TODO: Handle resizing | ||||
|             const dimensions = this.parentElement.getBoundingClientRect(); | ||||
|             this.height = Math.round(dimensions.height); | ||||
|         }, | ||||
|         setBoundsAndOptions() { | ||||
|             this.setBounds(); | ||||
|             this.setUpYAxisOptions(); | ||||
|         }, | ||||
|         setBounds() { | ||||
|             const bounds = this.yAxis.get('displayRange'); | ||||
|             if (bounds) { | ||||
|                 this.bounds = { | ||||
|                     start: bounds.min, | ||||
|                     end: bounds.max | ||||
|                 }; | ||||
|             } | ||||
|         }, | ||||
|         changeToLogTicks(logMode) { | ||||
|             this.logMode = logMode; | ||||
|         }, | ||||
|         getYAxisFromConfig() { | ||||
|             const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier); | ||||
|             let config = configStore.get(configId); | ||||
|   | ||||
| @@ -23,7 +23,7 @@ | ||||
| import MCTChartSeriesElement from './MCTChartSeriesElement'; | ||||
|  | ||||
| export default class MCTChartLineStepAfter extends MCTChartSeriesElement { | ||||
|     removePoint(point, index, count) { | ||||
|     removePoint(index) { | ||||
|         if (index > 0 && index / 2 < this.count) { | ||||
|             this.buffer[index + 1] = this.buffer[index - 1]; | ||||
|         } | ||||
|   | ||||
| @@ -85,11 +85,10 @@ export default class MCTChartSeriesElement { | ||||
|  | ||||
|         this.removeSegments(removalPoint, vertexCount); | ||||
|  | ||||
|         this.removePoint( | ||||
|             this.makePoint(point, series), | ||||
|             removalPoint, | ||||
|             vertexCount | ||||
|         ); | ||||
|         // TODO useless makePoint call? | ||||
|         this.makePoint(point, series); | ||||
|         this.removePoint(removalPoint); | ||||
|  | ||||
|         this.count -= (vertexCount / 2); | ||||
|     } | ||||
|  | ||||
| @@ -109,11 +108,7 @@ export default class MCTChartSeriesElement { | ||||
|         const insertionPoint = this.startIndexForPointAtIndex(index); | ||||
|         this.growIfNeeded(pointsRequired); | ||||
|         this.makeInsertionPoint(insertionPoint, pointsRequired); | ||||
|         this.addPoint( | ||||
|             this.makePoint(point, series), | ||||
|             insertionPoint, | ||||
|             pointsRequired | ||||
|         ); | ||||
|         this.addPoint(this.makePoint(point, series), insertionPoint); | ||||
|         this.count += (pointsRequired / 2); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -279,7 +279,7 @@ export default { | ||||
|             // Have to throw away the old canvas elements and replace with new | ||||
|             // canvas elements in order to get new drawing contexts. | ||||
|             const div = document.createElement('div'); | ||||
|             div.innerHTML = this.canvasTemplate + this.canvasTemplate; | ||||
|             div.innerHTML = this.TEMPLATE; | ||||
|             const mainCanvas = div.querySelectorAll("canvas")[1]; | ||||
|             const overlayCanvas = div.querySelectorAll("canvas")[0]; | ||||
|             this.canvas.parentNode.replaceChild(mainCanvas, this.canvas); | ||||
|   | ||||
| @@ -71,6 +71,7 @@ export default class Model extends EventEmitter { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @abstract | ||||
|      * @param {ModelOptions<T, O>} options | ||||
|      */ | ||||
|     initialize(options) { | ||||
|   | ||||
| @@ -23,6 +23,7 @@ import _ from 'lodash'; | ||||
| import Model from "./Model"; | ||||
| import { MARKER_SHAPES } from '../draw/MarkerShapes'; | ||||
| import configStore from "../configuration/ConfigStore"; | ||||
| import { symlog } from '../mathUtils'; | ||||
|  | ||||
| /** | ||||
|  * Plot series handle interpreting telemetry metadata for a single telemetry | ||||
| @@ -63,6 +64,8 @@ import configStore from "../configuration/ConfigStore"; | ||||
|  * @extends {Model<PlotSeriesModelType, PlotSeriesModelOptions>} | ||||
|  */ | ||||
| export default class PlotSeries extends Model { | ||||
|     logMode = false; | ||||
|  | ||||
|     /** | ||||
|      @param {import('./Model').ModelOptions<PlotSeriesModelType, PlotSeriesModelOptions>} options | ||||
|      */ | ||||
| @@ -70,6 +73,8 @@ export default class PlotSeries extends Model { | ||||
|  | ||||
|         super(options); | ||||
|  | ||||
|         this.logMode = options.collection.plot.model.yAxis.logMode; | ||||
|  | ||||
|         this.listenTo(this, 'change:xKey', this.onXKeyChange, this); | ||||
|         this.listenTo(this, 'change:yKey', this.onYKeyChange, this); | ||||
|         this.persistedConfig = options.persistedConfig; | ||||
| @@ -229,6 +234,7 @@ export default class PlotSeries extends Model { | ||||
|             this.getXVal = format.parse.bind(format); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Update y formatter on change, default to stepAfter interpolation if | ||||
|      * y range is an enumeration. | ||||
| @@ -251,7 +257,12 @@ export default class PlotSeries extends Model { | ||||
|             return this.limitEvaluator.evaluate(datum, valueMetadata); | ||||
|         }.bind(this); | ||||
|         const format = this.formats[newKey]; | ||||
|         this.getYVal = format.parse.bind(format); | ||||
|         this.getYVal = (value) => { | ||||
|             const scale = 1; // TODO get from UI, positive number above 0 | ||||
|             const y = format.parse(value); | ||||
|  | ||||
|             return this.logMode ? scale * symlog(y, 10) : y; | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     formatX(point) { | ||||
| @@ -519,7 +530,8 @@ export default class PlotSeries extends Model { | ||||
|  | ||||
|     /** | ||||
|      * Update the series data with the given value. | ||||
|      * @returns {Array<{ | ||||
|      * This return type definition is totally wrong, only covers sinwave generator. It needs to be generic. | ||||
|      * @return-example {Array<{ | ||||
|             cos: number | ||||
|             sin: number | ||||
|             mctLimitState: { | ||||
|   | ||||
| @@ -19,7 +19,7 @@ | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| import _ from 'lodash'; | ||||
| import { antisymlog, symlog } from '../mathUtils'; | ||||
| import Model from './Model'; | ||||
|  | ||||
| /** | ||||
| @@ -31,7 +31,7 @@ import Model from './Model'; | ||||
|  * | ||||
|  * `autoscale`: boolean, whether or not to autoscale. | ||||
|  * `autoscalePadding`: float, percent of padding to display in plots. | ||||
|  * `displayRange`: the current display range for the x Axis. | ||||
|  * `displayRange`: the current display range for the axis. | ||||
|  * `format`: the formatter for the axis. | ||||
|  * `frozen`: boolean, if true, displayRange will not be updated automatically. | ||||
|  *           Used to temporarily disable automatic updates during user interaction. | ||||
| @@ -54,6 +54,7 @@ export default class YAxisModel extends Model { | ||||
|         this.listenTo(this, 'change:stats', this.calculateAutoscaleExtents, this); | ||||
|         this.listenTo(this, 'change:autoscale', this.toggleAutoscale, this); | ||||
|         this.listenTo(this, 'change:autoscalePadding', this.updatePadding, this); | ||||
|         this.listenTo(this, 'change:logMode', this.onLogModeChange, this); | ||||
|         this.listenTo(this, 'change:frozen', this.toggleFreeze, this); | ||||
|         this.listenTo(this, 'change:range', this.updateDisplayRange, this); | ||||
|         this.updateDisplayRange(this.get('range')); | ||||
| @@ -173,13 +174,38 @@ export default class YAxisModel extends Model { | ||||
|             this.set('displayRange', this.get('range')); | ||||
|         } | ||||
|     } | ||||
|     /** @param {boolean} logMode */ | ||||
|     onLogModeChange(logMode) { | ||||
|         const range = this.get('displayRange'); | ||||
|         const scale = 1; // TODO get from UI, positive number above 0 | ||||
|  | ||||
|         if (logMode) { | ||||
|             range.min = scale * symlog(range.min, 10); | ||||
|             range.max = scale * symlog(range.max, 10); | ||||
|         } else { | ||||
|             range.min = antisymlog(range.min / scale, 10); | ||||
|             range.max = antisymlog(range.max / scale, 10); | ||||
|         } | ||||
|  | ||||
|         this.set('displayRange', range); | ||||
|  | ||||
|         this.resetSeries(); | ||||
|     } | ||||
|     resetSeries() { | ||||
|         this.plot.series.forEach((plotSeries) => { | ||||
|             plotSeries.logMode = this.get('logMode'); | ||||
|             plotSeries.reset(plotSeries.getSeriesData()); | ||||
|         }); | ||||
|         // Update the series collection labels and formatting | ||||
|         this.updateFromSeries(this.seriesCollection); | ||||
|     } | ||||
|     /** | ||||
|      * Update yAxis format, values, and label from known series. | ||||
|      * @param {import('./SeriesCollection').default} seriesCollection | ||||
|      */ | ||||
|     updateFromSeries(seriesCollection) { | ||||
|         const plotModel = this.plot.get('domainObject'); | ||||
|         const label = _.get(plotModel, 'configuration.yAxis.label'); | ||||
|         const label = plotModel.configuration?.yAxis?.label; | ||||
|         const sampleSeries = seriesCollection.first(); | ||||
|         if (!sampleSeries) { | ||||
|             if (!label) { | ||||
| @@ -192,7 +218,14 @@ export default class YAxisModel extends Model { | ||||
|         const yKey = sampleSeries.get('yKey'); | ||||
|         const yMetadata = sampleSeries.metadata.value(yKey); | ||||
|         const yFormat = sampleSeries.formats[yKey]; | ||||
|         this.set('format', yFormat.format.bind(yFormat)); | ||||
|         const scale = 1; // TODO get from UI, positive number above 0 | ||||
|  | ||||
|         if (this.get('logMode')) { | ||||
|             this.set('format', (n) => yFormat.format(antisymlog(n / scale, 10))); | ||||
|         } else { | ||||
|             this.set('format', (n) => yFormat.format(n)); | ||||
|         } | ||||
|  | ||||
|         this.set('values', yMetadata.values); | ||||
|         if (!label) { | ||||
|             const labelName = seriesCollection.map(function (s) { | ||||
| @@ -246,6 +279,7 @@ export default class YAxisModel extends Model { | ||||
|         return { | ||||
|             frozen: false, | ||||
|             autoscale: true, | ||||
|             logMode: options.model?.logMode ?? false, | ||||
|             autoscalePadding: 0.1 | ||||
|         }; | ||||
|     } | ||||
| @@ -256,6 +290,7 @@ export default class YAxisModel extends Model { | ||||
| /** | ||||
| @typedef {import('./XAxisModel').AxisModelType & { | ||||
|     autoscale: boolean | ||||
|     logMode: boolean | ||||
|     autoscalePadding: number | ||||
|     stats: import('./XAxisModel').NumberRange | ||||
|     values: Array<TODO> | ||||
|   | ||||
| @@ -48,11 +48,19 @@ | ||||
|             <li class="grid-row"> | ||||
|                 <div | ||||
|                     class="grid-cell label" | ||||
|                     title="Automatically scale the Y axis to keep all values in view." | ||||
|                 >Autoscale</div> | ||||
|                     title="Enable log mode." | ||||
|                 >Log mode</div> | ||||
|                 <div class="grid-cell value"> | ||||
|                     {{ autoscale ? "Enabled: " : "Disabled" }} | ||||
|                     {{ autoscale ? autoscalePadding : "" }} | ||||
|                     {{ logMode ? "Enabled" : "Disabled" }} | ||||
|                 </div> | ||||
|             </li> | ||||
|             <li class="grid-row"> | ||||
|                 <div | ||||
|                     class="grid-cell label" | ||||
|                     title="Automatically scale the Y axis to keep all values in view." | ||||
|                 >Auto scale</div> | ||||
|                 <div class="grid-cell value"> | ||||
|                     {{ autoscale ? "Enabled: " + autoscalePadding : "Disabled" }} | ||||
|                 </div> | ||||
|             </li> | ||||
|             <li | ||||
| @@ -142,6 +150,7 @@ export default { | ||||
|             config: {}, | ||||
|             label: '', | ||||
|             autoscale: '', | ||||
|             logMode: false, | ||||
|             autoscalePadding: '', | ||||
|             rangeMin: '', | ||||
|             rangeMax: '', | ||||
| @@ -172,6 +181,7 @@ export default { | ||||
|         initConfiguration() { | ||||
|             this.label = this.config.yAxis.get('label'); | ||||
|             this.autoscale = this.config.yAxis.get('autoscale'); | ||||
|             this.logMode = this.config.yAxis.get('logMode'); | ||||
|             this.autoscalePadding = this.config.yAxis.get('autoscalePadding'); | ||||
|             const range = this.config.yAxis.get('range'); | ||||
|             if (range) { | ||||
|   | ||||
| @@ -14,9 +14,22 @@ | ||||
|                 @change="updateForm('label')" | ||||
|             ></div> | ||||
|         </li> | ||||
|     </ul> | ||||
|     <ul class="l-inspector-part"> | ||||
|         <h2>Y Axis Scaling</h2> | ||||
|         <li class="grid-row"> | ||||
|             <div | ||||
|                 class="grid-cell label" | ||||
|                 title="Enable log mode." | ||||
|             > | ||||
|                 Log mode | ||||
|             </div> | ||||
|             <div class="grid-cell value"> | ||||
|                 <!-- eslint-disable-next-line vue/html-self-closing --> | ||||
|                 <input | ||||
|                     v-model="logMode" | ||||
|                     type="checkbox" | ||||
|                     @change="updateForm('logMode')" | ||||
|                 /> | ||||
|             </div> | ||||
|         </li> | ||||
|         <li class="grid-row"> | ||||
|             <div | ||||
|                 class="grid-cell label" | ||||
| @@ -88,7 +101,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { objectPath, validate, coerce } from "./formUtil"; | ||||
| import { objectPath, validate } from "./formUtil"; | ||||
| import _ from "lodash"; | ||||
|  | ||||
| export default { | ||||
| @@ -105,6 +118,7 @@ export default { | ||||
|         return { | ||||
|             label: '', | ||||
|             autoscale: '', | ||||
|             logMode: false, | ||||
|             autoscalePadding: '', | ||||
|             rangeMin: '', | ||||
|             rangeMax: '', | ||||
| @@ -117,23 +131,23 @@ export default { | ||||
|     }, | ||||
|     methods: { | ||||
|         initialize: function () { | ||||
|             this.fields = [ | ||||
|                 { | ||||
|                     modelProp: 'label', | ||||
|             this.fields = { | ||||
|                 label: { | ||||
|                     objectPath: 'configuration.yAxis.label' | ||||
|                 }, | ||||
|                 { | ||||
|                     modelProp: 'autoscale', | ||||
|                 autoscale: { | ||||
|                     coerce: Boolean, | ||||
|                     objectPath: 'configuration.yAxis.autoscale' | ||||
|                 }, | ||||
|                 { | ||||
|                     modelProp: 'autoscalePadding', | ||||
|                 autoscalePadding: { | ||||
|                     coerce: Number, | ||||
|                     objectPath: 'configuration.yAxis.autoscalePadding' | ||||
|                 }, | ||||
|                 { | ||||
|                     modelProp: 'range', | ||||
|                 logMode: { | ||||
|                     coerce: Boolean, | ||||
|                     objectPath: 'configuration.yAxis.logMode' | ||||
|                 }, | ||||
|                 range: { | ||||
|                     objectPath: 'configuration.yAxis.range', | ||||
|                     coerce: function coerceRange(range) { | ||||
|                         if (!range) { | ||||
| @@ -186,11 +200,12 @@ export default { | ||||
|                         return true; | ||||
|                     } | ||||
|                 } | ||||
|             ]; | ||||
|             }; | ||||
|         }, | ||||
|         initFormValues() { | ||||
|             this.label = this.yAxis.get('label'); | ||||
|             this.autoscale = this.yAxis.get('autoscale'); | ||||
|             this.logMode = this.yAxis.get('logMode'); | ||||
|             this.autoscalePadding = this.yAxis.get('autoscalePadding'); | ||||
|             const range = this.yAxis.get('range'); | ||||
|             if (!range) { | ||||
| @@ -212,8 +227,8 @@ export default { | ||||
|                 newVal = this[formKey]; | ||||
|             } | ||||
|  | ||||
|             const oldVal = this.yAxis.get(formKey); | ||||
|             const formField = this.fields.find((field) => field.modelProp === formKey); | ||||
|             let oldVal = this.yAxis.get(formKey); | ||||
|             const formField = this.fields[formKey]; | ||||
|  | ||||
|             const path = objectPath(formField.objectPath); | ||||
|             const validationResult = validate(newVal, this.yAxis, formField.validate); | ||||
| @@ -225,13 +240,17 @@ export default { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if (!_.isEqual(coerce(newVal, formField.coerce), coerce(oldVal, formField.coerce))) { | ||||
|                 this.yAxis.set(formKey, coerce(newVal, formField.coerce)); | ||||
|             newVal = formField.coerce?.(newVal) ?? newVal; | ||||
|             oldVal = formField.coerce?.(oldVal) ?? oldVal; | ||||
|  | ||||
|             if (!_.isEqual(newVal, oldVal)) { | ||||
|                 // TODO: Why do we mutate yAxis twice, once directly, once via objects.mutate? | ||||
|                 this.yAxis.set(formKey, newVal); | ||||
|                 if (path) { | ||||
|                     this.openmct.objects.mutate( | ||||
|                         this.domainObject, | ||||
|                         path(this.domainObject, this.yAxis), | ||||
|                         coerce(newVal, formField.coerce) | ||||
|                         newVal | ||||
|                     ); | ||||
|                 } | ||||
|             } | ||||
|   | ||||
							
								
								
									
										44
									
								
								src/plugins/plot/mathUtils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/plugins/plot/mathUtils.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| /** The natural number `e`. */ | ||||
| export const e = Math.exp(1); | ||||
|  | ||||
| /** | ||||
| Returns the logarithm of a number, using the given base or the natural number | ||||
| `e` as base if not specified. | ||||
| @param {number} n | ||||
| @param {number=} base log base, defaults to e | ||||
| */ | ||||
| export function log(n, base = e) { | ||||
|     if (base === e) { | ||||
|         return Math.log(n); | ||||
|     } | ||||
|  | ||||
|     return Math.log(n) / Math.log(base); | ||||
| } | ||||
|  | ||||
| /** | ||||
| Returns the inverse of the logarithm of a number, using the given base or the | ||||
| natural number `e` as base if not specified. | ||||
| @param {number} n | ||||
| @param {number=} base log base, defaults to e | ||||
| */ | ||||
| export function antilog(n, base = e) { | ||||
|     return Math.pow(base, n); | ||||
| } | ||||
|  | ||||
| /** | ||||
| A symmetric logarithm function. See https://github.com/nasa/openmct/issues/2297#issuecomment-1032914258 | ||||
| @param {number} n | ||||
| @param {number=} base log base, defaults to e | ||||
| */ | ||||
| export function symlog(n, base = e) { | ||||
|     return Math.sign(n) * log(Math.abs(n) + 1, base); | ||||
| } | ||||
|  | ||||
| /** | ||||
| An inverse symmetric logarithm function. See https://github.com/nasa/openmct/issues/2297#issuecomment-1032914258 | ||||
| @param {number} n | ||||
| @param {number=} base log base, defaults to e | ||||
| */ | ||||
| export function antisymlog(n, base = e) { | ||||
|     return Math.sign(n) * (antilog(Math.abs(n), base) - 1); | ||||
| } | ||||
| @@ -1,3 +1,5 @@ | ||||
| import { antisymlog, symlog } from "./mathUtils"; | ||||
|  | ||||
| const e10 = Math.sqrt(50); | ||||
| const e5 = Math.sqrt(10); | ||||
| const e2 = Math.sqrt(2); | ||||
| @@ -40,6 +42,50 @@ function getPrecision(step) { | ||||
|     return precision; | ||||
| } | ||||
|  | ||||
| export function getLogTicks(start, stop, mainTickCount = 8, secondaryTickCount = 6) { | ||||
|     // log()'ed values | ||||
|     const mainLogTicks = ticks(start, stop, mainTickCount); | ||||
|  | ||||
|     // original values | ||||
|     const scale = 1; // TODO get from UI, positive number above 0 | ||||
|     const mainTicks = mainLogTicks.map(n => antisymlog(n / scale, 10)); | ||||
|  | ||||
|     const result = []; | ||||
|  | ||||
|     let i = 0; | ||||
|     for (const logTick of mainLogTicks) { | ||||
|         result.push(logTick); | ||||
|  | ||||
|         if (i === mainLogTicks.length - 1) { | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         const tick = mainTicks[i]; | ||||
|         const nextTick = mainTicks[i + 1]; | ||||
|         const rangeBetweenMainTicks = nextTick - tick; | ||||
|  | ||||
|         const secondaryLogTicks = ticks( | ||||
|             tick + rangeBetweenMainTicks / (secondaryTickCount + 1), | ||||
|             nextTick - rangeBetweenMainTicks / (secondaryTickCount + 1), | ||||
|             secondaryTickCount - 2 | ||||
|         ) | ||||
|             .map(n => scale * symlog(n, 10)); | ||||
|  | ||||
|         result.push(...secondaryLogTicks); | ||||
|  | ||||
|         i++; | ||||
|     } | ||||
|  | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| export function getLogTicks2(start, stop, count = 8) { | ||||
|     const scale = 1; // TODO get from UI, positive number above 0 | ||||
|  | ||||
|     return ticks(antisymlog(start / scale, 10), antisymlog(stop / scale, 10), count) | ||||
|         .map(n => scale * symlog(n, 10)); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Linear tick generation from d3-array. | ||||
|  */ | ||||
|   | ||||
							
								
								
									
										118
									
								
								src/ui/components/d3-ticks/D3Axis.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/ui/components/d3-ticks/D3Axis.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| <template> | ||||
| <div | ||||
|     ref="axisHolder" | ||||
|     class="c-d3-axis" | ||||
| > | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import * as d3Selection from 'd3-selection'; | ||||
| import * as d3Axis from 'd3-axis'; | ||||
| import * as d3Scale from 'd3-scale'; | ||||
|  | ||||
| //TODO: UI direction needed for the following property values | ||||
| const PADDING = 1; | ||||
| const RESIZE_POLL_INTERVAL = 200; | ||||
| // const PIXELS_PER_TICK = 100; | ||||
| // const PIXELS_PER_TICK_WIDE = 200; | ||||
| //This offset needs to be re-considered | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct', 'domainObject'], | ||||
|     props: { | ||||
|         bounds: { | ||||
|             type: Object, | ||||
|             default() { | ||||
|                 return {}; | ||||
|             } | ||||
|         }, | ||||
|         contentHeight: { | ||||
|             type: Number, | ||||
|             default() { | ||||
|                 return 0; | ||||
|             } | ||||
|         }, | ||||
|         offset: { | ||||
|             type: Number, | ||||
|             default() { | ||||
|                 return 0; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|         bounds(newBounds) { | ||||
|             this.drawAxis(newBounds); | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.container = d3Selection.select(this.$refs.axisHolder); | ||||
|         this.svgElement = this.container.append("svg:svg"); | ||||
|         // draw x axis with labels. CSS is used to position them. | ||||
|         this.axisElement = this.svgElement.append("g") | ||||
|             .attr("class", "axis") | ||||
|             .attr('font-size', '1.3em') | ||||
|             .attr("transform", "translate(25,0)"); | ||||
|  | ||||
|         this.setDimensions(); | ||||
|         this.drawAxis(this.bounds); | ||||
|         this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL); | ||||
|     }, | ||||
|     destroyed() { | ||||
|         clearInterval(this.resizeTimer); | ||||
|     }, | ||||
|     methods: { | ||||
|         resize() { | ||||
|             if (this.$refs.axisHolder.clientHeight !== this.height) { | ||||
|                 this.setDimensions(); | ||||
|                 this.drawAxis(this.bounds); | ||||
|             } | ||||
|         }, | ||||
|         setDimensions() { | ||||
|             const axisHolder = this.$refs.axisHolder; | ||||
|             this.width = axisHolder.clientWidth; | ||||
|  | ||||
|             this.height = axisHolder.clientHeight; | ||||
|             this.offsetHeight = this.height - this.offset; | ||||
|  | ||||
|             this.svgElement.attr("width", this.width); | ||||
|             this.svgElement.attr("height", this.height); | ||||
|         }, | ||||
|         drawAxis(bounds) { | ||||
|             let viewBounds = Object.assign({}, bounds); | ||||
|  | ||||
|             this.setScale(viewBounds); | ||||
|             this.setAxis(viewBounds); | ||||
|             this.axisElement.call(this.xAxis); | ||||
|  | ||||
|         }, | ||||
|         setScale(bounds) { | ||||
|             if (!this.height) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             this.xScale = d3Scale.scaleSymlog(); | ||||
|             if (bounds.start < 0) { | ||||
|                 this.xScale.domain( | ||||
|                     [bounds.end, bounds.start] | ||||
|                 ); | ||||
|             } else { | ||||
|                 this.xScale.domain( | ||||
|                     [bounds.start, bounds.end] | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
|             this.xScale.range([PADDING, this.offsetHeight - PADDING * 2]); | ||||
|         }, | ||||
|         setAxis() { | ||||
|             this.xAxis = d3Axis.axisLeft(this.xScale); | ||||
|             // | ||||
|             // if (this.height > 1800) { | ||||
|             //     this.xAxis.ticks(this.offsetHeight / PIXELS_PER_TICK_WIDE); | ||||
|             // } else { | ||||
|             //     this.xAxis.ticks(this.offsetHeight / PIXELS_PER_TICK); | ||||
|             // } | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| </script> | ||||
							
								
								
									
										28
									
								
								src/ui/components/d3-ticks/d3-axis.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/ui/components/d3-ticks/d3-axis.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| .c-d3-axis { | ||||
|     $w: 30px; | ||||
|     height: $w; | ||||
|  | ||||
|     svg { | ||||
|         $lineC: rgba($colorBodyFg, 0.3) !important; | ||||
|         text-rendering: geometricPrecision; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|  | ||||
|         .domain { | ||||
|             stroke: $lineC; | ||||
|         } | ||||
|  | ||||
|         .tick { | ||||
|             line { | ||||
|                 stroke: $lineC; | ||||
|             } | ||||
|  | ||||
|             text { | ||||
|                 // Tick labels | ||||
|                 fill: $colorBodyFg; | ||||
|                 paint-order: stroke; | ||||
|                 font-weight: bold; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -44,7 +44,7 @@ const config = { | ||||
|             "bourbon": "bourbon.scss", | ||||
|             "plotly-basic": "plotly.js-basic-dist", | ||||
|             "plotly-gl2d": "plotly.js-gl2d-dist", | ||||
|             "d3-scale": path.join(__dirname, "node_modules/d3-scale/build/d3-scale.min.js"), | ||||
|             "d3-scale": path.join(__dirname, "node_modules/d3-scale/dist/d3-scale.min.js"), | ||||
|             "printj": path.join(__dirname, "node_modules/printj/dist/printj.min.js"), | ||||
|             "styles": path.join(__dirname, "src/styles"), | ||||
|             "MCT": path.join(__dirname, "src/MCT"), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user