Skip to content

Commit

Permalink
feat: Moving Event graph to Chart.js for more interactions
Browse files Browse the repository at this point in the history
  • Loading branch information
praveen5959 committed Jan 18, 2025
1 parent e94a62e commit 5a63d72
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 54 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
"@tabler/icons-react": "^3.3.0",
"@types/js-cookie": "^3.0.3",
"axios": "^1.4.0",
"chart.js": "^4.4.7",
"chartjs-plugin-annotation": "^3.1.0",
"chartjs-plugin-zoom": "^2.2.0",
"dayjs": "^1.11.10",
"embla-carousel-react": "7.1.0",
"html2canvas": "^1.4.1",
Expand All @@ -48,6 +51,7 @@
"protobufjs": "^7.2.5",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-chartjs-2": "^5.3.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.10",
"react-grid-layout": "^1.4.4",
Expand Down
67 changes: 67 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions src/pages/Correlation/components/SavedCorrelationItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Stack, Box, Button, Text, px, Code } from '@mantine/core';
import { IconClock, IconEye, IconEyeOff, IconTrash, IconX } from '@tabler/icons-react';
import { useState, useCallback, Fragment } from 'react';
import { useState, useCallback, Fragment, FC } from 'react';
import classes from '../styles/SavedCorrelationItem.module.css';
import { Correlation } from '@/@types/parseable/api/correlation';
import dayjs from 'dayjs';
Expand Down Expand Up @@ -42,7 +42,7 @@ interface JoinConfig {
joinConditions: JoinCondition[];
}

const SelectedFields: React.FC<{ tableConfigs: TableConfig[] }> = ({ tableConfigs }) => {
const SelectedFields: FC<{ tableConfigs: TableConfig[] }> = ({ tableConfigs }) => {
const fields = tableConfigs.flatMap((config) =>
config.selectedFields.map((field) => ({
key: `${config.tableName}-${field}`,
Expand All @@ -63,7 +63,7 @@ const SelectedFields: React.FC<{ tableConfigs: TableConfig[] }> = ({ tableConfig
);
};

const JoinConditions: React.FC<{ joinConfig: JoinConfig }> = ({ joinConfig }) => {
const JoinConditions: FC<{ joinConfig: JoinConfig }> = ({ joinConfig }) => {
return (
<>
{joinConfig.joinConditions.map((join, index) => {
Expand Down
201 changes: 201 additions & 0 deletions src/pages/Stream/components/AreaChartComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import React from 'react';
import { Line } from 'react-chartjs-2';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Tooltip,
ChartOptions,
Filler,
} from 'chart.js';
import Annotation from 'chartjs-plugin-annotation';
import zoomPlugin from 'chartjs-plugin-zoom';
import { HumanizeNumber } from '@/utils/formatBytes';
import timeRangeUtils from '@/utils/timeRangeUtils';

const { makeTimeRangeLabel } = timeRangeUtils;

ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Tooltip, Annotation, Filler, zoomPlugin);

interface ChartComponentProps {
graphData: any;
avgEventCount: number;
setTimeRangeFromGraph: (minute: string) => void;
hasData: boolean;
onZoomOrPanComplete: (startTime: string, endTime: string) => void;
}

const AreaChartComponent: React.FC<ChartComponentProps> = ({
graphData,
avgEventCount,
setTimeRangeFromGraph,
hasData,
onZoomOrPanComplete,
}) => {
const chartData = {
labels: graphData.map((item: any) => item.minute),
datasets: [
{
label: 'Events',
data: graphData.map((item: any) => item.events),
fill: true,
// backgroundColor: 'rgba(99, 102, 241, 0.5)',
borderColor: 'rgb(99, 102, 241)',
borderWidth: 1.25,
pointRadius: 2.5,
pointBorderWidth: 1,
pointBackgroundColor: 'rgb(99, 102, 241)',
tension: 0.4,
backgroundColor: (context: any) => {
const ctx = context.chart.ctx;
const gradient = ctx.createLinearGradient(0, 0, 0, context.chart.height);
gradient.addColorStop(0, 'rgba(99, 102, 241, 0.5)');
gradient.addColorStop(1, 'rgba(99, 102, 241, 0)');
return gradient;
},
},
],
};

const handleRangeComplete = (chart: any) => {
const { min, max } = chart.scales.x;
const startIndex = Math.floor(min);
const endIndex = Math.ceil(max);
// Get the time range for the zoomed area
if (startIndex >= 0 && endIndex < graphData.length) {
const startTime = graphData[startIndex].startTime;
const endTime = graphData[endIndex].endTime;
onZoomOrPanComplete(startTime, endTime);
}
};

const chartOptions: ChartOptions<'line'> = {
responsive: true,
maintainAspectRatio: false,
plugins: {
tooltip: {
position: 'nearest',
backgroundColor: 'white',
titleColor: 'black',
bodyColor: 'black',
footerColor: 'black',
borderColor: 'rgb(99, 102, 241)',
borderWidth: 1,
callbacks: {
title: (tooltipItems: any) => {
const index = tooltipItems[0].dataIndex;
const { startTime, endTime } = graphData[index];
return makeTimeRangeLabel(new Date(startTime), new Date(endTime));
},
label: (tooltipItem: any) => {
return `Events: ${new Intl.NumberFormat('en-US').format(tooltipItem.raw)}`;
},
// footer: (tooltipItems: any) => {
// const index = tooltipItems[0].dataIndex;
// const { aboveAvgPercent } = graphData[index];
// const isAboveAvg = aboveAvgPercent > 0;
// return `${isAboveAvg ? '+' : ''}${aboveAvgPercent}% ${
// isAboveAvg ? 'above' : 'below'
// } average in the given time-range`;
// },
},
},
annotation: {
annotations: {
avgLine: {
type: 'line',
yMin: avgEventCount,
yMax: avgEventCount,
borderColor: 'rgb(156, 163, 175)',
borderWidth: 1,
label: {
content: 'Avg',
position: 'start',
backgroundColor: 'transparent',
color: 'black',
font: {
size: 12,
},
},
},
},
},
zoom: {
limits: {
x: { min: 'original', max: 'original' },
y: { min: 'original', max: 'original' },
},
zoom: {
wheel: {
enabled: false,
},
pinch: {
enabled: true,
},
mode: 'x',
drag: {
enabled: true,
backgroundColor: 'rgba(99, 102, 241, 0.1)',
},
onZoomComplete: ({ chart }) => {
handleRangeComplete(chart);
},
},
},
},
scales: {
x: {
type: 'category',
display: false,
},
y: {
display: hasData,
ticks: {
count: 2,
callback: (value: string | number) => {
const numericValue = typeof value === 'number' ? value : parseFloat(value);
return HumanizeNumber(numericValue);
},
},
grid: {
drawTicks: false,
},
},
},
elements: {
line: {
borderWidth: 1.25,
},
point: {
radius: 2.5,
hitRadius: 6,
},
},
layout: {
padding: {
top: 0,
},
},
hover: {
mode: 'nearest',
axis: 'x',
intersect: false,
},
onClick: (_event, elements) => {
if (elements && elements.length > 0) {
const index = elements[0].index;
setTimeRangeFromGraph(graphData[index].minute);
}
},
};

return (
<div style={{ height: '100%', width: '100%', cursor: 'pointer' }}>
<Line data={chartData} options={chartOptions} />
</div>
);
};

export default AreaChartComponent;
Loading

0 comments on commit 5a63d72

Please sign in to comment.