aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-06-22 11:22:24 +0200
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-07-04 14:15:34 +0200
commit35b4bc1e0319f991f9bb72c681c6be7f7991b478 (patch)
tree139e7f78caab331629e7a697c6d1dd046a35e241
parent7feb62d1317819a82df8dcbc71969d9c1d51bdc7 (diff)
downloadsonarqube-35b4bc1e0319f991f9bb72c681c6be7f7991b478.tar.gz
sonarqube-35b4bc1e0319f991f9bb72c681c6be7f7991b478.zip
SONAR-9402 Add wheel zoom to the project history graphs
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js16
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js25
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphs.js6
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.js2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap78
-rw-r--r--server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js44
-rw-r--r--server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js95
-rw-r--r--server/sonar-web/src/main/less/components/graphics.less6
8 files changed, 167 insertions, 105 deletions
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js
index 3dea9f1a188..73e9411b862 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js
@@ -19,10 +19,9 @@
*/
// @flow
import React from 'react';
-import { some, throttle } from 'lodash';
+import { some } from 'lodash';
import { AutoSizer } from 'react-virtualized';
import ZoomTimeLine from '../../../components/charts/ZoomTimeLine';
-import type { RawQuery } from '../../../helpers/query';
import type { Serie } from '../../../components/charts/AdvancedTimeline';
type Props = {
@@ -33,22 +32,14 @@ type Props = {
metricsType: string,
series: Array<Serie>,
showAreas?: boolean,
- updateGraphZoom: (from: ?Date, to: ?Date) => void,
- updateQuery: RawQuery => void
+ updateGraphZoom: (from: ?Date, to: ?Date) => void
};
export default class GraphsZoom extends React.PureComponent {
props: Props;
- constructor(props: Props) {
- super(props);
- this.updateDateRange = throttle(this.updateDateRange, 100);
- }
-
hasHistoryData = () => some(this.props.series, serie => serie.data && serie.data.length > 2);
- updateDateRange = (from: ?Date, to: ?Date) => this.props.updateQuery({ from, to });
-
render() {
const { loading } = this.props;
if (loading || !this.hasHistoryData()) {
@@ -70,8 +61,7 @@ export default class GraphsZoom extends React.PureComponent {
series={this.props.series}
showAreas={this.props.showAreas}
startDate={this.props.graphStartDate}
- updateZoom={this.updateDateRange}
- updateZoomFast={this.props.updateGraphZoom}
+ updateZoom={this.props.updateGraphZoom}
/>
)}
</AutoSizer>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js
index db1eb841dac..004ef709197 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js
@@ -19,6 +19,7 @@
*/
// @flow
import React from 'react';
+import { debounce, sortBy } from 'lodash';
import ProjectActivityGraphsHeader from './ProjectActivityGraphsHeader';
import GraphsZoom from './GraphsZoom';
import StaticGraphs from './StaticGraphs';
@@ -62,6 +63,7 @@ export default class ProjectActivityGraphs extends React.PureComponent {
graphEndDate: props.query.to || null,
series
};
+ this.updateQueryDateRange = debounce(this.updateQueryDateRange, 250);
}
componentWillReceiveProps(nextProps: Props) {
@@ -100,8 +102,27 @@ export default class ProjectActivityGraphs extends React.PureComponent {
};
});
- updateGraphZoom = (graphStartDate: ?Date, graphEndDate: ?Date) =>
+ updateGraphZoom = (graphStartDate: ?Date, graphEndDate: ?Date) => {
+ if (graphEndDate != null && graphStartDate != null) {
+ const msDiff = Math.abs(graphEndDate.valueOf() - graphStartDate.valueOf());
+ // 12 hours minimum between the two dates
+ if (msDiff < 1000 * 60 * 60 * 12) {
+ return;
+ }
+ }
+
this.setState({ graphStartDate, graphEndDate });
+ this.updateQueryDateRange([graphStartDate, graphEndDate]);
+ };
+
+ updateQueryDateRange = (dates: Array<?Date>) => {
+ if (dates[0] == null || dates[1] == null) {
+ this.props.updateQuery({ from: dates[0], to: dates[1] });
+ } else {
+ const sortedDates = sortBy(dates);
+ this.props.updateQuery({ from: sortedDates[0], to: sortedDates[1] });
+ }
+ };
render() {
const { leakPeriodDate, loading, metricsType, query } = this.props;
@@ -120,6 +141,7 @@ export default class ProjectActivityGraphs extends React.PureComponent {
project={this.props.project}
series={series}
showAreas={['coverage', 'duplications'].includes(query.graph)}
+ updateGraphZoom={this.updateGraphZoom}
/>
<GraphsZoom
graphEndDate={this.state.graphEndDate}
@@ -130,7 +152,6 @@ export default class ProjectActivityGraphs extends React.PureComponent {
series={series}
showAreas={['coverage', 'duplications'].includes(query.graph)}
updateGraphZoom={this.updateGraphZoom}
- updateQuery={this.props.updateQuery}
/>
</div>
);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphs.js b/server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphs.js
index e14f5f097fb..7428bdd3282 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphs.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphs.js
@@ -32,13 +32,14 @@ import type { Serie } from '../../../components/charts/AdvancedTimeline';
type Props = {
analyses: Array<Analysis>,
eventFilter: string,
+ graphEndDate: ?Date,
graphStartDate: ?Date,
leakPeriodDate: Date,
loading: boolean,
metricsType: string,
series: Array<Serie>,
showAreas?: boolean,
- graphEndDate: ?Date
+ updateGraphZoom: (from: ?Date, to: ?Date) => void
};
export default class StaticGraphs extends React.PureComponent {
@@ -108,6 +109,7 @@ export default class StaticGraphs extends React.PureComponent {
endDate={this.props.graphEndDate}
events={this.getEvents()}
height={height}
+ width={width}
interpolate="linear"
formatValue={this.formatValue}
formatYTick={this.formatYTick}
@@ -116,7 +118,7 @@ export default class StaticGraphs extends React.PureComponent {
series={series}
showAreas={this.props.showAreas}
startDate={this.props.graphStartDate}
- width={width}
+ updateZoom={this.props.updateGraphZoom}
/>
)}
</AutoSizer>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.js b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.js
index 3434f552fae..2b27845d57c 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.js
@@ -80,7 +80,7 @@ it('should render correctly the graph and legends', () => {
expect(shallow(<ProjectActivityGraphs {...DEFAULT_PROPS} />)).toMatchSnapshot();
});
-it('should render correctly filter history on dates', () => {
+it('should render correctly with filter history on dates', () => {
const wrapper = shallow(
<ProjectActivityGraphs
{...DEFAULT_PROPS}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap
index 0fa696ab435..67899574868 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap
@@ -1,39 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render correctly filter history on dates 1`] = `
-Object {
- "filteredSeries": Array [
- Object {
- "data": Array [],
- "name": "code_smells",
- "style": 1,
- "translatedName": "metric.code_smells.name",
- },
- ],
- "series": Array [
- Object {
- "data": Array [
- Object {
- "x": 2016-10-26T10:17:29.000Z,
- "y": 2286,
- },
- Object {
- "x": 2016-10-27T10:21:15.000Z,
- "y": 1749,
- },
- Object {
- "x": 2016-10-27T14:33:50.000Z,
- "y": 500,
- },
- ],
- "name": "code_smells",
- "style": 1,
- "translatedName": "metric.code_smells.name",
- },
- ],
-}
-`;
-
exports[`should render correctly the graph and legends 1`] = `
<div
className="project-activity-layout-page-main-inner boxed-group boxed-group-inner"
@@ -80,7 +46,13 @@ exports[`should render correctly the graph and legends 1`] = `
]
}
eventFilter=""
- filteredSeries={
+ graphEndDate={null}
+ graphStartDate={null}
+ leakPeriodDate="2017-05-16T13:50:02+0200"
+ loading={false}
+ metricsType="INT"
+ project="org.sonarsource.sonarqube:sonarqube"
+ series={
Array [
Object {
"data": Array [
@@ -103,10 +75,15 @@ exports[`should render correctly the graph and legends 1`] = `
},
]
}
+ showAreas={false}
+ updateGraphZoom={[Function]}
+ />
+ <GraphsZoom
+ graphEndDate={null}
+ graphStartDate={null}
leakPeriodDate="2017-05-16T13:50:02+0200"
loading={false}
metricsType="INT"
- project="org.sonarsource.sonarqube:sonarqube"
series={
Array [
Object {
@@ -131,6 +108,35 @@ exports[`should render correctly the graph and legends 1`] = `
]
}
showAreas={false}
+ updateGraphZoom={[Function]}
/>
</div>
`;
+
+exports[`should render correctly with filter history on dates 1`] = `
+Object {
+ "graphEndDate": null,
+ "graphStartDate": "2016-10-27T12:21:15+0200",
+ "series": Array [
+ Object {
+ "data": Array [
+ Object {
+ "x": 2016-10-26T10:17:29.000Z,
+ "y": 2286,
+ },
+ Object {
+ "x": 2016-10-27T10:21:15.000Z,
+ "y": 1749,
+ },
+ Object {
+ "x": 2016-10-27T14:33:50.000Z,
+ "y": 500,
+ },
+ ],
+ "name": "code_smells",
+ "style": 1,
+ "translatedName": "metric.code_smells.name",
+ },
+ ],
+}
+`;
diff --git a/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js b/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js
index 7beb5d31afc..fa495a1ae8c 100644
--- a/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js
+++ b/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js
@@ -44,7 +44,9 @@ type Props = {
series: Array<Serie>,
showAreas?: boolean,
showEventMarkers?: boolean,
- startDate: ?Date
+ startDate: ?Date,
+ updateZoom: (start: ?Date, endDate: ?Date) => void,
+ zoomSpeed: number
};
export default class AdvancedTimeline extends React.PureComponent {
@@ -52,7 +54,8 @@ export default class AdvancedTimeline extends React.PureComponent {
static defaultProps = {
eventSize: 8,
- padding: [10, 10, 30, 60]
+ padding: [10, 10, 30, 60],
+ zoomSpeed: 1
};
getRatingScale = (availableHeight: number) =>
@@ -75,7 +78,11 @@ export default class AdvancedTimeline extends React.PureComponent {
const dateRange = extent(flatData, d => d.x);
const start = this.props.startDate ? this.props.startDate : dateRange[0];
const end = this.props.endDate ? this.props.endDate : dateRange[1];
- return scaleTime().domain(sortBy([start, end])).range([0, availableWidth]).clamp(false);
+ const xScale = scaleTime().domain(sortBy([start, end])).range([0, availableWidth]).clamp(false);
+ return {
+ xScale,
+ maxXRange: dateRange.map(xScale)
+ };
};
getScales = () => {
@@ -83,7 +90,7 @@ export default class AdvancedTimeline extends React.PureComponent {
const availableHeight = this.props.height - this.props.padding[0] - this.props.padding[2];
const flatData = flatten(this.props.series.map((serie: Serie) => serie.data));
return {
- xScale: this.getXScale(availableWidth, flatData),
+ ...this.getXScale(availableWidth, flatData),
yScale: this.getYScale(availableHeight, flatData)
};
};
@@ -93,6 +100,21 @@ export default class AdvancedTimeline extends React.PureComponent {
return `M${half} 0 L${size} ${half} L ${half} ${size} L0 ${half} L${half} 0 L${size} ${half}`;
};
+ handleWheel = (xScale: Scale, maxXRange: Array<number>) => (
+ evt: WheelEvent & { target: HTMLElement }
+ ) => {
+ evt.preventDefault();
+ const parentBbox = evt.target.getBoundingClientRect();
+ const mouseXPos = (evt.clientX - parentBbox.left) / parentBbox.width;
+ const xRange = xScale.range();
+ const speed = evt.deltaMode ? 25 / evt.deltaMode * this.props.zoomSpeed : this.props.zoomSpeed;
+ const leftPos = xRange[0] - Math.round(speed * evt.deltaY * mouseXPos);
+ const rightPos = xRange[1] + Math.round(speed * evt.deltaY * (1 - mouseXPos));
+ const startDate = leftPos > maxXRange[0] ? xScale.invert(leftPos) : null;
+ const endDate = rightPos < maxXRange[1] ? xScale.invert(rightPos) : null;
+ this.props.updateZoom(startDate, endDate);
+ };
+
renderHorizontalGrid = (xScale: Scale, yScale: Scale) => {
const hasTicks = typeof yScale.ticks === 'function';
const ticks = hasTicks ? yScale.ticks(4) : yScale.domain();
@@ -243,12 +265,23 @@ export default class AdvancedTimeline extends React.PureComponent {
);
};
+ renderZoomOverlay = (xScale: Scale, yScale: Scale, maxXRange: Array<number>) => {
+ return (
+ <rect
+ className="chart-wheel-zoom-overlay"
+ width={xScale.range()[1]}
+ height={yScale.range()[0]}
+ onWheel={this.handleWheel(xScale, maxXRange)}
+ />
+ );
+ };
+
render() {
if (!this.props.width || !this.props.height) {
return <div />;
}
- const { xScale, yScale } = this.getScales();
+ const { maxXRange, xScale, yScale } = this.getScales();
const isZoomed = this.props.startDate || this.props.endDate;
return (
<svg
@@ -262,6 +295,7 @@ export default class AdvancedTimeline extends React.PureComponent {
{this.renderTicks(xScale, yScale)}
{this.props.showAreas && this.renderAreas(xScale, yScale)}
{this.renderLines(xScale, yScale)}
+ {this.renderZoomOverlay(xScale, yScale, maxXRange)}
{this.props.showEventMarkers && this.renderEvents(xScale, yScale)}
</g>
</svg>
diff --git a/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js b/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js
index 85b11114e07..34a11d878cf 100644
--- a/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js
+++ b/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js
@@ -21,7 +21,7 @@
import React from 'react';
import classNames from 'classnames';
import { flatten, sortBy } from 'lodash';
-import { extent, max, min } from 'd3-array';
+import { extent, max } from 'd3-array';
import { scaleLinear, scalePoint, scaleTime } from 'd3-scale';
import { line as d3Line, area, curveBasis } from 'd3-shape';
import Draggable, { DraggableCore } from 'react-draggable';
@@ -41,8 +41,7 @@ type Props = {
showAreas?: boolean,
showXTicks?: boolean,
startDate: ?Date,
- updateZoom: (start: ?Date, endDate: ?Date) => void,
- updateZoomFast: (start: ?Date, endDate: ?Date) => void
+ updateZoom: (start: ?Date, endDate: ?Date) => void
};
type State = {
@@ -96,35 +95,42 @@ export default class ZoomTimeLine extends React.PureComponent {
handleSelectionDrag = (
xScale: Scale,
- updateFunc: (xScale: Scale, xArray: Array<number>) => void,
+ width: number,
+ xDim: Array<number>,
checkDelta?: boolean
) => (e: Event, data: DraggableData) => {
if (!checkDelta || data.deltaX) {
- updateFunc(xScale, [data.x, data.node.getBoundingClientRect().width + data.x]);
+ const x = Math.max(xDim[0], Math.min(data.x, xDim[1] - width));
+ this.handleZoomUpdate(xScale, [x, width + x]);
}
};
handleSelectionHandleDrag = (
xScale: Scale,
fixedX: number,
- updateFunc: (xScale: Scale, xArray: Array<number>) => void,
+ xDim: Array<number>,
handleDirection: string,
checkDelta?: boolean
) => (e: Event, data: DraggableData) => {
if (!checkDelta || data.deltaX) {
- updateFunc(xScale, handleDirection === 'right' ? [fixedX, data.x] : [data.x, fixedX]);
+ const x = Math.max(xDim[0], Math.min(data.x, xDim[1]));
+ this.handleZoomUpdate(xScale, handleDirection === 'right' ? [fixedX, x] : [x, fixedX]);
}
};
- handleNewZoomDragStart = (e: Event, data: DraggableData) =>
- this.setState({ newZoomStart: data.x - data.node.getBoundingClientRect().left });
+ handleNewZoomDragStart = (xDim: Array<number>) => (e: Event, data: DraggableData) =>
+ this.setState({
+ newZoomStart: Math.round(
+ Math.max(xDim[0], Math.min(data.x - data.node.getBoundingClientRect().left, xDim[1]))
+ )
+ });
- handleNewZoomDrag = (xScale: Scale) => (e: Event, data: DraggableData) => {
+ handleNewZoomDrag = (xScale: Scale, xDim: Array<number>) => (e: Event, data: DraggableData) => {
const { newZoomStart } = this.state;
if (newZoomStart != null && data.deltaX) {
- this.handleFastZoomUpdate(xScale, [
+ this.handleZoomUpdate(xScale, [
newZoomStart,
- data.x - data.node.getBoundingClientRect().left
+ Math.max(xDim[0], Math.min(data.x - data.node.getBoundingClientRect().left, xDim[1]))
]);
}
};
@@ -135,7 +141,10 @@ export default class ZoomTimeLine extends React.PureComponent {
) => {
const { newZoomStart } = this.state;
if (newZoomStart != null) {
- const x = data.x - data.node.getBoundingClientRect().left;
+ const x = Math.max(
+ xDim[0],
+ Math.min(data.x - data.node.getBoundingClientRect().left, xDim[1])
+ );
this.handleZoomUpdate(xScale, newZoomStart === x ? xDim : [newZoomStart, x]);
this.setState({ newZoomStart: null });
}
@@ -143,24 +152,17 @@ export default class ZoomTimeLine extends React.PureComponent {
handleZoomUpdate = (xScale: Scale, xArray: Array<number>) => {
const xRange = xScale.range();
- const xStart = min(xArray);
- const xEnd = max(xArray);
- const startDate = xStart > xRange[0] ? xScale.invert(xStart) : null;
- const endDate = xEnd < xRange[xRange.length - 1] ? xScale.invert(xEnd) : null;
+ const startDate = xArray[0] > xRange[0] && xArray[0] < xRange[xRange.length - 1]
+ ? xScale.invert(xArray[0])
+ : null;
+ const endDate = xArray[1] > xRange[0] && xArray[1] < xRange[xRange.length - 1]
+ ? xScale.invert(xArray[1])
+ : null;
if (this.props.startDate !== startDate || this.props.endDate !== endDate) {
this.props.updateZoom(startDate, endDate);
}
};
- handleFastZoomUpdate = (xScale: Scale, xArray: Array<number>) => {
- const xRange = xScale.range();
- const startDate = xArray[0] > xRange[0] ? xScale.invert(xArray[0]) : null;
- const endDate = xArray[1] < xRange[xRange.length - 1] ? xScale.invert(xArray[1]) : null;
- if (this.props.startDate !== startDate || this.props.endDate !== endDate) {
- this.props.updateZoomFast(startDate, endDate);
- }
- };
-
renderBaseLine = (xScale: Scale, yScale: Scale) => {
return (
<line
@@ -252,7 +254,7 @@ export default class ZoomTimeLine extends React.PureComponent {
};
renderZoomHandle = (
- opts: {
+ options: {
xScale: Scale,
xPos: number,
fixedPos: number,
@@ -263,26 +265,26 @@ export default class ZoomTimeLine extends React.PureComponent {
) => (
<Draggable
axis="x"
- bounds={{ left: opts.xDim[0], right: opts.xDim[1] }}
- position={{ x: opts.xPos, y: 0 }}
+ bounds={{ left: options.xDim[0], right: options.xDim[1] }}
+ position={{ x: options.xPos, y: 0 }}
onDrag={this.handleSelectionHandleDrag(
- opts.xScale,
- opts.fixedPos,
- this.handleFastZoomUpdate,
- opts.direction,
+ options.xScale,
+ options.fixedPos,
+ options.xDim,
+ options.direction,
true
)}
onStop={this.handleSelectionHandleDrag(
- opts.xScale,
- opts.fixedPos,
- this.handleZoomUpdate,
- opts.direction
+ options.xScale,
+ options.fixedPos,
+ options.xDim,
+ options.direction
)}>
<rect
className="zoom-selection-handle"
x={-3}
- y={opts.yDim[1]}
- height={opts.yDim[0] - opts.yDim[1]}
+ y={options.yDim[1]}
+ height={options.yDim[0] - options.yDim[1]}
width={6}
/>
</Draggable>
@@ -296,12 +298,13 @@ export default class ZoomTimeLine extends React.PureComponent {
const startX = Math.round(this.props.startDate ? xScale(this.props.startDate) : xDim[0]);
const endX = Math.round(this.props.endDate ? xScale(this.props.endDate) : xDim[1]);
const xArray = sortBy([startX, endX]);
+ const zoomBoxWidth = xArray[1] - xArray[0];
const showZoomArea = this.state.newZoomStart == null || this.state.newZoomStart === startX;
return (
<g className="chart-zoom">
<DraggableCore
- onStart={this.handleNewZoomDragStart}
- onDrag={this.handleNewZoomDrag(xScale)}
+ onStart={this.handleNewZoomDragStart(xDim)}
+ onDrag={this.handleNewZoomDrag(xScale, xDim)}
onStop={this.handleNewZoomDragEnd(xScale, xDim)}>
<rect
className="zoom-overlay"
@@ -314,16 +317,16 @@ export default class ZoomTimeLine extends React.PureComponent {
{showZoomArea &&
<Draggable
axis="x"
- bounds={{ left: xDim[0], right: xDim[1] - xArray[1] + xArray[0] }}
+ bounds={{ left: xDim[0], right: Math.floor(xDim[1] - zoomBoxWidth) }}
position={{ x: xArray[0], y: 0 }}
- onDrag={this.handleSelectionDrag(xScale, this.handleFastZoomUpdate, true)}
- onStop={this.handleSelectionDrag(xScale, this.handleZoomUpdate)}>
+ onDrag={this.handleSelectionDrag(xScale, zoomBoxWidth, xDim, true)}
+ onStop={this.handleSelectionDrag(xScale, zoomBoxWidth, xDim)}>
<rect
className="zoom-selection"
x={0}
- y={yDim[1]}
+ y={yDim[1] + 1}
height={yDim[0] - yDim[1]}
- width={xArray[1] - xArray[0]}
+ width={zoomBoxWidth}
/>
</Draggable>}
{showZoomArea &&
diff --git a/server/sonar-web/src/main/less/components/graphics.less b/server/sonar-web/src/main/less/components/graphics.less
index 7aef4947c40..916fea38a74 100644
--- a/server/sonar-web/src/main/less/components/graphics.less
+++ b/server/sonar-web/src/main/less/components/graphics.less
@@ -284,6 +284,12 @@
text-anchor: middle;
}
+.chart-wheel-zoom-overlay {
+ fill: none;
+ stroke: none;
+ pointer-events: all;
+}
+
.chart-zoom {
.zoom-overlay{