| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  | import React, { MouseEvent, memo } from 'react'; | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  | import cx from 'classnames'; | 
					
						
							| 
									
										
										
										
											2021-05-18 22:30:27 +08:00
										 |  |  | import { Field, getFieldColorModeForField, GrafanaTheme2 } from '@grafana/data'; | 
					
						
							|  |  |  | import { useStyles2, useTheme2 } from '@grafana/ui'; | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  | import { NodeDatum } from './types'; | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  | import { css } from 'emotion'; | 
					
						
							|  |  |  | import tinycolor from 'tinycolor2'; | 
					
						
							|  |  |  | import { statToString } from './utils'; | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | const nodeR = 40; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  | const getStyles = (theme: GrafanaTheme2) => ({ | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |   mainGroup: css`
 | 
					
						
							|  |  |  |     cursor: pointer; | 
					
						
							|  |  |  |     font-size: 10px; | 
					
						
							|  |  |  |   `,
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   mainCircle: css`
 | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |     fill: ${theme.components.panel.background}; | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |   `,
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   hoverCircle: css`
 | 
					
						
							|  |  |  |     opacity: 0.5; | 
					
						
							|  |  |  |     fill: transparent; | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |     stroke: ${theme.colors.primary.text}; | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |   `,
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   text: css`
 | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |     fill: ${theme.colors.text.primary}; | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |   `,
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   titleText: css`
 | 
					
						
							|  |  |  |     text-align: center; | 
					
						
							|  |  |  |     text-overflow: ellipsis; | 
					
						
							|  |  |  |     overflow: hidden; | 
					
						
							|  |  |  |     white-space: nowrap; | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |     background-color: ${tinycolor(theme.colors.background.primary).setAlpha(0.6).toHex8String()}; | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  |     width: 100px; | 
					
						
							|  |  |  |   `,
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   statsText: css`
 | 
					
						
							|  |  |  |     text-align: center; | 
					
						
							|  |  |  |     text-overflow: ellipsis; | 
					
						
							|  |  |  |     overflow: hidden; | 
					
						
							|  |  |  |     white-space: nowrap; | 
					
						
							|  |  |  |     width: 70px; | 
					
						
							|  |  |  |   `,
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   textHovering: css`
 | 
					
						
							|  |  |  |     width: 200px; | 
					
						
							|  |  |  |     & span { | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |       background-color: ${tinycolor(theme.colors.background.primary).setAlpha(0.8).toHex8String()}; | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |   `,
 | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  | }); | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export const Node = memo(function Node(props: { | 
					
						
							|  |  |  |   node: NodeDatum; | 
					
						
							|  |  |  |   onMouseEnter: (id: string) => void; | 
					
						
							|  |  |  |   onMouseLeave: (id: string) => void; | 
					
						
							|  |  |  |   onClick: (event: MouseEvent<SVGElement>, node: NodeDatum) => void; | 
					
						
							|  |  |  |   hovering: boolean; | 
					
						
							|  |  |  | }) { | 
					
						
							|  |  |  |   const { node, onMouseEnter, onMouseLeave, onClick, hovering } = props; | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |   const styles = useStyles2(getStyles); | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   if (!(node.x !== undefined && node.y !== undefined)) { | 
					
						
							|  |  |  |     return null; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return ( | 
					
						
							|  |  |  |     <g | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |       data-node-id={node.id} | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |       className={styles.mainGroup} | 
					
						
							|  |  |  |       onMouseEnter={() => { | 
					
						
							|  |  |  |         onMouseEnter(node.id); | 
					
						
							|  |  |  |       }} | 
					
						
							|  |  |  |       onMouseLeave={() => { | 
					
						
							|  |  |  |         onMouseLeave(node.id); | 
					
						
							|  |  |  |       }} | 
					
						
							| 
									
										
										
										
											2021-01-20 14:59:48 +08:00
										 |  |  |       onClick={(event) => { | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |         onClick(event, node); | 
					
						
							|  |  |  |       }} | 
					
						
							|  |  |  |       aria-label={`Node: ${node.title}`} | 
					
						
							|  |  |  |     > | 
					
						
							|  |  |  |       <circle className={styles.mainCircle} r={nodeR} cx={node.x} cy={node.y} /> | 
					
						
							|  |  |  |       {hovering && <circle className={styles.hoverCircle} r={nodeR - 3} cx={node.x} cy={node.y} strokeWidth={2} />} | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  |       <ColorCircle node={node} /> | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |       <g className={styles.text}> | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  |         <foreignObject x={node.x - (hovering ? 100 : 35)} y={node.y - 15} width={hovering ? '200' : '70'} height="30"> | 
					
						
							|  |  |  |           <div className={cx(styles.statsText, hovering && styles.textHovering)}> | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |             <span>{node.mainStat && statToString(node.mainStat, node.dataFrameRowIndex)}</span> | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |             <br /> | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |             <span>{node.secondaryStat && statToString(node.secondaryStat, node.dataFrameRowIndex)}</span> | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  |           </div> | 
					
						
							|  |  |  |         </foreignObject> | 
					
						
							|  |  |  |         <foreignObject | 
					
						
							|  |  |  |           x={node.x - (hovering ? 100 : 50)} | 
					
						
							|  |  |  |           y={node.y + nodeR + 5} | 
					
						
							|  |  |  |           width={hovering ? '200' : '100'} | 
					
						
							|  |  |  |           height="30" | 
					
						
							|  |  |  |         > | 
					
						
							|  |  |  |           <div className={cx(styles.titleText, hovering && styles.textHovering)}> | 
					
						
							|  |  |  |             <span>{node.title}</span> | 
					
						
							|  |  |  |             <br /> | 
					
						
							|  |  |  |             <span>{node.subTitle}</span> | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |           </div> | 
					
						
							|  |  |  |         </foreignObject> | 
					
						
							|  |  |  |       </g> | 
					
						
							|  |  |  |     </g> | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  |  * Shows the outer segmented circle with different colors based on the supplied data. | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  | function ColorCircle(props: { node: NodeDatum }) { | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |   const { node } = props; | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |   const fullStat = node.arcSections.find((s) => s.values.get(node.dataFrameRowIndex) === 1); | 
					
						
							| 
									
										
										
										
											2021-05-18 22:30:27 +08:00
										 |  |  |   const theme = useTheme2(); | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   if (fullStat) { | 
					
						
							|  |  |  |     // Doing arc with path does not work well so it's better to just do a circle in that case
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       <circle | 
					
						
							|  |  |  |         fill="none" | 
					
						
							| 
									
										
										
										
											2021-05-18 22:30:27 +08:00
										 |  |  |         stroke={theme.visualization.getColorByName(fullStat.config.color?.fixedColor || '')} | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |         strokeWidth={2} | 
					
						
							|  |  |  |         r={nodeR} | 
					
						
							|  |  |  |         cx={node.x} | 
					
						
							|  |  |  |         cy={node.y} | 
					
						
							|  |  |  |       /> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |   const nonZero = node.arcSections.filter((s) => s.values.get(node.dataFrameRowIndex) !== 0); | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  |   if (nonZero.length === 0) { | 
					
						
							|  |  |  |     // Fallback if no arc is defined
 | 
					
						
							| 
									
										
										
										
											2021-05-18 22:30:27 +08:00
										 |  |  |     return ( | 
					
						
							|  |  |  |       <circle | 
					
						
							|  |  |  |         fill="none" | 
					
						
							|  |  |  |         stroke={node.color ? getColor(node.color, node.dataFrameRowIndex, theme) : 'gray'} | 
					
						
							|  |  |  |         strokeWidth={2} | 
					
						
							|  |  |  |         r={nodeR} | 
					
						
							|  |  |  |         cx={node.x} | 
					
						
							|  |  |  |         cy={node.y} | 
					
						
							|  |  |  |       /> | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   const { elements } = nonZero.reduce( | 
					
						
							|  |  |  |     (acc, section) => { | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |       const color = section.config.color?.fixedColor || ''; | 
					
						
							|  |  |  |       const value = section.values.get(node.dataFrameRowIndex); | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |       const el = ( | 
					
						
							|  |  |  |         <ArcSection | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |           key={color} | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |           r={nodeR} | 
					
						
							|  |  |  |           x={node.x!} | 
					
						
							|  |  |  |           y={node.y!} | 
					
						
							|  |  |  |           startPercent={acc.percent} | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |           percent={value} | 
					
						
							| 
									
										
										
										
											2021-05-18 22:30:27 +08:00
										 |  |  |           color={theme.visualization.getColorByName(color)} | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |           strokeWidth={2} | 
					
						
							|  |  |  |         /> | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       acc.elements.push(el); | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |       acc.percent = acc.percent + value; | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |       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 ( | 
					
						
							|  |  |  |     <path | 
					
						
							|  |  |  |       fill="none" | 
					
						
							|  |  |  |       d={`M ${startXPos} ${startYPos} A ${r} ${r} 0 ${largeArc} 1 ${endXPos} ${endYPos}`} | 
					
						
							|  |  |  |       stroke={color} | 
					
						
							|  |  |  |       strokeWidth={strokeWidth} | 
					
						
							|  |  |  |     /> | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-05-18 22:30:27 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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)); | 
					
						
							|  |  |  | } |