/* * SonarQube * Copyright (C) 2009-2023 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 { extent, max } from 'd3-array'; import { ScaleLinear, scaleLinear } from 'd3-scale'; import { curveBasis, area as d3Area, line as d3Line } from 'd3-shape'; import * as React from 'react'; import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'; import './LineChart.css'; interface DataPoint { x: number; y?: number; } interface Props { backdropConstraints?: [number, number]; data: DataPoint[]; displayBackdrop?: boolean; displayPoints?: boolean; displayVerticalGrid?: boolean; domain?: [number, number]; height: number; padding?: [number, number, number, number]; width?: number; xTicks?: {}[]; xValues?: {}[]; } export default class LineChart extends React.PureComponent { renderBackdrop(xScale: ScaleLinear, yScale: ScaleLinear) { const { displayBackdrop = true } = this.props; if (!displayBackdrop) { return null; } const area = d3Area() .x((d) => xScale(d.x)) .y0(yScale.range()[0]) .y1((d) => yScale(d.y || 0)) .defined((d) => d.y != null) .curve(curveBasis); let { data } = this.props; if (this.props.backdropConstraints) { const c = this.props.backdropConstraints; data = data.filter((d) => c[0] <= d.x && d.x <= c[1]); } return ; } renderPoints(xScale: ScaleLinear, yScale: ScaleLinear) { const { displayPoints = true } = this.props; if (!displayPoints) { return null; } const points = this.props.data .filter((point) => point.y != null) .map((point, index) => { const x = xScale(point.x); const y = yScale(point.y || 0); // eslint-disable-next-line react/no-array-index-key return ; }); return {points}; } renderVerticalGrid(xScale: ScaleLinear, yScale: ScaleLinear) { const { displayVerticalGrid = true } = this.props; if (!displayVerticalGrid) { return null; } const lines = this.props.data.map((point, index) => { const x = xScale(point.x); const y1 = yScale.range()[0]; const y2 = yScale(point.y || 0); // eslint-disable-next-line react/no-array-index-key return ; }); return {lines}; } renderXTicks(xScale: ScaleLinear, yScale: ScaleLinear) { const { xTicks = [] } = this.props; if (!xTicks.length) { return null; } const ticks = xTicks.map((tick, index) => { const point = this.props.data[index]; const x = xScale(point.x); const y = yScale.range()[0]; return ( // eslint-disable-next-line react/no-array-index-key {tick} ); }); return {ticks}; } renderXValues(xScale: ScaleLinear, yScale: ScaleLinear) { const { xValues = [] } = this.props; if (!xValues.length) { return null; } const ticks = xValues.map((value, index) => { const point = this.props.data[index]; const x = xScale(point.x); const y = yScale(point.y || 0); return ( // eslint-disable-next-line react/no-array-index-key {value} ); }); return {ticks}; } renderLine(xScale: ScaleLinear, yScale: ScaleLinear) { const p = d3Line() .x((d) => xScale(d.x)) .y((d) => yScale(d.y || 0)) .defined((d) => d.y != null) .curve(curveBasis); return ; } renderChart = (width: number) => { const { height, padding = [10, 10, 10, 10] } = this.props; if (!width || !height) { return
; } const availableWidth = width - padding[1] - padding[3]; const availableHeight = height - padding[0] - padding[2]; const xScale = scaleLinear() .domain(extent(this.props.data, (d) => d.x) as [number, number]) .range([0, availableWidth]); const yScale = scaleLinear().range([availableHeight, 0]); if (this.props.domain) { yScale.domain(this.props.domain); } else { const maxY = max(this.props.data, (d) => d.y) as number; yScale.domain([0, maxY]); } return ( {this.renderVerticalGrid(xScale, yScale)} {this.renderBackdrop(xScale, yScale)} {this.renderLine(xScale, yScale)} {this.renderPoints(xScale, yScale)} {this.renderXTicks(xScale, yScale)} {this.renderXValues(xScale, yScale)} ); }; render() { return this.props.width !== undefined ? ( this.renderChart(this.props.width) ) : ( {(size) => this.renderChart(size.width)} ); } }