import { css } from '@emotion/css'; import cx from 'classnames'; import React, { MouseEvent, memo } from 'react'; import tinycolor from 'tinycolor2'; import { Field, getFieldColorModeForField, GrafanaTheme2 } from '@grafana/data'; import { useStyles2, useTheme2 } from '@grafana/ui'; import { NodeDatum } from './types'; import { statToString } from './utils'; const nodeR = 40; const getStyles = (theme: GrafanaTheme2) => ({ mainGroup: css` cursor: pointer; font-size: 10px; `, mainCircle: css` fill: ${theme.components.panel.background}; `, hoverCircle: css` opacity: 0.5; fill: transparent; stroke: ${theme.colors.primary.text}; `, text: css` fill: ${theme.colors.text.primary}; `, titleText: css` text-align: center; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; background-color: ${tinycolor(theme.colors.background.primary).setAlpha(0.6).toHex8String()}; width: 100px; `, statsText: css` text-align: center; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; width: 70px; `, textHovering: css` width: 200px; & span { background-color: ${tinycolor(theme.colors.background.primary).setAlpha(0.8).toHex8String()}; } `, }); export const Node = memo(function Node(props: { node: NodeDatum; onMouseEnter: (id: string) => void; onMouseLeave: (id: string) => void; onClick: (event: MouseEvent, node: NodeDatum) => void; hovering: boolean; }) { const { node, onMouseEnter, onMouseLeave, onClick, hovering } = props; const styles = useStyles2(getStyles); if (!(node.x !== undefined && node.y !== undefined)) { return null; } return ( { onMouseEnter(node.id); }} onMouseLeave={() => { onMouseLeave(node.id); }} onClick={(event) => { onClick(event, node); }} aria-label={`Node: ${node.title}`} > {hovering && }
{node.mainStat && statToString(node.mainStat, node.dataFrameRowIndex)}
{node.secondaryStat && statToString(node.secondaryStat, node.dataFrameRowIndex)}
{node.title}
{node.subTitle}
); }); /** * Shows the outer segmented circle with different colors based on the supplied data. */ function ColorCircle(props: { node: NodeDatum }) { const { node } = props; const fullStat = node.arcSections.find((s) => s.values.get(node.dataFrameRowIndex) === 1); const theme = useTheme2(); if (fullStat) { // Doing arc with path does not work well so it's better to just do a circle in that case return ( ); } const nonZero = node.arcSections.filter((s) => s.values.get(node.dataFrameRowIndex) !== 0); if (nonZero.length === 0) { // Fallback if no arc is defined return ( ); } const { elements } = nonZero.reduce( (acc, section) => { const color = section.config.color?.fixedColor || ''; const value = section.values.get(node.dataFrameRowIndex); const el = ( ); acc.elements.push(el); acc.percent = acc.percent + value; return acc; }, { elements: [] as React.ReactNode[], percent: 0 } ); return <>{elements}; } function ArcSection({ r, x, y, startPercent, percent, color, strokeWidth = 2, }: { r: number; x: number; y: number; startPercent: number; percent: number; color: string; strokeWidth?: number; }) { const endPercent = startPercent + percent; const startXPos = x + Math.sin(2 * Math.PI * startPercent) * r; const startYPos = y - Math.cos(2 * Math.PI * startPercent) * r; const endXPos = x + Math.sin(2 * Math.PI * endPercent) * r; const endYPos = y - Math.cos(2 * Math.PI * endPercent) * r; const largeArc = percent > 0.5 ? '1' : '0'; return ( ); } function getColor(field: Field, index: number, theme: GrafanaTheme2): string { if (!field.config.color) { return field.values.get(index); } return getFieldColorModeForField(field).getCalculator(field, theme)(0, field.values.get(index)); }