Commit 4bbc0e4ac9a371f657e3b5770a80fb7417d45bbd

Authored by Goutte
1 parent db69e5a7

Continue THE GREAT REFACTO

Showing 2 changed files with 1180 additions and 1163 deletions   Show diff stats
web/static/js/main.js
@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 * We're using a custom build of 4.9.1, one line changed, see d3-custom.js 17 * We're using a custom build of 4.9.1, one line changed, see d3-custom.js
18 * Event bubbling cannot trigger two rects unless we make an event dispatcher, 18 * Event bubbling cannot trigger two rects unless we make an event dispatcher,
19 * and d3's brush is stopping propagation, as it should by default. 19 * and d3's brush is stopping propagation, as it should by default.
20 - * 20 + *
21 * 21 *
22 * Code is weird, with ref$ 22 * Code is weird, with ref$
23 * ------------------------ 23 * ------------------------
@@ -27,1322 +27,1308 @@ @@ -27,1322 +27,1308 @@
27 */ 27 */
28 28
29 (function () { 29 (function () {
30 - let Target;  
31 - // let SpaceWeather;  
32 - let TimeSeries;  
33 - let Orbits; 30 + // let TimeSeries;
  31 + // let Orbits;
34 const global = typeof exports !== 'undefined' && exports || this; 32 const global = typeof exports !== 'undefined' && exports || this;
35 const GOLDEN_RATIO = 2 / (1 + Math.sqrt(5)); 33 const GOLDEN_RATIO = 2 / (1 + Math.sqrt(5));
36 - Target = ((() => {  
37 - // Target.displayName = 'Target';  
38 - // const prototype = Target.prototype;  
39 - // const constructor = Target;  
40 - function Target(slug, name, config) { 34 +
  35 + class Target {
  36 + constructor(slug, name, config) {
41 this.slug = slug; 37 this.slug = slug;
42 this.name = name; 38 this.name = name;
43 this.config = config; 39 this.config = config;
44 this.active = this.config.active; 40 this.active = this.config.active;
45 } 41 }
  42 + }
46 43
47 - return Target;  
48 - })());  
49 -  
50 -  
51 - global.SpaceWeather = ((() => { 44 + // global.SpaceWeather = ((() => {
52 // "The main app, instanciated from an inline script.\nIt defaults to an interval starting two months ago, and ending in a month.\n(both at midnight)"; 45 // "The main app, instanciated from an inline script.\nIt defaults to an interval starting two months ago, and ending in a month.\n(both at midnight)";
53 // SpaceWeather.displayName = 'SpaceWeather'; 46 // SpaceWeather.displayName = 'SpaceWeather';
54 // const prototype = SpaceWeather.prototype; 47 // const prototype = SpaceWeather.prototype;
55 // const constructor = SpaceWeather; 48 // const constructor = SpaceWeather;
56 - const API_TIME_FORMAT = "YYYY-MM-DDTHH:mm:ss";  
57 - const INPUT_TIME_FORMAT = "YYYY-MM-DD";  
58 -  
59 - class SpaceWeather {  
60 - constructor(configuration) {  
61 - const that = this;  
62 - this.configuration = configuration;  
63 - this.parameters = {};  
64 - this.targets = {};  
65 - console.info("©2017\n _ _ _ _ ____\n | | | | ___| (_) ___ | _ \\ _ __ ___ _ __ __ _\n | |_| |/ _ \\ | |/ _ \\| |_) | '__/ _ \\| '_ \\ / _` |\n | _ | __/ | | (_) | __/| | | (_) | |_) | (_| |\n |_| |_|\\___|_|_|\\___/|_|_ |_|_ \\___/| .__/ \\__,_|\n | |__ _ _ / ___| _ \\| _ \\| _ \\_|\n | '_ \\| | | | | | | | | | |_) | |_) |\n | |_) | |_| | | |___| |_| | __/| __/\n |_.__/ \\__, | \\____|____/|_| |_|\n |___/\n\nThe full source of this website is available at :\nhttps://gitlab.irap.omp.eu/CDPP/SPACEWEATHERONLINE");  
66 -  
67 - let targets_configs = [];  
68 - for (let k in this.configuration.targets) {  
69 - targets_configs.push(this.configuration.targets[k]);  
70 - }  
71 - targets_configs.forEach(tc => that.addTarget(new Target(tc.slug, tc.name, tc)));  
72 -  
73 - this.configuration['parameters'].forEach(p => that.parameters[p['id']] = p);  
74 - this.orbits = null;  
75 - this.time_series = [];  
76 - }  
77 49
78 - init(started_at, stopped_at) {  
79 - // "This is called by the inline bootstrap javascript code.\nThis ain't in the constructor because it might return a Promise later on.\n(for the loader, for example)";  
80 - const this$ = this;  
81 - started_at = moment(started_at).hours(0).minutes(0).seconds(0);  
82 - stopped_at = moment(stopped_at).hours(0).minutes(0).seconds(0);  
83 - this.setStartAndStop(started_at, stopped_at);  
84 - this.loadAndCreatePlots(started_at, stopped_at);  
85 - window.addEventListener('resize', () => this$.resize());  
86 - return this;  
87 - } 50 + const API_TIME_FORMAT = "YYYY-MM-DDTHH:mm:ss";
  51 + const INPUT_TIME_FORMAT = "YYYY-MM-DD";
88 52
89 - buildDataUrlForTarget(target_slug, started_at, stopped_at) {  
90 - let url;  
91 - url = this.configuration['api']['data_for_interval'];  
92 - url = url.replace('<target>', target_slug);  
93 - url = url.replace('<started_at>', started_at);  
94 - url = url.replace('<stopped_at>', stopped_at);  
95 - return url;  
96 - } 53 + class SpaceWeather {
  54 + constructor(configuration) {
  55 + const that = this;
  56 + this.configuration = configuration;
  57 + this.parameters = {};
  58 + this.targets = {};
  59 + console.info("©2017\n _ _ _ _ ____\n | | | | ___| (_) ___ | _ \\ _ __ ___ _ __ __ _\n | |_| |/ _ \\ | |/ _ \\| |_) | '__/ _ \\| '_ \\ / _` |\n | _ | __/ | | (_) | __/| | | (_) | |_) | (_| |\n |_| |_|\\___|_|_|\\___/|_|_ |_|_ \\___/| .__/ \\__,_|\n | |__ _ _ / ___| _ \\| _ \\| _ \\_|\n | '_ \\| | | | | | | | | | |_) | |_) |\n | |_) | |_| | | |___| |_| | __/| __/\n |_.__/ \\__, | \\____|____/|_| |_|\n |___/\n\nThe full source of this website is available at :\nhttps://gitlab.irap.omp.eu/CDPP/SPACEWEATHERONLINE");
97 60
98 - buildDownloadUrl() {  
99 - let ref$;  
100 - let started_at;  
101 - let stopped_at;  
102 - let targets;  
103 - let t;  
104 - let url;  
105 - ref$ = this.getDomain(), started_at = ref$[0], stopped_at = ref$[1];  
106 - targets = (function () {  
107 - const results$ = [];  
108 - for (t in this.targets) {  
109 - if (this.targets[t].active) {  
110 - results$.push(t);  
111 - }  
112 - }  
113 - return results$;  
114 - }.call(this)).sort().join('-');  
115 - url = this.configuration['api']['download'];  
116 - url = url.replace('<targets>', targets);  
117 - url = url.replace('<started_at>', started_at.format(API_TIME_FORMAT));  
118 - url = url.replace('<stopped_at>', stopped_at.format(API_TIME_FORMAT));  
119 - return url; 61 + let targets_configs = [];
  62 + for (let k in this.configuration.targets) {
  63 + targets_configs.push(this.configuration.targets[k]);
120 } 64 }
  65 + targets_configs.forEach(tc => that.addTarget(new Target(tc.slug, tc.name, tc)));
121 66
122 - buildSampUrl() {  
123 - let ref$;  
124 - let started_at;  
125 - let stopped_at;  
126 - let targets;  
127 - let t;  
128 - let parameters;  
129 - let p;  
130 - let url;  
131 - ref$ = this.getDomain(), started_at = ref$[0], stopped_at = ref$[1];  
132 - targets = (function () {  
133 - const results$ = [];  
134 - for (t in this.targets) {  
135 - if (this.targets[t].active) {  
136 - results$.push(t);  
137 - }  
138 - }  
139 - return results$;  
140 - }.call(this)).sort().join('-');  
141 - parameters = (function () {  
142 - const results$ = [];  
143 - for (p in this.parameters) {  
144 - if (this.parameters[p].active) {  
145 - results$.push(p);  
146 - }  
147 - }  
148 - return results$;  
149 - }.call(this)).sort().join('-');  
150 - url = this.configuration['api']['samp'];  
151 - url = url.replace('<targets>', targets);  
152 - url = url.replace('<params>', parameters);  
153 - url = url.replace('<started_at>', started_at.format(API_TIME_FORMAT));  
154 - url = url.replace('<stopped_at>', stopped_at.format(API_TIME_FORMAT));  
155 - return url;  
156 - } 67 + this.configuration['parameters'].forEach(p => that.parameters[p['id']] = p);
  68 + this.orbits = null;
  69 + this.time_series = [];
  70 + }
157 71
158 - buildSampName() {  
159 - let ref$;  
160 - let started_at;  
161 - let stopped_at;  
162 - let targets;  
163 - let t;  
164 - ref$ = this.getDomain(), started_at = ref$[0], stopped_at = ref$[1];  
165 - targets = (function () {  
166 - let i$;  
167 - let ref$;  
168 - let len$;  
169 - const results$ = [];  
170 - for (i$ = 0, len$ = (ref$ = this.getEnabledTargets()).length; i$ < len$; ++i$) {  
171 - t = ref$[i$];  
172 - results$.push(t.name);  
173 - }  
174 - return results$;  
175 - }.call(this)).sort().join(', ');  
176 - return `Heliopropa for ${targets} from ${started_at.format(API_TIME_FORMAT)} to ${stopped_at.format(API_TIME_FORMAT)}.`;  
177 - } 72 + init(started_at, stopped_at) {
  73 + // "This is called by the inline bootstrap javascript code.\nThis ain't in the constructor because it might return a Promise later on.\n(for the loader, for example)";
  74 + const this$ = this;
  75 + started_at = moment(started_at).hours(0).minutes(0).seconds(0);
  76 + stopped_at = moment(stopped_at).hours(0).minutes(0).seconds(0);
  77 + this.setStartAndStop(started_at, stopped_at);
  78 + this.loadAndCreatePlots(started_at, stopped_at);
  79 + window.addEventListener('resize', () => this$.resize());
  80 + return this;
  81 + }
178 82
179 - addTarget(target) {  
180 - this.targets[target.slug] = target;  
181 - return this;  
182 - } 83 + buildDataUrlForTarget(target_slug, started_at, stopped_at) {
  84 + let url;
  85 + url = this.configuration['api']['data_for_interval'];
  86 + url = url.replace('<target>', target_slug);
  87 + url = url.replace('<started_at>', started_at);
  88 + url = url.replace('<stopped_at>', stopped_at);
  89 + return url;
  90 + }
183 91
184 - getEnabledTargets() {  
185 - let slug;  
186 - let ref$;  
187 - let target; 92 + buildDownloadUrl() {
  93 + let ref$;
  94 + let started_at;
  95 + let stopped_at;
  96 + let targets;
  97 + let t;
  98 + let url;
  99 + ref$ = this.getDomain(), started_at = ref$[0], stopped_at = ref$[1];
  100 + targets = (function () {
188 const results$ = []; 101 const results$ = [];
189 - for (slug in ref$ = this.targets) {  
190 - target = ref$[slug];  
191 - if (target.active) {  
192 - results$.push(target); 102 + for (t in this.targets) {
  103 + if (this.targets[t].active) {
  104 + results$.push(t);
193 } 105 }
194 } 106 }
195 return results$; 107 return results$;
196 - } 108 + }.call(this)).sort().join('-');
  109 + url = this.configuration['api']['download'];
  110 + url = url.replace('<targets>', targets);
  111 + url = url.replace('<started_at>', started_at.format(API_TIME_FORMAT));
  112 + url = url.replace('<stopped_at>', stopped_at.format(API_TIME_FORMAT));
  113 + return url;
  114 + }
197 115
198 - enableTarget(target_slug) {  
199 - let ref$;  
200 - const this$ = this;  
201 - this.time_series.forEach(ts => {  
202 - if (ts.target.slug === target_slug && this$.parameters[ts.parameter].active) {  
203 - return ts.show(); 116 + buildSampUrl() {
  117 + let ref$;
  118 + let started_at;
  119 + let stopped_at;
  120 + let targets;
  121 + let t;
  122 + let parameters;
  123 + let p;
  124 + let url;
  125 + ref$ = this.getDomain(), started_at = ref$[0], stopped_at = ref$[1];
  126 + targets = (function () {
  127 + const results$ = [];
  128 + for (t in this.targets) {
  129 + if (this.targets[t].active) {
  130 + results$.push(t);
204 } 131 }
205 - });  
206 - this.targets[target_slug].active = true;  
207 - if ((ref$ = this.orbits) != null) {  
208 - ref$.enableTarget(target_slug);  
209 } 132 }
210 - return this;  
211 - } 133 + return results$;
  134 + }.call(this)).sort().join('-');
  135 + parameters = (function () {
  136 + const results$ = [];
  137 + for (p in this.parameters) {
  138 + if (this.parameters[p].active) {
  139 + results$.push(p);
  140 + }
  141 + }
  142 + return results$;
  143 + }.call(this)).sort().join('-');
  144 + url = this.configuration['api']['samp'];
  145 + url = url.replace('<targets>', targets);
  146 + url = url.replace('<params>', parameters);
  147 + url = url.replace('<started_at>', started_at.format(API_TIME_FORMAT));
  148 + url = url.replace('<stopped_at>', stopped_at.format(API_TIME_FORMAT));
  149 + return url;
  150 + }
212 151
213 - disableTarget(target_slug) { 152 + buildSampName() {
  153 + let ref$;
  154 + let started_at;
  155 + let stopped_at;
  156 + let targets;
  157 + let t;
  158 + ref$ = this.getDomain(), started_at = ref$[0], stopped_at = ref$[1];
  159 + targets = (function () {
  160 + let i$;
214 let ref$; 161 let ref$;
215 - this.time_series.forEach(ts => {  
216 - if (ts.target.slug === target_slug) {  
217 - return ts.hide();  
218 - }  
219 - });  
220 - this.targets[target_slug].active = false;  
221 - if ((ref$ = this.orbits) != null) {  
222 - ref$.disableTarget(target_slug); 162 + let len$;
  163 + const results$ = [];
  164 + for (i$ = 0, len$ = (ref$ = this.getEnabledTargets()).length; i$ < len$; ++i$) {
  165 + t = ref$[i$];
  166 + results$.push(t.name);
  167 + }
  168 + return results$;
  169 + }.call(this)).sort().join(', ');
  170 + return `Heliopropa for ${targets} from ${started_at.format(API_TIME_FORMAT)} to ${stopped_at.format(API_TIME_FORMAT)}.`;
  171 + }
  172 +
  173 + addTarget(target) {
  174 + this.targets[target.slug] = target;
  175 + return this;
  176 + }
  177 +
  178 + getEnabledTargets() {
  179 + let slug;
  180 + let ref$;
  181 + let target;
  182 + const results$ = [];
  183 + for (slug in ref$ = this.targets) {
  184 + target = ref$[slug];
  185 + if (target.active) {
  186 + results$.push(target);
223 } 187 }
224 - return this;  
225 } 188 }
  189 + return results$;
  190 + }
226 191
227 - resize() {  
228 - let ref$;  
229 - if ((ref$ = this.orbits) != null) {  
230 - ref$.resize(); 192 + enableTarget(target_slug) {
  193 + let ref$;
  194 + const this$ = this;
  195 + this.time_series.forEach(ts => {
  196 + if (ts.target.slug === target_slug && this$.parameters[ts.parameter].active) {
  197 + return ts.show();
231 } 198 }
232 - this.time_series.forEach(ts => ts.resize());  
233 - return this; 199 + });
  200 + this.targets[target_slug].active = true;
  201 + if ((ref$ = this.orbits) != null) {
  202 + ref$.enableTarget(target_slug);
234 } 203 }
  204 + return this;
  205 + }
235 206
236 - showLoader() {  
237 - return $('#plots_loader').show(); 207 + disableTarget(target_slug) {
  208 + let ref$;
  209 + this.time_series.forEach(ts => {
  210 + if (ts.target.slug === target_slug) {
  211 + return ts.hide();
  212 + }
  213 + });
  214 + this.targets[target_slug].active = false;
  215 + if ((ref$ = this.orbits) != null) {
  216 + ref$.disableTarget(target_slug);
238 } 217 }
  218 + return this;
  219 + }
239 220
240 - hideLoader() {  
241 - return $('#plots_loader').hide(); 221 + resize() {
  222 + let ref$;
  223 + if ((ref$ = this.orbits) != null) {
  224 + ref$.resize();
242 } 225 }
  226 + this.time_series.forEach(ts => ts.resize());
  227 + return this;
  228 + }
243 229
244 - loadData(target_slug, started_at, stopped_at) {  
245 - // "Load the data as CSV for the specified target and interval,\nand return it in a Promise.";  
246 - let sw;  
247 - sw = this;  
248 - return new Promise((resolve, reject) => {  
249 - let url;  
250 - url = sw.buildDataUrlForTarget(target_slug, started_at, stopped_at);  
251 - return d3.csv(url, csv => {  
252 - let timeFormat;  
253 - let data;  
254 - console.debug(`Requested CSV for ${target_slug}…`, csv);  
255 - timeFormat = d3.utcParse('%Y-%m-%dT%H:%M:%S%Z');  
256 - data = {  
257 - 'hee': []  
258 - };  
259 - configuration['parameters'].forEach(parameter => data[parameter['id']] = []);  
260 - if (!csv) {  
261 - reject('invalid');  
262 - }  
263 - if (!csv.length) {  
264 - reject('empty');  
265 - }  
266 - csv.forEach(d => {  
267 - let dtime;  
268 - dtime = timeFormat(d['time']);  
269 - configuration['parameters'].forEach(parameter => {  
270 - let id;  
271 - let val;  
272 - id = parameter['id'];  
273 - val = parseFloat(d[id]);  
274 - if (!isNaN(val)) {  
275 - return data[id].push({  
276 - x: dtime,  
277 - y: val  
278 - });  
279 - }  
280 - });  
281 - if (d['xhee'] && d['yhee']) {  
282 - return data['hee'].push({  
283 - t: dtime,  
284 - x: parseFloat(d['xhee']),  
285 - y: parseFloat(d['yhee']) 230 + showLoader() {
  231 + return $('#plots_loader').show();
  232 + }
  233 +
  234 + hideLoader() {
  235 + return $('#plots_loader').hide();
  236 + }
  237 +
  238 + loadData(target_slug, started_at, stopped_at) {
  239 + // "Load the data as CSV for the specified target and interval,\nand return it in a Promise.";
  240 + let sw;
  241 + sw = this;
  242 + return new Promise((resolve, reject) => {
  243 + let url;
  244 + url = sw.buildDataUrlForTarget(target_slug, started_at, stopped_at);
  245 + return d3.csv(url, csv => {
  246 + let timeFormat;
  247 + let data;
  248 + console.debug(`Requested CSV for ${target_slug}…`, csv);
  249 + timeFormat = d3.utcParse('%Y-%m-%dT%H:%M:%S%Z');
  250 + data = {
  251 + 'hee': []
  252 + };
  253 + configuration['parameters'].forEach(parameter => data[parameter['id']] = []);
  254 + if (!csv) {
  255 + reject('invalid');
  256 + }
  257 + if (!csv.length) {
  258 + reject('empty');
  259 + }
  260 + csv.forEach(d => {
  261 + let dtime;
  262 + dtime = timeFormat(d['time']);
  263 + configuration['parameters'].forEach(parameter => {
  264 + let id;
  265 + let val;
  266 + id = parameter['id'];
  267 + val = parseFloat(d[id]);
  268 + if (!isNaN(val)) {
  269 + return data[id].push({
  270 + x: dtime,
  271 + y: val
286 }); 272 });
287 } 273 }
288 }); 274 });
289 - return resolve(data); 275 + if (d['xhee'] && d['yhee']) {
  276 + return data['hee'].push({
  277 + t: dtime,
  278 + x: parseFloat(d['xhee']),
  279 + y: parseFloat(d['yhee'])
  280 + });
  281 + }
290 }); 282 });
  283 + return resolve(data);
291 }); 284 });
292 - } 285 + });
  286 + }
293 287
294 - loadAndCreatePlots(started_at, stopped_at) {  
295 - // "started_at: moment(.js) datetime object\nstopped_at: moment(.js) datetime object";  
296 - let targets;  
297 - let res$;  
298 - let k;  
299 - let handleTarget;  
300 - const this$ = this;  
301 - this.showLoader();  
302 - this.started_at = started_at;  
303 - this.stopped_at = stopped_at;  
304 - this.orbits = new Orbits(this.configuration.orbits_container, this.configuration);  
305 - started_at = started_at.format(API_TIME_FORMAT);  
306 - stopped_at = stopped_at.format(API_TIME_FORMAT);  
307 - res$ = [];  
308 - for (k in this.targets) {  
309 - res$.push(this.targets[k]); 288 + loadAndCreatePlots(started_at, stopped_at) {
  289 + // "started_at: moment(.js) datetime object\nstopped_at: moment(.js) datetime object";
  290 + let targets;
  291 + let res$;
  292 + let k;
  293 + let handleTarget;
  294 + const this$ = this;
  295 + this.showLoader();
  296 + this.started_at = started_at;
  297 + this.stopped_at = stopped_at;
  298 + this.orbits = new Orbits(this.configuration.orbits_container, this.configuration);
  299 + started_at = started_at.format(API_TIME_FORMAT);
  300 + stopped_at = stopped_at.format(API_TIME_FORMAT);
  301 + res$ = [];
  302 + for (k in this.targets) {
  303 + res$.push(this.targets[k]);
  304 + }
  305 + targets = res$;
  306 + targets.forEach(target => {
  307 + let targetButton;
  308 + targetButton = $(`.targets-filters .target.${target.slug}`);
  309 + targetButton.addClass('loading');
  310 + return targetButton.removeClass('failed error empty');
  311 + });
  312 + handleTarget = i => {
  313 + let target;
  314 + let targetButton;
  315 + if (i >= targets.length) {
  316 + return;
310 } 317 }
311 - targets = res$;  
312 - targets.forEach(target => {  
313 - let targetButton;  
314 - targetButton = $(`.targets-filters .target.${target.slug}`);  
315 - targetButton.addClass('loading');  
316 - return targetButton.removeClass('failed error empty');  
317 - });  
318 - handleTarget = i => {  
319 - let target;  
320 - let targetButton;  
321 - if (i >= targets.length) {  
322 - return;  
323 - }  
324 - target = targets[i];  
325 - console.info(`Loading CSV data of ${target.name}…`);  
326 - targetButton = $(`.targets-filters .target.${target.slug}`);  
327 - return this$.loadData(target.slug, started_at, stopped_at).then(data => {  
328 - console.info(`Loaded CSV data of ${target.name}.`, data);  
329 - this$.createTimeSeries(target, data);  
330 - this$.orbits.initOrbiter(target.slug, target.config, data['hee']);  
331 - targetButton.removeClass('loading');  
332 - if (target.active) {  
333 - this$.hideLoader();  
334 - } else {  
335 - this$.disableTarget(target.slug);  
336 - }  
337 - return handleTarget(i + 1);  
338 - }, error => {  
339 - let msg;  
340 - switch (error) {  
341 - case 'invalid':  
342 - console.error(`Failed loading CSV data of ${target.name}.`);  
343 - targetButton.addClass('error');  
344 - break;  
345 - case 'empty':  
346 - msg = `No data for ${target.name}\n during interval from \n${started_at} to ${stopped_at}.`;  
347 - console.warn(msg);  
348 - targetButton.addClass('empty');  
349 - break;  
350 - }  
351 - targetButton.addClass('failed');  
352 - targetButton.removeClass('loading'); 318 + target = targets[i];
  319 + console.info(`Loading CSV data of ${target.name}…`);
  320 + targetButton = $(`.targets-filters .target.${target.slug}`);
  321 + return this$.loadData(target.slug, started_at, stopped_at).then(data => {
  322 + console.info(`Loaded CSV data of ${target.name}.`, data);
  323 + this$.createTimeSeries(target, data);
  324 + this$.orbits.initOrbiter(target.slug, target.config, data['hee']);
  325 + targetButton.removeClass('loading');
  326 + if (target.active) {
353 this$.hideLoader(); 327 this$.hideLoader();
354 - return handleTarget(i + 1);  
355 - });  
356 - };  
357 - handleTarget(0);  
358 - return this;  
359 - }  
360 -  
361 - clearPlots() {  
362 - this.orbits.clear();  
363 - this.time_series.forEach(ts => ts.clear());  
364 - this.orbits = null;  
365 - this.time_series = [];  
366 - return this;  
367 - }  
368 -  
369 - createTimeSeries(target, data) {  
370 - const this$ = this;  
371 - this.configuration['parameters'].forEach(parameter => {  
372 - let container;  
373 - let id;  
374 - let title;  
375 - container = this$.configuration['time_series_container'];  
376 - id = parameter['id'];  
377 - title = parameter['title'];  
378 - if (!(id in data)) {  
379 - console.error(`No data for id '${id}'.`, data); 328 + } else {
  329 + this$.disableTarget(target.slug);
380 } 330 }
381 - console.log(target['name'], id, data[id]);  
382 - if (data[id].length) {  
383 - return this$.time_series.push(new TimeSeries(id, title, target, data[id], this$.parameters[id].active, container, {  
384 - 'started_at': this$.started_at,  
385 - 'stopped_at': this$.stopped_at  
386 - })); 331 + return handleTarget(i + 1);
  332 + }, error => {
  333 + let msg;
  334 + switch (error) {
  335 + case 'invalid':
  336 + console.error(`Failed loading CSV data of ${target.name}.`);
  337 + targetButton.addClass('error');
  338 + break;
  339 + case 'empty':
  340 + msg = `No data for ${target.name}\n during interval from \n${started_at} to ${stopped_at}.`;
  341 + console.warn(msg);
  342 + targetButton.addClass('empty');
  343 + break;
387 } 344 }
  345 + targetButton.addClass('failed');
  346 + targetButton.removeClass('loading');
  347 + this$.hideLoader();
  348 + return handleTarget(i + 1);
388 }); 349 });
389 - this.time_series.forEach(ts => {  
390 - ts.options['onMouseOver'] = () => true;  
391 - ts.options['onMouseOut'] = () => {  
392 - this$.time_series.forEach(ts2 => ts2.hideCursor());  
393 - return true;  
394 - };  
395 - ts.options['onMouseMove'] = t => {  
396 - let ref$;  
397 - this$.time_series.forEach(ts2 => ts2.moveCursor(t));  
398 - if ((ref$ = this$.orbits) != null) {  
399 - ref$.moveToDate(t);  
400 - }  
401 - return true;  
402 - };  
403 - ts.options['onBrushEnd'] = (sta, sto) => {  
404 - this$.resizeDomain(moment(sta), moment(sto));  
405 - return true;  
406 - };  
407 - return ts.options['onDblClick'] = () => {  
408 - let ref$;  
409 - this$.resetZoom();  
410 - if ((ref$ = $("#zoom_controls_help")) != null) {  
411 - ref$.remove();  
412 - }  
413 - return true;  
414 - };  
415 - });  
416 - return this.time_series;  
417 - }  
418 -  
419 - getEnabledParameters() {  
420 - let i$;  
421 - let ref$;  
422 - let len$;  
423 - let p;  
424 - let slug;  
425 - const results$ = [];  
426 - for (i$ = 0, len$ = (ref$ = this.parameters).length; i$ < len$; ++i$) {  
427 - p = i$;  
428 - slug = ref$[i$];  
429 - if (p.active) {  
430 - results$.push(p);  
431 - }  
432 - } 350 + };
  351 + handleTarget(0);
  352 + return this;
  353 + }
433 354
434 - return results$;  
435 - } 355 + clearPlots() {
  356 + this.orbits.clear();
  357 + this.time_series.forEach(ts => ts.clear());
  358 + this.orbits = null;
  359 + this.time_series = [];
  360 + return this;
  361 + }
436 362
437 - enableParameter(parameter_slug) {  
438 - const this$ = this;  
439 - if (!(parameter_slug in this.parameters)) {  
440 - console.error(`Unknown parameter ${parameter_slug}.`); 363 + createTimeSeries(target, data) {
  364 + const this$ = this;
  365 + this.configuration['parameters'].forEach(parameter => {
  366 + let container;
  367 + let id;
  368 + let title;
  369 + container = this$.configuration['time_series_container'];
  370 + id = parameter['id'];
  371 + title = parameter['title'];
  372 + if (!(id in data)) {
  373 + console.error(`No data for id '${id}'.`, data);
  374 + }
  375 + console.log(target['name'], id, data[id]);
  376 + if (data[id].length) {
  377 + return this$.time_series.push(new TimeSeries(id, title, target, data[id], this$.parameters[id].active, container, {
  378 + 'started_at': this$.started_at,
  379 + 'stopped_at': this$.stopped_at
  380 + }));
441 } 381 }
442 - this.parameters[parameter_slug].active = true;  
443 - this.time_series.forEach(ts => {  
444 - if (ts.parameter === parameter_slug && this$.targets[ts.target.slug].active) {  
445 - return ts.show(); 382 + });
  383 + this.time_series.forEach(ts => {
  384 + ts.options['onMouseOver'] = () => true;
  385 + ts.options['onMouseOut'] = () => {
  386 + this$.time_series.forEach(ts2 => ts2.hideCursor());
  387 + return true;
  388 + };
  389 + ts.options['onMouseMove'] = t => {
  390 + let ref$;
  391 + this$.time_series.forEach(ts2 => ts2.moveCursor(t));
  392 + if ((ref$ = this$.orbits) != null) {
  393 + ref$.moveToDate(t);
446 } 394 }
447 - }); 395 + return true;
  396 + };
  397 + ts.options['onBrushEnd'] = (sta, sto) => {
  398 + this$.resizeDomain(moment(sta), moment(sto));
  399 + return true;
  400 + };
  401 + return ts.options['onDblClick'] = () => {
  402 + let ref$;
  403 + this$.resetZoom();
  404 + if ((ref$ = $("#zoom_controls_help")) != null) {
  405 + ref$.remove();
  406 + }
  407 + return true;
  408 + };
  409 + });
  410 + return this.time_series;
  411 + }
448 412
449 - return this; 413 + getEnabledParameters() {
  414 + let i$;
  415 + let ref$;
  416 + let len$;
  417 + let p;
  418 + let slug;
  419 + const results$ = [];
  420 + for (i$ = 0, len$ = (ref$ = this.parameters).length; i$ < len$; ++i$) {
  421 + p = i$;
  422 + slug = ref$[i$];
  423 + if (p.active) {
  424 + results$.push(p);
  425 + }
450 } 426 }
451 427
452 - disableParameter(parameter_slug) {  
453 - if (!(parameter_slug in this.parameters)) {  
454 - console.error(`Unknown parameter ${parameter_slug}.`);  
455 - }  
456 - this.parameters[parameter_slug].active = false;  
457 - this.time_series.forEach(ts => {  
458 - if (ts.parameter === parameter_slug) {  
459 - return ts.hide();  
460 - }  
461 - }); 428 + return results$;
  429 + }
462 430
463 - return this; 431 + enableParameter(parameter_slug) {
  432 + const this$ = this;
  433 + if (!(parameter_slug in this.parameters)) {
  434 + console.error(`Unknown parameter ${parameter_slug}.`);
464 } 435 }
  436 + this.parameters[parameter_slug].active = true;
  437 + this.time_series.forEach(ts => {
  438 + if (ts.parameter === parameter_slug && this$.targets[ts.target.slug].active) {
  439 + return ts.show();
  440 + }
  441 + });
465 442
466 - showCatalogLayer(catalog_slug) {  
467 - this.time_series.forEach(ts => ts.showCatalogLayer(catalog_slug));  
468 - return this;  
469 - } 443 + return this;
  444 + }
470 445
471 - hideCatalogLayer(catalog_slug) {  
472 - this.time_series.forEach(ts => ts.hideCatalogLayer(catalog_slug));  
473 - return this; 446 + disableParameter(parameter_slug) {
  447 + if (!(parameter_slug in this.parameters)) {
  448 + console.error(`Unknown parameter ${parameter_slug}.`);
474 } 449 }
475 -  
476 - getDomain() {  
477 - if (this.current_started_at != null && this.current_stopped_at != null) {  
478 - return [this.current_started_at, this.current_stopped_at]; 450 + this.parameters[parameter_slug].active = false;
  451 + this.time_series.forEach(ts => {
  452 + if (ts.parameter === parameter_slug) {
  453 + return ts.hide();
479 } 454 }
480 - return [this.started_at, this.stopped_at]; 455 + });
  456 +
  457 + return this;
  458 + }
  459 +
  460 + showCatalogLayer(catalog_slug) {
  461 + this.time_series.forEach(ts => ts.showCatalogLayer(catalog_slug));
  462 + return this;
  463 + }
  464 +
  465 + hideCatalogLayer(catalog_slug) {
  466 + this.time_series.forEach(ts => ts.hideCatalogLayer(catalog_slug));
  467 + return this;
  468 + }
  469 +
  470 + getDomain() {
  471 + if (this.current_started_at != null && this.current_stopped_at != null) {
  472 + return [this.current_started_at, this.current_stopped_at];
481 } 473 }
  474 + return [this.started_at, this.stopped_at];
  475 + }
482 476
483 - resizeDomain(started_at, stopped_at) {  
484 - let ref$;  
485 - let max_stopped_at;  
486 - let formatted_started_at;  
487 - let formatted_stopped_at;  
488 - let tsv;  
489 - let tsv_cursor;  
490 - let tsv_length;  
491 - let zoomedOnVisible;  
492 - if (stopped_at < started_at) {  
493 - ref$ = [stopped_at, started_at], started_at = ref$[0], stopped_at = ref$[1];  
494 - }  
495 - if (started_at === stopped_at) {  
496 - console.warn("Please provide distinct start and stop dates.");  
497 - return;  
498 - }  
499 - max_stopped_at = started_at.clone().add(2, 'years');  
500 - if (stopped_at > max_stopped_at) {  
501 - console.warn("The time interval was truncated beacuse it was bigger than two years.");  
502 - stopped_at = max_stopped_at;  
503 - }  
504 - this.setStartAndStop(started_at, stopped_at);  
505 - formatted_started_at = started_at.format();  
506 - formatted_stopped_at = stopped_at.format();  
507 - if ((this.started_at <= started_at && started_at <= this.stopped_at) && (this.started_at <= stopped_at && stopped_at <= this.stopped_at)) {  
508 - console.info(`Resizing the temporal domain from ${formatted_started_at} to ${formatted_stopped_at} without fetching new data…`);  
509 - tsv = this.time_series.filter(ts => ts.visible);  
510 - tsv_cursor = 0;  
511 - tsv_length = tsv.length;  
512 - zoomedOnVisible = new Promise((resolve, reject) => {  
513 - let tsv_zoom_on_next;  
514 - tsv_zoom_on_next = i => {  
515 - let ts;  
516 - if (i >= tsv_length) {  
517 - resolve();  
518 - return;  
519 - }  
520 - ts = tsv[i];  
521 - return ts.zoomIn(started_at, stopped_at).then(() => tsv_zoom_on_next(i + 1));  
522 - };  
523 - return tsv_zoom_on_next(0);  
524 - });  
525 - zoomedOnVisible.then(function () {  
526 - return this.time_series.forEach(ts => {  
527 - if (!ts.visible) {  
528 - return ts.zoomIn(started_at, stopped_at);  
529 - }  
530 - }); 477 + resizeDomain(started_at, stopped_at) {
  478 + let ref$;
  479 + let max_stopped_at;
  480 + let formatted_started_at;
  481 + let formatted_stopped_at;
  482 + let tsv;
  483 + let tsv_cursor;
  484 + let tsv_length;
  485 + let zoomedOnVisible;
  486 + if (stopped_at < started_at) {
  487 + ref$ = [stopped_at, started_at], started_at = ref$[0], stopped_at = ref$[1];
  488 + }
  489 + if (started_at === stopped_at) {
  490 + console.warn("Please provide distinct start and stop dates.");
  491 + return;
  492 + }
  493 + max_stopped_at = started_at.clone().add(2, 'years');
  494 + if (stopped_at > max_stopped_at) {
  495 + console.warn("The time interval was truncated because it was bigger than two years.");
  496 + stopped_at = max_stopped_at;
  497 + }
  498 + this.setStartAndStop(started_at, stopped_at);
  499 + formatted_started_at = started_at.format();
  500 + formatted_stopped_at = stopped_at.format();
  501 + if ((this.started_at <= started_at && started_at <= this.stopped_at) &&
  502 + (this.started_at <= stopped_at && stopped_at <= this.stopped_at)) {
  503 + console.info(`Resizing the temporal domain from ${formatted_started_at} to ${formatted_stopped_at} without fetching new data…`);
  504 + tsv = this.time_series.filter(ts => ts.visible);
  505 + tsv_length = tsv.length;
  506 + zoomedOnVisible = new Promise((resolve, reject) => {
  507 + let tsv_zoom_on_next;
  508 + tsv_zoom_on_next = i => {
  509 + let ts;
  510 + if (i >= tsv_length) {
  511 + resolve();
  512 + return;
  513 + }
  514 + ts = tsv[i];
  515 + ts.zoomIn(started_at, stopped_at)
  516 + .then(() => tsv_zoom_on_next(i + 1));
  517 + };
  518 + return tsv_zoom_on_next(0);
  519 + });
  520 + zoomedOnVisible.then(() => {
  521 + this.time_series.forEach(ts => {
  522 + if (!ts.visible) {
  523 + ts.zoomIn(started_at, stopped_at);
  524 + }
531 }); 525 });
532 - this.orbits.resizeDomain(started_at, stopped_at);  
533 - } else {  
534 - console.info(`Resizing the temporal domain from ${formatted_started_at} to ${formatted_stopped_at} and fetching new data…`);  
535 - console.warn("This might take a good while… Why not see what else we're up to on http://cdpp.eu while you're waiting?");  
536 - this.clearPlots();  
537 - this.loadAndCreatePlots(started_at, stopped_at);  
538 - }  
539 - return this; 526 + });
  527 + this.orbits.resizeDomain(started_at, stopped_at);
  528 + } else {
  529 + console.info(`Resizing the temporal domain from ${formatted_started_at} to ${formatted_stopped_at} and fetching new data…`);
  530 + console.warn("This might take a good while… Why not see what else we're up to on http://cdpp.eu while you're waiting?");
  531 + this.clearPlots();
  532 + this.loadAndCreatePlots(started_at, stopped_at);
540 } 533 }
  534 + return this;
  535 + }
541 536
542 - resetZoom() {  
543 - this.time_series.forEach(ts => ts.resetZoom());  
544 - this.orbits.resetZoom();  
545 - this.setStartAndStop(this.started_at, this.stopped_at); 537 + resetZoom() {
  538 + this.time_series.forEach(ts => ts.resetZoom());
  539 + this.orbits.resetZoom();
  540 + this.setStartAndStop(this.started_at, this.stopped_at);
546 541
547 - return this;  
548 - } 542 + return this;
  543 + }
549 544
550 - setStartAndStop(started_at, stopped_at) {  
551 - console.info(`Setting time interval from ${started_at} to ${stopped_at}…`);  
552 - this.current_started_at = started_at;  
553 - this.current_stopped_at = stopped_at;  
554 - $("#started_at").val(started_at.format(INPUT_TIME_FORMAT));  
555 - $("#stopped_at").val(stopped_at.format(INPUT_TIME_FORMAT)); 545 + setStartAndStop(started_at, stopped_at) {
  546 + console.info(`Setting time interval from ${started_at} to ${stopped_at}…`);
  547 + this.current_started_at = started_at;
  548 + this.current_stopped_at = stopped_at;
  549 + $("#started_at").val(started_at.format(INPUT_TIME_FORMAT));
  550 + $("#stopped_at").val(stopped_at.format(INPUT_TIME_FORMAT));
556 551
557 - return this;  
558 - } 552 + return this;
559 } 553 }
  554 + }
560 555
561 - return SpaceWeather;  
562 - })()); 556 + // return SpaceWeather;
  557 + // })());
563 558
564 ///////////////////////////////////////////////////////////////////////////// 559 /////////////////////////////////////////////////////////////////////////////
565 //// TIME SERIES //////////////////////////////////////////////////////////// 560 //// TIME SERIES ////////////////////////////////////////////////////////////
566 561
567 - global.TimeSeries = TimeSeries = ((() => {  
568 - const RATIO = GOLDEN_RATIO ** 4;  
569 -  
570 - class TimeSeries {  
571 - constructor(parameter, title, target, data, visible, container, options) {  
572 - this.onBrushEnd = this.onBrushEnd.bind(this);  
573 - this.onDoubleClick = this.onDoubleClick.bind(this);  
574 - this.onMouseOut = this.onMouseOut.bind(this);  
575 - this.onMouseOver = this.onMouseOver.bind(this);  
576 - this.onMouseMove = this.onMouseMove.bind(this);  
577 - this.parameter = parameter;  
578 - this.title = title;  
579 - this.target = target;  
580 - this.data = data;  
581 - this.visible = visible;  
582 - this.container = container;  
583 - this.options = options || {};  
584 - let now = moment();  
585 - const dataLength = this.data.length;  
586 - const predictiveData = [];  
587 - let d;  
588 - for (let i = 0; i < dataLength; ++i) {  
589 - d = this.data[i];  
590 - if (moment(d.x) >= now) {  
591 - predictiveData.push(d);  
592 - } 562 + // global.TimeSeries = TimeSeries = ((() => {
  563 + const RATIO = GOLDEN_RATIO ** 4;
  564 +
  565 + class TimeSeries {
  566 + constructor(parameter, title, target, data, visible, container, options) {
  567 + this.onBrushEnd = this.onBrushEnd.bind(this);
  568 + this.onDoubleClick = this.onDoubleClick.bind(this);
  569 + this.onMouseOut = this.onMouseOut.bind(this);
  570 + this.onMouseOver = this.onMouseOver.bind(this);
  571 + this.onMouseMove = this.onMouseMove.bind(this);
  572 + this.parameter = parameter;
  573 + this.title = title;
  574 + this.target = target;
  575 + this.data = data;
  576 + this.visible = visible;
  577 + this.container = container;
  578 + this.options = options || {};
  579 + let now = moment();
  580 + const dataLength = this.data.length;
  581 + const predictiveData = [];
  582 + let d;
  583 + for (let i = 0; i < dataLength; ++i) {
  584 + d = this.data[i];
  585 + if (moment(d.x) >= now) {
  586 + predictiveData.push(d);
593 } 587 }
594 - this.predictiveData = predictiveData;  
595 - this.init();  
596 } 588 }
  589 + this.predictiveData = predictiveData;
  590 + this.init();
  591 + }
597 592
598 - toString() {  
599 - return `${this.title} of ${this.target.name}`;  
600 - } 593 + toString() {
  594 + return `${this.title} of ${this.target.name}`;
  595 + }
601 596
602 - init() {  
603 - let ref$;  
604 - let width;  
605 - let height;  
606 - let formatMillisecond;  
607 - let formatSecond;  
608 - let formatMinute;  
609 - let formatHour;  
610 - let formatDay;  
611 - let formatWeek;  
612 - let formatMonth;  
613 - let formatYear;  
614 - let multiFormat;  
615 - let clipId;  
616 - let i$;  
617 - let len$;  
618 - let line;  
619 - let lineElement;  
620 - let dx;  
621 - const this$ = this;  
622 - console.info(`Initializing plot of ${this}…`);  
623 - this.margin = {  
624 - top: 30,  
625 - right: 20,  
626 - bottom: 30,  
627 - left: 80  
628 - };  
629 - ref$ = this.recomputeDimensions(), width = ref$[0], height = ref$[1];  
630 - this.xDataExtent = d3.extent(this.data, d => d.x);  
631 - this.yDataExtent = d3.extent(this.data, d => d.y);  
632 - if (this.options['started_at']) {  
633 - this.xDataExtent[0] = this.options['started_at']; 597 + init() {
  598 + let ref$;
  599 + let width;
  600 + let height;
  601 + let formatMillisecond;
  602 + let formatSecond;
  603 + let formatMinute;
  604 + let formatHour;
  605 + let formatDay;
  606 + let formatWeek;
  607 + let formatMonth;
  608 + let formatYear;
  609 + let multiFormat;
  610 + let clipId;
  611 + let i$;
  612 + let len$;
  613 + let line;
  614 + let lineElement;
  615 + let dx;
  616 + const this$ = this;
  617 + console.info(`Initializing plot of ${this}…`);
  618 + this.margin = {
  619 + top: 30,
  620 + right: 20,
  621 + bottom: 30,
  622 + left: 80
  623 + };
  624 + ref$ = this.recomputeDimensions(), width = ref$[0], height = ref$[1];
  625 + this.xDataExtent = d3.extent(this.data, d => d.x);
  626 + this.yDataExtent = d3.extent(this.data, d => d.y);
  627 + if (this.options['started_at']) {
  628 + this.xDataExtent[0] = this.options['started_at'];
  629 + }
  630 + if (this.options['stopped_at']) {
  631 + this.xDataExtent[1] = this.options['stopped_at'];
  632 + }
  633 + this.xScale = d3.scaleTime().domain(this.xDataExtent);
  634 + this.yScale = d3.scaleLinear().domain(this.yDataExtent);
  635 + formatMillisecond = d3.utcFormat(".%L");
  636 + formatSecond = d3.utcFormat(":%S");
  637 + formatMinute = d3.utcFormat("%H:%M");
  638 + formatHour = d3.utcFormat("%H:%M");
  639 + formatDay = d3.utcFormat("%a %d");
  640 + formatWeek = d3.utcFormat("%b %d");
  641 + formatMonth = d3.utcFormat("%B");
  642 + formatYear = d3.utcFormat("%Y");
  643 + multiFormat = date => {
  644 + if (date > d3.timeSecond(date)) {
  645 + return formatMillisecond(date);
634 } 646 }
635 - if (this.options['stopped_at']) {  
636 - this.xDataExtent[1] = this.options['stopped_at']; 647 + if (date > d3.timeMinute(date)) {
  648 + return formatSecond(date);
637 } 649 }
638 - this.xScale = d3.scaleTime().domain(this.xDataExtent);  
639 - this.yScale = d3.scaleLinear().domain(this.yDataExtent);  
640 - formatMillisecond = d3.utcFormat(".%L");  
641 - formatSecond = d3.utcFormat(":%S");  
642 - formatMinute = d3.utcFormat("%H:%M");  
643 - formatHour = d3.utcFormat("%H:%M");  
644 - formatDay = d3.utcFormat("%a %d");  
645 - formatWeek = d3.utcFormat("%b %d");  
646 - formatMonth = d3.utcFormat("%B");  
647 - formatYear = d3.utcFormat("%Y");  
648 - multiFormat = date => {  
649 - if (date > d3.timeSecond(date)) {  
650 - return formatMillisecond(date);  
651 - }  
652 - if (date > d3.timeMinute(date)) {  
653 - return formatSecond(date);  
654 - }  
655 - if (date > d3.timeHour(date)) {  
656 - return formatMinute(date);  
657 - }  
658 - if (date > d3.timeDay(date)) {  
659 - return formatHour(date);  
660 - }  
661 - if (date > d3.timeMonth(date)) {  
662 - if (date > d3.timeWeek(date)) {  
663 - return formatDay(date);  
664 - } else {  
665 - return formatWeek(date);  
666 - }  
667 - }  
668 - if (date > d3.timeYear(date)) {  
669 - return formatMonth(date);  
670 - }  
671 - return formatYear(date);  
672 - };  
673 - this.xAxis = d3.axisBottom().tickFormat(multiFormat).ticks(7);  
674 - this.yAxis = d3.axisLeft().ticks(10);  
675 - this.svg = d3.select(this.container).append('svg');  
676 - this.svg.attr("class", `${this.parameter} ${this.target.slug}`);  
677 - this.line = d3.line().x(d => this$.xScale(d.x)).y(d => this$.yScale(d.y));  
678 - this.plotWrapper = this.svg.append('g');  
679 - this.plotWrapper.attr('transform', `translate(${this.margin.left},${this.margin.top})`);  
680 - clipId = `ts-clip-${this.parameter}-${this.target.slug}`;  
681 - this.clip = this.svg.append("defs").append("svg:clipPath").attr("id", clipId).append("svg:rect").attr("x", 0).attr("y", 0);  
682 - this.pathWrapper = this.plotWrapper.append('g');  
683 - this.pathWrapper.attr("clip-path", `url(#${clipId})`);  
684 - this.path = this.pathWrapper.append('path').datum(this.data).classed('line', true);  
685 - this.predictiveDataPath = this.pathWrapper.append('path').datum(this.predictiveData).classed('predictive-line', true);  
686 - this.createCatalogLayers();  
687 - this.horizontalLines = [];  
688 - if (this.options['horizontalLines']) {  
689 - for (i$ = 0, len$ = (ref$ = this.options['horizontalLines']).length; i$ < len$; ++i$) {  
690 - line = ref$[i$];  
691 - lineElement = this.svg.append("line").attr("class", "line horitonal-line").style("stroke", "orange").style("stroke-dasharray", "3, 2");  
692 - this.horizontalLines.push({  
693 - 'element': lineElement,  
694 - 'config': line  
695 - }); 650 + if (date > d3.timeHour(date)) {
  651 + return formatMinute(date);
  652 + }
  653 + if (date > d3.timeDay(date)) {
  654 + return formatHour(date);
  655 + }
  656 + if (date > d3.timeMonth(date)) {
  657 + if (date > d3.timeWeek(date)) {
  658 + return formatDay(date);
  659 + } else {
  660 + return formatWeek(date);
696 } 661 }
697 } 662 }
698 - this.brush = this.plotWrapper.append("g").attr("class", "brush");  
699 - this.plotWrapper.append('g').classed('x axis', true);  
700 - this.plotWrapper.append('g').classed('y axis', true);  
701 - this.yAxisText = this.plotWrapper.append("text").attr("transform", "rotate(-90)").attr("dy", "1em").style("text-anchor", "middle").text(this.title);  
702 - this.yAxisTextTarget = this.plotWrapper.append("text").attr("transform", "rotate(-90)").attr("dy", "1em").style("text-anchor", "middle").style("font-style", "oblique").text(this.target.name);  
703 - this.focus = this.plotWrapper.append('g').style("display", "none");  
704 - this.cursorCircle = this.focus.append("circle").attr("class", "cursor-circle").attr("r", 3);  
705 - dx = 8;  
706 - this.cursorValueShadow = this.focus.append("text").attr("class", "cursor-text cursor-text-shadow").attr("dx", dx).attr("dy", "-.3em");  
707 - this.cursorValue = this.focus.append("text").attr("class", "cursor-text cursor-value").attr("dx", dx).attr("dy", "-.3em");  
708 - this.cursorDateShadow = this.focus.append("text").attr("class", "cursor-text cursor-text-shadow").attr("dx", dx).attr("dy", "1em");  
709 - this.cursorDate = this.focus.append("text").attr("class", "cursor-text cursor-date").attr("dx", dx).attr("dy", "1em");  
710 - this.brushFunction = d3.brushX().extent([[0, 0], [width, height]]).handleSize(0).on("end", this.onBrushEnd);  
711 - this.brush.call(this.brushFunction);  
712 - this.brushOverlay = this.svg.select(".brush .overlay");  
713 - this.brushOverlay.on("mouseover.swapp", this.onMouseOver).on("mouseout.swapp", this.onMouseOut).on("mousemove.swapp", this.onMouseMove).on("dblclick.swapp", this.onDoubleClick);  
714 - return this.resize();  
715 - }  
716 -  
717 - recomputeDimensions() {  
718 - let width;  
719 - let height;  
720 - width = Math.ceil($(this.container).width() - this.margin.left - this.margin.right);  
721 - height = Math.ceil(RATIO * width);  
722 - this.plotWidth = width;  
723 - this.plotHeight = height;  
724 - return [width, height];  
725 - }  
726 -  
727 - resize() {  
728 - let ref$;  
729 - let width;  
730 - let height;  
731 - let i$;  
732 - let len$;  
733 - let line;  
734 - let lineValue;  
735 - ref$ = this.recomputeDimensions(), width = ref$[0], height = ref$[1];  
736 - console.debug(`Resizing ${this}: ${width} x ${height}…`);  
737 - this.xScale.range([0, width]);  
738 - this.yScale.range([height, 0]);  
739 - this.svg.attr('width', width + this.margin.right + this.margin.left).attr('height', height + this.margin.top + this.margin.bottom);  
740 - this.clip.attr("width", width).attr("height", height);  
741 - this.path.attr('d', this.line);  
742 - this.predictiveDataPath.attr('d', this.line);  
743 - for (i$ = 0, len$ = (ref$ = this.horizontalLines).length; i$ < len$; ++i$) {  
744 - line = ref$[i$];  
745 - lineValue = this.yScale(line['config']['value']) + this.margin.top;  
746 - line['element'].attr("x1", this.margin.left).attr("y1", lineValue).attr("x2", this.margin.left + width).attr("y2", lineValue); 663 + if (date > d3.timeYear(date)) {
  664 + return formatMonth(date);
747 } 665 }
748 - this.xAxis.scale(this.xScale);  
749 - this.yAxis.scale(this.yScale);  
750 - this.xAxis.ticks(Math.floor(width / 80.0));  
751 - this.yAxis.ticks(Math.floor(height / 18.0));  
752 - this.svg.select('.x.axis').attr('transform', `translate(0,${height})`).call(this.xAxis);  
753 - this.svg.select('.y.axis').call(this.yAxis);  
754 - this.yAxisText.attr("y", 20 - this.margin.left).attr("x", 0 - height / 2);  
755 - this.yAxisTextTarget.attr("y", 0 - this.margin.left).attr("x", 0 - height / 2.0);  
756 - this.resizeCatalogLayers();  
757 - if (!this.visible) {  
758 - this.hide(); 666 + return formatYear(date);
  667 + };
  668 + this.xAxis = d3.axisBottom().tickFormat(multiFormat).ticks(7);
  669 + this.yAxis = d3.axisLeft().ticks(10);
  670 + this.svg = d3.select(this.container).append('svg');
  671 + this.svg.attr("class", `${this.parameter} ${this.target.slug}`);
  672 + this.line = d3.line().x(d => this$.xScale(d.x)).y(d => this$.yScale(d.y));
  673 + this.plotWrapper = this.svg.append('g');
  674 + this.plotWrapper.attr('transform', `translate(${this.margin.left},${this.margin.top})`);
  675 + clipId = `ts-clip-${this.parameter}-${this.target.slug}`;
  676 + this.clip = this.svg.append("defs").append("svg:clipPath").attr("id", clipId).append("svg:rect").attr("x", 0).attr("y", 0);
  677 + this.pathWrapper = this.plotWrapper.append('g');
  678 + this.pathWrapper.attr("clip-path", `url(#${clipId})`);
  679 + this.path = this.pathWrapper.append('path').datum(this.data).classed('line', true);
  680 + this.predictiveDataPath = this.pathWrapper.append('path').datum(this.predictiveData).classed('predictive-line', true);
  681 + this.createCatalogLayers();
  682 + this.horizontalLines = [];
  683 + if (this.options['horizontalLines']) {
  684 + for (i$ = 0, len$ = (ref$ = this.options['horizontalLines']).length; i$ < len$; ++i$) {
  685 + line = ref$[i$];
  686 + lineElement = this.svg.append("line").attr("class", "line horitonal-line").style("stroke", "orange").style("stroke-dasharray", "3, 2");
  687 + this.horizontalLines.push({
  688 + 'element': lineElement,
  689 + 'config': line
  690 + });
759 } 691 }
760 - return this;  
761 } 692 }
  693 + this.brush = this.plotWrapper.append("g").attr("class", "brush");
  694 + this.plotWrapper.append('g').classed('x axis', true);
  695 + this.plotWrapper.append('g').classed('y axis', true);
  696 + this.yAxisText = this.plotWrapper.append("text").attr("transform", "rotate(-90)").attr("dy", "1em").style("text-anchor", "middle").text(this.title);
  697 + this.yAxisTextTarget = this.plotWrapper.append("text").attr("transform", "rotate(-90)").attr("dy", "1em").style("text-anchor", "middle").style("font-style", "oblique").text(this.target.name);
  698 + this.focus = this.plotWrapper.append('g').style("display", "none");
  699 + this.cursorCircle = this.focus.append("circle").attr("class", "cursor-circle").attr("r", 3);
  700 + dx = 8;
  701 + this.cursorValueShadow = this.focus.append("text").attr("class", "cursor-text cursor-text-shadow").attr("dx", dx).attr("dy", "-.3em");
  702 + this.cursorValue = this.focus.append("text").attr("class", "cursor-text cursor-value").attr("dx", dx).attr("dy", "-.3em");
  703 + this.cursorDateShadow = this.focus.append("text").attr("class", "cursor-text cursor-text-shadow").attr("dx", dx).attr("dy", "1em");
  704 + this.cursorDate = this.focus.append("text").attr("class", "cursor-text cursor-date").attr("dx", dx).attr("dy", "1em");
  705 + this.brushFunction = d3.brushX().extent([[0, 0], [width, height]]).handleSize(0).on("end", this.onBrushEnd);
  706 + this.brush.call(this.brushFunction);
  707 + this.brushOverlay = this.svg.select(".brush .overlay");
  708 + this.brushOverlay.on("mouseover.swapp", this.onMouseOver).on("mouseout.swapp", this.onMouseOut).on("mousemove.swapp", this.onMouseMove).on("dblclick.swapp", this.onDoubleClick);
  709 + return this.resize();
  710 + }
762 711
763 - clear() {  
764 - $(this.svg.node()).remove();  
765 - return this.visible = false;  
766 - } 712 + recomputeDimensions() {
  713 + let width;
  714 + let height;
  715 + width = Math.ceil($(this.container).width() - this.margin.left - this.margin.right);
  716 + height = Math.ceil(RATIO * width);
  717 + this.plotWidth = width;
  718 + this.plotHeight = height;
  719 + return [width, height];
  720 + }
767 721
768 - show() {  
769 - $(this.svg.node()).show();  
770 - return this.visible = true;  
771 - } 722 + resize() {
  723 + let ref$;
  724 + let width;
  725 + let height;
  726 + let i$;
  727 + let len$;
  728 + let line;
  729 + let lineValue;
  730 + ref$ = this.recomputeDimensions(), width = ref$[0], height = ref$[1];
  731 + console.debug(`Resizing ${this}: ${width} x ${height}…`);
  732 + this.xScale.range([0, width]);
  733 + this.yScale.range([height, 0]);
  734 + this.svg.attr('width', width + this.margin.right + this.margin.left).attr('height', height + this.margin.top + this.margin.bottom);
  735 + this.clip.attr("width", width).attr("height", height);
  736 + this.path.attr('d', this.line);
  737 + this.predictiveDataPath.attr('d', this.line);
  738 + for (i$ = 0, len$ = (ref$ = this.horizontalLines).length; i$ < len$; ++i$) {
  739 + line = ref$[i$];
  740 + lineValue = this.yScale(line['config']['value']) + this.margin.top;
  741 + line['element'].attr("x1", this.margin.left).attr("y1", lineValue).attr("x2", this.margin.left + width).attr("y2", lineValue);
  742 + }
  743 + this.xAxis.scale(this.xScale);
  744 + this.yAxis.scale(this.yScale);
  745 + this.xAxis.ticks(Math.floor(width / 80.0));
  746 + this.yAxis.ticks(Math.floor(height / 18.0));
  747 + this.svg.select('.x.axis').attr('transform', `translate(0,${height})`).call(this.xAxis);
  748 + this.svg.select('.y.axis').call(this.yAxis);
  749 + this.yAxisText.attr("y", 20 - this.margin.left).attr("x", 0 - height / 2);
  750 + this.yAxisTextTarget.attr("y", 0 - this.margin.left).attr("x", 0 - height / 2.0);
  751 + this.resizeCatalogLayers();
  752 + if (!this.visible) {
  753 + this.hide();
  754 + }
  755 + return this;
  756 + }
  757 +
  758 + clear() {
  759 + $(this.svg.node()).remove();
  760 + return this.visible = false;
  761 + }
772 762
773 - hide() {  
774 - $(this.svg.node()).hide();  
775 - return this.visible = false; 763 + show() {
  764 + $(this.svg.node()).show();
  765 + return this.visible = true;
  766 + }
  767 +
  768 + hide() {
  769 + $(this.svg.node()).hide();
  770 + return this.visible = false;
  771 + }
  772 +
  773 + onMouseMove() {
  774 + let x;
  775 + x = this.xScale.invert(d3.mouse(this.brushOverlay.node())[0]);
  776 + if (this.options.onMouseMove != null) {
  777 + return this.options.onMouseMove(x);
  778 + } else {
  779 + return this.moveCursor(x);
776 } 780 }
  781 + }
777 782
778 - onMouseMove() {  
779 - let x;  
780 - x = this.xScale.invert(d3.mouse(this.brushOverlay.node())[0]);  
781 - if (this.options.onMouseMove != null) {  
782 - return this.options.onMouseMove(x);  
783 - } else {  
784 - return this.moveCursor(x);  
785 - } 783 + onMouseOver() {
  784 + if (this.options.onMouseOver != null) {
  785 + return this.options.onMouseOver();
  786 + } else {
  787 + return this.showCursor();
786 } 788 }
  789 + }
787 790
788 - onMouseOver() {  
789 - if (this.options.onMouseOver != null) {  
790 - return this.options.onMouseOver();  
791 - } else {  
792 - return this.showCursor();  
793 - } 791 + onMouseOut() {
  792 + if (this.options.onMouseOut != null) {
  793 + return this.options.onMouseOut();
  794 + } else {
  795 + return this.hideCursor();
794 } 796 }
  797 + }
795 798
796 - onMouseOut() {  
797 - if (this.options.onMouseOut != null) {  
798 - return this.options.onMouseOut();  
799 - } else {  
800 - return this.hideCursor();  
801 - } 799 + onDoubleClick() {
  800 + if (this.options.onDblClick != null) {
  801 + return this.options.onDblClick();
  802 + } else {
  803 + return this.resetZoom();
802 } 804 }
  805 + }
803 806
804 - onDoubleClick() {  
805 - if (this.options.onDblClick != null) {  
806 - return this.options.onDblClick(); 807 + onBrushEnd() {
  808 + let s;
  809 + let minmax;
  810 + s = d3.event.selection;
  811 + if (s) {
  812 + minmax = [s[0], s[1]].map(this.xScale.invert, this.xScale);
  813 + this.brush.call(this.brushFunction.move, null);
  814 + if (this.options.onBrushEnd != null) {
  815 + return this.options.onBrushEnd(minmax[0], minmax[1]);
807 } else { 816 } else {
808 - return this.resetZoom(); 817 + return this.zoomIn(minmax[0], minmax[1]);
809 } 818 }
810 } 819 }
  820 + }
811 821
812 - onBrushEnd() {  
813 - let s;  
814 - let minmax;  
815 - s = d3.event.selection;  
816 - if (s) {  
817 - minmax = [s[0], s[1]].map(this.xScale.invert, this.xScale);  
818 - this.brush.call(this.brushFunction.move, null);  
819 - if (this.options.onBrushEnd != null) {  
820 - return this.options.onBrushEnd(minmax[0], minmax[1]);  
821 - } else {  
822 - return this.zoomIn(minmax[0], minmax[1]);  
823 - }  
824 - } 822 + zoomIn(startDate, stopDate) {
  823 + let ref$;
  824 + let minDate;
  825 + let maxDate;
  826 + console.debug(`Zooming in ${this} from ${startDate} to ${stopDate}.`);
  827 + ref$ = this.xDataExtent, minDate = ref$[0], maxDate = ref$[1];
  828 + if (startDate < minDate) {
  829 + startDate = minDate;
825 } 830 }
826 -  
827 - zoomIn(startDate, stopDate) { 831 + if (stopDate > maxDate) {
  832 + stopDate = maxDate;
  833 + }
  834 + this.xScale.domain([startDate, stopDate]);
  835 + this.yScale.domain(d3.extent(this.data, d => {
828 let ref$; 836 let ref$;
829 - let minDate;  
830 - let maxDate;  
831 - console.debug(`Zooming in ${this} from ${startDate} to ${stopDate}.`);  
832 - ref$ = this.xDataExtent, minDate = ref$[0], maxDate = ref$[1];  
833 - if (startDate < minDate) {  
834 - startDate = minDate;  
835 - }  
836 - if (stopDate > maxDate) {  
837 - stopDate = maxDate; 837 + if (startDate <= (ref$ = d.x) && ref$ <= stopDate) {
  838 + return d.y;
  839 + } else {
  840 + return 0;
838 } 841 }
839 - this.xScale.domain([startDate, stopDate]);  
840 - this.yScale.domain(d3.extent(this.data, d => {  
841 - let ref$;  
842 - if (startDate <= (ref$ = d.x) && ref$ <= stopDate) {  
843 - return d.y;  
844 - } else {  
845 - return 0;  
846 - }  
847 - }));  
848 - return this.applyZoom();  
849 - } 842 + }));
  843 + return this.applyZoom();
  844 + }
850 845
851 - resetZoom() {  
852 - this.xScale.domain(this.xDataExtent);  
853 - this.yScale.domain(this.yDataExtent);  
854 - return this.applyZoom();  
855 - } 846 + resetZoom() {
  847 + this.xScale.domain(this.xDataExtent);
  848 + this.yScale.domain(this.yDataExtent);
  849 + return this.applyZoom();
  850 + }
856 851
857 - applyZoom() {  
858 - let duration;  
859 - let t;  
860 - duration = 0;  
861 - if (this.visible) {  
862 - duration = 750;  
863 - console.debug(`Applying zoom to visible ${this}…`);  
864 - t = this.svg.transition().duration(duration);  
865 - this.svg.select('.x.axis').transition(t).call(this.xAxis);  
866 - this.svg.select('.y.axis').transition(t).call(this.yAxis);  
867 - this.path.transition(t).attr('d', this.line);  
868 - this.predictiveDataPath.transition(t).attr('d', this.line); 852 + applyZoom() {
  853 + let duration;
  854 + let t;
  855 + duration = 0;
  856 + if (this.visible) {
  857 + duration = 750;
  858 + console.debug(`Applying zoom to visible ${this}…`);
  859 + t = this.svg.transition().duration(duration);
  860 + this.svg.select('.x.axis').transition(t).call(this.xAxis);
  861 + this.svg.select('.y.axis').transition(t).call(this.yAxis);
  862 + this.path.transition(t).attr('d', this.line);
  863 + this.predictiveDataPath.transition(t).attr('d', this.line);
  864 + } else {
  865 + console.debug(`Applying zoom to hidden ${this}…`);
  866 + this.svg.select('.x.axis').call(this.xAxis);
  867 + this.svg.select('.y.axis').call(this.yAxis);
  868 + this.path.attr('d', this.line);
  869 + this.predictiveDataPath.attr('d', this.line);
  870 + }
  871 + this.resizeCatalogLayers();
  872 + this.hideCursor();
  873 + return new Promise((resolve, reject) => {
  874 + if (0 === duration) {
  875 + return resolve();
869 } else { 876 } else {
870 - console.debug(`Applying zoom to hidden ${this}…`);  
871 - this.svg.select('.x.axis').call(this.xAxis);  
872 - this.svg.select('.y.axis').call(this.yAxis);  
873 - this.path.attr('d', this.line);  
874 - this.predictiveDataPath.attr('d', this.line); 877 + return setTimeout(() => resolve(), duration + 50);
875 } 878 }
876 - this.resizeCatalogLayers();  
877 - this.hideCursor();  
878 - return new Promise((resolve, reject) => {  
879 - if (0 === duration) {  
880 - return resolve();  
881 - } else {  
882 - return setTimeout(() => resolve(), duration + 50);  
883 - }  
884 - });  
885 - } 879 + });
  880 + }
886 881
887 - createCatalogLayers() {  
888 - let catalog_slug;  
889 - let ref$;  
890 - let layers;  
891 - let i$;  
892 - let len$;  
893 - let layer;  
894 - let started_at;  
895 - let stopped_at;  
896 - this.layers_rects = {};  
897 - for (catalog_slug in ref$ = this.target.config.layers) {  
898 - layers = ref$[catalog_slug];  
899 - this.layers_rects[catalog_slug] = [];  
900 - for (i$ = 0, len$ = layers.length; i$ < len$; ++i$) {  
901 - layer = layers[i$];  
902 - started_at = moment(layer.start);  
903 - stopped_at = moment(layer.stop);  
904 - this.layers_rects[catalog_slug].push(this.createCatalogLayer(started_at, stopped_at));  
905 - }  
906 - this.hideCatalogLayer(catalog_slug); 882 + createCatalogLayers() {
  883 + let catalog_slug;
  884 + let ref$;
  885 + let layers;
  886 + let i$;
  887 + let len$;
  888 + let layer;
  889 + let started_at;
  890 + let stopped_at;
  891 + this.layers_rects = {};
  892 + for (catalog_slug in ref$ = this.target.config.layers) {
  893 + layers = ref$[catalog_slug];
  894 + this.layers_rects[catalog_slug] = [];
  895 + for (i$ = 0, len$ = layers.length; i$ < len$; ++i$) {
  896 + layer = layers[i$];
  897 + started_at = moment(layer.start);
  898 + stopped_at = moment(layer.stop);
  899 + this.layers_rects[catalog_slug].push(this.createCatalogLayer(started_at, stopped_at));
907 } 900 }
908 - return this;  
909 - }  
910 -  
911 - createCatalogLayer(started_at, stopped_at) {  
912 - let layer_rect;  
913 - layer_rect = this.pathWrapper.append("rect").attr('y', 0).attr('height', this.plotHeight).attr('fill', '#FFFD64C2');  
914 - return layer_rect; 901 + this.hideCatalogLayer(catalog_slug);
915 } 902 }
  903 + return this;
  904 + }
916 905
917 - resizeCatalogLayers() {  
918 - let catalog_slug;  
919 - let ref$;  
920 - let layers;  
921 - let i$;  
922 - let len$;  
923 - let i;  
924 - let layer;  
925 - let started_at;  
926 - let stopped_at;  
927 - let width;  
928 - for (catalog_slug in ref$ = this.target.config.layers) {  
929 - layers = ref$[catalog_slug];  
930 - for (i$ = 0, len$ = layers.length; i$ < len$; ++i$) {  
931 - i = i$;  
932 - layer = layers[i$];  
933 - started_at = moment(layer.start);  
934 - stopped_at = moment(layer.stop);  
935 - width = Math.max(2, this.xScale(stopped_at) - this.xScale(started_at));  
936 - this.layers_rects[catalog_slug][i].attr('x', this.xScale(started_at)).attr('width', width);  
937 - }  
938 - }  
939 - return this;  
940 - } 906 + createCatalogLayer(started_at, stopped_at) {
  907 + let layer_rect;
  908 + layer_rect = this.pathWrapper.append("rect").attr('y', 0).attr('height', this.plotHeight).attr('fill', '#FFFD64C2');
  909 + return layer_rect;
  910 + }
941 911
942 - showCatalogLayer(catalog_slug) {  
943 - let i$;  
944 - let ref$;  
945 - let len$;  
946 - let i;  
947 - let layer;  
948 - for (i$ = 0, len$ = (ref$ = this.target.config.layers[catalog_slug]).length; i$ < len$; ++i$) { 912 + resizeCatalogLayers() {
  913 + let catalog_slug;
  914 + let ref$;
  915 + let layers;
  916 + let i$;
  917 + let len$;
  918 + let i;
  919 + let layer;
  920 + let started_at;
  921 + let stopped_at;
  922 + let width;
  923 + for (catalog_slug in ref$ = this.target.config.layers) {
  924 + layers = ref$[catalog_slug];
  925 + for (i$ = 0, len$ = layers.length; i$ < len$; ++i$) {
949 i = i$; 926 i = i$;
950 - layer = ref$[i$];  
951 - this.layers_rects[catalog_slug][i].style("display", null); 927 + layer = layers[i$];
  928 + started_at = moment(layer.start);
  929 + stopped_at = moment(layer.stop);
  930 + width = Math.max(2, this.xScale(stopped_at) - this.xScale(started_at));
  931 + this.layers_rects[catalog_slug][i].attr('x', this.xScale(started_at)).attr('width', width);
952 } 932 }
953 - return this;  
954 } 933 }
  934 + return this;
  935 + }
955 936
956 - hideCatalogLayer(catalog_slug) {  
957 - let i$;  
958 - let ref$;  
959 - let len$;  
960 - let i;  
961 - let layer;  
962 - for (i$ = 0, len$ = (ref$ = this.target.config.layers[catalog_slug]).length; i$ < len$; ++i$) {  
963 - i = i$;  
964 - layer = ref$[i$];  
965 - this.layers_rects[catalog_slug][i].style("display", "none");  
966 - }  
967 - return this;  
968 - } 937 + showCatalogLayer(catalog_slug) {
  938 + let i$;
  939 + let ref$;
  940 + let len$;
  941 + let i;
  942 + let layer;
  943 + for (i$ = 0, len$ = (ref$ = this.target.config.layers[catalog_slug]).length; i$ < len$; ++i$) {
  944 + i = i$;
  945 + layer = ref$[i$];
  946 + this.layers_rects[catalog_slug][i].style("display", null);
  947 + }
  948 + return this;
  949 + }
969 950
970 - showCursor() {  
971 - return this.focus.style("display", null);  
972 - } 951 + hideCatalogLayer(catalog_slug) {
  952 + let i$;
  953 + let ref$;
  954 + let len$;
  955 + let i;
  956 + let layer;
  957 + for (i$ = 0, len$ = (ref$ = this.target.config.layers[catalog_slug]).length; i$ < len$; ++i$) {
  958 + i = i$;
  959 + layer = ref$[i$];
  960 + this.layers_rects[catalog_slug][i].style("display", "none");
  961 + }
  962 + return this;
  963 + }
973 964
974 - hideCursor() {  
975 - return this.focus.style("display", "none");  
976 - } 965 + showCursor() {
  966 + return this.focus.style("display", null);
  967 + }
977 968
978 - moveCursor(x0) {  
979 - let i;  
980 - let d0;  
981 - let d1;  
982 - let d;  
983 - let xx;  
984 - let yy;  
985 - let dx;  
986 - let transform;  
987 - i = this.bisectDate(this.data, x0, 1);  
988 - d0 = this.data[i - 1];  
989 - d1 = this.data[i];  
990 - if (!d1 || !d0) {  
991 - this.hideCursor();  
992 - return;  
993 - }  
994 - d = x0 - d0.x > d1.x - x0 ? d1 : d0;  
995 - xx = this.xScale(d.x);  
996 - yy = this.yScale(d.y);  
997 - const mirrored = this.plotWidth != null && xx > this.plotWidth / 2;  
998 - dx = 8;  
999 - if (mirrored) {  
1000 - dx = -1 * dx;  
1001 - }  
1002 - transform = `translate(${xx}, ${yy})`;  
1003 - this.cursorCircle.attr("transform", transform);  
1004 - this.cursorValue.attr("transform", transform).text(d.y).attr('text-anchor', mirrored ? 'end' : 'start').attr("dx", dx);  
1005 - this.cursorValueShadow.attr("transform", transform).text(d.y).attr('text-anchor', mirrored ? 'end' : 'start').attr("dx", dx);  
1006 - this.cursorDate.attr("transform", transform).text(this.timeFormat(d.x)).attr('text-anchor', mirrored ? 'end' : 'start').attr("dx", dx);  
1007 - this.cursorDateShadow.attr("transform", transform).text(this.timeFormat(d.x)).attr('text-anchor', mirrored ? 'end' : 'start').attr("dx", dx);  
1008 - this.showCursor();  
1009 - return this;  
1010 - } 969 + hideCursor() {
  970 + return this.focus.style("display", "none");
  971 + }
  972 +
  973 + moveCursor(x0) {
  974 + let i;
  975 + let d0;
  976 + let d1;
  977 + let d;
  978 + let xx;
  979 + let yy;
  980 + let dx;
  981 + let transform;
  982 + i = this.bisectDate(this.data, x0, 1);
  983 + d0 = this.data[i - 1];
  984 + d1 = this.data[i];
  985 + if (!d1 || !d0) {
  986 + this.hideCursor();
  987 + return;
  988 + }
  989 + d = x0 - d0.x > d1.x - x0 ? d1 : d0;
  990 + xx = this.xScale(d.x);
  991 + yy = this.yScale(d.y);
  992 + const mirrored = this.plotWidth != null && xx > this.plotWidth / 2;
  993 + dx = 8;
  994 + if (mirrored) {
  995 + dx = -1 * dx;
  996 + }
  997 + transform = `translate(${xx}, ${yy})`;
  998 + this.cursorCircle.attr("transform", transform);
  999 + this.cursorValue.attr("transform", transform).text(d.y).attr('text-anchor', mirrored ? 'end' : 'start').attr("dx", dx);
  1000 + this.cursorValueShadow.attr("transform", transform).text(d.y).attr('text-anchor', mirrored ? 'end' : 'start').attr("dx", dx);
  1001 + this.cursorDate.attr("transform", transform).text(this.timeFormat(d.x)).attr('text-anchor', mirrored ? 'end' : 'start').attr("dx", dx);
  1002 + this.cursorDateShadow.attr("transform", transform).text(this.timeFormat(d.x)).attr('text-anchor', mirrored ? 'end' : 'start').attr("dx", dx);
  1003 + this.showCursor();
  1004 + return this;
1011 } 1005 }
  1006 + }
1012 1007
1013 - TimeSeries.prototype.bisectDate = d3.bisector(d => d.x).left;  
1014 - TimeSeries.prototype.timeFormat = d3.utcFormat("%Y-%m-%d %H:%M");  
1015 - return TimeSeries;  
1016 - })()); 1008 + TimeSeries.prototype.bisectDate = d3.bisector(d => d.x).left;
  1009 + TimeSeries.prototype.timeFormat = d3.utcFormat("%Y-%m-%d %H:%M");
1017 1010
1018 1011
1019 /////////////////////////////////////////////////////////////////////////// 1012 ///////////////////////////////////////////////////////////////////////////
1020 //// ORBITS PLOT ////////////////////////////////////////////////////////// 1013 //// ORBITS PLOT //////////////////////////////////////////////////////////
1021 1014
1022 - global.Orbits = Orbits = ((() => {  
1023 - // "View of the solar system from above, with orbits segments for selected time\ninterval, from real data.";  
1024 - // Orbits.displayName = 'Orbits';  
1025 - // const prototype = Orbits.prototype;  
1026 - // const constructor = Orbits;  
1027 -  
1028 - class Orbits {  
1029 - constructor(container, options) {  
1030 - this.container = container;  
1031 - this.options = options != null  
1032 - ? options  
1033 - : {};  
1034 - console.log("Initializing plot of orbits…");  
1035 - this.margin = {  
1036 - top: 30,  
1037 - right: 20,  
1038 - bottom: 42,  
1039 - left: 60  
1040 - };  
1041 - this.data = {};  
1042 - this.orbiters = {};  
1043 - this.orbitersElements = {};  
1044 - this.orbitersExtrema = {};  
1045 - this.lastOrbiterData = {};  
1046 - this.xScale = d3.scaleLinear().domain([-1, 1]);  
1047 - this.yScale = d3.scaleLinear().domain([1, -1]);  
1048 - this.xAxis = d3.axisBottom().ticks(10);  
1049 - this.yAxis = d3.axisLeft().ticks(10);  
1050 - this.svg = d3.select(this.container).append('svg');  
1051 - this.plotWrapper = this.svg.append('g');  
1052 - this.plotWrapper.attr('transform', `translate(${this.margin.left},${this.margin.top})`);  
1053 - this.xAxisLine = this.plotWrapper.append('g').classed('x axis', true);  
1054 - this.yAxisLine = this.plotWrapper.append('g').classed('y axis', true);  
1055 - this.xAxisTitle = this.xAxisLine.append('text').attr('fill', '#000');  
1056 - this.xAxisTitle.style("text-anchor", "middle");  
1057 - this.xAxisTitle.append('tspan').text('Y');  
1058 - this.xAxisTitle.append('tspan').attr('dy', '3px').text('HEE').attr('font-size', '8px');  
1059 - this.xAxisTitle.append('tspan').attr('dy', '-3px').text(' (AU)');  
1060 - this.yAxisTitle = this.yAxisLine.append('text').attr('fill', '#000');  
1061 - this.yAxisTitle.style("text-anchor", "middle");  
1062 - this.yAxisTitle.append('tspan').text('X');  
1063 - this.yAxisTitle.append('tspan').attr('dy', '3px').text('HEE').attr('font-size', '8px');  
1064 - this.yAxisTitle.append('tspan').attr('dy', '-3px').text(' (AU)');  
1065 - this.yAxisTitle.attr('transform', 'rotate(-90)');  
1066 - this.sun = this.plotWrapper.append("svg:image").attr('xlink:href', this.options.sun.img).attr('width', '32px').attr('height', '32px');  
1067 - this.sun.append('svg:title').text("Sun");  
1068 - $(this.svg.node()).hide();  
1069 - this.resize();  
1070 - } 1015 + class Orbits {
  1016 + constructor(container, options) {
  1017 + this.container = container;
  1018 + this.options = options || {};
  1019 + console.log("Initializing plot of orbits…");
  1020 + this.margin = {
  1021 + top: 30,
  1022 + right: 20,
  1023 + bottom: 42,
  1024 + left: 60
  1025 + };
  1026 + this.data = {};
  1027 + this.orbiters = {};
  1028 + this.orbitersElements = {};
  1029 + this.orbitersExtrema = {};
  1030 + this.lastOrbiterData = {};
  1031 + this.xScale = d3.scaleLinear().domain([-1, 1]);
  1032 + this.yScale = d3.scaleLinear().domain([1, -1]);
  1033 + this.xAxis = d3.axisBottom().ticks(10);
  1034 + this.yAxis = d3.axisLeft().ticks(10);
  1035 + this.svg = d3.select(this.container).append('svg');
  1036 + this.plotWrapper = this.svg.append('g');
  1037 + this.plotWrapper.attr('transform', `translate(${this.margin.left},${this.margin.top})`);
  1038 + this.xAxisLine = this.plotWrapper.append('g').classed('x axis', true);
  1039 + this.yAxisLine = this.plotWrapper.append('g').classed('y axis', true);
  1040 + this.xAxisTitle = this.xAxisLine.append('text').attr('fill', '#000');
  1041 + this.xAxisTitle.style("text-anchor", "middle");
  1042 + this.xAxisTitle.append('tspan').text('Y');
  1043 + this.xAxisTitle.append('tspan').attr('dy', '3px').text('HEE').attr('font-size', '8px');
  1044 + this.xAxisTitle.append('tspan').attr('dy', '-3px').text(' (AU)');
  1045 + this.yAxisTitle = this.yAxisLine.append('text').attr('fill', '#000');
  1046 + this.yAxisTitle.style("text-anchor", "middle");
  1047 + this.yAxisTitle.append('tspan').text('X');
  1048 + this.yAxisTitle.append('tspan').attr('dy', '3px').text('HEE').attr('font-size', '8px');
  1049 + this.yAxisTitle.append('tspan').attr('dy', '-3px').text(' (AU)');
  1050 + this.yAxisTitle.attr('transform', 'rotate(-90)');
  1051 + this.sun = this.plotWrapper.append("svg:image").attr('xlink:href', this.options.sun.img).attr('width', '32px').attr('height', '32px');
  1052 + this.sun.append('svg:title').text("Sun");
  1053 + $(this.svg.node()).hide();
  1054 + this.resize();
  1055 + }
1071 1056
1072 - initOrbiter(slug, config, data) {  
1073 - const thisOrbit = this;  
1074 - let orbit_ellipse;  
1075 - let orbiter;  
1076 - let orbit_line;  
1077 - let orbit_section;  
1078 - this.data[slug] = data;  
1079 - this.orbiters[slug] = config;  
1080 - if (data.length) {  
1081 - console.info(`Initializing orbit of ${config.name}…`);  
1082 - } else {  
1083 - console.warn(`No orbit data for ${config.name}…`);  
1084 - return;  
1085 - }  
1086 - if (slug in this.orbitersElements) {  
1087 - throw new Error(`Second init of ${slug}`);  
1088 - }  
1089 - orbit_ellipse = this.plotWrapper.append("svg:ellipse").classed('orbit orbit_ellipse', true);  
1090 - orbiter = this.plotWrapper.append("svg:image").attr('xlink:href', config['img']).attr('width', '32px').attr('height', '32px');  
1091 - orbiter.append('svg:title').text(config.name);  
1092 - orbit_line = d3.line().x(d => thisOrbit.xScale(d.y)).y(d => thisOrbit.yScale(d.x));  
1093 - orbit_section = this.plotWrapper.append('path').datum(data).classed('orbit orbit_section', true);  
1094 - this.orbitersElements[slug] = {  
1095 - orbiter,  
1096 - orbit_ellipse,  
1097 - orbit_section,  
1098 - orbit_line  
1099 - };  
1100 - this.orbitersExtrema[slug] = d3.max(data, d => Math.max(Math.abs(d.x), Math.abs(d.y)));  
1101 - $(this.svg.node()).show();  
1102 - if (config.active) {  
1103 - this.enableTarget(slug);  
1104 - } else {  
1105 - this.disableTarget(slug);  
1106 - }  
1107 - return this;  
1108 - } 1057 + initOrbiter(slug, config, data) {
  1058 + const thisOrbit = this;
  1059 + let orbit_ellipse;
  1060 + let orbiter;
  1061 + let orbit_line;
  1062 + let orbit_section;
  1063 + this.data[slug] = data;
  1064 + this.orbiters[slug] = config;
  1065 + if (data.length) {
  1066 + console.info(`Initializing orbit of ${config.name}…`);
  1067 + } else {
  1068 + console.warn(`No orbit data for ${config.name}…`);
  1069 + return;
  1070 + }
  1071 + if (slug in this.orbitersElements) {
  1072 + throw new Error(`Second init of ${slug}.`);
  1073 + }
  1074 + orbit_ellipse = this.plotWrapper.append("svg:ellipse").classed('orbit orbit_ellipse', true);
  1075 + orbiter = this.plotWrapper.append("svg:image").attr('xlink:href', config['img']).attr('width', '32px').attr('height', '32px');
  1076 + orbiter.append('svg:title').text(config.name);
  1077 + orbit_line = d3.line().x(d => thisOrbit.xScale(d.y)).y(d => thisOrbit.yScale(d.x));
  1078 + orbit_section = this.plotWrapper.append('path').datum(data).classed('orbit orbit_section', true);
  1079 + this.orbitersElements[slug] = {
  1080 + orbiter,
  1081 + orbit_ellipse,
  1082 + orbit_section,
  1083 + orbit_line
  1084 + };
  1085 + this.orbitersExtrema[slug] = d3.max(data, d => Math.max(Math.abs(d.x), Math.abs(d.y)));
  1086 + $(this.svg.node()).show();
  1087 + if (config.active) {
  1088 + this.enableTarget(slug);
  1089 + } else {
  1090 + this.disableTarget(slug);
  1091 + }
  1092 + return this;
  1093 + }
1109 1094
1110 - enableTarget(slug) {  
1111 - this.orbiters[slug].enabled = true;  
1112 - return this.showOrbiter(slug);  
1113 - } 1095 + enableTarget(slug) {
  1096 + this.orbiters[slug].enabled = true;
  1097 + return this.showOrbiter(slug);
  1098 + }
1114 1099
1115 - disableTarget(slug) {  
1116 - this.orbiters[slug].enabled = false;  
1117 - return this.hideOrbiter(slug);  
1118 - } 1100 + disableTarget(slug) {
  1101 + this.orbiters[slug].enabled = false;
  1102 + return this.hideOrbiter(slug);
  1103 + }
1119 1104
1120 - showOrbiter(slug) {  
1121 - if (!this.data[slug].length) {  
1122 - return;  
1123 - }  
1124 - if (!this.orbiters[slug].enabled) {  
1125 - return;  
1126 - }  
1127 - this.orbiters[slug].hidden = false;  
1128 - this.orbitersElements[slug].orbiter.style("display", null);  
1129 - this.orbitersElements[slug].orbit_ellipse.style("display", null);  
1130 - this.orbitersElements[slug].orbit_section.style("display", null);  
1131 - return this.resize(true); 1105 + showOrbiter(slug) {
  1106 + if (!this.data[slug].length) {
  1107 + return;
1132 } 1108 }
1133 -  
1134 - hideOrbiter(slug) {  
1135 - if (!this.data[slug].length) {  
1136 - return;  
1137 - }  
1138 - this.orbiters[slug].hidden = true;  
1139 - this.orbitersElements[slug].orbiter.style("display", "none");  
1140 - this.orbitersElements[slug].orbit_ellipse.style("display", "none");  
1141 - this.orbitersElements[slug].orbit_section.style("display", "none");  
1142 - return this.resize(true); 1109 + if (!this.orbiters[slug].enabled) {
  1110 + return;
1143 } 1111 }
  1112 + this.orbiters[slug].hidden = false;
  1113 + this.orbitersElements[slug].orbiter.style("display", null);
  1114 + this.orbitersElements[slug].orbit_ellipse.style("display", null);
  1115 + this.orbitersElements[slug].orbit_section.style("display", null);
  1116 + return this.resize(true);
  1117 + }
1144 1118
1145 - clear() {  
1146 - return $(this.svg.node()).remove(); 1119 + hideOrbiter(slug) {
  1120 + if (!this.data[slug].length) {
  1121 + return;
1147 } 1122 }
  1123 + this.orbiters[slug].hidden = true;
  1124 + this.orbitersElements[slug].orbiter.style("display", "none");
  1125 + this.orbitersElements[slug].orbit_ellipse.style("display", "none");
  1126 + this.orbitersElements[slug].orbit_section.style("display", "none");
  1127 + return this.resize(true);
  1128 + }
1148 1129
1149 - resize(animate, extremum) {  
1150 - let width;  
1151 - let height;  
1152 - let s;  
1153 - let o;  
1154 - let slug;  
1155 - let ref$;  
1156 - let config;  
1157 - let t;  
1158 - let t1;  
1159 - animate == null && (animate = false);  
1160 - extremum == null && (extremum = null);  
1161 - width = Math.ceil($(this.container).width() - this.margin.left - this.margin.right);  
1162 - height = Math.ceil(1.0 * width);  
1163 - console.debug(`Resizing orbits : ${width} × ${height}…`);  
1164 - if (extremum === null) {  
1165 - extremum = 1.1 * d3.max((function () {  
1166 - let ref$;  
1167 - const results$ = [];  
1168 - for (s in ref$ = this.orbiters) {  
1169 - o = ref$[s];  
1170 - if (!o.hidden) {  
1171 - results$.push(this.orbitersExtrema[s]);  
1172 - } 1130 + clear() {
  1131 + return $(this.svg.node()).remove();
  1132 + }
  1133 +
  1134 + resize(animate, extremum) {
  1135 + let s;
  1136 + let o;
  1137 + let slug;
  1138 + let ref$;
  1139 + let config;
  1140 + let t;
  1141 + let t1;
  1142 + animate == null && (animate = false);
  1143 + extremum == null && (extremum = null);
  1144 + const width = Math.ceil($(this.container).width() - this.margin.left - this.margin.right);
  1145 + const height = width; // it's a square
  1146 + console.debug(`Resizing orbits : ${width} × ${height}…`);
  1147 + if (extremum === null) {
  1148 + extremum = 1.1 * d3.max((function () {
  1149 + let ref$;
  1150 + const results$ = [];
  1151 + for (s in ref$ = this.orbiters) {
  1152 + o = ref$[s];
  1153 + if (!o.hidden) {
  1154 + results$.push(this.orbitersExtrema[s]);
1173 } 1155 }
1174 - return results$;  
1175 - }.call(this)));  
1176 - }  
1177 - this.xScale = d3.scaleLinear().domain([-1 * extremum, extremum]);  
1178 - this.yScale = d3.scaleLinear().domain([extremum, -1 * extremum]);  
1179 - this.xScale.range([0, width]);  
1180 - this.yScale.range([height, 0]);  
1181 - this.svg.attr('width', width + this.margin.right + this.margin.left).attr('height', height + this.margin.top + this.margin.bottom);  
1182 - this.sun.attr("x", width / 2.0 - 16).attr("y", height / 2.0 - 16);  
1183 - for (slug in ref$ = this.orbiters) {  
1184 - config = ref$[slug];  
1185 - this.resizeOrbiter(slug, config, width, height, animate);  
1186 - }  
1187 - this.xAxis.scale(this.xScale);  
1188 - this.yAxis.scale(this.yScale);  
1189 - this.svg.select('.x.axis').attr('transform', `translate(0,${height})`);  
1190 - if (animate) {  
1191 - t = this.svg.transition().duration(750);  
1192 - t1 = this.svg.transition().duration(4750);  
1193 - this.svg.select('.x.axis').transition(t).call(this.xAxis);  
1194 - this.svg.select('.y.axis').transition(t).call(this.yAxis);  
1195 - } else {  
1196 - this.svg.select('.x.axis').call(this.xAxis);  
1197 - this.svg.select('.y.axis').call(this.yAxis);  
1198 - }  
1199 - this.xAxisTitle.attr("x", width / 2).attr("y", 37);  
1200 - this.yAxisTitle.attr("x", -1 * height / 2).attr("y", -30);  
1201 - return this; 1156 + }
  1157 + return results$;
  1158 + }.call(this)));
  1159 + }
  1160 + this.xScale = d3.scaleLinear().domain([-1 * extremum, extremum]);
  1161 + this.yScale = d3.scaleLinear().domain([extremum, -1 * extremum]);
  1162 + this.xScale.range([0, width]);
  1163 + this.yScale.range([height, 0]);
  1164 + this.svg.attr('width', width + this.margin.right + this.margin.left).attr('height', height + this.margin.top + this.margin.bottom);
  1165 + this.sun.attr("x", width / 2.0 - 16).attr("y", height / 2.0 - 16);
  1166 + for (slug in ref$ = this.orbiters) {
  1167 + config = ref$[slug];
  1168 + this.resizeOrbiter(slug, config, width, height, animate);
  1169 + }
  1170 + this.xAxis.scale(this.xScale);
  1171 + this.yAxis.scale(this.yScale);
  1172 + this.svg.select('.x.axis').attr('transform', `translate(0,${height})`);
  1173 + if (animate) {
  1174 + t = this.svg.transition().duration(750);
  1175 + // t1 = this.svg.transition().duration(4750);
  1176 + this.svg.select('.x.axis').transition(t).call(this.xAxis);
  1177 + this.svg.select('.y.axis').transition(t).call(this.yAxis);
  1178 + } else {
  1179 + this.svg.select('.x.axis').call(this.xAxis);
  1180 + this.svg.select('.y.axis').call(this.yAxis);
1202 } 1181 }
  1182 + this.xAxisTitle.attr("x", width / 2).attr("y", 37);
  1183 + this.yAxisTitle.attr("x", -1 * height / 2).attr("y", -30);
  1184 + return this;
  1185 + }
1203 1186
1204 - resizeOrbiter(slug, config, width, height, animate) {  
1205 - let data;  
1206 - let tt;  
1207 - let el;  
1208 - let orbit_section;  
1209 - let t;  
1210 - let a;  
1211 - let b;  
1212 - let c;  
1213 - let cx;  
1214 - let cy;  
1215 - let orbit_ellipse;  
1216 - animate == null && (animate = false);  
1217 - data = this.data[slug];  
1218 - if (!data.length) {  
1219 - return;  
1220 - }  
1221 - console.debug(`Resizing orbit of ${slug}…`);  
1222 - tt = this.svg.transition().duration(750);  
1223 - el = this.orbitersElements[slug];  
1224 - orbit_section = el['orbit_section'];  
1225 - if (animate) {  
1226 - t = this.svg.transition().duration(750);  
1227 - orbit_section = orbit_section.transition(tt);  
1228 - }  
1229 - orbit_section.attr('d', el['orbit_line']);  
1230 - a = config['orbit']['a'];  
1231 - b = config['orbit']['b'];  
1232 - c = Math.sqrt(a * a - b * b);  
1233 - cx = width / 2 - c;  
1234 - cy = height / 2;  
1235 - orbit_ellipse = el['orbit_ellipse'];  
1236 - if (animate) {  
1237 - t = this.svg.transition().duration(750);  
1238 - orbit_ellipse = orbit_ellipse.transition(t);  
1239 - }  
1240 - orbit_ellipse.attr('cx', cx).attr('cy', cy).attr('rx', this.xScale(a) - this.xScale(0)).attr('ry', this.yScale(b) - this.yScale(0));  
1241 - this.repositionOrbiter(slug, null, true);  
1242 - return this;  
1243 - } 1187 + resizeOrbiter(slug, config, width, height, animate) {
  1188 + let data;
  1189 + let tt;
  1190 + let el;
  1191 + let orbit_section;
  1192 + let t;
  1193 + let a;
  1194 + let b;
  1195 + let c;
  1196 + let cx;
  1197 + let cy;
  1198 + let orbit_ellipse;
  1199 + animate == null && (animate = false);
  1200 + data = this.data[slug];
  1201 + if (!data.length) {
  1202 + return;
  1203 + }
  1204 + console.debug(`Resizing orbit of ${slug}…`);
  1205 + tt = this.svg.transition().duration(750);
  1206 + el = this.orbitersElements[slug];
  1207 + orbit_section = el['orbit_section'];
  1208 + if (animate) {
  1209 + t = this.svg.transition().duration(750);
  1210 + orbit_section = orbit_section.transition(tt);
  1211 + }
  1212 + orbit_section.attr('d', el['orbit_line']);
  1213 + a = config['orbit']['a'];
  1214 + b = config['orbit']['b'];
  1215 + c = Math.sqrt(a * a - b * b);
  1216 + cx = width / 2 - c;
  1217 + cy = height / 2;
  1218 + orbit_ellipse = el['orbit_ellipse'];
  1219 + if (animate) {
  1220 + t = this.svg.transition().duration(750);
  1221 + orbit_ellipse = orbit_ellipse.transition(t);
  1222 + }
  1223 + orbit_ellipse.attr('cx', cx).attr('cy', cy).attr('rx', this.xScale(a) - this.xScale(0)).attr('ry', this.yScale(b) - this.yScale(0));
  1224 + this.repositionOrbiter(slug, null, true);
  1225 + return this;
  1226 + }
1244 1227
1245 - zoomToTarget(slug) {  
1246 - return this.resize(true, 1.1 * this.orbitersExtrema[slug]);  
1247 - } 1228 + zoomToTarget(slug) {
  1229 + return this.resize(true, 1.1 * this.orbitersExtrema[slug]);
  1230 + }
1248 1231
1249 - repositionOrbiter(slug, datum, animate) {  
1250 - let data;  
1251 - let el;  
1252 - let t;  
1253 - animate == null && (animate = false); 1232 + repositionOrbiter(slug, datum, animate) {
  1233 + let data;
  1234 + let el;
  1235 + let t;
  1236 + animate == null && (animate = false);
  1237 + data = this.data[slug];
  1238 + if (!data.length) {
  1239 + return;
  1240 + }
  1241 + datum == null && (datum = this.lastOrbiterData[slug]);
  1242 + datum == null && (datum = data[data.length - 1]);
  1243 + this.lastOrbiterData[slug] = datum;
  1244 + el = this.orbitersElements[slug]['orbiter'];
  1245 + if (animate) {
  1246 + t = this.svg.transition().duration(750);
  1247 + el = el.transition(t);
  1248 + }
  1249 + el.attr('x', this.xScale(datum.y) - 16);
  1250 + el.attr('y', this.yScale(datum.x) - 16);
  1251 + return this;
  1252 + }
  1253 +
  1254 + moveToDate(t) {
  1255 + let slug;
  1256 + let ref$;
  1257 + let el;
  1258 + let data;
  1259 + let i;
  1260 + let d0;
  1261 + let d1;
  1262 + let d;
  1263 + if (!t) {
  1264 + console.warn("Trying to move to an undefined date !");
  1265 + }
  1266 + for (slug in ref$ = this.orbitersElements) {
  1267 + el = ref$[slug];
1254 data = this.data[slug]; 1268 data = this.data[slug];
1255 - if (!data.length) {  
1256 - return;  
1257 - }  
1258 - datum == null && (datum = this.lastOrbiterData[slug]);  
1259 - datum == null && (datum = data[data.length - 1]);  
1260 - this.lastOrbiterData[slug] = datum;  
1261 - el = this.orbitersElements[slug]['orbiter'];  
1262 - if (animate) {  
1263 - t = this.svg.transition().duration(750);  
1264 - el = el.transition(t); 1269 + i = this.bisectDate(data, t, 1);
  1270 + d0 = data[i - 1];
  1271 + d1 = data[i];
  1272 + if (!(d1 && d0)) {
  1273 + continue;
1265 } 1274 }
1266 - el.attr('x', this.xScale(datum.y) - 16);  
1267 - el.attr('y', this.yScale(datum.x) - 16);  
1268 - return this; 1275 + d = t - d0.t > d1.t - t ? d1 : d0;
  1276 + this.repositionOrbiter(slug, d);
1269 } 1277 }
  1278 + return this;
  1279 + }
1270 1280
1271 - moveToDate(t) {  
1272 - let slug;  
1273 - let ref$;  
1274 - let el;  
1275 - let data;  
1276 - let i;  
1277 - let d0;  
1278 - let d1;  
1279 - let d;  
1280 - if (!t) {  
1281 - console.warn("Trying to move to an undefined date !");  
1282 - }  
1283 - for (slug in ref$ = this.orbitersElements) {  
1284 - el = ref$[slug];  
1285 - data = this.data[slug];  
1286 - i = this.bisectDate(data, t, 1);  
1287 - d0 = data[i - 1];  
1288 - d1 = data[i];  
1289 - if (!(d1 && d0)) {  
1290 - continue;  
1291 - }  
1292 - d = t - d0.t > d1.t - t ? d1 : d0;  
1293 - this.repositionOrbiter(slug, d); 1281 + resizeDomain(started_at, stopped_at) {
  1282 + let slug;
  1283 + let ref$;
  1284 + let config;
  1285 + let el;
  1286 + let data;
  1287 + const results$ = [];
  1288 + for (slug in ref$ = this.orbiters) {
  1289 + config = ref$[slug];
  1290 + el = this.orbitersElements[slug];
  1291 + data = this.data[slug].filter(onlyDataInRange);
  1292 + if (!data.length) {
  1293 + this.hideOrbiter(slug);
  1294 + continue;
1294 } 1295 }
1295 - return this; 1296 + el['orbit_section'].datum(data);
  1297 + el['orbit_section'].attr('d', el['orbit_line']);
  1298 + results$.push(this.showOrbiter(slug));
1296 } 1299 }
  1300 + return results$;
1297 1301
1298 - resizeDomain(started_at, stopped_at) {  
1299 - let slug;  
1300 - let ref$;  
1301 - let config;  
1302 - let el;  
1303 - let data;  
1304 - const results$ = [];  
1305 - for (slug in ref$ = this.orbiters) {  
1306 - config = ref$[slug];  
1307 - el = this.orbitersElements[slug];  
1308 - data = this.data[slug].filter(onlyDataInRange);  
1309 - if (!data.length) {  
1310 - this.hideOrbiter(slug);  
1311 - continue;  
1312 - }  
1313 - el['orbit_section'].datum(data);  
1314 - el['orbit_section'].attr('d', el['orbit_line']);  
1315 - results$.push(this.showOrbiter(slug));  
1316 - }  
1317 - return results$;  
1318 -  
1319 - function onlyDataInRange(d) {  
1320 - return started_at <= d.t && d.t <= stopped_at;  
1321 - } 1302 + function onlyDataInRange(d) {
  1303 + return started_at <= d.t && d.t <= stopped_at;
1322 } 1304 }
  1305 + }
1323 1306
1324 - resetZoom() {  
1325 - let slug;  
1326 - let ref$;  
1327 - let config;  
1328 - let el;  
1329 - const results$ = [];  
1330 - for (slug in ref$ = this.orbiters) {  
1331 - config = ref$[slug];  
1332 - el = this.orbitersElements[slug];  
1333 - if (!this.data[slug].length) {  
1334 - this.hideOrbiter(slug);  
1335 - continue;  
1336 - }  
1337 - el['orbit_section'].datum(this.data[slug]);  
1338 - el['orbit_section'].attr('d', el['orbit_line']);  
1339 - results$.push(this.showOrbiter(slug)); 1307 + resetZoom() {
  1308 + let slug;
  1309 + let ref$;
  1310 + let config;
  1311 + let el;
  1312 + const results$ = [];
  1313 + for (slug in ref$ = this.orbiters) {
  1314 + config = ref$[slug];
  1315 + el = this.orbitersElements[slug];
  1316 + if (!this.data[slug].length) {
  1317 + this.hideOrbiter(slug);
  1318 + continue;
1340 } 1319 }
1341 - return results$; 1320 + el['orbit_section'].datum(this.data[slug]);
  1321 + el['orbit_section'].attr('d', el['orbit_line']);
  1322 + results$.push(this.showOrbiter(slug));
1342 } 1323 }
  1324 + return results$;
1343 } 1325 }
  1326 + }
  1327 +
  1328 + Orbits.prototype.bisectDate = d3.bisector(d => d.t).left;
  1329 +
  1330 + global.SpaceWeather = SpaceWeather;
  1331 + global.TimeSeries = TimeSeries;
  1332 + global.Orbits = Orbits;
1344 1333
1345 - Orbits.prototype.bisectDate = d3.bisector(d => d.t).left;  
1346 - return Orbits;  
1347 - })());  
1348 }).call(this); 1334 }).call(this);
web/static/js/swapp.ls
  1 +
  2 +
  3 +
  4 +
  5 +
  6 +
  7 +
  8 +
  9 +
  10 +
  11 +
  12 +
  13 +
  14 +
  15 +# DEPRECATED
  16 +# Use web/static/js/main.js instead.
  17 +
  18 +
  19 +
  20 +
  21 +
  22 +
  23 +
  24 +
  25 +
  26 +
  27 +
  28 +
  29 +
  30 +
  31 +
1 # Livescript transpiles to javascript, and is easier on the eyes and brain. 32 # Livescript transpiles to javascript, and is easier on the eyes and brain.
2 # Get the `lsc` binary from here : http://livescript.net 33 # Get the `lsc` binary from here : http://livescript.net
3 # It is quite close to Python, syntax-wise, and full of sugar. 34 # It is quite close to Python, syntax-wise, and full of sugar.