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 IChartContext { chart: ChartJS; dataIndex?: number; dataset?: ChartDataSet; } const defaultProps: Partial = { timeLabelStep: 10, plugins: [ZebraStripes] }; BarChart.defaultProps = defaultProps; export function BarChart(props: Props) { const { name, data, className, timeLabelStep, plugins, options: customOptions, ...settings } = props; const { textColorPrimary, borderFaintColor, chartStripesColor } = themeStore.activeTheme.colors; const savedName = useRef(); useEffect(() => { savedName.current = props.name; }); const getBarColor = (ctx: IChartContext) => { 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) => { 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.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 => { const now = new Date().getTime() if (new Date(tooltipItems[0].xLabel).getTime() > now) return ""; return `${tooltipItems[0].xLabel}` }, labelColor: ({ datasetIndex }) => { 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) { return } return ( ) } // Default options for all charts containing memory units (network, disk, memory, etc) export const memoryOptions: ChartOptions = { scales: { yAxes: [{ ticks: { callback: value => { if (!value) return 0; return parseFloat(value) < 1 ? value.toFixed(3) : bytesToUnits(parseInt(value)); }, stepSize: 1 } }] }, tooltips: { callbacks: { label: ({ datasetIndex, index }, { datasets }) => { 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 => { if (value == 0) return 0; if (value < 10) return value.toFixed(3); if (value < 100) return value.toFixed(2); return value.toFixed(1); } } }] }, tooltips: { callbacks: { label: ({ datasetIndex, index }, { datasets }) => { const { label, data } = datasets[datasetIndex]; const value = data[index] as ChartPoint; return `${label}: ${parseFloat(value.y as string).toPrecision(2)}`; } } } }