1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/src/renderer/components/chart/chart.tsx
Jari Kolehmainen 2d0609ed24
Check source files for license header (#2763)
* check source files for license header

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* tweak

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* add license header to all relevant source files

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
2021-05-12 18:33:26 +03:00

238 lines
6.5 KiB
TypeScript

/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import "./chart.scss";
import React from "react";
import ChartJS from "chart.js";
import { remove } from "lodash";
import { cssNames } from "../../utils";
import { StatusBrick } from "../status-brick";
import { Badge } from "../badge";
export interface ChartData extends ChartJS.ChartData {
datasets?: ChartDataSets[];
}
export interface ChartDataSets extends ChartJS.ChartDataSets {
id?: string;
tooltip?: string;
}
export interface ChartProps {
data: ChartData;
options?: ChartJS.ChartOptions; // Passed to ChartJS instance
width?: number | string;
height?: number | string;
type?: ChartKind;
showChart?: boolean; // Possible to show legend only if false
showLegend?: boolean;
legendPosition?: "bottom";
legendColors?: string[]; // Hex colors for each of the labels in data object
plugins?: any[];
redraw?: boolean; // If true - recreate chart instance with no animation
title?: string;
className?: string;
}
export enum ChartKind {
PIE = "pie",
BAR = "bar",
LINE = "line",
DOUGHNUT = "doughnut"
}
const defaultProps: Partial<ChartProps> = {
type: ChartKind.DOUGHNUT,
options: {},
showChart: true,
showLegend: true,
legendPosition: "bottom",
plugins: [],
redraw: false
};
export class Chart extends React.Component<ChartProps> {
static defaultProps = defaultProps as object;
private canvas = React.createRef<HTMLCanvasElement>();
private chart: ChartJS;
// ChartJS adds _meta field to any data object passed to it.
// We clone new data prop into currentChartData to compare props and prevProps
private currentChartData: ChartData;
componentDidMount() {
const { showChart } = this.props;
if (!showChart) return;
this.renderChart();
}
componentDidUpdate() {
const { showChart, redraw } = this.props;
if (redraw) {
this.chart.destroy();
this.renderChart();
return;
}
if (showChart) {
if (!this.chart) this.renderChart();
else this.updateChart();
}
}
memoizeDataProps() {
const { data } = this.props;
this.currentChartData = {
...data,
datasets: data.datasets && data.datasets.map(set => {
return {
...set
};
})
};
}
updateChart() {
const { options } = this.props;
if (!this.chart) return;
this.chart.options = ChartJS.helpers.configMerge(this.chart.options, options);
this.memoizeDataProps();
const datasets: ChartDataSets[] = this.chart.config.data.datasets;
const nextDatasets: ChartDataSets[] = this.currentChartData.datasets || [];
// Remove stale datasets if they're not available in nextDatasets
if (datasets.length > nextDatasets.length) {
const sets = [...datasets];
sets.forEach(set => {
if (!nextDatasets.find(next => next.id === set.id)) {
remove(datasets, (item => item.id === set.id));
}
});
}
// Mutating inner chart datasets to enable seamless transitions
nextDatasets.forEach((next, datasetIndex) => {
const index = datasets.findIndex(set => set.id === next.id);
if (index !== -1) {
datasets[index].data = datasets[index].data.slice(); // "Clean" mobx observables data to use in ChartJS
datasets[index].data.splice(next.data.length);
next.data.forEach((point: any, dataIndex: number) => {
datasets[index].data[dataIndex] = next.data[dataIndex];
});
// Merge other fields
const { data, ...props } = next;
datasets[index] = {
...datasets[index],
...props
};
} else {
datasets[datasetIndex] = next;
}
});
this.chart.update();
}
renderLegend() {
if (!this.props.showLegend) return null;
const { data, legendColors } = this.props;
const { labels, datasets } = data;
const labelElem = (title: string, color: string, tooltip?: string) => (
<Badge
key={title}
className="flex gaps align-center"
label={(
<div>
<StatusBrick style={{ backgroundColor: color }}/>
<span>{title}</span>
</div>
)}
tooltip={tooltip}
/>
);
return (
<div className="legend flex wrap gaps">
{labels && labels.map((label: string, index) => {
const { backgroundColor } = datasets[0] as any;
const color = legendColors ? legendColors[index] : backgroundColor[index];
return labelElem(label, color);
})}
{!labels && datasets.map(({ borderColor, label, tooltip }) =>
labelElem(label, borderColor as any, tooltip)
)}
</div>
);
}
renderChart() {
const { type, options, plugins } = this.props;
this.memoizeDataProps();
this.chart = new ChartJS(this.canvas.current, {
type,
plugins,
options: {
...options,
legend: {
display: false
},
},
data: this.currentChartData,
});
}
render() {
const { width, height, showChart, title, className } = this.props;
return (
<>
<div className={cssNames("Chart", className)}>
{title && <div className="chart-title">{title}</div>}
{showChart &&
<div className="chart-container">
<canvas
ref={this.canvas}
width={width}
height={height}
/>
<div className="chartjs-tooltip flex column"></div>
</div>
}
{this.renderLegend()}
</div>
</>
);
}
}