codekingpro/portable-devtools
114k
1/////////////////////////////////////////////////////////////2//3// pgAdmin 4 - PostgreSQL Tools4//5// Copyright (C) 2013 - 2024, The pgAdmin Development Team6// This software is released under the PostgreSQL Licence7//8//////////////////////////////////////////////////////////////9import React, { useEffect } from 'react';10import { Chart, registerables } from 'chart.js';11import zoomPlugin from 'chartjs-plugin-zoom';12import PropTypes from 'prop-types';13import _ from 'lodash';14import { useTheme } from '@mui/material';15 16export const DATA_POINT_STYLE = ['circle', 'cross', 'crossRot', 'rect',17 'rectRounded', 'rectRot', 'star', 'triangle'];18export const DATA_POINT_SIZE = 3;19export const CHART_THEME_COLORS_LENGTH = 20;20export const CHART_THEME_COLORS = {21 'standard':['#1F77B4', '#FF7F0E', '#2CA02C', '#D62728', '#9467BD', '#8C564B',22 '#E377C2', '#7F7F7F', '#BCBD22', '#17BECF', '#3366CC', '#DC3912', '#FF9900',23 '#109618', '#990099', '#0099C6','#DD4477', '#66AA00', '#B82E2E', '#316395'],24 'dark': ['#4878D0', '#EE854A', '#6ACC64', '#D65F5F', '#956CB4', '#8C613C',25 '#DC7EC0', '#797979', '#D5BB67', '#82C6E2', '#7371FC', '#3A86FF', '#979DAC',26 '#D4A276', '#2A9D8F', '#FFEE32', '#70E000', '#FF477E', '#7DC9F1', '#52B788'],27 'high_contrast': ['#023EFF', '#FF7C00', '#1AC938', '#E8000B', '#8B2BE2',28 '#9F4800', '#F14CC1', '#A3A3A3', '#FFC400', '#00D7FF', '#FF6C49', '#00B4D8',29 '#45D48A', '#FFFB69', '#B388EB', '#D4A276', '#2EC4B6', '#7DC9F1', '#50B0F0',30 '#52B788']31};32 33const defaultOptions = {34 responsive: true,35 maintainAspectRatio: false,36 normalized: true,37 animation: {38 duration: 0,39 active: {40 duration: 0,41 },42 resize: {43 duration: 0,44 },45 },46 elements: {47 line: {48 tension: 0,49 fill: false,50 },51 },52 layout: {53 padding: 8,54 },55 scales: {56 x: {57 display: false,58 grid: {59 display: false,60 },61 ticks: {62 display: false,63 },64 },65 y: {66 ticks: {67 callback: function(label) {68 if (Math.floor(label) === label) {69 return label;70 }71 },72 },73 grid: {74 drawBorder: false,75 },76 },77 },78 plugins: {79 zoom: {80 pan: {81 enabled: false,82 mode: 'x',83 modifierKey: 'ctrl',84 },85 zoom: {86 drag: {87 enabled: false,88 borderColor: 'rgb(54, 162, 235)',89 borderWidth: 1,90 backgroundColor: 'rgba(54, 162, 235, 0.3)'91 },92 mode: 'x',93 },94 }95 }96};97 98export default function BaseChart({type='line', id, options, data, redraw=false, plugins={}, ...props}) {99 const chartRef = React.useRef();100 const chartObj = React.useRef();101 const theme = useTheme();102 103 const initChart = function() {104 Chart.register(...registerables);105 // Register for Zoom Plugin106 Chart.register(zoomPlugin);107 let chartContext = chartRef.current.getContext('2d');108 chartObj.current = new Chart(chartContext, {109 type: type,110 data: data,111 plugins: [plugins],112 options: options,113 });114 props.onInit?.(chartObj.current);115 };116 117 const destroyChart = function() {118 chartObj.current?.resetZoom?.();119 chartObj.current?.destroy();120 };121 122 useEffect(()=>{123 initChart();124 return destroyChart;125 }, []);126 127 useEffect(()=>{128 let theme1 = {129 scales: {130 x: {131 ticks: {132 color: theme.palette.text.primary,133 },134 },135 y: {136 ticks: {137 color: theme.palette.text.primary,138 },139 grid: {140 zeroLineColor: theme.otherVars.borderColor,141 color: theme.otherVars.borderColor142 },143 },144 },145 };146 let merged = _.merge(options, theme1);147 chartObj.current.options = merged;148 chartObj.current.update(props.updateOptions || {});149 },[theme]);150 151 useEffect(()=>{152 if(typeof(chartObj.current) != 'undefined') {153 chartObj.current.data = data;154 for(let i=0; i<chartObj.current.data.datasets.length; i++) {155 chartObj.current.data.datasets[i] = {156 ...chartObj.current.data.datasets[i],157 ...data.datasets[i],158 };159 }160 chartObj.current.options = options;161 chartObj.current.update(props.updateOptions || {});162 props.onUpdate?.(chartObj.current);163 }164 }, [data, options]);165 166 useEffect(()=>{167 if(redraw) {168 destroyChart();169 initChart();170 }171 }, [redraw]);172 173 return (174 <canvas id={id} ref={chartRef}></canvas>175 );176}177 178BaseChart.propTypes = {179 type: PropTypes.string.isRequired,180 id: PropTypes.string,181 data: PropTypes.object.isRequired,182 options: PropTypes.object,183 redraw: PropTypes.bool,184 updateOptions: PropTypes.object,185 onInit: PropTypes.func,186 onUpdate: PropTypes.func,187 plugins: PropTypes.object,188};189 190const stackedOptions = {191 scales: {192 x: {193 stacked: true,194 },195 y: {196 stacked: true,197 },198 }199};200 201export function LineChart({stacked, options, ...props}) {202 // if stacked true then merge the stacked specific options.203 let optionsMerged = _.merge(defaultOptions, options, stacked ? stackedOptions : {});204 return (205 <BaseChart {...props} options={optionsMerged} type='line'/>206 );207}208LineChart.propTypes = {209 options: PropTypes.object,210 stacked: PropTypes.bool211};212 213export function BarChart({stacked, options, ...props}) {214 // if stacked true then merge the stacked specific options.215 let optionsMerged = _.merge(defaultOptions, options, stacked ? stackedOptions : {});216 return (217 <BaseChart {...props} options={optionsMerged} type='bar'/>218 );219}220BarChart.propTypes = {221 options: PropTypes.object,222 stacked: PropTypes.bool223};224 225export function PieChart({options, ...props}) {226 let optionsMerged = _.merge({227 responsive: true,228 maintainAspectRatio: false,229 normalized: true230 }, options);231 return (232 <BaseChart {...props} options={optionsMerged} type='pie'/>233 );234}235PieChart.propTypes = {236 options: PropTypes.object237};238 239export function ConvertHexToRGBA(hex, opacity) {240 const tempHex = hex.replace('#', '');241 const r = parseInt(tempHex.substring(0, 2), 16);242 const g = parseInt(tempHex.substring(2, 4), 16);243 const b = parseInt(tempHex.substring(4, 6), 16);244 245 return `rgba(${r},${g},${b},${opacity / 100})`;246}247 248// This function is used to Lighten/Darken color.249export function LightenDarkenColor(color, amount) {250 let usePound = false;251 if (color[0] == '#') {252 color = color.slice(1);253 usePound = true;254 }255 256 let num = parseInt(color, 16);257 258 let r = (num >> 16) + amount;259 if (r > 255) {260 r = 255;261 } else if (r < 0) {262 r = 0;263 }264 265 let b = ((num >> 8) & 0x00FF) + amount;266 if (b > 255) {267 b = 255;268 } else if (b < 0) {269 b = 0;270 }271 272 let g = (num & 0x0000FF) + amount;273 if (g > 255) {274 g = 255;275 } else if (g < 0) {276 g = 0;277 }278 279 return (usePound?'#':'') + (g | (b << 8) | (r << 16)).toString(16);280}281 