Blame view

web/static/js/swapp.ls 17.6 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
f1f1e797   Goutte   Rewrite ugly java...
3

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

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

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

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

ae0aa7d2   Goutte   Add an x axis lab...
16
17
18
###############################################################################

export class SpaceWeather
4cf497e0   Goutte   Make the targets ...
19
20
21
  """
  The main app, instanciated from an inline script.
  """
ae0aa7d2   Goutte   Add an x axis lab...
22

f75faf5f   Goutte   WIP
23
  (@configuration) ->
fe3132dd   Goutte   Refactor even more.
24
    console.info "Creating Space Weather app...", @configuration
f75faf5f   Goutte   WIP
25
    @sources = {}
fe3132dd   Goutte   Refactor even more.
26
27
28
29
    configs = [@configuration.sources[k] for k of @configuration.sources]
    configs.forEach((source_config) ~>
      @sources[source_config.slug] = new Source(source_config.slug, source_config.name, source_config)
    )
b7fe650c   Goutte   Misc bundle of ol...
30
31
32
33
    @parameters = {}
    @configuration['parameters'].forEach((p) ~>
        @parameters[p['id']] = p
    )
ae0aa7d2   Goutte   Add an x axis lab...
34

a4a9ef03   Goutte   Cache generated C...
35
  buildDataUrlForSource: (source_slug, started_at, stopped_at) ->
f75faf5f   Goutte   WIP
36
37
    url = @configuration['api']['data_for_interval']
    url = url.replace('<source>', source_slug)
a4a9ef03   Goutte   Cache generated C...
38
39
    url = url.replace('<started_at>', started_at)
    url = url.replace('<stopped_at>', stopped_at)
f75faf5f   Goutte   WIP
40
    url
ae0aa7d2   Goutte   Add an x axis lab...
41

f75faf5f   Goutte   WIP
42
43
44
45
46
47
  addSource: (source) ->
    @sources[source.slug] = source
    this

  showAllSources: ->
    for slug, source of @sources
4cf497e0   Goutte   Make the targets ...
48
      showSource(slug)
f75faf5f   Goutte   WIP
49
50
    this

4cf497e0   Goutte   Make the targets ...
51
52
53
  showSource: (source_slug) ->
    timeSeries.forEach((ts) ~> $(ts.svg.node()).show() if ts.source.slug == source_slug && @parameters[ts.parameter].active)
    @sources[source_slug].active = true
f75faf5f   Goutte   WIP
54
55
    this

4cf497e0   Goutte   Make the targets ...
56
57
58
  hideSource: (source_slug) ->
    timeSeries.forEach((ts) -> $(ts.svg.node()).hide() if ts.source.slug == source_slug)
    @sources[source_slug].active = false
f75faf5f   Goutte   WIP
59
60
    this

fe3132dd   Goutte   Refactor even more.
61
  resize: ->
a21f81d9   Goutte   Enable Venus and ...
62
    @orbits?.resize();
d49a163c   Goutte   Fix the resize an...
63
    timeSeries.forEach((ts) -> ts.resize())
fe3132dd   Goutte   Refactor even more.
64
65
66

  init: ->
    active_sources = [ @sources[k] for k of @sources when @sources[k].config.active ]
a21f81d9   Goutte   Enable Venus and ...
67
    @orbits = new Orbits(@configuration.orbits_container, @configuration)
a4a9ef03   Goutte   Cache generated C...
68
69
70
71
72
73
74
75
76
77
78
79
#    day = 864e5  # milliseconds
#    now = new Date()
#    started_at = new Date(+now - 365 * days)
#    stopped_at = new Date(+now + 7 * days)
#    year = new Date().getFullYear()
#    started_at = "#{year - 1}-01-01T00:00:00"
#    stopped_at = "#{year + 1}-01-01T00:00:00"
    # Set the hours to zero so that files are cached for a day at most
    started_at = moment().subtract(1, 'years').hours(0).minutes(0).seconds(0)
    stopped_at = moment().add(7, 'days').hours(0).minutes(0).seconds(0)
    started_at = started_at.format("YYYY-MM-DDTHH:mm:ss")
    stopped_at = stopped_at.format("YYYY-MM-DDTHH:mm:ss")
fe3132dd   Goutte   Refactor even more.
80
    active_sources.forEach((source) ~>
a4a9ef03   Goutte   Cache generated C...
81
      @loadData(source.slug, started_at, stopped_at).then(
fe3132dd   Goutte   Refactor even more.
82
        (data) ~>
a21f81d9   Goutte   Enable Venus and ...
83
          console.info "Loaded CSV data for #{source.slug}."
fe3132dd   Goutte   Refactor even more.
84
          @createTimeSeries(source, data)
a21f81d9   Goutte   Enable Venus and ...
85
          @orbits.initOrbiter(source.slug, source.config, data['hci'])
fe3132dd   Goutte   Refactor even more.
86
        ,
b7fe650c   Goutte   Misc bundle of ol...
87
        (data) -> console.error('Failed to load SW data.', data)
fe3132dd   Goutte   Refactor even more.
88
89
      )
    )
a975b380   Goutte   Clean up.
90
    window.addEventListener 'resize', ~> @resize()
fe3132dd   Goutte   Refactor even more.
91

f75faf5f   Goutte   WIP
92
93
94
  loadData: (source_slug, started_at, stopped_at) ->
    sw = this
    promise = new Promise((resolve, reject) ->
a4a9ef03   Goutte   Cache generated C...
95
96
      url = sw.buildDataUrlForSource(source_slug, started_at, stopped_at)
      d3.csv(url, (csv) ->
f75faf5f   Goutte   WIP
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
        timeFormat = d3.timeParse('%Y-%m-%dT%H:%M:%S%Z')
        data = {'hci': []};
        configuration['parameters'].forEach((parameter) ->
            data[parameter['id']] = []
        )
        csv.forEach((d) ->
          dtime = timeFormat(d['time'])
          configuration['parameters'].forEach((parameter) ->
            id = parameter['id']
            data[id].push({x: dtime, y: parseFloat(d[id])})
          )
          if (d['xhci'] && d['yhci'])
            data['hci'].push({t: dtime, x: parseFloat(d['xhci']), y: parseFloat(d['yhci'])});
        )
        resolve(data)
      )
    )
    promise
ae0aa7d2   Goutte   Add an x axis lab...
115

7d6dee0f   Goutte   Continue refacto ...
116
  timeSeries = []
fe3132dd   Goutte   Refactor even more.
117
  createTimeSeries: (source, data) ->
b7fe650c   Goutte   Misc bundle of ol...
118
    @configuration['parameters'].forEach((parameter) ~>
4816cef4   Goutte   Refactor some more.
119
120
121
      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)
b7fe650c   Goutte   Misc bundle of ol...
122
      timeSeries.push(new TimeSeries(id, title, source, data[id], @parameters[id].active, container))
4816cef4   Goutte   Refactor some more.
123
    )
fe3132dd   Goutte   Refactor even more.
124
    timeSeries.forEach((ts) ~>
4816cef4   Goutte   Refactor some more.
125
126
127
128
      ts.options['onMouseOver'] = ->
        timeSeries.forEach((ts2) -> ts2.showCursor())
      ts.options['onMouseOut'] = ->
        timeSeries.forEach((ts2) -> ts2.hideCursor())
fe3132dd   Goutte   Refactor even more.
129
      ts.options['onMouseMove'] = (t) ~>
4816cef4   Goutte   Refactor some more.
130
        timeSeries.forEach((ts2) -> ts2.moveCursor(t))
fe3132dd   Goutte   Refactor even more.
131
        @orbits?.moveToDate(t)
4816cef4   Goutte   Refactor some more.
132
133
    )

b7fe650c   Goutte   Misc bundle of ol...
134
135
  enableParameter: (parameter_slug) ->
    if parameter_slug not of @parameters then console.error("Unknown parameter #{parameter_slug}.")
b7fe650c   Goutte   Misc bundle of ol...
136
    @parameters[parameter_slug].active = true
4cf497e0   Goutte   Make the targets ...
137
    timeSeries.forEach((ts) ~> $(ts.svg.node()).show() if ts.parameter == parameter_slug && @sources[ts.source.slug].active)
b7fe650c   Goutte   Misc bundle of ol...
138
    this
4816cef4   Goutte   Refactor some more.
139

b7fe650c   Goutte   Misc bundle of ol...
140
141
  disableParameter: (parameter_slug) ->
    if parameter_slug not of @parameters then console.error("Unknown parameter #{parameter_slug}.")
b7fe650c   Goutte   Misc bundle of ol...
142
    @parameters[parameter_slug].active = false
4cf497e0   Goutte   Make the targets ...
143
    timeSeries.forEach((ts) -> $(ts.svg.node()).hide() if ts.parameter == parameter_slug)
b7fe650c   Goutte   Misc bundle of ol...
144
    this
ae0aa7d2   Goutte   Add an x axis lab...
145
146
147


class Source
fe3132dd   Goutte   Refactor even more.
148
  (@slug, @name, @config) ->
4cf497e0   Goutte   Make the targets ...
149
    @active = true
ae0aa7d2   Goutte   Add an x axis lab...
150
151
152
153
154
155
156




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

f1f1e797   Goutte   Rewrite ugly java...
157
158
159
160
export class TimeSeries
  # Time in x-axis
  # Data in y-axis

4cf497e0   Goutte   Make the targets ...
161
162
  (@parameter, @title, @source, @data, @active, @container, @options = {}) ->
    # parameter : slug of the parameter to observe, like magn or pdyn
f1f1e797   Goutte   Rewrite ugly java...
163
    # title : string
4cf497e0   Goutte   Make the targets ...
164
    # source : slug of the TARGET, like jupiter or tchouri
f1f1e797   Goutte   Rewrite ugly java...
165
    # data : list of {x: <datetime>, y: <float>}
b7fe650c   Goutte   Misc bundle of ol...
166
167
    if @active then console.info "Creating time series '#{@title}'..."
               else console.info "Creating inactive time series '#{@title}'..."
f1f1e797   Goutte   Rewrite ugly java...
168
169
170
    @init()

  init: ->
ae0aa7d2   Goutte   Add an x axis lab...
171
    console.info "Initializing time series '#{@title}'...", @data, @options
f1f1e797   Goutte   Rewrite ugly java...
172
173
174
175
176

    @margin = {
      top: 30,
      right: 20,
      bottom: 30,
fe3132dd   Goutte   Refactor even more.
177
      left: 80
f1f1e797   Goutte   Rewrite ugly java...
178
179
180
181
182
    }

    @xScale = d3.scaleTime().domain(d3.extent(@data, (d) -> d.x))
    @yScale = d3.scaleLinear().domain(d3.extent(@data, (d) -> d.y))

541e2936   Goutte   Synchronize the t...
183
    #console.info("Y domain #{@title}", d3.extent(@data, (d) -> d.y))
2463bd16   Goutte   Add a circle foll...
184

f1f1e797   Goutte   Rewrite ugly java...
185
    @xAxis = d3.axisBottom()
f1f1e797   Goutte   Rewrite ugly java...
186
               .tickFormat(d3.timeFormat("%Y-%m-%d"))
d49a163c   Goutte   Fix the resize an...
187
188
               .ticks(7)
#               .ticks(7, ",f")
f1f1e797   Goutte   Rewrite ugly java...
189
190
191
192
193
194
195
196
    @yAxis = d3.axisLeft()
               .ticks(10)

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

    @svg = d3.select(@container).append('svg')
4cf497e0   Goutte   Make the targets ...
197
    @svg.attr("class", "#{@parameter} #{@source.slug}")
2463bd16   Goutte   Add a circle foll...
198

f1f1e797   Goutte   Rewrite ugly java...
199
200
201
    @plotWrapper = @svg.append('g')
    @plotWrapper.attr('transform', 'translate(' + @margin.left + ',' + @margin.top + ')')

f1f1e797   Goutte   Rewrite ugly java...
202
203
204
205
    @path = @plotWrapper.append('path')
                        .datum(@data)
                        .classed('line', true)

2463bd16   Goutte   Add a circle foll...
206
207
208
209
    @mouseCanvas = @plotWrapper.append("rect")
                               .style("fill", "none")
                               .style("pointer-events", "all")
    @mouseCanvas
541e2936   Goutte   Synchronize the t...
210
211
212
        .on("mouseover", @onMouseOver)
        .on("mouseout",  @onMouseOut)
        .on("mousemove", @onMouseMove)
2463bd16   Goutte   Add a circle foll...
213

f1f1e797   Goutte   Rewrite ugly java...
214
215
216
217
218
219
220
    @plotWrapper.append('g').classed('x axis', true)
    @plotWrapper.append('g').classed('y axis', true)
    @yAxisText = @plotWrapper.append("text")
        .attr("transform", "rotate(-90)")
        .attr("dy", "1em")
        .style("text-anchor", "middle")
        .text(@title)
fe3132dd   Goutte   Refactor even more.
221
222
223
224
225
226
    @yAxisTextSource = @plotWrapper.append("text")
        .attr("transform", "rotate(-90)")
        .attr("dy", "1em")
        .style("text-anchor", "middle")
        .style("font-style", "oblique")
        .text(@source.name)
f1f1e797   Goutte   Rewrite ugly java...
227

81c9b2e8   Goutte   Add the values to...
228
229
230
231
232
233
    @focus = @plotWrapper.append('g').style("display", "none")

    @cursorCircle = @focus.append("circle")
                         .attr("class", "cursor-circle")
                         .attr("r", 3)

541e2936   Goutte   Synchronize the t...
234
    dx = 8
81c9b2e8   Goutte   Add the values to...
235
    @cursorValueShadow = @focus.append("text")
541e2936   Goutte   Synchronize the t...
236
237
                              .attr("class", "cursor-text cursor-text-shadow")
                              .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
238
                              .attr("dy", "-.3em")
541e2936   Goutte   Synchronize the t...
239

81c9b2e8   Goutte   Add the values to...
240
241
    @cursorValue = @focus.append("text")
                        .attr("class", "cursor-text cursor-value")
541e2936   Goutte   Synchronize the t...
242
                        .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
243
244
                        .attr("dy", "-.3em")

541e2936   Goutte   Synchronize the t...
245

81c9b2e8   Goutte   Add the values to...
246
    @cursorDateShadow = @focus.append("text")
541e2936   Goutte   Synchronize the t...
247
248
                              .attr("class", "cursor-text cursor-text-shadow")
                              .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
249
                              .attr("dy", "1em")
541e2936   Goutte   Synchronize the t...
250

81c9b2e8   Goutte   Add the values to...
251
252
    @cursorDate = @focus.append("text")
                        .attr("class", "cursor-text cursor-date")
541e2936   Goutte   Synchronize the t...
253
                        .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
254
255
                        .attr("dy", "1em")

f1f1e797   Goutte   Rewrite ugly java...
256
257
258
259
    @resize()

  resize: ->
    width = jQuery(@container).width() - @margin.left - @margin.right
541e2936   Goutte   Synchronize the t...
260
261
262
263
    height = GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO * width

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

b7fe650c   Goutte   Misc bundle of ol...
265
    console.log("Resizing time series #{@title} : #{width} x #{height}")
f1f1e797   Goutte   Rewrite ugly java...
266
267
268
269
270
271
272
273
274
275
276
277

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

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

    @path.attr('d', @line) # wip, do we need to put this in resize ?

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

2463bd16   Goutte   Add a circle foll...
278
    #if width < 600 then @xAxis.ticks(3) else @xAxis.ticks(7, ",f")
d49a163c   Goutte   Fix the resize an...
279
280
    @xAxis.ticks(Math.floor(width / 90.0))  # not working as expected
    @yAxis.ticks(Math.floor(height / 18.0))
f1f1e797   Goutte   Rewrite ugly java...
281
282
283
284
285
286
287
288

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

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

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

fe3132dd   Goutte   Refactor even more.
292
293
294
    @yAxisTextSource.attr("y", 0 - @margin.left)
                    .attr("x", 0 - (height / 2))

2463bd16   Goutte   Add a circle foll...
295
296
297
    @mouseCanvas.attr("width", width)
                .attr("height", height)

b7fe650c   Goutte   Misc bundle of ol...
298
299

    unless @active then $(@svg.node()).hide()
f1f1e797   Goutte   Rewrite ugly java...
300
301
    this

541e2936   Goutte   Synchronize the t...
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
  onMouseMove: ~>
    x = @xScale.invert(d3.mouse(@mouseCanvas.node())[0])
    if @options.onMouseMove?
      @options.onMouseMove(x)
    else
      @moveCursor(x)

  onMouseOver: ~>
    if @options.onMouseOver?
      @options.onMouseOver()
    else
      @showCursor()

  onMouseOut: ~>
    if @options.onMouseOut?
      @options.onMouseOut()
    else
      @hideCursor()

  showCursor: ->
    @focus.style("display", null)

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

  bisectDate: d3.bisector((d) -> d.x).left # /!\ complex
81c9b2e8   Goutte   Add the values to...
328
  timeFormat: d3.timeFormat("%Y-%m-%d %Hh")
2463bd16   Goutte   Add a circle foll...
329

541e2936   Goutte   Synchronize the t...
330
  moveCursor: (x0) ->
2463bd16   Goutte   Add a circle foll...
331
332
333
    i = @bisectDate(@data, x0, 1)
    d0 = @data[i - 1]
    d1 = @data[i]
541e2936   Goutte   Synchronize the t...
334
    return unless d1 and d0
2463bd16   Goutte   Add a circle foll...
335
336
337
338
    d = if x0 - d0.x > d1.x - x0 then d1 else d0
    xx = @xScale(d.x)
    yy = @yScale(d.y)

81c9b2e8   Goutte   Add the values to...
339
340
    transform = "translate(#{xx}, #{yy})"

541e2936   Goutte   Synchronize the t...
341
342
343
344
345
346
    mirrored = if @plotWidth? and xx > @plotWidth / 2 then true else false
#    console.log("xx", xx)

    dx = 8
    dx = -1 * dx if mirrored

81c9b2e8   Goutte   Add the values to...
347
348
    @cursorCircle.attr("transform", transform)
    @cursorValue.attr("transform", transform).text(d.y)
541e2936   Goutte   Synchronize the t...
349
350
                .attr('text-anchor', if mirrored then 'end' else 'start')
                .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
351
    @cursorValueShadow.attr("transform", transform).text(d.y)
541e2936   Goutte   Synchronize the t...
352
353
                      .attr('text-anchor', if mirrored then 'end' else 'start')
                      .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
354
    @cursorDate.attr("transform", transform).text(@timeFormat(d.x))
541e2936   Goutte   Synchronize the t...
355
356
               .attr('text-anchor', if mirrored then 'end' else 'start')
               .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
357
    @cursorDateShadow.attr("transform", transform).text(@timeFormat(d.x))
541e2936   Goutte   Synchronize the t...
358
359
                     .attr('text-anchor', if mirrored then 'end' else 'start')
                     .attr("dx", dx)
2463bd16   Goutte   Add a circle foll...
360
361

    this
f1f1e797   Goutte   Rewrite ugly java...
362

ae0aa7d2   Goutte   Add an x axis lab...
363
364
365
366

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

f1f1e797   Goutte   Rewrite ugly java...
367
export class Orbits
ae0aa7d2   Goutte   Add an x axis lab...
368
369
  # View of the solar system from above, with orbits segments for selected time
  # interval, from real data.
f1f1e797   Goutte   Rewrite ugly java...
370

a21f81d9   Goutte   Enable Venus and ...
371
  (@container, @options = {}) ->
f1f1e797   Goutte   Rewrite ugly java...
372
373
374
    @init()

  init: ->
a21f81d9   Goutte   Enable Venus and ...
375
    console.log "Initializing orbits...", @options
f1f1e797   Goutte   Rewrite ugly java...
376
377
378
379

    @margin = {
      top: 30,
      right: 20,
11662eed   Goutte   Add Y axis label ...
380
      bottom: 42,
f1f1e797   Goutte   Rewrite ugly java...
381
382
      left: 60
    }
f1f1e797   Goutte   Rewrite ugly java...
383

a21f81d9   Goutte   Enable Venus and ...
384
385
386
    @data = {}  # slug => HCI array
    @orbiters = {}  # slug => config
    @extremum = 1
f1f1e797   Goutte   Rewrite ugly java...
387
388
389
390
391
392
    @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...
393
394
395
396
397
    @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...
398
399
400
401
402
403
404
405
406
    @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 ...
407
    @xAxisTitle.append('tspan').attr('dy', '3px').text('HEE').attr('font-size', '8px')
ae0aa7d2   Goutte   Add an x axis lab...
408
    @xAxisTitle.append('tspan').attr('dy', '-3px').text('   (AU)')
f1f1e797   Goutte   Rewrite ugly java...
409

11662eed   Goutte   Add Y axis label ...
410
411
412
413
414
415
416
    @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...
417
418
419
    @sun = @plotWrapper.append("svg:image")
                       .attr('xlink:href', @options.sun.img)
                       .attr('width', '32px').attr('height', '32px')
f1f1e797   Goutte   Rewrite ugly java...
420
    @sun.append('svg:title').text("Sol")
f1f1e797   Goutte   Rewrite ugly java...
421

a21f81d9   Goutte   Enable Venus and ...
422
423
#    for slug, config of @orbiters
#      @initOrbiter(slug, config)
438929a4   Goutte   Rewrite the orbit...
424

f1f1e797   Goutte   Rewrite ugly java...
425
426
    @resize()

438929a4   Goutte   Rewrite the orbit...
427
  orbitersElements: {}
a21f81d9   Goutte   Enable Venus and ...
428
429
  initOrbiter: (slug, config, data) ->
    console.log("Initializing target #{slug}'s orbit...", config, data)
438929a4   Goutte   Rewrite the orbit...
430
431
    if slug of @orbitersElements then throw new Error("Second init of #{slug}")

a21f81d9   Goutte   Enable Venus and ...
432
433
434
435
436
437
    @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...
438
439
440
441
442
443
444
445
446
447
448
449
    # 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 ...
450
                                .datum(data)
438929a4   Goutte   Rewrite the orbit...
451
452
                                .classed('orbit orbit_section', true)

a21f81d9   Goutte   Enable Venus and ...
453
454
    @orbiters[slug] = config
    @data[slug] = data
438929a4   Goutte   Rewrite the orbit...
455
456
457
458
459
460
    @orbitersElements[slug] =
      orbiter: orbiter
      orbit_ellipse: orbit_ellipse
      orbit_section: orbit_section
      orbit_line: orbit_line

a21f81d9   Goutte   Enable Venus and ...
461
462
    @resize()

438929a4   Goutte   Rewrite the orbit...
463
464
    this

f1f1e797   Goutte   Rewrite ugly java...
465
466
467
468
469
470
471
472
473
474
475
476
  resize: ->
    width = jQuery(@container).width() - @margin.left - @margin.right
    height = 1.0 * width

    console.log("Resize orbits : #{width} x #{height}")

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

438929a4   Goutte   Rewrite the orbit...
479
480
481
    for slug, config of @orbiters
      @resizeOrbiter(slug, config)

f1f1e797   Goutte   Rewrite ugly java...
482
483
484
485
486
487
488
489
490
491
    @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...
492
    @xAxisTitle.attr("x", width / 2)
11662eed   Goutte   Add Y axis label ...
493
494
495
               .attr("y", 37)
    @yAxisTitle.attr("x", -1 * height / 2)
               .attr("y", -30)
ae0aa7d2   Goutte   Add an x axis lab...
496

f1f1e797   Goutte   Rewrite ugly java...
497
498
    this

438929a4   Goutte   Rewrite the orbit...
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
  resizeOrbiter: (slug, config) ->
    width = jQuery(@container).width() - @margin.left - @margin.right
    height = 1.0 * width

    console.log("Resize orbiter #{slug}")

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

a21f81d9   Goutte   Enable Venus and ...
520
    data = @data[slug]
438929a4   Goutte   Rewrite the orbit...
521
522
523
524
525
526

    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...
527
  repositionOrbiter: (slug, datum) ->
a21f81d9   Goutte   Enable Venus and ...
528
    data = @data[slug]
ae0aa7d2   Goutte   Add an x axis lab...
529
530
531
532
    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...
533
534
535
536
537
    this

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

  moveToDate: (t) ->
a21f81d9   Goutte   Enable Venus and ...
538
    console.log("Trying to move to an undefined date") unless t
ae0aa7d2   Goutte   Add an x axis lab...
539
    for slug, el of @orbitersElements
a21f81d9   Goutte   Enable Venus and ...
540
      data = @data[slug]
ae0aa7d2   Goutte   Add an x axis lab...
541
542
543
544
545
546
      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
      @repositionOrbiter(slug, d)  # fixme
a21f81d9   Goutte   Enable Venus and ...
547
    this
ae0aa7d2   Goutte   Add an x axis lab...