Skip to content

Commit 118bb57

Browse files
Add thousand separator for numbers, unified rendering for single number charts (#71)
* Add thousand separator to SingleValueChart * Extract get type and renderer into util * Added unified rendering for single values/tables Co-authored-by: Marius Conjeaud <[email protected]> Co-authored-by: Niels de Jong <[email protected]>
1 parent feb3749 commit 118bb57

File tree

3 files changed

+140
-115
lines changed

3 files changed

+140
-115
lines changed

src/chart/SingleValueChart.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

22
import React from 'react';
33
import { ChartProps } from './Chart';
4+
5+
import { getRecordType, getRendererForValue, renderValueByType } from '../report/RecordProcessing';
46
/**
57
* Renders Neo4j records as their JSON representation.
68
*/
@@ -11,9 +13,10 @@ const NeoSingleValueChart = (props: ChartProps) => {
1113
const color = props.settings && props.settings.color ? props.settings.color : "rgba(0, 0, 0, 0.87)";
1214
const textAlign = props.settings && props.settings.textAlign ? props.settings.textAlign : "left";
1315

14-
const value = (records && records[0] && records[0]["_fields"] && records[0]["_fields"][0]) ? records[0]["_fields"][0].toString() : "";
16+
const value = (records && records[0] && records[0]["_fields"] && records[0]["_fields"][0]) ? records[0]["_fields"][0] : "";
17+
const displayValue = renderValueByType(value);
1518
return <div style={{marginTop: marginTop, textAlign: textAlign, marginLeft: "15px", marginRight: "15px"}}>
16-
<span style={{fontSize: fontSize, color: color}}>{value}</span>
19+
<span style={{fontSize: fontSize, color: color}}>{displayValue}</span>
1720
</div >;
1821
}
1922

src/chart/TableChart.tsx

Lines changed: 3 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,11 @@
11
import React from 'react';
22
import { DataGrid } from '@mui/x-data-grid';
3-
import { Chip } from '@material-ui/core';
4-
import { withStyles } from '@material-ui/core/styles';
5-
import Tooltip from '@material-ui/core/Tooltip';
63
import { ChartProps } from './Chart';
7-
import { valueIsNode, valueIsRelationship, getRecordType } from '../report/RecordProcessing';
8-
9-
function addDirection(relationship, start) {
10-
relationship.direction = (relationship.start.low == start.identity.low);
11-
return relationship;
12-
}
13-
14-
const rightRelationship = "polygon(10px 0%, calc(100% - 10px) 0%, 100% 50%, 100% calc(100% - 50%), calc(100% - 10px) 100%, 0px 100%, 0% calc(100% - 0px), 0% 0px)"
15-
const leftRelationship = "polygon(10px 0%, calc(100% - 0%) 0%, 100% 10px, 100% calc(100% - 10px), calc(100% - 0%) 100%, 10px 100%, 0% calc(100% - 50%), 0% 50%)"
16-
17-
const HtmlTooltip = withStyles((theme) => ({
18-
tooltip: {
19-
color: 'white',
20-
fontSize: theme.typography.pxToRem(12),
21-
border: '1px solid #fcfffa',
22-
},
23-
}))(Tooltip);
24-
25-
function RenderNode(value, key = 0) {
26-
return <HtmlTooltip key={key + "-" + value.identity} arrow title={<div><b> {value.labels.length > 0 ? value.labels.join(", ") : "Node"}</b><table><tbody>{Object.keys(value.properties).length == 0 ? <tr><td>(No properties)</td></tr> : Object.keys(value.properties).map((k, i) => <tr key={i}><td key={0}>{k.toString()}:</td><td key={1}>{value.properties[k].toString()}</td></tr>)}</tbody></table></div>}>
27-
<Chip label={value.labels.length > 0 ? value.labels.join(", ") : "Node"} />
28-
</HtmlTooltip>
29-
}
30-
31-
function RenderRelationship(value, key = 0) {
32-
return <HtmlTooltip key={key + "-" + value.identity} arrow title={<div><b> {value.type}</b><table><tbody>{Object.keys(value.properties).length == 0 ? <tr><td>(No properties)</td></tr> : Object.keys(value.properties).map((k, i) => <tr key={i}><td key={0}>{k.toString()}:</td><td key={1}>{value.properties[k].toString()}</td></tr>)}</tbody></table></div>}>
33-
<Chip style={{ borderRadius: 0, clipPath: (value.direction == undefined) ? "none" : ((value.direction) ? rightRelationship : leftRelationship) }} label={value.type} />
34-
</HtmlTooltip>
35-
}
36-
37-
function RenderPath(value) {
38-
return value.segments.map((segment, i) => {
39-
return RenderSubValue((i < value.segments.length - 1) ?
40-
[segment.start, addDirection(segment.relationship, segment.start)] :
41-
[segment.start, addDirection(segment.relationship, segment.start), segment.end], i)
42-
});
43-
}
44-
45-
function RenderArray(value) {
46-
const mapped = value.map((v, i) => {
47-
return <div>
48-
{RenderSubValue(v)}
49-
{i < value.length - 1 && !valueIsNode(v) && !valueIsRelationship(v) ? <span>,&nbsp;</span> : <></>}
50-
</div>
51-
});
52-
return mapped;
53-
}
54-
55-
function RenderString(value) {
56-
const str = value ? value.toString() : "";
57-
if (str.startsWith("http") || str.startsWith("https")) {
58-
return <a target="_blank" href={str}>{str}</a>;
59-
}
60-
return str;
61-
}
62-
63-
const customColumnProperties: any = {
64-
"node": {
65-
type: 'string',
66-
renderCell: (c) => RenderNode(c.value),
67-
},
68-
"relationship": {
69-
type: 'string',
70-
renderCell: (c) => RenderRelationship(c.value),
71-
},
72-
"path": {
73-
type: 'string',
74-
renderCell: (c) => RenderPath(c.value),
75-
},
76-
"object": {
77-
type: 'string',
78-
// valueGetter enables sorting and filtering on string values inside the object
79-
valueGetter: (c) => { return JSON.stringify(c.value) },
80-
},
81-
"array": {
82-
type: 'string',
83-
renderCell: (c) => RenderArray(c.value),
84-
},
85-
"string": {
86-
type: 'string',
87-
renderCell: (c) => RenderString(c.value),
88-
},
89-
"null": {
90-
type: 'string'
91-
},
92-
"undefined": {
93-
type: 'string'
94-
}
95-
};
4+
import { getRecordType, getRendererForValue } from '../report/RecordProcessing';
965

976
function ApplyColumnType(column, value) {
98-
column.type = getRecordType(value);
99-
const columnProperties = customColumnProperties[column.type];
7+
const renderer = getRendererForValue(value);
8+
const columnProperties = {type: renderer.type, renderCell: renderer.renderValue};
1009

10110
if (columnProperties) {
10211
column = { ...column, ...columnProperties }
@@ -105,24 +14,6 @@ function ApplyColumnType(column, value) {
10514
return column;
10615
}
10716

108-
function RenderSubValue(value, key = 0) {
109-
if (value == undefined) {
110-
return "";
111-
}
112-
const type = getRecordType(value);
113-
const columnProperties = customColumnProperties[type];
114-
115-
if (columnProperties) {
116-
if (columnProperties.renderCell) {
117-
return columnProperties.renderCell({ value: value });
118-
} else if (columnProperties.valueGetter) {
119-
return columnProperties.valueGetter({ value: value });
120-
}
121-
}
122-
123-
return RenderString(value);
124-
}
125-
12617
const NeoTableChart = (props: ChartProps) => {
12718
const fullscreen = props.fullscreen ? props.fullscreen : false;
12819
const transposed = props.settings && props.settings.transposed ? props.settings.transposed : false;

src/report/RecordProcessing.tsx

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import _ from 'lodash';
2-
import { DateTime } from 'neo4j-driver';
2+
3+
import React from 'react';
4+
import { Chip } from '@material-ui/core';
5+
import { withStyles } from '@material-ui/core/styles';
6+
import Tooltip from '@material-ui/core/Tooltip';
37

48
const OPTIONAL_FIELD_UNAVAILABLE_IDENTIFIER = "(none)";
59

@@ -285,3 +289,130 @@ export function getRecordType(value) {
285289
// Use string as default type
286290
return 'string';
287291
}
292+
293+
/* HELPER FUNCTIONS FOR RENDERING A FIELD BASED ON TYPE */
294+
const HtmlTooltip = withStyles((theme) => ({
295+
tooltip: {
296+
color: 'white',
297+
fontSize: theme.typography.pxToRem(12),
298+
border: '1px solid #fcfffa',
299+
},
300+
}))(Tooltip);
301+
302+
function addDirection(relationship, start) {
303+
relationship.direction = (relationship.start.low == start.identity.low);
304+
return relationship;
305+
}
306+
307+
const rightRelationship = "polygon(10px 0%, calc(100% - 10px) 0%, 100% 50%, 100% calc(100% - 50%), calc(100% - 10px) 100%, 0px 100%, 0% calc(100% - 0px), 0% 0px)"
308+
const leftRelationship = "polygon(10px 0%, calc(100% - 0%) 0%, 100% 10px, 100% calc(100% - 10px), calc(100% - 0%) 100%, 10px 100%, 0% calc(100% - 50%), 0% 50%)"
309+
310+
function RenderNode(value, key = 0) {
311+
return <HtmlTooltip key={key + "-" + value.identity} arrow title={<div><b> {value.labels.length > 0 ? value.labels.join(", ") : "Node"}</b><table><tbody>{Object.keys(value.properties).length == 0 ? <tr><td>(No properties)</td></tr> : Object.keys(value.properties).map((k, i) => <tr key={i}><td key={0}>{k.toString()}:</td><td key={1}>{value.properties[k].toString()}</td></tr>)}</tbody></table></div>}>
312+
<Chip label={value.labels.length > 0 ? value.labels.join(", ") : "Node"} />
313+
</HtmlTooltip>
314+
}
315+
316+
function RenderRelationship(value, key = 0) {
317+
return <HtmlTooltip key={key + "-" + value.identity} arrow title={<div><b> {value.type}</b><table><tbody>{Object.keys(value.properties).length == 0 ? <tr><td>(No properties)</td></tr> : Object.keys(value.properties).map((k, i) => <tr key={i}><td key={0}>{k.toString()}:</td><td key={1}>{value.properties[k].toString()}</td></tr>)}</tbody></table></div>}>
318+
<Chip style={{ borderRadius: 0, clipPath: (value.direction == undefined) ? "none" : ((value.direction) ? rightRelationship : leftRelationship) }} label={value.type} />
319+
</HtmlTooltip>
320+
}
321+
322+
function RenderPath(value) {
323+
return value.segments.map((segment, i) => {
324+
return RenderSubValue((i < value.segments.length - 1) ?
325+
[segment.start, addDirection(segment.relationship, segment.start)] :
326+
[segment.start, addDirection(segment.relationship, segment.start), segment.end], i)
327+
});
328+
}
329+
330+
function RenderArray(value) {
331+
const mapped = value.map((v, i) => {
332+
return <div>
333+
{RenderSubValue(v)}
334+
{i < value.length - 1 && !valueIsNode(v) && !valueIsRelationship(v) ? <span>,&nbsp;</span> : <></>}
335+
</div>
336+
});
337+
return mapped;
338+
}
339+
340+
function RenderString(value) {
341+
const str = value ? value.toString() : "";
342+
if (str.startsWith("http") || str.startsWith("https")) {
343+
return <a target="_blank" href={str}>{str}</a>;
344+
}
345+
return str;
346+
}
347+
348+
function RenderNumber(value) {
349+
const thousandsSeperator=" ";
350+
const number = value.toString().replace(/\B(?=(\d{3})+(?!\d))/g,thousandsSeperator);
351+
return number;
352+
}
353+
354+
function RenderSubValue(value, key = 0) {
355+
if (value == undefined) {
356+
return "";
357+
}
358+
const type = getRecordType(value);
359+
const columnProperties = rendererForType[type];
360+
361+
if (columnProperties) {
362+
if (columnProperties.renderCell) {
363+
return columnProperties.renderCell({ value: value });
364+
} else if (columnProperties.valueGetter) {
365+
return columnProperties.valueGetter({ value: value });
366+
}
367+
}
368+
369+
return RenderString(value);
370+
}
371+
372+
const rendererForType: any = {
373+
"node": {
374+
type: 'string',
375+
renderValue: (c) => RenderNode(c.value),
376+
},
377+
"relationship": {
378+
type: 'string',
379+
renderValue: (c) => RenderRelationship(c.value),
380+
},
381+
"path": {
382+
type: 'string',
383+
renderValue: (c) => RenderPath(c.value),
384+
},
385+
"object": {
386+
type: 'string',
387+
// valueGetter enables sorting and filtering on string values inside the object
388+
valueGetter: (c) => { return JSON.stringify(c.value) },
389+
},
390+
"array": {
391+
type: 'string',
392+
renderValue: (c) => RenderArray(c.value),
393+
},
394+
"string": {
395+
type: 'string',
396+
renderValue: (c) => RenderString(c.value),
397+
},
398+
"number": {
399+
type: 'number',
400+
renderValue: (c) => RenderNumber(c.value)
401+
},
402+
"null": {
403+
type: 'string'
404+
},
405+
"undefined": {
406+
type: 'string'
407+
}
408+
};
409+
410+
export function getRendererForValue(value) {
411+
const type = getRecordType(value);
412+
return rendererForType[type];
413+
}
414+
415+
export function renderValueByType(value){
416+
const renderer = getRendererForValue(value);
417+
renderer.renderValue({value:value});
418+
}

0 commit comments

Comments
 (0)