Commit d1c44c51ee5a6d366acecb225cb0d8752aeeace8
1 parent
952e3d8f
Exists in
master
and in
2 other branches
Enable Earth
Enable more precise configuration of each models' parameters
Showing
7 changed files
with
138 additions
and
59 deletions
Show diff stats
CHANGELOG.md
@@ -5,13 +5,22 @@ | @@ -5,13 +5,22 @@ | ||
5 | - [ ] Add a README to the download tarball | 5 | - [ ] Add a README to the download tarball |
6 | - [ ] Set the log level to _error_ in production (see `web/run.py`) | 6 | - [ ] Set the log level to _error_ in production (see `web/run.py`) |
7 | - [ ] CRON statements to call the cache cleanup and warmup | 7 | - [ ] CRON statements to call the cache cleanup and warmup |
8 | -- [ ] Cache warmup (generate for today's default interval) `/cache/warmup` | ||
9 | - [ ] Give the future data another color | 8 | - [ ] Give the future data another color |
10 | 9 | ||
11 | An heliospheric propagation 1D MHD model for solar wind prediction at planets, probes and comets. | 10 | An heliospheric propagation 1D MHD model for solar wind prediction at planets, probes and comets. |
12 | 11 | ||
13 | 12 | ||
14 | 13 | ||
14 | +## 1.0.0-rc5 | ||
15 | + | ||
16 | +- [ ] Rework the images of Rosetta and Juno | ||
17 | +- [ ] Cache warmup (generate for today's default interval) `/cache/warmup` | ||
18 | +- [ ] Improve the generated CDF for SAMP | ||
19 | +- [ ] Enable p67 | ||
20 | +- [x] Enable Earth | ||
21 | +- [x] Enable more precise configuration of each models' parameters | ||
22 | + | ||
23 | + | ||
15 | ## 1.0.0-rc4 | 24 | ## 1.0.0-rc4 |
16 | 25 | ||
17 | - [x] Sort times series by closeness to the sun | 26 | - [x] Sort times series by closeness to the sun |
README.md
@@ -24,6 +24,7 @@ It also gathers NetCDF data from AMDA, and serves it as CSV to the plotter. | @@ -24,6 +24,7 @@ It also gathers NetCDF data from AMDA, and serves it as CSV to the plotter. | ||
24 | - `config.yml` : the main configuration file. | 24 | - `config.yml` : the main configuration file. |
25 | - `web/run.py` : the front controller, holding most of the code. | 25 | - `web/run.py` : the front controller, holding most of the code. |
26 | - `web/view/home.html.jinja2` : the HTML template. | 26 | - `web/view/home.html.jinja2` : the HTML template. |
27 | +- `web/static/js/swapp.ls` : most of the javascript client-side. | ||
27 | 28 | ||
28 | 29 | ||
29 | ## Install | 30 | ## Install |
config.yml
@@ -98,15 +98,17 @@ targets: | @@ -98,15 +98,17 @@ targets: | ||
98 | - type: 'planet' | 98 | - type: 'planet' |
99 | slug: 'earth' | 99 | slug: 'earth' |
100 | name: 'Earth' | 100 | name: 'Earth' |
101 | - title: 'Earth (coming soon)' | 101 | + title: 'Earth' |
102 | orbit: | 102 | orbit: |
103 | - models: | ||
104 | - - slug: 'earth_orb_all' | ||
105 | - semimajor: 0 | ||
106 | - semiminor: 0 | 103 | + models: [] # Earth has no orbit models, we hard-coded it to (1, 0) |
107 | models: | 104 | models: |
108 | - - slug: 'tao_earth_sw' | ||
109 | - locked: true | 105 | + - slug: 'omni_hour_all' |
106 | + - slug: 'ace_swepam_real' | ||
107 | + parameters: | ||
108 | + dens: 'Dens' | ||
109 | + vtot: 'Vel' | ||
110 | + temp: 'Temp' | ||
111 | + locked: false | ||
110 | default: true | 112 | default: true |
111 | - type: 'planet' | 113 | - type: 'planet' |
112 | slug: 'mars' | 114 | slug: 'mars' |
@@ -159,6 +161,8 @@ targets: | @@ -159,6 +161,8 @@ targets: | ||
159 | stopped_at: '2014-08-02T00:00:00' | 161 | stopped_at: '2014-08-02T00:00:00' |
160 | - slug: 'p67_orb_all' | 162 | - slug: 'p67_orb_all' |
161 | started_at: '2014-08-02T00:00:00' | 163 | started_at: '2014-08-02T00:00:00' |
164 | + parameters: | ||
165 | + hee: 'XYZ_HEE' | ||
162 | models: | 166 | models: |
163 | - slug: 'tao_ros_sw' | 167 | - slug: 'tao_ros_sw' |
164 | locked: false | 168 | locked: false |
@@ -184,6 +188,8 @@ targets: | @@ -184,6 +188,8 @@ targets: | ||
184 | orbit: | 188 | orbit: |
185 | models: | 189 | models: |
186 | - slug: 'p67_orb_all' | 190 | - slug: 'p67_orb_all' |
191 | + parameters: | ||
192 | + hee: 'XYZ_HEE' | ||
187 | locked: true | 193 | locked: true |
188 | default: false | 194 | default: false |
189 | 195 |
web/run.py
@@ -74,7 +74,7 @@ CACHE_DIR = get_path('../cache') | @@ -74,7 +74,7 @@ CACHE_DIR = get_path('../cache') | ||
74 | # innermost loop of `get_data_for_target`. | 74 | # innermost loop of `get_data_for_target`. |
75 | # The javascript knows the targets' properties under these names. | 75 | # The javascript knows the targets' properties under these names. |
76 | PROPERTIES = ('time', 'vrad', 'vtan', 'vtot', 'btan', 'temp', 'pdyn', 'dens', | 76 | PROPERTIES = ('time', 'vrad', 'vtan', 'vtot', 'btan', 'temp', 'pdyn', 'dens', |
77 | - 'angl', 'xhee', 'yhee') | 77 | + 'atse', 'xhee', 'yhee') |
78 | 78 | ||
79 | # The parameters that the users can handle. | 79 | # The parameters that the users can handle. |
80 | # The slug MUST be one of the properties above. | 80 | # The slug MUST be one of the properties above. |
@@ -119,8 +119,8 @@ PARAMETERS = { | @@ -119,8 +119,8 @@ PARAMETERS = { | ||
119 | 'active': False, | 119 | 'active': False, |
120 | 'position': 50, | 120 | 'position': 50, |
121 | }, | 121 | }, |
122 | - 'angl': { | ||
123 | - 'slug': 'angl', | 122 | + 'atse': { |
123 | + 'slug': 'atse', | ||
124 | 'name': 'Angle T-S-E', | 124 | 'name': 'Angle T-S-E', |
125 | 'title': 'Angle Target-Sun-Earth.', | 125 | 'title': 'Angle Target-Sun-Earth.', |
126 | 'units': 'deg', | 126 | 'units': 'deg', |
@@ -426,9 +426,10 @@ def get_data_for_target(target_config, started_at, stopped_at): | @@ -426,9 +426,10 @@ def get_data_for_target(target_config, started_at, stopped_at): | ||
426 | % (target_config['slug'], str(e))) | 426 | % (target_config['slug'], str(e))) |
427 | try: | 427 | try: |
428 | orbits = target_config['orbit']['models'] | 428 | orbits = target_config['orbit']['models'] |
429 | - except Exception as e: | ||
430 | - abort(500, "Invalid orbit configuration for '%s' : %s" | ||
431 | - % (target_config['slug'], str(e))) | 429 | + except KeyError as e: |
430 | + orbits = [] | ||
431 | + # abort(500, "Invalid orbit configuration for '%s' : %s" | ||
432 | + # % (target_config['slug'], str(e))) | ||
432 | 433 | ||
433 | def _sta_sto(_cnf, _sta, _sto): | 434 | def _sta_sto(_cnf, _sta, _sto): |
434 | if 'started_at' in _cnf: | 435 | if 'started_at' in _cnf: |
@@ -443,12 +444,36 @@ def get_data_for_target(target_config, started_at, stopped_at): | @@ -443,12 +444,36 @@ def get_data_for_target(target_config, started_at, stopped_at): | ||
443 | _s1 = _sto | 444 | _s1 = _sto |
444 | return _s0, _s1 | 445 | return _s0, _s1 |
445 | 446 | ||
447 | + def _read_var(_nc, _keys, _key, mandatory=False): | ||
448 | + try: | ||
449 | + return _nc.variables[_keys[_key]] | ||
450 | + except KeyError: | ||
451 | + pass | ||
452 | + if mandatory: | ||
453 | + raise Exception("No variable '%s' found in NetCDF." % _keys[_key]) | ||
454 | + return [None] * len(_nc.variables['Time']) # slow -- use numpy! | ||
455 | + | ||
456 | + # Override them using the configuration, maybe also put these in config ? | ||
457 | + default_nc_keys = { | ||
458 | + 'hee': 'HEE', | ||
459 | + 'vtot': 'V', | ||
460 | + 'magn': 'B', | ||
461 | + 'temp': 'T', | ||
462 | + 'dens': 'N', | ||
463 | + 'pdyn': 'P_dyn', | ||
464 | + 'atse': 'Delta_angle', | ||
465 | + } | ||
466 | + | ||
446 | precision = "%Y-%m-%dT%H" # model and orbits times are only equal-ish | 467 | precision = "%Y-%m-%dT%H" # model and orbits times are only equal-ish |
447 | orbit_data = {} # keys are datetime as str, values arrays of XY | 468 | orbit_data = {} # keys are datetime as str, values arrays of XY |
448 | 469 | ||
449 | for orbit in orbits: | 470 | for orbit in orbits: |
450 | s0, s1 = _sta_sto(orbit, started_at, stopped_at) | 471 | s0, s1 = _sta_sto(orbit, started_at, stopped_at) |
451 | 472 | ||
473 | + nc_keys = default_nc_keys.copy() | ||
474 | + if 'parameters' in orbit: | ||
475 | + nc_keys.update(orbit['parameters']) | ||
476 | + | ||
452 | orbit_files = retrieve_amda_netcdf( | 477 | orbit_files = retrieve_amda_netcdf( |
453 | target_config['slug'], orbit['slug'], s0, s1 | 478 | target_config['slug'], orbit['slug'], s0, s1 |
454 | ) | 479 | ) |
@@ -457,19 +482,16 @@ def get_data_for_target(target_config, started_at, stopped_at): | @@ -457,19 +482,16 @@ def get_data_for_target(target_config, started_at, stopped_at): | ||
457 | (target_config['name'], orbit_file)) | 482 | (target_config['name'], orbit_file)) |
458 | cdf_handle = Dataset(orbit_file, "r", format="NETCDF4") | 483 | cdf_handle = Dataset(orbit_file, "r", format="NETCDF4") |
459 | times = cdf_handle.variables['Time'] # YYYY DOY HH MM SS .ms | 484 | times = cdf_handle.variables['Time'] # YYYY DOY HH MM SS .ms |
460 | - try: | ||
461 | - data_hee = cdf_handle.variables['HEE'] | ||
462 | - except KeyError: | ||
463 | - data_hee = cdf_handle.variables['XYZ_HEE'] # p67 uses this | 485 | + data_hee = _read_var(cdf_handle, nc_keys, 'hee', mandatory=True) |
464 | 486 | ||
465 | log.debug("%s: aggregating data from '%s'..." % | 487 | log.debug("%s: aggregating data from '%s'..." % |
466 | (target_config['name'], orbit_file)) | 488 | (target_config['name'], orbit_file)) |
467 | - for time, datum_hee in zip(times, data_hee): | 489 | + for ltime, datum_hee in zip(times, data_hee): |
468 | try: | 490 | try: |
469 | - dtime = datetime_from_list(time) | ||
470 | - except Exception as e: | ||
471 | - log.error("Failed to parse time from %s." % time) | ||
472 | - raise e | 491 | + dtime = datetime_from_list(ltime) |
492 | + except Exception: | ||
493 | + log.error("Failed to parse time from %s." % ltime) | ||
494 | + raise | ||
473 | if s0 <= dtime <= s1: | 495 | if s0 <= dtime <= s1: |
474 | dkey = round_time(dtime, 60*60).strftime(precision) | 496 | dkey = round_time(dtime, 60*60).strftime(precision) |
475 | orbit_data[dkey] = datum_hee | 497 | orbit_data[dkey] = datum_hee |
@@ -481,40 +503,66 @@ def get_data_for_target(target_config, started_at, stopped_at): | @@ -481,40 +503,66 @@ def get_data_for_target(target_config, started_at, stopped_at): | ||
481 | model_files = retrieve_amda_netcdf( | 503 | model_files = retrieve_amda_netcdf( |
482 | target_config['slug'], model['slug'], s0, s1 | 504 | target_config['slug'], model['slug'], s0, s1 |
483 | ) | 505 | ) |
506 | + nc_keys = default_nc_keys.copy() | ||
507 | + if 'parameters' in model: | ||
508 | + nc_keys.update(model['parameters']) | ||
509 | + | ||
484 | for model_file in model_files: | 510 | for model_file in model_files: |
485 | - # Time, StartTime, StopTime, V, B, N, T, Delta_angle, P_dyn | ||
486 | log.debug("%s: opening model NETCDF4 '%s'..." % | 511 | log.debug("%s: opening model NETCDF4 '%s'..." % |
487 | (target_config['name'], model_file)) | 512 | (target_config['name'], model_file)) |
488 | cdf_handle = Dataset(model_file, "r", format="NETCDF4") | 513 | cdf_handle = Dataset(model_file, "r", format="NETCDF4") |
514 | + # log.debug(cdf_handle.variables.keys()) | ||
489 | times = cdf_handle.variables['Time'] # YYYY DOY HH MM SS .ms | 515 | times = cdf_handle.variables['Time'] # YYYY DOY HH MM SS .ms |
490 | - data_v = cdf_handle.variables['V'] | ||
491 | - data_b = cdf_handle.variables['B'] | ||
492 | - data_t = cdf_handle.variables['T'] | ||
493 | - data_n = cdf_handle.variables['N'] | ||
494 | - data_p = cdf_handle.variables['P_dyn'] | ||
495 | - data_d = cdf_handle.variables['Delta_angle'] | 516 | + data_v = _read_var(cdf_handle, nc_keys, 'vtot') |
517 | + data_b = _read_var(cdf_handle, nc_keys, 'magn') | ||
518 | + data_t = _read_var(cdf_handle, nc_keys, 'temp') | ||
519 | + data_n = _read_var(cdf_handle, nc_keys, 'dens') | ||
520 | + data_p = _read_var(cdf_handle, nc_keys, 'pdyn') | ||
521 | + data_a = _read_var(cdf_handle, nc_keys, 'atse') | ||
522 | + | ||
523 | + # Usually: | ||
524 | + # Time, StartTime, StopTime, V, B, N, T, Delta_angle, P_dyn | ||
525 | + # Earth: | ||
526 | + # Time, BartelsNumber, ImfID, SwID, ImfPoints, | ||
527 | + # SwPoints, B_M_av, B_Vec_av, B_Theta_av, | ||
528 | + # B_Phi_av, B, T, N, V, Vlat, Vlon, | ||
529 | + # Alpha, RamP, E, Beta, Ma, Kp, R, DST, | ||
530 | + # AE, Flux, Flag, F10_Index, StartTime, StopTime | ||
531 | + | ||
496 | log.debug("%s: aggregating data from '%s'..." % | 532 | log.debug("%s: aggregating data from '%s'..." % |
497 | (target_config['name'], model_file)) | 533 | (target_config['name'], model_file)) |
498 | - for time, datum_v, datum_b, datum_t, datum_n, datum_p, datum_d \ | ||
499 | - in zip(times, data_v, data_b, data_t, data_n, data_p, data_d): | ||
500 | - vrad = datum_v[0] | ||
501 | - vtan = datum_v[1] | 534 | + for ltime, datum_v, datum_b, datum_t, datum_n, datum_p, datum_a \ |
535 | + in zip(times, data_v, data_b, data_t, data_n, data_p, data_a): | ||
536 | + | ||
502 | try: | 537 | try: |
503 | - dtime = datetime_from_list(time) | ||
504 | - except Exception as e: | ||
505 | - log.error("Failed to parse time from %s." % time) | ||
506 | - raise e | 538 | + dtime = datetime_from_list(ltime) |
539 | + except Exception: | ||
540 | + log.error("Failed to parse time from %s." % ltime) | ||
541 | + raise | ||
507 | if s0 <= dtime <= s1: | 542 | if s0 <= dtime <= s1: |
508 | dkey = round_time(dtime, 60*60).strftime(precision) | 543 | dkey = round_time(dtime, 60*60).strftime(precision) |
544 | + | ||
545 | + if hasattr(datum_v, '__len__'): | ||
546 | + vrad = datum_v[0] | ||
547 | + vtan = datum_v[1] | ||
548 | + vtot = sqrt(vrad * vrad + vtan * vtan) | ||
549 | + else: # eg: Earth | ||
550 | + vrad = None | ||
551 | + vtan = None | ||
552 | + vtot = datum_v | ||
553 | + | ||
509 | x_hee = None | 554 | x_hee = None |
510 | y_hee = None | 555 | y_hee = None |
556 | + if target_config['slug'] == 'earth': | ||
557 | + x_hee = 1 | ||
558 | + y_hee = 0 | ||
511 | if dkey in orbit_data: | 559 | if dkey in orbit_data: |
512 | x_hee = orbit_data[dkey][0] | 560 | x_hee = orbit_data[dkey][0] |
513 | y_hee = orbit_data[dkey][1] | 561 | y_hee = orbit_data[dkey][1] |
514 | all_data[dkey] = ( | 562 | all_data[dkey] = ( |
515 | dtime.strftime("%Y-%m-%dT%H:%M:%S+00:00"), | 563 | dtime.strftime("%Y-%m-%dT%H:%M:%S+00:00"), |
516 | - vrad, vtan, sqrt(vrad * vrad + vtan * vtan), | ||
517 | - datum_b, datum_t, datum_n, datum_p, datum_d, | 564 | + vrad, vtan, vtot, |
565 | + datum_b, datum_t, datum_n, datum_p, datum_a, | ||
518 | x_hee, y_hee | 566 | x_hee, y_hee |
519 | ) | 567 | ) |
520 | cdf_handle.close() | 568 | cdf_handle.close() |
@@ -565,7 +613,12 @@ def generate_csv_file_if_needed(target_slug, started_at, stopped_at): | @@ -565,7 +613,12 @@ def generate_csv_file_if_needed(target_slug, started_at, stopped_at): | ||
565 | stopped_at=stopped_at)) | 613 | stopped_at=stopped_at)) |
566 | log.info("Generation of '%s' done." % filename) | 614 | log.info("Generation of '%s' done." % filename) |
567 | except Exception as e: | 615 | except Exception as e: |
616 | + from sys import exc_info | ||
617 | + from traceback import extract_tb | ||
618 | + exc_type, exc_value, exc_traceback = exc_info() | ||
568 | log.error(e) | 619 | log.error(e) |
620 | + for trace in extract_tb(exc_traceback): | ||
621 | + log.error(trace) | ||
569 | if isfile(local_csv_file): | 622 | if isfile(local_csv_file): |
570 | log.warn("Removing failed CSV '%s'..." % local_csv_file) | 623 | log.warn("Removing failed CSV '%s'..." % local_csv_file) |
571 | removefile(local_csv_file) | 624 | removefile(local_csv_file) |
@@ -875,9 +928,9 @@ def download_targets_netcdf(targets, params, started_at, stopped_at): | @@ -875,9 +928,9 @@ def download_targets_netcdf(targets, params, started_at, stopped_at): | ||
875 | nc_y[:] = values_y | 928 | nc_y[:] = values_y |
876 | log.debug("Writing NetCDF '%s'..." % nc_filename) | 929 | log.debug("Writing NetCDF '%s'..." % nc_filename) |
877 | 930 | ||
878 | - except Exception as e: | 931 | + except Exception: |
879 | log.error("Failed to generate NetCDF '%s'." % nc_filename) | 932 | log.error("Failed to generate NetCDF '%s'." % nc_filename) |
880 | - raise e | 933 | + raise |
881 | finally: | 934 | finally: |
882 | nc_handle.close() | 935 | nc_handle.close() |
883 | 936 |
web/static/js/swapp.js
@@ -199,12 +199,15 @@ | @@ -199,12 +199,15 @@ | ||
199 | var dtime; | 199 | var dtime; |
200 | dtime = timeFormat(d['time']); | 200 | dtime = timeFormat(d['time']); |
201 | configuration['parameters'].forEach(function(parameter){ | 201 | configuration['parameters'].forEach(function(parameter){ |
202 | - var id; | 202 | + var id, val; |
203 | id = parameter['id']; | 203 | id = parameter['id']; |
204 | - return data[id].push({ | ||
205 | - x: dtime, | ||
206 | - y: parseFloat(d[id]) | ||
207 | - }); | 204 | + val = parseFloat(d[id]); |
205 | + if (!isNaN(val)) { | ||
206 | + return data[id].push({ | ||
207 | + x: dtime, | ||
208 | + y: val | ||
209 | + }); | ||
210 | + } | ||
208 | }); | 211 | }); |
209 | if (d['xhee'] && d['yhee']) { | 212 | if (d['xhee'] && d['yhee']) { |
210 | return data['hee'].push({ | 213 | return data['hee'].push({ |
@@ -299,10 +302,13 @@ | @@ -299,10 +302,13 @@ | ||
299 | if (!(id in data)) { | 302 | if (!(id in data)) { |
300 | console.error("No data for id '" + id + "'.", data); | 303 | console.error("No data for id '" + id + "'.", data); |
301 | } | 304 | } |
302 | - return this$.time_series.push(new TimeSeries(id, title, target, data[id], this$.parameters[id].active, container, { | ||
303 | - 'started_at': this$.started_at, | ||
304 | - 'stopped_at': this$.stopped_at | ||
305 | - })); | 305 | + console.log(target['name'], id, data[id]); |
306 | + if (data[id].length) { | ||
307 | + return this$.time_series.push(new TimeSeries(id, title, target, data[id], this$.parameters[id].active, container, { | ||
308 | + 'started_at': this$.started_at, | ||
309 | + 'stopped_at': this$.stopped_at | ||
310 | + })); | ||
311 | + } | ||
306 | }); | 312 | }); |
307 | this.time_series.forEach(function(ts){ | 313 | this.time_series.forEach(function(ts){ |
308 | ts.options['onMouseOver'] = function(){ | 314 | ts.options['onMouseOver'] = function(){ |
web/static/js/swapp.ls
@@ -169,7 +169,9 @@ https://gitlab.irap.omp.eu/CDPP/SPACEWEATHERONLINE | @@ -169,7 +169,9 @@ https://gitlab.irap.omp.eu/CDPP/SPACEWEATHERONLINE | ||
169 | dtime = timeFormat(d['time']) | 169 | dtime = timeFormat(d['time']) |
170 | configuration['parameters'].forEach((parameter) -> | 170 | configuration['parameters'].forEach((parameter) -> |
171 | id = parameter['id'] | 171 | id = parameter['id'] |
172 | - data[id].push({x: dtime, y: parseFloat(d[id])}) | 172 | + val = parseFloat(d[id]) |
173 | + if not isNaN(val) | ||
174 | + data[id].push({x: dtime, y: val}) | ||
173 | ) | 175 | ) |
174 | if d['xhee'] and d['yhee'] | 176 | if d['xhee'] and d['yhee'] |
175 | data['hee'].push({ | 177 | data['hee'].push({ |
@@ -250,12 +252,14 @@ https://gitlab.irap.omp.eu/CDPP/SPACEWEATHERONLINE | @@ -250,12 +252,14 @@ https://gitlab.irap.omp.eu/CDPP/SPACEWEATHERONLINE | ||
250 | container = @configuration['time_series_container'] | 252 | container = @configuration['time_series_container'] |
251 | id = parameter['id'] ; title = parameter['title'] | 253 | id = parameter['id'] ; title = parameter['title'] |
252 | if id not of data then console.error("No data for id '#{id}'.", data) | 254 | if id not of data then console.error("No data for id '#{id}'.", data) |
253 | - @time_series.push(new TimeSeries( | ||
254 | - id, title, target, data[id], @parameters[id].active, container, { | ||
255 | - 'started_at': @started_at, | ||
256 | - 'stopped_at': @stopped_at, | ||
257 | - } | ||
258 | - )) | 255 | + console.log(target['name'], id, data[id]) |
256 | + if data[id].length | ||
257 | + @time_series.push(new TimeSeries( | ||
258 | + id, title, target, data[id], @parameters[id].active, container, { | ||
259 | + 'started_at': @started_at, | ||
260 | + 'stopped_at': @stopped_at, | ||
261 | + } | ||
262 | + )) | ||
259 | ) | 263 | ) |
260 | @time_series.forEach((ts) ~> # returning true may be faster | 264 | @time_series.forEach((ts) ~> # returning true may be faster |
261 | ts.options['onMouseOver'] = ~> | 265 | ts.options['onMouseOver'] = ~> |
web/view/home.html.jinja2
@@ -11,7 +11,7 @@ | @@ -11,7 +11,7 @@ | ||
11 | <div class="target {{ target.slug }} {{ 'locked' if target.locked else 'active' }}" | 11 | <div class="target {{ target.slug }} {{ 'locked' if target.locked else 'active' }}" |
12 | data-target-slug="{{ target.slug }}"> | 12 | data-target-slug="{{ target.slug }}"> |
13 | <img width="64px" height="64px" | 13 | <img width="64px" height="64px" |
14 | - src="{{ static('img/target/'~target.slug~'_128.png') }}" | 14 | + src="{{ static('img/target/'~target.slug~'_256.png') }}" |
15 | title="{{ target.title }}" | 15 | title="{{ target.title }}" |
16 | alt="{{ target.name }}"> | 16 | alt="{{ target.name }}"> |
17 | <img width="64px" height="64px" | 17 | <img width="64px" height="64px" |