UNPKG

@grafana/ui

Version:
325 lines (322 loc) • 9.93 kB
import { jsx, jsxs } from 'react/jsx-runtime'; import $ from 'jquery'; import { uniqBy } from 'lodash'; import * as React from 'react'; import { PureComponent } from 'react'; import { createDimension } from '@grafana/data'; import { TooltipDisplayMode } from '@grafana/schema'; import { VizTooltip } from '../../components/VizTooltip/VizTooltip.mjs'; import '../../components/VizTooltip/VizTooltipContainer.mjs'; import '@emotion/css'; import '@grafana/i18n'; import 'hoist-non-react-statics'; import 'micro-memoize'; import '../../components/VizLegend/SeriesIcon.mjs'; import { GraphContextMenu } from './GraphContextMenu.mjs'; import { GraphTooltip } from './GraphTooltip/GraphTooltip.mjs'; import { graphTimeFormat, graphTickFormatter } from './utils.mjs'; class Graph extends PureComponent { constructor() { super(...arguments); this.state = { isTooltipVisible: false, isContextVisible: false }; this.element = null; this.$element = null; this.onPlotSelected = (event, ranges) => { const { onHorizontalRegionSelected } = this.props; if (onHorizontalRegionSelected) { onHorizontalRegionSelected(ranges.xaxis.from, ranges.xaxis.to); } }; this.onPlotHover = (event, pos, item) => { this.setState({ isTooltipVisible: true, activeItem: item, pos }); }; this.onPlotClick = (event, contextPos, item) => { this.setState({ isContextVisible: true, isTooltipVisible: false, contextItem: item, contextPos }); }; this.renderTooltip = () => { const { children, series, timeZone } = this.props; const { pos, activeItem, isTooltipVisible } = this.state; let tooltipElement; if (!isTooltipVisible || !pos || series.length === 0) { return null; } React.Children.forEach(children, (c) => { if (tooltipElement) { return; } const childType = c && c.type && (c.type.displayName || c.type.name); if (childType === VizTooltip.displayName) { tooltipElement = c; } }); if (!tooltipElement) { return null; } const tooltipElementProps = tooltipElement.props; const tooltipMode = tooltipElementProps.mode || "single"; if (!activeItem && tooltipMode === "single") { return null; } const tooltipContentRenderer = tooltipElementProps.tooltipComponent || GraphTooltip; const seriesIndex = activeItem ? activeItem.series.seriesIndex : 0; const rowIndex = activeItem ? activeItem.dataIndex : void 0; const activeDimensions = { // Described x-axis active item // When hovering over an item - let's take it's dataIndex, otherwise undefined // Tooltip itself needs to figure out correct datapoint display information based on pos passed to it xAxis: [seriesIndex, rowIndex], // Describes y-axis active item yAxis: activeItem ? [activeItem.series.seriesIndex, activeItem.dataIndex] : null }; const tooltipContentProps = { dimensions: { // time/value dimension columns are index-aligned - see getGraphSeriesModel xAxis: createDimension( "xAxis", series.map((s) => s.timeField) ), yAxis: createDimension( "yAxis", series.map((s) => s.valueField) ) }, activeDimensions, pos, mode: tooltipElementProps.mode || TooltipDisplayMode.Single, timeZone }; const tooltipContent = React.createElement(tooltipContentRenderer, { ...tooltipContentProps }); return React.cloneElement(tooltipElement, { content: tooltipContent, position: { x: pos.pageX, y: pos.pageY }, offset: { x: 10, y: 10 } }); }; this.renderContextMenu = () => { const { series } = this.props; const { contextPos, contextItem, isContextVisible } = this.state; if (!isContextVisible || !contextPos || !contextItem || series.length === 0) { return null; } const seriesIndex = contextItem ? contextItem.series.seriesIndex : 0; const rowIndex = contextItem ? contextItem.dataIndex : void 0; const contextDimensions = { // Described x-axis context item xAxis: [seriesIndex, rowIndex], // Describes y-axis context item yAxis: contextItem ? [contextItem.series.seriesIndex, contextItem.dataIndex] : null }; const dimensions = { // time/value dimension columns are index-aligned - see getGraphSeriesModel xAxis: createDimension( "xAxis", series.map((s) => s.timeField) ), yAxis: createDimension( "yAxis", series.map((s) => s.valueField) ) }; const closeContext = () => this.setState({ isContextVisible: false }); const getContextMenuSource = () => { return { datapoint: contextItem.datapoint, dataIndex: contextItem.dataIndex, series: contextItem.series, seriesIndex: contextItem.series.seriesIndex, pageX: contextPos.pageX, pageY: contextPos.pageY }; }; const contextContentProps = { x: contextPos.pageX, y: contextPos.pageY, onClose: closeContext, getContextMenuSource, timeZone: this.props.timeZone, dimensions, contextDimensions }; return /* @__PURE__ */ jsx(GraphContextMenu, { ...contextContentProps }); }; this.getBarWidth = () => { const { series } = this.props; return Math.min(...series.map((s) => s.timeStep)); }; } componentDidUpdate(prevProps, prevState) { if (prevProps !== this.props) { this.draw(); } } componentDidMount() { this.draw(); if (this.element) { this.$element = $(this.element); this.$element.bind("plotselected", this.onPlotSelected); this.$element.bind("plothover", this.onPlotHover); this.$element.bind("plotclick", this.onPlotClick); } } componentWillUnmount() { if (this.$element) { this.$element.unbind("plotselected", this.onPlotSelected); } } getYAxes(series) { if (series.length === 0) { return [{ show: true, min: -1, max: 1 }]; } return uniqBy( series.map((s) => { const index = s.yAxis ? s.yAxis.index : 1; const min = s.yAxis && s.yAxis.min && !isNaN(s.yAxis.min) ? s.yAxis.min : null; const tickDecimals = s.yAxis && s.yAxis.tickDecimals && !isNaN(s.yAxis.tickDecimals) ? s.yAxis.tickDecimals : null; return { show: true, index, position: index === 1 ? "left" : "right", min, tickDecimals }; }), (yAxisConfig) => yAxisConfig.index ); } draw() { if (this.element === null) { return; } const { width, series, timeRange, showLines, showBars, showPoints, isStacked, lineWidth, timeZone, onHorizontalRegionSelected } = this.props; if (!width) { return; } const ticks = width / 100; const min = timeRange.from.valueOf(); const max = timeRange.to.valueOf(); const yaxes = this.getYAxes(series); const flotOptions = { legend: { show: false }, series: { stack: isStacked, lines: { show: showLines, lineWidth, zero: false }, points: { show: showPoints, fill: 1, fillColor: false, radius: 2 }, bars: { show: showBars, fill: 1, // Dividig the width by 1.5 to make the bars not touch each other barWidth: showBars ? this.getBarWidth() / 1.5 : 1, zero: false, lineWidth }, shadowSize: 0 }, xaxis: { timezone: timeZone, show: true, mode: "time", min, max, label: "Datetime", ticks, timeformat: graphTimeFormat(ticks, min, max), tickFormatter: graphTickFormatter }, yaxes, grid: { minBorderMargin: 0, markings: [], backgroundColor: null, borderWidth: 0, hoverable: true, clickable: true, color: "#a1a1a1", margin: { left: 0, right: 0 }, labelMarginX: 0, mouseActiveRadius: 30 }, selection: { mode: onHorizontalRegionSelected ? "x" : null, color: "#666" }, crosshair: { mode: "x" } }; try { $.plot( this.element, series.filter((s) => s.isVisible), flotOptions ); } catch (err) { console.error("Graph rendering error", err, flotOptions, series); throw new Error("Error rendering panel"); } } render() { const { ariaLabel, height, width, series } = this.props; const noDataToBeDisplayed = series.length === 0; const tooltip = this.renderTooltip(); const context = this.renderContextMenu(); return /* @__PURE__ */ jsxs("div", { className: "graph-panel", "aria-label": ariaLabel, children: [ /* @__PURE__ */ jsx( "div", { className: "graph-panel__chart", ref: (e) => this.element = e, style: { height, width }, onMouseLeave: () => { this.setState({ isTooltipVisible: false }); } } ), noDataToBeDisplayed && /* @__PURE__ */ jsx("div", { className: "datapoints-warning", children: "No data" }), tooltip, context ] }); } } Graph.defaultProps = { showLines: true, showPoints: false, showBars: false, isStacked: false, lineWidth: 1 }; export { Graph, Graph as default }; //# sourceMappingURL=Graph.mjs.map