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 | 23 | role: Software Ninja |
24 | 24 | mail: antoine.goutenoir@irap.omp.eu |
25 | 25 | - name: Myriam Bouchemit |
26 | - role: Server Maintenance | |
26 | + role: Code Reviewer | |
27 | 27 | mail: myriam.bouchemit@irap.omp.eu |
28 | - - name: Mikel Indurain | |
29 | - role: Simulation Software | |
30 | - mail: mindurain@irap.omp.eu | |
31 | 28 | - name: Nicolas André |
32 | 29 | role: Project Lead |
33 | 30 | mail: nicolas.andre@irap.omp.eu |
34 | - - name: Vincent Genot | |
31 | + - name: Vincent Génot | |
35 | 32 | role: Project Coordinator |
36 | 33 | mail: vincent.genot@irap.omp.eu |
37 | 34 | ... | ... |
web/static/js/swapp.js
... | ... | @@ -243,6 +243,7 @@ |
243 | 243 | this.options = options != null |
244 | 244 | ? options |
245 | 245 | : {}; |
246 | + this.onBrushEnd = bind$(this, 'onBrushEnd', prototype); | |
246 | 247 | this.onMouseOut = bind$(this, 'onMouseOut', prototype); |
247 | 248 | this.onMouseOver = bind$(this, 'onMouseOver', prototype); |
248 | 249 | this.onMouseMove = bind$(this, 'onMouseMove', prototype); |
... | ... | @@ -263,7 +264,7 @@ |
263 | 264 | this.yScale = d3.scaleLinear().domain(d3.extent(this.data, function(d){ |
264 | 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 | 268 | this.yAxis = d3.axisLeft().ticks(10); |
268 | 269 | this.line = d3.line().x(function(d){ |
269 | 270 | return this$.xScale(d.x); |
... | ... | @@ -275,8 +276,8 @@ |
275 | 276 | this.plotWrapper = this.svg.append('g'); |
276 | 277 | this.plotWrapper.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')'); |
277 | 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 | 281 | this.plotWrapper.append('g').classed('x axis', true); |
281 | 282 | this.plotWrapper.append('g').classed('y axis', true); |
282 | 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 | 297 | height = GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO * width; |
297 | 298 | this.plotWidth = width; |
298 | 299 | this.plotHeight = height; |
299 | - console.log("Resizing time series " + this.title + " of " + this.target.name + ": " + width + " x " + height); | |
300 | 300 | this.xScale.range([0, width]); |
301 | 301 | this.yScale.range([height, 0]); |
302 | 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 | 310 | this.yAxisText.attr("y", 20 - this.margin.left).attr("x", 0 - height / 2); |
311 | 311 | this.yAxisTextTarget.attr("y", 0 - this.margin.left).attr("x", 0 - height / 2); |
312 | 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 | 319 | if (!this.active) { |
314 | 320 | $(this.svg.node()).hide(); |
315 | 321 | } |
... | ... | @@ -320,10 +326,11 @@ |
320 | 326 | var x; |
321 | 327 | x = this.xScale.invert(d3.mouse(this.mouseCanvas.node())[0]); |
322 | 328 | if (this.options.onMouseMove != null) { |
323 | - return this.options.onMouseMove(x); | |
329 | + this.options.onMouseMove(x); | |
324 | 330 | } else { |
325 | - return this.moveCursor(x); | |
331 | + this.moveCursor(x); | |
326 | 332 | } |
333 | + return true; | |
327 | 334 | }; |
328 | 335 | TimeSeries.prototype.onMouseOver = function(){ |
329 | 336 | if (this.options.onMouseOver != null) { |
... | ... | @@ -339,6 +346,24 @@ |
339 | 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 | 367 | TimeSeries.prototype.showCursor = function(){ |
343 | 368 | return this.focus.style("display", null); |
344 | 369 | }; | ... | ... |
web/static/js/swapp.ls
... | ... | @@ -207,7 +207,7 @@ export class TimeSeries |
207 | 207 | #console.info("Y domain #{@title}", d3.extent(@data, (d) -> d.y)) |
208 | 208 | |
209 | 209 | @xAxis = d3.axisBottom() |
210 | - .tickFormat(d3.timeFormat("%Y-%m-%d")) | |
210 | +# .tickFormat(d3.timeFormat("%Y-%m-%d")) | |
211 | 211 | .ticks(7) |
212 | 212 | @yAxis = d3.axisLeft() |
213 | 213 | .ticks(10) |
... | ... | @@ -226,17 +226,16 @@ export class TimeSeries |
226 | 226 | .datum(@data) |
227 | 227 | .classed('line', true) |
228 | 228 | |
229 | + @brush = @plotWrapper.append("g") | |
230 | + .attr("class", "brush") | |
231 | + | |
229 | 232 | @mouseCanvas = @plotWrapper.append("rect") |
230 | 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 | 240 | @plotWrapper.append('g').classed('x axis', true) |
242 | 241 | @plotWrapper.append('g').classed('y axis', true) |
... | ... | @@ -289,7 +288,7 @@ export class TimeSeries |
289 | 288 | @plotWidth = width |
290 | 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 | 293 | @xScale.range([0, width]); |
295 | 294 | @yScale.range([height, 0]); |
... | ... | @@ -297,7 +296,7 @@ export class TimeSeries |
297 | 296 | @svg.attr('width', width + @margin.right + @margin.left) |
298 | 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 | 301 | @xAxis.scale(@xScale) |
303 | 302 | @yAxis.scale(@yScale) |
... | ... | @@ -322,6 +321,26 @@ export class TimeSeries |
322 | 321 | @mouseCanvas.attr("width", width) |
323 | 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 | 345 | unless @active then $(@svg.node()).hide() |
327 | 346 | this |
... | ... | @@ -336,6 +355,7 @@ export class TimeSeries |
336 | 355 | @options.onMouseMove(x) |
337 | 356 | else |
338 | 357 | @moveCursor(x) |
358 | + true # fixme: figure out what to return | |
339 | 359 | |
340 | 360 | onMouseOver: ~> |
341 | 361 | if @options.onMouseOver? |
... | ... | @@ -349,17 +369,25 @@ export class TimeSeries |
349 | 369 | else |
350 | 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 | 375 | # if not s |
356 | 376 | # @xScale.domain(d3.extent(@data, (d) -> d.x)).nice() |
357 | 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 | 392 | showCursor: -> |
365 | 393 | @focus.style("display", null) | ... | ... |
web/view/home.html.jinja2
... | ... | @@ -51,7 +51,7 @@ |
51 | 51 | <a class="mdl-navigation__link parameter" data-ts-slug="vlen" href="#">Velocity</a> |
52 | 52 | <a class="mdl-navigation__link parameter" data-ts-slug="temp" href="#">Temperature</a> |
53 | 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 | 55 | </nav> |
56 | 56 | |
57 | 57 | <div class="mdl-layout-spacer"></div> |
... | ... | @@ -98,8 +98,9 @@ |
98 | 98 | svg text { |
99 | 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 | 105 | path.line { |
105 | 106 | fill: none; |
... | ... | @@ -241,7 +242,7 @@ var configuration = { |
241 | 242 | }, |
242 | 243 | { |
243 | 244 | id: 'angl', |
244 | - title: "Angle S-S-E (deg)", | |
245 | + title: "Angle T-S-E (deg)", | |
245 | 246 | active: false, |
246 | 247 | unit: "deg" |
247 | 248 | } | ... | ... |