import * as React from "react"; import * as ChartJS from "chart.js"; import { useEffect, useRef } from "react"; import merge from "lodash/merge"; import moment from "moment"; import Color from "color"; import { Chart, ChartDataSet, ChartKind, ChartProps } from "./chart"; import { ChartData, ChartOptions, ChartPoint } from "chart.js"; import { cssNames } from "../../utils"; import { bytesToUnits } from "../../utils"; import { ZebraStripes } from "./zebra-stripes.plugin"; import { themeStore } from "../../theme.store"; import { NoMetrics } from "../resource-metrics/no-metrics"; interface Props extends ChartProps { name?: string; title?: string; timeLabelStep?: number; // Minute labels appearance step } interface ChartContext { chart: ChartJS; dataIndex?: number; dataset?: ChartDataSet; } const defaultProps: Partial = { timeLabelStep: 10, plugins: [ZebraStripes] }; export function BarChart({ name: _name, data, className, timeLabelStep, plugins, options: customOptions, ...settings }: Props): JSX.Element { const { textColorPrimary, borderFaintColor, chartStripesColor } = themeStore.activeTheme.colors; const savedName = useRef(); useEffect(() => { savedName.current = name; }); const getBarColor = (ctx: ChartContext): string => { const { dataset } = ctx; const color = dataset.borderColor; return Color(color).alpha(0.2).string(); }; // Remove empty sets and insert default data const chartData: ChartData = { ...data, datasets: data.datasets .filter(set => set.data.length) .map(item => { return { type: ChartKind.BAR, borderWidth: { top: 3 }, barPercentage: 1, categoryPercentage: 1, ...item }; }) }; const formatTimeLabels = (timestamp: string, index: number): string => { const label = moment(parseInt(timestamp)).format("HH:mm"); const offset = " "; if (index == 0) { return offset + label; } if (index == 60) { return label + offset; } return index % timeLabelStep == 0 ? label : ""; }; const barOptions: ChartOptions = { maintainAspectRatio: false, responsive: true, scales: { xAxes: [{ type: "time", offset: true, gridLines: { display: false, }, stacked: true, ticks: { callback: formatTimeLabels, autoSkip: false, source: "data", backdropColor: "white", fontColor: textColorPrimary, fontSize: 11, maxRotation: 0, minRotation: 0 }, bounds: "data", time: { unit: "minute", displayFormats: { minute: "x" }, parser: (timestamp): moment.Moment => moment.unix(parseInt(timestamp)) } }], yAxes: [{ position: "right", gridLines: { color: borderFaintColor, drawBorder: false, tickMarkLength: 0, zeroLineWidth: 0 }, ticks: { maxTicksLimit: 6, fontColor: textColorPrimary, fontSize: 11, padding: 8, min: 0 } }] }, tooltips: { mode: "index", position: "cursor", callbacks: { title: (tooltipItems): string => { const now = new Date().getTime(); if (new Date(tooltipItems[0].xLabel).getTime() > now) { return ""; } return `${tooltipItems[0].xLabel}`; }, labelColor: ({ datasetIndex }): ChartJS.ChartTooltipLabelColor => { return { borderColor: "darkgray", backgroundColor: chartData.datasets[datasetIndex].borderColor as string }; } } }, animation: { duration: 0 }, elements: { rectangle: { backgroundColor: getBarColor.bind(null) } }, plugins: { ZebraStripes: { stripeColor: chartStripesColor } } }; const options = merge(barOptions, customOptions); if (chartData.datasets.length == 0) { return ; } return ( ); } BarChart.defaultProps = defaultProps; // Default options for all charts containing memory units (network, disk, memory, etc) export const memoryOptions: ChartOptions = { scales: { yAxes: [{ ticks: { callback: (value: number | string): string => { if (typeof value == "string") { const float = parseFloat(value); if (float < 1) { return float.toFixed(3); } return bytesToUnits(parseInt(value)); } return `${value}`; }, stepSize: 1 } }] }, tooltips: { callbacks: { label: ({ datasetIndex, index }, { datasets }): string => { const { label, data } = datasets[datasetIndex]; const value = data[index] as ChartPoint; return `${label}: ${bytesToUnits(parseInt(value.y.toString()), 3)}`; } } } }; // Default options for all charts with cpu units or other decimal numbers export const cpuOptions: ChartOptions = { scales: { yAxes: [{ ticks: { callback: (value: number | string): string => { const float = parseFloat(`${value}`); if (float == 0) { return "0"; } if (float < 10) { return float.toFixed(3); } if (float < 100) { return float.toFixed(2); } return float.toFixed(1); } } }] }, tooltips: { callbacks: { label: ({ datasetIndex, index }, { datasets }): string => { const { label, data } = datasets[datasetIndex]; const value = data[index] as ChartPoint; return `${label}: ${parseFloat(value.y as string).toPrecision(2)}`; } } } };