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
{
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
);
36 super.connectedCallback();
37 controllers
.syncImageLoad
.subscribe(() => {
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. */
63 this.timeSeriesButton
.updateTimestamps();
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();
103 img
.style
.pointerEvents
= 'none';
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
) {
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
);
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
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
});
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') {
174 dataEntry
.dataset
[timeStamp
] = colorbarValue
;
178 timeSeriesData
.push(dataEntry
);
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);
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) {
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
)
217 if (layerSpecification
== 'top-layer') {
218 return layer
.layerName
== simVars
.displayedColorbar
;
220 return layer
.hasColorbar
;
224 var totalFramesToLoad
= filteredTimeStamps
.length
* timeSeriesMarkers
.length
* colorbarLayers
.length
;
225 const frameLoaded
= () => {
227 this.timeSeriesButton
.setProgress(progress
/totalFramesToLoad
);
229 this.loadingTimeSeries
= true;
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
);