BUG FIX: Removes unused code and also fixes bug that required clicking
[wrfxweb.git] / fdds / js / components / timeSeriesController.js
blob436dc436400a880aa69d28afb3b2fa52fb66de05
1 import { LayerController } from './layerController.js';
2 import { controllers } from './Controller.js';
3 import { simVars } from '../simVars.js';
4 import { map } from '../map.js';
5 import { Marker } from './timeSeriesMarker.js';
6 import { TimeSeriesButton } from './timeSeriesButton.js';
8 /** This class extends LayerController and adds to it functionality for generating a timeseries
9 * mapping a specific pixel value to its corresponing location on the colorbar over a certain time
10 * range in the simulation. Uses the layer that is on top. To use, double click on image to bring up
11 * a popup showing the value of the pixel at that particular time stamp as well as a button to
12 * generate a timeseries of the pixel over a specified range. The first time a timeseries is generated,
13 * since it will need to fetch every single image in the specified range it will take longer to load.
14 * Every subsequent timeseries generated for a layer will be significantly sped up.
16 export class TimeSeriesController extends LayerController {
17 constructor() {
18 super();
20 this.timeSeriesButton = new TimeSeriesButton();
21 this.timeSeriesButton.getButton().disabled = true;
22 this.loadingTimeSeries = false;
24 const container = this.querySelector('#layer-controller-container');
25 const timeSeriesDiv = document.createElement('div');
26 timeSeriesDiv.className = 'layer-group';
27 timeSeriesDiv.id = 'timeseries-layer-group';
28 const h4 = document.createElement('h4');
29 h4.innerText = 'Timeseries over all Markers';
30 timeSeriesDiv.appendChild(h4);
31 timeSeriesDiv.appendChild(this.timeSeriesButton);
32 container.appendChild(timeSeriesDiv);
35 connectedCallback() {
36 super.connectedCallback();
37 controllers.syncImageLoad.subscribe(() => {
38 this.updateCanvases()
39 });
40 const loadTimeSeries = () => {
41 document.body.classList.add('waiting');
42 var startDate = this.timeSeriesButton.getStartDate();
43 var endDate = this.timeSeriesButton.getEndDate();
44 this.generateTimeSeriesData(startDate, endDate);
46 this.timeSeriesButton.setGenerateLoader(loadTimeSeries);
47 const cancelTimeSeries = () => {
48 this.loadingTimeSeries = false;
49 document.body.classList.remove('waiting');
51 this.timeSeriesButton.setCancelLoader(cancelTimeSeries);
53 var markerController = controllers.timeSeriesMarkers;
54 markerController.subscribe(() => {
55 if (markerController.getValue().length == 0) {
56 this.timeSeriesButton.getButton().disabled = true;
58 }, markerController.removeEvent);
61 /** When domain is switched, remove all timeSeries markers. */
62 domainSwitch() {
63 this.timeSeriesButton.updateTimestamps();
64 super.domainSwitch();
65 var timeSeriesMarkers = controllers.timeSeriesMarkers.getValue();
66 for (var marker of timeSeriesMarkers) {
67 marker.hideMarkerInfo();
68 marker.marker.removeFrom(map);
70 controllers.timeSeriesMarkers.value = [];
71 this.timeSeriesButton.getButton().disabled = true;
73 var startDate = controllers.startDate.getValue();
74 this.timeSeriesButton.setStartDate(startDate);
76 var endDate = controllers.endDate.getValue();
77 this.timeSeriesButton.setEndDate(endDate);
80 /** If a colorbar is included in the new added layer, need to set it up for timeSeries:
81 * Update the current canvases and markers to point to the new layer and create a callback to
82 * build a new marker when the new layer is double clicked. */
83 handleOverlayadd(layerName) {
84 super.handleOverlayadd(layerName);
85 var currentDomain = controllers.currentDomain.value;
86 var layer = this.getLayer(currentDomain, layerName);
87 var img = layer.imageOverlay._image;
88 if (layer.hasColorbar) {
89 img.ondblclick = (e) => {
90 var latLon = map.mouseEventToLatLng(e);
91 e.stopPropagation(); // needed because otherwise immediately closes the popup
92 var xCoord = e.offsetX / img.width;
93 var yCoord = e.offsetY / img.height;
94 this.createNewMarker(latLon, xCoord, yCoord);
95 this.timeSeriesButton.getButton().disabled = false;
97 var timeSeriesMarkers = controllers.timeSeriesMarkers.getValue();
98 if (timeSeriesMarkers.length > 0) {
99 this.timeSeriesButton.getButton().disabled = false;
100 this.updateMarkers();
102 } else {
103 img.style.pointerEvents = 'none';
107 updateTime() {
108 super.updateTime();
109 this.updateMarkers();
112 createNewMarker(latLon, xCoord, yCoord) {
113 var marker = new Marker(latLon, [xCoord, yCoord]);
114 controllers.timeSeriesMarkers.add(marker);
115 this.updateMarker(marker);
118 /** Maps location of marker to position on colorbar for current layer image and colorbar.
119 * Updates the content of the marker. */
120 async updateMarker(marker) {
121 var rgb = [0, 0, 0];
122 var clrbarLocation = null;
123 if (simVars.displayedColorbar != null) {
124 var currentDomain = controllers.currentDomain.value;
125 var currentTimestamp = controllers.currentTimestamp.value;
126 var layer = this.getLayer(currentDomain, simVars.displayedColorbar);
128 rgb = await layer.rgbValueAtLocation(currentTimestamp, marker.imageCoords);
129 clrbarLocation = await layer.rgbValueToColorValue(currentTimestamp, rgb);
131 marker.getContent().setRGBValues(rgb, clrbarLocation);
134 updateMarkers() {
135 var timeSeriesMarkers = controllers.timeSeriesMarkers.getValue();
136 for (var marker of timeSeriesMarkers) {
137 this.updateMarker(marker);
141 /** When removing a layer, need to find the most recent colorbar and update the timeSeries canvases
142 * to that layer. */
143 handleOverlayRemove(layerName) {
144 super.handleOverlayRemove(layerName);
145 if (!simVars.displayedColorbar) {
146 this.timeSeriesButton.getButton().disabled = true;
148 this.updateMarkers();
151 async generateTimeSeriesInBatches(index = 0, batchSize, colorbarLayers, timeStamps, layerData, frameLoaded) {
152 var timeSeriesMarkers = controllers.timeSeriesMarkers.getValue();
153 var endIndex = Math.min(timeStamps.length, index + batchSize);
154 var dataType = this.timeSeriesButton.getDataType();
156 for (var colorbarLayer of colorbarLayers) {
157 var layerName = colorbarLayer.layerName;
158 var timeSeriesData = (layerName in layerData) ? layerData[layerName] : [];
159 for (var i=0; i < timeSeriesMarkers.length; i++) {
160 var marker = timeSeriesMarkers[i];
161 var timeSeriesMarker = marker.getContent();
162 var dataEntry = ({label: timeSeriesMarker.getName(), latLon: marker._latlng, color: timeSeriesMarker.getChartColor(),
163 dataset: {}, hidden: timeSeriesMarker.hideOnChart});
164 if (index != 0) {
165 dataEntry = timeSeriesData[i];
167 for (var j = index; j < endIndex; j++) {
168 var timeStamp = timeStamps[j];
169 var coords = marker.imageCoords;
170 var colorbarValue = await colorbarLayer.colorValueAtLocation(timeStamp, coords);
171 if (colorbarValue == null && dataType == 'continuous') {
172 colorbarValue = 0;
174 dataEntry.dataset[timeStamp] = colorbarValue;
175 frameLoaded();
177 if (index == 0) {
178 timeSeriesData.push(dataEntry);
181 if (index == 0) {
182 layerData[layerName] = timeSeriesData;
186 if (this.loadingTimeSeries) {
187 if (j < timeStamps.length) {
188 var callback = async () => {
189 this.generateTimeSeriesInBatches(j, batchSize, colorbarLayers, timeStamps, layerData, frameLoaded);
191 setTimeout(callback, 10);
192 } else {
193 const timeSeriesChart = document.querySelector('timeseries-chart');
194 timeSeriesChart.populateChart(layerData);
195 document.body.classList.remove('waiting');
200 /** Iterates over all timestamps in given range of current simulation, loads the corresponding image and colorbar,
201 * and adds the value of the color at the xCoord, yCoord in the colorbar to a dictionary under a key representing
202 * the corresponding timestamp. */
203 async generateTimeSeriesData(startDate, endDate) {
204 if (simVars.displayedColorbar == null) {
205 return;
207 var currentDomain = controllers.currentDomain.value;
208 document.body.classList.add('waiting');
209 this.timeSeriesButton.setProgress(0);
211 var filteredTimeStamps = simVars.sortedTimestamps.filter(timestamp => timestamp >= startDate && timestamp <= endDate);
212 var layerSpecification = this.timeSeriesButton.getLayerSpecification();
213 var timeSeriesMarkers = controllers.timeSeriesMarkers.getValue();
214 var colorbarLayers = simVars.overlayOrder.map(layerName => {
215 return this.getLayer(currentDomain, layerName)
216 }).filter(layer => {
217 if (layerSpecification == 'top-layer') {
218 return layer.layerName == simVars.displayedColorbar;
220 return layer.hasColorbar;
223 var progress = 0;
224 var totalFramesToLoad = filteredTimeStamps.length * timeSeriesMarkers.length * colorbarLayers.length;
225 const frameLoaded = () => {
226 progress += 1;
227 this.timeSeriesButton.setProgress(progress/totalFramesToLoad);
229 this.loadingTimeSeries = true;
231 var layerData = {};
232 var batchSize = Math.min(Math.ceil(filteredTimeStamps.length / 20), 10);
234 this.generateTimeSeriesInBatches(0, batchSize, colorbarLayers, filteredTimeStamps, layerData, frameLoaded);
238 window.customElements.define('timeseries-controller', TimeSeriesController);