mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NodeGraph: Edge color and stroke-dasharray support (#83855)
* Adds color and stroke-dasharray support for node graph edges Adds support for providing color, highlighted color, and visual display of node graph edges as dashed lines via stroke-dasharray. * Updates node graph documentation * Updates documentation Adds default for `highlightedColor` * Update docs/sources/panels-visualizations/visualizations/node-graph/index.md Co-authored-by: Fabrizio <135109076+fabrizio-grafana@users.noreply.github.com> * Update packages/grafana-data/src/utils/nodeGraph.ts Co-authored-by: Fabrizio <135109076+fabrizio-grafana@users.noreply.github.com> * Update docs/sources/panels-visualizations/visualizations/node-graph/index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Removes highlightedColor; deprecates highlighted Per [request](https://github.com/grafana/grafana/pull/83855#issuecomment-1999810826), deprecates `highlighted` in code and documentation, and removes `highlightedColor` as an additional property. `highlighted` will continue to be supported in its original state (makes the edge red), but is superseded if `color` is provided. * Update types.ts Missed a file in my last commit. Removes `highlightedColor` and deprecates `highlighted`. * Add test scenario in test data source --------- Co-authored-by: Fabrizio <135109076+fabrizio-grafana@users.noreply.github.com> Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Co-authored-by: Andrej Ocenas <mr.ocenas@gmail.com>
This commit is contained in:
@@ -4719,9 +4719,6 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||||
],
|
],
|
||||||
"public/app/plugins/datasource/grafana-testdata-datasource/nodeGraphUtils.ts:5381": [
|
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
|
||||||
],
|
|
||||||
"public/app/plugins/datasource/grafana-testdata-datasource/runStreams.ts:5381": [
|
"public/app/plugins/datasource/grafana-testdata-datasource/runStreams.ts:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||||
],
|
],
|
||||||
|
@@ -104,13 +104,21 @@ Required fields:
|
|||||||
|
|
||||||
Optional fields:
|
Optional fields:
|
||||||
|
|
||||||
| Field name | Type | Description |
|
| Field name | Type | Description |
|
||||||
| ------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| --------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| mainstat | string/number | First stat shown in the overlay when hovering over the edge. It can be a string showing the value as is or it can be a number. If it is a number, any unit associated with that field is also shown |
|
| mainstat | string/number | First stat shown in the overlay when hovering over the edge. It can be a string showing the value as is or it can be a number. If it is a number, any unit associated with that field is also shown |
|
||||||
| secondarystat | string/number | Same as mainStat, but shown right under it. |
|
| secondarystat | string/number | Same as mainStat, but shown right under it. |
|
||||||
| detail\_\_\* | string/number | Any field prefixed with `detail__` will be shown in the header of context menu when clicked on the edge. Use `config.displayName` for more human readable label. |
|
| detail\_\_\* | string/number | Any field prefixed with `detail__` will be shown in the header of context menu when clicked on the edge. Use `config.displayName` for more human readable label. |
|
||||||
| thickness | number | The thickness of the edge. Default: `1` |
|
| thickness | number | The thickness of the edge. Default: `1` |
|
||||||
| highlighted | boolean | Sets whether the edge should be highlighted. Useful, for example, to represent a specific path in the graph by highlighting several nodes and edges. Default: `false` |
|
| highlighted | boolean | Sets whether the edge should be highlighted. Useful, for example, to represent a specific path in the graph by highlighting several nodes and edges. Default: `false` |
|
||||||
|
| color | string | Sets the default color of the edge. It can be an acceptable HTML color string. Default: `#999` |
|
||||||
|
| strokeDasharray | string | Sets the pattern of dashes and gaps used to render the edge. If unset, a solid line is used as edge. For more information and examples, refer to the [`stroke-dasharray` MDN documentation](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray). |
|
||||||
|
|
||||||
|
{{< admonition type="caution" >}}
|
||||||
|
Starting with 10.5, `highlighted` is deprecated.
|
||||||
|
It will be removed in a future release.
|
||||||
|
Use `color` to indicate a highlighted edge state instead.
|
||||||
|
{{< /admonition >}}
|
||||||
|
|
||||||
### Nodes data frame structure
|
### Nodes data frame structure
|
||||||
|
|
||||||
|
@@ -15,7 +15,7 @@ export enum NodeGraphDataFrameFieldNames {
|
|||||||
// grafana/ui [nodes]
|
// grafana/ui [nodes]
|
||||||
icon = 'icon',
|
icon = 'icon',
|
||||||
// Defines a single color if string (hex or html named value) or color mode config can be used as threshold or
|
// Defines a single color if string (hex or html named value) or color mode config can be used as threshold or
|
||||||
// gradient. arc__ fields must not be defined if used [nodes]
|
// gradient. arc__ fields must not be defined if used [nodes + edges]
|
||||||
color = 'color',
|
color = 'color',
|
||||||
|
|
||||||
// Id of the source node [required] [edges]
|
// Id of the source node [required] [edges]
|
||||||
@@ -32,6 +32,10 @@ export enum NodeGraphDataFrameFieldNames {
|
|||||||
// Thickness of the edge [edges]
|
// Thickness of the edge [edges]
|
||||||
thickness = 'thickness',
|
thickness = 'thickness',
|
||||||
|
|
||||||
// Whether the node or edge should be highlighted (e.g., shown in red) in the UI
|
// Whether the node or edge should be highlighted (e.g., shown in red) in the UI [nodes + edges]
|
||||||
|
// @deprecated -- for edges use color instead
|
||||||
highlighted = 'highlighted',
|
highlighted = 'highlighted',
|
||||||
|
|
||||||
|
// Defines the stroke dash array for the edge [edges]. See SVG strokeDasharray definition for syntax.
|
||||||
|
strokeDasharray = 'strokedasharray',
|
||||||
}
|
}
|
||||||
|
@@ -75,7 +75,7 @@ export interface SimulationQuery {
|
|||||||
export interface NodesQuery {
|
export interface NodesQuery {
|
||||||
count?: number;
|
count?: number;
|
||||||
seed?: number;
|
seed?: number;
|
||||||
type?: ('random' | 'response_small' | 'response_medium' | 'random edges');
|
type?: ('random' | 'response_small' | 'response_medium' | 'random edges' | 'feature_showcase');
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface USAQuery {
|
export interface USAQuery {
|
||||||
|
@@ -11,10 +11,11 @@ package dataquery
|
|||||||
|
|
||||||
// Defines values for NodesQueryType.
|
// Defines values for NodesQueryType.
|
||||||
const (
|
const (
|
||||||
NodesQueryTypeRandom NodesQueryType = "random"
|
NodesQueryTypeFeatureShowcase NodesQueryType = "feature_showcase"
|
||||||
NodesQueryTypeRandomEdges NodesQueryType = "random edges"
|
NodesQueryTypeRandom NodesQueryType = "random"
|
||||||
NodesQueryTypeResponseMedium NodesQueryType = "response_medium"
|
NodesQueryTypeRandomEdges NodesQueryType = "random edges"
|
||||||
NodesQueryTypeResponseSmall NodesQueryType = "response_small"
|
NodesQueryTypeResponseMedium NodesQueryType = "response_medium"
|
||||||
|
NodesQueryTypeResponseSmall NodesQueryType = "response_small"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defines values for StreamingQueryType.
|
// Defines values for StreamingQueryType.
|
||||||
|
@@ -54,4 +54,10 @@ export function NodeGraphEditor({ query, onChange }: Props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: Array<NodesQuery['type']> = ['random', 'response_small', 'response_medium', 'random edges'];
|
const options: Array<NodesQuery['type']> = [
|
||||||
|
'random',
|
||||||
|
'response_small',
|
||||||
|
'response_medium',
|
||||||
|
'random edges',
|
||||||
|
'feature_showcase',
|
||||||
|
];
|
||||||
|
@@ -83,7 +83,7 @@ composableKinds: DataQuery: {
|
|||||||
} @cuetsy(kind="interface")
|
} @cuetsy(kind="interface")
|
||||||
|
|
||||||
#NodesQuery: {
|
#NodesQuery: {
|
||||||
type?: "random" | "response_small" | "response_medium" | "random edges"
|
type?: "random" | "response_small" | "response_medium" | "random edges" | "feature_showcase"
|
||||||
count?: int64
|
count?: int64
|
||||||
seed?: int64
|
seed?: int64
|
||||||
} @cuetsy(kind="interface")
|
} @cuetsy(kind="interface")
|
||||||
|
@@ -73,7 +73,7 @@ export interface SimulationQuery {
|
|||||||
export interface NodesQuery {
|
export interface NodesQuery {
|
||||||
count?: number;
|
count?: number;
|
||||||
seed?: number;
|
seed?: number;
|
||||||
type?: ('random' | 'response_small' | 'response_medium' | 'random edges');
|
type?: ('random' | 'response_small' | 'response_medium' | 'random edges' | 'feature_showcase');
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface USAQuery {
|
export interface USAQuery {
|
||||||
|
@@ -22,7 +22,7 @@ import { DataSourceWithBackend, getBackendSrv, getGrafanaLiveSrv, getTemplateSrv
|
|||||||
|
|
||||||
import { Scenario, TestDataDataQuery, TestDataQueryType } from './dataquery.gen';
|
import { Scenario, TestDataDataQuery, TestDataQueryType } from './dataquery.gen';
|
||||||
import { queryMetricTree } from './metricTree';
|
import { queryMetricTree } from './metricTree';
|
||||||
import { generateRandomEdges, generateRandomNodes, savedNodesResponse } from './nodeGraphUtils';
|
import { generateRandomEdges, generateRandomNodes, generateShowcaseData, savedNodesResponse } from './nodeGraphUtils';
|
||||||
import { runStream } from './runStreams';
|
import { runStream } from './runStreams';
|
||||||
import { flameGraphData, flameGraphDataDiff } from './testData/flameGraphResponse';
|
import { flameGraphData, flameGraphDataDiff } from './testData/flameGraphResponse';
|
||||||
import { TestDataVariableSupport } from './variables';
|
import { TestDataVariableSupport } from './variables';
|
||||||
@@ -237,6 +237,9 @@ export class TestDataDataSource extends DataSourceWithBackend<TestDataDataQuery>
|
|||||||
const type = target.nodes?.type || 'random';
|
const type = target.nodes?.type || 'random';
|
||||||
let frames: DataFrame[];
|
let frames: DataFrame[];
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case 'feature_showcase':
|
||||||
|
frames = generateShowcaseData();
|
||||||
|
break;
|
||||||
case 'random':
|
case 'random':
|
||||||
frames = generateRandomNodes(target.nodes?.count, target.nodes?.seed);
|
frames = generateRandomNodes(target.nodes?.count, target.nodes?.seed);
|
||||||
break;
|
break;
|
||||||
|
@@ -7,6 +7,7 @@ import {
|
|||||||
MutableDataFrame,
|
MutableDataFrame,
|
||||||
NodeGraphDataFrameFieldNames,
|
NodeGraphDataFrameFieldNames,
|
||||||
DataFrame,
|
DataFrame,
|
||||||
|
addRow,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
|
||||||
import * as serviceMapResponseSmall from './testData/serviceMapResponse';
|
import * as serviceMapResponseSmall from './testData/serviceMapResponse';
|
||||||
@@ -56,7 +57,74 @@ export function generateRandomNodes(count = 10, seed?: number) {
|
|||||||
nodes[sourceIndex].edges.push(nodes[targetIndex].id);
|
nodes[sourceIndex].edges.push(nodes[targetIndex].id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeFields: Record<string, Omit<FieldDTO, 'name'> & { values: any[] }> = {
|
const { nodesFields, nodesFrame, edgesFrame } = makeDataFrames();
|
||||||
|
|
||||||
|
const edgesSet = new Set();
|
||||||
|
for (const node of nodes) {
|
||||||
|
nodesFields.id.values.push(node.id);
|
||||||
|
nodesFields.title.values.push(node.title);
|
||||||
|
nodesFields[NodeGraphDataFrameFieldNames.subTitle].values.push(node.subTitle);
|
||||||
|
nodesFields[NodeGraphDataFrameFieldNames.mainStat].values.push(node.stat1);
|
||||||
|
nodesFields[NodeGraphDataFrameFieldNames.secondaryStat].values.push(node.stat2);
|
||||||
|
nodesFields.arc__success.values.push(node.success);
|
||||||
|
nodesFields.arc__errors.values.push(node.error);
|
||||||
|
const rnd = Math.random();
|
||||||
|
nodesFields[NodeGraphDataFrameFieldNames.icon].values.push(rnd > 0.9 ? 'database' : rnd < 0.1 ? 'cloud' : '');
|
||||||
|
nodesFields[NodeGraphDataFrameFieldNames.nodeRadius].values.push(Math.max(rnd * 100, 30)); // ensure a minimum radius of 30 or icons will not fit well in the node
|
||||||
|
nodesFields[NodeGraphDataFrameFieldNames.highlighted].values.push(Math.random() > 0.5);
|
||||||
|
|
||||||
|
for (const edge of node.edges) {
|
||||||
|
const id = `${node.id}--${edge}`;
|
||||||
|
// We can have duplicate edges when we added some more by random
|
||||||
|
if (edgesSet.has(id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
edgesSet.add(id);
|
||||||
|
edgesFrame.fields[0].values.push(`${node.id}--${edge}`);
|
||||||
|
edgesFrame.fields[1].values.push(node.id);
|
||||||
|
edgesFrame.fields[2].values.push(edge);
|
||||||
|
edgesFrame.fields[3].values.push(Math.random() * 100);
|
||||||
|
edgesFrame.fields[4].values.push(Math.random() > 0.5);
|
||||||
|
edgesFrame.fields[5].values.push(Math.ceil(Math.random() * 15));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
edgesFrame.length = edgesFrame.fields[0].values.length;
|
||||||
|
|
||||||
|
return [nodesFrame, edgesFrame];
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeRandomNode(index: number) {
|
||||||
|
const success = Math.random();
|
||||||
|
const error = 1 - success;
|
||||||
|
return {
|
||||||
|
id: `service:${index}`,
|
||||||
|
title: `service:${index}`,
|
||||||
|
subTitle: 'service',
|
||||||
|
success,
|
||||||
|
error,
|
||||||
|
stat1: Math.random(),
|
||||||
|
stat2: Math.random(),
|
||||||
|
edges: [],
|
||||||
|
highlighted: Math.random() > 0.5,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function savedNodesResponse(size: 'small' | 'medium'): [DataFrame, DataFrame] {
|
||||||
|
const response = size === 'small' ? serviceMapResponseSmall : serviceMapResponsMedium;
|
||||||
|
return [new MutableDataFrame(response.nodes), new MutableDataFrame(response.edges)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates node graph data but only returns the edges
|
||||||
|
export function generateRandomEdges(count = 10, seed = 1) {
|
||||||
|
return generateRandomNodes(count, seed)[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeDataFrames(): {
|
||||||
|
nodesFrame: DataFrame;
|
||||||
|
edgesFrame: DataFrame;
|
||||||
|
nodesFields: Record<string, Omit<FieldDTO, 'name'> & { values: unknown[] }>;
|
||||||
|
} {
|
||||||
|
const nodesFields: Record<string, Omit<FieldDTO, 'name'> & { values: unknown[] }> = {
|
||||||
[NodeGraphDataFrameFieldNames.id]: {
|
[NodeGraphDataFrameFieldNames.id]: {
|
||||||
values: [],
|
values: [],
|
||||||
type: FieldType.string,
|
type: FieldType.string,
|
||||||
@@ -114,12 +182,20 @@ export function generateRandomNodes(count = 10, seed?: number) {
|
|||||||
values: [],
|
values: [],
|
||||||
type: FieldType.boolean,
|
type: FieldType.boolean,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[NodeGraphDataFrameFieldNames.detail + 'test_value']: {
|
||||||
|
values: [],
|
||||||
|
config: {
|
||||||
|
displayName: 'Test value',
|
||||||
|
},
|
||||||
|
type: FieldType.number,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodeFrame = new MutableDataFrame({
|
const nodesFrame = new MutableDataFrame({
|
||||||
name: 'nodes',
|
name: 'nodes',
|
||||||
fields: Object.keys(nodeFields).map((key) => ({
|
fields: Object.keys(nodesFields).map((key) => ({
|
||||||
...nodeFields[key],
|
...nodesFields[key],
|
||||||
name: key,
|
name: key,
|
||||||
})),
|
})),
|
||||||
meta: { preferredVisualisationType: 'nodeGraph' },
|
meta: { preferredVisualisationType: 'nodeGraph' },
|
||||||
@@ -134,67 +210,106 @@ export function generateRandomNodes(count = 10, seed?: number) {
|
|||||||
{ name: NodeGraphDataFrameFieldNames.mainStat, values: [], type: FieldType.number, config: {} },
|
{ name: NodeGraphDataFrameFieldNames.mainStat, values: [], type: FieldType.number, config: {} },
|
||||||
{ name: NodeGraphDataFrameFieldNames.highlighted, values: [], type: FieldType.boolean, config: {} },
|
{ name: NodeGraphDataFrameFieldNames.highlighted, values: [], type: FieldType.boolean, config: {} },
|
||||||
{ name: NodeGraphDataFrameFieldNames.thickness, values: [], type: FieldType.number, config: {} },
|
{ name: NodeGraphDataFrameFieldNames.thickness, values: [], type: FieldType.number, config: {} },
|
||||||
|
{ name: NodeGraphDataFrameFieldNames.color, values: [], type: FieldType.string, config: {} },
|
||||||
|
{ name: NodeGraphDataFrameFieldNames.strokeDasharray, values: [], type: FieldType.string, config: {} },
|
||||||
],
|
],
|
||||||
meta: { preferredVisualisationType: 'nodeGraph' },
|
meta: { preferredVisualisationType: 'nodeGraph' },
|
||||||
length: 0,
|
length: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const edgesSet = new Set();
|
return { nodesFrame, edgesFrame, nodesFields };
|
||||||
for (const node of nodes) {
|
|
||||||
nodeFields.id.values.push(node.id);
|
|
||||||
nodeFields.title.values.push(node.title);
|
|
||||||
nodeFields[NodeGraphDataFrameFieldNames.subTitle].values.push(node.subTitle);
|
|
||||||
nodeFields[NodeGraphDataFrameFieldNames.mainStat].values.push(node.stat1);
|
|
||||||
nodeFields[NodeGraphDataFrameFieldNames.secondaryStat].values.push(node.stat2);
|
|
||||||
nodeFields.arc__success.values.push(node.success);
|
|
||||||
nodeFields.arc__errors.values.push(node.error);
|
|
||||||
const rnd = Math.random();
|
|
||||||
nodeFields[NodeGraphDataFrameFieldNames.icon].values.push(rnd > 0.9 ? 'database' : rnd < 0.1 ? 'cloud' : '');
|
|
||||||
nodeFields[NodeGraphDataFrameFieldNames.nodeRadius].values.push(Math.max(rnd * 100, 30)); // ensure a minimum radius of 30 or icons will not fit well in the node
|
|
||||||
nodeFields[NodeGraphDataFrameFieldNames.highlighted].values.push(Math.random() > 0.5);
|
|
||||||
|
|
||||||
for (const edge of node.edges) {
|
|
||||||
const id = `${node.id}--${edge}`;
|
|
||||||
// We can have duplicate edges when we added some more by random
|
|
||||||
if (edgesSet.has(id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
edgesSet.add(id);
|
|
||||||
edgesFrame.fields[0].values.push(`${node.id}--${edge}`);
|
|
||||||
edgesFrame.fields[1].values.push(node.id);
|
|
||||||
edgesFrame.fields[2].values.push(edge);
|
|
||||||
edgesFrame.fields[3].values.push(Math.random() * 100);
|
|
||||||
edgesFrame.fields[4].values.push(Math.random() > 0.5);
|
|
||||||
edgesFrame.fields[5].values.push(Math.ceil(Math.random() * 15));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
edgesFrame.length = edgesFrame.fields[0].values.length;
|
|
||||||
|
|
||||||
return [nodeFrame, edgesFrame];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeRandomNode(index: number) {
|
export function generateShowcaseData() {
|
||||||
const success = Math.random();
|
const { nodesFrame, edgesFrame } = makeDataFrames();
|
||||||
const error = 1 - success;
|
|
||||||
return {
|
|
||||||
id: `service:${index}`,
|
|
||||||
title: `service:${index}`,
|
|
||||||
subTitle: 'service',
|
|
||||||
success,
|
|
||||||
error,
|
|
||||||
stat1: Math.random(),
|
|
||||||
stat2: Math.random(),
|
|
||||||
edges: [],
|
|
||||||
highlighted: Math.random() > 0.5,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function savedNodesResponse(size: 'small' | 'medium'): [DataFrame, DataFrame] {
|
addRow(nodesFrame, {
|
||||||
const response = size === 'small' ? serviceMapResponseSmall : serviceMapResponsMedium;
|
[NodeGraphDataFrameFieldNames.id]: 'root',
|
||||||
return [new MutableDataFrame(response.nodes), new MutableDataFrame(response.edges)];
|
[NodeGraphDataFrameFieldNames.title]: 'root',
|
||||||
}
|
[NodeGraphDataFrameFieldNames.subTitle]: 'client',
|
||||||
|
[NodeGraphDataFrameFieldNames.mainStat]: 1234,
|
||||||
|
[NodeGraphDataFrameFieldNames.secondaryStat]: 5678,
|
||||||
|
arc__success: 0.5,
|
||||||
|
arc__errors: 0.5,
|
||||||
|
[NodeGraphDataFrameFieldNames.icon]: '',
|
||||||
|
[NodeGraphDataFrameFieldNames.nodeRadius]: 40,
|
||||||
|
[NodeGraphDataFrameFieldNames.highlighted]: false,
|
||||||
|
[NodeGraphDataFrameFieldNames.detail + 'test_value']: 1,
|
||||||
|
});
|
||||||
|
|
||||||
// Generates node graph data but only returns the edges
|
addRow(nodesFrame, {
|
||||||
export function generateRandomEdges(count = 10, seed = 1) {
|
[NodeGraphDataFrameFieldNames.id]: 'app_service',
|
||||||
return generateRandomNodes(count, seed)[1];
|
[NodeGraphDataFrameFieldNames.title]: 'app service',
|
||||||
|
[NodeGraphDataFrameFieldNames.subTitle]: 'with icon',
|
||||||
|
[NodeGraphDataFrameFieldNames.mainStat]: 1.2,
|
||||||
|
[NodeGraphDataFrameFieldNames.secondaryStat]: 2.3,
|
||||||
|
arc__success: 1,
|
||||||
|
arc__errors: 0,
|
||||||
|
[NodeGraphDataFrameFieldNames.icon]: 'apps',
|
||||||
|
[NodeGraphDataFrameFieldNames.nodeRadius]: 40,
|
||||||
|
[NodeGraphDataFrameFieldNames.highlighted]: false,
|
||||||
|
[NodeGraphDataFrameFieldNames.detail + 'test_value']: 42,
|
||||||
|
});
|
||||||
|
|
||||||
|
addRow(edgesFrame, {
|
||||||
|
[NodeGraphDataFrameFieldNames.id]: 'root-app_service',
|
||||||
|
[NodeGraphDataFrameFieldNames.source]: 'root',
|
||||||
|
[NodeGraphDataFrameFieldNames.target]: 'app_service',
|
||||||
|
[NodeGraphDataFrameFieldNames.mainStat]: 3.4,
|
||||||
|
[NodeGraphDataFrameFieldNames.secondaryStat]: 4.5,
|
||||||
|
[NodeGraphDataFrameFieldNames.thickness]: 4,
|
||||||
|
[NodeGraphDataFrameFieldNames.color]: '',
|
||||||
|
[NodeGraphDataFrameFieldNames.strokeDasharray]: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
addRow(nodesFrame, {
|
||||||
|
[NodeGraphDataFrameFieldNames.id]: 'auth_service',
|
||||||
|
[NodeGraphDataFrameFieldNames.title]: 'auth service',
|
||||||
|
[NodeGraphDataFrameFieldNames.subTitle]: 'highlighted',
|
||||||
|
[NodeGraphDataFrameFieldNames.mainStat]: 3.4,
|
||||||
|
[NodeGraphDataFrameFieldNames.secondaryStat]: 4.5,
|
||||||
|
arc__success: 0,
|
||||||
|
arc__errors: 1,
|
||||||
|
[NodeGraphDataFrameFieldNames.icon]: '',
|
||||||
|
[NodeGraphDataFrameFieldNames.nodeRadius]: 40,
|
||||||
|
[NodeGraphDataFrameFieldNames.highlighted]: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
addRow(edgesFrame, {
|
||||||
|
[NodeGraphDataFrameFieldNames.id]: 'root-auth_service',
|
||||||
|
[NodeGraphDataFrameFieldNames.source]: 'root',
|
||||||
|
[NodeGraphDataFrameFieldNames.target]: 'auth_service',
|
||||||
|
[NodeGraphDataFrameFieldNames.mainStat]: 113.4,
|
||||||
|
[NodeGraphDataFrameFieldNames.secondaryStat]: 4.511,
|
||||||
|
[NodeGraphDataFrameFieldNames.thickness]: 8,
|
||||||
|
[NodeGraphDataFrameFieldNames.color]: 'red',
|
||||||
|
[NodeGraphDataFrameFieldNames.strokeDasharray]: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
addRow(nodesFrame, {
|
||||||
|
[NodeGraphDataFrameFieldNames.id]: 'db',
|
||||||
|
[NodeGraphDataFrameFieldNames.title]: 'db',
|
||||||
|
[NodeGraphDataFrameFieldNames.subTitle]: 'bigger size',
|
||||||
|
[NodeGraphDataFrameFieldNames.mainStat]: 9876.123,
|
||||||
|
[NodeGraphDataFrameFieldNames.secondaryStat]: 123.9876,
|
||||||
|
arc__success: 0.9,
|
||||||
|
arc__errors: 0.1,
|
||||||
|
[NodeGraphDataFrameFieldNames.icon]: 'database',
|
||||||
|
[NodeGraphDataFrameFieldNames.nodeRadius]: 60,
|
||||||
|
[NodeGraphDataFrameFieldNames.highlighted]: false,
|
||||||
|
[NodeGraphDataFrameFieldNames.detail + 'test_value']: 1357,
|
||||||
|
});
|
||||||
|
|
||||||
|
addRow(edgesFrame, {
|
||||||
|
[NodeGraphDataFrameFieldNames.id]: 'auth_service-db',
|
||||||
|
[NodeGraphDataFrameFieldNames.source]: 'auth_service',
|
||||||
|
[NodeGraphDataFrameFieldNames.target]: 'db',
|
||||||
|
[NodeGraphDataFrameFieldNames.mainStat]: 1139.4,
|
||||||
|
[NodeGraphDataFrameFieldNames.secondaryStat]: 477.511,
|
||||||
|
[NodeGraphDataFrameFieldNames.thickness]: 2,
|
||||||
|
[NodeGraphDataFrameFieldNames.color]: 'blue',
|
||||||
|
[NodeGraphDataFrameFieldNames.strokeDasharray]: '2 2',
|
||||||
|
});
|
||||||
|
|
||||||
|
return [nodesFrame, edgesFrame];
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ import { computeNodeCircumferenceStrokeWidth, nodeR } from './Node';
|
|||||||
import { EdgeDatum, NodeDatum } from './types';
|
import { EdgeDatum, NodeDatum } from './types';
|
||||||
import { shortenLine } from './utils';
|
import { shortenLine } from './utils';
|
||||||
|
|
||||||
export const highlightedEdgeColor = '#a00';
|
export const defaultHighlightedEdgeColor = '#a00';
|
||||||
export const defaultEdgeColor = '#999';
|
export const defaultEdgeColor = '#999';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -41,12 +41,18 @@ export const Edge = memo(function Edge(props: Props) {
|
|||||||
arrowHeadHeight
|
arrowHeadHeight
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const edgeColor = edge.color || defaultEdgeColor;
|
||||||
|
|
||||||
|
// @deprecated -- until 'highlighted' is removed we'll prioritize 'color'
|
||||||
|
// in case both are provided
|
||||||
|
const highlightedEdgeColor = edge.color || defaultHighlightedEdgeColor;
|
||||||
|
|
||||||
const markerId = `triangle-${edge.id}`;
|
const markerId = `triangle-${edge.id}`;
|
||||||
const coloredMarkerId = `triangle-colored-${edge.id}`;
|
const coloredMarkerId = `triangle-colored-${edge.id}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EdgeArrowMarker id={markerId} headHeight={arrowHeadHeight} />
|
<EdgeArrowMarker id={markerId} fill={edgeColor} headHeight={arrowHeadHeight} />
|
||||||
<EdgeArrowMarker id={coloredMarkerId} fill={highlightedEdgeColor} headHeight={arrowHeadHeight} />
|
<EdgeArrowMarker id={coloredMarkerId} fill={highlightedEdgeColor} headHeight={arrowHeadHeight} />
|
||||||
<g
|
<g
|
||||||
onClick={(event) => onClick(event, edge)}
|
onClick={(event) => onClick(event, edge)}
|
||||||
@@ -55,11 +61,12 @@ export const Edge = memo(function Edge(props: Props) {
|
|||||||
>
|
>
|
||||||
<line
|
<line
|
||||||
strokeWidth={(hovering ? 1 : 0) + (edge.highlighted ? 1 : 0) + edge.thickness}
|
strokeWidth={(hovering ? 1 : 0) + (edge.highlighted ? 1 : 0) + edge.thickness}
|
||||||
stroke={edge.highlighted ? highlightedEdgeColor : defaultEdgeColor}
|
stroke={edge.highlighted ? highlightedEdgeColor : edgeColor}
|
||||||
x1={line.x1}
|
x1={line.x1}
|
||||||
y1={line.y1}
|
y1={line.y1}
|
||||||
x2={line.x2}
|
x2={line.x2}
|
||||||
y2={line.y2}
|
y2={line.y2}
|
||||||
|
strokeDasharray={edge.strokeDasharray}
|
||||||
markerEnd={`url(#${edge.highlighted ? coloredMarkerId : markerId})`}
|
markerEnd={`url(#${edge.highlighted ? coloredMarkerId : markerId})`}
|
||||||
/>
|
/>
|
||||||
<line
|
<line
|
||||||
|
@@ -35,8 +35,13 @@ export type EdgeDatum = LinkDatum & {
|
|||||||
dataFrameRowIndex: number;
|
dataFrameRowIndex: number;
|
||||||
sourceNodeRadius: number;
|
sourceNodeRadius: number;
|
||||||
targetNodeRadius: number;
|
targetNodeRadius: number;
|
||||||
|
/**
|
||||||
|
* @deprecated -- for edges use color instead
|
||||||
|
*/
|
||||||
highlighted: boolean;
|
highlighted: boolean;
|
||||||
thickness: number;
|
thickness: number;
|
||||||
|
color?: string;
|
||||||
|
strokeDasharray?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// After layout is run D3 will change the string IDs for actual references to the nodes.
|
// After layout is run D3 will change the string IDs for actual references to the nodes.
|
||||||
|
@@ -86,8 +86,13 @@ export type EdgeFields = {
|
|||||||
mainStat?: Field;
|
mainStat?: Field;
|
||||||
secondaryStat?: Field;
|
secondaryStat?: Field;
|
||||||
details: Field[];
|
details: Field[];
|
||||||
|
/**
|
||||||
|
* @deprecated use `color` instead
|
||||||
|
*/
|
||||||
highlighted?: Field;
|
highlighted?: Field;
|
||||||
thickness?: Field;
|
thickness?: Field;
|
||||||
|
color?: Field;
|
||||||
|
strokeDasharray?: Field;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getEdgeFields(edges: DataFrame): EdgeFields {
|
export function getEdgeFields(edges: DataFrame): EdgeFields {
|
||||||
@@ -103,8 +108,11 @@ export function getEdgeFields(edges: DataFrame): EdgeFields {
|
|||||||
mainStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.mainStat.toLowerCase()),
|
mainStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.mainStat.toLowerCase()),
|
||||||
secondaryStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.secondaryStat.toLowerCase()),
|
secondaryStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.secondaryStat.toLowerCase()),
|
||||||
details: findFieldsByPrefix(edges, NodeGraphDataFrameFieldNames.detail.toLowerCase()),
|
details: findFieldsByPrefix(edges, NodeGraphDataFrameFieldNames.detail.toLowerCase()),
|
||||||
|
// @deprecated -- for edges use color instead
|
||||||
highlighted: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.highlighted.toLowerCase()),
|
highlighted: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.highlighted.toLowerCase()),
|
||||||
thickness: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.thickness.toLowerCase()),
|
thickness: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.thickness.toLowerCase()),
|
||||||
|
color: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.color.toLowerCase()),
|
||||||
|
strokeDasharray: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.strokeDasharray.toLowerCase()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,8 +242,11 @@ function processEdges(edges: DataFrame, edgeFields: EdgeFields, nodesMap: { [id:
|
|||||||
secondaryStat: edgeFields.secondaryStat
|
secondaryStat: edgeFields.secondaryStat
|
||||||
? statToString(edgeFields.secondaryStat.config, edgeFields.secondaryStat.values[index])
|
? statToString(edgeFields.secondaryStat.config, edgeFields.secondaryStat.values[index])
|
||||||
: '',
|
: '',
|
||||||
|
// @deprecated -- for edges use color instead
|
||||||
highlighted: edgeFields.highlighted?.values[index] || false,
|
highlighted: edgeFields.highlighted?.values[index] || false,
|
||||||
thickness: edgeFields.thickness?.values[index] || 1,
|
thickness: edgeFields.thickness?.values[index] || 1,
|
||||||
|
color: edgeFields.color?.values[index],
|
||||||
|
strokeDasharray: edgeFields.strokeDasharray?.values[index],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user