///////////////////////////////////////////////////////////// // // pgAdmin 4 - PostgreSQL Tools // // Copyright (C) 2013 - 2024, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // ////////////////////////////////////////////////////////////// import React, { useEffect } from 'react'; import { Chart, registerables } from 'chart.js'; import zoomPlugin from 'chartjs-plugin-zoom'; import PropTypes from 'prop-types'; import _ from 'lodash'; import { useTheme } from '@mui/material'; export const DATA_POINT_STYLE = ['circle', 'cross', 'crossRot', 'rect', 'rectRounded', 'rectRot', 'star', 'triangle']; export const DATA_POINT_SIZE = 3; const defaultOptions = { responsive: true, maintainAspectRatio: false, normalized: true, animation: { duration: 0, active: { duration: 0, }, resize: { duration: 0, }, }, elements: { line: { tension: 0, fill: false, }, }, layout: { padding: 8, }, scales: { x: { display: false, grid: { display: false, }, ticks: { display: false, }, }, y: { ticks: { callback: function(label) { if (Math.floor(label) === label) { return label; } }, }, grid: { drawBorder: false, }, }, }, plugins: { zoom: { pan: { enabled: false, mode: 'x', modifierKey: 'ctrl', }, zoom: { drag: { enabled: false, borderColor: 'rgb(54, 162, 235)', borderWidth: 1, backgroundColor: 'rgba(54, 162, 235, 0.3)' }, mode: 'x', }, } } }; export default function BaseChart({type='line', id, options, data, redraw=false, plugins={}, ...props}) { const chartRef = React.useRef(); const chartObj = React.useRef(); const theme = useTheme(); const initChart = function() { Chart.register(...registerables); // Register for Zoom Plugin Chart.register(zoomPlugin); let chartContext = chartRef.current.getContext('2d'); chartObj.current = new Chart(chartContext, { type: type, data: data, plugins: [plugins], options: options, }); props.onInit?.(chartObj.current); }; const destroyChart = function() { chartObj.current?.resetZoom?.(); chartObj.current?.destroy(); }; useEffect(()=>{ initChart(); return destroyChart; }, []); useEffect(()=>{ let scaleColors = { scales: { x: { ticks: { color: theme.palette.text.primary, }, }, y: { ticks: { color: theme.palette.text.primary, }, grid: { zeroLineColor: theme.otherVars.borderColor, color: theme.otherVars.borderColor }, }, }, }; let merged = options; // Override only if scales are actually defined. if(options.scales && Object.keys(options.scales).length > 0) { merged = _.merge(options, scaleColors); } chartObj.current.options = merged; chartObj.current.update(props.updateOptions || {}); },[theme]); useEffect(()=>{ if(typeof(chartObj.current) != 'undefined') { chartObj.current.data = data; for(let i=0; i{ if(redraw) { destroyChart(); initChart(); } }, [redraw]); return ( ); } BaseChart.propTypes = { type: PropTypes.string.isRequired, id: PropTypes.string, data: PropTypes.object.isRequired, options: PropTypes.object, redraw: PropTypes.bool, updateOptions: PropTypes.object, onInit: PropTypes.func, onUpdate: PropTypes.func, plugins: PropTypes.object, }; const stackedOptions = { scales: { x: { stacked: true, }, y: { stacked: true, }, } }; export function LineChart({stacked, options, ...props}) { // if stacked true then merge the stacked specific options. let optionsMerged = _.merge(defaultOptions, options, stacked ? stackedOptions : {}); return ( ); } LineChart.propTypes = { options: PropTypes.object, stacked: PropTypes.bool }; export function BarChart({stacked, options, ...props}) { // if stacked true then merge the stacked specific options. let optionsMerged = _.merge(defaultOptions, options, stacked ? stackedOptions : {}); return ( ); } BarChart.propTypes = { options: PropTypes.object, stacked: PropTypes.bool }; export function PieChart({options, ...props}) { let optionsMerged = _.merge({ responsive: true, maintainAspectRatio: false, normalized: true, }, options); return ( ); } PieChart.propTypes = { options: PropTypes.object }; export function ConvertHexToRGBA(hex, opacity) { const tempHex = hex.replace('#', ''); const r = parseInt(tempHex.substring(0, 2), 16); const g = parseInt(tempHex.substring(2, 4), 16); const b = parseInt(tempHex.substring(4, 6), 16); return `rgba(${r},${g},${b},${opacity / 100})`; } // This function is used to Lighten/Darken color. export function LightenDarkenColor(color, amount) { let usePound = false; if (color[0] == '#') { color = color.slice(1); usePound = true; } let num = parseInt(color, 16); let r = (num >> 16) + amount; if (r > 255) { r = 255; } else if (r < 0) { r = 0; } let b = ((num >> 8) & 0x00FF) + amount; if (b > 255) { b = 255; } else if (b < 0) { b = 0; } let g = (num & 0x0000FF) + amount; if (g > 255) { g = 255; } else if (g < 0) { g = 0; } return (usePound?'#':'') + (g | (b << 8) | (r << 16)).toString(16); }