@@ -46,6 +46,8 @@ | |||
}, | |||
"devDependencies": { | |||
"@types/classnames": "2.2.0", | |||
"@types/d3-array": "1.2.1", | |||
"@types/d3-scale": "1.0.10", | |||
"@types/date-fns": "2.6.0", | |||
"@types/enzyme": "2.8.6", | |||
"@types/escape-html": "0.0.19", |
@@ -140,6 +140,7 @@ export default class MeasureHeader extends React.PureComponent { | |||
<LanguageDistributionContainer | |||
alignTicks={true} | |||
distribution={secondaryMeasure.value} | |||
width={260} | |||
/> | |||
</div> | |||
)} |
@@ -57,7 +57,7 @@ export default class MetaSize extends React.PureComponent { | |||
return languageDistribution ? ( | |||
<div id="overview-language-distribution" className="overview-meta-size-lang-dist"> | |||
<LanguageDistributionContainer distribution={languageDistribution.value} /> | |||
<LanguageDistributionContainer distribution={languageDistribution.value} width={160} /> | |||
</div> | |||
) : null; | |||
}; |
@@ -60,8 +60,8 @@ export default function Summary({ component, measures }: Props) { | |||
</ul> | |||
{nclocDistribution && ( | |||
<div className="huge-spacer-top" style={{ width: 260 }}> | |||
<LanguageDistributionContainer distribution={nclocDistribution} /> | |||
<div className="huge-spacer-top"> | |||
<LanguageDistributionContainer distribution={nclocDistribution} width={260} /> | |||
</div> | |||
)} | |||
</section> |
@@ -82,14 +82,10 @@ exports[`renders 1`] = ` | |||
</ul> | |||
<div | |||
className="huge-spacer-top" | |||
style={ | |||
Object { | |||
"width": 260, | |||
} | |||
} | |||
> | |||
<Connect(LanguageDistribution) | |||
distribution="java=13;js=17" | |||
width={260} | |||
/> | |||
</div> | |||
</section> |
@@ -0,0 +1,146 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { max } from 'd3-array'; | |||
import { scaleLinear, scaleBand, ScaleLinear, ScaleBand } from 'd3-scale'; | |||
import Tooltip from '../controls/Tooltip'; | |||
interface Props { | |||
alignTicks?: boolean; | |||
bars: number[]; | |||
height: number; | |||
padding?: [number, number, number, number]; | |||
yTicks?: string[]; | |||
yTooltips?: string[]; | |||
yValues?: string[]; | |||
width: number; | |||
} | |||
const BAR_HEIGHT = 10; | |||
const DEFAULT_PADDING = [10, 10, 10, 10]; | |||
type XScale = ScaleLinear<number, number>; | |||
type YScale = ScaleBand<number>; | |||
export default class Histogram extends React.PureComponent<Props> { | |||
wrapWithTooltip(element: React.ReactNode, index: number) { | |||
const tooltip = this.props.yTooltips && this.props.yTooltips[index]; | |||
return tooltip ? ( | |||
<Tooltip key={index} overlay={tooltip} placement="top"> | |||
{element} | |||
</Tooltip> | |||
) : ( | |||
element | |||
); | |||
} | |||
renderBar(d: number, index: number, xScale: XScale, yScale: YScale) { | |||
const { alignTicks, padding = DEFAULT_PADDING } = this.props; | |||
const width = Math.round(xScale(d)) + /* minimum bar width */ 1; | |||
const x = xScale.range()[0] + (alignTicks ? padding[3] : 0); | |||
const y = Math.round(yScale(index)! + yScale.bandwidth() / 2); | |||
return <rect className="bar-chart-bar" x={x} y={y} width={width} height={BAR_HEIGHT} />; | |||
} | |||
renderValue(d: number, index: number, xScale: XScale, yScale: YScale) { | |||
const { alignTicks, padding = DEFAULT_PADDING, yValues } = this.props; | |||
const value = yValues && yValues[index]; | |||
if (!value) { | |||
return null; | |||
} | |||
const x = xScale(d) + (alignTicks ? padding[3] : 0); | |||
const y = Math.round(yScale(index)! + yScale.bandwidth() / 2 + BAR_HEIGHT / 2); | |||
return this.wrapWithTooltip( | |||
<text className="bar-chart-tick histogram-value" x={x} y={y} dx="1em" dy="0.3em"> | |||
{value} | |||
</text>, | |||
index | |||
); | |||
} | |||
renderTick(index: number, xScale: XScale, yScale: YScale) { | |||
const { alignTicks, yTicks } = this.props; | |||
const tick = yTicks && yTicks[index]; | |||
if (!tick) { | |||
return null; | |||
} | |||
const x = xScale.range()[0]; | |||
const y = Math.round(yScale(index)! + yScale.bandwidth() / 2 + BAR_HEIGHT / 2); | |||
const historyTickClass = alignTicks ? 'histogram-tick-start' : 'histogram-tick'; | |||
return ( | |||
<text | |||
className={'bar-chart-tick ' + historyTickClass} | |||
x={x} | |||
y={y} | |||
dx={alignTicks ? 0 : '-1em'} | |||
dy="0.3em"> | |||
{tick} | |||
</text> | |||
); | |||
} | |||
renderBars(xScale: XScale, yScale: YScale) { | |||
return ( | |||
<g> | |||
{this.props.bars.map((d, index) => { | |||
return ( | |||
<g key={index}> | |||
{this.renderBar(d, index, xScale, yScale)} | |||
{this.renderValue(d, index, xScale, yScale)} | |||
{this.renderTick(index, xScale, yScale)} | |||
</g> | |||
); | |||
})} | |||
</g> | |||
); | |||
} | |||
render() { | |||
const { bars, width, height, padding = DEFAULT_PADDING } = this.props; | |||
const availableWidth = width - padding[1] - padding[3]; | |||
const xScale: XScale = scaleLinear() | |||
.domain([0, max(bars)!]) | |||
.range([0, availableWidth]); | |||
const availableHeight = height - padding[0] - padding[2]; | |||
const yScale: YScale = scaleBand<number>() | |||
.domain(bars.map((_, index) => index)) | |||
.rangeRound([0, availableHeight]); | |||
return ( | |||
<svg className="bar-chart" width={this.props.width} height={this.props.height}> | |||
<g transform={`translate(${this.props.alignTicks ? 4 : padding[3]}, ${padding[0]})`}> | |||
{this.renderBars(xScale, yScale)} | |||
</g> | |||
</svg> | |||
); | |||
} | |||
} |
@@ -19,7 +19,7 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { find, sortBy } from 'lodash'; | |||
import { Histogram } from './histogram'; | |||
import Histogram from './Histogram'; | |||
import { formatMeasure } from '../../helpers/measures'; | |||
import { Language } from '../../api/languages'; | |||
import { translate } from '../../helpers/l10n'; | |||
@@ -28,37 +28,44 @@ interface Props { | |||
alignTicks?: boolean; | |||
distribution: string; | |||
languages?: Language[]; | |||
width: number; | |||
} | |||
export default function LanguageDistribution(props: Props) { | |||
let data = props.distribution.split(';').map((point, index) => { | |||
let distribution = props.distribution.split(';').map(point => { | |||
const tokens = point.split('='); | |||
return { x: parseInt(tokens[1], 10), y: index, value: tokens[0] }; | |||
return { language: tokens[0], lines: parseInt(tokens[1], 10) }; | |||
}); | |||
data = sortBy(data, d => -d.x); | |||
distribution = sortBy(distribution, d => -d.lines); | |||
const yTicks = data.map(point => getLanguageName(point.value)).map(cutLanguageName); | |||
const yValues = data.map(point => formatMeasure(point.x, 'SHORT_INT')); | |||
const data = distribution.map(d => d.lines); | |||
const yTicks = distribution.map(d => getLanguageName(d.language)).map(cutLanguageName); | |||
const yTooltips = distribution.map(d => (d.lines > 1000 ? formatMeasure(d.lines, 'INT') : '')); | |||
const yValues = distribution.map(d => formatMeasure(d.lines, 'SHORT_INT')); | |||
return ( | |||
<Histogram | |||
alignTicks={props.alignTicks} | |||
data={data} | |||
bars={data} | |||
height={distribution.length * 25} | |||
padding={[0, 60, 0, 80]} | |||
yTicks={yTicks} | |||
yTooltips={yTooltips} | |||
yValues={yValues} | |||
barsWidth={10} | |||
height={data.length * 25} | |||
padding={[0, 60, 0, 80]} | |||
width={props.width} | |||
/> | |||
); | |||
function getLanguageName(langKey: string) { | |||
if (langKey === '<null>') { | |||
return translate('unknown'); | |||
} | |||
const lang = find(props.languages, { key: langKey }); | |||
return lang ? lang.name : translate('unknown'); | |||
return lang ? lang.name : langKey; | |||
} | |||
} | |||
function cutLanguageName(name: string) { | |||
return name.length > 10 ? `${name.substr(0, 7)}...` : name; | |||
} | |||
function cutLanguageName(name: string) { | |||
return name.length > 10 ? `${name.substr(0, 7)}...` : name; | |||
} |
@@ -0,0 +1,68 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import Histogram from '../Histogram'; | |||
it('renders', () => { | |||
expect(shallow(<Histogram bars={[100, 75, 150]} height={75} width={100} />)).toMatchSnapshot(); | |||
}); | |||
it('renders with yValues', () => { | |||
expect( | |||
shallow( | |||
<Histogram | |||
bars={[100, 75, 150]} | |||
height={75} | |||
yValues={['100.0', '75.0', '150.0']} | |||
width={100} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('renders with yValues and yTicks', () => { | |||
expect( | |||
shallow( | |||
<Histogram | |||
bars={[100, 75, 150]} | |||
height={75} | |||
yTicks={['a', 'b', 'c']} | |||
yValues={['100.0', '75.0', '150.0']} | |||
width={100} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('renders with yValues, yTicks and yTooltips', () => { | |||
expect( | |||
shallow( | |||
<Histogram | |||
bars={[100, 75, 150]} | |||
height={75} | |||
yTicks={['a', 'b', 'c']} | |||
yTooltips={['a - 100', 'b - 75', 'c - 150']} | |||
yValues={['100.0', '75.0', '150.0']} | |||
width={100} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,34 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import LanguageDistribution from '../LanguageDistribution'; | |||
it('renders', () => { | |||
expect( | |||
shallow( | |||
<LanguageDistribution | |||
distribution="java=1734;js=845;cpp=73;<null>=15" | |||
languages={[{ key: 'java', name: 'Java' }, { key: 'js', name: 'JavaScript' }]} | |||
width={100} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,319 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<svg | |||
className="bar-chart" | |||
height={75} | |||
width={100} | |||
> | |||
<g | |||
transform="translate(10, 10)" | |||
> | |||
<g> | |||
<g> | |||
<rect | |||
className="bar-chart-bar" | |||
height={10} | |||
width={54} | |||
x={0} | |||
y={10} | |||
/> | |||
</g> | |||
<g> | |||
<rect | |||
className="bar-chart-bar" | |||
height={10} | |||
width={41} | |||
x={0} | |||
y={28} | |||
/> | |||
</g> | |||
<g> | |||
<rect | |||
className="bar-chart-bar" | |||
height={10} | |||
width={81} | |||
x={0} | |||
y={46} | |||
/> | |||
</g> | |||
</g> | |||
</g> | |||
</svg> | |||
`; | |||
exports[`renders with yValues 1`] = ` | |||
<svg | |||
className="bar-chart" | |||
height={75} | |||
width={100} | |||
> | |||
<g | |||
transform="translate(10, 10)" | |||
> | |||
<g> | |||
<g> | |||
<rect | |||
className="bar-chart-bar" | |||
height={10} | |||
width={54} | |||
x={0} | |||
y={10} | |||
/> | |||
<text | |||
className="bar-chart-tick histogram-value" | |||
dx="1em" | |||
dy="0.3em" | |||
x={53.33333333333333} | |||
y={15} | |||
> | |||
100.0 | |||
</text> | |||
</g> | |||
<g> | |||
<rect | |||
className="bar-chart-bar" | |||
height={10} | |||
width={41} | |||
x={0} | |||
y={28} | |||
/> | |||
<text | |||
className="bar-chart-tick histogram-value" | |||
dx="1em" | |||
dy="0.3em" | |||
x={40} | |||
y={33} | |||
> | |||
75.0 | |||
</text> | |||
</g> | |||
<g> | |||
<rect | |||
className="bar-chart-bar" | |||
height={10} | |||
width={81} | |||
x={0} | |||
y={46} | |||
/> | |||
<text | |||
className="bar-chart-tick histogram-value" | |||
dx="1em" | |||
dy="0.3em" | |||
x={80} | |||
y={51} | |||
> | |||
150.0 | |||
</text> | |||
</g> | |||
</g> | |||
</g> | |||
</svg> | |||
`; | |||
exports[`renders with yValues and yTicks 1`] = ` | |||
<svg | |||
className="bar-chart" | |||
height={75} | |||
width={100} | |||
> | |||
<g | |||
transform="translate(10, 10)" | |||
> | |||
<g> | |||
<g> | |||
<rect | |||
className="bar-chart-bar" | |||
height={10} | |||
width={54} | |||
x={0} | |||
y={10} | |||
/> | |||
<text | |||
className="bar-chart-tick histogram-value" | |||
dx="1em" | |||
dy="0.3em" | |||
x={53.33333333333333} | |||
y={15} | |||
> | |||
100.0 | |||
</text> | |||
<text | |||
className="bar-chart-tick histogram-tick" | |||
dx="-1em" | |||
dy="0.3em" | |||
x={0} | |||
y={15} | |||
> | |||
a | |||
</text> | |||
</g> | |||
<g> | |||
<rect | |||
className="bar-chart-bar" | |||
height={10} | |||
width={41} | |||
x={0} | |||
y={28} | |||
/> | |||
<text | |||
className="bar-chart-tick histogram-value" | |||
dx="1em" | |||
dy="0.3em" | |||
x={40} | |||
y={33} | |||
> | |||
75.0 | |||
</text> | |||
<text | |||
className="bar-chart-tick histogram-tick" | |||
dx="-1em" | |||
dy="0.3em" | |||
x={0} | |||
y={33} | |||
> | |||
b | |||
</text> | |||
</g> | |||
<g> | |||
<rect | |||
className="bar-chart-bar" | |||
height={10} | |||
width={81} | |||
x={0} | |||
y={46} | |||
/> | |||
<text | |||
className="bar-chart-tick histogram-value" | |||
dx="1em" | |||
dy="0.3em" | |||
x={80} | |||
y={51} | |||
> | |||
150.0 | |||
</text> | |||
<text | |||
className="bar-chart-tick histogram-tick" | |||
dx="-1em" | |||
dy="0.3em" | |||
x={0} | |||
y={51} | |||
> | |||
c | |||
</text> | |||
</g> | |||
</g> | |||
</g> | |||
</svg> | |||
`; | |||
exports[`renders with yValues, yTicks and yTooltips 1`] = ` | |||
<svg | |||
className="bar-chart" | |||
height={75} | |||
width={100} | |||
> | |||
<g | |||
transform="translate(10, 10)" | |||
> | |||
<g> | |||
<g> | |||
<rect | |||
className="bar-chart-bar" | |||
height={10} | |||
width={54} | |||
x={0} | |||
y={10} | |||
/> | |||
<Tooltip | |||
overlay="a - 100" | |||
placement="top" | |||
> | |||
<text | |||
className="bar-chart-tick histogram-value" | |||
dx="1em" | |||
dy="0.3em" | |||
x={53.33333333333333} | |||
y={15} | |||
> | |||
100.0 | |||
</text> | |||
</Tooltip> | |||
<text | |||
className="bar-chart-tick histogram-tick" | |||
dx="-1em" | |||
dy="0.3em" | |||
x={0} | |||
y={15} | |||
> | |||
a | |||
</text> | |||
</g> | |||
<g> | |||
<rect | |||
className="bar-chart-bar" | |||
height={10} | |||
width={41} | |||
x={0} | |||
y={28} | |||
/> | |||
<Tooltip | |||
overlay="b - 75" | |||
placement="top" | |||
> | |||
<text | |||
className="bar-chart-tick histogram-value" | |||
dx="1em" | |||
dy="0.3em" | |||
x={40} | |||
y={33} | |||
> | |||
75.0 | |||
</text> | |||
</Tooltip> | |||
<text | |||
className="bar-chart-tick histogram-tick" | |||
dx="-1em" | |||
dy="0.3em" | |||
x={0} | |||
y={33} | |||
> | |||
b | |||
</text> | |||
</g> | |||
<g> | |||
<rect | |||
className="bar-chart-bar" | |||
height={10} | |||
width={81} | |||
x={0} | |||
y={46} | |||
/> | |||
<Tooltip | |||
overlay="c - 150" | |||
placement="top" | |||
> | |||
<text | |||
className="bar-chart-tick histogram-value" | |||
dx="1em" | |||
dy="0.3em" | |||
x={80} | |||
y={51} | |||
> | |||
150.0 | |||
</text> | |||
</Tooltip> | |||
<text | |||
className="bar-chart-tick histogram-tick" | |||
dx="-1em" | |||
dy="0.3em" | |||
x={0} | |||
y={51} | |||
> | |||
c | |||
</text> | |||
</g> | |||
</g> | |||
</g> | |||
</svg> | |||
`; |
@@ -0,0 +1,48 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders 1`] = ` | |||
<Histogram | |||
bars={ | |||
Array [ | |||
1734, | |||
845, | |||
73, | |||
15, | |||
] | |||
} | |||
height={100} | |||
padding={ | |||
Array [ | |||
0, | |||
60, | |||
0, | |||
80, | |||
] | |||
} | |||
width={100} | |||
yTicks={ | |||
Array [ | |||
"Java", | |||
"JavaScript", | |||
"cpp", | |||
"unknown", | |||
] | |||
} | |||
yTooltips={ | |||
Array [ | |||
"1,734", | |||
"", | |||
"", | |||
"", | |||
] | |||
} | |||
yValues={ | |||
Array [ | |||
"1.7k", | |||
"845", | |||
"73", | |||
"15", | |||
] | |||
} | |||
/> | |||
`; |
@@ -1,166 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import createReactClass from 'create-react-class'; | |||
import PropTypes from 'prop-types'; | |||
import { max } from 'd3-array'; | |||
import { scaleLinear, scaleBand } from 'd3-scale'; | |||
import { ResizeMixin } from './../mixins/resize-mixin'; | |||
import { TooltipsMixin } from './../mixins/tooltips-mixin'; | |||
export const Histogram = createReactClass({ | |||
displayName: 'Histogram', | |||
propTypes: { | |||
alignTicks: PropTypes.bool, | |||
data: PropTypes.arrayOf(PropTypes.object).isRequired, | |||
yTicks: PropTypes.arrayOf(PropTypes.any), | |||
yValues: PropTypes.arrayOf(PropTypes.any), | |||
width: PropTypes.number, | |||
height: PropTypes.number, | |||
padding: PropTypes.arrayOf(PropTypes.number), | |||
barsHeight: PropTypes.number, | |||
onBarClick: PropTypes.func | |||
}, | |||
mixins: [ResizeMixin, TooltipsMixin], | |||
getDefaultProps() { | |||
return { | |||
xTicks: [], | |||
xValues: [], | |||
padding: [10, 10, 10, 10], | |||
barsHeight: 10 | |||
}; | |||
}, | |||
getInitialState() { | |||
return { width: this.props.width, height: this.props.height }; | |||
}, | |||
handleClick(point) { | |||
this.props.onBarClick(point); | |||
}, | |||
renderTicks(xScale, yScale) { | |||
if (!this.props.yTicks.length) { | |||
return null; | |||
} | |||
const ticks = this.props.yTicks.map((tick, index) => { | |||
const point = this.props.data[index]; | |||
const x = xScale.range()[0]; | |||
const y = Math.round(yScale(point.y) + yScale.bandwidth() / 2 + this.props.barsHeight / 2); | |||
const label = tick.label ? tick.label : tick; | |||
const tooltip = tick.tooltip ? tick.tooltip : null; | |||
const historyTickClass = this.props.alignTicks ? 'histogram-tick-start' : 'histogram-tick'; | |||
return ( | |||
<text | |||
key={index} | |||
className={'bar-chart-tick ' + historyTickClass} | |||
onClick={this.props.onBarClick && this.handleClick.bind(this, point)} | |||
style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }} | |||
data-title={tooltip} | |||
data-toggle={tooltip ? 'tooltip' : null} | |||
x={x} | |||
y={y} | |||
dx={this.props.alignTicks ? 0 : '-1em'} | |||
dy="0.3em"> | |||
{label} | |||
</text> | |||
); | |||
}); | |||
return <g>{ticks}</g>; | |||
}, | |||
renderValues(xScale, yScale) { | |||
if (!this.props.yValues.length) { | |||
return null; | |||
} | |||
const ticks = this.props.yValues.map((value, index) => { | |||
const point = this.props.data[index]; | |||
const x = xScale(point.x) + (this.props.alignTicks ? this.props.padding[3] : 0); | |||
const y = Math.round(yScale(point.y) + yScale.bandwidth() / 2 + this.props.barsHeight / 2); | |||
return ( | |||
<text | |||
key={index} | |||
onClick={this.props.onBarClick && this.handleClick.bind(this, point)} | |||
className="bar-chart-tick histogram-value" | |||
style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }} | |||
x={x} | |||
y={y} | |||
dx="1em" | |||
dy="0.3em"> | |||
{value} | |||
</text> | |||
); | |||
}); | |||
return <g>{ticks}</g>; | |||
}, | |||
renderBars(xScale, yScale) { | |||
const bars = this.props.data.map((d, index) => { | |||
const width = Math.round(xScale(d.x)) + /* minimum bar width */ 1; | |||
const x = xScale.range()[0] + (this.props.alignTicks ? this.props.padding[3] : 0); | |||
const y = Math.round(yScale(d.y) + yScale.bandwidth() / 2); | |||
return ( | |||
<rect | |||
key={index} | |||
className="bar-chart-bar" | |||
onClick={this.props.onBarClick && this.handleClick.bind(this, d)} | |||
style={{ cursor: this.props.onBarClick ? 'pointer' : 'default' }} | |||
x={x} | |||
y={y} | |||
width={width} | |||
height={this.props.barsHeight} | |||
/> | |||
); | |||
}); | |||
return <g>{bars}</g>; | |||
}, | |||
render() { | |||
if (!this.state.width || !this.state.height) { | |||
return <div />; | |||
} | |||
const availableWidth = this.state.width - this.props.padding[1] - this.props.padding[3]; | |||
const availableHeight = this.state.height - this.props.padding[0] - this.props.padding[2]; | |||
const maxX = max(this.props.data, d => d.x); | |||
const xScale = scaleLinear() | |||
.domain([0, maxX]) | |||
.range([0, availableWidth]); | |||
const yScale = scaleBand() | |||
.domain(this.props.data.map(d => d.y)) | |||
.rangeRound([0, availableHeight]); | |||
return ( | |||
<svg className="bar-chart" width={this.state.width} height={this.state.height}> | |||
<g | |||
transform={`translate(${this.props.alignTicks ? 4 : this.props.padding[3]}, ${this.props | |||
.padding[0]})`}> | |||
{this.renderTicks(xScale, yScale)} | |||
{this.renderValues(xScale, yScale)} | |||
{this.renderBars(xScale, yScale)} | |||
</g> | |||
</svg> | |||
); | |||
} | |||
}); |
@@ -17,6 +17,20 @@ | |||
version "2.2.0" | |||
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.0.tgz#f2312039e780bdf89d7d4102a26ec11de5ec58aa" | |||
"@types/d3-array@1.2.1": | |||
version "1.2.1" | |||
resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-1.2.1.tgz#e489605208d46a1c9d980d2e5772fa9c75d9ec65" | |||
"@types/d3-scale@1.0.10": | |||
version "1.0.10" | |||
resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-1.0.10.tgz#8c5c1dca54a159eed042b46719dbb3bdb7e8c842" | |||
dependencies: | |||
"@types/d3-time" "*" | |||
"@types/d3-time@*": | |||
version "1.0.7" | |||
resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-1.0.7.tgz#4266d7c9be15fa81256a88d1d052d61cd8dc572c" | |||
"@types/date-fns@2.6.0": | |||
version "2.6.0" | |||
resolved "https://registry.yarnpkg.com/@types/date-fns/-/date-fns-2.6.0.tgz#b062ca46562002909be0c63a6467ed173136acc1" |