Commit 2038c9fb475f8b7ad7ead44afa1c853f4a4ac0e9

Authored by Goutte
1 parent f7a10f28

Add a zoom reset on double click, and a nice console log masthead.

Showing 2 changed files with 91 additions and 42 deletions   Show diff stats
web/static/js/swapp.js
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 function SpaceWeather(configuration){ 20 function SpaceWeather(configuration){
21 var configs, res$, k, this$ = this; 21 var configs, res$, k, this$ = this;
22 this.configuration = configuration; 22 this.configuration = configuration;
23 - console.info("Creating HelioPropa app...", this.configuration); 23 + console.info(" _ _ _ _ ____\n | | | | ___| (_) ___ | _ \\ _ __ ___ _ __ __ _\n | |_| |/ _ \\ | |/ _ \\| |_) | '__/ _ \\| '_ \\ / _` |\n | _ | __/ | | (_) | __/| | | (_) | |_) | (_| |\n |_| |_|\\___|_|_|\\___/|_|_ |_|_ \\___/| .__/ \\__,_|\n | |__ _ _ / ___| _ \\| _ \\| _ \\_|\n | '_ \\| | | | | | | | | | |_) | |_) |\n | |_) | |_| | | |___| |_| | __/| __/\n |_.__/ \\__, | \\____|____/|_| |_|\n |___/\n\nThe full source of this website is available at :\nhttps://gitlab.irap.omp.eu/CDPP/SPACEWEATHERONLINE");
24 this.targets = {}; 24 this.targets = {};
25 res$ = []; 25 res$ = [];
26 for (k in this.configuration.targets) { 26 for (k in this.configuration.targets) {
@@ -244,14 +244,18 @@ @@ -244,14 +244,18 @@
244 ? options 244 ? options
245 : {}; 245 : {};
246 this.onBrushEnd = bind$(this, 'onBrushEnd', prototype); 246 this.onBrushEnd = bind$(this, 'onBrushEnd', prototype);
  247 + this.onDoubleClick = bind$(this, 'onDoubleClick', prototype);
247 this.onMouseOut = bind$(this, 'onMouseOut', prototype); 248 this.onMouseOut = bind$(this, 'onMouseOut', prototype);
248 this.onMouseOver = bind$(this, 'onMouseOver', prototype); 249 this.onMouseOver = bind$(this, 'onMouseOver', prototype);
249 this.onMouseMove = bind$(this, 'onMouseMove', prototype); 250 this.onMouseMove = bind$(this, 'onMouseMove', prototype);
250 this.init(); 251 this.init();
251 } 252 }
  253 + TimeSeries.prototype.toString = function(){
  254 + return this.title + " of " + this.target.name;
  255 + };
252 TimeSeries.prototype.init = function(){ 256 TimeSeries.prototype.init = function(){
253 var dx, this$ = this; 257 var dx, this$ = this;
254 - console.info("Initializing time series " + this.title + " of " + this.target.name + "..."); 258 + console.info("Initializing time series " + this + "...");
255 this.margin = { 259 this.margin = {
256 top: 30, 260 top: 30,
257 right: 20, 261 right: 20,
@@ -311,26 +315,25 @@ @@ -311,26 +315,25 @@
311 this.yAxisTextTarget.attr("y", 0 - this.margin.left).attr("x", 0 - height / 2); 315 this.yAxisTextTarget.attr("y", 0 - this.margin.left).attr("x", 0 - height / 2);
312 this.mouseCanvas.attr("width", width).attr("height", height); 316 this.mouseCanvas.attr("width", width).attr("height", height);
313 if (this.brushFunction == null) { 317 if (this.brushFunction == null) {
314 - console.log("Creating the zooming brush for time series " + this.title + " of " + this.target.name + "..."); 318 + console.log("Creating the zooming brush for " + this + "...");
315 this.brushFunction = d3.brushX().extent([[0, 0], [width, height]]).handleSize(0).on("end", this.onBrushEnd); 319 this.brushFunction = d3.brushX().extent([[0, 0], [width, height]]).handleSize(0).on("end", this.onBrushEnd);
316 this.brush.call(this.brushFunction); 320 this.brush.call(this.brushFunction);
317 - this.svg.select(".brush .overlay").on("mouseover", this.onMouseOver).on("mouseout", this.onMouseOut).on("mousemove", this.onMouseMove); 321 + this.svg.select(".brush .overlay").on("mouseover.swappcursor", this.onMouseOver).on("mouseout.swappcursor", this.onMouseOut).on("mousemove.swappcursor", this.onMouseMove).on("dblclick", this.onDoubleClick);
318 } 322 }
319 if (!this.active) { 323 if (!this.active) {
320 $(this.svg.node()).hide(); 324 $(this.svg.node()).hide();
321 } 325 }
322 return this; 326 return this;
323 }; 327 };
324 - TimeSeries.prototype.resizeDomain = function(started_at, stopped_at){}; 328 + TimeSeries.prototype.resizeDomain = function(startDate, stopDate){};
325 TimeSeries.prototype.onMouseMove = function(){ 329 TimeSeries.prototype.onMouseMove = function(){
326 var x; 330 var x;
327 x = this.xScale.invert(d3.mouse(this.mouseCanvas.node())[0]); 331 x = this.xScale.invert(d3.mouse(this.mouseCanvas.node())[0]);
328 if (this.options.onMouseMove != null) { 332 if (this.options.onMouseMove != null) {
329 - this.options.onMouseMove(x); 333 + return this.options.onMouseMove(x);
330 } else { 334 } else {
331 - this.moveCursor(x); 335 + return this.moveCursor(x);
332 } 336 }
333 - return true;  
334 }; 337 };
335 TimeSeries.prototype.onMouseOver = function(){ 338 TimeSeries.prototype.onMouseOver = function(){
336 if (this.options.onMouseOver != null) { 339 if (this.options.onMouseOver != null) {
@@ -346,18 +349,44 @@ @@ -346,18 +349,44 @@
346 return this.hideCursor(); 349 return this.hideCursor();
347 } 350 }
348 }; 351 };
  352 + TimeSeries.prototype.onDoubleClick = function(){
  353 + console.debug("Resetting zoom of " + this + ".");
  354 + return this.resetZoom();
  355 + };
349 TimeSeries.prototype.onBrushEnd = function(){ 356 TimeSeries.prototype.onBrushEnd = function(){
350 var s, minmax; 357 var s, minmax;
351 s = d3.event.selection; 358 s = d3.event.selection;
352 - console.log("on brush end", s);  
353 if (s) { 359 if (s) {
354 minmax = [s[0], s[1]].map(this.xScale.invert, this.xScale); 360 minmax = [s[0], s[1]].map(this.xScale.invert, this.xScale);
355 - console.info("Zooming in from " + minmax[0], this.xScale.domain([s[0], s[1]].map(this.xScale.invert, this.xScale)));  
356 this.brush.call(this.brushFunction.move, null); 361 this.brush.call(this.brushFunction.move, null);
357 - return this.zoomIn(); 362 + return this.zoomIn(minmax[0], minmax[1]);
  363 + }
  364 + };
  365 + TimeSeries.prototype.zoomIn = function(startDate, stopDate){
  366 + var ref$, minDate, maxDate;
  367 + console.debug("Zooming in " + this + " from " + startDate + " to " + stopDate + ".");
  368 + ref$ = d3.extent(this.data, function(d){
  369 + return d.x;
  370 + }), minDate = ref$[0], maxDate = ref$[1];
  371 + if (startDate < minDate) {
  372 + startDate = minDate;
  373 + }
  374 + if (stopDate > maxDate) {
  375 + stopDate = maxDate;
358 } 376 }
  377 + this.xScale.domain([startDate, stopDate]);
  378 + return this.applyZoom();
  379 + };
  380 + TimeSeries.prototype.resetZoom = function(){
  381 + this.xScale.domain(d3.extent(this.data, function(d){
  382 + return d.x;
  383 + }));
  384 + this.yScale.domain(d3.extent(this.data, function(d){
  385 + return d.y;
  386 + }));
  387 + return this.applyZoom();
359 }; 388 };
360 - TimeSeries.prototype.zoomIn = function(){ 389 + TimeSeries.prototype.applyZoom = function(){
361 var t; 390 var t;
362 t = this.svg.transition().duration(750); 391 t = this.svg.transition().duration(750);
363 this.svg.select('.x.axis').transition(t).call(this.xAxis); 392 this.svg.select('.x.axis').transition(t).call(this.xAxis);
web/static/js/swapp.ls
@@ -31,7 +31,21 @@ export class SpaceWeather @@ -31,7 +31,21 @@ export class SpaceWeather
31 """ 31 """
32 32
33 (@configuration) -> 33 (@configuration) ->
34 - console.info "Creating HelioPropa app...", @configuration 34 + console.info """
  35 + _ _ _ _ ____
  36 + | | | | ___| (_) ___ | _ \\ _ __ ___ _ __ __ _
  37 + | |_| |/ _ \\ | |/ _ \\| |_) | '__/ _ \\| '_ \\ / _` |
  38 + | _ | __/ | | (_) | __/| | | (_) | |_) | (_| |
  39 + |_| |_|\\___|_|_|\\___/|_|_ |_|_ \\___/| .__/ \\__,_|
  40 + | |__ _ _ / ___| _ \\| _ \\| _ \\_|
  41 + | '_ \\| | | | | | | | | | |_) | |_) |
  42 + | |_) | |_| | | |___| |_| | __/| __/
  43 + |_.__/ \\__, | \\____|____/|_| |_|
  44 + |___/
  45 +
  46 +The full source of this website is available at :
  47 +https://gitlab.irap.omp.eu/CDPP/SPACEWEATHERONLINE
  48 +""" # HelioPropa by CDPP
35 @targets = {} 49 @targets = {}
36 configs = [@configuration.targets[k] for k of @configuration.targets] 50 configs = [@configuration.targets[k] for k of @configuration.targets]
37 configs.forEach((target_config) ~> 51 configs.forEach((target_config) ~>
@@ -191,8 +205,10 @@ export class TimeSeries @@ -191,8 +205,10 @@ export class TimeSeries
191 # data : list of {x: <datetime>, y: <float>} 205 # data : list of {x: <datetime>, y: <float>}
192 @init() 206 @init()
193 207
  208 + toString: -> "#{@title} of #{@target.name}"
  209 +
194 init: -> 210 init: ->
195 - console.info "Initializing time series #{@title} of #{@target.name}..." 211 + console.info "Initializing time series #{@}..."
196 212
197 @margin = { 213 @margin = {
198 top: 30, 214 top: 30,
@@ -204,8 +220,6 @@ export class TimeSeries @@ -204,8 +220,6 @@ export class TimeSeries
204 @xScale = d3.scaleTime().domain(d3.extent(@data, (d) -> d.x)) 220 @xScale = d3.scaleTime().domain(d3.extent(@data, (d) -> d.x))
205 @yScale = d3.scaleLinear().domain(d3.extent(@data, (d) -> d.y)) 221 @yScale = d3.scaleLinear().domain(d3.extent(@data, (d) -> d.y))
206 222
207 - #console.info("Y domain #{@title}", d3.extent(@data, (d) -> d.y))  
208 -  
209 @xAxis = d3.axisBottom() 223 @xAxis = d3.axisBottom()
210 # .tickFormat(d3.timeFormat("%Y-%m-%d")) 224 # .tickFormat(d3.timeFormat("%Y-%m-%d"))
211 .ticks(7) 225 .ticks(7)
@@ -220,22 +234,20 @@ export class TimeSeries @@ -220,22 +234,20 @@ export class TimeSeries
220 @svg.attr("class", "#{@parameter} #{@target.slug}") 234 @svg.attr("class", "#{@parameter} #{@target.slug}")
221 235
222 @plotWrapper = @svg.append('g') 236 @plotWrapper = @svg.append('g')
223 - @plotWrapper.attr('transform', 'translate(' + @margin.left + ',' + @margin.top + ')') 237 + @plotWrapper.attr('transform',
  238 + 'translate(' + @margin.left + ',' + @margin.top + ')')
224 239
225 @path = @plotWrapper.append('path') 240 @path = @plotWrapper.append('path')
226 .datum(@data) 241 .datum(@data)
227 .classed('line', true) 242 .classed('line', true)
228 243
229 @brush = @plotWrapper.append("g") 244 @brush = @plotWrapper.append("g")
230 - .attr("class", "brush") 245 + .attr("class", "brush")
231 246
  247 + # deprecated, use brush's 'overlay' child
232 @mouseCanvas = @plotWrapper.append("rect") 248 @mouseCanvas = @plotWrapper.append("rect")
233 .style("fill", "none") 249 .style("fill", "none")
234 # .style("pointer-events", "all") 250 # .style("pointer-events", "all")
235 -# @mouseCanvas  
236 -# .on("mouseover", @onMouseOver)  
237 -# .on("mouseout", @onMouseOut)  
238 -# .on("mousemove", @onMouseMove)  
239 251
240 @plotWrapper.append('g').classed('x axis', true) 252 @plotWrapper.append('g').classed('x axis', true)
241 @plotWrapper.append('g').classed('y axis', true) 253 @plotWrapper.append('g').classed('y axis', true)
@@ -322,7 +334,7 @@ export class TimeSeries @@ -322,7 +334,7 @@ export class TimeSeries
322 .attr("height", height) 334 .attr("height", height)
323 335
324 if not @brushFunction? 336 if not @brushFunction?
325 - console.log "Creating the zooming brush for time series #{@title} of #{@target.name}..." 337 + console.log "Creating the zooming brush for #{@}..."
326 # looks like d3.brush handles its own resizing on window.resize 338 # looks like d3.brush handles its own resizing on window.resize
327 @brushFunction = 339 @brushFunction =
328 d3.brushX() 340 d3.brushX()
@@ -333,19 +345,18 @@ export class TimeSeries @@ -333,19 +345,18 @@ export class TimeSeries
333 # .on("move", @onBrushMove) 345 # .on("move", @onBrushMove)
334 @brush.call(@brushFunction) 346 @brush.call(@brushFunction)
335 347
336 -# @mouseCanvas = @brush.append("rect")  
337 -# .style("fill", "none")  
338 -# .style("pointer-events", "all") 348 + # We're also adding our own cursor events to the brush's overlay,
  349 + # because it captures events and a rect cannot contain another.
339 @svg.select(".brush .overlay") 350 @svg.select(".brush .overlay")
340 - .on("mouseover", @onMouseOver)  
341 - .on("mouseout", @onMouseOut)  
342 - .on("mousemove", @onMouseMove)  
343 - 351 + .on("mouseover.swappcursor", @onMouseOver)
  352 + .on("mouseout.swappcursor", @onMouseOut)
  353 + .on("mousemove.swappcursor", @onMouseMove)
  354 + .on("dblclick", @onDoubleClick)
344 355
345 unless @active then $(@svg.node()).hide() 356 unless @active then $(@svg.node()).hide()
346 this 357 this
347 358
348 - resizeDomain: (started_at, stopped_at) -> 359 + resizeDomain: (startDate, stopDate) ->
349 # fixme 360 # fixme
350 # d3.behavior.zoom() 361 # d3.behavior.zoom()
351 362
@@ -355,7 +366,6 @@ export class TimeSeries @@ -355,7 +366,6 @@ export class TimeSeries
355 @options.onMouseMove(x) 366 @options.onMouseMove(x)
356 else 367 else
357 @moveCursor(x) 368 @moveCursor(x)
358 - true # fixme: figure out what to return  
359 369
360 onMouseOver: ~> 370 onMouseOver: ~>
361 if @options.onMouseOver? 371 if @options.onMouseOver?
@@ -369,26 +379,36 @@ export class TimeSeries @@ -369,26 +379,36 @@ export class TimeSeries
369 else 379 else
370 @hideCursor() 380 @hideCursor()
371 381
  382 + onDoubleClick: ~>
  383 + console.debug "Resetting zoom of #{@}."
  384 + @resetZoom()
  385 +
372 onBrushEnd: ~> 386 onBrushEnd: ~>
373 s = d3.event.selection 387 s = d3.event.selection
374 - console.log("on brush end", s)  
375 -# if not s  
376 -# @xScale.domain(d3.extent(@data, (d) -> d.x)).nice()  
377 -# @yScale.domain(d3.extent(@data, (d) -> d.y)).nice()  
378 if s 388 if s
379 minmax = [s[0], s[1]].map(@xScale.invert, @xScale) 389 minmax = [s[0], s[1]].map(@xScale.invert, @xScale)
380 - console.info "Zooming in from #{minmax[0]}",  
381 - @xScale.domain([s[0], s[1]].map(@xScale.invert, @xScale))  
382 @brush.call(@brushFunction.move, null) # some voodoo to hide the brush 390 @brush.call(@brushFunction.move, null) # some voodoo to hide the brush
383 - @zoomIn()  
384 -  
385 - zoomIn: -> 391 + @zoomIn(minmax[0], minmax[1])
  392 +
  393 + zoomIn: (startDate, stopDate) ->
  394 + console.debug "Zooming in #{@} from #{startDate} to #{stopDate}."
  395 + [minDate, maxDate] = d3.extent(@data, (d) -> d.x)
  396 + if startDate < minDate then startDate = minDate
  397 + if stopDate > maxDate then stopDate = maxDate
  398 + @xScale.domain([startDate, stopDate])
  399 + @applyZoom()
  400 +
  401 + resetZoom: ->
  402 + @xScale.domain(d3.extent(@data, (d) -> d.x))
  403 + @yScale.domain(d3.extent(@data, (d) -> d.y))
  404 + @applyZoom()
  405 +
  406 + applyZoom: ->
386 t = @svg.transition().duration(750); 407 t = @svg.transition().duration(750);
387 @svg.select('.x.axis').transition(t).call(@xAxis); 408 @svg.select('.x.axis').transition(t).call(@xAxis);
388 @svg.select('.y.axis').transition(t).call(@yAxis); 409 @svg.select('.y.axis').transition(t).call(@yAxis);
389 @path.transition(t).attr('d', @line) 410 @path.transition(t).attr('d', @line)
390 411
391 -  
392 showCursor: -> 412 showCursor: ->
393 @focus.style("display", null) 413 @focus.style("display", null)
394 414