You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Histogram.tsx 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import { max } from 'd3-array';
  21. import { scaleBand, ScaleBand, scaleLinear, ScaleLinear } from 'd3-scale';
  22. import * as React from 'react';
  23. import Tooltip from '../controls/Tooltip';
  24. import './BarChart.css';
  25. import './Histogram.css';
  26. interface Props {
  27. alignTicks?: boolean;
  28. bars: number[];
  29. height: number;
  30. padding?: [number, number, number, number];
  31. yTicks?: string[];
  32. yTooltips?: string[];
  33. yValues?: string[];
  34. width: number;
  35. }
  36. const BAR_HEIGHT = 10;
  37. const DEFAULT_PADDING = [10, 10, 10, 10];
  38. type XScale = ScaleLinear<number, number>;
  39. type YScale = ScaleBand<number>;
  40. export default class Histogram extends React.PureComponent<Props> {
  41. renderBar(d: number, index: number, xScale: XScale, yScale: YScale) {
  42. const { alignTicks, padding = DEFAULT_PADDING } = this.props;
  43. const width = Math.round(xScale(d)) + /* minimum bar width */ 1;
  44. const x = xScale.range()[0] + (alignTicks ? padding[3] : 0);
  45. const y = Math.round(yScale(index)! + yScale.bandwidth() / 2);
  46. return <rect className="bar-chart-bar" height={BAR_HEIGHT} width={width} x={x} y={y} />;
  47. }
  48. renderValue(d: number, index: number, xScale: XScale, yScale: YScale) {
  49. const { alignTicks, padding = DEFAULT_PADDING, yValues } = this.props;
  50. const value = yValues && yValues[index];
  51. if (!value) {
  52. return null;
  53. }
  54. const x = xScale(d) + (alignTicks ? padding[3] : 0);
  55. const y = Math.round(yScale(index)! + yScale.bandwidth() / 2 + BAR_HEIGHT / 2);
  56. return (
  57. <Tooltip overlay={this.props.yTooltips && this.props.yTooltips[index]}>
  58. <text className="bar-chart-tick histogram-value" dx="1em" dy="0.3em" x={x} y={y}>
  59. {value}
  60. </text>
  61. </Tooltip>
  62. );
  63. }
  64. renderTick(index: number, xScale: XScale, yScale: YScale) {
  65. const { alignTicks, yTicks } = this.props;
  66. const tick = yTicks && yTicks[index];
  67. if (!tick) {
  68. return null;
  69. }
  70. const x = xScale.range()[0];
  71. const y = Math.round(yScale(index)! + yScale.bandwidth() / 2 + BAR_HEIGHT / 2);
  72. const historyTickClass = alignTicks ? 'histogram-tick-start' : 'histogram-tick';
  73. return (
  74. <text
  75. className={'bar-chart-tick ' + historyTickClass}
  76. dx={alignTicks ? 0 : '-1em'}
  77. dy="0.3em"
  78. x={x}
  79. y={y}>
  80. {tick}
  81. </text>
  82. );
  83. }
  84. renderBars(xScale: XScale, yScale: YScale) {
  85. return (
  86. <g>
  87. {this.props.bars.map((d, index) => {
  88. return (
  89. <g key={index}>
  90. {this.renderBar(d, index, xScale, yScale)}
  91. {this.renderValue(d, index, xScale, yScale)}
  92. {this.renderTick(index, xScale, yScale)}
  93. </g>
  94. );
  95. })}
  96. </g>
  97. );
  98. }
  99. render() {
  100. const { bars, width, height, padding = DEFAULT_PADDING } = this.props;
  101. const availableWidth = width - padding[1] - padding[3];
  102. const xScale: XScale = scaleLinear()
  103. .domain([0, max(bars)!])
  104. .range([0, availableWidth]);
  105. const availableHeight = height - padding[0] - padding[2];
  106. const yScale: YScale = scaleBand<number>()
  107. .domain(bars.map((_, index) => index))
  108. .rangeRound([0, availableHeight]);
  109. return (
  110. <svg className="bar-chart" height={this.props.height} width={this.props.width}>
  111. <g transform={`translate(${this.props.alignTicks ? 4 : padding[3]}, ${padding[0]})`}>
  112. {this.renderBars(xScale, yScale)}
  113. </g>
  114. </svg>
  115. );
  116. }
  117. }