Commit 11d8685108026d6b23be92fe78cf686b0e8ae2b2
1 parent
20fdc1a4
Exists in
master
and in
2 other branches
Add support for saturn's aurora HST campaign.
(finally! -- biggest commit of the tree) This was much more work than expected. Jupiter will follow.
Showing
8 changed files
with
1151 additions
and
459 deletions
Show diff stats
CHANGELOG.md
... | ... | @@ -32,11 +32,17 @@ et prendre aussi |
32 | 32 | - [ ] Rework the images of Rosetta and Juno |
33 | 33 | - [ ] Optimize data aggregation (numpy vectorization?) |
34 | 34 | - [ ] IE compat, if you can (I can't) |
35 | -- [ ] Add a README to the download tarball (no tarball anymore) | |
36 | 35 | - [ ] Bump D3JS to v5 (and its promises) |
37 | 36 | - [ ] Enable p67 |
38 | 37 | |
39 | 38 | |
39 | +## 1.8 | |
40 | + | |
41 | +- [x] Auroral Emissions (Saturn) | |
42 | +- [ ] Auroral Emissions Fixes | |
43 | +- [ ] Fix the warmup on the prod server | |
44 | + | |
45 | + | |
40 | 46 | ## 1.7 |
41 | 47 | |
42 | 48 | - [x] Only get SWRT after OMNI | ... | ... |
config.yml
... | ... | @@ -59,9 +59,11 @@ layers: |
59 | 59 | - slug: "hstjupiterobservations" |
60 | 60 | name: "HST Jupiter" |
61 | 61 | desc: "Hubble Space Telescope Jupiter Observations" |
62 | + live: false | |
62 | 63 | - slug: "hstsaturnobservations" |
63 | 64 | name: "HST Saturn" |
64 | 65 | desc: "Hubble Space Telescope Saturn Observations" |
66 | + live: true | |
65 | 67 | catalogs: |
66 | 68 | - slug: "cmecatalogs" |
67 | 69 | name: "CME Catalogs" |
... | ... | @@ -267,6 +269,8 @@ targets: |
267 | 269 | sb: |
268 | 270 | - slug: 'tao_sat_sw' |
269 | 271 | - slug: 'tao_sat_swrt' |
272 | + tap: | |
273 | + target_name: 'Saturn' | |
270 | 274 | locked: false |
271 | 275 | default: true |
272 | 276 | - type: 'planet' | ... | ... |
requirements.txt
... | ... | @@ -13,6 +13,11 @@ MarkupSafe==1.0 |
13 | 13 | python-slugify==1.2.4 |
14 | 14 | requests==2.19.1 |
15 | 15 | |
16 | +# Why not pyvo ? | |
17 | +# For obscure reasons within spacepy, only numpy 1.7 or 1.8 will work | |
18 | +# and pyvo requires astropy, that requires a more recent numpy. | |
19 | +#pyvo==0.9.* | |
20 | + | |
16 | 21 | |
17 | 22 | ## SECOND LEVEL DEPS |
18 | 23 | # (comment them out if you have compatibility issues) | ... | ... |
web/run.py
... | ... | @@ -10,8 +10,9 @@ import tarfile |
10 | 10 | import time |
11 | 11 | import urllib |
12 | 12 | import requests |
13 | +import re # regex | |
13 | 14 | |
14 | -from csv import writer as csv_writer | |
15 | +from csv import writer as csv_writer, DictWriter as csv_dict_writer | |
15 | 16 | from math import sqrt, isnan |
16 | 17 | from os import environ, remove as removefile |
17 | 18 | from os.path import isfile, join, abspath, dirname |
... | ... | @@ -356,10 +357,27 @@ def get_active_targets(): |
356 | 357 | return [t for t in all_targets if not ('locked' in t and t['locked'])] |
357 | 358 | |
358 | 359 | |
359 | -def retrieve_auroral_emissions(target_name): | |
360 | +def validate_tap_target_config(target): | |
361 | + tc = get_target_config(target) | |
362 | + if 'tap' not in tc: | |
363 | + raise Exception("No `tap` configuration for target `%s`." % target) | |
364 | + if 'target_name' not in tc['tap']: | |
365 | + raise Exception("No `target_name` in the `tap` configuration for target `%s`." % target) | |
366 | + return tc | |
367 | + | |
368 | + | |
369 | +# Using pyvo would be best. | |
370 | +# def retrieve_auroral_emissions_vopy(target_name): | |
371 | +# api_url = "http://voparis-tap.obspm.fr/__system__/tap/run/tap/sync" | |
372 | +# import pyvo as vo | |
373 | +# service = vo.dal.TAPService(api_url) | |
374 | +# # โฆ can't figure out how to install pyvo and spacepy alongside (forking?) | |
375 | + | |
376 | + | |
377 | +def retrieve_auroral_emissions(target_name, d_started_at=None, d_stopped_at=None): | |
360 | 378 | """ |
361 | 379 | Work In Progress. |
362 | - :param target_name: You should probably not let users choose this value, | |
380 | + :param target_name: You should probably not let users define this value, | |
363 | 381 | as our sanitizing for ADQL may not be 100% safe. |
364 | 382 | Use values from YAML configuration, instead. |
365 | 383 | Below is a list of the ids we found to be existing. |
... | ... | @@ -377,16 +395,34 @@ def retrieve_auroral_emissions(target_name): |
377 | 395 | - Saturn |
378 | 396 | :return: |
379 | 397 | """ |
398 | + | |
399 | + # Try out the form | |
400 | + # http://voparis-tap-planeto.obspm.fr/__system__/adql/query/form | |
401 | + | |
380 | 402 | api_url = "http://voparis-tap.obspm.fr/__system__/tap/run/tap/sync" |
381 | - d_started_at = datetime.datetime.now() | |
382 | - t_started_at = time.mktime(d_started_at.timetuple()) - 3600 * 24 * 365 * 10 # fixme | |
383 | - # t_started_at = 1 | |
384 | - d_stopped_at = datetime.datetime.now() | |
403 | + if d_started_at is None: | |
404 | + d_started_at = datetime.datetime.now() | |
405 | + t_started_at = time.mktime(d_started_at.timetuple()) - 3600 * 24 * 365 * 2 | |
406 | + # t_started_at = 1 | |
407 | + else: | |
408 | + t_started_at = time.mktime(d_started_at.timetuple()) | |
409 | + | |
410 | + if d_stopped_at is None: | |
411 | + d_stopped_at = datetime.datetime.now() | |
385 | 412 | t_stopped_at = time.mktime(d_stopped_at.timetuple()) |
386 | 413 | |
387 | - def to_jday(timestamp): | |
414 | + def timestamp_to_jday(timestamp): | |
388 | 415 | return timestamp / 86400.0 + 2440587.5 |
389 | 416 | |
417 | + def jday_to_timestamp(jday): | |
418 | + return (jday - 2440587.5) * 86400.0 | |
419 | + | |
420 | + def jday_to_datetime(jday): | |
421 | + return datetime.datetime.utcfromtimestamp(jday_to_timestamp(jday)) | |
422 | + | |
423 | + # SELECT DISTINCT dataproduct_type FROM apis.epn_core | |
424 | + # > im sp sc | |
425 | + | |
390 | 426 | query = """ |
391 | 427 | SELECT |
392 | 428 | time_min, |
... | ... | @@ -396,13 +432,13 @@ SELECT |
396 | 432 | FROM apis.epn_core |
397 | 433 | WHERE target_name='{target_name}' |
398 | 434 | AND dataproduct_type='im' |
399 | -AND time_min > {jday_start} | |
400 | -AND time_min < {jday_stop} | |
435 | +AND time_min >= {jday_start} | |
436 | +AND time_min <= {jday_stop} | |
401 | 437 | ORDER BY time_min, granule_gid |
402 | 438 | """.format( |
403 | 439 | target_name=target_name.replace("'", "\\'"), |
404 | - jday_start=to_jday(t_started_at), | |
405 | - jday_stop=to_jday(t_stopped_at) | |
440 | + jday_start=timestamp_to_jday(t_started_at), | |
441 | + jday_stop=timestamp_to_jday(t_stopped_at) | |
406 | 442 | ) |
407 | 443 | |
408 | 444 | # query = """ |
... | ... | @@ -427,8 +463,8 @@ ORDER BY time_min, granule_gid |
427 | 463 | rows = [] |
428 | 464 | for row in root.findall(rows_xpath, namespaces): |
429 | 465 | rows.append({ |
430 | - 'time_min': row[0].text, | |
431 | - 'time_max': row[1].text, | |
466 | + 'time_min': jday_to_datetime(float(row[0].text)), | |
467 | + 'time_max': jday_to_datetime(float(row[1].text)), | |
432 | 468 | 'thumbnail_url': row[2].text, |
433 | 469 | 'external_link': row[3].text, |
434 | 470 | }) |
... | ... | @@ -1045,7 +1081,7 @@ def increment_hit_counter(): |
1045 | 1081 | |
1046 | 1082 | def update_spacepy(): |
1047 | 1083 | """ |
1048 | - Importing pydcf will fail if the toolbox is not up to date. | |
1084 | + Importing pycdf will fail if the toolbox is not up to date. | |
1049 | 1085 | """ |
1050 | 1086 | try: |
1051 | 1087 | log.info("Updating spacepy's toolboxโฆ") |
... | ... | @@ -1500,6 +1536,61 @@ def download_targets_cdf(targets, inp, started_at, stopped_at): |
1500 | 1536 | return send_from_directory(CACHE_DIR, cdf_filename) |
1501 | 1537 | |
1502 | 1538 | |
1539 | +@app.route("/<target>_auroral_catalog.csv") | |
1540 | +def download_auroral_catalog_csv(target): | |
1541 | + tc = validate_tap_target_config(target) | |
1542 | + log.debug("Requesting auroral emissions CSV for %s..." % tc['name']) | |
1543 | + | |
1544 | + filename = "%s_auroral_catalog.csv" % (target) | |
1545 | + local_csv_file = join(CACHE_DIR, filename) | |
1546 | + | |
1547 | + | |
1548 | + target_name = tc['tap']['target_name'] | |
1549 | + emissions = retrieve_auroral_emissions(target_name) | |
1550 | + | |
1551 | + # Be careful with regexes in python 2 ; best always use the ^$ | |
1552 | + thumbnail_url_filter = re.compile("^.*proc(?:_small)?\\.(?:jpe?g|png|webp|gif|bmp|tiff)$") | |
1553 | + | |
1554 | + # Filter the emissions | |
1555 | + def _keep_emission(emission): | |
1556 | + ok = thumbnail_url_filter.match(emission['thumbnail_url']) | |
1557 | + # print("ok", ok, emission['thumbnail_url']) | |
1558 | + return bool(ok) | |
1559 | + | |
1560 | + emissions = [e for e in emissions if _keep_emission(e)] | |
1561 | + | |
1562 | + header = ('time_min', 'time_max', 'thumbnail_url', 'external_link') | |
1563 | + if len(emissions): | |
1564 | + header = emissions[0].keys() | |
1565 | + si = StringIO.StringIO() | |
1566 | + cw = csv_dict_writer(si, fieldnames=header) | |
1567 | + cw.writeheader() | |
1568 | + # 'time_min', 'time_max', 'thumbnail_url', 'external_link' | |
1569 | + #cw.writerow(head) | |
1570 | + | |
1571 | + log.debug("Writing auroral emissions CSV for %s..." % tc['name']) | |
1572 | + cw.writerows(emissions) | |
1573 | + | |
1574 | + log.info("Generated auroral emissions CSV contents for %s." % tc['name']) | |
1575 | + return si.getvalue() | |
1576 | + | |
1577 | + # if not isfile(local_csv_file): | |
1578 | + # abort(500, "Could not cache CSV file at '%s'." % local_csv_file) | |
1579 | + # | |
1580 | + # return send_from_directory(CACHE_DIR, filename) | |
1581 | + | |
1582 | + | |
1583 | + | |
1584 | + | |
1585 | +@app.route("/test/auroral/<target>") | |
1586 | +def test_auroral_emissions(target): | |
1587 | + tc = validate_tap_target_config(target) | |
1588 | + target_name = tc['tap']['target_name'] | |
1589 | + retrieved = retrieve_auroral_emissions(target_name) | |
1590 | + | |
1591 | + return "%d results:\n%s" % (len(retrieved), str(retrieved)) | |
1592 | + | |
1593 | + | |
1503 | 1594 | # API ######################################################################### |
1504 | 1595 | |
1505 | 1596 | @app.route("/cache/clear") | ... | ... |
... | ... | @@ -0,0 +1,456 @@ |
1 | +.mdl-layout__drawer hr { | |
2 | + margin: 0.5em 0; | |
3 | +} | |
4 | + | |
5 | +.mdl-layout__drawer .mdl-layout-title { | |
6 | + line-height: 42px; | |
7 | + display: inline-block; | |
8 | +} | |
9 | + | |
10 | +.mdl-layout__drawer > details > summary { | |
11 | + padding-left: 15px; | |
12 | + cursor: pointer; | |
13 | + outline: none; | |
14 | +} | |
15 | + | |
16 | +.mdl-layout__drawer .mdl-layout-title:first-of-type { | |
17 | + line-height: 60px; | |
18 | +} | |
19 | + | |
20 | +.plots-buttons { | |
21 | + text-align: center; | |
22 | + margin: 0 auto; | |
23 | +} | |
24 | + | |
25 | +.plots-buttons button { | |
26 | + margin: 1em 1em; | |
27 | +} | |
28 | + | |
29 | +#time_series svg { | |
30 | + cursor: crosshair; | |
31 | +} | |
32 | + | |
33 | +#time_series .help { | |
34 | + position: absolute; | |
35 | + text-align: center; | |
36 | + font-size: 0.9em; | |
37 | + font-style: italic; | |
38 | + color: darkgrey; | |
39 | + display: none; | |
40 | +} | |
41 | + | |
42 | +#time_series:hover .help { | |
43 | + display: block; | |
44 | +} | |
45 | + | |
46 | +#time_series svg .brush .selection { | |
47 | + fill: #efa02c; | |
48 | + fill-opacity: 0.382; | |
49 | +} | |
50 | + | |
51 | +.axis path, .axis line { | |
52 | + fill: none; | |
53 | + /*{# stroke: #f4f4f4;#}*/ | |
54 | + shape-rendering: crispEdges; | |
55 | + stroke-width: 1px; | |
56 | +} | |
57 | + | |
58 | +path.line { | |
59 | + fill: none; | |
60 | + stroke: steelblue; | |
61 | + stroke-width: 1px; | |
62 | +} | |
63 | + | |
64 | +path.predictive-line { | |
65 | + fill: none; | |
66 | + stroke: #ff4081; | |
67 | + stroke-width: 2px; | |
68 | +} | |
69 | + | |
70 | +circle.cursor-circle { | |
71 | + fill: black; | |
72 | + stroke: rgba(20, 20, 20, 0.48); | |
73 | +} | |
74 | + | |
75 | +text.cursor-text { | |
76 | + /*{# font-family: 'Ubuntu', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif;#}*/ | |
77 | + /*{# font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;#}*/ | |
78 | + font-family: "Ubuntu Mono", 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; | |
79 | + text-align: right; | |
80 | +} | |
81 | + | |
82 | +text.cursor-text-shadow { | |
83 | + stroke: white; | |
84 | + stroke-width: 5px; | |
85 | + opacity: 0.777 | |
86 | +} | |
87 | + | |
88 | +path.orbit.orbit_section { | |
89 | + fill: none; | |
90 | + stroke: steelblue; | |
91 | + stroke-width: 1.5px; | |
92 | +} | |
93 | + | |
94 | +ellipse.orbit.orbit_ellipse { | |
95 | + fill: none; | |
96 | + stroke: #a3a3a3; | |
97 | + stroke-width: 1px; | |
98 | + stroke-dasharray: 5px; | |
99 | +} | |
100 | + | |
101 | +#form_time_interval { | |
102 | + padding-left: 30px; | |
103 | +} | |
104 | + | |
105 | +#form_time_interval .mdl-textfield { | |
106 | + padding-top: 0; | |
107 | +} | |
108 | + | |
109 | +#started_at, #stopped_at { | |
110 | + width: 85%; | |
111 | +} | |
112 | + | |
113 | +.section-drawer { | |
114 | + padding-left: 3em; | |
115 | +} | |
116 | + | |
117 | +.targets-filters { | |
118 | + padding-left: 17px; | |
119 | +} | |
120 | + | |
121 | +.targets-filters .target { | |
122 | + float: left; | |
123 | + cursor: pointer; | |
124 | + position: relative; | |
125 | +} | |
126 | + | |
127 | +.targets-filters .target:not(.active) { | |
128 | + -webkit-filter: grayscale(100%); | |
129 | + -moz-filter: grayscale(100%); | |
130 | + -o-filter: grayscale(100%); | |
131 | + /*{# -ms-filter: grayscale(100%);#}*/ | |
132 | + filter: grayscale(100%); | |
133 | +} | |
134 | + | |
135 | +.targets-filters .target.locked { | |
136 | + cursor: not-allowed; | |
137 | +} | |
138 | + | |
139 | +.targets-filters .target.loading { | |
140 | + -webkit-animation-name: keyframes_rotate; | |
141 | + -webkit-animation-duration: 4000ms; | |
142 | + -webkit-animation-iteration-count: infinite; | |
143 | + -webkit-animation-timing-function: linear; | |
144 | + -moz-animation-name: keyframes_rotate; | |
145 | + -moz-animation-duration: 4000ms; | |
146 | + -moz-animation-iteration-count: infinite; | |
147 | + -moz-animation-timing-function: linear; | |
148 | + -ms-animation-name: keyframes_rotate; | |
149 | + -ms-animation-duration: 4000ms; | |
150 | + -ms-animation-iteration-count: infinite; | |
151 | + -ms-animation-timing-function: linear; | |
152 | + animation-name: keyframes_rotate; | |
153 | + animation-duration: 4000ms; | |
154 | + animation-iteration-count: infinite; | |
155 | + animation-timing-function: linear; | |
156 | +} | |
157 | + | |
158 | +.targets-filters .target .decorator { | |
159 | + position: absolute; | |
160 | + top: 0; | |
161 | + left: 0; | |
162 | + display: none; | |
163 | +} | |
164 | + | |
165 | +.targets-filters .target.empty .decorator.empty { | |
166 | + display: block; | |
167 | +} | |
168 | + | |
169 | +.targets-filters .target.error .decorator.error { | |
170 | + display: block; | |
171 | +} | |
172 | + | |
173 | +.targets-filters .target .decorator.loading { | |
174 | + top: 19px; | |
175 | + left: 19px; | |
176 | +} | |
177 | + | |
178 | +.targets-filters .target.loading .decorator.loading { | |
179 | + display: block; | |
180 | +} | |
181 | + | |
182 | +#parameters .parameter { | |
183 | + outline: 0; | |
184 | + padding-top: 7px; | |
185 | + padding-bottom: 7px; | |
186 | + | |
187 | +} | |
188 | + | |
189 | +#parameters .parameter.active { | |
190 | + background-color: #c8d3e1; | |
191 | +} | |
192 | + | |
193 | +.option-layer-campaign.loading { | |
194 | + background: #00acc1; | |
195 | +} | |
196 | + | |
197 | + | |
198 | +/* Preview Box */ | |
199 | + | |
200 | +#time_series_cursor_morebox { | |
201 | + margin-top: 1em; | |
202 | + padding-left: 7%; | |
203 | + padding-right: 7%; | |
204 | +} | |
205 | +#time_series_cursor_image_link:focus { | |
206 | + outline: none; | |
207 | +} | |
208 | +#time_series_cursor_image { | |
209 | + width: 100%; | |
210 | +} | |
211 | +#time_series_cursor_link { | |
212 | + display: block; | |
213 | +} | |
214 | +#time_series_cursor_image_extra { | |
215 | + text-align: center; | |
216 | + padding-top: 1em; | |
217 | +} | |
218 | +#time_series_cursor_image_comment { | |
219 | + /*text-align: center;*/ | |
220 | +} | |
221 | +#time_series_cursor_image_comment *[title]{ | |
222 | + text-decoration: dotted lightskyblue; | |
223 | +} | |
224 | + | |
225 | +/*{# CSS Spinners #}*/ | |
226 | + | |
227 | +#download_spinner { | |
228 | + display: none; | |
229 | + top: 2px; | |
230 | + left: 4px; | |
231 | + width: 14px; | |
232 | + height: 14px; | |
233 | +} | |
234 | + | |
235 | +#plots_loader { | |
236 | + position: fixed; | |
237 | + top: 0; | |
238 | + left: 0; | |
239 | + bottom: 0; | |
240 | + right: 0; | |
241 | + height: 100%; | |
242 | + width: 100%; | |
243 | + background-color: #fff; | |
244 | + z-index: 1000; | |
245 | +} | |
246 | + | |
247 | +#plots_loader .loader-text { | |
248 | + width: 200px; | |
249 | + height: 30px; | |
250 | + position: absolute; | |
251 | + top: -240px; | |
252 | + left: -32px; | |
253 | + bottom: 0; | |
254 | + right: 0; | |
255 | + margin: auto; | |
256 | + text-align: center; | |
257 | + font-size: 1.0em; | |
258 | + font-style: italic; | |
259 | + color: darkgrey; | |
260 | +} | |
261 | + | |
262 | +#plots_loader .img { | |
263 | + width: 100px; | |
264 | + height: 100px; | |
265 | + border-radius: 100%; | |
266 | + position: absolute; | |
267 | + border: 1px solid #6978ff; | |
268 | + animation: keyframes_rotate 1s; | |
269 | + animation-iteration-count: infinite; | |
270 | + transition: 2s; | |
271 | + border-bottom: none; | |
272 | + border-right: none; | |
273 | + animation-timing-function: linear; | |
274 | + margin-left: -70px; | |
275 | + margin-top: -70px; | |
276 | + left: 50%; | |
277 | + top: 50%; | |
278 | +} | |
279 | + | |
280 | +#plots_loader #plots_loader_img2 { | |
281 | + width: 90px; | |
282 | + height: 90px; | |
283 | + left: 50.35%; | |
284 | + top: 50.7%; | |
285 | + animation-delay: .2s; | |
286 | +} | |
287 | + | |
288 | +#plots_loader #plots_loader_img3 { | |
289 | + width: 80px; | |
290 | + height: 80px; | |
291 | + left: 50.70%; | |
292 | + top: 51.4%; | |
293 | + animation-delay: .4s; | |
294 | +} | |
295 | + | |
296 | +#plots_loader #plots_loader_img4 { | |
297 | + width: 70px; | |
298 | + height: 70px; | |
299 | + left: 51.05%; | |
300 | + top: 52.1%; | |
301 | + animation-delay: .6s; | |
302 | +} | |
303 | + | |
304 | +#plots_loader #plots_loader_img5 { | |
305 | + width: 60px; | |
306 | + height: 60px; | |
307 | + left: 51.40%; | |
308 | + top: 52.8%; | |
309 | + animation-delay: .8s; | |
310 | +} | |
311 | + | |
312 | +@keyframes keyframes_rotate { | |
313 | + from { | |
314 | + transform: rotate(0deg); | |
315 | + } | |
316 | + 50% { | |
317 | + transform: rotate(180deg); | |
318 | + } | |
319 | + 100% { | |
320 | + transform: rotate(360deg); | |
321 | + } | |
322 | +} | |
323 | + | |
324 | +.small-loader-container { | |
325 | + width: 27px; | |
326 | + margin: 0 auto; | |
327 | + background: none; | |
328 | + pointer-events: none; | |
329 | +} | |
330 | + | |
331 | +.small-loader-circle-1 { | |
332 | + height: 27px; | |
333 | + width: 27px; | |
334 | + background: rgba(255, 238, 195, 0.72); | |
335 | +} | |
336 | + | |
337 | +.small-loader-circle-2 { | |
338 | + height: 22px; | |
339 | + width: 22px; | |
340 | + background: none; | |
341 | +} | |
342 | + | |
343 | +.small-loader-circle-3 { | |
344 | + height: 18px; | |
345 | + width: 18px; | |
346 | + background: rgba(29, 65, 255, 0.9); | |
347 | +} | |
348 | + | |
349 | +.small-loader-circle-4 { | |
350 | + height: 13px; | |
351 | + width: 13px; | |
352 | + background: none; | |
353 | +} | |
354 | + | |
355 | +.small-loader-circle-5 { | |
356 | + height: 9px; | |
357 | + width: 9px; | |
358 | + background: rgba(238, 238, 238, 0.8); | |
359 | +} | |
360 | + | |
361 | +.small-loader-circle-6 { | |
362 | + height: 4px; | |
363 | + width: 4px; | |
364 | + background: none; | |
365 | +} | |
366 | + | |
367 | +.small-loader-circle-7 { | |
368 | + height: 2px; | |
369 | + width: 2px; | |
370 | + background: rgb(110, 102, 255); | |
371 | +} | |
372 | + | |
373 | +.small-loader-circle-1, | |
374 | +.small-loader-circle-2, | |
375 | +.small-loader-circle-3, | |
376 | +.small-loader-circle-4, | |
377 | +.small-loader-circle-5, | |
378 | +.small-loader-circle-6, | |
379 | +.small-loader-circle-7 { | |
380 | + border-bottom: none; | |
381 | + border-radius: 50%; | |
382 | + -o-border-radius: 50%; | |
383 | + -ms-border-radius: 50%; | |
384 | + -webkit-border-radius: 50%; | |
385 | + -moz-border-radius: 50%; | |
386 | + box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.1); | |
387 | + -o-box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.1); | |
388 | + -ms-box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.1); | |
389 | + -webkit-box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.1); | |
390 | + -moz-box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.1); | |
391 | + animation-name: small-loader-spin; | |
392 | + -o-animation-name: small-loader-spin; | |
393 | + -ms-animation-name: small-loader-spin; | |
394 | + -webkit-animation-name: small-loader-spin; | |
395 | + -moz-animation-name: small-loader-spin; | |
396 | + animation-duration: 4600ms; | |
397 | + -o-animation-duration: 4600ms; | |
398 | + -ms-animation-duration: 4600ms; | |
399 | + -webkit-animation-duration: 4600ms; | |
400 | + -moz-animation-duration: 4600ms; | |
401 | + animation-iteration-count: infinite; | |
402 | + -o-animation-iteration-count: infinite; | |
403 | + -ms-animation-iteration-count: infinite; | |
404 | + -webkit-animation-iteration-count: infinite; | |
405 | + -moz-animation-iteration-count: infinite; | |
406 | + animation-timing-function: linear; | |
407 | + -o-animation-timing-function: linear; | |
408 | + -ms-animation-timing-function: linear; | |
409 | + -webkit-animation-timing-function: linear; | |
410 | + -moz-animation-timing-function: linear; | |
411 | +} | |
412 | + | |
413 | +@keyframes small-loader-spin { | |
414 | + from { | |
415 | + transform: rotate(0deg); | |
416 | + } | |
417 | + to { | |
418 | + transform: rotate(360deg); | |
419 | + } | |
420 | +} | |
421 | + | |
422 | +@-o-keyframes small-loader-spin { | |
423 | + from { | |
424 | + -o-transform: rotate(0deg); | |
425 | + } | |
426 | + to { | |
427 | + -o-transform: rotate(360deg); | |
428 | + } | |
429 | +} | |
430 | + | |
431 | +@-ms-keyframes small-loader-spin { | |
432 | + from { | |
433 | + -ms-transform: rotate(0deg); | |
434 | + } | |
435 | + to { | |
436 | + -ms-transform: rotate(360deg); | |
437 | + } | |
438 | +} | |
439 | + | |
440 | +@-webkit-keyframes small-loader-spin { | |
441 | + from { | |
442 | + -webkit-transform: rotate(0deg); | |
443 | + } | |
444 | + to { | |
445 | + -webkit-transform: rotate(360deg); | |
446 | + } | |
447 | +} | |
448 | + | |
449 | +@-moz-keyframes small-loader-spin { | |
450 | + from { | |
451 | + -moz-transform: rotate(0deg); | |
452 | + } | |
453 | + to { | |
454 | + -moz-transform: rotate(360deg); | |
455 | + } | |
456 | +} | ... | ... |
web/static/js/main.js
... | ... | @@ -83,6 +83,13 @@ |
83 | 83 | this.configuration['parameters'].forEach(p => that.parameters[p['id']] = p); |
84 | 84 | this.orbits = null; |
85 | 85 | this.time_series = []; |
86 | + | |
87 | + // Holds the downloaded data for each layer hash | |
88 | + // layer hash => data (array of dict) | |
89 | + this._image_preview_layers_data = {}; | |
90 | + // Holds the activation status for each layer hash | |
91 | + // layer hash => activation status (bool) | |
92 | + this._image_preview_layers_live = {}; | |
86 | 93 | } |
87 | 94 | |
88 | 95 | /** |
... | ... | @@ -282,7 +289,7 @@ |
282 | 289 | data = { |
283 | 290 | 'hee': [] |
284 | 291 | }; |
285 | - configuration['parameters'].forEach(parameter => data[parameter['id']] = []); | |
292 | + app.configuration['parameters'].forEach(parameter => data[parameter['id']] = []); | |
286 | 293 | if (!csv) { |
287 | 294 | reject('invalid'); |
288 | 295 | } |
... | ... | @@ -292,7 +299,7 @@ |
292 | 299 | csv.forEach(d => { |
293 | 300 | let dtime; |
294 | 301 | dtime = timeFormat(d['time']); |
295 | - configuration['parameters'].forEach(parameter => { | |
302 | + app.configuration['parameters'].forEach(parameter => { | |
296 | 303 | let id; |
297 | 304 | let val; |
298 | 305 | id = parameter['id']; |
... | ... | @@ -424,12 +431,15 @@ |
424 | 431 | app.time_series.forEach(ts2 => ts2.hideCursor()); |
425 | 432 | return true; |
426 | 433 | }; |
427 | - ts.options['onMouseMove'] = t => { | |
428 | - let ref$; | |
429 | - app.time_series.forEach(ts2 => ts2.moveCursor(t)); | |
430 | - if ((ref$ = app.orbits) != null) { | |
431 | - ref$.moveToDate(t); | |
434 | + ts.options['onMouseMove'] = time => { | |
435 | + app.time_series.forEach(ts2 => | |
436 | + ts2.moveCursor(time) | |
437 | + ); | |
438 | + if (app.orbits != null) { | |
439 | + app.orbits.moveToDate(time); | |
432 | 440 | } |
441 | + ts.updateImagePreviewFromCursor(time); | |
442 | + | |
433 | 443 | return true; |
434 | 444 | }; |
435 | 445 | |
... | ... | @@ -508,6 +518,123 @@ |
508 | 518 | return this; |
509 | 519 | } |
510 | 520 | |
521 | + /////////////////////////////////////////////////////////////////////// | |
522 | + | |
523 | + hashImagePreviewLayer(target_slug, layer_slug) { | |
524 | + return target_slug + "_" + layer_slug; | |
525 | + } | |
526 | + | |
527 | + getImagePreviewData(target_slug, layer_slug) { | |
528 | + const app = this; // not taking any risks with `this` binding. | |
529 | + return new Promise((resolve, reject) => { | |
530 | + const h = app.hashImagePreviewLayer(target_slug, layer_slug); | |
531 | + if (h in this._image_preview_layers_data) { | |
532 | + resolve(app._image_preview_layers_data[h]); | |
533 | + } else { | |
534 | + let url_route = "saturn_auroral_catalog.csv"; // FIXME | |
535 | + let url = app.configuration['api']['root'] + url_route; | |
536 | + //url += "/" + url_route; // Extra / yields havoc -- flask? | |
537 | + let d3csv = d3.csv(url, csv => { | |
538 | + console.info("Fetched image preview CSV data.", url, csv); | |
539 | + console.assert(app === this); // peace of mind | |
540 | + // Now's a good time to do some pre-process on the data | |
541 | + let data = csv.map(d => { | |
542 | + return { | |
543 | + time_min: moment(d.time_min), | |
544 | + time_max: moment(d.time_max), | |
545 | + thumbnail_url: d.thumbnail_url.trim(), | |
546 | + external_link: d.external_link.trim() | |
547 | + }; | |
548 | + }); | |
549 | + app._image_preview_layers_data[h] = data; | |
550 | + // โฆ also a good place to build an index for example, | |
551 | + // since right now we're bisecting on each cursor move, | |
552 | + // and that's expensive. (Binary Search) | |
553 | + resolve(data); | |
554 | + }); | |
555 | + } | |
556 | + }); | |
557 | + } | |
558 | + | |
559 | + isImagePreviewLayerShown(target_slug, layer_slug) { | |
560 | + const h = this.hashImagePreviewLayer(target_slug, layer_slug); | |
561 | + if (h in this._image_preview_layers_live) { | |
562 | + return this._image_preview_layers_live[h]; | |
563 | + } else { | |
564 | + return false; | |
565 | + } | |
566 | + } | |
567 | + | |
568 | + showImagePreviewLayer(target_slug, layer_slug) { | |
569 | + const app = this; | |
570 | + | |
571 | + return new Promise((resolve, reject) => { | |
572 | + if (this.isImagePreviewLayerShown(target_slug, layer_slug)) { | |
573 | + resolve(); // already shown | |
574 | + } | |
575 | + const h = this.hashImagePreviewLayer(target_slug, layer_slug); | |
576 | + //console.log("showImagePreviewLayer", h); | |
577 | + //console.log("_image_preview_layers_data", this._image_preview_layers_data); | |
578 | + | |
579 | + this._image_preview_layers_live[h] = true; | |
580 | + | |
581 | + const data_promise = this.getImagePreviewData(target_slug, layer_slug); | |
582 | + data_promise.then((data) => { | |
583 | + //console.debug("getImagePreviewData OK !", data); | |
584 | + let ts_left_to_process = this.time_series.length; | |
585 | + this.time_series.forEach(ts => { | |
586 | + if (ts.target.slug === target_slug) { | |
587 | + ts.showImagePreviewLayer(layer_slug, data).then( | |
588 | + () => { | |
589 | + //ts_left_to_process--; | |
590 | + //console.debug("showImagePreviewLayer promise ok!"); | |
591 | + }, | |
592 | + (err) => { | |
593 | + //ts_left_to_process--; | |
594 | + //console.error("showImagePreviewLayer promise failed.", err); | |
595 | + }, | |
596 | + ).finally(() => { | |
597 | + ts_left_to_process--; | |
598 | + //console.debug("showImagePreviewLayer left", ts_left_to_process); | |
599 | + if (0 === ts_left_to_process) { | |
600 | + //console.debug("showImagePreviewLayer resolve"); | |
601 | + resolve(); | |
602 | + } | |
603 | + }); | |
604 | + } else { | |
605 | + ts_left_to_process--; | |
606 | + } | |
607 | + }); | |
608 | + }, (err) => { | |
609 | + console.error("getImagePreviewData promise failed.", err); | |
610 | + reject(err); | |
611 | + }); | |
612 | + | |
613 | + }); | |
614 | + } | |
615 | + | |
616 | + hideImagePreviewLayer(target_slug, layer_slug) { | |
617 | + // perhaps make this promise-based as well? | |
618 | + | |
619 | + if ( ! this.isImagePreviewLayerShown(target_slug, layer_slug)) { | |
620 | + return; // already hidden | |
621 | + } | |
622 | + | |
623 | + const h = this.hashImagePreviewLayer(target_slug, layer_slug); | |
624 | + //console.log("hideImagePreviewLayer", h); | |
625 | + | |
626 | + this._image_preview_layers_live[h] = false; | |
627 | + | |
628 | + let ts; | |
629 | + for (let key in this.time_series) { | |
630 | + if ( ! this.time_series.hasOwnProperty(key)) continue; | |
631 | + ts = this.time_series[key]; | |
632 | + if (ts.target.slug !== target_slug) continue; | |
633 | + ts.hideImagePreviewLayer(layer_slug); | |
634 | + } | |
635 | + | |
636 | + } | |
637 | + | |
511 | 638 | getDomain() { |
512 | 639 | if (this.current_started_at != null && this.current_stopped_at != null) { |
513 | 640 | return [this.current_started_at, this.current_stopped_at]; |
... | ... | @@ -619,6 +746,20 @@ |
619 | 746 | } |
620 | 747 | } |
621 | 748 | this.predictiveData = predictiveData; |
749 | + | |
750 | + this._image_preview_layers_live = {}; // layer_hash => is active? (bool) | |
751 | + this._image_preview_layers_rect = {}; // layer_hash => rects (array) | |
752 | + this._image_preview_layers_data = {}; // layer_hash => data (array) (/!. not a copy) | |
753 | + // datum example: | |
754 | + // { | |
755 | + // time_min: moment("2017-09-09 00:13:49.845227"), | |
756 | + // thumbnail_url: "http://voparis-srv.obspm.fr/vo/planeto/apis/dataset/Bastet/Saturn_-_2017_14_Feb-09_Sept/od9u21u4q_proc_small.jpg", | |
757 | + // time_max: moment("2017-09-09 00:58:50.044784"), | |
758 | + // external_link: "http://apis.obspm.fr/spip.php?page=observation&id=bas_8167" | |
759 | + // } | |
760 | + // Note that time_min and time_max are pre-processed | |
761 | + // to moment.js instances already during creation. | |
762 | + | |
622 | 763 | this.init(); |
623 | 764 | } |
624 | 765 | |
... | ... | @@ -778,6 +919,7 @@ |
778 | 919 | this.yAxisText.attr("y", 20 - this.margin.left).attr("x", 0 - height / 2); |
779 | 920 | this.yAxisTextTarget.attr("y", 0 - this.margin.left).attr("x", 0 - height / 2.0); |
780 | 921 | this.resizeCatalogLayers(); |
922 | + this.resizeImagePreviewLayers(); | |
781 | 923 | if (!this.visible) { |
782 | 924 | this.hide(); |
783 | 925 | } |
... | ... | @@ -891,6 +1033,7 @@ |
891 | 1033 | this.predictiveDataPath.attr('d', this.line); |
892 | 1034 | } |
893 | 1035 | this.resizeCatalogLayers(); |
1036 | + this.resizeImagePreviewLayers(); | |
894 | 1037 | this.hideCursor(); |
895 | 1038 | return new Promise((resolve, reject) => { |
896 | 1039 | if (0 === duration) { |
... | ... | @@ -901,6 +1044,19 @@ |
901 | 1044 | }); |
902 | 1045 | } |
903 | 1046 | |
1047 | + createLayerRect(started_at, stopped_at, color) { | |
1048 | + // We're ignoring the parameters, since we're | |
1049 | + // doing the resize logic in resizeXXX() | |
1050 | + let layer_rect; // positioning is done in resize() | |
1051 | + layer_rect = this.pathWrapper.append("rect") | |
1052 | + .attr('y', 0) | |
1053 | + .attr('height', this.plotHeight) // => move to resize too? | |
1054 | + .attr('fill', color); | |
1055 | + //won't work, possibly because of our input catcher rect. | |
1056 | + //layer_rect.append('svg:title').text("!"); | |
1057 | + return layer_rect; | |
1058 | + } | |
1059 | + | |
904 | 1060 | createCatalogLayers() { |
905 | 1061 | let catalog_slug; |
906 | 1062 | let ref$; |
... | ... | @@ -912,35 +1068,32 @@ |
912 | 1068 | let stopped_at; |
913 | 1069 | this.layers_rects = {}; |
914 | 1070 | for (catalog_slug in ref$ = this.target.config.layers) { |
1071 | + if ( ! (ref$.hasOwnProperty(catalog_slug))) continue; // oh, js | |
915 | 1072 | layers = ref$[catalog_slug]; |
916 | 1073 | this.layers_rects[catalog_slug] = []; |
917 | 1074 | for (i$ = 0, len$ = layers.length; i$ < len$; ++i$) { |
918 | 1075 | layer = layers[i$]; |
919 | 1076 | started_at = moment(layer.start); |
920 | 1077 | stopped_at = moment(layer.stop); |
921 | - this.layers_rects[catalog_slug].push(this.createCatalogLayer(started_at, stopped_at)); | |
1078 | + this.layers_rects[catalog_slug].push(this.createCatalogLayerRect(started_at, stopped_at)); | |
922 | 1079 | } |
923 | 1080 | this.hideCatalogLayer(catalog_slug); |
924 | 1081 | } |
925 | 1082 | return this; |
926 | 1083 | } |
927 | 1084 | |
928 | - createCatalogLayer(started_at, stopped_at) { | |
929 | - let layer_rect; | |
930 | - layer_rect = this.pathWrapper.append("rect") | |
931 | - .attr('y', 0) | |
932 | - .attr('height', this.plotHeight) | |
933 | - .attr('fill', '#FFFD64C2'); | |
934 | - //won't work, possibly because of our input catcher rect. | |
935 | - //layer_rect.append('svg:title').text("!"); | |
936 | - return layer_rect; | |
1085 | + createCatalogLayerRect(started_at, stopped_at) { | |
1086 | + return this.createLayerRect(started_at, stopped_at, '#FFFD64C2'); | |
937 | 1087 | } |
938 | 1088 | |
939 | 1089 | resizeCatalogLayers() { |
1090 | + let animate = true; // move to param if needed | |
1091 | + // Perhaps make that transition part of the prototype instead? | |
1092 | + let t = this.svg.transition().duration(750); | |
1093 | + | |
940 | 1094 | let catalog_slug; |
941 | 1095 | let ref$; |
942 | 1096 | let layers; |
943 | - let i$; | |
944 | 1097 | let len$; |
945 | 1098 | let i; |
946 | 1099 | let layer; |
... | ... | @@ -948,47 +1101,352 @@ |
948 | 1101 | let stopped_at; |
949 | 1102 | let width; |
950 | 1103 | for (catalog_slug in ref$ = this.target.config.layers) { |
1104 | + if ( ! (ref$.hasOwnProperty(catalog_slug))) continue; | |
951 | 1105 | layers = ref$[catalog_slug]; |
952 | - for (i$ = 0, len$ = layers.length; i$ < len$; ++i$) { | |
953 | - i = i$; | |
954 | - layer = layers[i$]; | |
1106 | + for (i = 0, len$ = layers.length; i < len$; ++i) { | |
1107 | + layer = layers[i]; | |
955 | 1108 | started_at = moment(layer.start); |
956 | 1109 | stopped_at = moment(layer.stop); |
957 | 1110 | width = Math.max(2, this.xScale(stopped_at) - this.xScale(started_at)); |
958 | - this.layers_rects[catalog_slug][i].attr('x', this.xScale(started_at)).attr('width', width); | |
1111 | + | |
1112 | + if (animate) { | |
1113 | + this.layers_rects[catalog_slug][i] | |
1114 | + .transition(t) | |
1115 | + .attr('x', this.xScale(started_at)) | |
1116 | + .attr('width', width); | |
1117 | + } else { | |
1118 | + this.layers_rects[catalog_slug][i] | |
1119 | + .attr('x', this.xScale(started_at)) | |
1120 | + .attr('width', width); | |
1121 | + } | |
1122 | + | |
959 | 1123 | } |
960 | 1124 | } |
961 | 1125 | return this; |
962 | 1126 | } |
963 | 1127 | |
964 | 1128 | showCatalogLayer(catalog_slug) { |
965 | - let i$; | |
966 | 1129 | let ref$; |
967 | 1130 | let len$; |
968 | 1131 | let i; |
969 | 1132 | let layer; |
970 | - for (i$ = 0, len$ = (ref$ = this.target.config.layers[catalog_slug]).length; i$ < len$; ++i$) { | |
971 | - i = i$; | |
972 | - layer = ref$[i$]; | |
1133 | + for (i = 0, len$ = (ref$ = this.target.config.layers[catalog_slug]).length; i < len$; ++i) { | |
1134 | + layer = ref$[i]; | |
973 | 1135 | this.layers_rects[catalog_slug][i].style("display", null); |
974 | 1136 | } |
975 | 1137 | return this; |
976 | 1138 | } |
977 | 1139 | |
978 | 1140 | hideCatalogLayer(catalog_slug) { |
979 | - let i$; | |
980 | 1141 | let ref$; |
981 | 1142 | let len$; |
982 | 1143 | let i; |
983 | - let layer; | |
984 | - for (i$ = 0, len$ = (ref$ = this.target.config.layers[catalog_slug]).length; i$ < len$; ++i$) { | |
985 | - i = i$; | |
986 | - layer = ref$[i$]; | |
1144 | + //let layer; | |
1145 | + for (i = 0, len$ = (ref$ = this.target.config.layers[catalog_slug]).length; i < len$; ++i) { | |
1146 | + //layer = ref$[i]; | |
987 | 1147 | this.layers_rects[catalog_slug][i].style("display", "none"); |
988 | 1148 | } |
989 | 1149 | return this; |
990 | 1150 | } |
991 | 1151 | |
1152 | + /////////////////////////////////////////////////////////////////////// | |
1153 | + | |
1154 | + hasImagePreviewLayer(layer_slug) { | |
1155 | + return layer_slug in this._image_preview_layers_data; | |
1156 | + } | |
1157 | + | |
1158 | + // createImagePreviewLayers(layer_slug, data) { | |
1159 | + // | |
1160 | + // } | |
1161 | + | |
1162 | + createImagePreviewLayer(layer_slug, data) { | |
1163 | + this._image_preview_layers_data[layer_slug] = data; | |
1164 | + this._image_preview_layers_rect[layer_slug] = []; | |
1165 | + this._image_preview_layers_live[layer_slug] = true; | |
1166 | + let datum; | |
1167 | + for (let key in data) { | |
1168 | + if (data.hasOwnProperty(key)) { | |
1169 | + if ("columns" === key) continue; | |
1170 | + // console.debug("datum", key, data[key]); | |
1171 | + datum = data[key]; | |
1172 | + // { time_min: "2017-09-09 00:13:49.845227", thumbnail_url: "http://voparis-srv.obspm.fr/vo/planeto/apis/dataset/Bastet/Saturn_-_2017_14_Feb-09_Sept/od9u21u4q_proc_small.jpg", time_max: "2017-09-09 00:58:50.044784", external_link: "http://apis.obspm.fr/spip.php?page=observation&id=bas_8167" } | |
1173 | + this._image_preview_layers_rect[layer_slug].push( | |
1174 | + this.createImagePreviewLayerRect( | |
1175 | + datum.time_min, datum.time_max | |
1176 | + ) | |
1177 | + ); | |
1178 | + } | |
1179 | + } | |
1180 | + } | |
1181 | + | |
1182 | + isImagePreviewLayerLive(layer_slug) { | |
1183 | + if (layer_slug in this._image_preview_layers_live) { | |
1184 | + return this._image_preview_layers_live[layer_slug]; | |
1185 | + } else { | |
1186 | + return false; | |
1187 | + } | |
1188 | + } | |
1189 | + | |
1190 | + resizeImagePreviewLayers() { | |
1191 | + for (let layer_slug in this._image_preview_layers_live) { | |
1192 | + if (!(this._image_preview_layers_live.hasOwnProperty(layer_slug))) | |
1193 | + continue; | |
1194 | + console.debug("Resize image layer", layer_slug); | |
1195 | + this.resizeImagePreviewLayer(layer_slug); | |
1196 | + } | |
1197 | + } | |
1198 | + | |
1199 | + resizeImagePreviewLayer(layer_slug) { | |
1200 | + if (! this.isImagePreviewLayerLive(layer_slug)) { | |
1201 | + return; | |
1202 | + } | |
1203 | + | |
1204 | + const rects = this._image_preview_layers_rect[layer_slug]; | |
1205 | + if (! rects) { | |
1206 | + console.error( | |
1207 | + "Tried to resize an image preview layer without rects.", | |
1208 | + layer_slug, this | |
1209 | + ); | |
1210 | + return; | |
1211 | + } | |
1212 | + | |
1213 | + const data = this._image_preview_layers_data[layer_slug]; | |
1214 | + if (! data) { | |
1215 | + console.error( | |
1216 | + "Tried to resize an image preview layer without data.", | |
1217 | + layer_slug, this | |
1218 | + ); | |
1219 | + return; | |
1220 | + } | |
1221 | + | |
1222 | + let animate = true; | |
1223 | + let t = this.svg.transition().duration(750); | |
1224 | + | |
1225 | + let width; | |
1226 | + let started_at; | |
1227 | + let stopped_at; | |
1228 | + //for (let [dkey, datum] of data) { // nope | |
1229 | + let datum; | |
1230 | + for (let dkey in data) { | |
1231 | + if ( ! data.hasOwnProperty(dkey)) continue; | |
1232 | + if ("columns" === dkey) continue; | |
1233 | + | |
1234 | + datum = data[dkey]; | |
1235 | + | |
1236 | + // We assume that moment(moment(x)) == moment(x) (it's true) | |
1237 | + // Remove these moment() casts for some more perfs since we're | |
1238 | + // already casting to moment() in the data pre-process. | |
1239 | + started_at = moment(datum.time_min); | |
1240 | + stopped_at = moment(datum.time_max); | |
1241 | + | |
1242 | + //console.debug("Resize image layer rect", started_at, stopped_at); | |
1243 | + | |
1244 | + width = this.xScale(stopped_at) - this.xScale(started_at); | |
1245 | + // Under 2 pixels wide the rects appear glitchy | |
1246 | + width = Math.max(2, width); | |
1247 | + | |
1248 | + if (animate) { | |
1249 | + rects[dkey] | |
1250 | + .transition(t) | |
1251 | + .attr('x', this.xScale(started_at)) | |
1252 | + .attr('width', width); | |
1253 | + } else { | |
1254 | + rects[dkey] | |
1255 | + .attr('x', this.xScale(started_at)) | |
1256 | + .attr('width', width); | |
1257 | + } | |
1258 | + | |
1259 | + } | |
1260 | + } | |
1261 | + | |
1262 | + static get COLOR_IMAGE_PREVIEW_LAYER() { | |
1263 | + return '#FF339942'; | |
1264 | + } | |
1265 | + | |
1266 | + static get COLOR_IMAGE_PREVIEW_LAYER_ACTIVE() { | |
1267 | + return '#FF3399D2'; | |
1268 | + } | |
1269 | + | |
1270 | + createImagePreviewLayerRect(started_at, stopped_at) { | |
1271 | + return this.createLayerRect( | |
1272 | + started_at, stopped_at, TimeSeries.COLOR_IMAGE_PREVIEW_LAYER | |
1273 | + ); | |
1274 | + } | |
1275 | + | |
1276 | + highlightImagePreviewLayerRect(layer_slug, rect_key) { | |
1277 | + const rects = this._image_preview_layers_rect[layer_slug]; | |
1278 | + if (! rects) { | |
1279 | + console.error( | |
1280 | + "Tried to highlight an image preview layer without rects.", | |
1281 | + layer_slug, this | |
1282 | + ); | |
1283 | + return; | |
1284 | + } | |
1285 | + | |
1286 | + const rect_to_highlight = rects[rect_key]; | |
1287 | + if (! rect_to_highlight) { | |
1288 | + console.error( | |
1289 | + "Tried to highlight a non-existent rect.", | |
1290 | + layer_slug, rect_key, this | |
1291 | + ); | |
1292 | + return; | |
1293 | + } | |
1294 | + | |
1295 | + for (let rect of rects) { | |
1296 | + TimeSeries.setRectColor( | |
1297 | + rect, TimeSeries.COLOR_IMAGE_PREVIEW_LAYER | |
1298 | + ); | |
1299 | + } | |
1300 | + TimeSeries.setRectColor( | |
1301 | + rect_to_highlight, TimeSeries.COLOR_IMAGE_PREVIEW_LAYER_ACTIVE | |
1302 | + ); | |
1303 | + | |
1304 | + } | |
1305 | + | |
1306 | + static setRectColor(rect, color) { | |
1307 | + rect.attr('fill', color); | |
1308 | + } | |
1309 | + | |
1310 | + showImagePreviewLayer(layer_slug, data) { | |
1311 | + return new Promise((resolve, reject) => { | |
1312 | + //console.log("showImagePreviewLayer Promiseโฆ"); | |
1313 | + | |
1314 | + if (this._image_preview_layers_live[layer_slug]) { | |
1315 | + console.warn("TS.showImagePreviewLayer: already shown."); | |
1316 | + resolve(); // we're already shown | |
1317 | + } | |
1318 | + | |
1319 | + if ( ! this.hasImagePreviewLayer(layer_slug)) { | |
1320 | + this.createImagePreviewLayer(layer_slug, data); | |
1321 | + } | |
1322 | + | |
1323 | + this._image_preview_layers_live[layer_slug] = true; | |
1324 | + | |
1325 | + this.resizeImagePreviewLayer(layer_slug); | |
1326 | + | |
1327 | + const rects = this._image_preview_layers_rect[layer_slug]; | |
1328 | + if (! rects) { | |
1329 | + console.error( | |
1330 | + "Tried to show an image preview layer without rects.", | |
1331 | + layer_slug, this | |
1332 | + ); | |
1333 | + return; | |
1334 | + } | |
1335 | + | |
1336 | + for (let rect of rects) { | |
1337 | + rect.style("display", null) | |
1338 | + } | |
1339 | + | |
1340 | + resolve(); | |
1341 | + }); | |
1342 | + } | |
1343 | + | |
1344 | + hideImagePreviewLayer(layer_slug) { | |
1345 | + // console.log( | |
1346 | + // "TS.hideImagePreviewLayer()", | |
1347 | + // this._image_preview_layers_live[layer_slug], | |
1348 | + // this._image_preview_layers_live | |
1349 | + // ); | |
1350 | + if ( ! this._image_preview_layers_live[layer_slug]) { | |
1351 | + console.warn("TS.hideImagePreviewLayer: already hidden."); | |
1352 | + return; // we're already hidden | |
1353 | + } | |
1354 | + this._image_preview_layers_live[layer_slug] = false; | |
1355 | + | |
1356 | + const rects = this._image_preview_layers_rect[layer_slug]; | |
1357 | + //console.debug("RECTS", rects); | |
1358 | + | |
1359 | + if (! rects) { | |
1360 | + console.error( | |
1361 | + "Tried to hide an image preview layer without rects.", | |
1362 | + layer_slug, this | |
1363 | + ); | |
1364 | + return; | |
1365 | + } | |
1366 | + | |
1367 | + | |
1368 | + for (let rect of rects) { | |
1369 | + rect.style("display", "none"); | |
1370 | + } | |
1371 | + } | |
1372 | + | |
1373 | + showImagePreview(image_url, external_link, comment) { | |
1374 | + const box = jQuery('#time_series_cursor_morebox'); | |
1375 | + const img = jQuery('#time_series_cursor_image'); | |
1376 | + const cil = jQuery('#time_series_cursor_image_link'); | |
1377 | + const cic = jQuery('#time_series_cursor_image_comment'); | |
1378 | + const lnk = jQuery('#time_series_cursor_link'); | |
1379 | + | |
1380 | + const previous_image_url = img.attr('src'); | |
1381 | + if (image_url === previous_image_url) { | |
1382 | + return; | |
1383 | + } | |
1384 | + | |
1385 | + img.attr('src', image_url); | |
1386 | + lnk.attr('href', external_link); | |
1387 | + cic.html(comment); | |
1388 | + | |
1389 | + // Interesting ; using the regex in the prototype yields โฆ fails | |
1390 | + // Race conditions are probably reunited, afaik. | |
1391 | + //const match = this.imageRegex.exec(image_url); | |
1392 | + // Let's compile our regex every time instead. Optimize later. | |
1393 | + const imageRegex = /(.+?)_small([.][a-zA-Z0-9]+)$/g; | |
1394 | + const match = imageRegex.exec(image_url); | |
1395 | + let image_link = "#"; | |
1396 | + if (match) { | |
1397 | + //console.log("Matched image url!", match, image_url); | |
1398 | + image_link = match[1] + match[2]; // remove the `_small` | |
1399 | + } else { | |
1400 | + console.warn("Could not find bigger image for url:", image_url); | |
1401 | + } | |
1402 | + cil.attr('href', image_link); | |
1403 | + | |
1404 | + box.removeClass('hidden'); | |
1405 | + } | |
1406 | + | |
1407 | + // hideImagePreview() {} // TODO | |
1408 | + | |
1409 | + updateImagePreviewFromCursor(x0) { | |
1410 | + let data = this._image_preview_layers_data; | |
1411 | + let i; | |
1412 | + let layer_slug; | |
1413 | + for (layer_slug in data) { | |
1414 | + if ( ! data.hasOwnProperty(layer_slug)) | |
1415 | + continue; | |
1416 | + if ( ! this.isImagePreviewLayerLive(layer_slug)) { | |
1417 | + continue; | |
1418 | + } | |
1419 | + i = this.bisectIpod(data[layer_slug], x0, 1); | |
1420 | + //console.debug("Found i ", i, x0, data[layer_slug]); | |
1421 | + if (i == null) continue; // ignore errors and data out of range | |
1422 | + break; | |
1423 | + } | |
1424 | + if (i == null) { | |
1425 | + //console.debug("No live image preview found."); | |
1426 | + return; | |
1427 | + } | |
1428 | + | |
1429 | + let d0 = data[layer_slug][i - 1]; | |
1430 | + let d1 = data[layer_slug][i]; | |
1431 | + if (!d1 || !d0) { | |
1432 | + //this.hideImagePreview(); | |
1433 | + return; | |
1434 | + } | |
1435 | + let d = x0 - d0.time_min > d1.time_min - x0 ? d1 : d0; | |
1436 | + | |
1437 | + //console.info("Cursor", x0, d0); | |
1438 | + | |
1439 | + this.highlightImagePreviewLayerRect( | |
1440 | + layer_slug, d === d0 ? i-1 : i | |
1441 | + ); | |
1442 | + this.showImagePreview( | |
1443 | + d.thumbnail_url, d.external_link, | |
1444 | + "Integration: "+d.time_min.from(d.time_max, true) | |
1445 | + ); | |
1446 | + } | |
1447 | + | |
1448 | + /////////////////////////////////////////////////////////////////////// | |
1449 | + | |
992 | 1450 | showCursor() { |
993 | 1451 | return this.focus.style("display", null); |
994 | 1452 | } |
... | ... | @@ -998,40 +1456,53 @@ |
998 | 1456 | } |
999 | 1457 | |
1000 | 1458 | moveCursor(x0) { |
1001 | - let i; | |
1002 | - let d0; | |
1003 | - let d1; | |
1004 | - let d; | |
1005 | - let xx; | |
1006 | - let yy; | |
1007 | - let dx; | |
1008 | - let transform; | |
1009 | - i = this.bisectDate(this.data, x0, 1); | |
1010 | - d0 = this.data[i - 1]; | |
1011 | - d1 = this.data[i]; | |
1459 | + let i = this.bisectDate(this.data, x0, 1); | |
1460 | + let d0 = this.data[i - 1]; | |
1461 | + let d1 = this.data[i]; | |
1012 | 1462 | if (!d1 || !d0) { |
1013 | 1463 | this.hideCursor(); |
1014 | 1464 | return; |
1015 | 1465 | } |
1016 | - d = x0 - d0.x > d1.x - x0 ? d1 : d0; | |
1017 | - xx = this.xScale(d.x); | |
1018 | - yy = this.yScale(d.y); | |
1466 | + let d = x0 - d0.x > d1.x - x0 ? d1 : d0; | |
1467 | + let xx = this.xScale(d.x); | |
1468 | + let yy = this.yScale(d.y); | |
1019 | 1469 | const mirrored = this.plotWidth != null && xx > this.plotWidth / 2; |
1020 | - dx = 8; | |
1470 | + let dx = 8; | |
1021 | 1471 | if (mirrored) { |
1022 | 1472 | dx = -1 * dx; |
1023 | 1473 | } |
1024 | - transform = `translate(${xx}, ${yy})`; | |
1025 | - this.cursorCircle.attr("transform", transform); | |
1026 | - this.cursorValue.attr("transform", transform).text(d.y).attr('text-anchor', mirrored ? 'end' : 'start').attr("dx", dx); | |
1027 | - this.cursorValueShadow.attr("transform", transform).text(d.y).attr('text-anchor', mirrored ? 'end' : 'start').attr("dx", dx); | |
1028 | - this.cursorDate.attr("transform", transform).text(this.timeFormat(d.x)).attr('text-anchor', mirrored ? 'end' : 'start').attr("dx", dx); | |
1029 | - this.cursorDateShadow.attr("transform", transform).text(this.timeFormat(d.x)).attr('text-anchor', mirrored ? 'end' : 'start').attr("dx", dx); | |
1474 | + const transform = `translate(${xx}, ${yy})`; | |
1475 | + this.cursorCircle | |
1476 | + .attr("transform", transform); | |
1477 | + this.cursorValue | |
1478 | + .text(d.y) | |
1479 | + .attr("transform", transform) | |
1480 | + .attr('text-anchor', mirrored ? 'end' : 'start') | |
1481 | + .attr("dx", dx); | |
1482 | + this.cursorValueShadow | |
1483 | + .text(d.y) | |
1484 | + .attr("transform", transform) | |
1485 | + .attr('text-anchor', mirrored ? 'end' : 'start') | |
1486 | + .attr("dx", dx); | |
1487 | + this.cursorDate | |
1488 | + .text(this.timeFormat(d.x)) | |
1489 | + .attr("transform", transform) | |
1490 | + .attr('text-anchor', mirrored ? 'end' : 'start') | |
1491 | + .attr("dx", dx); | |
1492 | + this.cursorDateShadow | |
1493 | + .text(this.timeFormat(d.x)) | |
1494 | + .attr("transform", transform) | |
1495 | + .attr('text-anchor', mirrored ? 'end' : 'start') | |
1496 | + .attr("dx", dx); | |
1030 | 1497 | this.showCursor(); |
1498 | + | |
1031 | 1499 | return this; |
1032 | 1500 | } |
1033 | 1501 | } |
1034 | 1502 | |
1503 | + // Don't use the prototype for a regex ; there are race conditions | |
1504 | + //TimeSeries.prototype.imageRegex = /(.+?)_small([.][a-zA-Z0-9]+)$/g; | |
1505 | + TimeSeries.prototype.bisectIpod = d3.bisector(d => d.time_min).left; | |
1035 | 1506 | TimeSeries.prototype.bisectDate = d3.bisector(d => d.x).left; |
1036 | 1507 | TimeSeries.prototype.timeFormat = d3.utcFormat("%Y-%m-%d %H:%M"); |
1037 | 1508 | ... | ... |
web/static/js/swapp.ls
web/view/home.html.jinja2
... | ... | @@ -178,7 +178,7 @@ |
178 | 178 | <section class="section-drawer"> |
179 | 179 | {% for layer in config.layers.campaigns %} |
180 | 180 | <label class="mdl-radio mdl-js-radio mdl-js-ripple-effect" for="option-layer-{{ layer.slug }}" title="{{ layer.desc }}"> |
181 | - <input type="checkbox" id="option-layer-{{ layer.slug }}" class="mdl-radio__button option-layer" name="option-layer-{{ layer.slug }}" value="{{ layer.slug }}" {{ 'disabled' if not layer.data }}> | |
181 | + <input type="checkbox" id="option-layer-{{ layer.slug }}" class="mdl-radio__button option-layer option-layer-campaign" name="option-layer-{{ layer.slug }}" value="{{ layer.slug }}" {{ 'disabled' if not layer.live }}> | |
182 | 182 | <span class="mdl-radio__label">{{ layer.name }}</span> |
183 | 183 | </label> |
184 | 184 | <br /> |
... | ... | @@ -197,7 +197,7 @@ |
197 | 197 | <section class="section-drawer"> |
198 | 198 | {% for layer in config.layers.catalogs %} |
199 | 199 | <label class="mdl-radio mdl-js-radio mdl-js-ripple-effect" for="option-layer-{{ layer.slug }}" title="{{ layer.desc }}"> |
200 | - <input type="checkbox" id="option-layer-{{ layer.slug }}" class="mdl-radio__button option-layer" name="option-layer-{{ layer.slug }}" value="{{ layer.slug }}" {{ 'disabled' if not layer.data }}> | |
200 | + <input type="checkbox" id="option-layer-{{ layer.slug }}" class="mdl-radio__button option-layer option-layer-catalog" name="option-layer-{{ layer.slug }}" value="{{ layer.slug }}" {{ 'disabled' if not layer.data }}> | |
201 | 201 | <span class="mdl-radio__label">{{ layer.name }}</span> |
202 | 202 | </label> |
203 | 203 | <br /> |
... | ... | @@ -250,7 +250,32 @@ |
250 | 250 | Alert |
251 | 251 | </button> |
252 | 252 | {% endif %} |
253 | + | |
253 | 254 | </div> |
255 | + <div> | |
256 | + <div id="time_series_cursor_morebox" class="hidden"> | |
257 | + <a | |
258 | + id="time_series_cursor_image_link" | |
259 | + target="_blank" | |
260 | + href="#"> | |
261 | + <img | |
262 | + id="time_series_cursor_image" | |
263 | + {# src=""#} | |
264 | + src="http://voparis-srv.obspm.fr/vo/planeto/apis/dataset/Bastet/Saturn_-_2017_14_Feb-09_Sept/od9u09rgq_proc_small.jpg" | |
265 | + alt="Image Capture of Aurorae" | |
266 | + > | |
267 | + </a> | |
268 | + <div id="time_series_cursor_image_extra"> | |
269 | + <a id="time_series_cursor_link" target="_blank"> | |
270 | + View details on APIS. | |
271 | + </a> | |
272 | + <p id="time_series_cursor_image_comment"> | |
273 | + Nothing to comment. | |
274 | + </p> | |
275 | + </div> | |
276 | + </div> | |
277 | + </div> | |
278 | + | |
254 | 279 | </div> |
255 | 280 | <div class="mdl-cell mdl-cell--8-col mdl-cell--8-col-tablet mdl-cell--4-col-phone"> |
256 | 281 | <section id="time_series"> |
... | ... | @@ -267,391 +292,9 @@ |
267 | 292 | {#### CSS ####################################################################} |
268 | 293 | |
269 | 294 | {% block styles %} |
270 | - <style> | |
271 | - .mdl-layout__drawer hr { | |
272 | - margin: 0.5em 0; | |
273 | - } | |
274 | - .mdl-layout__drawer .mdl-layout-title { | |
275 | - line-height: 42px; | |
276 | - display: inline-block; | |
277 | - } | |
278 | - .mdl-layout__drawer > details > summary { | |
279 | - padding-left: 15px; | |
280 | - cursor: pointer; | |
281 | - outline: none; | |
282 | - } | |
283 | - .mdl-layout__drawer .mdl-layout-title:first-of-type { | |
284 | - line-height: 60px; | |
285 | - } | |
286 | - | |
287 | - .plots-buttons { | |
288 | - text-align: center; | |
289 | - margin: 0 auto; | |
290 | - } | |
291 | - .plots-buttons button { | |
292 | - margin: 1em 1em; | |
293 | - } | |
294 | - | |
295 | - #time_series svg { | |
296 | - cursor: crosshair; | |
297 | - } | |
298 | - #time_series .help { | |
299 | - position: absolute; | |
300 | - text-align: center; | |
301 | - font-size: 0.9em; | |
302 | - font-style: italic; | |
303 | - color: darkgrey; | |
304 | - display: none; | |
305 | - } | |
306 | - #time_series:hover .help { | |
307 | - display: block; | |
308 | - } | |
309 | - #time_series svg .brush .selection { | |
310 | - fill: #efa02c; | |
311 | - fill-opacity: 0.382; | |
312 | - } | |
313 | - .axis path, .axis line { | |
314 | - fill: none; | |
315 | -{# stroke: #f4f4f4;#} | |
316 | - shape-rendering: crispEdges; | |
317 | - stroke-width: 1px; | |
318 | - } | |
319 | - path.line { | |
320 | - fill: none; | |
321 | - stroke: steelblue; | |
322 | - stroke-width: 1px; | |
323 | - } | |
324 | - path.predictive-line { | |
325 | - fill: none; | |
326 | - stroke: #ff4081; | |
327 | - stroke-width: 2px; | |
328 | - } | |
329 | - circle.cursor-circle { | |
330 | - fill: black; | |
331 | - stroke: rgba(20, 20, 20, 0.48); | |
332 | - } | |
333 | - text.cursor-text { | |
334 | -{# font-family: 'Ubuntu', 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif;#} | |
335 | -{# font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;#} | |
336 | - font-family: "Ubuntu Mono", 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; | |
337 | - text-align: right; | |
338 | - } | |
339 | - text.cursor-text-shadow { | |
340 | - stroke: white; | |
341 | - stroke-width: 5px; | |
342 | - opacity: 0.777 | |
343 | - } | |
344 | - path.orbit.orbit_section { | |
345 | - fill: none; | |
346 | - stroke: steelblue; | |
347 | - stroke-width: 1.5px; | |
348 | - } | |
349 | - ellipse.orbit.orbit_ellipse { | |
350 | - fill: none; | |
351 | - stroke: #a3a3a3; | |
352 | - stroke-width: 1px; | |
353 | - stroke-dasharray: 5px; | |
354 | - } | |
355 | - #form_time_interval { | |
356 | - padding-left: 30px; | |
357 | - } | |
358 | - #form_time_interval .mdl-textfield { | |
359 | - padding-top: 0; | |
360 | - } | |
361 | - #started_at, #stopped_at { | |
362 | - width: 85%; | |
363 | - } | |
364 | - .section-drawer { | |
365 | - padding-left: 3em; | |
366 | - } | |
367 | - .targets-filters { | |
368 | - padding-left: 17px; | |
369 | - } | |
370 | - .targets-filters .target { | |
371 | - float: left; | |
372 | - cursor: pointer; | |
373 | - position: relative; | |
374 | - } | |
375 | - .targets-filters .target:not(.active) { | |
376 | - -webkit-filter: grayscale(100%); | |
377 | - -moz-filter: grayscale(100%); | |
378 | - -o-filter: grayscale(100%); | |
379 | -{# -ms-filter: grayscale(100%);#} | |
380 | - filter: grayscale(100%); | |
381 | - } | |
382 | - .targets-filters .target.locked { | |
383 | - cursor: not-allowed; | |
384 | - } | |
385 | - .targets-filters .target.loading { | |
386 | - -webkit-animation-name: keyframes_rotate; | |
387 | - -webkit-animation-duration: 4000ms; | |
388 | - -webkit-animation-iteration-count: infinite; | |
389 | - -webkit-animation-timing-function: linear; | |
390 | - -moz-animation-name: keyframes_rotate; | |
391 | - -moz-animation-duration: 4000ms; | |
392 | - -moz-animation-iteration-count: infinite; | |
393 | - -moz-animation-timing-function: linear; | |
394 | - -ms-animation-name: keyframes_rotate; | |
395 | - -ms-animation-duration: 4000ms; | |
396 | - -ms-animation-iteration-count: infinite; | |
397 | - -ms-animation-timing-function: linear; | |
398 | - animation-name: keyframes_rotate; | |
399 | - animation-duration: 4000ms; | |
400 | - animation-iteration-count: infinite; | |
401 | - animation-timing-function: linear; | |
402 | - } | |
403 | - | |
404 | - .targets-filters .target .decorator { | |
405 | - position: absolute; | |
406 | - top: 0; | |
407 | - left: 0; | |
408 | - display: none; | |
409 | - } | |
410 | - .targets-filters .target.empty .decorator.empty { | |
411 | - display: block; | |
412 | - } | |
413 | - .targets-filters .target.error .decorator.error { | |
414 | - display: block; | |
415 | - } | |
416 | - .targets-filters .target .decorator.loading { | |
417 | - top: 19px; | |
418 | - left: 19px; | |
419 | - } | |
420 | - .targets-filters .target.loading .decorator.loading { | |
421 | - display: block; | |
422 | - } | |
423 | - | |
424 | - #parameters .parameter { | |
425 | - outline: 0; | |
426 | - padding-top: 7px; | |
427 | - padding-bottom: 7px; | |
428 | - | |
429 | - } | |
430 | - #parameters .parameter.active { | |
431 | - background-color: #c8d3e1; | |
432 | - } | |
433 | - | |
434 | - | |
435 | - {# CSS Spinners #} | |
436 | - | |
437 | - #download_spinner { | |
438 | - display: none; | |
439 | - top: 2px; | |
440 | - left: 4px; | |
441 | - width: 14px; | |
442 | - height: 14px; | |
443 | - } | |
444 | - | |
445 | - #plots_loader { | |
446 | - position: fixed; | |
447 | - top: 0; left: 0; bottom: 0; right: 0; | |
448 | - height: 100%; | |
449 | - width: 100%; | |
450 | - background-color: #fff; | |
451 | - z-index: 1000; | |
452 | - } | |
453 | - | |
454 | - #plots_loader .loader-text { | |
455 | - width: 200px; | |
456 | - height: 30px; | |
457 | - position: absolute; | |
458 | - top: -240px; left: -32px; bottom: 0; right: 0; | |
459 | - margin: auto; | |
460 | - text-align: center; | |
461 | - font-size: 1.0em; | |
462 | - font-style: italic; | |
463 | - color: darkgrey; | |
464 | - } | |
465 | - | |
466 | - #plots_loader .img { | |
467 | - width: 100px; | |
468 | - height: 100px; | |
469 | - border-radius: 100%; | |
470 | - position: absolute; | |
471 | - border: 1px solid #6978ff; | |
472 | - animation: keyframes_rotate 1s; | |
473 | - animation-iteration-count: infinite; | |
474 | - transition: 2s; | |
475 | - border-bottom: none; | |
476 | - border-right: none; | |
477 | - animation-timing-function: linear; | |
478 | - margin-left: -70px; | |
479 | - margin-top: -70px; | |
480 | - left: 50%; | |
481 | - top: 50%; | |
482 | - } | |
483 | - | |
484 | - #plots_loader #plots_loader_img2 { | |
485 | - width: 90px; | |
486 | - height: 90px; | |
487 | - left: 50.35%; | |
488 | - top: 50.7%; | |
489 | - animation-delay: .2s; | |
490 | - } | |
491 | - | |
492 | - #plots_loader #plots_loader_img3 { | |
493 | - width: 80px; | |
494 | - height: 80px; | |
495 | - left: 50.70%; | |
496 | - top: 51.4%; | |
497 | - animation-delay: .4s; | |
498 | - } | |
499 | - | |
500 | - #plots_loader #plots_loader_img4 { | |
501 | - width: 70px; | |
502 | - height: 70px; | |
503 | - left: 51.05%; | |
504 | - top: 52.1%; | |
505 | - animation-delay: .6s; | |
506 | - } | |
507 | - | |
508 | - #plots_loader #plots_loader_img5 { | |
509 | - width: 60px; | |
510 | - height: 60px; | |
511 | - left: 51.40%; | |
512 | - top: 52.8%; | |
513 | - animation-delay: .8s; | |
514 | - } | |
515 | - | |
516 | - @keyframes keyframes_rotate { | |
517 | - from { | |
518 | - transform: rotate(0deg); | |
519 | - } | |
520 | - 50% { | |
521 | - transform: rotate(180deg); | |
522 | - } | |
523 | - 100% { | |
524 | - transform: rotate(360deg); | |
525 | - } | |
526 | - } | |
527 | - | |
528 | - .small-loader-container { | |
529 | - width: 27px; | |
530 | - margin: 0 auto; | |
531 | - background: none; | |
532 | - pointer-events: none; | |
533 | - } | |
534 | - .small-loader-circle-1 { | |
535 | - height: 27px; | |
536 | - width: 27px; | |
537 | - background: rgba(255, 238, 195, 0.72); | |
538 | - } | |
539 | - .small-loader-circle-2 { | |
540 | - height: 22px; | |
541 | - width: 22px; | |
542 | - background: none; | |
543 | - } | |
544 | - .small-loader-circle-3 { | |
545 | - height: 18px; | |
546 | - width: 18px; | |
547 | - background: rgba(29, 65, 255, 0.9); | |
548 | - } | |
549 | - .small-loader-circle-4 { | |
550 | - height: 13px; | |
551 | - width: 13px; | |
552 | - background: none; | |
553 | - } | |
554 | - .small-loader-circle-5 { | |
555 | - height: 9px; | |
556 | - width: 9px; | |
557 | - background: rgba(238, 238, 238, 0.8); | |
558 | - } | |
559 | - .small-loader-circle-6 { | |
560 | - height: 4px; | |
561 | - width: 4px; | |
562 | - background: none; | |
563 | - } | |
564 | - .small-loader-circle-7 { | |
565 | - height: 2px; | |
566 | - width: 2px; | |
567 | - background: rgb(110, 102, 255); | |
568 | - } | |
569 | - .small-loader-circle-1, | |
570 | - .small-loader-circle-2, | |
571 | - .small-loader-circle-3, | |
572 | - .small-loader-circle-4, | |
573 | - .small-loader-circle-5, | |
574 | - .small-loader-circle-6, | |
575 | - .small-loader-circle-7 { | |
576 | - border-bottom: none; | |
577 | - border-radius: 50%; | |
578 | - -o-border-radius: 50%; | |
579 | - -ms-border-radius: 50%; | |
580 | - -webkit-border-radius: 50%; | |
581 | - -moz-border-radius: 50%; | |
582 | - box-shadow: 0px 0px 0px rgba(0,0,0,0.1); | |
583 | - -o-box-shadow: 0px 0px 0px rgba(0,0,0,0.1); | |
584 | - -ms-box-shadow: 0px 0px 0px rgba(0,0,0,0.1); | |
585 | - -webkit-box-shadow: 0px 0px 0px rgba(0,0,0,0.1); | |
586 | - -moz-box-shadow: 0px 0px 0px rgba(0,0,0,0.1); | |
587 | - animation-name: small-loader-spin; | |
588 | - -o-animation-name: small-loader-spin; | |
589 | - -ms-animation-name: small-loader-spin; | |
590 | - -webkit-animation-name: small-loader-spin; | |
591 | - -moz-animation-name: small-loader-spin; | |
592 | - animation-duration: 4600ms; | |
593 | - -o-animation-duration: 4600ms; | |
594 | - -ms-animation-duration: 4600ms; | |
595 | - -webkit-animation-duration: 4600ms; | |
596 | - -moz-animation-duration: 4600ms; | |
597 | - animation-iteration-count: infinite; | |
598 | - -o-animation-iteration-count: infinite; | |
599 | - -ms-animation-iteration-count: infinite; | |
600 | - -webkit-animation-iteration-count: infinite; | |
601 | - -moz-animation-iteration-count: infinite; | |
602 | - animation-timing-function: linear; | |
603 | - -o-animation-timing-function: linear; | |
604 | - -ms-animation-timing-function: linear; | |
605 | - -webkit-animation-timing-function: linear; | |
606 | - -moz-animation-timing-function: linear; | |
607 | - } | |
608 | - | |
609 | - @keyframes small-loader-spin { | |
610 | - from { | |
611 | - transform: rotate(0deg); | |
612 | - } | |
613 | - to { | |
614 | - transform: rotate(360deg); | |
615 | - } | |
616 | - } | |
617 | - | |
618 | - @-o-keyframes small-loader-spin { | |
619 | - from { | |
620 | - -o-transform: rotate(0deg); | |
621 | - } | |
622 | - to { | |
623 | - -o-transform: rotate(360deg); | |
624 | - } | |
625 | - } | |
626 | - | |
627 | - @-ms-keyframes small-loader-spin { | |
628 | - from { | |
629 | - -ms-transform: rotate(0deg); | |
630 | - } | |
631 | - to { | |
632 | - -ms-transform: rotate(360deg); | |
633 | - } | |
634 | - } | |
635 | - | |
636 | - @-webkit-keyframes small-loader-spin { | |
637 | - from { | |
638 | - -webkit-transform: rotate(0deg); | |
639 | - } | |
640 | - to { | |
641 | - -webkit-transform: rotate(360deg); | |
642 | - } | |
643 | - } | |
644 | - | |
645 | - @-moz-keyframes small-loader-spin { | |
646 | - from { | |
647 | - -moz-transform: rotate(0deg); | |
648 | - } | |
649 | - to { | |
650 | - -moz-transform: rotate(360deg); | |
651 | - } | |
652 | - } | |
653 | - | |
654 | - </style> | |
295 | + <link rel="stylesheet" type="text/css" href="{{ static('css/home.css') }}"> | |
296 | +{# <style>#} | |
297 | +{# </style>#} | |
655 | 298 | {% endblock %} |
656 | 299 | |
657 | 300 | |
... | ... | @@ -664,10 +307,11 @@ |
664 | 307 | <script type="application/javascript" src="{{ static('js/main.js') }}"></script> |
665 | 308 | <script type="application/javascript"> |
666 | 309 | |
667 | -var configuration = { | |
310 | +var sw_configuration = { | |
668 | 311 | time_series_container: '#time_series', |
669 | 312 | orbits_container: '#orbits', |
670 | 313 | api: { |
314 | + 'root': "{{ request.url_root }}", | |
671 | 315 | 'data_for_interval': "{{ request.url_root }}<target>_{{ input_slug }}_<started_at>_<stopped_at>.csv", |
672 | 316 | 'download': "{{ request.url_root }}<targets>_{{ input_slug }}_<started_at>_<stopped_at>.cdf", |
673 | 317 | 'samp': "{{ request.url_root }}<targets>_{{ input_slug }}_<started_at>_<stopped_at>.cdf" |
... | ... | @@ -724,7 +368,7 @@ var configuration = { |
724 | 368 | var sw; |
725 | 369 | jQuery().ready(function($){ |
726 | 370 | // Space Weather app itself, handling data downloads and plot draws. |
727 | - sw = new SpaceWeather(configuration); | |
371 | + sw = new SpaceWeather(sw_configuration); | |
728 | 372 | sw.init("{{ started_at }}Z", "{{ stopped_at }}Z"); |
729 | 373 | |
730 | 374 | // User Interface (except plots' interactivity, such as mouse hovers) |
... | ... | @@ -784,13 +428,27 @@ jQuery().ready(function($){ |
784 | 428 | return false; |
785 | 429 | }); |
786 | 430 | |
787 | - $('.option-layer').on("click", function(e){ | |
431 | + $('.option-layer-catalog').on("click", function(e){ | |
788 | 432 | var catalog_slug = $(this).attr('value'); |
789 | 433 | var checked = $(this).prop('checked'); |
790 | 434 | if (checked) { sw.showCatalogLayer(catalog_slug); } |
791 | 435 | else { sw.hideCatalogLayer(catalog_slug); } |
792 | 436 | }); |
793 | 437 | |
438 | + $('#option-layer-hstsaturnobservations').on("click", function(e){ | |
439 | + var that = $(this); | |
440 | + var catalog_slug = $(this).attr('value'); | |
441 | + var checked = $(this).prop('checked'); | |
442 | + if (checked) { | |
443 | + $(this).addClass('loading'); | |
444 | + sw.showImagePreviewLayer("saturn", catalog_slug).finally( function(){ | |
445 | + that.removeClass('loading'); | |
446 | + }); | |
447 | + } else { | |
448 | + sw.hideImagePreviewLayer("saturn", catalog_slug); | |
449 | + } | |
450 | + }); | |
451 | + | |
794 | 452 | var download_spinner = $('#download_spinner'); |
795 | 453 | var waiting_for_download = false; |
796 | 454 | |
... | ... | @@ -861,6 +519,7 @@ jQuery().ready(function($){ |
861 | 519 | var connector = new samp.Connector("Sender", { |
862 | 520 | "samp.name": "Heliopropa", |
863 | 521 | "samp.description.text": "{{ config.meta.description }}", |
522 | + // {{ request.url_root }} <- use this instead ? | |
864 | 523 | "samp.icon.url": "http://heliopropa.irap.omp.eu/static/img/target/earth_128.png", |
865 | 524 | }); |
866 | 525 | connector.onHubAvailability(onHubAvailability, 7000); | ... | ... |