import React from 'react';
+
import { getMeasures } from '../../../api/measures';
+import DrilldownLink from '../helpers/drilldown-link';
const METRICS = [
});
}
- renderCoverage (coverage, lineCoverage, branchCoverage) {
+ renderValue (value, metricKey) {
+ if (value != null) {
+ return <DrilldownLink component={this.props.component.key} metric={metricKey}>
+ {window.formatMeasure(value, 'PERCENT')}
+ </DrilldownLink>;
+ } else {
+ return '—';
+ }
+ }
+
+ renderCoverage (coverage, lineCoverage, branchCoverage, prefix) {
return <table className="data zebra">
<tbody>
<tr>
<td>Coverage</td>
<td className="thin nowrap text-right">
- {formatCoverage(coverage)}
+ {this.renderValue(coverage, prefix + 'coverage')}
</td>
</tr>
<tr>
<td>Line Coverage</td>
<td className="thin nowrap text-right">
- {formatCoverage(lineCoverage)}
+ {this.renderValue(lineCoverage, prefix + 'line_coverage')}
</td>
</tr>
<tr>
<td>Branch Coverage</td>
<td className="thin nowrap text-right">
- {formatCoverage(branchCoverage)}
+ {this.renderValue(branchCoverage, prefix + 'branch_coverage')}
</td>
</tr>
</tbody>
{this.renderCoverage(
this.state.measures['coverage'],
this.state.measures['line_coverage'],
- this.state.measures['branch_coverage'])}
+ this.state.measures['branch_coverage'],
+ '')}
</div>;
}
{this.renderCoverage(
this.state.measures['it_coverage'],
this.state.measures['it_line_coverage'],
- this.state.measures['it_branch_coverage'])}
+ this.state.measures['it_branch_coverage'],
+ 'it_')}
</div>;
}
{this.renderCoverage(
this.state.measures['overall_coverage'],
this.state.measures['overall_line_coverage'],
- this.state.measures['overall_branch_coverage'])}
+ this.state.measures['overall_branch_coverage'],
+ 'overall_')}
</div>;
}
import React from 'react';
-import { getMeasures } from '../../../api/measures';
-import { formatMeasure } from '../formatting';
+
+import { DomainMeasuresList } from '../domain/measures-list';
const METRICS = [
];
-function format (value, metric) {
- return value != null ? formatMeasure(value, metric) : '—';
-}
-
-
export class TestsDetails extends React.Component {
- constructor () {
- super();
- this.state = { measures: {} };
- }
-
- componentDidMount () {
- this.requestDetails();
- }
-
- requestDetails () {
- return getMeasures(this.props.component.key, METRICS).then(measures => {
- this.setState({ measures });
- });
- }
-
render () {
return <div className="overview-domain-section">
- <h2 className="overview-title">Tests Details</h2>
- <table className="data zebra">
- <tbody>
- <tr>
- <td>Tests</td>
- <td className="thin nowrap text-right">
- {format(this.state.measures['tests'], 'tests')}
- </td>
- </tr>
- <tr>
- <td>Skipped Tests</td>
- <td className="thin nowrap text-right">
- {format(this.state.measures['skipped_tests'], 'skipped_tests')}
- </td>
- </tr>
- <tr>
- <td>Test Errors</td>
- <td className="thin nowrap text-right">
- {format(this.state.measures['test_errors'], 'test_errors')}
- </td>
- </tr>
- <tr>
- <td>Test Failures</td>
- <td className="thin nowrap text-right">
- {format(this.state.measures['test_failures'], 'test_failures')}
- </td>
- </tr>
- <tr>
- <td>Tests Execution Time</td>
- <td className="thin nowrap text-right">
- {format(this.state.measures['test_execution_time'], 'test_execution_time')}
- </td>
- </tr>
- <tr>
- <td>Tests Success</td>
- <td className="thin nowrap text-right">
- {format(this.state.measures['test_success_density'], 'test_success_density')}
- </td>
- </tr>
- </tbody>
- </table>
+ <h2 className="overview-title">Tests</h2>
+ <DomainMeasuresList {...this.props} metricsToDisplay={METRICS}/>
</div>;
}
}
tooltip: this.getTooltip(component)
};
});
- let xGrid = this.state.files.map(component => getMeasure(component, this.props.xMetric));
let formatXTick = (tick) => window.formatMeasure(tick, this.state.xMetric.type);
let formatYTick = (tick) => window.formatMeasure(tick, this.state.yMetric.type);
return <BubbleChart items={items}
- xGrid={xGrid}
height={HEIGHT}
padding={[25, 30, 50, 60]}
formatXTick={formatXTick}
import { getMeasures } from '../../../api/measures';
-function format (value, type) {
- return value != null ? window.formatMeasure(value, type) : '—';
-}
-
-
export class DomainMeasuresList extends React.Component {
constructor () {
super();
return _.findWhere(this.props.metrics, { key: metricKey });
}
+ renderValue (value, metricKey, metricType) {
+ if (value != null) {
+ return <DrilldownLink component={this.props.component.key} metric={metricKey}>
+ {window.formatMeasure(value, metricType)}
+ </DrilldownLink>;
+ } else {
+ return '—';
+ }
+ }
+
render () {
let rows = this.props.metricsToDisplay.map(metric => {
let metricObject = this.getMetricObject(metric);
return <tr key={metric}>
<td>{metricObject.name}</td>
<td className="thin nowrap text-right">
- <DrilldownLink component={this.props.component.key} metric={metric}>
- {format(this.state.measures[metric], metricObject.type)}
- </DrilldownLink>
+ {this.renderValue(this.state.measures[metric], metric, metricObject.type)}
</td>
</tr>;
});
</div>;
}
+ renderWhenNoHistoricalData () {
+ return <div className="overview-chart-placeholder" style={{ height: HEIGHT }}>
+ There is no historical data.
+ </div>;
+ }
+
renderLineChart () {
if (this.state.loading) {
return this.renderLoading();
}
+ if (!this.state.events.length || !this.state.snapshots.length) {
+ return this.renderWhenNoHistoricalData();
+ }
+
let events = this.prepareEvents();
let currentMetricType = _.findWhere(this.props.metrics, { key: this.state.currentMetric }).type;
import React from 'react';
-import { getMeasures } from '../../../api/measures';
-import { formatMeasure } from '../formatting';
+
+import { DomainMeasuresList } from '../domain/measures-list';
const METRICS = [
export class DuplicationsDetails extends React.Component {
- constructor () {
- super();
- this.state = { measures: {} };
- }
-
- componentDidMount () {
- this.requestDetails();
- }
-
- requestDetails () {
- return getMeasures(this.props.component.key, METRICS).then(measures => {
- this.setState({ measures });
- });
- }
-
render () {
return <div className="overview-domain-section">
- <h2 className="overview-title">Details</h2>
- <table className="data zebra">
- <tbody>
- <tr>
- <td>Duplications</td>
- <td className="thin nowrap text-right">
- {formatMeasure(this.state.measures['duplicated_lines_density'], 'duplicated_lines_density')}
- </td>
- </tr>
- <tr>
- <td>Blocks</td>
- <td className="thin nowrap text-right">
- {formatMeasure(this.state.measures['duplicated_blocks'], 'duplicated_blocks')}
- </td>
- </tr>
- <tr>
- <td>Files</td>
- <td className="thin nowrap text-right">
- {formatMeasure(this.state.measures['duplicated_files'], 'duplicated_files')}
- </td>
- </tr>
- <tr>
- <td>Lines</td>
- <td className="thin nowrap text-right">
- {formatMeasure(this.state.measures['duplicated_lines'], 'duplicated_lines')}
- </td>
- </tr>
- </tbody>
- </table>
+ <h2 className="overview-title">Duplications</h2>
+ <DomainMeasuresList {...this.props} metricsToDisplay={METRICS}/>
</div>;
}
}
import d3 from 'd3';
import React from 'react';
-export class BarChart extends React.Component {
- constructor (props) {
- super();
- this.state = { width: props.width, height: props.height };
- }
+import { ResizeMixin } from './mixins/resize-mixin';
+import { TooltipsMixin } from './mixins/tooltips-mixin';
- componentDidMount () {
- if (!this.props.width || !this.props.height) {
- this.handleResize();
- window.addEventListener('resize', this.handleResize.bind(this));
- }
- }
+export const BarChart = React.createClass({
+ mixins: [ResizeMixin, TooltipsMixin],
- componentWillUnmount () {
- if (!this.props.width || !this.props.height) {
- window.removeEventListener('resize', this.handleResize.bind(this));
- }
- }
+ propTypes: {
+ data: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
+ xTicks: React.PropTypes.arrayOf(React.PropTypes.any),
+ xValues: React.PropTypes.arrayOf(React.PropTypes.any),
+ height: React.PropTypes.number,
+ padding: React.PropTypes.arrayOf(React.PropTypes.number),
+ barsWidth: React.PropTypes.number
+ },
- handleResize () {
- let boundingClientRect = React.findDOMNode(this).parentNode.getBoundingClientRect();
- let newWidth = this.props.width || boundingClientRect.width;
- let newHeight = this.props.height || boundingClientRect.height;
- this.setState({ width: newWidth, height: newHeight });
- }
+ getDefaultProps() {
+ return {
+ xTicks: [],
+ xValues: [],
+ padding: [10, 10, 10, 10],
+ barsWidth: 40
+ };
+ },
+
+ getInitialState () {
+ return { width: this.props.width, height: this.props.height };
+ },
renderXTicks (xScale, yScale) {
if (!this.props.xTicks.length) {
return <text key={index} className="bar-chart-tick" x={x} y={y} dy="1.5em">{tick}</text>;
});
return <g>{ticks}</g>;
- }
+ },
renderXValues (xScale, yScale) {
if (!this.props.xValues.length) {
return <text key={index} className="bar-chart-tick" x={x} y={y} dy="-1em">{value}</text>;
});
return <g>{ticks}</g>;
- }
+ },
renderBars (xScale, yScale) {
let bars = this.props.data.map((d, index) => {
x={x} y={y} width={this.props.barsWidth} height={height}/>;
});
return <g>{bars}</g>;
- }
+ },
render () {
if (!this.state.width || !this.state.height) {
</g>
</svg>;
}
-}
-
-BarChart.defaultProps = {
- xTicks: [],
- xValues: [],
- padding: [10, 10, 10, 10],
- barsWidth: 40
-};
-
-BarChart.propTypes = {
- data: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
- xTicks: React.PropTypes.arrayOf(React.PropTypes.any),
- xValues: React.PropTypes.arrayOf(React.PropTypes.any),
- padding: React.PropTypes.arrayOf(React.PropTypes.number),
- barsWidth: React.PropTypes.number
-};
+});
-import $ from 'jquery';
import d3 from 'd3';
import React from 'react';
-export class Bubble extends React.Component {
+import { ResizeMixin } from './mixins/resize-mixin';
+import { TooltipsMixin } from './mixins/tooltips-mixin';
+
+
+export const Bubble = React.createClass({
+ propTypes: {
+ x: React.PropTypes.number.isRequired,
+ y: React.PropTypes.number.isRequired,
+ r: React.PropTypes.number.isRequired,
+ tooltip: React.PropTypes.string,
+ link: React.PropTypes.string
+ },
+
handleClick () {
if (this.props.link) {
window.location = this.props.link;
}
- }
+ },
render () {
let tooltipAttrs = {};
'title': this.props.tooltip
};
}
- return <circle onClick={this.handleClick.bind(this)} className="bubble-chart-bubble"
+ return <circle onClick={this.handleClick} className="bubble-chart-bubble"
r={this.props.r} {...tooltipAttrs}
transform={`translate(${this.props.x}, ${this.props.y})`}/>;
}
-}
-
-
-export class BubbleChart extends React.Component {
- constructor (props) {
- super();
- this.state = { width: props.width, height: props.height };
- }
-
- componentDidMount () {
- if (!this.props.width || !this.props.height) {
- this.handleResize();
- window.addEventListener('resize', this.handleResize.bind(this));
- }
- this.initTooltips();
- }
-
- componentDidUpdate () {
- this.initTooltips();
- }
-
- componentWillUnmount () {
- if (!this.props.width || !this.props.height) {
- window.removeEventListener('resize', this.handleResize.bind(this));
- }
- }
-
- handleResize () {
- let boundingClientRect = React.findDOMNode(this).parentNode.getBoundingClientRect();
- let newWidth = this.props.width || boundingClientRect.width;
- let newHeight = this.props.height || boundingClientRect.height;
- this.setState({ width: newWidth, height: newHeight });
- }
-
- initTooltips () {
- $('[data-toggle="tooltip"]', React.findDOMNode(this))
- .tooltip({ container: 'body', placement: 'bottom', html: true });
- }
+});
+
+
+export const BubbleChart = React.createClass({
+ mixins: [ResizeMixin, TooltipsMixin],
+
+ propTypes: {
+ items: React.PropTypes.arrayOf(React.PropTypes.object),
+ sizeRange: React.PropTypes.arrayOf(React.PropTypes.number),
+ displayXGrid: React.PropTypes.bool,
+ displayXTicks: React.PropTypes.bool,
+ displayYGrid: React.PropTypes.bool,
+ displayYTicks: React.PropTypes.bool,
+ height: React.PropTypes.number,
+ padding: React.PropTypes.arrayOf(React.PropTypes.number),
+ formatXTick: React.PropTypes.func,
+ formatYTick: React.PropTypes.func
+ },
+
+ getDefaultProps() {
+ return {
+ sizeRange: [5, 45],
+ displayXGrid: true,
+ displayYGrid: true,
+ displayXTicks: true,
+ displayYTicks: true,
+ padding: [10, 10, 10, 10],
+ formatXTick: d => d,
+ formatYTick: d => d
+ };
+ },
+
+ getInitialState() {
+ return { width: this.props.width, height: this.props.height };
+ },
getXRange (xScale, sizeScale, availableWidth) {
var minX = d3.min(this.props.items, d => xScale(d.x) - sizeScale(d.size)),
dMinX = minX < 0 ? xScale.range()[0] - minX : xScale.range()[0],
dMaxX = maxX > xScale.range()[1] ? maxX - xScale.range()[1] : 0;
return [dMinX, availableWidth - dMaxX];
- }
+ },
getYRange (yScale, sizeScale, availableHeight) {
var minY = d3.min(this.props.items, d => yScale(d.y) - sizeScale(d.size)),
dMinY = minY < 0 ? yScale.range()[1] - minY : yScale.range()[1],
dMaxY = maxY > yScale.range()[0] ? maxY - yScale.range()[0] : 0;
return [availableHeight - dMaxY, dMinY];
- }
+ },
renderXGrid (xScale, yScale) {
if (!this.props.displayXGrid) {
});
return <g ref="xGrid">{lines}</g>;
- }
+ },
renderYGrid (xScale, yScale) {
if (!this.props.displayYGrid) {
});
return <g ref="yGrid">{lines}</g>;
- }
+ },
renderXTicks (xScale, yScale) {
if (!this.props.displayXTicks) {
});
return <g>{ticks}</g>;
- }
+ },
renderYTicks (xScale, yScale) {
if (!this.props.displayYTicks) {
});
return <g>{ticks}</g>;
- }
+ },
render () {
if (!this.state.width || !this.state.height) {
let availableHeight = this.state.height - this.props.padding[0] - this.props.padding[2];
let xScale = d3.scale.linear()
- .domain([0, d3.max(this.props.items, d => d.x)])
- .range([0, availableWidth])
- .nice();
+ .domain([0, d3.max(this.props.items, d => d.x)])
+ .range([0, availableWidth])
+ .nice();
let yScale = d3.scale.linear()
- .domain([0, d3.max(this.props.items, d => d.y)])
- .range([availableHeight, 0])
- .nice();
+ .domain([0, d3.max(this.props.items, d => d.y)])
+ .range([availableHeight, 0])
+ .nice();
let sizeScale = d3.scale.linear()
- .domain([0, d3.max(this.props.items, d => d.size)])
- .range(this.props.sizeRange);
+ .domain([0, d3.max(this.props.items, d => d.size)])
+ .range(this.props.sizeRange);
xScale.range(this.getXRange(xScale, sizeScale, availableWidth));
yScale.range(this.getYRange(yScale, sizeScale, availableHeight));
let bubbles = this.props.items
- .map((item, index) => {
- return <Bubble key={index}
- tooltip={item.tooltip}
- link={item.link}
- x={xScale(item.x)} y={yScale(item.y)} r={sizeScale(item.size)}/>;
- });
+ .map((item, index) => {
+ return <Bubble key={index}
+ tooltip={item.tooltip}
+ link={item.link}
+ x={xScale(item.x)} y={yScale(item.y)} r={sizeScale(item.size)}/>;
+ });
return <svg className="bubble-chart" width={this.state.width} height={this.state.height}>
<g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}>
</g>
</svg>;
}
-}
-
-BubbleChart.defaultProps = {
- sizeRange: [5, 45],
- displayXGrid: true,
- displayYGrid: true,
- displayXTicks: true,
- displayYTicks: true,
- tooltips: [],
- padding: [10, 10, 10, 10],
- formatXTick: d => d,
- formatYTick: d => d
-};
-
-BubbleChart.propTypes = {
- sizeRange: React.PropTypes.arrayOf(React.PropTypes.number),
- displayXGrid: React.PropTypes.bool,
- displayYGrid: React.PropTypes.bool,
- padding: React.PropTypes.arrayOf(React.PropTypes.number),
- formatXTick: React.PropTypes.func,
- formatYTick: React.PropTypes.func
-};
+});
import d3 from 'd3';
import React from 'react';
-export class LineChart extends React.Component {
- constructor (props) {
- super();
- this.state = { width: props.width, height: props.height };
- }
-
- componentDidMount () {
- if (!this.props.width || !this.props.height) {
- this.handleResize();
- window.addEventListener('resize', this.handleResize.bind(this));
- }
- }
-
- componentWillUnmount () {
- if (!this.props.width || !this.props.height) {
- window.removeEventListener('resize', this.handleResize.bind(this));
- }
- }
-
- handleResize () {
- let boundingClientRect = React.findDOMNode(this).parentNode.getBoundingClientRect();
- let newWidth = this.props.width || boundingClientRect.width;
- let newHeight = this.props.height || boundingClientRect.height;
- this.setState({ width: newWidth, height: newHeight });
- }
+import { ResizeMixin } from './mixins/resize-mixin';
+import { TooltipsMixin } from './mixins/tooltips-mixin';
+
+
+export const LineChart = React.createClass({
+ mixins: [ResizeMixin, TooltipsMixin],
+
+ propTypes: {
+ data: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
+ xTicks: React.PropTypes.arrayOf(React.PropTypes.any),
+ xValues: React.PropTypes.arrayOf(React.PropTypes.any),
+ padding: React.PropTypes.arrayOf(React.PropTypes.number),
+ backdropConstraints: React.PropTypes.arrayOf(React.PropTypes.number),
+ displayBackdrop: React.PropTypes.bool,
+ displayPoints: React.PropTypes.bool,
+ displayVerticalGrid: React.PropTypes.bool,
+ height: React.PropTypes.number,
+ interpolate: React.PropTypes.string
+ },
+
+ getDefaultProps() {
+ return {
+ displayBackdrop: true,
+ displayPoints: true,
+ displayVerticalGrid: true,
+ xTicks: [],
+ xValues: [],
+ padding: [10, 10, 10, 10],
+ interpolate: 'basis'
+ };
+ },
+
+ getInitialState() {
+ return { width: this.props.width, height: this.props.height };
+ },
renderBackdrop (xScale, yScale) {
if (!this.props.displayBackdrop) {
}
let area = d3.svg.area()
- .x(d => xScale(d.x))
- .y0(yScale.range()[0])
- .y1(d => yScale(d.y))
- .interpolate(this.props.interpolate);
+ .x(d => xScale(d.x))
+ .y0(yScale.range()[0])
+ .y1(d => yScale(d.y))
+ .interpolate(this.props.interpolate);
let data = this.props.data;
if (this.props.backdropConstraints) {
// TODO extract styling
return <path d={area(data)} fill="#4b9fd5" fillOpacity="0.2"/>;
- }
+ },
renderPoints (xScale, yScale) {
if (!this.props.displayPoints) {
return <circle key={index} className="line-chart-point" r="3" cx={x} cy={y}/>;
});
return <g>{points}</g>;
- }
+ },
renderVerticalGrid (xScale, yScale) {
if (!this.props.displayVerticalGrid) {
return <line key={index} className="line-chart-grid" x1={x} x2={x} y1={y1} y2={y2}/>;
});
return <g>{lines}</g>;
- }
+ },
renderXTicks (xScale, yScale) {
if (!this.props.xTicks.length) {
return <text key={index} className="line-chart-tick" x={x} y={y} dy="1.5em">{tick}</text>;
});
return <g>{ticks}</g>;
- }
+ },
renderXValues (xScale, yScale) {
if (!this.props.xValues.length) {
return <text key={index} className="line-chart-tick" x={x} y={y} dy="-1em">{value}</text>;
});
return <g>{ticks}</g>;
- }
+ },
renderLine (xScale, yScale) {
let path = d3.svg.line()
- .x(d => xScale(d.x))
- .y(d => yScale(d.y))
- .interpolate(this.props.interpolate);
+ .x(d => xScale(d.x))
+ .y(d => yScale(d.y))
+ .interpolate(this.props.interpolate);
return <path className="line-chart-path" d={path(this.props.data)}/>;
- }
+ },
render () {
if (!this.state.width || !this.state.height) {
let maxY = d3.max(this.props.data, d => d.y);
let xScale = d3.scale.linear()
- .domain(d3.extent(this.props.data, d => d.x))
- .range([0, availableWidth]);
+ .domain(d3.extent(this.props.data, d => d.x))
+ .range([0, availableWidth]);
let yScale = d3.scale.linear()
- .domain([0, maxY])
- .range([availableHeight, 0]);
+ .domain([0, maxY])
+ .range([availableHeight, 0]);
return <svg className="line-chart" width={this.state.width} height={this.state.height}>
<g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}>
</g>
</svg>;
}
-}
-
-LineChart.defaultProps = {
- displayBackdrop: true,
- displayPoints: true,
- displayVerticalGrid: true,
- xTicks: [],
- xValues: [],
- padding: [10, 10, 10, 10],
- interpolate: 'basis'
-};
-
-LineChart.propTypes = {
- data: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
- xTicks: React.PropTypes.arrayOf(React.PropTypes.any),
- xValues: React.PropTypes.arrayOf(React.PropTypes.any),
- padding: React.PropTypes.arrayOf(React.PropTypes.number),
- backdropConstraints: React.PropTypes.arrayOf(React.PropTypes.number)
-};
+});
--- /dev/null
+import React from 'react';
+
+export const ResizeMixin = {
+ componentDidMount () {
+ if (this.isResizable()) {
+ this.handleResize();
+ window.addEventListener('resize', this.handleResize);
+ }
+ },
+
+ componentWillUnmount () {
+ if (this.isResizable()) {
+ window.removeEventListener('resize', this.handleResize);
+ }
+ },
+
+ handleResize () {
+ let boundingClientRect = React.findDOMNode(this).parentNode.getBoundingClientRect();
+ let newWidth = this.props.width || boundingClientRect.width;
+ let newHeight = this.props.height || boundingClientRect.height;
+ this.setState({ width: newWidth, height: newHeight });
+ },
+
+ isResizable() {
+ return !this.props.width || !this.props.height;
+ }
+};
--- /dev/null
+import $ from 'jquery';
+import React from 'react';
+
+export const TooltipsMixin = {
+ componentDidMount () {
+ this.initTooltips();
+ },
+
+ componentDidUpdate () {
+ this.initTooltips();
+ },
+
+ initTooltips () {
+ $('[data-toggle="tooltip"]', React.findDOMNode(this))
+ .tooltip({ container: 'body', placement: 'bottom', html: true });
+ }
+};
+++ /dev/null
-import d3 from 'd3';
-import React from 'react';
-
-export class Timeline extends React.Component {
- constructor (props) {
- super();
- this.state = { width: props.width, height: props.height };
- }
-
- componentDidMount () {
- if (!this.props.width || !this.props.height) {
- this.handleResize();
- window.addEventListener('resize', this.handleResize.bind(this));
- }
- }
-
- componentWillUnmount () {
- if (!this.props.width || !this.props.height) {
- window.removeEventListener('resize', this.handleResize.bind(this));
- }
- }
-
- handleResize () {
- let boundingClientRect = React.findDOMNode(this).parentNode.getBoundingClientRect();
- let newWidth = this.props.width || boundingClientRect.width;
- let newHeight = this.props.height || boundingClientRect.height;
- this.setState({ width: newWidth, height: newHeight });
- }
-
- renderBackdrop (xScale, yScale, maxY) {
- if (!this.props.displayBackdrop) {
- return null;
- }
-
- let area = d3.svg.area()
- .x(d => xScale(d.date))
- .y0(maxY)
- .y1(d => yScale(d.value))
- .interpolate(this.props.interpolate);
-
- // TODO extract styling
- return <path d={area(this.props.snapshots)} fill="#4b9fd5" fillOpacity="0.2"/>;
- }
-
- renderLine (xScale, yScale) {
- let path = d3.svg.line()
- .x(d => xScale(d.date))
- .y(d => yScale(d.value))
- .interpolate(this.props.interpolate);
-
- // TODO extract styling
- return <path d={path(this.props.snapshots)} stroke="#4b9fd5" strokeWidth={this.props.lineWidth} fill="none"/>;
- }
-
- render () {
- if (!this.state.width || !this.state.height) {
- return <div/>;
- }
-
- let maxY = d3.max(this.props.snapshots, d => d.value);
- let xScale = d3.time.scale()
- .domain(d3.extent(this.props.snapshots, d => d.date))
- .range([0, this.state.width - this.props.lineWidth]);
- let yScale = d3.scale.linear()
- .domain([0, maxY])
- .range([this.state.height, 0]);
-
- return <svg width={this.state.width} height={this.state.height}>
- <g transform={`translate(${this.props.lineWidth / 2}, ${this.props.lineWidth / 2})`}>
- {this.renderBackdrop(xScale, yScale, maxY)}
- {this.renderLine(xScale, yScale)}
- </g>
- </svg>;
- }
-}
-
-Timeline.defaultProps = {
- lineWidth: 2,
- displayBackdrop: true,
- interpolate: 'basis'
-};
-
-Timeline.propTypes = {
- snapshots: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
-};
-import $ from 'jquery';
import _ from 'underscore';
import d3 from 'd3';
import React from 'react';
+import { ResizeMixin } from './mixins/resize-mixin';
+import { TooltipsMixin } from './mixins/tooltips-mixin';
+
const SIZE_SCALE = d3.scale.linear()
- .domain([3, 15])
- .range([11, 18])
- .clamp(true);
+ .domain([3, 15])
+ .range([11, 18])
+ .clamp(true);
function mostCommitPrefix (strings) {
}
-export class TreemapRect extends React.Component {
+export const TreemapRect = React.createClass({
+ propTypes: {
+ x: React.PropTypes.number.isRequired,
+ y: React.PropTypes.number.isRequired,
+ width: React.PropTypes.number.isRequired,
+ height: React.PropTypes.number.isRequired,
+ fill: React.PropTypes.string.isRequired,
+ label: React.PropTypes.string.isRequired,
+ prefix: React.PropTypes.string
+ },
+
render () {
let tooltipAttrs = {};
if (this.props.tooltip) {
style={{ maxWidth: this.props.width }}/>
</div>;
}
-}
+});
-TreemapRect.propTypes = {
- x: React.PropTypes.number.isRequired,
- y: React.PropTypes.number.isRequired,
- width: React.PropTypes.number.isRequired,
- height: React.PropTypes.number.isRequired,
- fill: React.PropTypes.string.isRequired
-};
+export const Treemap = React.createClass({
+ mixins: [ResizeMixin, TooltipsMixin],
-export class Treemap extends React.Component {
- constructor (props) {
- super();
- this.state = { width: props.width, height: props.height };
- }
+ propTypes: {
+ items: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
+ height: React.PropTypes.number
+ },
- componentDidMount () {
- if (!this.props.width || !this.props.height) {
- this.handleResize();
- window.addEventListener('resize', this.handleResize.bind(this));
- }
- this.initTooltips();
- }
-
- componentDidUpdate () {
- this.initTooltips();
- }
-
- componentWillUnmount () {
- if (!this.props.width || !this.props.height) {
- window.removeEventListener('resize', this.handleResize.bind(this));
- }
- }
-
- initTooltips () {
- $('[data-toggle="tooltip"]', React.findDOMNode(this))
- .tooltip({ container: 'body', placement: 'top', html: true });
- }
-
- handleResize () {
- let boundingClientRect = React.findDOMNode(this).parentNode.getBoundingClientRect();
- let newWidth = this.props.width || boundingClientRect.width;
- let newHeight = this.props.height || boundingClientRect.height;
- this.setState({ width: newWidth, height: newHeight });
- }
+ getInitialState() {
+ return { width: this.props.width, height: this.props.height };
+ },
render () {
if (!this.state.width || !this.state.height || !this.props.items.length) {
}
let sizeScale = d3.scale.linear()
- .domain([0, d3.max(this.props.items, d => d.size)])
- .range([5, 45]);
+ .domain([0, d3.max(this.props.items, d => d.size)])
+ .range([5, 45]);
let treemap = d3.layout.treemap()
- .round(true)
- .value(d => sizeScale(d.size))
- .size([this.state.width, 360]);
+ .round(true)
+ .value(d => sizeScale(d.size))
+ .size([this.state.width, 360]);
let nodes = treemap
.nodes({ children: this.props.items })
.filter(d => !d.children);
</div>
</div>;
}
-}
-
-Treemap.propTypes = {
- items: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
-};
+});
-import $ from 'jquery';
import _ from 'underscore';
import React from 'react';
-export class Word extends React.Component {
+import { TooltipsMixin } from './mixins/tooltips-mixin';
+
+export const Word = React.createClass({
+ propTypes: {
+ size: React.PropTypes.number.isRequired,
+ text: React.PropTypes.string.isRequired,
+ tooltip: React.PropTypes.string,
+ link: React.PropTypes.string.isRequired
+ },
+
render () {
let tooltipAttrs = {};
if (this.props.tooltip) {
}
return <a {...tooltipAttrs} style={{ fontSize: this.props.size }} href={this.props.link}>{this.props.text}</a>;
}
-}
+});
-export class WordCloud extends React.Component {
- componentDidMount () {
- this.initTooltips();
- }
+export const WordCloud = React.createClass({
+ mixins: [TooltipsMixin],
- componentDidUpdate () {
- this.initTooltips();
- }
+ propTypes: {
+ items: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
+ sizeRange: React.PropTypes.arrayOf(React.PropTypes.number)
+ },
- initTooltips () {
- $('[data-toggle="tooltip"]', React.findDOMNode(this))
- .tooltip({ container: 'body', placement: 'bottom', html: true });
- }
+ getDefaultProps() {
+ return {
+ sizeRange: [10, 24]
+ };
+ },
render () {
let len = this.props.items.length;
});
let sizeScale = d3.scale.linear()
- .domain([0, d3.max(this.props.items, d => d.size)])
- .range(this.props.sizeRange);
+ .domain([0, d3.max(this.props.items, d => d.size)])
+ .range(this.props.sizeRange);
let words = sortedItems
.map((item, index) => <Word key={index}
text={item.text}
tooltip={item.tooltip}/>);
return <div className="word-cloud">{words}</div>;
}
-}
-
-WordCloud.defaultProps = {
- sizeRange: [10, 24]
-};
-
-WordCloud.propTypes = {
- sizeRange: React.PropTypes.arrayOf(React.PropTypes.number)
-};
+});