From 08569a6b50caa694b921c4f44cc303900dda965e Mon Sep 17 00:00:00 2001 From: Goutte Date: Wed, 28 Jun 2017 10:51:34 +0200 Subject: [PATCH] Add a zooming brush on each time series. Implement changes suggested by Vincent Génot. --- config.yml | 7 ++----- web/static/js/swapp.js | 37 +++++++++++++++++++++++++++++++------ web/static/js/swapp.ls | 68 ++++++++++++++++++++++++++++++++++++++++++++++++-------------------- web/view/home.html.jinja2 | 9 +++++---- 4 files changed, 86 insertions(+), 35 deletions(-) diff --git a/config.yml b/config.yml index 88617db..10df19b 100644 --- a/config.yml +++ b/config.yml @@ -23,15 +23,12 @@ authors: role: Software Ninja mail: antoine.goutenoir@irap.omp.eu - name: Myriam Bouchemit - role: Server Maintenance + role: Code Reviewer mail: myriam.bouchemit@irap.omp.eu - - name: Mikel Indurain - role: Simulation Software - mail: mindurain@irap.omp.eu - name: Nicolas André role: Project Lead mail: nicolas.andre@irap.omp.eu - - name: Vincent Genot + - name: Vincent Génot role: Project Coordinator mail: vincent.genot@irap.omp.eu diff --git a/web/static/js/swapp.js b/web/static/js/swapp.js index 2e61df8..f96308a 100644 --- a/web/static/js/swapp.js +++ b/web/static/js/swapp.js @@ -243,6 +243,7 @@ this.options = options != null ? options : {}; + this.onBrushEnd = bind$(this, 'onBrushEnd', prototype); this.onMouseOut = bind$(this, 'onMouseOut', prototype); this.onMouseOver = bind$(this, 'onMouseOver', prototype); this.onMouseMove = bind$(this, 'onMouseMove', prototype); @@ -263,7 +264,7 @@ this.yScale = d3.scaleLinear().domain(d3.extent(this.data, function(d){ return d.y; })); - this.xAxis = d3.axisBottom().tickFormat(d3.timeFormat("%Y-%m-%d")).ticks(7); + this.xAxis = d3.axisBottom().ticks(7); this.yAxis = d3.axisLeft().ticks(10); this.line = d3.line().x(function(d){ return this$.xScale(d.x); @@ -275,8 +276,8 @@ this.plotWrapper = this.svg.append('g'); this.plotWrapper.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')'); this.path = this.plotWrapper.append('path').datum(this.data).classed('line', true); - this.mouseCanvas = this.plotWrapper.append("rect").style("fill", "none").style("pointer-events", "all"); - this.mouseCanvas.on("mouseover", this.onMouseOver).on("mouseout", this.onMouseOut).on("mousemove", this.onMouseMove); + this.brush = this.plotWrapper.append("g").attr("class", "brush"); + this.mouseCanvas = this.plotWrapper.append("rect").style("fill", "none"); this.plotWrapper.append('g').classed('x axis', true); this.plotWrapper.append('g').classed('y axis', true); this.yAxisText = this.plotWrapper.append("text").attr("transform", "rotate(-90)").attr("dy", "1em").style("text-anchor", "middle").text(this.title); @@ -296,7 +297,6 @@ height = GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO * GOLDEN_RATIO * width; this.plotWidth = width; this.plotHeight = height; - console.log("Resizing time series " + this.title + " of " + this.target.name + ": " + width + " x " + height); this.xScale.range([0, width]); this.yScale.range([height, 0]); this.svg.attr('width', width + this.margin.right + this.margin.left).attr('height', height + this.margin.top + this.margin.bottom); @@ -310,6 +310,12 @@ this.yAxisText.attr("y", 20 - this.margin.left).attr("x", 0 - height / 2); this.yAxisTextTarget.attr("y", 0 - this.margin.left).attr("x", 0 - height / 2); this.mouseCanvas.attr("width", width).attr("height", height); + if (this.brushFunction == null) { + console.log("Creating the zooming brush for time series " + this.title + " of " + this.target.name + "..."); + this.brushFunction = d3.brushX().extent([[0, 0], [width, height]]).handleSize(0).on("end", this.onBrushEnd); + this.brush.call(this.brushFunction); + this.svg.select(".brush .overlay").on("mouseover", this.onMouseOver).on("mouseout", this.onMouseOut).on("mousemove", this.onMouseMove); + } if (!this.active) { $(this.svg.node()).hide(); } @@ -320,10 +326,11 @@ var x; x = this.xScale.invert(d3.mouse(this.mouseCanvas.node())[0]); if (this.options.onMouseMove != null) { - return this.options.onMouseMove(x); + this.options.onMouseMove(x); } else { - return this.moveCursor(x); + this.moveCursor(x); } + return true; }; TimeSeries.prototype.onMouseOver = function(){ if (this.options.onMouseOver != null) { @@ -339,6 +346,24 @@ return this.hideCursor(); } }; + TimeSeries.prototype.onBrushEnd = function(){ + var s, minmax; + s = d3.event.selection; + console.log("on brush end", s); + if (s) { + minmax = [s[0], s[1]].map(this.xScale.invert, this.xScale); + console.info("Zooming in from " + minmax[0], this.xScale.domain([s[0], s[1]].map(this.xScale.invert, this.xScale))); + this.brush.call(this.brushFunction.move, null); + return this.zoomIn(); + } + }; + TimeSeries.prototype.zoomIn = function(){ + var t; + t = this.svg.transition().duration(750); + this.svg.select('.x.axis').transition(t).call(this.xAxis); + this.svg.select('.y.axis').transition(t).call(this.yAxis); + return this.path.transition(t).attr('d', this.line); + }; TimeSeries.prototype.showCursor = function(){ return this.focus.style("display", null); }; diff --git a/web/static/js/swapp.ls b/web/static/js/swapp.ls index bf272f9..9d5b074 100644 --- a/web/static/js/swapp.ls +++ b/web/static/js/swapp.ls @@ -207,7 +207,7 @@ export class TimeSeries #console.info("Y domain #{@title}", d3.extent(@data, (d) -> d.y)) @xAxis = d3.axisBottom() - .tickFormat(d3.timeFormat("%Y-%m-%d")) +# .tickFormat(d3.timeFormat("%Y-%m-%d")) .ticks(7) @yAxis = d3.axisLeft() .ticks(10) @@ -226,17 +226,16 @@ export class TimeSeries .datum(@data) .classed('line', true) + @brush = @plotWrapper.append("g") + .attr("class", "brush") + @mouseCanvas = @plotWrapper.append("rect") .style("fill", "none") - .style("pointer-events", "all") - @mouseCanvas - .on("mouseover", @onMouseOver) - .on("mouseout", @onMouseOut) - .on("mousemove", @onMouseMove) - -# @plotWrapper.append("g") -# .attr("class", "brush") -# .call(d3.brush().on("brush", @onBrushEnd)); +# .style("pointer-events", "all") +# @mouseCanvas +# .on("mouseover", @onMouseOver) +# .on("mouseout", @onMouseOut) +# .on("mousemove", @onMouseMove) @plotWrapper.append('g').classed('x axis', true) @plotWrapper.append('g').classed('y axis', true) @@ -289,7 +288,7 @@ export class TimeSeries @plotWidth = width @plotHeight = height - console.log("Resizing time series #{@title} of #{@target.name}: #{width} x #{height}") + #console.log("Resizing time series #{@title} of #{@target.name}: #{width} x #{height}") @xScale.range([0, width]); @yScale.range([height, 0]); @@ -297,7 +296,7 @@ export class TimeSeries @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 ? + @path.attr('d', @line) @xAxis.scale(@xScale) @yAxis.scale(@yScale) @@ -322,6 +321,26 @@ export class TimeSeries @mouseCanvas.attr("width", width) .attr("height", height) + if not @brushFunction? + console.log "Creating the zooming brush for time series #{@title} of #{@target.name}..." + # looks like d3.brush handles its own resizing on window.resize + @brushFunction = + d3.brushX() + .extent([[0, 0], [width, height]]) + .handleSize(0) + .on("end", @onBrushEnd) +# .on("start", @onBrushStart) +# .on("move", @onBrushMove) + @brush.call(@brushFunction) + +# @mouseCanvas = @brush.append("rect") +# .style("fill", "none") +# .style("pointer-events", "all") + @svg.select(".brush .overlay") + .on("mouseover", @onMouseOver) + .on("mouseout", @onMouseOut) + .on("mousemove", @onMouseMove) + unless @active then $(@svg.node()).hide() this @@ -336,6 +355,7 @@ export class TimeSeries @options.onMouseMove(x) else @moveCursor(x) + true # fixme: figure out what to return onMouseOver: ~> if @options.onMouseOver? @@ -349,17 +369,25 @@ export class TimeSeries else @hideCursor() -# onBrushEnd: ~> -# s = d3.event.selection -# console.info("on brush end", s) + onBrushEnd: ~> + s = d3.event.selection + console.log("on brush end", s) # if not s # @xScale.domain(d3.extent(@data, (d) -> d.x)).nice() # @yScale.domain(d3.extent(@data, (d) -> d.y)).nice() -# else -# console.info "brush end", s -# @xScale.domain([s[0][0], s[1][0]].map(x.invert, x)) -# @yScale.domain([s[1][1], s[0][1]].map(y.invert, y)) -# scatter.select(".brush").call(brush.move, null) + if s + minmax = [s[0], s[1]].map(@xScale.invert, @xScale) + console.info "Zooming in from #{minmax[0]}", + @xScale.domain([s[0], s[1]].map(@xScale.invert, @xScale)) + @brush.call(@brushFunction.move, null) # some voodoo to hide the brush + @zoomIn() + + zoomIn: -> + t = @svg.transition().duration(750); + @svg.select('.x.axis').transition(t).call(@xAxis); + @svg.select('.y.axis').transition(t).call(@yAxis); + @path.transition(t).attr('d', @line) + showCursor: -> @focus.style("display", null) diff --git a/web/view/home.html.jinja2 b/web/view/home.html.jinja2 index 0ea28de..a309c59 100755 --- a/web/view/home.html.jinja2 +++ b/web/view/home.html.jinja2 @@ -51,7 +51,7 @@ Velocity Temperature Density - Angle Source-Sun-Earth + Angle Target-Sun-Earth
@@ -98,8 +98,9 @@ svg text { {# fill: #f4f4f4;#} } - svg .brush { - border: 1px solid black; + #time_series svg .brush .selection { + fill: #efa02c; + fill-opacity: 0.382; } path.line { fill: none; @@ -241,7 +242,7 @@ var configuration = { }, { id: 'angl', - title: "Angle S-S-E (deg)", + title: "Angle T-S-E (deg)", active: false, unit: "deg" } -- libgit2 0.21.2