| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  | import { | 
					
						
							|  |  |  |   ArrayVector, | 
					
						
							|  |  |  |   DataFrame, | 
					
						
							|  |  |  |   Field, | 
					
						
							|  |  |  |   FieldCache, | 
					
						
							| 
									
										
										
										
											2022-06-23 21:20:56 +08:00
										 |  |  |   FieldColorModeId, | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  |   FieldType, | 
					
						
							| 
									
										
										
										
											2021-05-03 15:45:54 +08:00
										 |  |  |   GrafanaTheme2, | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  |   MutableDataFrame, | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |   NodeGraphDataFrameFieldNames, | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  | } from '@grafana/data'; | 
					
						
							| 
									
										
										
										
											2022-04-22 21:33:13 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-23 21:20:56 +08:00
										 |  |  | import { EdgeDatum, NodeDatum, NodeGraphOptions } from './types'; | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | type Line = { x1: number; y1: number; x2: number; y2: number }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Makes line shorter while keeping the middle in he same place. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export function shortenLine(line: Line, length: number): Line { | 
					
						
							|  |  |  |   const vx = line.x2 - line.x1; | 
					
						
							|  |  |  |   const vy = line.y2 - line.y1; | 
					
						
							|  |  |  |   const mag = Math.sqrt(vx * vx + vy * vy); | 
					
						
							|  |  |  |   const ratio = Math.max((mag - length) / mag, 0); | 
					
						
							|  |  |  |   const vx2 = vx * ratio; | 
					
						
							|  |  |  |   const vy2 = vy * ratio; | 
					
						
							|  |  |  |   const xDiff = vx - vx2; | 
					
						
							|  |  |  |   const yDiff = vy - vy2; | 
					
						
							|  |  |  |   const newx1 = line.x1 + xDiff / 2; | 
					
						
							|  |  |  |   const newy1 = line.y1 + yDiff / 2; | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     x1: newx1, | 
					
						
							|  |  |  |     y1: newy1, | 
					
						
							|  |  |  |     x2: newx1 + vx2, | 
					
						
							|  |  |  |     y2: newy1 + vy2, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function getNodeFields(nodes: DataFrame) { | 
					
						
							| 
									
										
										
										
											2022-04-13 23:18:40 +08:00
										 |  |  |   const normalizedFrames = { | 
					
						
							|  |  |  |     ...nodes, | 
					
						
							|  |  |  |     fields: nodes.fields.map((field) => ({ ...field, name: field.name.toLowerCase() })), | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   const fieldsCache = new FieldCache(normalizedFrames); | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |   return { | 
					
						
							| 
									
										
										
										
											2022-04-13 23:18:40 +08:00
										 |  |  |     id: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.id.toLowerCase()), | 
					
						
							|  |  |  |     title: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.title.toLowerCase()), | 
					
						
							|  |  |  |     subTitle: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.subTitle.toLowerCase()), | 
					
						
							|  |  |  |     mainStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.mainStat.toLowerCase()), | 
					
						
							|  |  |  |     secondaryStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.secondaryStat.toLowerCase()), | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |     arc: findFieldsByPrefix(nodes, NodeGraphDataFrameFieldNames.arc), | 
					
						
							|  |  |  |     details: findFieldsByPrefix(nodes, NodeGraphDataFrameFieldNames.detail), | 
					
						
							|  |  |  |     color: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.color), | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function getEdgeFields(edges: DataFrame) { | 
					
						
							| 
									
										
										
										
											2022-04-13 23:18:40 +08:00
										 |  |  |   const normalizedFrames = { | 
					
						
							|  |  |  |     ...edges, | 
					
						
							|  |  |  |     fields: edges.fields.map((field) => ({ ...field, name: field.name.toLowerCase() })), | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   const fieldsCache = new FieldCache(normalizedFrames); | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |   return { | 
					
						
							| 
									
										
										
										
											2022-04-13 23:18:40 +08:00
										 |  |  |     id: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.id.toLowerCase()), | 
					
						
							|  |  |  |     source: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.source.toLowerCase()), | 
					
						
							|  |  |  |     target: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.target.toLowerCase()), | 
					
						
							|  |  |  |     mainStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.mainStat.toLowerCase()), | 
					
						
							|  |  |  |     secondaryStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.secondaryStat.toLowerCase()), | 
					
						
							|  |  |  |     details: findFieldsByPrefix(edges, NodeGraphDataFrameFieldNames.detail.toLowerCase()), | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function findFieldsByPrefix(frame: DataFrame, prefix: string) { | 
					
						
							| 
									
										
										
										
											2021-01-20 14:59:48 +08:00
										 |  |  |   return frame.fields.filter((f) => f.name.match(new RegExp('^' + prefix))); | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Transform nodes and edges dataframes into array of objects that the layout code can then work with. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  | export function processNodes( | 
					
						
							|  |  |  |   nodes: DataFrame | undefined, | 
					
						
							|  |  |  |   edges: DataFrame | undefined, | 
					
						
							| 
									
										
										
										
											2021-05-03 15:45:54 +08:00
										 |  |  |   theme: GrafanaTheme2 | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  | ): { | 
					
						
							|  |  |  |   nodes: NodeDatum[]; | 
					
						
							|  |  |  |   edges: EdgeDatum[]; | 
					
						
							|  |  |  |   legend?: Array<{ | 
					
						
							|  |  |  |     color: string; | 
					
						
							|  |  |  |     name: string; | 
					
						
							|  |  |  |   }>; | 
					
						
							|  |  |  | } { | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |   if (!nodes) { | 
					
						
							|  |  |  |     return { nodes: [], edges: [] }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const nodeFields = getNodeFields(nodes); | 
					
						
							|  |  |  |   if (!nodeFields.id) { | 
					
						
							|  |  |  |     throw new Error('id field is required for nodes data frame.'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const nodesMap = | 
					
						
							|  |  |  |     nodeFields.id.values.toArray().reduce<{ [id: string]: NodeDatum }>((acc, id, index) => { | 
					
						
							|  |  |  |       acc[id] = { | 
					
						
							|  |  |  |         id: id, | 
					
						
							|  |  |  |         title: nodeFields.title?.values.get(index) || '', | 
					
						
							|  |  |  |         subTitle: nodeFields.subTitle ? nodeFields.subTitle.values.get(index) : '', | 
					
						
							|  |  |  |         dataFrameRowIndex: index, | 
					
						
							|  |  |  |         incoming: 0, | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |         mainStat: nodeFields.mainStat, | 
					
						
							|  |  |  |         secondaryStat: nodeFields.secondaryStat, | 
					
						
							|  |  |  |         arcSections: nodeFields.arc, | 
					
						
							| 
									
										
										
										
											2021-05-18 22:30:27 +08:00
										 |  |  |         color: nodeFields.color, | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |       }; | 
					
						
							|  |  |  |       return acc; | 
					
						
							|  |  |  |     }, {}) || {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let edgesMapped: EdgeDatum[] = []; | 
					
						
							|  |  |  |   // We may not have edges in case of single node
 | 
					
						
							|  |  |  |   if (edges) { | 
					
						
							|  |  |  |     const edgeFields = getEdgeFields(edges); | 
					
						
							|  |  |  |     if (!edgeFields.id) { | 
					
						
							|  |  |  |       throw new Error('id field is required for edges data frame.'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     edgesMapped = edgeFields.id.values.toArray().map((id, index) => { | 
					
						
							|  |  |  |       const target = edgeFields.target?.values.get(index); | 
					
						
							|  |  |  |       const source = edgeFields.source?.values.get(index); | 
					
						
							|  |  |  |       // We are adding incoming edges count so we can later on find out which nodes are the roots
 | 
					
						
							|  |  |  |       nodesMap[target].incoming++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return { | 
					
						
							|  |  |  |         id, | 
					
						
							|  |  |  |         dataFrameRowIndex: index, | 
					
						
							|  |  |  |         source, | 
					
						
							|  |  |  |         target, | 
					
						
							|  |  |  |         mainStat: edgeFields.mainStat ? statToString(edgeFields.mainStat, index) : '', | 
					
						
							|  |  |  |         secondaryStat: edgeFields.secondaryStat ? statToString(edgeFields.secondaryStat, index) : '', | 
					
						
							|  |  |  |       } as EdgeDatum; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     nodes: Object.values(nodesMap), | 
					
						
							|  |  |  |     edges: edgesMapped || [], | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  |     legend: nodeFields.arc.map((f) => { | 
					
						
							|  |  |  |       return { | 
					
						
							|  |  |  |         color: f.config.color?.fixedColor ?? '', | 
					
						
							|  |  |  |         name: f.config.displayName || f.name, | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     }), | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  | export function statToString(field: Field, index: number) { | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |   if (field.type === FieldType.string) { | 
					
						
							|  |  |  |     return field.values.get(index); | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     const decimals = field.config.decimals || 2; | 
					
						
							|  |  |  |     const val = field.values.get(index); | 
					
						
							|  |  |  |     if (Number.isFinite(val)) { | 
					
						
							|  |  |  |       return field.values.get(index).toFixed(decimals) + (field.config.unit ? ' ' + field.config.unit : ''); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       return ''; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Utilities mainly for testing | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function makeNodesDataFrame(count: number) { | 
					
						
							|  |  |  |   const frame = nodesFrame(); | 
					
						
							|  |  |  |   for (let i = 0; i < count; i++) { | 
					
						
							|  |  |  |     frame.add(makeNode(i)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return frame; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function makeNode(index: number) { | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     id: index.toString(), | 
					
						
							|  |  |  |     title: `service:${index}`, | 
					
						
							| 
									
										
										
										
											2022-04-13 23:18:40 +08:00
										 |  |  |     subtitle: 'service', | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |     arc__success: 0.5, | 
					
						
							|  |  |  |     arc__errors: 0.5, | 
					
						
							| 
									
										
										
										
											2022-04-13 23:18:40 +08:00
										 |  |  |     mainstat: 0.1, | 
					
						
							|  |  |  |     secondarystat: 2, | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  |     color: 0.5, | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function nodesFrame() { | 
					
						
							|  |  |  |   const fields: any = { | 
					
						
							|  |  |  |     [NodeGraphDataFrameFieldNames.id]: { | 
					
						
							|  |  |  |       values: new ArrayVector(), | 
					
						
							|  |  |  |       type: FieldType.string, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     [NodeGraphDataFrameFieldNames.title]: { | 
					
						
							|  |  |  |       values: new ArrayVector(), | 
					
						
							|  |  |  |       type: FieldType.string, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     [NodeGraphDataFrameFieldNames.subTitle]: { | 
					
						
							|  |  |  |       values: new ArrayVector(), | 
					
						
							|  |  |  |       type: FieldType.string, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     [NodeGraphDataFrameFieldNames.mainStat]: { | 
					
						
							|  |  |  |       values: new ArrayVector(), | 
					
						
							|  |  |  |       type: FieldType.number, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     [NodeGraphDataFrameFieldNames.secondaryStat]: { | 
					
						
							|  |  |  |       values: new ArrayVector(), | 
					
						
							|  |  |  |       type: FieldType.number, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     [NodeGraphDataFrameFieldNames.arc + 'success']: { | 
					
						
							|  |  |  |       values: new ArrayVector(), | 
					
						
							|  |  |  |       type: FieldType.number, | 
					
						
							|  |  |  |       config: { color: { fixedColor: 'green' } }, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     [NodeGraphDataFrameFieldNames.arc + 'errors']: { | 
					
						
							|  |  |  |       values: new ArrayVector(), | 
					
						
							|  |  |  |       type: FieldType.number, | 
					
						
							|  |  |  |       config: { color: { fixedColor: 'red' } }, | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2021-03-31 23:56:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     [NodeGraphDataFrameFieldNames.color]: { | 
					
						
							|  |  |  |       values: new ArrayVector(), | 
					
						
							|  |  |  |       type: FieldType.number, | 
					
						
							|  |  |  |       config: { color: { mode: 'continuous-GrYlRd' } }, | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return new MutableDataFrame({ | 
					
						
							|  |  |  |     name: 'nodes', | 
					
						
							| 
									
										
										
										
											2021-01-20 14:59:48 +08:00
										 |  |  |     fields: Object.keys(fields).map((key) => ({ | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |       ...fields[key], | 
					
						
							|  |  |  |       name: key, | 
					
						
							|  |  |  |     })), | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function makeEdgesDataFrame(edges: Array<[number, number]>) { | 
					
						
							|  |  |  |   const frame = edgesFrame(); | 
					
						
							|  |  |  |   for (const edge of edges) { | 
					
						
							|  |  |  |     frame.add({ | 
					
						
							|  |  |  |       id: edge[0] + '--' + edge[1], | 
					
						
							|  |  |  |       source: edge[0].toString(), | 
					
						
							|  |  |  |       target: edge[1].toString(), | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return frame; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function edgesFrame() { | 
					
						
							|  |  |  |   const fields: any = { | 
					
						
							|  |  |  |     [NodeGraphDataFrameFieldNames.id]: { | 
					
						
							|  |  |  |       values: new ArrayVector(), | 
					
						
							|  |  |  |       type: FieldType.string, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     [NodeGraphDataFrameFieldNames.source]: { | 
					
						
							|  |  |  |       values: new ArrayVector(), | 
					
						
							|  |  |  |       type: FieldType.string, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     [NodeGraphDataFrameFieldNames.target]: { | 
					
						
							|  |  |  |       values: new ArrayVector(), | 
					
						
							|  |  |  |       type: FieldType.string, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return new MutableDataFrame({ | 
					
						
							|  |  |  |     name: 'edges', | 
					
						
							| 
									
										
										
										
											2021-01-20 14:59:48 +08:00
										 |  |  |     fields: Object.keys(fields).map((key) => ({ | 
					
						
							| 
									
										
										
										
											2021-01-19 23:34:43 +08:00
										 |  |  |       ...fields[key], | 
					
						
							|  |  |  |       name: key, | 
					
						
							|  |  |  |     })), | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-05-12 22:04:21 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export interface Bounds { | 
					
						
							|  |  |  |   top: number; | 
					
						
							|  |  |  |   right: number; | 
					
						
							|  |  |  |   bottom: number; | 
					
						
							|  |  |  |   left: number; | 
					
						
							|  |  |  |   center: { | 
					
						
							|  |  |  |     x: number; | 
					
						
							|  |  |  |     y: number; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Get bounds of the graph meaning the extent of the nodes in all directions. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export function graphBounds(nodes: NodeDatum[]): Bounds { | 
					
						
							|  |  |  |   if (nodes.length === 0) { | 
					
						
							|  |  |  |     return { top: 0, right: 0, bottom: 0, left: 0, center: { x: 0, y: 0 } }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const bounds = nodes.reduce( | 
					
						
							|  |  |  |     (acc, node) => { | 
					
						
							|  |  |  |       if (node.x! > acc.right) { | 
					
						
							|  |  |  |         acc.right = node.x!; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (node.x! < acc.left) { | 
					
						
							|  |  |  |         acc.left = node.x!; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (node.y! > acc.bottom) { | 
					
						
							|  |  |  |         acc.bottom = node.y!; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (node.y! < acc.top) { | 
					
						
							|  |  |  |         acc.top = node.y!; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return acc; | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     { top: Infinity, right: -Infinity, bottom: -Infinity, left: Infinity } | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const y = bounds.top + (bounds.bottom - bounds.top) / 2; | 
					
						
							|  |  |  |   const x = bounds.left + (bounds.right - bounds.left) / 2; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     ...bounds, | 
					
						
							|  |  |  |     center: { | 
					
						
							|  |  |  |       x, | 
					
						
							|  |  |  |       y, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-01-28 01:19:54 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-23 21:20:56 +08:00
										 |  |  | export function getNodeGraphDataFrames(frames: DataFrame[], options?: NodeGraphOptions) { | 
					
						
							| 
									
										
										
										
											2022-01-28 01:19:54 +08:00
										 |  |  |   // TODO: this not in sync with how other types of responses are handled. Other types have a query response
 | 
					
						
							|  |  |  |   //  processing pipeline which ends up populating redux state with proper data. As we move towards more dataFrame
 | 
					
						
							|  |  |  |   //  oriented API it seems like a better direction to move such processing into to visualisations and do minimal
 | 
					
						
							|  |  |  |   //  and lazy processing here. Needs bigger refactor so keeping nodeGraph and Traces as they are for now.
 | 
					
						
							| 
									
										
										
										
											2022-06-23 21:20:56 +08:00
										 |  |  |   let nodeGraphFrames = frames.filter((frame) => { | 
					
						
							| 
									
										
										
										
											2022-03-03 21:42:08 +08:00
										 |  |  |     if (frame.meta?.preferredVisualisationType === 'nodeGraph') { | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-04-13 23:18:40 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (frame.name === 'nodes' || frame.name === 'edges' || frame.refId === 'nodes' || frame.refId === 'edges') { | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const fieldsCache = new FieldCache(frame); | 
					
						
							|  |  |  |     if (fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.id)) { | 
					
						
							| 
									
										
										
										
											2022-03-03 21:42:08 +08:00
										 |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2022-06-23 21:20:56 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // If panel options are provided, interpolate their values in to the data frames
 | 
					
						
							|  |  |  |   if (options) { | 
					
						
							|  |  |  |     nodeGraphFrames = applyOptionsToFrames(nodeGraphFrames, options); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return nodeGraphFrames; | 
					
						
							| 
									
										
										
										
											2022-01-28 01:19:54 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-06-23 21:20:56 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export const applyOptionsToFrames = (frames: DataFrame[], options: NodeGraphOptions): DataFrame[] => { | 
					
						
							|  |  |  |   return frames.map((frame) => { | 
					
						
							|  |  |  |     const fieldsCache = new FieldCache(frame); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Edges frame has source which can be used to identify nodes vs edges frames
 | 
					
						
							|  |  |  |     if (fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.source.toLowerCase())) { | 
					
						
							|  |  |  |       if (options?.edges?.mainStatUnit) { | 
					
						
							|  |  |  |         const field = frame.fields.find((field) => field.name.toLowerCase() === NodeGraphDataFrameFieldNames.mainStat); | 
					
						
							|  |  |  |         if (field) { | 
					
						
							|  |  |  |           field.config = { ...field.config, unit: options.edges.mainStatUnit }; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (options?.edges?.secondaryStatUnit) { | 
					
						
							|  |  |  |         const field = frame.fields.find( | 
					
						
							|  |  |  |           (field) => field.name.toLowerCase() === NodeGraphDataFrameFieldNames.secondaryStat | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         if (field) { | 
					
						
							|  |  |  |           field.config = { ...field.config, unit: options.edges.secondaryStatUnit }; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       if (options?.nodes?.mainStatUnit) { | 
					
						
							|  |  |  |         const field = frame.fields.find((field) => field.name.toLowerCase() === NodeGraphDataFrameFieldNames.mainStat); | 
					
						
							|  |  |  |         if (field) { | 
					
						
							|  |  |  |           field.config = { ...field.config, unit: options.nodes.mainStatUnit }; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (options?.nodes?.secondaryStatUnit) { | 
					
						
							|  |  |  |         const field = frame.fields.find( | 
					
						
							|  |  |  |           (field) => field.name.toLowerCase() === NodeGraphDataFrameFieldNames.secondaryStat | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         if (field) { | 
					
						
							|  |  |  |           field.config = { ...field.config, unit: options.nodes.secondaryStatUnit }; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (options?.nodes?.arcs?.length) { | 
					
						
							|  |  |  |         for (const arc of options.nodes.arcs) { | 
					
						
							|  |  |  |           const field = frame.fields.find((field) => field.name.toLowerCase() === arc.field); | 
					
						
							|  |  |  |           if (field && arc.color) { | 
					
						
							|  |  |  |             field.config = { ...field.config, color: { fixedColor: arc.color, mode: FieldColorModeId.Fixed } }; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return frame; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2022-07-12 20:14:45 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Returns an array of node ids which are connected to a given edge
 | 
					
						
							|  |  |  | export const findConnectedNodesForEdge = (nodes: NodeDatum[], edges: EdgeDatum[], edgeId: string): string[] => { | 
					
						
							|  |  |  |   const edge = edges.find((edge) => edge.id === edgeId); | 
					
						
							|  |  |  |   if (edge) { | 
					
						
							|  |  |  |     return [ | 
					
						
							|  |  |  |       ...new Set(nodes.filter((node) => edge.source === node.id || edge.target === node.id).map((node) => node.id)), | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return []; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Returns an array of node ids which are connected to a given node
 | 
					
						
							|  |  |  | export const findConnectedNodesForNode = (nodes: NodeDatum[], edges: EdgeDatum[], nodeId: string): string[] => { | 
					
						
							|  |  |  |   const node = nodes.find((node) => node.id === nodeId); | 
					
						
							|  |  |  |   if (node) { | 
					
						
							|  |  |  |     const linkedEdges = edges.filter((edge) => edge.source === node.id || edge.target === node.id); | 
					
						
							|  |  |  |     return [ | 
					
						
							|  |  |  |       ...new Set( | 
					
						
							|  |  |  |         linkedEdges.flatMap((edge) => | 
					
						
							|  |  |  |           nodes.filter((n) => edge.source === n.id || edge.target === n.id).map((n) => n.id) | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return []; | 
					
						
							|  |  |  | }; |