Skip to content

Commit d6188fb

Browse files
Adding Radar Charts (#207)
* Radar 0.1 * Radar 1.0 * Changed selection for radar chart * Removed old advanced config for radars * Bump docs version * Updated examples * Removed debug statements Co-authored-by: Niels de Jong <[email protected]>
1 parent 88b39c0 commit d6188fb

File tree

12 files changed

+1664
-1765
lines changed

12 files changed

+1664
-1765
lines changed

docs/modules/ROOT/images/radar.png

94.8 KB
Loading
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
= Radar Chart
2+
3+
A Radar chart can be used to render multivariate data from an array of nodes
4+
into the form of a two dimensional chart of three or more quantitative variables.
5+
6+
A radar chart expects a single index, to which a list of numeric fields can be linked.
7+
8+
== Examples
9+
10+
=== Basic Radar
11+
12+
[source,cypher]
13+
----
14+
MATCH (s:Skill)
15+
MATCH (:Player{name:"Messi"})-[h1:HAS_SKILL]->(s)
16+
MATCH (:Player{name:"Mbappe"})-[h2:HAS_SKILL]->(s)
17+
MATCH (:Player{name:"Benzema"})-[h3:HAS_SKILL]->(s)
18+
MATCH (:Player{name:"C Ronaldo"})-[h4:HAS_SKILL]->(s)
19+
MATCH (:Player{name:"Lewandowski"})-[h5:HAS_SKILL]->(s)
20+
RETURN s.name as Skill, h1.value as Messi, h2.value as Mbappe, h3.value as Benzema,
21+
h4.value as `C Ronaldo`, h5.value as Lewandowski
22+
----
23+
24+
image::radar.png[Radar Chart]
25+
26+
== Advanced Settings
27+
28+
[width="100%",cols="15%,2%,26%,57%",options="header",]
29+
|===
30+
|Name |Type |Default Value |Description
31+
|Enable interactivity |on/off |on |If enabled, turn on animations when a
32+
user hovers over a layer.
33+
34+
|Show Legend |on/off |off |If enabled, shows a legend on the bottom of
35+
the visualization.
36+
37+
|Color Scheme |List | |The color scheme to use for the Radar. Each polygon will have a color from the list.
38+
39+
|Margin Left (px) |number |24 |The margin in pixels on the left side of
40+
the visualization.
41+
42+
|Margin Right (px) |number |24 |The margin in pixels on the right side
43+
of the visualization.
44+
45+
|Margin Top (px) |number |24 |The margin in pixels on the top side of
46+
the visualization.
47+
48+
|Margin Bottom (px) |number |40 |The margin in pixels on the bottom side
49+
of the visualization.
50+
51+
|Dot Size |number |10 |Size of the dots (px).
52+
53+
|Dot Border Width |number |2 |Width of the dots border (px).
54+
55+
|Grid Levels |number |5 |Number of levels to display for grid.
56+
57+
|Grid Label Offset (px) |number |16 |Label offset from outer radius (px)
58+
59+
|Blend Mode |List |normal |This will define CSS mix-blend-mode for layers
60+
61+
|Motion Configuration |List |gentle |This parameter will select the motion config for react-spring.
62+
63+
|Curve |List |linearClosed |This parameter will select the type of curve interpolation.
64+
65+
|Auto-run query |on/off |on |When activated, automatically runs the
66+
query when the report is displayed. When set to `off', the query is
67+
displayed and will need to be executed manually.
68+
|===

docs/package-lock.json

Lines changed: 1243 additions & 1700 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
}
2020
},
2121
"dependencies": {
22-
"@antora/cli": "^2.3.3",
23-
"@antora/site-generator-default": "^2.3.3",
22+
"@antora/cli": "^3.1.1",
23+
"@antora/site-generator-default": "^3.1.1",
2424
"@neo4j-documentation/macros": "^1.0.0",
2525
"@neo4j-documentation/remote-include": "^1.0.0",
2626
"express": "^4.17.1",

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"@nivo/geo": "^0.79.1",
4343
"@nivo/line": "^0.79.1",
4444
"@nivo/pie": "^0.79.1",
45+
"@nivo/radar": "^0.79.1",
4546
"@nivo/sankey": "^0.79.1",
4647
"@nivo/sunburst": "^0.79.1",
4748
"@nivo/treemap": "^0.79.1",

src/chart/RadarChart.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from 'react';
2+
import { ChartProps } from './Chart';
3+
import RadarVisualization from './visualizations/RadarVisualization';
4+
5+
6+
/**
7+
* Embeds a RadarChart (from Charts) into NeoDash.
8+
*/
9+
const NeoRadarChart = (props: ChartProps) => {
10+
if (props.records == null || props.records.length == 0 || props.records[0].keys == null) {
11+
return <>No data, re-run the report.</>
12+
}
13+
return <RadarVisualization records={props.records} settings={props.settings} selection={props.selection}
14+
first={(props.records) ? props.records[0] : undefined}
15+
/>
16+
}
17+
18+
export default NeoRadarChart;
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import React, { useEffect } from 'react';
2+
import { ResponsiveRadar } from '@nivo/radar'
3+
import { ChartReportProps, ExtendedChartReportProps } from './VisualizationProps'
4+
import { checkResultKeys, recordToNative } from './Utils'
5+
import { categoricalColorSchemes } from "../../config/ColorConfig";
6+
import { evaluateRulesOnDict, evaluateRulesOnNode } from '../../report/ReportRuleEvaluator'
7+
import { convertRecordObjectToString, valueIsArray, valueIsNode, valueIsPath, valueIsRelationship } from "../../report/ReportRecordProcessing";
8+
9+
export default function RadarVisualization(props: ExtendedChartReportProps) {
10+
const { records, first } = props
11+
12+
if (!first) {
13+
return <p>Loading data...</p>
14+
}
15+
16+
const selection = (props.selection) ? props.selection : {};
17+
const settings = (props.settings) ? props.settings : {};
18+
const legendHeight = 20;
19+
const legendWidth = 20;
20+
const marginRight = (settings["marginRight"]) ? settings["marginRight"] : 24;
21+
const marginLeft = (settings["marginLeft"]) ? settings["marginLeft"] : 24;
22+
const marginTop = (settings["marginTop"]) ? settings["marginTop"] : 40;
23+
const marginBottom = (settings["marginBottom"]) ? settings["marginBottom"] : 40;
24+
const dotSize = (settings["dotSize"]) ? settings["dotSize"] : 10;
25+
const dotBorderWidth = (settings["dotBorderWidth"]) ? settings["dotBorderWidth"] : 2;
26+
const gridLabelOffset = (settings["gridLabelOffset"]) ? settings["gridLabelOffset"] : 16;
27+
const gridLevels = (settings["gridLevels"]) ? settings["gridLevels"] : 5;
28+
const interactive = (settings["interactive"] !== undefined) ? settings["interactive"] : true;
29+
const animate = (settings["animate"] !== undefined) ? settings["animate"] : true;
30+
const legend = (settings["legend"] !== undefined) ? settings["legend"] : false;
31+
const colorScheme = (settings["colors"]) ? settings["colors"] : 'set2';
32+
const blendMode = (settings["blendMode"]) ? settings["blendMode"] : 'normal';
33+
const motionConfig = (settings["motionConfig"]) ? settings["motionConfig"] : 'gentle';
34+
const curve = (settings["curve"]) ? settings["curve"] : 'linearClosed';
35+
const styleRules = settings && settings["styleRules"] ? settings["styleRules"] : [];
36+
const keys = first!.keys.slice(1).map(k => k.substring(7,k.length - 1));
37+
38+
39+
if (!selection || props.records == null || props.records.length == 0 || props.records[0].keys == null) {
40+
return <p>No data.</p>;
41+
}
42+
43+
44+
// Compute slice color based on rules - overrides default color scheme completely.
45+
const getCircleColor = (slice) => {
46+
const data = {}
47+
if (!props.selection) {
48+
return "grey";
49+
}
50+
data[props.selection['value']] = slice.value;
51+
data[props.selection['index']] = slice.id;
52+
const validRuleIndex = evaluateRulesOnDict(data, styleRules, ['slice color']);
53+
if (validRuleIndex !== -1) {
54+
return styleRules[validRuleIndex]['customizationValue'];
55+
}
56+
return "grey"
57+
}
58+
59+
const data = records.map(r => {
60+
const entry = {}
61+
first!.keys.forEach((k,i) => {
62+
const key = k == "index" ? selection["index"]: k.substring(7,k.length - 1);
63+
const fieldIndex = r["_fieldLookup"][k];
64+
entry[key] = ""+r["_fields"][fieldIndex];
65+
});
66+
return entry;
67+
});
68+
69+
return <ResponsiveRadar
70+
data={data}
71+
isInteractive={interactive}
72+
animate={animate}
73+
margin={{ top: (legend) ? legendHeight + marginTop : marginTop, right: (legend) ? legendWidth + marginRight : marginRight, bottom: marginBottom, left: (legend) ? legendHeight + marginLeft : marginLeft }}
74+
gridLevels={gridLevels}
75+
keys={keys}
76+
indexBy={selection['index']}
77+
valueFormat=">-.2f"
78+
borderColor={{ from: 'color' }}
79+
gridLabelOffset={gridLabelOffset}
80+
dotSize={dotSize}
81+
dotColor={{ theme: 'background' }}
82+
dotBorderWidth={dotBorderWidth}
83+
colors={styleRules.length >= 1 ? getCircleColor : { scheme: colorScheme }}
84+
blendMode={blendMode}
85+
motionConfig={motionConfig}
86+
curve={curve}
87+
legends={(legend) ? [
88+
{
89+
anchor: 'top-left',
90+
direction: 'column',
91+
translateX: 0,
92+
translateY: -40,
93+
itemWidth: 100,
94+
itemHeight: 14,
95+
itemTextColor: '#999',
96+
symbolSize: 14,
97+
symbolShape: 'circle',
98+
effects: [
99+
{
100+
on: 'hover',
101+
style: {
102+
itemTextColor: '#000'
103+
}
104+
}
105+
]
106+
}
107+
] : []}
108+
{...props.config}
109+
/>
110+
111+
}

0 commit comments

Comments
 (0)