Blame view

web/static/js/swapp.ls 26.5 KB
f1f1e797   Goutte   Rewrite ugly java...
1
# Livescript transpiles to javascript, and is easier on the eyes and brain.
4cf497e0   Goutte   Make the targets ...
2
# Get the `lsc` binary from here : http://livescript.net
8cb213b9   Goutte   Clean up, and pre...
3
# It is quite close to Python, syntax-wise, and full of sugar.
f1f1e797   Goutte   Rewrite ugly java...
4

4cf497e0   Goutte   Make the targets ...
5
# To transpile this file to javascript, and generate `swapp.js` :
a21f81d9   Goutte   Enable Venus and ...
6
# $ lsc --compile swapp.ls
4cf497e0   Goutte   Make the targets ...
7
8
# You can have it watch for changes and transpile automatically :
# $ lsc --watch --compile swapp.ls
a21f81d9   Goutte   Enable Venus and ...
9

4cf497e0   Goutte   Make the targets ...
10
11
12
# All the "javascript" code is in this file, except for inline scripts in
# templates, such as `home.html.jinja2`.

8cb213b9   Goutte   Clean up, and pre...
13
# Note: We use Promises and ES6 whenever relevant.
9c0c4509   Goutte   Add a loader to t...
14
15
16
17
# You also will need d3js v4 documentation : https://d3js.org/
# We're using a custom build of 4.9.1, one line changed, see d3-custom.js
# Event bubbling cannot trigger two rects unless we make an event dispatcher,
# and d3's brush is stopping propagation, as it should by default.
b60e7acd   Goutte   Rename "source" i...
18

4cf497e0   Goutte   Make the targets ...
19
20
21
###############################################################################

const GOLDEN_RATIO = 2 / (1 + Math.sqrt(5))  # Between 0 and 1 (0.618...)
f1f1e797   Goutte   Rewrite ugly java...
22

ae0aa7d2   Goutte   Add an x axis lab...
23
24
###############################################################################

b60e7acd   Goutte   Rename "source" i...
25
26
class Target
  (@slug, @name, @config) ->
2c0e1515   Goutte   Refactor loading ...
27
    @active = @config.active
b60e7acd   Goutte   Rename "source" i...
28
29
30

###############################################################################

ae0aa7d2   Goutte   Add an x axis lab...
31
export class SpaceWeather
4cf497e0   Goutte   Make the targets ...
32
33
  """
  The main app, instanciated from an inline script.
b60e7acd   Goutte   Rename "source" i...
34
  It defaults to an interval starting a year ago, and ending in seven days.
6bb225d6   Goutte   Link the time ser...
35
  (both at midnight)
4cf497e0   Goutte   Make the targets ...
36
  """
ae0aa7d2   Goutte   Add an x axis lab...
37

2c0e1515   Goutte   Refactor loading ...
38
  API_TIME_FORMAT = "YYYY-MM-DDTHH:mm:ss"
243cd8a4   Goutte   Timestamp party c...
39
  INPUT_TIME_FORMAT = "YYYY-MM-DD"
2c0e1515   Goutte   Refactor loading ...
40

f75faf5f   Goutte   WIP
41
  (@configuration) ->
2038c9fb   Goutte   Add a zoom reset ...
42
43
44
45
46
47
48
49
50
51
52
53
54
55
    console.info """
  _   _      _ _       ____
 | | | | ___| (_) ___ |  _ \\ _ __ ___  _ __   __ _
 | |_| |/ _ \\ | |/ _ \\| |_) | '__/ _ \\| '_ \\ / _` |
 |  _  |  __/ | | (_) |  __/| | | (_) | |_) | (_| |
 |_| |_|\\___|_|_|\\___/|_|_  |_|_ \\___/| .__/ \\__,_|
 | |__  _   _   / ___|  _ \\|  _ \\|  _ \\_|
 | '_ \\| | | | | |   | | | | |_) | |_) |
 | |_) | |_| | | |___| |_| |  __/|  __/
 |_.__/ \\__, |  \\____|____/|_|   |_|
        |___/

The full source of this website is available at :
https://gitlab.irap.omp.eu/CDPP/SPACEWEATHERONLINE
6bb225d6   Goutte   Link the time ser...
56
"""  # HelioPropa by CDPP (mushed 'cause we need to escape backslashes)
b60e7acd   Goutte   Rename "source" i...
57
58
59
60
    @targets = {}
    configs = [@configuration.targets[k] for k of @configuration.targets]
    configs.forEach((target_config) ~>
      @targets[target_config.slug] = new Target(target_config.slug, target_config.name, target_config)
fe3132dd   Goutte   Refactor even more.
61
    )
b7fe650c   Goutte   Misc bundle of ol...
62
63
64
65
    @parameters = {}
    @configuration['parameters'].forEach((p) ~>
        @parameters[p['id']] = p
    )
7994cf1a   Goutte   Hunt bugs.
66
67
    @orbiter = null   # our Orbiter
    @timeSeries = []  # a List of TimeSeries objects
ae0aa7d2   Goutte   Add an x axis lab...
68

b60e7acd   Goutte   Rename "source" i...
69
70
71
72
73
74
  init: ->
    """
    This is called by the inline bootstrap javascript code.
    This ain't in the constructor because it might return a Promise later on.
    (for the loader, for example)
    """
97d6cb96   Goutte   Change default ti...
75
    # Default time interval is from two weeks ago to one week ahead.
2c0e1515   Goutte   Refactor loading ...
76
    # We set the h/m/s to zero to benefit from a daily cache.
9bfa6c42   Goutte   More bug hunting.
77
    started_at = moment().subtract(1, 'year').hours(0).minutes(0).seconds(0)
97d6cb96   Goutte   Change default ti...
78
    stopped_at = moment().add(1, 'week').hours(0).minutes(0).seconds(0)
7994cf1a   Goutte   Hunt bugs.
79
    @setStartAndStop(started_at, stopped_at)
2c0e1515   Goutte   Refactor loading ...
80
    @loadAndCreatePlots(started_at, stopped_at)
243cd8a4   Goutte   Timestamp party c...
81

b60e7acd   Goutte   Rename "source" i...
82
83
84
    window.addEventListener 'resize', ~> @resize()

  buildDataUrlForTarget: (target_slug, started_at, stopped_at) ->
f75faf5f   Goutte   WIP
85
    url = @configuration['api']['data_for_interval']
b60e7acd   Goutte   Rename "source" i...
86
    url = url.replace('<target>', target_slug)
a4a9ef03   Goutte   Cache generated C...
87
88
    url = url.replace('<started_at>', started_at)
    url = url.replace('<stopped_at>', stopped_at)
f75faf5f   Goutte   WIP
89
    url
ae0aa7d2   Goutte   Add an x axis lab...
90

6b149919   Goutte   Add a Download bu...
91
92
93
94
95
96
97
98
99
  buildDownloadUrl: ->
    [started_at, stopped_at] = @getDomain()
    targets = [t for t of @targets when @targets[t].active].sort().join('-')
    url = @configuration['api']['download']
    url = url.replace('<targets>', targets)
    url = url.replace('<started_at>', started_at.format(API_TIME_FORMAT))
    url = url.replace('<stopped_at>', stopped_at.format(API_TIME_FORMAT))
    url

b60e7acd   Goutte   Rename "source" i...
100
101
  addTarget: (target) ->
    @targets[target.slug] = target
f75faf5f   Goutte   WIP
102
103
    this

2c0e1515   Goutte   Refactor loading ...
104
105
106
107
#  enableAllTargets: ->
#    for slug, target of @targets
#      enableTarget(slug)
#    this
f75faf5f   Goutte   WIP
108

6bb225d6   Goutte   Link the time ser...
109
  enableTarget: (target_slug) ->
243cd8a4   Goutte   Timestamp party c...
110
    @timeSeries.forEach((ts) ~> ts.show() if ts.target.slug == target_slug && @parameters[ts.parameter].active)
b60e7acd   Goutte   Rename "source" i...
111
    @targets[target_slug].active = true
f75faf5f   Goutte   WIP
112
113
    this

6bb225d6   Goutte   Link the time ser...
114
  disableTarget: (target_slug) ->
243cd8a4   Goutte   Timestamp party c...
115
    @timeSeries.forEach((ts) -> ts.hide() if ts.target.slug == target_slug)
b60e7acd   Goutte   Rename "source" i...
116
    @targets[target_slug].active = false
f75faf5f   Goutte   WIP
117
118
    this

fe3132dd   Goutte   Refactor even more.
119
  resize: ->
a21f81d9   Goutte   Enable Venus and ...
120
    @orbits?.resize();
243cd8a4   Goutte   Timestamp party c...
121
    @timeSeries.forEach((ts) -> ts.resize())
fe3132dd   Goutte   Refactor even more.
122

2c0e1515   Goutte   Refactor loading ...
123
124
125
  showLoader: ->
    $("\#plots_loader").show();

9c0c4509   Goutte   Add a loader to t...
126
127
128
  hideLoader: ->
    $("\#plots_loader").hide();

b60e7acd   Goutte   Rename "source" i...
129
130
131
132
133
  loadData: (target_slug, started_at, stopped_at) ->
    """
    Load the data as CSV for the specified target and interval,
    and return it in a Promise.
    """
243cd8a4   Goutte   Timestamp party c...
134

f75faf5f   Goutte   WIP
135
    sw = this
2c0e1515   Goutte   Refactor loading ...
136
    new Promise((resolve, reject) ->
b60e7acd   Goutte   Rename "source" i...
137
      url = sw.buildDataUrlForTarget(target_slug, started_at, stopped_at)
a4a9ef03   Goutte   Cache generated C...
138
      d3.csv(url, (csv) ->
9bfa6c42   Goutte   More bug hunting.
139
        console.debug("Requested CSV for #{target_slug}...", csv)
f75faf5f   Goutte   WIP
140
        timeFormat = d3.timeParse('%Y-%m-%dT%H:%M:%S%Z')
06a3a1f1   Goutte   Continue bug hunt...
141
        data = { 'hci': [] }
f75faf5f   Goutte   WIP
142
        configuration['parameters'].forEach((parameter) ->
06a3a1f1   Goutte   Continue bug hunt...
143
          data[parameter['id']] = []
f75faf5f   Goutte   WIP
144
        )
06a3a1f1   Goutte   Continue bug hunt...
145
        unless csv then reject "CSV is empty or nonexistent at URL '#{url}'."
9bfa6c42   Goutte   More bug hunting.
146
        unless csv.length then reject "CSV is empty at '#{url}'."
f75faf5f   Goutte   WIP
147
148
149
150
151
152
        csv.forEach((d) ->
          dtime = timeFormat(d['time'])
          configuration['parameters'].forEach((parameter) ->
            id = parameter['id']
            data[id].push({x: dtime, y: parseFloat(d[id])})
          )
06a3a1f1   Goutte   Continue bug hunt...
153
154
155
156
          if d['xhci'] and d['yhci']
            data['hci'].push({
              t: dtime, x: parseFloat(d['xhci']), y: parseFloat(d['yhci'])
            })
f75faf5f   Goutte   WIP
157
        )
9bfa6c42   Goutte   More bug hunting.
158
        resolve data
f75faf5f   Goutte   WIP
159
160
      )
    )
2c0e1515   Goutte   Refactor loading ...
161
162

  loadAndCreatePlots: (started_at, stopped_at) ->
243cd8a4   Goutte   Timestamp party c...
163
164
165
166
    """
    started_at: moment(.js) object
    stopped_at: moment(.js) object
    """
2c0e1515   Goutte   Refactor loading ...
167
    @showLoader()
7994cf1a   Goutte   Hunt bugs.
168
169
170
    @started_at = started_at
    @stopped_at = stopped_at
    @orbits = new Orbits(@configuration.orbits_container, @configuration)
243cd8a4   Goutte   Timestamp party c...
171
172
    started_at = started_at.format(API_TIME_FORMAT)
    stopped_at = stopped_at.format(API_TIME_FORMAT)
5e099488   Goutte   Fix that loading ...
173
174
    # active_targets = [@targets[k] for k of @targets when @targets[k].active]
    [@targets[k] for k of @targets].forEach((target) ~>
2c0e1515   Goutte   Refactor loading ...
175
      console.info "Loading CSV data of #{target.name}…"
4900d232   Goutte   Add the time inte...
176
      targetButton = $(".targets-filters .target.#{target.slug}")
2c0e1515   Goutte   Refactor loading ...
177
      targetButton.addClass('loading')
27097d87   Goutte   Change the error ...
178
      targetButton.removeClass('failed')
2c0e1515   Goutte   Refactor loading ...
179
180
      @loadData(target.slug, started_at, stopped_at).then(
        (data) ~>
9bfa6c42   Goutte   More bug hunting.
181
          console.info "Loaded CSV data of #{target.name}.", data
2c0e1515   Goutte   Refactor loading ...
182
183
184
          @createTimeSeries(target, data)
          @orbits.initOrbiter(target.slug, target.config, data['hci'])
          targetButton.removeClass('loading')
5e099488   Goutte   Fix that loading ...
185
          if target.active then @hideLoader() else @disableTarget(target.slug)
2c0e1515   Goutte   Refactor loading ...
186
        ,
9bfa6c42   Goutte   More bug hunting.
187
        (error) ~>
27097d87   Goutte   Change the error ...
188
189
          # Sometimes, AMDA's API returns garbage, so the CSV sometime fails
          # But when we re-generate it a second time, usually it's okay.
9bfa6c42   Goutte   More bug hunting.
190
          console.error("Failed loading CSV data of #{target.name}.", error)
27097d87   Goutte   Change the error ...
191
192
          alert("There was an error with #{target.name}.\nPlease retry.")
          targetButton.addClass('failed')
9bfa6c42   Goutte   More bug hunting.
193
194
          targetButton.removeClass('loading')
          @hideLoader()
2c0e1515   Goutte   Refactor loading ...
195
196
197
198
199
      )
    )

  clearPlots: ->
    @orbits.clear()
243cd8a4   Goutte   Timestamp party c...
200
    @timeSeries.forEach((ts) -> ts.clear())
2c0e1515   Goutte   Refactor loading ...
201
    @orbits = null
243cd8a4   Goutte   Timestamp party c...
202
    @timeSeries = []  # do we de-reference everything ? listeners ? #memleak?
2c0e1515   Goutte   Refactor loading ...
203
    this
ae0aa7d2   Goutte   Add an x axis lab...
204

b60e7acd   Goutte   Rename "source" i...
205
  createTimeSeries: (target, data) ->
b7fe650c   Goutte   Misc bundle of ol...
206
    @configuration['parameters'].forEach((parameter) ~>
4816cef4   Goutte   Refactor some more.
207
208
209
      container = @configuration['time_series_container']
      id = parameter['id'] ; title = parameter['title']
      if id not of data then console.error("No data for id '#{id}'.", data)
243cd8a4   Goutte   Timestamp party c...
210
      @timeSeries.push(new TimeSeries(
c3008fb2   Goutte   Clean up and refa...
211
        id, title, target, data[id], @parameters[id].active, container
6bb225d6   Goutte   Link the time ser...
212
      ))
4816cef4   Goutte   Refactor some more.
213
    )
243cd8a4   Goutte   Timestamp party c...
214
215
216
217
218
    @timeSeries.forEach((ts) ~>  # returning true may be faster
      ts.options['onMouseOver'] = ~>
        @timeSeries.forEach((ts2) -> ts2.showCursor()) ; true
      ts.options['onMouseOut'] = ~>
        @timeSeries.forEach((ts2) -> ts2.hideCursor()) ; true
fe3132dd   Goutte   Refactor even more.
219
      ts.options['onMouseMove'] = (t) ~>
243cd8a4   Goutte   Timestamp party c...
220
        @timeSeries.forEach((ts2) -> ts2.moveCursor(t))
9c0c4509   Goutte   Add a loader to t...
221
        @orbits?.moveToDate(t) ; true
c3008fb2   Goutte   Clean up and refa...
222
      ts.options['onBrushEnd'] = (sta, sto) ~>
243cd8a4   Goutte   Timestamp party c...
223
        @resizeDomain(moment(sta), moment(sto)) ; true
c3008fb2   Goutte   Clean up and refa...
224
      ts.options['onDblClick'] = ~>
9c0c4509   Goutte   Add a loader to t...
225
        @resetZoom() ; $("\#zoom_controls_help")?.remove() ; true
4816cef4   Goutte   Refactor some more.
226
    )
243cd8a4   Goutte   Timestamp party c...
227
    @timeSeries
4816cef4   Goutte   Refactor some more.
228

b7fe650c   Goutte   Misc bundle of ol...
229
230
  enableParameter: (parameter_slug) ->
    if parameter_slug not of @parameters then console.error("Unknown parameter #{parameter_slug}.")
b7fe650c   Goutte   Misc bundle of ol...
231
    @parameters[parameter_slug].active = true
243cd8a4   Goutte   Timestamp party c...
232
    @timeSeries.forEach((ts) ~> ts.show() if ts.parameter == parameter_slug && @targets[ts.target.slug].active)
b7fe650c   Goutte   Misc bundle of ol...
233
    this
4816cef4   Goutte   Refactor some more.
234

b7fe650c   Goutte   Misc bundle of ol...
235
236
  disableParameter: (parameter_slug) ->
    if parameter_slug not of @parameters then console.error("Unknown parameter #{parameter_slug}.")
b7fe650c   Goutte   Misc bundle of ol...
237
    @parameters[parameter_slug].active = false
243cd8a4   Goutte   Timestamp party c...
238
    @timeSeries.forEach((ts) -> ts.hide() if ts.parameter == parameter_slug)
b7fe650c   Goutte   Misc bundle of ol...
239
    this
ae0aa7d2   Goutte   Add an x axis lab...
240

a06a0a67   Goutte   Prepare the time ...
241
242
243
244
245
  getDomain: ->
    if @current_started_at? and @current_stopped_at?
      return [@current_started_at, @current_stopped_at]
    return [@started_at, @stopped_at]

8cb213b9   Goutte   Clean up, and pre...
246
247
  resizeDomain: (started_at, stopped_at) ->
    if stopped_at < started_at
7994cf1a   Goutte   Hunt bugs.
248
      [started_at, stopped_at] = [stopped_at, started_at]
8cb213b9   Goutte   Clean up, and pre...
249
    if started_at == stopped_at
6bb225d6   Goutte   Link the time ser...
250
      console.warn "Please provide distinct start and stop dates."
8cb213b9   Goutte   Clean up, and pre...
251
      return
1754789b   Goutte   Decorate and clea...
252
253
254
255
    max_stopped_at = started_at.clone().add(2, 'years')
    if stopped_at > max_stopped_at
      console.warn "The time interval was truncated beacuse it was bigger than two years."
      stopped_at = max_stopped_at
8cb213b9   Goutte   Clean up, and pre...
256

243cd8a4   Goutte   Timestamp party c...
257
258
259
    @setStartAndStop(started_at, stopped_at)
    formatted_started_at = started_at.format()
    formatted_stopped_at = stopped_at.format()
a06a0a67   Goutte   Prepare the time ...
260

6bb225d6   Goutte   Link the time ser...
261
262
    if (@started_at <= started_at <= @stopped_at) and
       (@started_at <= stopped_at <= @stopped_at) then
243cd8a4   Goutte   Timestamp party c...
263
      console.info "Resizing the temporal domain from #{formatted_started_at} to #{formatted_stopped_at} without fetching new data…"
6bb225d6   Goutte   Link the time ser...
264
      # We first resize the hidden time series and only afterwards we resize
a06a0a67   Goutte   Prepare the time ...
265
      # the visible ones, for a smoother transition.
243cd8a4   Goutte   Timestamp party c...
266
267
      @timeSeries.forEach((ts) -> if not ts.visible then ts.zoomIn(started_at, stopped_at))
      @timeSeries.forEach((ts) -> if     ts.visible then ts.zoomIn(started_at, stopped_at))
8cb213b9   Goutte   Clean up, and pre...
268
      @orbits.resizeDomain started_at, stopped_at
243cd8a4   Goutte   Timestamp party c...
269
270
271
272
273
274
    else
      console.info "Resizing the temporal domain from #{formatted_started_at} to #{formatted_stopped_at} and fetching new data…"
      console.warn "This might take a while… Why not see what else we're up to on http://cdpp.eu while you're waiting?"
      # fetch new data and remake the plots
      @clearPlots()
      @loadAndCreatePlots(started_at, stopped_at)
2c0e1515   Goutte   Refactor loading ...
275

243cd8a4   Goutte   Timestamp party c...
276
    this
8cb213b9   Goutte   Clean up, and pre...
277

6bb225d6   Goutte   Link the time ser...
278
  resetZoom: ->
243cd8a4   Goutte   Timestamp party c...
279
    @timeSeries.forEach((ts) -> ts.resetZoom())
667eeb24   Goutte   Resize the domain...
280
    @orbits.resetZoom()
243cd8a4   Goutte   Timestamp party c...
281
282
283
284
    @setStartAndStop(@started_at, @stopped_at)
    this

  setStartAndStop: (started_at, stopped_at) ->
7994cf1a   Goutte   Hunt bugs.
285
    console.info "Setting time interval from #{started_at} to #{stopped_at}…"
243cd8a4   Goutte   Timestamp party c...
286
287
288
289
290
    @current_started_at = started_at
    @current_stopped_at = stopped_at
    $("\#started_at").val(started_at.format(INPUT_TIME_FORMAT))
    $("\#stopped_at").val(stopped_at.format(INPUT_TIME_FORMAT))
    this
8cb213b9   Goutte   Clean up, and pre...
291
292


ae0aa7d2   Goutte   Add an x axis lab...
293

ae0aa7d2   Goutte   Add an x axis lab...
294
295
296
###############################################################################
###############################################################################

b60e7acd   Goutte   Rename "source" i...
297

f1f1e797   Goutte   Rewrite ugly java...
298
299
300
301
export class TimeSeries
  # Time in x-axis
  # Data in y-axis

c3008fb2   Goutte   Clean up and refa...
302
  (@parameter, @title, @target, data, @visible, @container, @options = {}) ->
4cf497e0   Goutte   Make the targets ...
303
    # parameter : slug of the parameter to observe, like magn or pdyn
c3008fb2   Goutte   Clean up and refa...
304
305
    # title : string, more descriptive, shown on the left of the Y axis
    # target : target object, like described in configuration
f1f1e797   Goutte   Rewrite ugly java...
306
    # data : list of {x: <datetime>, y: <float>}
6bb225d6   Goutte   Link the time ser...
307
    @setData(data)
f1f1e797   Goutte   Rewrite ugly java...
308
309
    @init()

2038c9fb   Goutte   Add a zoom reset ...
310
311
  toString: -> "#{@title} of #{@target.name}"

6bb225d6   Goutte   Link the time ser...
312
  setData: (data) ->
c3008fb2   Goutte   Clean up and refa...
313
    @data = data  # and pre-compute extents for performance when zooming
6bb225d6   Goutte   Link the time ser...
314
315
316
    @xDataExtent = d3.extent(@data, (d) -> d.x)
    @yDataExtent = d3.extent(@data, (d) -> d.y)

f1f1e797   Goutte   Rewrite ugly java...
317
  init: ->
c3008fb2   Goutte   Clean up and refa...
318
    console.info "Initializing plot of #{@}…"
f1f1e797   Goutte   Rewrite ugly java...
319
320
321
322
323

    @margin = {
      top: 30,
      right: 20,
      bottom: 30,
fe3132dd   Goutte   Refactor even more.
324
      left: 80
f1f1e797   Goutte   Rewrite ugly java...
325
326
    }

6bb225d6   Goutte   Link the time ser...
327
328
    @xScale = d3.scaleTime().domain(@xDataExtent)
    @yScale = d3.scaleLinear().domain(@yDataExtent)
f1f1e797   Goutte   Rewrite ugly java...
329

f1f1e797   Goutte   Rewrite ugly java...
330
    @xAxis = d3.axisBottom()
08569a6b   Goutte   Add a zooming bru...
331
#               .tickFormat(d3.timeFormat("%Y-%m-%d"))
d49a163c   Goutte   Fix the resize an...
332
               .ticks(7)
f1f1e797   Goutte   Rewrite ugly java...
333
334
335
336
337
338
339
340
    @yAxis = d3.axisLeft()
               .ticks(10)

    @line = d3.line()
              .x((d) ~> @xScale(d.x))
              .y((d) ~> @yScale(d.y))

    @svg = d3.select(@container).append('svg')
b60e7acd   Goutte   Rename "source" i...
341
    @svg.attr("class", "#{@parameter} #{@target.slug}")
2463bd16   Goutte   Add a circle foll...
342

f1f1e797   Goutte   Rewrite ugly java...
343
    @plotWrapper = @svg.append('g')
2038c9fb   Goutte   Add a zoom reset ...
344
345
    @plotWrapper.attr('transform',
                      'translate(' + @margin.left + ',' + @margin.top + ')')
f1f1e797   Goutte   Rewrite ugly java...
346

123313cb   Goutte   Clip the paths of...
347
348
349
350
351
352
353
354
355
356
    clipId = "ts-clip-#{@parameter}-#{@target.slug}"
    @clip = @svg.append("defs").append("svg:clipPath")
                .attr("id", clipId)
                .append("svg:rect")
                .attr("x", 0)
                .attr("y", 0)

    @pathWrapper = @plotWrapper.append('g')
    @pathWrapper.attr("clip-path", "url(\##{clipId})")
    @path = @pathWrapper.append('path')
f1f1e797   Goutte   Rewrite ugly java...
357
358
359
                        .datum(@data)
                        .classed('line', true)

08569a6b   Goutte   Add a zooming bru...
360
    @brush = @plotWrapper.append("g")
2038c9fb   Goutte   Add a zoom reset ...
361
                         .attr("class", "brush")
08569a6b   Goutte   Add a zooming bru...
362

2038c9fb   Goutte   Add a zoom reset ...
363
    # deprecated, use brush's 'overlay' child
2463bd16   Goutte   Add a circle foll...
364
365
    @mouseCanvas = @plotWrapper.append("rect")
                               .style("fill", "none")
3ee0b596   Goutte   Fix an annoying b...
366

f1f1e797   Goutte   Rewrite ugly java...
367
368
369
    @plotWrapper.append('g').classed('x axis', true)
    @plotWrapper.append('g').classed('y axis', true)
    @yAxisText = @plotWrapper.append("text")
123313cb   Goutte   Clip the paths of...
370
371
372
373
                             .attr("transform", "rotate(-90)")
                             .attr("dy", "1em")
                             .style("text-anchor", "middle")
                             .text(@title)
b60e7acd   Goutte   Rename "source" i...
374
    @yAxisTextTarget = @plotWrapper.append("text")
123313cb   Goutte   Clip the paths of...
375
376
377
378
379
                                   .attr("transform", "rotate(-90)")
                                   .attr("dy", "1em")
                                   .style("text-anchor", "middle")
                                   .style("font-style", "oblique")
                                   .text(@target.name)
f1f1e797   Goutte   Rewrite ugly java...
380

81c9b2e8   Goutte   Add the values to...
381
382
383
    @focus = @plotWrapper.append('g').style("display", "none")

    @cursorCircle = @focus.append("circle")
123313cb   Goutte   Clip the paths of...
384
385
                          .attr("class", "cursor-circle")
                          .attr("r", 3)
81c9b2e8   Goutte   Add the values to...
386

541e2936   Goutte   Synchronize the t...
387
    dx = 8
81c9b2e8   Goutte   Add the values to...
388
    @cursorValueShadow = @focus.append("text")
123313cb   Goutte   Clip the paths of...
389
390
391
                               .attr("class", "cursor-text cursor-text-shadow")
                               .attr("dx", dx)
                               .attr("dy", "-.3em")
541e2936   Goutte   Synchronize the t...
392

81c9b2e8   Goutte   Add the values to...
393
    @cursorValue = @focus.append("text")
123313cb   Goutte   Clip the paths of...
394
395
396
                         .attr("class", "cursor-text cursor-value")
                         .attr("dx", dx)
                         .attr("dy", "-.3em")
541e2936   Goutte   Synchronize the t...
397

81c9b2e8   Goutte   Add the values to...
398
    @cursorDateShadow = @focus.append("text")
541e2936   Goutte   Synchronize the t...
399
400
                              .attr("class", "cursor-text cursor-text-shadow")
                              .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
401
                              .attr("dy", "1em")
541e2936   Goutte   Synchronize the t...
402

81c9b2e8   Goutte   Add the values to...
403
404
    @cursorDate = @focus.append("text")
                        .attr("class", "cursor-text cursor-date")
541e2936   Goutte   Synchronize the t...
405
                        .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
406
407
                        .attr("dy", "1em")

f1f1e797   Goutte   Rewrite ugly java...
408
409
    @resize()

123313cb   Goutte   Clip the paths of...
410
  RATIO = GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO
f1f1e797   Goutte   Rewrite ugly java...
411
  resize: ->
123313cb   Goutte   Clip the paths of...
412
413
    width  = Math.ceil($(@container).width() - @margin.left - @margin.right)
    height = Math.ceil(RATIO * width)
541e2936   Goutte   Synchronize the t...
414
415
416

    @plotWidth = width
    @plotHeight = height
f1f1e797   Goutte   Rewrite ugly java...
417

123313cb   Goutte   Clip the paths of...
418
    console.debug("Resizing #{@}: #{width} x #{height}…")
f1f1e797   Goutte   Rewrite ugly java...
419
420
421
422
423
424
425

    @xScale.range([0, width]);
    @yScale.range([height, 0]);

    @svg.attr('width',  width + @margin.right + @margin.left)
        .attr('height', height + @margin.top + @margin.bottom)

123313cb   Goutte   Clip the paths of...
426
427
428
    @clip.attr("width", width)
         .attr("height", height)

08569a6b   Goutte   Add a zooming bru...
429
    @path.attr('d', @line)
f1f1e797   Goutte   Rewrite ugly java...
430
431
432
433

    @xAxis.scale(@xScale)
    @yAxis.scale(@yScale)

2463bd16   Goutte   Add a circle foll...
434
    #if width < 600 then @xAxis.ticks(3) else @xAxis.ticks(7, ",f")
d49a163c   Goutte   Fix the resize an...
435
436
    @xAxis.ticks(Math.floor(width / 90.0))  # not working as expected
    @yAxis.ticks(Math.floor(height / 18.0))
f1f1e797   Goutte   Rewrite ugly java...
437
438
439
440
441
442
443
444

    @svg.select('.x.axis')
        .attr('transform', 'translate(0,' + height + ')')
        .call(@xAxis)

    @svg.select('.y.axis')
        .call(@yAxis)

fe3132dd   Goutte   Refactor even more.
445
    @yAxisText.attr("y", 20 - @margin.left)
f1f1e797   Goutte   Rewrite ugly java...
446
447
              .attr("x", 0 - (height / 2))

b60e7acd   Goutte   Rename "source" i...
448
    @yAxisTextTarget.attr("y", 0 - @margin.left)
fe3132dd   Goutte   Refactor even more.
449
450
                    .attr("x", 0 - (height / 2))

2463bd16   Goutte   Add a circle foll...
451
452
453
    @mouseCanvas.attr("width", width)
                .attr("height", height)

08569a6b   Goutte   Add a zooming bru...
454
    if not @brushFunction?
c3008fb2   Goutte   Clean up and refa...
455
      console.debug "Creating the zooming brush for #{@}…"
08569a6b   Goutte   Add a zooming bru...
456
457
458
459
460
461
462
463
464
465
      # looks like d3.brush handles its own resizing on window.resize
      @brushFunction =
          d3.brushX()
            .extent([[0, 0], [width, height]])
            .handleSize(0)
            .on("end", @onBrushEnd)
#            .on("start", @onBrushStart)
#            .on("move", @onBrushMove)
      @brush.call(@brushFunction)

2038c9fb   Goutte   Add a zoom reset ...
466
467
      # We're also adding our own cursor events to the brush's overlay,
      # because it captures events and a rect cannot contain another.
08569a6b   Goutte   Add a zooming bru...
468
      @svg.select(".brush .overlay")
c3008fb2   Goutte   Clean up and refa...
469
470
471
472
          .on("mouseover.swapp", @onMouseOver)
          .on("mouseout.swapp",  @onMouseOut)
          .on("mousemove.swapp", @onMouseMove)
          .on("dblclick.swapp",  @onDoubleClick)
b7fe650c   Goutte   Misc bundle of ol...
473

6bb225d6   Goutte   Link the time ser...
474
    unless @visible then @hide()
f1f1e797   Goutte   Rewrite ugly java...
475
476
    this

2c0e1515   Goutte   Refactor loading ...
477
478
479
480
  clear: ->
    $(@svg.node()).remove()
    @visible = false

6bb225d6   Goutte   Link the time ser...
481
482
483
484
485
486
487
  show: ->
    $(@svg.node()).show()
    @visible = true

  hide: ->
    $(@svg.node()).hide()
    @visible = false
8cb213b9   Goutte   Clean up, and pre...
488

541e2936   Goutte   Synchronize the t...
489
490
  onMouseMove: ~>
    x = @xScale.invert(d3.mouse(@mouseCanvas.node())[0])
c3008fb2   Goutte   Clean up and refa...
491
    if @options.onMouseMove? then @options.onMouseMove(x) else @moveCursor(x)
541e2936   Goutte   Synchronize the t...
492
493

  onMouseOver: ~>
c3008fb2   Goutte   Clean up and refa...
494
    if @options.onMouseOver? then @options.onMouseOver() else @showCursor()
541e2936   Goutte   Synchronize the t...
495
496

  onMouseOut: ~>
c3008fb2   Goutte   Clean up and refa...
497
    if @options.onMouseOut? then @options.onMouseOut() else @hideCursor()
541e2936   Goutte   Synchronize the t...
498

2038c9fb   Goutte   Add a zoom reset ...
499
  onDoubleClick: ~>
c3008fb2   Goutte   Clean up and refa...
500
    if @options.onDblClick? then @options.onDblClick() else @resetZoom()
2038c9fb   Goutte   Add a zoom reset ...
501

08569a6b   Goutte   Add a zooming bru...
502
503
  onBrushEnd: ~>
    s = d3.event.selection
08569a6b   Goutte   Add a zooming bru...
504
505
    if s
      minmax = [s[0], s[1]].map(@xScale.invert, @xScale)
08569a6b   Goutte   Add a zooming bru...
506
      @brush.call(@brushFunction.move, null)  # some voodoo to hide the brush
c3008fb2   Goutte   Clean up and refa...
507
508
      if @options.onBrushEnd? then @options.onBrushEnd(minmax[0], minmax[1])
                              else @zoomIn(minmax[0], minmax[1])
2038c9fb   Goutte   Add a zoom reset ...
509
510
511

  zoomIn: (startDate, stopDate) ->
    console.debug "Zooming in #{@} from #{startDate} to #{stopDate}."
6bb225d6   Goutte   Link the time ser...
512
    [minDate, maxDate] = @xDataExtent
2038c9fb   Goutte   Add a zoom reset ...
513
514
515
516
517
518
    if startDate < minDate then startDate = minDate
    if stopDate > maxDate then stopDate = maxDate
    @xScale.domain([startDate, stopDate])
    @applyZoom()

  resetZoom: ->
6bb225d6   Goutte   Link the time ser...
519
520
    @xScale.domain(@xDataExtent)
    @yScale.domain(@yDataExtent)
2038c9fb   Goutte   Add a zoom reset ...
521
522
523
    @applyZoom()

  applyZoom: ->
6bb225d6   Goutte   Link the time ser...
524
    if @visible
c3008fb2   Goutte   Clean up and refa...
525
      console.debug("Applying zoom to visible #{@}…")
6bb225d6   Goutte   Link the time ser...
526
527
528
529
530
      t = @svg.transition().duration(750)
      @svg.select('.x.axis').transition(t).call(@xAxis);
      @svg.select('.y.axis').transition(t).call(@yAxis);
      @path.transition(t).attr('d', @line)
    else
c3008fb2   Goutte   Clean up and refa...
531
      console.debug("Applying zoom to hidden #{@}…")
6bb225d6   Goutte   Link the time ser...
532
533
534
      @svg.select('.x.axis').call(@xAxis);
      @svg.select('.y.axis').call(@yAxis);
      @path.attr('d', @line)
08569a6b   Goutte   Add a zooming bru...
535

541e2936   Goutte   Synchronize the t...
536
537
538
539
540
541
  showCursor: ->
    @focus.style("display", null)

  hideCursor: ->
    @focus.style("display", "none")

8cb213b9   Goutte   Clean up, and pre...
542
  bisectDate: d3.bisector((d) -> d.x).left  # /!\ complex
81c9b2e8   Goutte   Add the values to...
543
  timeFormat: d3.timeFormat("%Y-%m-%d %Hh")
2463bd16   Goutte   Add a circle foll...
544

541e2936   Goutte   Synchronize the t...
545
  moveCursor: (x0) ->
2463bd16   Goutte   Add a circle foll...
546
547
548
    i = @bisectDate(@data, x0, 1)
    d0 = @data[i - 1]
    d1 = @data[i]
541e2936   Goutte   Synchronize the t...
549
    return unless d1 and d0
2463bd16   Goutte   Add a circle foll...
550
551
552
553
    d = if x0 - d0.x > d1.x - x0 then d1 else d0
    xx = @xScale(d.x)
    yy = @yScale(d.y)

541e2936   Goutte   Synchronize the t...
554
    mirrored = if @plotWidth? and xx > @plotWidth / 2 then true else false
541e2936   Goutte   Synchronize the t...
555

8cb213b9   Goutte   Clean up, and pre...
556
    dx = 8  # horizontal delta between the dot and the text
541e2936   Goutte   Synchronize the t...
557
558
    dx = -1 * dx if mirrored

8cb213b9   Goutte   Clean up, and pre...
559
    transform = "translate(#{xx}, #{yy})"
81c9b2e8   Goutte   Add the values to...
560
561
    @cursorCircle.attr("transform", transform)
    @cursorValue.attr("transform", transform).text(d.y)
541e2936   Goutte   Synchronize the t...
562
563
                .attr('text-anchor', if mirrored then 'end' else 'start')
                .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
564
    @cursorValueShadow.attr("transform", transform).text(d.y)
541e2936   Goutte   Synchronize the t...
565
566
                      .attr('text-anchor', if mirrored then 'end' else 'start')
                      .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
567
    @cursorDate.attr("transform", transform).text(@timeFormat(d.x))
541e2936   Goutte   Synchronize the t...
568
569
               .attr('text-anchor', if mirrored then 'end' else 'start')
               .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
570
    @cursorDateShadow.attr("transform", transform).text(@timeFormat(d.x))
541e2936   Goutte   Synchronize the t...
571
572
                     .attr('text-anchor', if mirrored then 'end' else 'start')
                     .attr("dx", dx)
2463bd16   Goutte   Add a circle foll...
573
574

    this
f1f1e797   Goutte   Rewrite ugly java...
575

ae0aa7d2   Goutte   Add an x axis lab...
576
577
578
579

###############################################################################
###############################################################################

f1f1e797   Goutte   Rewrite ugly java...
580
export class Orbits
8cb213b9   Goutte   Clean up, and pre...
581
582
583
584
  """
  View of the solar system from above, with orbits segments for selected time
  interval, from real data.
  """
f1f1e797   Goutte   Rewrite ugly java...
585

a21f81d9   Goutte   Enable Venus and ...
586
  (@container, @options = {}) ->
f1f1e797   Goutte   Rewrite ugly java...
587
588
589
    @init()

  init: ->
c3008fb2   Goutte   Clean up and refa...
590
    console.log "Initializing plot of orbits…"
f1f1e797   Goutte   Rewrite ugly java...
591
592
593
594

    @margin = {
      top: 30,
      right: 20,
11662eed   Goutte   Add Y axis label ...
595
      bottom: 42,
f1f1e797   Goutte   Rewrite ugly java...
596
597
      left: 60
    }
f1f1e797   Goutte   Rewrite ugly java...
598

a21f81d9   Goutte   Enable Venus and ...
599
600
    @data = {}  # slug => HCI array
    @orbiters = {}  # slug => config
7994cf1a   Goutte   Hunt bugs.
601
    @orbitersElements = {}
a21f81d9   Goutte   Enable Venus and ...
602
    @extremum = 1
f1f1e797   Goutte   Rewrite ugly java...
603
604
605
606
607
608
    @xScale = d3.scaleLinear().domain([-1 * @extremum, @extremum])
    @yScale = d3.scaleLinear().domain([-1 * @extremum, @extremum])

    @xAxis = d3.axisBottom().ticks(10)
    @yAxis = d3.axisLeft().ticks(10)

f1f1e797   Goutte   Rewrite ugly java...
609
610
611
612
613
    @svg = d3.select(@container).append('svg')

    @plotWrapper = @svg.append('g')
    @plotWrapper.attr('transform', 'translate(' + @margin.left + ',' + @margin.top + ')')

ae0aa7d2   Goutte   Add an x axis lab...
614
615
616
617
618
619
620
621
622
    @xAxisLine = @plotWrapper.append('g').classed('x axis', true)
    @yAxisLine = @plotWrapper.append('g').classed('y axis', true)

    @xAxisTitle = @xAxisLine.append('text').attr('fill', '#000')
    @xAxisTitle.style("text-anchor", "middle")
    @xAxisTitle.append('tspan').text('X')
    # No : https://bugzilla.mozilla.org/show_bug.cgi?id=308338
    # @xAxisTitle.append('tspan').attr('baseline-shift', 'sub').text('HEE')
    # Also, don't use em as dy units
11662eed   Goutte   Add Y axis label ...
623
    @xAxisTitle.append('tspan').attr('dy', '3px').text('HEE').attr('font-size', '8px')
ae0aa7d2   Goutte   Add an x axis lab...
624
    @xAxisTitle.append('tspan').attr('dy', '-3px').text('   (AU)')
f1f1e797   Goutte   Rewrite ugly java...
625

11662eed   Goutte   Add Y axis label ...
626
627
628
629
630
631
632
    @yAxisTitle = @yAxisLine.append('text').attr('fill', '#000')
    @yAxisTitle.style("text-anchor", "middle")
    @yAxisTitle.append('tspan').text('Y')
    @yAxisTitle.append('tspan').attr('dy', '3px').text('HEE').attr('font-size', '8px')
    @yAxisTitle.append('tspan').attr('dy', '-3px').text('   (AU)')
    @yAxisTitle.attr('transform', 'rotate(-90)')

8bd715ad   Goutte   Use a pixel art i...
633
634
635
    @sun = @plotWrapper.append("svg:image")
                       .attr('xlink:href', @options.sun.img)
                       .attr('width', '32px').attr('height', '32px')
f1f1e797   Goutte   Rewrite ugly java...
636
    @sun.append('svg:title').text("Sol")
f1f1e797   Goutte   Rewrite ugly java...
637

3ee0b596   Goutte   Fix an annoying b...
638
    $(@svg.node()).hide();  # we'll show it later when there'll be data
f1f1e797   Goutte   Rewrite ugly java...
639
640
    @resize()

a21f81d9   Goutte   Enable Venus and ...
641
  initOrbiter: (slug, config, data) ->
ebe77ce4   Goutte   Clean up.
642
    console.info "Initializing orbit of #{config.name}…"
438929a4   Goutte   Rewrite the orbit...
643
644
    if slug of @orbitersElements then throw new Error("Second init of #{slug}")

a21f81d9   Goutte   Enable Venus and ...
645
646
647
648
649
650
    @extremum = Math.max(@extremum, 1.11 * d3.max(data, (d) ->
      Math.max(Math.abs(d.x), Math.abs(d.y))
    ))
    @xScale = d3.scaleLinear().domain([-1 * @extremum, @extremum])
    @yScale = d3.scaleLinear().domain([-1 * @extremum, @extremum])

438929a4   Goutte   Rewrite the orbit...
651
652
653
654
655
656
657
658
659
660
661
662
    # The order is important, as it will define the default z-order
    orbit_ellipse = @plotWrapper.append("svg:ellipse")
                                .classed('orbit orbit_ellipse', true)
    orbiter = @plotWrapper.append("svg:image")
                          .attr('xlink:href', config['img'])
                          .attr('width', '32px').attr('height', '32px')

    orbit_line = d3.line()
                   .x((d) ~> @xScale(d.x))
                   .y((d) ~> @yScale(d.y))

    orbit_section = @plotWrapper.append('path')
a21f81d9   Goutte   Enable Venus and ...
663
                                .datum(data)
438929a4   Goutte   Rewrite the orbit...
664
665
                                .classed('orbit orbit_section', true)

a21f81d9   Goutte   Enable Venus and ...
666
667
    @orbiters[slug] = config
    @data[slug] = data
438929a4   Goutte   Rewrite the orbit...
668
669
670
671
672
673
    @orbitersElements[slug] =
      orbiter: orbiter
      orbit_ellipse: orbit_ellipse
      orbit_section: orbit_section
      orbit_line: orbit_line

a21f81d9   Goutte   Enable Venus and ...
674
675
    @resize()

3ee0b596   Goutte   Fix an annoying b...
676
    $(@svg.node()).show();
8cb213b9   Goutte   Clean up, and pre...
677

438929a4   Goutte   Rewrite the orbit...
678
679
    this

2c0e1515   Goutte   Refactor loading ...
680
681
682
  clear: ->
    $(@svg.node()).remove()

f1f1e797   Goutte   Rewrite ugly java...
683
  resize: ->
abcf4c94   Goutte   Clean up.
684
685
    width = Math.ceil($(@container).width() - @margin.left - @margin.right)
    height = Math.ceil(1.0 * width)
f1f1e797   Goutte   Rewrite ugly java...
686

abcf4c94   Goutte   Clean up.
687
    console.debug("Resizing orbits : #{width} × #{height}…")
f1f1e797   Goutte   Rewrite ugly java...
688
689
690
691
692
693
694

    @xScale.range([0, width]);
    @yScale.range([height, 0]);

    @svg.attr('width',  width + @margin.right + @margin.left)
        .attr('height', height + @margin.top + @margin.bottom)

8bd715ad   Goutte   Use a pixel art i...
695
    @sun.attr("x", width / 2 - 16).attr("y", height / 2 - 16)
f1f1e797   Goutte   Rewrite ugly java...
696

438929a4   Goutte   Rewrite the orbit...
697
    for slug, config of @orbiters
abcf4c94   Goutte   Clean up.
698
      @resizeOrbiter(slug, config, width, height)
438929a4   Goutte   Rewrite the orbit...
699

f1f1e797   Goutte   Rewrite ugly java...
700
701
702
703
704
705
706
707
708
709
    @xAxis.scale(@xScale)
    @yAxis.scale(@yScale)

    @svg.select('.x.axis')
        .attr('transform', 'translate(0,' + height + ')')
        .call(@xAxis)

    @svg.select('.y.axis')
        .call(@yAxis)

ae0aa7d2   Goutte   Add an x axis lab...
710
    @xAxisTitle.attr("x", width / 2)
11662eed   Goutte   Add Y axis label ...
711
712
713
               .attr("y", 37)
    @yAxisTitle.attr("x", -1 * height / 2)
               .attr("y", -30)
ae0aa7d2   Goutte   Add an x axis lab...
714

f1f1e797   Goutte   Rewrite ugly java...
715
716
    this

abcf4c94   Goutte   Clean up.
717
718
  resizeOrbiter: (slug, config, width, height) ->
    console.debug("Resizing orbit of #{slug}…")
438929a4   Goutte   Rewrite the orbit...
719
720
721
722
723
724
725
726
727
728
729
730
731

    el = @orbitersElements[slug]
    el['orbit_section'].attr('d', el['orbit_line'])

    a = config['orbit']['a']
    b = config['orbit']['b']
    c = Math.sqrt(a*a - b*b)
    cx = (width / 2) - c
    cy = (height / 2)
    @yScale.range([0, height])
    el['orbit_ellipse'].attr('cx', cx).attr('cy', cy)
        .attr('rx', @xScale(a) - @xScale(0))
        .attr('ry', @yScale(b) - @yScale(0))
a21f81d9   Goutte   Enable Venus and ...
732
#        .attr('transform', 'rotate(66,'+(cx+c)+', '+cy+')')
438929a4   Goutte   Rewrite the orbit...
733
734
    @yScale.range([height, 0])

a21f81d9   Goutte   Enable Venus and ...
735
    data = @data[slug]
438929a4   Goutte   Rewrite the orbit...
736
737
738
739
740
741

    el['orbiter'].attr('x', @xScale(data[data.length - 1].x) - 16)
    el['orbiter'].attr('y', @yScale(data[data.length - 1].y) - 16)

    this

ae0aa7d2   Goutte   Add an x axis lab...
742
  repositionOrbiter: (slug, datum) ->
a21f81d9   Goutte   Enable Venus and ...
743
    data = @data[slug]
ae0aa7d2   Goutte   Add an x axis lab...
744
745
746
747
    datum ?= data[data.length - 1]
    el = @orbitersElements[slug]
    el['orbiter'].attr('x', @xScale(datum.x) - 16)
    el['orbiter'].attr('y', @yScale(datum.y) - 16)
ae0aa7d2   Goutte   Add an x axis lab...
748
749
750
751
752
    this

  bisectDate: d3.bisector((d) -> d.t).left

  moveToDate: (t) ->
abcf4c94   Goutte   Clean up.
753
    console.warn("Trying to move to an undefined date !") unless t
ae0aa7d2   Goutte   Add an x axis lab...
754
    for slug, el of @orbitersElements
a21f81d9   Goutte   Enable Venus and ...
755
      data = @data[slug]
ae0aa7d2   Goutte   Add an x axis lab...
756
757
758
759
760
      i = @bisectDate(data, t, 1)
      d0 = data[i - 1]
      d1 = data[i]
      continue unless d1 and d0
      d = if t - d0.t > d1.t - t then d1 else d0
abcf4c94   Goutte   Clean up.
761
      @repositionOrbiter(slug, d)
a21f81d9   Goutte   Enable Venus and ...
762
    this
ae0aa7d2   Goutte   Add an x axis lab...
763

8cb213b9   Goutte   Clean up, and pre...
764
  resizeDomain: (started_at, stopped_at) ->
667eeb24   Goutte   Resize the domain...
765
766
767
768
769
    for slug, config of @orbiters
      el = @orbitersElements[slug]
      data = @data[slug].filter (d) -> started_at <= d.t <= stopped_at
      el['orbit_section'].datum(data)
      el['orbit_section'].attr('d', el['orbit_line'])
ae0aa7d2   Goutte   Add an x axis lab...
770

667eeb24   Goutte   Resize the domain...
771
772
773
774
775
  resetZoom: ->
    for slug, config of @orbiters
      el = @orbitersElements[slug]
      el['orbit_section'].datum(@data[slug])
      el['orbit_section'].attr('d', el['orbit_line'])
abcf4c94   Goutte   Clean up.