Blame view

web/static/js/swapp.ls 18.7 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.
b60e7acd   Goutte   Rename "source" i...
14

4cf497e0   Goutte   Make the targets ...
15
16
17
###############################################################################

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

ae0aa7d2   Goutte   Add an x axis lab...
19
20
###############################################################################

b60e7acd   Goutte   Rename "source" i...
21
22
23
24
25
26
class Target
  (@slug, @name, @config) ->
    @active = true  # by default, all targets are active at first

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

ae0aa7d2   Goutte   Add an x axis lab...
27
export class SpaceWeather
4cf497e0   Goutte   Make the targets ...
28
29
  """
  The main app, instanciated from an inline script.
b60e7acd   Goutte   Rename "source" i...
30
  It defaults to an interval starting a year ago, and ending in seven days.
4cf497e0   Goutte   Make the targets ...
31
  """
ae0aa7d2   Goutte   Add an x axis lab...
32

f75faf5f   Goutte   WIP
33
  (@configuration) ->
8cb213b9   Goutte   Clean up, and pre...
34
    console.info "Creating HelioPropa app...", @configuration
b60e7acd   Goutte   Rename "source" i...
35
36
37
38
    @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.
39
    )
b7fe650c   Goutte   Misc bundle of ol...
40
41
42
43
    @parameters = {}
    @configuration['parameters'].forEach((p) ~>
        @parameters[p['id']] = p
    )
ae0aa7d2   Goutte   Add an x axis lab...
44

b60e7acd   Goutte   Rename "source" i...
45
46
47
48
49
50
51
52
53
  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)
    """
    active_targets = [ @targets[k] for k of @targets when @targets[k].config.active ]
    @orbits = new Orbits(@configuration.orbits_container, @configuration)
    # Set the h/m/s to zero so that files are cached per whole days
8cb213b9   Goutte   Clean up, and pre...
54
55
56
57
    @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")
b60e7acd   Goutte   Rename "source" i...
58
59
60
61
62
63
64
65
66
67
68
69
70
    active_targets.forEach((target) ~>
      @loadData(target.slug, started_at, stopped_at).then(
        (data) ~>
          console.info "Loaded CSV data for #{target.slug}."
          @createTimeSeries(target, data)
          @orbits.initOrbiter(target.slug, target.config, data['hci'])
        ,
        (error) -> console.error('Failed to load CSV data.', error)
      )
    )
    window.addEventListener 'resize', ~> @resize()

  buildDataUrlForTarget: (target_slug, started_at, stopped_at) ->
f75faf5f   Goutte   WIP
71
    url = @configuration['api']['data_for_interval']
b60e7acd   Goutte   Rename "source" i...
72
    url = url.replace('<target>', target_slug)
a4a9ef03   Goutte   Cache generated C...
73
74
    url = url.replace('<started_at>', started_at)
    url = url.replace('<stopped_at>', stopped_at)
f75faf5f   Goutte   WIP
75
    url
ae0aa7d2   Goutte   Add an x axis lab...
76

b60e7acd   Goutte   Rename "source" i...
77
78
  addTarget: (target) ->
    @targets[target.slug] = target
f75faf5f   Goutte   WIP
79
80
    this

b60e7acd   Goutte   Rename "source" i...
81
82
83
  showAllTargets: ->
    for slug, target of @targets
      showTarget(slug)
f75faf5f   Goutte   WIP
84
85
    this

b60e7acd   Goutte   Rename "source" i...
86
87
88
  showTarget: (target_slug) ->
    timeSeries.forEach((ts) ~> $(ts.svg.node()).show() if ts.target.slug == target_slug && @parameters[ts.parameter].active)
    @targets[target_slug].active = true
f75faf5f   Goutte   WIP
89
90
    this

b60e7acd   Goutte   Rename "source" i...
91
92
93
  hideTarget: (target_slug) ->
    timeSeries.forEach((ts) -> $(ts.svg.node()).hide() if ts.target.slug == target_slug)
    @targets[target_slug].active = false
f75faf5f   Goutte   WIP
94
95
    this

fe3132dd   Goutte   Refactor even more.
96
  resize: ->
a21f81d9   Goutte   Enable Venus and ...
97
    @orbits?.resize();
d49a163c   Goutte   Fix the resize an...
98
    timeSeries.forEach((ts) -> ts.resize())
fe3132dd   Goutte   Refactor even more.
99

b60e7acd   Goutte   Rename "source" i...
100
101
102
103
104
  loadData: (target_slug, started_at, stopped_at) ->
    """
    Load the data as CSV for the specified target and interval,
    and return it in a Promise.
    """
f75faf5f   Goutte   WIP
105
106
    sw = this
    promise = new Promise((resolve, reject) ->
b60e7acd   Goutte   Rename "source" i...
107
      url = sw.buildDataUrlForTarget(target_slug, started_at, stopped_at)
a4a9ef03   Goutte   Cache generated C...
108
      d3.csv(url, (csv) ->
f75faf5f   Goutte   WIP
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
        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...
127

8cb213b9   Goutte   Clean up, and pre...
128
  timeSeries = []  # Not sure why this ain't an instance prop. Probably should.
b60e7acd   Goutte   Rename "source" i...
129
  createTimeSeries: (target, data) ->
b7fe650c   Goutte   Misc bundle of ol...
130
    @configuration['parameters'].forEach((parameter) ~>
4816cef4   Goutte   Refactor some more.
131
132
133
      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)
b60e7acd   Goutte   Rename "source" i...
134
      timeSeries.push(new TimeSeries(id, title, target, data[id], @parameters[id].active, container))
4816cef4   Goutte   Refactor some more.
135
    )
fe3132dd   Goutte   Refactor even more.
136
    timeSeries.forEach((ts) ~>
4816cef4   Goutte   Refactor some more.
137
138
139
140
      ts.options['onMouseOver'] = ->
        timeSeries.forEach((ts2) -> ts2.showCursor())
      ts.options['onMouseOut'] = ->
        timeSeries.forEach((ts2) -> ts2.hideCursor())
fe3132dd   Goutte   Refactor even more.
141
      ts.options['onMouseMove'] = (t) ~>
4816cef4   Goutte   Refactor some more.
142
        timeSeries.forEach((ts2) -> ts2.moveCursor(t))
fe3132dd   Goutte   Refactor even more.
143
        @orbits?.moveToDate(t)
4816cef4   Goutte   Refactor some more.
144
145
    )

b7fe650c   Goutte   Misc bundle of ol...
146
147
  enableParameter: (parameter_slug) ->
    if parameter_slug not of @parameters then console.error("Unknown parameter #{parameter_slug}.")
b7fe650c   Goutte   Misc bundle of ol...
148
    @parameters[parameter_slug].active = true
b60e7acd   Goutte   Rename "source" i...
149
    timeSeries.forEach((ts) ~> $(ts.svg.node()).show() if ts.parameter == parameter_slug && @targets[ts.target.slug].active)
b7fe650c   Goutte   Misc bundle of ol...
150
    this
4816cef4   Goutte   Refactor some more.
151

b7fe650c   Goutte   Misc bundle of ol...
152
153
  disableParameter: (parameter_slug) ->
    if parameter_slug not of @parameters then console.error("Unknown parameter #{parameter_slug}.")
b7fe650c   Goutte   Misc bundle of ol...
154
    @parameters[parameter_slug].active = false
4cf497e0   Goutte   Make the targets ...
155
    timeSeries.forEach((ts) -> $(ts.svg.node()).hide() if ts.parameter == parameter_slug)
b7fe650c   Goutte   Misc bundle of ol...
156
    this
ae0aa7d2   Goutte   Add an x axis lab...
157

8cb213b9   Goutte   Clean up, and pre...
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
  resizeDomain: (started_at, stopped_at) ->
    if stopped_at < started_at
      tmp = started_at
      started_at = stopped_at
      stopped_at = started_at

    if started_at == stopped_at
      console.warn "Please provide different start and stop dates."
      return

    if (@started_at <= started_at <= @stopped_at) and (@started_at <= stopped_at <= @stopped_at)
      console.info "Resizing the temporal domain without fetching new data..."
      timeSeries.forEach((ts) -> ts.resizeDomain started_at, stopped_at)
      @orbits.resizeDomain started_at, stopped_at
      return

    # todo: fetch new data and remake the plots



ae0aa7d2   Goutte   Add an x axis lab...
178

ae0aa7d2   Goutte   Add an x axis lab...
179
180
181
###############################################################################
###############################################################################

b60e7acd   Goutte   Rename "source" i...
182

f1f1e797   Goutte   Rewrite ugly java...
183
184
185
186
export class TimeSeries
  # Time in x-axis
  # Data in y-axis

b60e7acd   Goutte   Rename "source" i...
187
  (@parameter, @title, @target, @data, @active, @container, @options = {}) ->
4cf497e0   Goutte   Make the targets ...
188
    # parameter : slug of the parameter to observe, like magn or pdyn
f1f1e797   Goutte   Rewrite ugly java...
189
    # title : string
8cb213b9   Goutte   Clean up, and pre...
190
    # target : slug of the target, like jupiter or tchouri
f1f1e797   Goutte   Rewrite ugly java...
191
    # data : list of {x: <datetime>, y: <float>}
f1f1e797   Goutte   Rewrite ugly java...
192
193
194
    @init()

  init: ->
8cb213b9   Goutte   Clean up, and pre...
195
    console.info "Initializing time series #{@title} of #{@target}..."
f1f1e797   Goutte   Rewrite ugly java...
196
197
198
199
200

    @margin = {
      top: 30,
      right: 20,
      bottom: 30,
fe3132dd   Goutte   Refactor even more.
201
      left: 80
f1f1e797   Goutte   Rewrite ugly java...
202
203
204
205
206
    }

    @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...
207
    #console.info("Y domain #{@title}", d3.extent(@data, (d) -> d.y))
2463bd16   Goutte   Add a circle foll...
208

f1f1e797   Goutte   Rewrite ugly java...
209
    @xAxis = d3.axisBottom()
f1f1e797   Goutte   Rewrite ugly java...
210
               .tickFormat(d3.timeFormat("%Y-%m-%d"))
d49a163c   Goutte   Fix the resize an...
211
               .ticks(7)
f1f1e797   Goutte   Rewrite ugly java...
212
213
214
215
216
217
218
219
    @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...
220
    @svg.attr("class", "#{@parameter} #{@target.slug}")
2463bd16   Goutte   Add a circle foll...
221

f1f1e797   Goutte   Rewrite ugly java...
222
223
224
    @plotWrapper = @svg.append('g')
    @plotWrapper.attr('transform', 'translate(' + @margin.left + ',' + @margin.top + ')')

f1f1e797   Goutte   Rewrite ugly java...
225
226
227
228
    @path = @plotWrapper.append('path')
                        .datum(@data)
                        .classed('line', true)

2463bd16   Goutte   Add a circle foll...
229
230
231
232
    @mouseCanvas = @plotWrapper.append("rect")
                               .style("fill", "none")
                               .style("pointer-events", "all")
    @mouseCanvas
541e2936   Goutte   Synchronize the t...
233
234
235
        .on("mouseover", @onMouseOver)
        .on("mouseout",  @onMouseOut)
        .on("mousemove", @onMouseMove)
2463bd16   Goutte   Add a circle foll...
236

f1f1e797   Goutte   Rewrite ugly java...
237
238
239
240
241
242
243
    @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)
b60e7acd   Goutte   Rename "source" i...
244
    @yAxisTextTarget = @plotWrapper.append("text")
fe3132dd   Goutte   Refactor even more.
245
246
247
248
        .attr("transform", "rotate(-90)")
        .attr("dy", "1em")
        .style("text-anchor", "middle")
        .style("font-style", "oblique")
b60e7acd   Goutte   Rename "source" i...
249
        .text(@target.name)
f1f1e797   Goutte   Rewrite ugly java...
250

81c9b2e8   Goutte   Add the values to...
251
252
253
254
255
256
    @focus = @plotWrapper.append('g').style("display", "none")

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

541e2936   Goutte   Synchronize the t...
257
    dx = 8
81c9b2e8   Goutte   Add the values to...
258
    @cursorValueShadow = @focus.append("text")
541e2936   Goutte   Synchronize the t...
259
260
                              .attr("class", "cursor-text cursor-text-shadow")
                              .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
261
                              .attr("dy", "-.3em")
541e2936   Goutte   Synchronize the t...
262

81c9b2e8   Goutte   Add the values to...
263
264
    @cursorValue = @focus.append("text")
                        .attr("class", "cursor-text cursor-value")
541e2936   Goutte   Synchronize the t...
265
                        .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
266
267
                        .attr("dy", "-.3em")

541e2936   Goutte   Synchronize the t...
268

81c9b2e8   Goutte   Add the values to...
269
    @cursorDateShadow = @focus.append("text")
541e2936   Goutte   Synchronize the t...
270
271
                              .attr("class", "cursor-text cursor-text-shadow")
                              .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
272
                              .attr("dy", "1em")
541e2936   Goutte   Synchronize the t...
273

81c9b2e8   Goutte   Add the values to...
274
275
    @cursorDate = @focus.append("text")
                        .attr("class", "cursor-text cursor-date")
541e2936   Goutte   Synchronize the t...
276
                        .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
277
278
                        .attr("dy", "1em")

f1f1e797   Goutte   Rewrite ugly java...
279
280
281
282
    @resize()

  resize: ->
    width = jQuery(@container).width() - @margin.left - @margin.right
541e2936   Goutte   Synchronize the t...
283
284
285
286
    height = GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO * width

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

8cb213b9   Goutte   Clean up, and pre...
288
    console.log("Resizing time series #{@title} of #{@target}: #{width} x #{height}")
f1f1e797   Goutte   Rewrite ugly java...
289
290
291
292
293
294
295
296
297
298
299
300

    @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...
301
    #if width < 600 then @xAxis.ticks(3) else @xAxis.ticks(7, ",f")
d49a163c   Goutte   Fix the resize an...
302
303
    @xAxis.ticks(Math.floor(width / 90.0))  # not working as expected
    @yAxis.ticks(Math.floor(height / 18.0))
f1f1e797   Goutte   Rewrite ugly java...
304
305
306
307
308
309
310
311

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

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

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

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

2463bd16   Goutte   Add a circle foll...
318
319
320
    @mouseCanvas.attr("width", width)
                .attr("height", height)

b7fe650c   Goutte   Misc bundle of ol...
321
322

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

8cb213b9   Goutte   Clean up, and pre...
325
326
327
  resizeDomain: (started_at, stopped_at) ->
    # fixme

541e2936   Goutte   Synchronize the t...
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
  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")

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

541e2936   Goutte   Synchronize the t...
356
  moveCursor: (x0) ->
2463bd16   Goutte   Add a circle foll...
357
358
359
    i = @bisectDate(@data, x0, 1)
    d0 = @data[i - 1]
    d1 = @data[i]
541e2936   Goutte   Synchronize the t...
360
    return unless d1 and d0
2463bd16   Goutte   Add a circle foll...
361
362
363
364
    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...
365
    mirrored = if @plotWidth? and xx > @plotWidth / 2 then true else false
541e2936   Goutte   Synchronize the t...
366

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

8cb213b9   Goutte   Clean up, and pre...
370
    transform = "translate(#{xx}, #{yy})"
81c9b2e8   Goutte   Add the values to...
371
372
    @cursorCircle.attr("transform", transform)
    @cursorValue.attr("transform", transform).text(d.y)
541e2936   Goutte   Synchronize the t...
373
374
                .attr('text-anchor', if mirrored then 'end' else 'start')
                .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
375
    @cursorValueShadow.attr("transform", transform).text(d.y)
541e2936   Goutte   Synchronize the t...
376
377
                      .attr('text-anchor', if mirrored then 'end' else 'start')
                      .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
378
    @cursorDate.attr("transform", transform).text(@timeFormat(d.x))
541e2936   Goutte   Synchronize the t...
379
380
               .attr('text-anchor', if mirrored then 'end' else 'start')
               .attr("dx", dx)
81c9b2e8   Goutte   Add the values to...
381
    @cursorDateShadow.attr("transform", transform).text(@timeFormat(d.x))
541e2936   Goutte   Synchronize the t...
382
383
                     .attr('text-anchor', if mirrored then 'end' else 'start')
                     .attr("dx", dx)
2463bd16   Goutte   Add a circle foll...
384
385

    this
f1f1e797   Goutte   Rewrite ugly java...
386

ae0aa7d2   Goutte   Add an x axis lab...
387
388
389
390

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

f1f1e797   Goutte   Rewrite ugly java...
391
export class Orbits
8cb213b9   Goutte   Clean up, and pre...
392
393
394
395
  """
  View of the solar system from above, with orbits segments for selected time
  interval, from real data.
  """
f1f1e797   Goutte   Rewrite ugly java...
396

a21f81d9   Goutte   Enable Venus and ...
397
  (@container, @options = {}) ->
f1f1e797   Goutte   Rewrite ugly java...
398
399
400
    @init()

  init: ->
a21f81d9   Goutte   Enable Venus and ...
401
    console.log "Initializing orbits...", @options
f1f1e797   Goutte   Rewrite ugly java...
402
403
404
405

    @margin = {
      top: 30,
      right: 20,
11662eed   Goutte   Add Y axis label ...
406
      bottom: 42,
f1f1e797   Goutte   Rewrite ugly java...
407
408
      left: 60
    }
f1f1e797   Goutte   Rewrite ugly java...
409

a21f81d9   Goutte   Enable Venus and ...
410
411
412
    @data = {}  # slug => HCI array
    @orbiters = {}  # slug => config
    @extremum = 1
f1f1e797   Goutte   Rewrite ugly java...
413
414
415
416
417
418
    @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...
419
420
421
422
423
    @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...
424
425
426
427
428
429
430
431
432
    @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 ...
433
    @xAxisTitle.append('tspan').attr('dy', '3px').text('HEE').attr('font-size', '8px')
ae0aa7d2   Goutte   Add an x axis lab...
434
    @xAxisTitle.append('tspan').attr('dy', '-3px').text('   (AU)')
f1f1e797   Goutte   Rewrite ugly java...
435

11662eed   Goutte   Add Y axis label ...
436
437
438
439
440
441
442
    @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...
443
444
445
    @sun = @plotWrapper.append("svg:image")
                       .attr('xlink:href', @options.sun.img)
                       .attr('width', '32px').attr('height', '32px')
f1f1e797   Goutte   Rewrite ugly java...
446
    @sun.append('svg:title').text("Sol")
f1f1e797   Goutte   Rewrite ugly java...
447

8cb213b9   Goutte   Clean up, and pre...
448
    $(@svg.node).hide();  # we'll show it later when there'll be data
f1f1e797   Goutte   Rewrite ugly java...
449
450
    @resize()

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

a21f81d9   Goutte   Enable Venus and ...
456
457
458
459
460
461
    @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...
462
463
464
465
466
467
468
469
470
471
472
473
    # 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 ...
474
                                .datum(data)
438929a4   Goutte   Rewrite the orbit...
475
476
                                .classed('orbit orbit_section', true)

a21f81d9   Goutte   Enable Venus and ...
477
478
    @orbiters[slug] = config
    @data[slug] = data
438929a4   Goutte   Rewrite the orbit...
479
480
481
482
483
484
    @orbitersElements[slug] =
      orbiter: orbiter
      orbit_ellipse: orbit_ellipse
      orbit_section: orbit_section
      orbit_line: orbit_line

a21f81d9   Goutte   Enable Venus and ...
485
486
    @resize()

8cb213b9   Goutte   Clean up, and pre...
487
488
    $(@svg.node).show();

438929a4   Goutte   Rewrite the orbit...
489
490
    this

f1f1e797   Goutte   Rewrite ugly java...
491
492
493
494
  resize: ->
    width = jQuery(@container).width() - @margin.left - @margin.right
    height = 1.0 * width

8cb213b9   Goutte   Clean up, and pre...
495
    #console.log("Resizing orbits : #{width} x #{height}")
f1f1e797   Goutte   Rewrite ugly java...
496
497
498
499
500
501
502

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

438929a4   Goutte   Rewrite the orbit...
505
506
507
    for slug, config of @orbiters
      @resizeOrbiter(slug, config)

f1f1e797   Goutte   Rewrite ugly java...
508
509
510
511
512
513
514
515
516
517
    @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...
518
    @xAxisTitle.attr("x", width / 2)
11662eed   Goutte   Add Y axis label ...
519
520
521
               .attr("y", 37)
    @yAxisTitle.attr("x", -1 * height / 2)
               .attr("y", -30)
ae0aa7d2   Goutte   Add an x axis lab...
522

f1f1e797   Goutte   Rewrite ugly java...
523
524
    this

438929a4   Goutte   Rewrite the orbit...
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
  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 ...
543
#        .attr('transform', 'rotate(66,'+(cx+c)+', '+cy+')')
438929a4   Goutte   Rewrite the orbit...
544
545
    @yScale.range([height, 0])

a21f81d9   Goutte   Enable Venus and ...
546
    data = @data[slug]
438929a4   Goutte   Rewrite the orbit...
547
548
549
550
551
552

    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...
553
  repositionOrbiter: (slug, datum) ->
a21f81d9   Goutte   Enable Venus and ...
554
    data = @data[slug]
ae0aa7d2   Goutte   Add an x axis lab...
555
556
557
558
    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...
559
560
561
562
563
    this

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

  moveToDate: (t) ->
a21f81d9   Goutte   Enable Venus and ...
564
    console.log("Trying to move to an undefined date") unless t
ae0aa7d2   Goutte   Add an x axis lab...
565
    for slug, el of @orbitersElements
a21f81d9   Goutte   Enable Venus and ...
566
      data = @data[slug]
ae0aa7d2   Goutte   Add an x axis lab...
567
568
569
570
571
      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
8cb213b9   Goutte   Clean up, and pre...
572
      @repositionOrbiter(slug, d)  # fixme <--(why?)
a21f81d9   Goutte   Enable Venus and ...
573
    this
ae0aa7d2   Goutte   Add an x axis lab...
574

8cb213b9   Goutte   Clean up, and pre...
575
576
  resizeDomain: (started_at, stopped_at) ->
    # fixme
ae0aa7d2   Goutte   Add an x axis lab...