Commit 08569a6b50caa694b921c4f44cc303900dda965e
1 parent
908b4423
Exists in
master
and in
3 other branches
Add a zooming brush on each time series.
Implement changes suggested by Vincent Génot.
Showing
4 changed files
with
86 additions
and
35 deletions
Show diff stats
config.yml
@@ -23,15 +23,12 @@ authors: | @@ -23,15 +23,12 @@ authors: | ||
23 | role: Software Ninja | 23 | role: Software Ninja |
24 | mail: antoine.goutenoir@irap.omp.eu | 24 | mail: antoine.goutenoir@irap.omp.eu |
25 | - name: Myriam Bouchemit | 25 | - name: Myriam Bouchemit |
26 | - role: Server Maintenance | 26 | + role: Code Reviewer |
27 | mail: myriam.bouchemit@irap.omp.eu | 27 | mail: myriam.bouchemit@irap.omp.eu |
28 | - - name: Mikel Indurain | ||
29 | - role: Simulation Software | ||
30 | - mail: mindurain@irap.omp.eu | ||
31 | - name: Nicolas André | 28 | - name: Nicolas André |
32 | role: Project Lead | 29 | role: Project Lead |
33 | mail: nicolas.andre@irap.omp.eu | 30 | mail: nicolas.andre@irap.omp.eu |
34 | - - name: Vincent Genot | 31 | + - name: Vincent Génot |
35 | role: Project Coordinator | 32 | role: Project Coordinator |
36 | mail: vincent.genot@irap.omp.eu | 33 | mail: vincent.genot@irap.omp.eu |
37 | 34 |
web/static/js/swapp.js
@@ -243,6 +243,7 @@ | @@ -243,6 +243,7 @@ | ||
243 | this.options = options != null | 243 | this.options = options != null |
244 | ? options | 244 | ? options |
245 | : {}; | 245 | : {}; |
246 | + this.onBrushEnd = bind$(this, 'onBrushEnd', prototype); | ||
246 | this.onMouseOut = bind$(this, 'onMouseOut', prototype); | 247 | this.onMouseOut = bind$(this, 'onMouseOut', prototype); |
247 | this.onMouseOver = bind$(this, 'onMouseOver', prototype); | 248 | this.onMouseOver = bind$(this, 'onMouseOver', prototype); |
248 | this.onMouseMove = bind$(this, 'onMouseMove', prototype); | 249 | this.onMouseMove = bind$(this, 'onMouseMove', prototype); |
@@ -263,7 +264,7 @@ | @@ -263,7 +264,7 @@ | ||
263 | this.yScale = d3.scaleLinear().domain(d3.extent(this.data, function(d){ | 264 | this.yScale = d3.scaleLinear().domain(d3.extent(this.data, function(d){ |
264 | return d.y; | 265 | return d.y; |
265 | })); | 266 | })); |
266 | - this.xAxis = d3.axisBottom().tickFormat(d3.timeFormat("%Y-%m-%d")).ticks(7); | 267 | + this.xAxis = d3.axisBottom().ticks(7); |
267 | this.yAxis = d3.axisLeft().ticks(10); | 268 | this.yAxis = d3.axisLeft().ticks(10); |
268 | this.line = d3.line().x(function(d){ | 269 | this.line = d3.line().x(function(d){ |
269 | return this$.xScale(d.x); | 270 | return this$.xScale(d.x); |
@@ -275,8 +276,8 @@ | @@ -275,8 +276,8 @@ | ||
275 | this.plotWrapper = this.svg.append('g'); | 276 | this.plotWrapper = this.svg.append('g'); |
276 | this.plotWrapper.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')'); | 277 | this.plotWrapper.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')'); |
277 | this.path = this.plotWrapper.append('path').datum(this.data).classed('line', true); | 278 | this.path = this.plotWrapper.append('path').datum(this.data).classed('line', true); |
278 | - this.mouseCanvas = this.plotWrapper.append("rect").style("fill", "none").style("pointer-events", "all"); | ||
279 | - this.mouseCanvas.on("mouseover", this.onMouseOver).on("mouseout", this.onMouseOut).on("mousemove", this.onMouseMove); | 279 | + this.brush = this.plotWrapper.append("g").attr("class", "brush"); |
280 | + this.mouseCanvas = this.plotWrapper.append("rect").style("fill", "none"); | ||
280 | this.plotWrapper.append('g').classed('x axis', true); | 281 | this.plotWrapper.append('g').classed('x axis', true); |
281 | this.plotWrapper.append('g').classed('y axis', true); | 282 | this.plotWrapper.append('g').classed('y axis', true); |
282 | this.yAxisText = this.plotWrapper.append("text").attr("transform", "rotate(-90)").attr("dy", "1em").style("text-anchor", "middle").text(this.title); | 283 | this.yAxisText = this.plotWrapper.append("text").attr("transform", "rotate(-90)").attr("dy", "1em").style("text-anchor", "middle").text(this.title); |
@@ -296,7 +297,6 @@ | @@ -296,7 +297,6 @@ | ||
296 | height = GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO * width; | 297 | height = GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO * width; |
297 | this.plotWidth = width; | 298 | this.plotWidth = width; |
298 | this.plotHeight = height; | 299 | this.plotHeight = height; |
299 | - console.log("Resizing time series " + this.title + " of " + this.target.name + ": " + width + " x " + height); | ||
300 | this.xScale.range([0, width]); | 300 | this.xScale.range([0, width]); |
301 | this.yScale.range([height, 0]); | 301 | this.yScale.range([height, 0]); |
302 | this.svg.attr('width', width + this.margin.right + this.margin.left).attr('height', height + this.margin.top + this.margin.bottom); | 302 | this.svg.attr('width', width + this.margin.right + this.margin.left).attr('height', height + this.margin.top + this.margin.bottom); |
@@ -310,6 +310,12 @@ | @@ -310,6 +310,12 @@ | ||
310 | this.yAxisText.attr("y", 20 - this.margin.left).attr("x", 0 - height / 2); | 310 | this.yAxisText.attr("y", 20 - this.margin.left).attr("x", 0 - height / 2); |
311 | this.yAxisTextTarget.attr("y", 0 - this.margin.left).attr("x", 0 - height / 2); | 311 | this.yAxisTextTarget.attr("y", 0 - this.margin.left).attr("x", 0 - height / 2); |
312 | this.mouseCanvas.attr("width", width).attr("height", height); | 312 | this.mouseCanvas.attr("width", width).attr("height", height); |
313 | + if (this.brushFunction == null) { | ||
314 | + console.log("Creating the zooming brush for time series " + this.title + " of " + this.target.name + "..."); | ||
315 | + this.brushFunction = d3.brushX().extent([[0, 0], [width, height]]).handleSize(0).on("end", this.onBrushEnd); | ||
316 | + this.brush.call(this.brushFunction); | ||
317 | + this.svg.select(".brush .overlay").on("mouseover", this.onMouseOver).on("mouseout", this.onMouseOut).on("mousemove", this.onMouseMove); | ||
318 | + } | ||
313 | if (!this.active) { | 319 | if (!this.active) { |
314 | $(this.svg.node()).hide(); | 320 | $(this.svg.node()).hide(); |
315 | } | 321 | } |
@@ -320,10 +326,11 @@ | @@ -320,10 +326,11 @@ | ||
320 | var x; | 326 | var x; |
321 | x = this.xScale.invert(d3.mouse(this.mouseCanvas.node())[0]); | 327 | x = this.xScale.invert(d3.mouse(this.mouseCanvas.node())[0]); |
322 | if (this.options.onMouseMove != null) { | 328 | if (this.options.onMouseMove != null) { |
323 | - return this.options.onMouseMove(x); | 329 | + this.options.onMouseMove(x); |
324 | } else { | 330 | } else { |
325 | - return this.moveCursor(x); | 331 | + this.moveCursor(x); |
326 | } | 332 | } |
333 | + return true; | ||
327 | }; | 334 | }; |
328 | TimeSeries.prototype.onMouseOver = function(){ | 335 | TimeSeries.prototype.onMouseOver = function(){ |
329 | if (this.options.onMouseOver != null) { | 336 | if (this.options.onMouseOver != null) { |
@@ -339,6 +346,24 @@ | @@ -339,6 +346,24 @@ | ||
339 | return this.hideCursor(); | 346 | return this.hideCursor(); |
340 | } | 347 | } |
341 | }; | 348 | }; |
349 | + TimeSeries.prototype.onBrushEnd = function(){ | ||
350 | + var s, minmax; | ||
351 | + s = d3.event.selection; | ||
352 | + console.log("on brush end", s); | ||
353 | + if (s) { | ||
354 | + 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); | ||
357 | + return this.zoomIn(); | ||
358 | + } | ||
359 | + }; | ||
360 | + TimeSeries.prototype.zoomIn = function(){ | ||
361 | + var t; | ||
362 | + t = this.svg.transition().duration(750); | ||
363 | + this.svg.select('.x.axis').transition(t).call(this.xAxis); | ||
364 | + this.svg.select('.y.axis').transition(t).call(this.yAxis); | ||
365 | + return this.path.transition(t).attr('d', this.line); | ||
366 | + }; | ||
342 | TimeSeries.prototype.showCursor = function(){ | 367 | TimeSeries.prototype.showCursor = function(){ |
343 | return this.focus.style("display", null); | 368 | return this.focus.style("display", null); |
344 | }; | 369 | }; |
web/static/js/swapp.ls
@@ -207,7 +207,7 @@ export class TimeSeries | @@ -207,7 +207,7 @@ export class TimeSeries | ||
207 | #console.info("Y domain #{@title}", d3.extent(@data, (d) -> d.y)) | 207 | #console.info("Y domain #{@title}", d3.extent(@data, (d) -> d.y)) |
208 | 208 | ||
209 | @xAxis = d3.axisBottom() | 209 | @xAxis = d3.axisBottom() |
210 | - .tickFormat(d3.timeFormat("%Y-%m-%d")) | 210 | +# .tickFormat(d3.timeFormat("%Y-%m-%d")) |
211 | .ticks(7) | 211 | .ticks(7) |
212 | @yAxis = d3.axisLeft() | 212 | @yAxis = d3.axisLeft() |
213 | .ticks(10) | 213 | .ticks(10) |
@@ -226,17 +226,16 @@ export class TimeSeries | @@ -226,17 +226,16 @@ export class TimeSeries | ||
226 | .datum(@data) | 226 | .datum(@data) |
227 | .classed('line', true) | 227 | .classed('line', true) |
228 | 228 | ||
229 | + @brush = @plotWrapper.append("g") | ||
230 | + .attr("class", "brush") | ||
231 | + | ||
229 | @mouseCanvas = @plotWrapper.append("rect") | 232 | @mouseCanvas = @plotWrapper.append("rect") |
230 | .style("fill", "none") | 233 | .style("fill", "none") |
231 | - .style("pointer-events", "all") | ||
232 | - @mouseCanvas | ||
233 | - .on("mouseover", @onMouseOver) | ||
234 | - .on("mouseout", @onMouseOut) | ||
235 | - .on("mousemove", @onMouseMove) | ||
236 | - | ||
237 | -# @plotWrapper.append("g") | ||
238 | -# .attr("class", "brush") | ||
239 | -# .call(d3.brush().on("brush", @onBrushEnd)); | 234 | +# .style("pointer-events", "all") |
235 | +# @mouseCanvas | ||
236 | +# .on("mouseover", @onMouseOver) | ||
237 | +# .on("mouseout", @onMouseOut) | ||
238 | +# .on("mousemove", @onMouseMove) | ||
240 | 239 | ||
241 | @plotWrapper.append('g').classed('x axis', true) | 240 | @plotWrapper.append('g').classed('x axis', true) |
242 | @plotWrapper.append('g').classed('y axis', true) | 241 | @plotWrapper.append('g').classed('y axis', true) |
@@ -289,7 +288,7 @@ export class TimeSeries | @@ -289,7 +288,7 @@ export class TimeSeries | ||
289 | @plotWidth = width | 288 | @plotWidth = width |
290 | @plotHeight = height | 289 | @plotHeight = height |
291 | 290 | ||
292 | - console.log("Resizing time series #{@title} of #{@target.name}: #{width} x #{height}") | 291 | + #console.log("Resizing time series #{@title} of #{@target.name}: #{width} x #{height}") |
293 | 292 | ||
294 | @xScale.range([0, width]); | 293 | @xScale.range([0, width]); |
295 | @yScale.range([height, 0]); | 294 | @yScale.range([height, 0]); |
@@ -297,7 +296,7 @@ export class TimeSeries | @@ -297,7 +296,7 @@ export class TimeSeries | ||
297 | @svg.attr('width', width + @margin.right + @margin.left) | 296 | @svg.attr('width', width + @margin.right + @margin.left) |
298 | .attr('height', height + @margin.top + @margin.bottom) | 297 | .attr('height', height + @margin.top + @margin.bottom) |
299 | 298 | ||
300 | - @path.attr('d', @line) # wip, do we need to put this in resize ? | 299 | + @path.attr('d', @line) |
301 | 300 | ||
302 | @xAxis.scale(@xScale) | 301 | @xAxis.scale(@xScale) |
303 | @yAxis.scale(@yScale) | 302 | @yAxis.scale(@yScale) |
@@ -322,6 +321,26 @@ export class TimeSeries | @@ -322,6 +321,26 @@ export class TimeSeries | ||
322 | @mouseCanvas.attr("width", width) | 321 | @mouseCanvas.attr("width", width) |
323 | .attr("height", height) | 322 | .attr("height", height) |
324 | 323 | ||
324 | + if not @brushFunction? | ||
325 | + console.log "Creating the zooming brush for time series #{@title} of #{@target.name}..." | ||
326 | + # looks like d3.brush handles its own resizing on window.resize | ||
327 | + @brushFunction = | ||
328 | + d3.brushX() | ||
329 | + .extent([[0, 0], [width, height]]) | ||
330 | + .handleSize(0) | ||
331 | + .on("end", @onBrushEnd) | ||
332 | +# .on("start", @onBrushStart) | ||
333 | +# .on("move", @onBrushMove) | ||
334 | + @brush.call(@brushFunction) | ||
335 | + | ||
336 | +# @mouseCanvas = @brush.append("rect") | ||
337 | +# .style("fill", "none") | ||
338 | +# .style("pointer-events", "all") | ||
339 | + @svg.select(".brush .overlay") | ||
340 | + .on("mouseover", @onMouseOver) | ||
341 | + .on("mouseout", @onMouseOut) | ||
342 | + .on("mousemove", @onMouseMove) | ||
343 | + | ||
325 | 344 | ||
326 | unless @active then $(@svg.node()).hide() | 345 | unless @active then $(@svg.node()).hide() |
327 | this | 346 | this |
@@ -336,6 +355,7 @@ export class TimeSeries | @@ -336,6 +355,7 @@ export class TimeSeries | ||
336 | @options.onMouseMove(x) | 355 | @options.onMouseMove(x) |
337 | else | 356 | else |
338 | @moveCursor(x) | 357 | @moveCursor(x) |
358 | + true # fixme: figure out what to return | ||
339 | 359 | ||
340 | onMouseOver: ~> | 360 | onMouseOver: ~> |
341 | if @options.onMouseOver? | 361 | if @options.onMouseOver? |
@@ -349,17 +369,25 @@ export class TimeSeries | @@ -349,17 +369,25 @@ export class TimeSeries | ||
349 | else | 369 | else |
350 | @hideCursor() | 370 | @hideCursor() |
351 | 371 | ||
352 | -# onBrushEnd: ~> | ||
353 | -# s = d3.event.selection | ||
354 | -# console.info("on brush end", s) | 372 | + onBrushEnd: ~> |
373 | + s = d3.event.selection | ||
374 | + console.log("on brush end", s) | ||
355 | # if not s | 375 | # if not s |
356 | # @xScale.domain(d3.extent(@data, (d) -> d.x)).nice() | 376 | # @xScale.domain(d3.extent(@data, (d) -> d.x)).nice() |
357 | # @yScale.domain(d3.extent(@data, (d) -> d.y)).nice() | 377 | # @yScale.domain(d3.extent(@data, (d) -> d.y)).nice() |
358 | -# else | ||
359 | -# console.info "brush end", s | ||
360 | -# @xScale.domain([s[0][0], s[1][0]].map(x.invert, x)) | ||
361 | -# @yScale.domain([s[1][1], s[0][1]].map(y.invert, y)) | ||
362 | -# scatter.select(".brush").call(brush.move, null) | 378 | + if s |
379 | + 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 | ||
383 | + @zoomIn() | ||
384 | + | ||
385 | + zoomIn: -> | ||
386 | + t = @svg.transition().duration(750); | ||
387 | + @svg.select('.x.axis').transition(t).call(@xAxis); | ||
388 | + @svg.select('.y.axis').transition(t).call(@yAxis); | ||
389 | + @path.transition(t).attr('d', @line) | ||
390 | + | ||
363 | 391 | ||
364 | showCursor: -> | 392 | showCursor: -> |
365 | @focus.style("display", null) | 393 | @focus.style("display", null) |
web/view/home.html.jinja2
@@ -51,7 +51,7 @@ | @@ -51,7 +51,7 @@ | ||
51 | <a class="mdl-navigation__link parameter" data-ts-slug="vlen" href="#">Velocity</a> | 51 | <a class="mdl-navigation__link parameter" data-ts-slug="vlen" href="#">Velocity</a> |
52 | <a class="mdl-navigation__link parameter" data-ts-slug="temp" href="#">Temperature</a> | 52 | <a class="mdl-navigation__link parameter" data-ts-slug="temp" href="#">Temperature</a> |
53 | <a class="mdl-navigation__link parameter" data-ts-slug="dens" href="#">Density</a> | 53 | <a class="mdl-navigation__link parameter" data-ts-slug="dens" href="#">Density</a> |
54 | - <a class="mdl-navigation__link parameter" data-ts-slug="angl" href="#">Angle Source-Sun-Earth</a> | 54 | + <a class="mdl-navigation__link parameter" data-ts-slug="angl" href="#">Angle Target-Sun-Earth</a> |
55 | </nav> | 55 | </nav> |
56 | 56 | ||
57 | <div class="mdl-layout-spacer"></div> | 57 | <div class="mdl-layout-spacer"></div> |
@@ -98,8 +98,9 @@ | @@ -98,8 +98,9 @@ | ||
98 | svg text { | 98 | svg text { |
99 | {# fill: #f4f4f4;#} | 99 | {# fill: #f4f4f4;#} |
100 | } | 100 | } |
101 | - svg .brush { | ||
102 | - border: 1px solid black; | 101 | + #time_series svg .brush .selection { |
102 | + fill: #efa02c; | ||
103 | + fill-opacity: 0.382; | ||
103 | } | 104 | } |
104 | path.line { | 105 | path.line { |
105 | fill: none; | 106 | fill: none; |
@@ -241,7 +242,7 @@ var configuration = { | @@ -241,7 +242,7 @@ var configuration = { | ||
241 | }, | 242 | }, |
242 | { | 243 | { |
243 | id: 'angl', | 244 | id: 'angl', |
244 | - title: "Angle S-S-E (deg)", | 245 | + title: "Angle T-S-E (deg)", |
245 | active: false, | 246 | active: false, |
246 | unit: "deg" | 247 | unit: "deg" |
247 | } | 248 | } |