????

Your IP : 216.73.216.60


Current Path : C:/opt/pgsql/pgAdmin 4/web/pgadmin/static/js/components/PgChart/
Upload File :
Current File : C:/opt/pgsql/pgAdmin 4/web/pgadmin/static/js/components/PgChart/StreamingChart.jsx

import React, { useRef, useMemo } from 'react';
import UplotReact from 'uplot-react';
import { useResizeDetector } from 'react-resize-detector';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import { useTheme } from '@mui/styles';

function tooltipPlugin(refreshRate) {
  let tooltipTopOffset = -20;
  let tooltipLeftOffset = 10;

  function showTooltip() {
    if(!window.uplotTooltip) {
      window.uplotTooltip = document.createElement('div');
      window.uplotTooltip.className = 'uplot-tooltip';
      document.body.appendChild(window.uplotTooltip);
    }
  }

  function hideTooltip() {
    window.uplotTooltip?.remove();
    window.uplotTooltip = null;
  }

  function setTooltip(u) {
    if(u.cursor.top <= 0) {
      hideTooltip();
      return;
    }

    if(u.legend?.values?.slice(1).every((v)=>v['_']=='')) {
      return;
    }

    showTooltip();

    let tooltipHtml=`<div>${(u.data[1].length-1-parseInt(u.legend.values[0]['_'])) * refreshRate + gettext(' seconds ago')}</div>`;
    for(let i=1; i<u.series.length; i++) {
      tooltipHtml += `<div class='uplot-tooltip-label'><div style='height:12px; width:12px; background-color:${u.series[i].stroke()}'></div> ${u.series[i].label}: ${u.legend.values[i]['_']}</div>`;
    }
    window.uplotTooltip.innerHTML = tooltipHtml;

    let overBBox = u.over.getBoundingClientRect();
    let tooltipBBox = window.uplotTooltip.getBoundingClientRect();
    let left = (tooltipLeftOffset + u.cursor.left + overBBox.left);
    /* Should not outside the graph right */
    if((left+tooltipBBox.width) > overBBox.right) {
      left = left - tooltipBBox.width - tooltipLeftOffset*2;
    }
    window.uplotTooltip.style.left = left + 'px';
    window.uplotTooltip.style.top = (tooltipTopOffset + u.cursor.top + overBBox.top) + 'px';
  }

  return {
    hooks: {
      setCursor: [
        u => {
          setTooltip(u);
        }
      ],
    }
  };
}

export default function StreamingChart({xRange=75, data, options, valueFormatter, showSecondAxis=false}) {
  const chartRef = useRef();
  const theme = useTheme();
  const { width, height, ref:containerRef } = useResizeDetector();

  const defaultOptions = useMemo(()=> {
    const series = [
      {},
      ...(data.datasets?.map((datum, index) => ({
        label: datum.label,
        stroke: datum.borderColor,
        value: valueFormatter ? (_u, t)=>valueFormatter(t) : undefined,
        width: options.lineBorderWidth ?? 1,
        scale: showSecondAxis && (index === 1) ? 'y1' : 'y',
        points: { show: options.showDataPoints ?? false, size: datum.pointHitRadius * 2 },
      })) ?? []),
    ];

    const axes = [
      {
        show: false,
        stroke: theme.palette.text.primary,
      },
    ];

    const yAxesValues = (self, values) => {
      if(valueFormatter && values) {
        return values.map((value) => {
          return valueFormatter(value);
        });
      }
      return values ?? [];
    };

    // ref: https://raw.githubusercontent.com/leeoniya/uPlot/master/demos/axis-autosize.html
    const yAxesSize = (self, values, axisIdx, cycleNum) => {
      let axis = self.axes[axisIdx];

      // bail out, force convergence
      if (cycleNum > 1)
        return axis._size;

      let axisSize = axis.ticks.size + axis.gap + 8;

      // find longest value
      let longestVal = (values ?? []).reduce((acc, val) => (
        val.length > acc.length ? val : acc
      ), '');

      if (longestVal != '') {
        self.ctx.font = axis.font[0];
        axisSize += self.ctx.measureText(longestVal).width / devicePixelRatio;
      }

      return Math.ceil(axisSize);
    };

    axes.push({
      scale: 'y',
      grid: {
        stroke: theme.otherVars.borderColor,
        width: 0.5,
      },
      stroke: theme.palette.text.primary,
      size: yAxesSize,
      values: valueFormatter ? yAxesValues : undefined,
    });

    if(showSecondAxis){
      axes.push({
        scale: 'y1',
        side: 1,
        stroke: theme.palette.text.primary,
        grid: {show: false},
        size: yAxesSize,
        values: valueFormatter ? yAxesValues : undefined,
      });
    }


    return {
      title: '',
      width: width,
      height: height,
      padding: [10, 0, 10, 0],
      focus: {
        alpha: 0.3,
      },
      cursor: {
        y: false,
        drag: {
          setScale: false,
        }
      },
      series: series,
      scales: {
        x: {
          time: false,
          auto: false,
          range: [0, xRange-1],
        }
      },
      axes: axes,
      plugins: options.showTooltip ? [tooltipPlugin(data.refreshRate)] : [],
    };
  }, [data.refreshRate, data?.datasets?.length, width, height, options]);

  const initialState = [
    Array.from(new Array(xRange).keys()),
    ...(data.datasets?.map((d)=>{
      let ret = new Array(xRange).fill(null);
      ret.splice(0, d.data.length, ...d.data);
      ret.reverse();
      return ret;
    })??{}),
  ];

  return (
    <div ref={containerRef} style={{width: '100%', height: '100%'}}>
      <UplotReact target={containerRef.current} options={defaultOptions} data={initialState} onCreate={(obj)=>{
        chartRef.current=obj;
      }} resetScales={false} />
    </div>
  );
}

const propTypeData = PropTypes.shape({
  datasets: PropTypes.array,
  refreshRate: PropTypes.number.isRequired,
});

StreamingChart.propTypes = {
  xRange: PropTypes.number.isRequired,
  data: propTypeData.isRequired,
  options: PropTypes.object,
  showSecondAxis: PropTypes.bool,
  valueFormatter: PropTypes.func,
};