Blame view

web/static/js/swapp.ls 25.9 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

b60e7acd   Goutte   Rename "source" i...
91
92
  addTarget: (target) ->
    @targets[target.slug] = target
f75faf5f   Goutte   WIP
93
94
    this

2c0e1515   Goutte   Refactor loading ...
95
96
97
98
#  enableAllTargets: ->
#    for slug, target of @targets
#      enableTarget(slug)
#    this
f75faf5f   Goutte   WIP
99

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

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

fe3132dd   Goutte   Refactor even more.
110
  resize: ->
a21f81d9   Goutte   Enable Venus and ...
111
    @orbits?.resize();
243cd8a4   Goutte   Timestamp party c...
112
    @timeSeries.forEach((ts) -> ts.resize())
fe3132dd   Goutte   Refactor even more.
113

2c0e1515   Goutte   Refactor loading ...
114
115
116
  showLoader: ->
    $("\#plots_loader").show();

9c0c4509   Goutte   Add a loader to t...
117
118
119
  hideLoader: ->
    $("\#plots_loader").hide();

b60e7acd   Goutte   Rename "source" i...
120
121
122
123
124
  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...
125

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

  loadAndCreatePlots: (started_at, stopped_at) ->
243cd8a4   Goutte   Timestamp party c...
154
155
156
157
    """
    started_at: moment(.js) object
    stopped_at: moment(.js) object
    """
2c0e1515   Goutte   Refactor loading ...
158
    @showLoader()
7994cf1a   Goutte   Hunt bugs.
159
160
161
    @started_at = started_at
    @stopped_at = stopped_at
    @orbits = new Orbits(@configuration.orbits_container, @configuration)
243cd8a4   Goutte   Timestamp party c...
162
163
    started_at = started_at.format(API_TIME_FORMAT)
    stopped_at = stopped_at.format(API_TIME_FORMAT)
5e099488   Goutte   Fix that loading ...
164
165
    # active_targets = [@targets[k] for k of @targets when @targets[k].active]
    [@targets[k] for k of @targets].forEach((target) ~>
2c0e1515   Goutte   Refactor loading ...
166
      console.info "Loading CSV data of #{target.name}…"
4900d232   Goutte   Add the time inte...
167
      targetButton = $(".targets-filters .target.#{target.slug}")
2c0e1515   Goutte   Refactor loading ...
168
169
170
      targetButton.addClass('loading')
      @loadData(target.slug, started_at, stopped_at).then(
        (data) ~>
9bfa6c42   Goutte   More bug hunting.
171
          console.info "Loaded CSV data of #{target.name}.", data
2c0e1515   Goutte   Refactor loading ...
172
173
174
          @createTimeSeries(target, data)
          @orbits.initOrbiter(target.slug, target.config, data['hci'])
          targetButton.removeClass('loading')
5e099488   Goutte   Fix that loading ...
175
          if target.active then @hideLoader() else @disableTarget(target.slug)
2c0e1515   Goutte   Refactor loading ...
176
        ,
9bfa6c42   Goutte   More bug hunting.
177
178
        (error) ~>
          console.error("Failed loading CSV data of #{target.name}.", error)
b40fb4b0   Goutte   Improve error han...
179
          alert("There was an error with the CSV of #{target.name}.\nPlease try another interval.\n\n#{error}")
9bfa6c42   Goutte   More bug hunting.
180
181
          targetButton.removeClass('loading')
          @hideLoader()
2c0e1515   Goutte   Refactor loading ...
182
183
184
185
186
      )
    )

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

b60e7acd   Goutte   Rename "source" i...
192
  createTimeSeries: (target, data) ->
b7fe650c   Goutte   Misc bundle of ol...
193
    @configuration['parameters'].forEach((parameter) ~>
4816cef4   Goutte   Refactor some more.
194
195
196
      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...
197
      @timeSeries.push(new TimeSeries(
c3008fb2   Goutte   Clean up and refa...
198
        id, title, target, data[id], @parameters[id].active, container
6bb225d6   Goutte   Link the time ser...
199
      ))
4816cef4   Goutte   Refactor some more.
200
    )
243cd8a4   Goutte   Timestamp party c...
201
202
203
204
205
    @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.
206
      ts.options['onMouseMove'] = (t) ~>
243cd8a4   Goutte   Timestamp party c...
207
        @timeSeries.forEach((ts2) -> ts2.moveCursor(t))
9c0c4509   Goutte   Add a loader to t...
208
        @orbits?.moveToDate(t) ; true
c3008fb2   Goutte   Clean up and refa...
209
      ts.options['onBrushEnd'] = (sta, sto) ~>
243cd8a4   Goutte   Timestamp party c...
210
        @resizeDomain(moment(sta), moment(sto)) ; true
c3008fb2   Goutte   Clean up and refa...
211
      ts.options['onDblClick'] = ~>
9c0c4509   Goutte   Add a loader to t...
212
        @resetZoom() ; $("\#zoom_controls_help")?.remove() ; true
4816cef4   Goutte   Refactor some more.
213
    )
243cd8a4   Goutte   Timestamp party c...
214
    @timeSeries
4816cef4   Goutte   Refactor some more.
215

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

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

a06a0a67   Goutte   Prepare the time ...
228
229
230
231
232
  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...
233
234
  resizeDomain: (started_at, stopped_at) ->
    if stopped_at < started_at
7994cf1a   Goutte   Hunt bugs.
235
      [started_at, stopped_at] = [stopped_at, started_at]
8cb213b9   Goutte   Clean up, and pre...
236
    if started_at == stopped_at
6bb225d6   Goutte   Link the time ser...
237
      console.warn "Please provide distinct start and stop dates."
8cb213b9   Goutte   Clean up, and pre...
238
      return
1754789b   Goutte   Decorate and clea...
239
240
241
242
    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...
243

243cd8a4   Goutte   Timestamp party c...
244
245
246
    @setStartAndStop(started_at, stopped_at)
    formatted_started_at = started_at.format()
    formatted_stopped_at = stopped_at.format()
a06a0a67   Goutte   Prepare the time ...
247

6bb225d6   Goutte   Link the time ser...
248
249
    if (@started_at <= started_at <= @stopped_at) and
       (@started_at <= stopped_at <= @stopped_at) then
243cd8a4   Goutte   Timestamp party c...
250
      console.info "Resizing the temporal domain from #{formatted_started_at} to #{formatted_stopped_at} without fetching new data…"
6bb225d6   Goutte   Link the time ser...
251
      # We first resize the hidden time series and only afterwards we resize
a06a0a67   Goutte   Prepare the time ...
252
      # the visible ones, for a smoother transition.
243cd8a4   Goutte   Timestamp party c...
253
254
      @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...
255
      @orbits.resizeDomain started_at, stopped_at
243cd8a4   Goutte   Timestamp party c...
256
257
258
259
260
261
    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 ...
262

243cd8a4   Goutte   Timestamp party c...
263
    this
8cb213b9   Goutte   Clean up, and pre...
264

6bb225d6   Goutte   Link the time ser...
265
  resetZoom: ->
243cd8a4   Goutte   Timestamp party c...
266
    @timeSeries.forEach((ts) -> ts.resetZoom())
667eeb24   Goutte   Resize the domain...
267
    @orbits.resetZoom()
243cd8a4   Goutte   Timestamp party c...
268
269
270
271
    @setStartAndStop(@started_at, @stopped_at)
    this

  setStartAndStop: (started_at, stopped_at) ->
7994cf1a   Goutte   Hunt bugs.
272
    console.info "Setting time interval from #{started_at} to #{stopped_at}…"
243cd8a4   Goutte   Timestamp party c...
273
274
275
276
277
    @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...
278
279


ae0aa7d2   Goutte   Add an x axis lab...
280

ae0aa7d2   Goutte   Add an x axis lab...
281
282
283
###############################################################################
###############################################################################

b60e7acd   Goutte   Rename "source" i...
284

f1f1e797   Goutte   Rewrite ugly java...
285
286
287
288
export class TimeSeries
  # Time in x-axis
  # Data in y-axis

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

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

6bb225d6   Goutte   Link the time ser...
299
  setData: (data) ->
c3008fb2   Goutte   Clean up and refa...
300
    @data = data  # and pre-compute extents for performance when zooming
6bb225d6   Goutte   Link the time ser...
301
302
303
    @xDataExtent = d3.extent(@data, (d) -> d.x)
    @yDataExtent = d3.extent(@data, (d) -> d.y)

f1f1e797   Goutte   Rewrite ugly java...
304
  init: ->
c3008fb2   Goutte   Clean up and refa...
305
    console.info "Initializing plot of #{@}…"
f1f1e797   Goutte   Rewrite ugly java...
306
307
308
309
310

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

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

f1f1e797   Goutte   Rewrite ugly java...
317
    @xAxis = d3.axisBottom()
08569a6b   Goutte   Add a zooming bru...
318
#               .tickFormat(d3.timeFormat("%Y-%m-%d"))
d49a163c   Goutte   Fix the resize an...
319
               .ticks(7)
f1f1e797   Goutte   Rewrite ugly java...
320
321
322
323
324
325
326
327
    @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...
328
    @svg.attr("class", "#{@parameter} #{@target.slug}")
2463bd16   Goutte   Add a circle foll...
329

f1f1e797   Goutte   Rewrite ugly java...
330
    @plotWrapper = @svg.append('g')
2038c9fb   Goutte   Add a zoom reset ...
331
332
    @plotWrapper.attr('transform',
                      'translate(' + @margin.left + ',' + @margin.top + ')')
f1f1e797   Goutte   Rewrite ugly java...
333

123313cb   Goutte   Clip the paths of...
334
335
336
337
338
339
340
341
342
343
    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...
344
345
346
                        .datum(@data)
                        .classed('line', true)

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

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

f1f1e797   Goutte   Rewrite ugly java...
354
355
356
    @plotWrapper.append('g').classed('x axis', true)
    @plotWrapper.append('g').classed('y axis', true)
    @yAxisText = @plotWrapper.append("text")
123313cb   Goutte   Clip the paths of...
357
358
359
360
                             .attr("transform", "rotate(-90)")
                             .attr("dy", "1em")
                             .style("text-anchor", "middle")
                             .text(@title)
b60e7acd   Goutte   Rename "source" i...
361
    @yAxisTextTarget = @plotWrapper.append("text")
123313cb   Goutte   Clip the paths of...
362
363
364
365
366
                                   .attr("transform", "rotate(-90)")
                                   .attr("dy", "1em")
                                   .style("text-anchor", "middle")
                                   .style("font-style", "oblique")
                                   .text(@target.name)
f1f1e797   Goutte   Rewrite ugly java...
367

81c9b2e8   Goutte   Add the values to...
368
369
370
    @focus = @plotWrapper.append('g').style("display", "none")

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

541e2936   Goutte   Synchronize the t...
374
    dx = 8
81c9b2e8   Goutte   Add the values to...
375
    @cursorValueShadow = @focus.append("text")
123313cb   Goutte   Clip the paths of...
376
377
378
                               .attr("class", "cursor-text cursor-text-shadow")
                               .attr("dx", dx)
                               .attr("dy", "-.3em")
541e2936   Goutte   Synchronize the t...
379

81c9b2e8   Goutte   Add the values to...
380
    @cursorValue = @focus.append("text")
123313cb   Goutte   Clip the paths of...
381
382
383
                         .attr("class", "cursor-text cursor-value")
                         .attr("dx", dx)
                         .attr("dy", "-.3em")
541e2936   Goutte   Synchronize the t...
384

81c9b2e8   Goutte   Add the values to...
385
    @cursorDateShadow = @focus.append("text")
541e2936   Goutte   Synchronize the t...
386
387
                              .attr("class", "cursor-text cursor-text-shadow")
                              .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
388
                              .attr("dy", "1em")
541e2936   Goutte   Synchronize the t...
389

81c9b2e8   Goutte   Add the values to...
390
391
    @cursorDate = @focus.append("text")
                        .attr("class", "cursor-text cursor-date")
541e2936   Goutte   Synchronize the t...
392
                        .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
393
394
                        .attr("dy", "1em")

f1f1e797   Goutte   Rewrite ugly java...
395
396
    @resize()

123313cb   Goutte   Clip the paths of...
397
  RATIO = GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO
f1f1e797   Goutte   Rewrite ugly java...
398
  resize: ->
123313cb   Goutte   Clip the paths of...
399
400
    width  = Math.ceil($(@container).width() - @margin.left - @margin.right)
    height = Math.ceil(RATIO * width)
541e2936   Goutte   Synchronize the t...
401
402
403

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

123313cb   Goutte   Clip the paths of...
405
    console.debug("Resizing #{@}: #{width} x #{height}…")
f1f1e797   Goutte   Rewrite ugly java...
406
407
408
409
410
411
412

    @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...
413
414
415
    @clip.attr("width", width)
         .attr("height", height)

08569a6b   Goutte   Add a zooming bru...
416
    @path.attr('d', @line)
f1f1e797   Goutte   Rewrite ugly java...
417
418
419
420

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

2463bd16   Goutte   Add a circle foll...
421
    #if width < 600 then @xAxis.ticks(3) else @xAxis.ticks(7, ",f")
d49a163c   Goutte   Fix the resize an...
422
423
    @xAxis.ticks(Math.floor(width / 90.0))  # not working as expected
    @yAxis.ticks(Math.floor(height / 18.0))
f1f1e797   Goutte   Rewrite ugly java...
424
425
426
427
428
429
430
431

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

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

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

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

2463bd16   Goutte   Add a circle foll...
438
439
440
    @mouseCanvas.attr("width", width)
                .attr("height", height)

08569a6b   Goutte   Add a zooming bru...
441
    if not @brushFunction?
c3008fb2   Goutte   Clean up and refa...
442
      console.debug "Creating the zooming brush for #{@}…"
08569a6b   Goutte   Add a zooming bru...
443
444
445
446
447
448
449
450
451
452
      # 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 ...
453
454
      # 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...
455
      @svg.select(".brush .overlay")
c3008fb2   Goutte   Clean up and refa...
456
457
458
459
          .on("mouseover.swapp", @onMouseOver)
          .on("mouseout.swapp",  @onMouseOut)
          .on("mousemove.swapp", @onMouseMove)
          .on("dblclick.swapp",  @onDoubleClick)
b7fe650c   Goutte   Misc bundle of ol...
460

6bb225d6   Goutte   Link the time ser...
461
    unless @visible then @hide()
f1f1e797   Goutte   Rewrite ugly java...
462
463
    this

2c0e1515   Goutte   Refactor loading ...
464
465
466
467
  clear: ->
    $(@svg.node()).remove()
    @visible = false

6bb225d6   Goutte   Link the time ser...
468
469
470
471
472
473
474
  show: ->
    $(@svg.node()).show()
    @visible = true

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

541e2936   Goutte   Synchronize the t...
476
477
  onMouseMove: ~>
    x = @xScale.invert(d3.mouse(@mouseCanvas.node())[0])
c3008fb2   Goutte   Clean up and refa...
478
    if @options.onMouseMove? then @options.onMouseMove(x) else @moveCursor(x)
541e2936   Goutte   Synchronize the t...
479
480

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

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

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

08569a6b   Goutte   Add a zooming bru...
489
490
  onBrushEnd: ~>
    s = d3.event.selection
08569a6b   Goutte   Add a zooming bru...
491
492
    if s
      minmax = [s[0], s[1]].map(@xScale.invert, @xScale)
08569a6b   Goutte   Add a zooming bru...
493
      @brush.call(@brushFunction.move, null)  # some voodoo to hide the brush
c3008fb2   Goutte   Clean up and refa...
494
495
      if @options.onBrushEnd? then @options.onBrushEnd(minmax[0], minmax[1])
                              else @zoomIn(minmax[0], minmax[1])
2038c9fb   Goutte   Add a zoom reset ...
496
497
498

  zoomIn: (startDate, stopDate) ->
    console.debug "Zooming in #{@} from #{startDate} to #{stopDate}."
6bb225d6   Goutte   Link the time ser...
499
    [minDate, maxDate] = @xDataExtent
2038c9fb   Goutte   Add a zoom reset ...
500
501
502
503
504
505
    if startDate < minDate then startDate = minDate
    if stopDate > maxDate then stopDate = maxDate
    @xScale.domain([startDate, stopDate])
    @applyZoom()

  resetZoom: ->
6bb225d6   Goutte   Link the time ser...
506
507
    @xScale.domain(@xDataExtent)
    @yScale.domain(@yDataExtent)
2038c9fb   Goutte   Add a zoom reset ...
508
509
510
    @applyZoom()

  applyZoom: ->
6bb225d6   Goutte   Link the time ser...
511
    if @visible
c3008fb2   Goutte   Clean up and refa...
512
      console.debug("Applying zoom to visible #{@}…")
6bb225d6   Goutte   Link the time ser...
513
514
515
516
517
      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...
518
      console.debug("Applying zoom to hidden #{@}…")
6bb225d6   Goutte   Link the time ser...
519
520
521
      @svg.select('.x.axis').call(@xAxis);
      @svg.select('.y.axis').call(@yAxis);
      @path.attr('d', @line)
08569a6b   Goutte   Add a zooming bru...
522

541e2936   Goutte   Synchronize the t...
523
524
525
526
527
528
  showCursor: ->
    @focus.style("display", null)

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

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

541e2936   Goutte   Synchronize the t...
532
  moveCursor: (x0) ->
2463bd16   Goutte   Add a circle foll...
533
534
535
    i = @bisectDate(@data, x0, 1)
    d0 = @data[i - 1]
    d1 = @data[i]
541e2936   Goutte   Synchronize the t...
536
    return unless d1 and d0
2463bd16   Goutte   Add a circle foll...
537
538
539
540
    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...
541
    mirrored = if @plotWidth? and xx > @plotWidth / 2 then true else false
541e2936   Goutte   Synchronize the t...
542

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

8cb213b9   Goutte   Clean up, and pre...
546
    transform = "translate(#{xx}, #{yy})"
81c9b2e8   Goutte   Add the values to...
547
548
    @cursorCircle.attr("transform", transform)
    @cursorValue.attr("transform", transform).text(d.y)
541e2936   Goutte   Synchronize the t...
549
550
                .attr('text-anchor', if mirrored then 'end' else 'start')
                .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
551
    @cursorValueShadow.attr("transform", transform).text(d.y)
541e2936   Goutte   Synchronize the t...
552
553
                      .attr('text-anchor', if mirrored then 'end' else 'start')
                      .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
554
    @cursorDate.attr("transform", transform).text(@timeFormat(d.x))
541e2936   Goutte   Synchronize the t...
555
556
               .attr('text-anchor', if mirrored then 'end' else 'start')
               .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
557
    @cursorDateShadow.attr("transform", transform).text(@timeFormat(d.x))
541e2936   Goutte   Synchronize the t...
558
559
                     .attr('text-anchor', if mirrored then 'end' else 'start')
                     .attr("dx", dx)
2463bd16   Goutte   Add a circle foll...
560
561

    this
f1f1e797   Goutte   Rewrite ugly java...
562

ae0aa7d2   Goutte   Add an x axis lab...
563
564
565
566

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

f1f1e797   Goutte   Rewrite ugly java...
567
export class Orbits
8cb213b9   Goutte   Clean up, and pre...
568
569
570
571
  """
  View of the solar system from above, with orbits segments for selected time
  interval, from real data.
  """
f1f1e797   Goutte   Rewrite ugly java...
572

a21f81d9   Goutte   Enable Venus and ...
573
  (@container, @options = {}) ->
f1f1e797   Goutte   Rewrite ugly java...
574
575
576
    @init()

  init: ->
c3008fb2   Goutte   Clean up and refa...
577
    console.log "Initializing plot of orbits…"
f1f1e797   Goutte   Rewrite ugly java...
578
579
580
581

    @margin = {
      top: 30,
      right: 20,
11662eed   Goutte   Add Y axis label ...
582
      bottom: 42,
f1f1e797   Goutte   Rewrite ugly java...
583
584
      left: 60
    }
f1f1e797   Goutte   Rewrite ugly java...
585

a21f81d9   Goutte   Enable Venus and ...
586
587
    @data = {}  # slug => HCI array
    @orbiters = {}  # slug => config
7994cf1a   Goutte   Hunt bugs.
588
    @orbitersElements = {}
a21f81d9   Goutte   Enable Venus and ...
589
    @extremum = 1
f1f1e797   Goutte   Rewrite ugly java...
590
591
592
593
594
595
    @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...
596
597
598
599
600
    @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...
601
602
603
604
605
606
607
608
609
    @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 ...
610
    @xAxisTitle.append('tspan').attr('dy', '3px').text('HEE').attr('font-size', '8px')
ae0aa7d2   Goutte   Add an x axis lab...
611
    @xAxisTitle.append('tspan').attr('dy', '-3px').text('   (AU)')
f1f1e797   Goutte   Rewrite ugly java...
612

11662eed   Goutte   Add Y axis label ...
613
614
615
616
617
618
619
    @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...
620
621
622
    @sun = @plotWrapper.append("svg:image")
                       .attr('xlink:href', @options.sun.img)
                       .attr('width', '32px').attr('height', '32px')
f1f1e797   Goutte   Rewrite ugly java...
623
    @sun.append('svg:title').text("Sol")
f1f1e797   Goutte   Rewrite ugly java...
624

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

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

a21f81d9   Goutte   Enable Venus and ...
632
633
634
635
636
637
    @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...
638
639
640
641
642
643
644
645
646
647
648
649
    # 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 ...
650
                                .datum(data)
438929a4   Goutte   Rewrite the orbit...
651
652
                                .classed('orbit orbit_section', true)

a21f81d9   Goutte   Enable Venus and ...
653
654
    @orbiters[slug] = config
    @data[slug] = data
438929a4   Goutte   Rewrite the orbit...
655
656
657
658
659
660
    @orbitersElements[slug] =
      orbiter: orbiter
      orbit_ellipse: orbit_ellipse
      orbit_section: orbit_section
      orbit_line: orbit_line

a21f81d9   Goutte   Enable Venus and ...
661
662
    @resize()

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

438929a4   Goutte   Rewrite the orbit...
665
666
    this

2c0e1515   Goutte   Refactor loading ...
667
668
669
  clear: ->
    $(@svg.node()).remove()

f1f1e797   Goutte   Rewrite ugly java...
670
  resize: ->
abcf4c94   Goutte   Clean up.
671
672
    width = Math.ceil($(@container).width() - @margin.left - @margin.right)
    height = Math.ceil(1.0 * width)
f1f1e797   Goutte   Rewrite ugly java...
673

abcf4c94   Goutte   Clean up.
674
    console.debug("Resizing orbits : #{width} × #{height}…")
f1f1e797   Goutte   Rewrite ugly java...
675
676
677
678
679
680
681

    @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...
682
    @sun.attr("x", width / 2 - 16).attr("y", height / 2 - 16)
f1f1e797   Goutte   Rewrite ugly java...
683

438929a4   Goutte   Rewrite the orbit...
684
    for slug, config of @orbiters
abcf4c94   Goutte   Clean up.
685
      @resizeOrbiter(slug, config, width, height)
438929a4   Goutte   Rewrite the orbit...
686

f1f1e797   Goutte   Rewrite ugly java...
687
688
689
690
691
692
693
694
695
696
    @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...
697
    @xAxisTitle.attr("x", width / 2)
11662eed   Goutte   Add Y axis label ...
698
699
700
               .attr("y", 37)
    @yAxisTitle.attr("x", -1 * height / 2)
               .attr("y", -30)
ae0aa7d2   Goutte   Add an x axis lab...
701

f1f1e797   Goutte   Rewrite ugly java...
702
703
    this

abcf4c94   Goutte   Clean up.
704
705
  resizeOrbiter: (slug, config, width, height) ->
    console.debug("Resizing orbit of #{slug}…")
438929a4   Goutte   Rewrite the orbit...
706
707
708
709
710
711
712
713
714
715
716
717
718

    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 ...
719
#        .attr('transform', 'rotate(66,'+(cx+c)+', '+cy+')')
438929a4   Goutte   Rewrite the orbit...
720
721
    @yScale.range([height, 0])

a21f81d9   Goutte   Enable Venus and ...
722
    data = @data[slug]
438929a4   Goutte   Rewrite the orbit...
723
724
725
726
727
728

    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...
729
  repositionOrbiter: (slug, datum) ->
a21f81d9   Goutte   Enable Venus and ...
730
    data = @data[slug]
ae0aa7d2   Goutte   Add an x axis lab...
731
732
733
734
    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...
735
736
737
738
739
    this

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

  moveToDate: (t) ->
abcf4c94   Goutte   Clean up.
740
    console.warn("Trying to move to an undefined date !") unless t
ae0aa7d2   Goutte   Add an x axis lab...
741
    for slug, el of @orbitersElements
a21f81d9   Goutte   Enable Venus and ...
742
      data = @data[slug]
ae0aa7d2   Goutte   Add an x axis lab...
743
744
745
746
747
      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.
748
      @repositionOrbiter(slug, d)
a21f81d9   Goutte   Enable Venus and ...
749
    this
ae0aa7d2   Goutte   Add an x axis lab...
750

8cb213b9   Goutte   Clean up, and pre...
751
  resizeDomain: (started_at, stopped_at) ->
667eeb24   Goutte   Resize the domain...
752
753
754
755
756
    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...
757

667eeb24   Goutte   Resize the domain...
758
759
760
761
762
  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.