mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Lens app source code (#119)
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
1 parent
936cbd53d4
commit
1d0815abd2
797 files changed
+73714
-215
No files matched your search
@@ -0,0 +1,42 @@
|
||||
import { default as ChartJS } from "chart.js";
|
||||
import get from "lodash/get";
|
||||
|
||||
const defaultOptions = {
|
||||
interval: 61,
|
||||
coverBars: 3,
|
||||
borderColor: "#44474A",
|
||||
backgroundColor: "#00000033"
|
||||
}
|
||||
|
||||
export const BackgroundBlock = {
|
||||
options: {},
|
||||
|
||||
getOptions(chart: ChartJS) {
|
||||
return get(chart, "options.plugins.BackgroundBlock");
|
||||
},
|
||||
|
||||
afterInit(chart: ChartJS) {
|
||||
this.options = {
|
||||
...defaultOptions,
|
||||
...this.getOptions(chart)
|
||||
}
|
||||
},
|
||||
|
||||
beforeDraw(chart: ChartJS) {
|
||||
if (!chart.chartArea) return;
|
||||
const { interval, coverBars, borderColor, backgroundColor } = this.options;
|
||||
const { ctx, chartArea } = chart;
|
||||
const { left, right, top, bottom } = chartArea;
|
||||
const blockWidth = (right - left) / interval * coverBars;
|
||||
|
||||
ctx.save();
|
||||
ctx.fillStyle = backgroundColor;
|
||||
ctx.strokeStyle = borderColor;
|
||||
ctx.fillRect(right - blockWidth, top, blockWidth, bottom - top);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(right - blockWidth + 1.5, top);
|
||||
ctx.lineTo(right - blockWidth + 1.5, bottom);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
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<Props> = {
|
||||
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<string>();
|
||||
|
||||
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 <NoMetrics/>
|
||||
}
|
||||
return (
|
||||
<Chart
|
||||
className={cssNames("BarChart flex box grow column", className)}
|
||||
type={ChartKind.BAR}
|
||||
data={chartData}
|
||||
options={options}
|
||||
plugins={plugins}
|
||||
{...settings}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// 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)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
.Chart {
|
||||
position: relative;
|
||||
|
||||
&.BarChart {
|
||||
margin-left: -$margin * 2.2;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.legend {
|
||||
.Badge {
|
||||
background: transparent;
|
||||
transition: background-color 250ms;
|
||||
white-space: normal;
|
||||
|
||||
&:hover {
|
||||
background: $colorVague;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.zebra-cover {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
import "./chart.scss";
|
||||
import React, { createRef, PureComponent } from "react";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import remove from "lodash/remove";
|
||||
import * as ChartJS from "chart.js";
|
||||
import { ChartData as ChartDataOrig, ChartDataSets, ChartOptions } from "chart.js";
|
||||
import { StatusBrick } from "../status-brick";
|
||||
import { cssNames } from "../../utils";
|
||||
import { Badge } from "../badge";
|
||||
|
||||
export interface ChartData extends ChartDataOrig {
|
||||
datasets?: ChartDataSet[];
|
||||
}
|
||||
|
||||
export interface ChartDataSet extends ChartDataSets {
|
||||
id?: string;
|
||||
borderWidth?: { top: number } | any;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export interface ChartProps {
|
||||
data: {
|
||||
labels?: Array<string | string[]>;
|
||||
datasets?: ChartDataSet[];
|
||||
};
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
options?: ChartOptions; // Passed to ChartJS instance
|
||||
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 PureComponent<ChartProps> {
|
||||
static defaultProps = defaultProps as object;
|
||||
|
||||
private canvas = 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(prevProps: ChartProps) {
|
||||
const { data, showChart, redraw } = this.props
|
||||
if (redraw) {
|
||||
this.chart.destroy()
|
||||
this.renderChart()
|
||||
return
|
||||
}
|
||||
if (!isEqual(prevProps.data, data) && 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: ChartDataSet[] = this.chart.config.data.datasets
|
||||
const nextDatasets: ChartDataSet[] = 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./chart"
|
||||
export * from "./pie-chart"
|
||||
export * from "./bar-chart"
|
||||
@@ -0,0 +1,18 @@
|
||||
.PieChart {
|
||||
.chart-container {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.legend {
|
||||
margin-top: $margin;
|
||||
flex-direction: column;
|
||||
|
||||
> * {
|
||||
&.Badge:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
margin-bottom: $margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import "./pie-chart.scss";
|
||||
import * as React from "react";
|
||||
import * as ChartJS from "chart.js";
|
||||
import { ChartData, ChartOptions } from "chart.js";
|
||||
import { Chart, ChartProps } from "./chart";
|
||||
import { cssNames } from "../../utils";
|
||||
import { themeStore } from "../../theme.store";
|
||||
|
||||
interface Props extends ChartProps {
|
||||
data: ChartData;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export class PieChart extends React.Component<Props> {
|
||||
render() {
|
||||
const { data, className, options, ...settings } = this.props
|
||||
const { contentColor } = themeStore.activeTheme.colors;
|
||||
const cutouts = [88, 76, 63]
|
||||
const opts: ChartOptions = this.props.showChart === false ? {} : {
|
||||
maintainAspectRatio: false,
|
||||
tooltips: {
|
||||
mode: "index",
|
||||
callbacks: {
|
||||
title: () => "",
|
||||
label: (tooltipItem, data) => {
|
||||
const dataset: any = data["datasets"][tooltipItem.datasetIndex]
|
||||
const metaData = Object.values<{ total: number }>(dataset["_meta"])[0]
|
||||
const percent = Math.round((dataset["data"][tooltipItem["index"]] / metaData.total) * 100)
|
||||
if (isNaN(percent)) return "N/A";
|
||||
return percent + "%";
|
||||
},
|
||||
},
|
||||
filter: ({ datasetIndex, index }, { datasets }) => {
|
||||
const { data } = datasets[datasetIndex];
|
||||
if (datasets.length === 1) return true;
|
||||
return index !== data.length - 1;
|
||||
},
|
||||
position: "cursor",
|
||||
},
|
||||
elements: {
|
||||
arc: {
|
||||
borderWidth: 1,
|
||||
borderColor: contentColor
|
||||
},
|
||||
},
|
||||
cutoutPercentage: cutouts[data.datasets.length - 1] || 50,
|
||||
responsive: true,
|
||||
...options
|
||||
}
|
||||
return (
|
||||
<Chart
|
||||
className={cssNames("PieChart flex column align-center", className)}
|
||||
data={data}
|
||||
options={opts}
|
||||
{...settings}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ChartJS.Tooltip.positioners.cursor = function (elements: any, position: { x: number; y: number }) {
|
||||
return position;
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import moment from "moment";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useInterval } from "../../hooks";
|
||||
|
||||
type IMetricValues = [number, string][];
|
||||
type IChartData = { x: number; y: string }[]
|
||||
|
||||
const defaultParams = {
|
||||
fetchInterval: 15,
|
||||
updateInterval: 5
|
||||
}
|
||||
|
||||
export function useRealTimeMetrics(metrics: IMetricValues, chartData: IChartData, params = defaultParams) {
|
||||
const [index, setIndex] = useState(0);
|
||||
const { fetchInterval, updateInterval } = params;
|
||||
const rangeMetrics = metrics.slice(-updateInterval);
|
||||
const steps = fetchInterval / updateInterval;
|
||||
const data = [...chartData];
|
||||
|
||||
useEffect(() => {
|
||||
setIndex(0);
|
||||
}, [metrics]);
|
||||
|
||||
useInterval(() => {
|
||||
if (index < steps + 1) {
|
||||
setIndex(index + steps - 1);
|
||||
}
|
||||
}, updateInterval * 1000);
|
||||
|
||||
if (data.length && metrics.length) {
|
||||
const lastTime = data[data.length - 1].x;
|
||||
const values = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
values[i] = moment.unix(lastTime).add(i + 1, "m").unix();
|
||||
}
|
||||
data.push(
|
||||
{ x: values[0], y: "0" },
|
||||
{ x: values[1], y: parseFloat(rangeMetrics[index][1]).toFixed(3) },
|
||||
{ x: values[2], y: "0" }
|
||||
);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Plugin for drawing stripe bars on top of any timeseries barchart
|
||||
// Based on cover DIV element with repeating-linear-gradient style
|
||||
|
||||
import { ChartPoint, default as ChartJS } from "chart.js";
|
||||
import moment, { Moment } from "moment";
|
||||
import get from "lodash/get";
|
||||
|
||||
const defaultOptions = {
|
||||
interval: 61,
|
||||
stripeMinutes: 10,
|
||||
stripeColor: "#ffffff08",
|
||||
}
|
||||
|
||||
export const ZebraStripes = {
|
||||
updated: null as Moment, // timestamp which all stripe movements based on
|
||||
options: {},
|
||||
|
||||
getOptions(chart: ChartJS) {
|
||||
return get(chart, "options.plugins.ZebraStripes");
|
||||
},
|
||||
|
||||
getLastUpdate(chart: ChartJS) {
|
||||
const data = chart.data.datasets[0].data[0] as ChartPoint;
|
||||
return moment.unix(parseInt(data.x as string));
|
||||
},
|
||||
|
||||
getStripesElem(chart: ChartJS) {
|
||||
return chart.canvas.parentElement.querySelector(".zebra-cover");
|
||||
},
|
||||
|
||||
removeStripesElem(chart: ChartJS) {
|
||||
const elem = this.getStripesElem(chart);
|
||||
if (!elem) return;
|
||||
chart.canvas.parentElement.removeChild(elem);
|
||||
},
|
||||
|
||||
renderStripes(chart: ChartJS) {
|
||||
if (!chart.data.datasets.length) return;
|
||||
const { interval, stripeMinutes, stripeColor } = this.options;
|
||||
const { top, left, bottom, right } = chart.chartArea;
|
||||
const step = (right - left) / interval;
|
||||
const stripeWidth = step * stripeMinutes;
|
||||
const cover = document.createElement("div");
|
||||
const styles = cover.style;
|
||||
|
||||
if (this.getStripesElem(chart)) return;
|
||||
|
||||
cover.className = "zebra-cover";
|
||||
styles.width = right - left + "px";
|
||||
styles.left = left + "px";
|
||||
styles.top = top + "px";
|
||||
styles.height = bottom - top + "px";
|
||||
styles.backgroundImage = `
|
||||
repeating-linear-gradient(to right, ${stripeColor} 0px, ${stripeColor} ${stripeWidth}px,
|
||||
transparent ${stripeWidth}px, transparent ${stripeWidth * 2 + step}px)
|
||||
`;
|
||||
chart.canvas.parentElement.appendChild(cover);
|
||||
},
|
||||
|
||||
afterInit(chart: ChartJS) {
|
||||
if (!chart.data.datasets.length) return;
|
||||
this.options = {
|
||||
...defaultOptions,
|
||||
...this.getOptions(chart)
|
||||
}
|
||||
this.updated = this.getLastUpdate(chart);
|
||||
},
|
||||
|
||||
afterUpdate(chart: ChartJS) {
|
||||
this.renderStripes(chart);
|
||||
},
|
||||
|
||||
resize(chart: ChartJS) {
|
||||
this.removeStripesElem(chart);
|
||||
},
|
||||
|
||||
afterDatasetUpdate(chart: ChartJS): void {
|
||||
if (!this.updated) this.updated = this.getLastUpdate(chart);
|
||||
|
||||
const { interval } = this.options;
|
||||
const { left, right } = chart.chartArea;
|
||||
const step = (right - left) / interval;
|
||||
const diff = moment(this.updated).diff(this.getLastUpdate(chart), "minutes");
|
||||
const minutes = Math.abs(diff);
|
||||
|
||||
this.removeStripesElem(chart);
|
||||
this.renderStripes(chart);
|
||||
|
||||
if (minutes > 0) {
|
||||
// Move position regarding to difference in time
|
||||
const cover = this.getStripesElem(chart);
|
||||
cover.style.backgroundPositionX = -step * minutes + "px";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user