Skip to content

Commit eca0fc4

Browse files
Merge pull request #218 from mercedes-benz/VULCAN-1719/y-axis-custom-hover
Vulcan 1719/y axis custom hover
2 parents 468df37 + e948bfd commit eca0fc4

File tree

4 files changed

+155
-2
lines changed

4 files changed

+155
-2
lines changed

src/chart/bar/BarChart.tsx

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { convertRecordObjectToString, recordToNative } from '../ChartUtils';
88
import { themeNivo, themeNivoCanvas } from '../Utils';
99
import { extensionEnabled } from '../../utils/ReportUtils';
1010
import { getPageNumbersAndNamesList, getRule, performActionOnElement } from '../../extensions/advancedcharts/Utils';
11-
import { getOriginalRecordForNivoClickEvent } from './util';
11+
import { formatToolTipValue, getOriginalRecordForNivoClickEvent, getRecordByCategory } from './util';
12+
import { BarChartTooltip } from './BarChartTooltip';
1213

1314
const NeoBarChart = (props: ChartProps) => {
1415
const { records, selection } = props;
@@ -40,6 +41,8 @@ const NeoBarChart = (props: ChartProps) => {
4041
const labelSkipHeight = settings.labelSkipHeight ? settings.labelSkipHeight : 0;
4142
const enableLabel = settings.barValues ? settings.barValues : false;
4243
const positionLabel = settings.positionLabel ? settings.positionLabel : 'off';
44+
// New configurable tooltip property (name of field to show on hover instead of default value)
45+
const { tooltipField } = settings;
4346

4447
// New value toggle related settings (primary vs alternate numeric field)
4548
const { alternateValueField } = settings; // optional second numeric field name
@@ -395,6 +398,46 @@ const NeoBarChart = (props: ChartProps) => {
395398
overflowY: 'auto',
396399
};
397400

401+
const handleToolTipRendering =
402+
settings.tooltipField || settings.alternateValueField
403+
? (
404+
bar: {
405+
id: string | number;
406+
value: number;
407+
formattedValue: string;
408+
index: number;
409+
indexValue: string | number;
410+
data: object;
411+
},
412+
_color: string,
413+
_label: string
414+
) => {
415+
// Find the original record by matching category and group (not value, since value changes with toggle)
416+
const record = getRecordByCategory(bar, records, selection, bar);
417+
// Priority1: Display tooltipField value if available, otherwise fall back to bar.value
418+
// Format bar.value if it's an array
419+
let content = `${bar.id} - ${bar.indexValue}: <strong>${formatToolTipValue(bar.value)}</strong>`;
420+
if (tooltipField && record && record[tooltipField] !== undefined) {
421+
content = `${tooltipField}: <strong>${formatToolTipValue(record[tooltipField])}</strong>`;
422+
} else if (record) {
423+
// Priority 2: Show field based on current mode (alternate or primary)
424+
if (valueFieldMode === 'alternate' && alternateValueField && record[alternateValueField] !== undefined) {
425+
// Alternate mode: show alternate field
426+
content = `${alternateValueField} - ${bar.indexValue}: <strong>${formatToolTipValue(
427+
record[alternateValueField]
428+
)}</strong>`;
429+
} else if (selection?.value && record[selection.value] !== undefined) {
430+
// Primary mode: show primary field
431+
content = `${selection.value} - ${bar.indexValue}: <strong>${formatToolTipValue(
432+
record[selection.value]
433+
)}</strong>`;
434+
}
435+
}
436+
const isDarkMode = props.theme === 'dark';
437+
return <BarChartTooltip content={content} isDarkMode={isDarkMode} barColor={bar.color} />;
438+
}
439+
: undefined;
440+
398441
const chart = (
399442
<div style={barChartStyle}>
400443
<div style={scrollableWrapperStyle}>
@@ -427,6 +470,7 @@ const NeoBarChart = (props: ChartProps) => {
427470
tickPadding: 5,
428471
tickRotation: 0,
429472
}}
473+
tooltip={handleToolTipRendering}
430474
labelSkipWidth={labelSkipWidth}
431475
labelSkipHeight={labelSkipHeight}
432476
labelTextColor={{ from: 'color', modifiers: [['darker', 1.6]] }}

src/chart/bar/BarChartTooltip.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from 'react';
2+
3+
/**
4+
* Custom tooltip component for bar charts that adapts to light/dark theme
5+
* Styling follows the Nivo chart library defaults for consistency
6+
*/
7+
export const BarChartTooltip = ({ content, isDarkMode, barColor }) => {
8+
const tooltipStyle: React.CSSProperties = isDarkMode
9+
? {
10+
font: 'Nunito Sans, sans-serif !important',
11+
background: '#25354d',
12+
color: '#ffffff',
13+
padding: '9px 12px',
14+
border: 'none',
15+
borderRadius: 2,
16+
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.25)',
17+
whiteSpace: 'nowrap',
18+
}
19+
: {
20+
font: "'Nunito Sans', sans-serif !important",
21+
background: '#dadde3',
22+
color: 'rgb(var(--palette-neutral-text-default))',
23+
padding: '9px 12px',
24+
border: '1px solid rgba(0, 0, 0, 0.1)',
25+
borderRadius: 2,
26+
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)',
27+
whiteSpace: 'nowrap',
28+
};
29+
30+
const colorBoxStyle: React.CSSProperties = {
31+
display: 'inline-block',
32+
width: 12,
33+
height: 12,
34+
backgroundColor: barColor,
35+
marginRight: 8,
36+
verticalAlign: 'middle',
37+
};
38+
39+
const indexStyle: React.CSSProperties = {
40+
fontWeight: 600,
41+
marginRight: 8,
42+
};
43+
44+
return (
45+
<div style={tooltipStyle}>
46+
<span style={colorBoxStyle} />
47+
<span dangerouslySetInnerHTML={{ __html: content }} />
48+
</div>
49+
);
50+
};

src/chart/bar/util.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { convertRecordObjectToString, recordToNative } from '../ChartUtils';
2+
13
/**
24
* Utility function to reverse engineer, from an event on a Nivo bar chart, what the original Neo4j record was the data came from.
35
* Once we have this record, we can pass it to the action rule handler, so that users can define report actions on any variable
@@ -43,3 +45,60 @@ export function getOriginalRecordForNivoClickEvent(e, records, selection) {
4345
}
4446
}
4547
}
48+
49+
/**
50+
* Get the original record for a Nivo click event.
51+
* @param e The click event on the bar chart.
52+
* @param records The Neo4j records used to build the visualization.
53+
* @param selection The selection made by the user (category, index, group*).
54+
* @param bar The bar object from the Nivo chart.
55+
* @returns The original record as a dictionary or null if not found.
56+
*/
57+
export function getRecordByCategory(e, records, selection, bar) {
58+
let record: Record<string, any> | null = null;
59+
const category = bar.indexValue;
60+
const group = bar.id;
61+
const usesGroups = selection.key !== '(none)';
62+
// Search for matching record
63+
for (const r of records) {
64+
try {
65+
const recordCategory = convertRecordObjectToString(r.get(selection.index));
66+
67+
if (usesGroups) {
68+
const recordGroup = recordToNative(r.get(selection.key));
69+
if (recordCategory == category && recordGroup == group) {
70+
// Build dictionary of all fields from this record
71+
const dict: Record<string, any> = {};
72+
r.keys.forEach((key) => {
73+
if (typeof key === 'string') {
74+
dict[key] = recordToNative(r.get(key));
75+
}
76+
});
77+
record = dict;
78+
break;
79+
}
80+
} else if (recordCategory == category) {
81+
// Build dictionary of all fields from this record
82+
const dict: Record<string, any> = {};
83+
r.keys.forEach((key) => {
84+
if (typeof key === 'string') {
85+
dict[key] = recordToNative(r.get(key));
86+
}
87+
});
88+
record = dict;
89+
break;
90+
}
91+
} catch (e) {
92+
// Skip records that don't have required fields
93+
continue;
94+
}
95+
}
96+
return record;
97+
}
98+
99+
export const formatToolTipValue = (value: any): string => {
100+
if (Array.isArray(value)) {
101+
return value.join(', ');
102+
}
103+
return String(value);
104+
};

src/chart/line/LineChart.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const NeoLineChart = (props: ChartProps) => {
5555
const curve = settings.curve ? settings.curve : 'linear';
5656
const marginRight = settings.marginRight ? settings.marginRight : 24;
5757
const marginLeft = settings.marginLeft ? settings.marginLeft : 36;
58-
const marginTop = settings.marginTop ? settings.marginTop : 24;
58+
const marginTop = settings.marginTop ? settings.marginTop : 40;
5959
const marginBottom = settings.marginBottom ? settings.marginBottom : 40;
6060
const lineWidth = settings.type == 'scatter' ? 0 : settings.lineWidth || 2;
6161
const pointSize = settings.pointSize ? settings.pointSize : 10;

0 commit comments

Comments
 (0)