Browse Source

SONAR-9608 SONAR-9636 Add treemap legend on project measures page

tags/6.6-RC1
Grégoire Aubert 6 years ago
parent
commit
6c3d97c429

+ 35
- 13
server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.js View File

@@ -23,6 +23,8 @@ import { AutoSizer } from 'react-virtualized';
import { scaleLinear, scaleOrdinal } from 'd3-scale';
import QualifierIcon from '../../../components/shared/QualifierIcon';
import TreeMap from '../../../components/charts/TreeMap';
import ColorBoxLegend from '../../../components/charts/ColorBoxLegend';
import ColorGradientLegend from '../../../components/charts/ColorGradientLegend';
import { translate, translateWithParameters, getLocalizedMetricName } from '../../../helpers/l10n';
import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
import { getComponentUrl } from '../../../helpers/urls';
@@ -41,6 +43,8 @@ type State = {
};

const HEIGHT = 500;
const COLORS = ['#00aa00', '#b0d513', '#eabe06', '#ed7d20', '#d4333f'];
const LEVEL_COLORS = ['#d4333f', '#ed7d20', '#00aa00', '#b4b4b4'];

export default class TreeMapView extends React.PureComponent {
props: Props;
@@ -52,7 +56,7 @@ export default class TreeMapView extends React.PureComponent {
}

componentWillReceiveProps(nextProps: Props) {
if (nextProps.components !== this.props.components) {
if (nextProps.components !== this.props.components || nextProps.metric !== this.props.metric) {
this.setState({ treemapItems: this.getTreemapComponents(nextProps) });
}
}
@@ -95,24 +99,15 @@ export default class TreeMapView extends React.PureComponent {
};

getLevelColorScale = () =>
scaleOrdinal()
.domain(['ERROR', 'WARN', 'OK', 'NONE'])
.range(['#d4333f', '#ed7d20', '#00aa00', '#b4b4b4']);
scaleOrdinal().domain(['ERROR', 'WARN', 'OK', 'NONE']).range(LEVEL_COLORS);

getPercentColorScale = (metric: Metric) => {
const color = scaleLinear().domain([0, 25, 50, 75, 100]);
color.range(
metric.direction === 1
? ['#d4333f', '#ed7d20', '#eabe06', '#b0d513', '#00aa00']
: ['#00aa00', '#b0d513', '#eabe06', '#ed7d20', '#d4333f']
);
color.range(metric.direction === 1 ? COLORS.reverse() : COLORS);
return color;
};

getRatingColorScale = () =>
scaleLinear()
.domain([1, 2, 3, 4, 5])
.range(['#00aa00', '#b0d513', '#eabe06', '#ed7d20', '#d4333f']);
getRatingColorScale = () => scaleLinear().domain([1, 2, 3, 4, 5]).range(COLORS);

getColorScale = (metric: Metric) => {
if (metric.type === 'LEVEL') {
@@ -144,6 +139,30 @@ export default class TreeMapView extends React.PureComponent {
);
};

renderLegend() {
const { metric } = this.props;
const colorScale = this.getColorScale(metric);
if (['LEVEL', 'RATING'].includes(metric.type)) {
return (
<ColorBoxLegend
className="measure-details-treemap-legend color-box-full"
colorScale={colorScale}
metricType={metric.type}
/>
);
}
return (
<ColorGradientLegend
className="measure-details-treemap-legend"
colorScale={colorScale}
colorNA="#777"
direction={metric.direction}
height={20}
width={200}
/>
);
}

render() {
return (
<div className="measure-details-treemap">
@@ -160,6 +179,9 @@ export default class TreeMapView extends React.PureComponent {
translate('metric.ncloc.name')
)}
</li>
<li className="pull-right">
{this.renderLegend()}
</li>
</ul>
<AutoSizer>
{({ width }) =>

+ 8
- 0
server/sonar-web/src/main/js/apps/component-measures/style.css View File

@@ -71,6 +71,14 @@
font-size: 12px;
}

.measure-details-treemap-legend {
margin-right: -4px;
}

.measure-details-treemap-legend.color-box-legend {
margin-right: 0;
}

.measure-view-select {
width: 50px;
}

+ 57
- 0
server/sonar-web/src/main/js/components/charts/ColorBoxLegend.js View File

@@ -0,0 +1,57 @@
/*
* 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.
*/
// @flow
import React from 'react';
import classNames from 'classnames';
import { formatMeasure } from '../../helpers/measures';

type Props = {
className?: string,
colorScale: Object,
colorNA?: string,
metricType: string
};

export default function ColorBoxLegend({ className, colorScale, colorNA, metricType }: Props) {
const colorDomain = colorScale.domain();
const colorRange = colorScale.range();
return (
<div className={classNames('color-box-legend', className)}>
{colorDomain.map((value, idx) =>
<div key={value}>
<span className="color-box-legend-rect" style={{ borderColor: colorRange[idx] }}>
<span
className="color-box-legend-rect-inner"
style={{ backgroundColor: colorRange[idx] }}
/>
</span>
{formatMeasure(value, metricType)}
</div>
)}
{colorNA &&
<div>
<span className="color-box-legend-rect" style={{ borderColor: colorNA }}>
<span className="color-box-legend-rect-inner" style={{ backgroundColor: colorNA }} />
</span>
N/A
</div>}
</div>
);
}

+ 96
- 0
server/sonar-web/src/main/js/components/charts/ColorGradientLegend.js View File

@@ -0,0 +1,96 @@
/*
* 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.
*/
// @flow
import React from 'react';

type Props = {
className?: string,
colorScale: Object,
colorNA?: string,
direction?: number,
padding?: Array<number>,
height: number,
width: number
};

const NA_SPACING = 4;

export default function ColorGradientLegend({
className,
colorScale,
colorNA,
direction,
padding = [12, 24, 0, 0],
height,
width
}: Props) {
let colorDomain = colorScale.domain();
let colorRange = colorScale.range();
if (direction !== 1) {
colorDomain = colorDomain.reverse();
colorRange = colorRange.reverse();
}

const lastColorIdx = colorRange.length - 1;
const lastDomainIdx = colorDomain.length - 1;
const widthNoPadding = width - padding[1];
const rectHeight = height - padding[0];
return (
<svg className={className} width={width} height={height}>
<defs>
<linearGradient id="gradient-legend">
{colorRange.map((color, idx) =>
<stop key={idx} offset={idx / lastColorIdx} stopColor={color} />
)}
</linearGradient>
</defs>
<g transform={`translate(${padding[3]}, ${padding[0]})`}>
<rect fill="url(#gradient-legend)" x={0} y={0} height={rectHeight} width={widthNoPadding} />
{colorDomain.map((d, idx) =>
<text
className="gradient-legend-text"
key={idx}
x={widthNoPadding * (idx / lastDomainIdx)}
y={0}
dy="-2px">
{d}
</text>
)}
</g>
{colorNA &&
<g transform={`translate(${widthNoPadding}, ${padding[0]})`}>
<rect
fill={colorNA}
x={NA_SPACING}
y={0}
height={rectHeight}
width={padding[1] - NA_SPACING}
/>
<text
className="gradient-legend-na"
x={NA_SPACING + (padding[1] - NA_SPACING) / 2}
y={0}
dy="-2px">
N/A
</text>
</g>}
</svg>
);
}

+ 3
- 3
server/sonar-web/src/main/js/components/charts/ColorRatingsLegend.js View File

@@ -25,14 +25,14 @@ import { RATING_COLORS } from '../../helpers/constants';

export default function ColorRatingsLegend({ className }: { className?: string }) {
return (
<div className={classNames('color-ratings-legend', className)}>
<div className={classNames('color-box-legend', className)}>
{[1, 2, 3, 4, 5].map(rating =>
<div key={rating}>
<span
className="color-ratings-legend-rect"
className="color-box-legend-rect"
style={{ borderColor: RATING_COLORS[rating - 1] }}>
<span
className="color-ratings-legend-rect-inner"
className="color-box-legend-rect-inner"
style={{ backgroundColor: RATING_COLORS[rating - 1] }}
/>
</span>

+ 28
- 3
server/sonar-web/src/main/less/components/graphics.less View File

@@ -263,7 +263,11 @@
text-anchor: end;
}

.color-ratings-legend {
/*
* Legends
*/

.color-box-legend {
display: flex;
justify-content: center;

@@ -271,7 +275,7 @@
margin-left: 24px;
}

.color-ratings-legend-rect {
.color-box-legend-rect {
display: inline-block;
vertical-align: top;
margin-top: 1px;
@@ -279,12 +283,33 @@
border: 1px solid;
}

.color-ratings-legend-rect-inner {
.color-box-legend-rect-inner {
display: block;
width: 8px;
height: 8px;
opacity: 0.2;
}

&.color-box-full .color-box-legend-rect-inner {
opacity: 1;
}
}

.gradient-legend-text,
.gradient-legend-na {
text-anchor: middle;
fill: @secondFontColor;
font-size: 10px;
}

.gradient-legend-text {
&:first-of-type {
text-anchor: start;
}

&:last-of-type {
text-anchor: end;
}
}

/*

Loading…
Cancel
Save