};
},
+ componentWillMount () {
+ this.handleScan = _.debounce(this.handleScan, 10, true);
+ },
+
componentDidMount () {
Promise.all([
this.requestTimeMachineData(this.state.currentMetric, this.state.comparisonMetric),
return groupByDomain(this.props.metrics);
},
+ handleScan(scanner) {
+ this.setState({ scanner });
+ },
+
renderLoading () {
return <div className="overview-chart-placeholder" style={{ height: HEIGHT }}>
<i className="spinner"/>
formatValue={formatValue}
formatYTick={formatYTick}
leakPeriodDate={this.props.leakPeriodDate}
+ scanner={this.state.scanner}
+ onScan={this.handleScan}
padding={[25, 25, 25, 60]}/>
</div>;
},
+import $ from 'jquery';
import _ from 'underscore';
import d3 from 'd3';
import moment from 'moment';
},
getInitialState() {
- return { width: this.props.width, height: this.props.height };
+ return { width: this.props.width, height: this.props.height, scanner: 0 };
},
getRatingScale(availableHeight) {
}
},
+ handleMouseMove(xScale, e) {
+ const x = e.pageX - this.refs.container.getBoundingClientRect().left;
+ const scanner = this.props.data
+ .reduce((previousValue, currentValue) => {
+ return Math.abs(xScale(currentValue.x) - x) < Math.abs(xScale(previousValue.x) - x) ?
+ currentValue : previousValue;
+ }, _.first(this.props.data));
+ this.props.onScan(scanner.x);
+ },
+
renderHorizontalGrid (xScale, yScale) {
let hasTicks = typeof yScale.ticks === 'function';
let ticks = hasTicks ? yScale.ticks(4) : yScale.domain();
return <g>{ticks}</g>;
},
+ renderBackdrop (xScale, yScale) {
+ let opts = {
+ x: _.first(xScale.range()),
+ y: _.last(yScale.range()),
+ width: _.last(xScale.range()) - _.first(xScale.range()),
+ height: _.first(yScale.range()) - _.last(yScale.range()),
+ style: {
+ opacity: 0
+ }
+ };
+ return <rect {...opts} ref="container" onMouseMove={this.handleMouseMove.bind(this, xScale)}/>;
+ },
+
+ renderScanner (xScale, yScale) {
+ if (this.props.scanner == null) {
+ return null;
+ }
+ let snapshotIndex = this.props.data.findIndex(snapshot => {
+ return snapshot.x.getTime() === this.props.scanner.getTime();
+ });
+ $(this.refs[`snapshot${snapshotIndex}`]).tooltip('show');
+ return <line
+ style={{ opacity: 0 }}
+ className="line-chart-grid"
+ x1={xScale(this.props.scanner)}
+ y1={_.first(yScale.range())}
+ x2={xScale(this.props.scanner)}
+ y2={_.last(yScale.range())}/>;
+ },
+
renderLeak (xScale, yScale) {
if (!this.props.leakPeriodDate) {
return null;
return <path className="line-chart-path" d={path(this.props.data)}/>;
},
+ renderTooltips(xScale, yScale) {
+ let points = this.props.data
+ .map(snapshot => {
+ let event = this.props.events.find(e => snapshot.x.getTime() === e.date.getTime());
+ return _.extend(snapshot, { event });
+ })
+ .map((snapshot, index) => {
+ let tooltipLines = [];
+ if (snapshot.event) {
+ tooltipLines.push(`<span class="nowrap">${snapshot.event.version}</span>`);
+ }
+ tooltipLines.push(`<span class="nowrap">${moment(snapshot.x).format('LL')}</span>`);
+ tooltipLines.push(`<span class="nowrap">${snapshot.y ? this.props.formatValue(snapshot.y) : '—'}</span>`);
+ let tooltip = tooltipLines.join('<br>');
+ return <circle key={index} ref={`snapshot${index}`} style={{ opacity: 0 }}
+ r="4" cx={xScale(snapshot.x)} cy={yScale(snapshot.y)}
+ data-toggle="tooltip" title={tooltip}/>;
+ });
+ return <g>{points}</g>;
+ },
+
renderEvents(xScale, yScale) {
let points = this.props.events
.map(event => {
{this.renderHorizontalGrid(xScale, yScale)}
{this.renderTicks(xScale, yScale)}
{this.renderLine(xScale, yScale)}
+ {this.renderTooltips(xScale, yScale)}
{this.renderEvents(xScale, yScale)}
+ {this.renderScanner(xScale, yScale)}
+ {this.renderBackdrop(xScale, yScale)}
</g>
</svg>;
}
{ x: new Date(2015, 0, 4), y: 'WARN' }
];
- let timeline = <Timeline width={100} height={100} data={DATA} metricType="LEVEL" events={[]}
+ let timeline = <Timeline width={100}
+ height={100}
+ data={DATA}
+ metricType="LEVEL"
+ events={[]}
+ formatValue={FORMAT}
formatYTick={FORMAT}/>;
let output = TestUtils.renderIntoDocument(timeline);
let ticks = TestUtils.scryRenderedDOMComponentsWithClass(output, 'line-chart-tick-x');
{ x: new Date(2015, 0, 4), y: 4 }
];
- let timeline = <Timeline width={100} height={100} data={DATA} metricType="RATING" events={[]}
+ let timeline = <Timeline width={100}
+ height={100}
+ data={DATA}
+ metricType="RATING"
+ events={[]}
+ formatValue={FORMAT}
formatYTick={FORMAT}/>;
let output = TestUtils.renderIntoDocument(timeline);
let ticks = TestUtils.scryRenderedDOMComponentsWithClass(output, 'line-chart-tick-x');
});
it('should display the zero Y tick if all values are zero', function () {
- let timeline = <Timeline width={100} height={100} data={ZERO_DATA} events={[]} formatYTick={FORMAT}/>;
+ let timeline = <Timeline width={100}
+ height={100}
+ data={ZERO_DATA}
+ events={[]}
+ formatValue={FORMAT}
+ formatYTick={FORMAT}/>;
let output = TestUtils.renderIntoDocument(timeline);
let tick = TestUtils.findRenderedDOMComponentWithClass(output, 'line-chart-tick-x');
expect(tick.textContent).to.equal('0');
});
it('should display the zero Y tick if all values are undefined', function () {
- let timeline = <Timeline width={100} height={100} data={NULL_DATA} events={[]} formatYTick={FORMAT}/>;
+ let timeline = <Timeline width={100}
+ height={100}
+ data={NULL_DATA}
+ events={[]}
+ formatValue={FORMAT}
+ formatYTick={FORMAT}/>;
let output = TestUtils.renderIntoDocument(timeline);
let tick = TestUtils.findRenderedDOMComponentWithClass(output, 'line-chart-tick-x');
expect(tick.textContent).to.equal('0');