Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [1.1.2] - 2024-12-10

## Added
- Tooltip support for top label sections, now configurable as a general setting. This feature can be enabled or disabled using the tooltipLabel property.
* Formatting Support: The tooltip for top labels supports the standard format configuration for customizing display values.
* Callback for Custom Behavior: A new tooltipLabel callback is available to override the default behavior, providing full control over tooltip content and interactions.

## [1.1.1] - 2024-12-08

## Fixed
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ graph.draw();

- **tooltip**
Callback function for tooltip events, overriding the default implementation.
`(event, { label, value, x, y }) => {}`

- **tooltipLabel**
Callback function for tooltip events, overriding the default implementation.
`(event, { label, value, x, y, sectionDetails }) => {}`

- **Signature**:
`(event, { label, value }) => {}`
Expand Down Expand Up @@ -190,7 +195,10 @@ graph.draw();
Whether details should be displayed (`true | false`).

- **tooltip**
Whether the tooltip should be displayed (`true | false`).
Whether the tooltip should be displayed (`(default) true | false`).

- **tooltipLabel**
Whether the tooltip label should be displayed (`true | false (default)`).

**Note:** Tooltip display depends on the details display for range calculations according to the dividers.

Expand Down
158 changes: 131 additions & 27 deletions dist/js/funnel-graph.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/funnel-graph.min.js

Large diffs are not rendered by default.

15 changes: 6 additions & 9 deletions examples/example.html
Original file line number Diff line number Diff line change
Expand Up @@ -120,22 +120,17 @@
label: (event, opts) => {
// label handler logic
console.log("label clicked", opts);
}
},
// tooltipLabel: (event, opts) => {
// console.log(opts)
// }
},
// example for overriding the lanels
// format: {
// value: (opt) => {
// if (opt.index === 1) {
// return 1;
// }

// return formatLargeNumber(opt.value);
// },
// tooltip: (opt) => {
// if (opt.index === 1) {
// return 1;
// }

// return formatLargeNumber(opt.value);
// }
// }
Expand Down Expand Up @@ -164,6 +159,8 @@
{ id: 'gradientToggleDirection', text: 'Gradient Toggle Direction', action: () => graph.gradientToggleDirection() },
{ id: 'hideTooltip', text: 'Hide Tooltip', action: () => graph.updateData({ tooltip: false }) },
{ id: 'showTooltip', text: 'Show Tooltip', action: () => graph.updateData({ tooltip: true }) },
{ id: 'hideTooltipLabel', text: 'Hide Tooltip Lbl', action: () => graph.updateData({ tooltipLabel: false }) },
{ id: 'showTooltipLabel', text: 'Show Tooltip Lbl', action: () => graph.updateData({ tooltipLabel: true }) },
{ id: 'hideDetails', text: 'Hide Details', action: () => graph.updateData({ width: 600, height: 400, details: false, margin: { top: 0, right: 0, bottom: 0, left: 0, text: 0 } }) },
{ id: 'showDetails', text: 'Show Details', action: () => graph.updateData({ width: 600, height: 400, details: true, margin: { top: 100, right: 80, bottom: 80, left: 80, text: 20 } }) },
{ id: 'setResize', text: 'Toggle Resize', action: () => { resize = !resize; graph.updateData({ resize }); } },
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "d3-funnel-graph",
"version": "1.1.1",
"version": "1.1.2",
"description": "SVG Funnel Graph Javascript Library",
"main": "dist/js/funnel-graph.min.js",
"style": "dist/funnel-graph.min.css",
Expand All @@ -19,10 +19,10 @@
"url": "https://github.com/lastboy/funnel-graph-js.git"
},
"bugs": {
"url": "https://github.com/lastboy/funnel-graph-js.git/issues"
"url": "https://github.com/lastboy/funnel-graph-js/issues"
},
"homepage": "https://github.com/lastboy/funnel-graph-js.git#readme",
"changelog": "https://github.com/lastboy/funnel-graph-js.git/releases",
"homepage": "https://github.com/lastboy/funnel-graph-js/#readme",
"changelog": "https://github.com/lastboy/funnel-graph-js/blob/master/CHANGELOG.md",
"keywords": [
"d3",
"funnel",
Expand Down
37 changes: 31 additions & 6 deletions src/js/d3-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,14 @@ export const addMouseEventIfNotExists = ({ context, updateLinePositions }) => (p
tooltipTimeout = timeout(() => {

const path = select(this);
const isMouseOnTooltip = handlerMetadata?.mouseOnTooltip;
const isMouseOnTooltipLabel = handlerMetadata?.mouseOnTooltipLabel;

if (context.showTooltip() && path && tooltipElement) {
const showTooltip = (isMouseOnTooltip && context.showTooltip()) || (isMouseOnTooltipLabel && context.showTooltipLabel())

if (showTooltip && path && tooltipElement) {

const { index = -1 } = metadata;

// get the mouse point
const [x, y] = pointer(e, path);
Expand All @@ -47,8 +53,9 @@ export const addMouseEventIfNotExists = ({ context, updateLinePositions }) => (p
label = is2d ? handlerMetadata.subLabel || label : label;
const value = handlerMetadata.value;

const sectionDetails = handlerMetadata?.sectionsDetails?.[index];
if (tooltipHandler) {
tooltipHandler(e, { label, value, x, y })
tooltipHandler(e, { label, value, x, y, sectionDetails });
} else {

const format = context.getFormat();
Expand All @@ -57,15 +64,26 @@ export const addMouseEventIfNotExists = ({ context, updateLinePositions }) => (p
labelFormatCallback = format.tooltip;
}

const tooltipText = `${label}: ${labelFormatCallback(handlerMetadata)}`;
let tooltipText = `<div>${label}: ${labelFormatCallback(handlerMetadata)}</div>`;
if (sectionDetails) {
tooltipText = sectionDetails
.map((item) => `<div><strong>${item?.name}</strong>: ${labelFormatCallback({ ...handlerMetadata, value: item?.value })}</div>`)
.join("");
}

tooltipElement
// TODO: when exceeding the document area - move the tooltip up/down or left/right
// according to the position (e.g. top /right window eƒxceeded or right)
.style("left", (clickPoint.x + 10) + "px")
.style("top", (clickPoint.y + 10) + "px")
.text(tooltipText)
.style("display", "flex")
.style("align-items", sectionDetails ? "start" : "center")
.style("flex-direction", "column")
.style("height", "auto")
.style("gap", "10px")
.style("padding", "4px")
.html(tooltipText)
.style("opacity", "1")
.style("display", "flex");
}
}
}, 500);
Expand Down Expand Up @@ -143,13 +161,20 @@ export const mouseInfoHandler = ({ context, clickHandler, metadata, tooltip, upd
const dataInfoValues = dataInfoItem?.values || [];
const dataInfoLabels = dataInfoItem?.labels || [];
const dataInfoSubLabels = dataInfoItem?.subLabels || [];
const sectionsDetails = dataInfoItem?.sectionsDetails;
const mouseOnTooltipLabel = dataInfoItem?.mouseOnTooltipLabel;
const mouseOnTooltip = dataInfoItem?.mouseOnTooltip;

const index = metadata.hasOwnProperty("index") ? metadata.index : -1;

dataInfoItemForArea = {
value: dataInfoValues?.[areaIndex],
label: dataInfoLabels?.[areaIndex],
subLabel: dataInfoSubLabels?.[index],
sectionIndex: areaIndex
sectionIndex: areaIndex,
sectionsDetails,
mouseOnTooltipLabel,
mouseOnTooltip
}

metadata = {
Expand Down
70 changes: 63 additions & 7 deletions src/js/d3.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,17 +203,31 @@ const onEachPathHandler = ({ context }) => function (d, i, nodes) {
const onEachPathCallbacksHandler = ({ context }) => function (d, i, nodes) {

const callbacks = context.getCallBacks();
const d3Path = select(nodes[i]);
const d3Element = select(nodes[i]);

const addMouseHandler = addMouseEventIfNotExists({ context, updateLinePositions });
addMouseHandler(
d3Path,
d3Element,
(typeof callbacks?.click === 'function') ? callbacks.click : undefined,
(typeof callbacks?.tooltip === 'function') ? callbacks.tooltip : undefined,
{ index: i }
);
};

const onEachGroupLabelsCallbacksHandler = ({ context }) => function (d, i, nodes) {

const callbacks = context.getCallBacks();
const d3Element = select(nodes[i]);

const addMouseHandler = addMouseEventIfNotExists({ context, updateLinePositions });
addMouseHandler(
d3Element,
null,
(typeof callbacks?.tooltipLabel === 'function') ? callbacks.tooltipLabel : undefined,
{ index: i }
);
};

/**
* Get the data nfo for each path
*/
Expand All @@ -229,7 +243,29 @@ const getDataInfo = ({ context }) => (d, i) => {
const infoItemLabels = data.labels || [];
const infoItemSubLabels = data?.subLabels || [];

return `{ "values": ${JSON.stringify(infoItemValues)}, "labels": ${JSON.stringify(infoItemLabels)}, "subLabels": ${JSON.stringify(infoItemSubLabels)} }`;
return `{ "mouseOnTooltip": true, "values": ${JSON.stringify(infoItemValues)}, "labels": ${JSON.stringify(infoItemLabels)}, "subLabels": ${JSON.stringify(infoItemSubLabels)} }`;
}

/**
* Get the data nfo for each path
*/
const getGroupLabelDataInfo = ({ context }) => (d, i) => {

const is2d = context.is2d();
const data = {
values: context.getValues(),
labels: context.getLabels(),
subLabels: context.getSubLabels()
};

const infoItemValues = data.values.map(item => is2d ? item.reduce((acc, current) => acc + current, 0) : item ) || [];
const infoItemLabels = data.labels || [];

const sectionDetailsAvailable = (data.subLabels?.length && is2d);
const sectionsDetailsObject = sectionDetailsAvailable ? data.values.map(arr => arr.map( (nestedArr, index) => ( { value: nestedArr, name: data.subLabels?.[index] || "NA" } ))) : undefined;
const sectionsDetails = sectionDetailsAvailable ? `, "sectionsDetails": ${JSON.stringify(sectionsDetailsObject)}` : "";

return `{ "mouseOnTooltipLabel": true ,"values": ${JSON.stringify(infoItemValues)}, "labels": ${JSON.stringify(infoItemLabels)} ${sectionsDetails} }`;
}

/**
Expand Down Expand Up @@ -390,13 +426,17 @@ const drawInfo = ({
labelFormatCallback = format.value;
}

const groupLabelsCallbackHandler = onEachGroupLabelsCallbacksHandler({ context });
const getDataInfoHandler = getGroupLabelDataInfo({ context });

getInfoSvgGroup(id, margin).selectAll('g.label__group')
.data(info)
.join(
enter => {

return enter.append("g")
.attr("class", "label__group")
.attr('data-info', getDataInfoHandler)
.each(function (d, i) {
const x = !vertical ? calcTextPos(i) : margin.text.left;
const y = !vertical ? margin.text.top : calcTextPos(i);
Expand Down Expand Up @@ -430,10 +470,20 @@ const drawInfo = ({

removeClickEvent(g);
addGroupLabelHandler(g, i);

})
.transition()
.duration(400)
.on("end", function (d, i, nodes) {
const pathElement = select(this);
pathElement.style("pointer-events", "all");
groupLabelsCallbackHandler(d, i, nodes);
});
},

update => update.each(function (d, i) {
update => update
.attr('data-info', getDataInfoHandler)
.each(function (d, i) {

const x = !vertical ? calcTextPos(i) : margin.text.left;
const y = !vertical ? margin.text.top : calcTextPos(i);
Expand Down Expand Up @@ -470,9 +520,15 @@ const drawInfo = ({

removeClickEvent(g);
addGroupLabelHandler(g, i);

}),
exit => exit
})
.transition()
.duration(400)
.on("end", function (d, i, nodes) {
const pathElement = select(this);
pathElement.style("pointer-events", "all");
groupLabelsCallbackHandler(d, i, nodes);
})
,exit => exit
.each(function () {
const g = select(this);
removeClickEvent(g);
Expand Down
2 changes: 1 addition & 1 deletion src/js/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/
export const getLogger = ({ module }) => {

const projectName = "[Funnel Graph JS]";
const projectName = "[D3 Funnel Graph]";
const _style = "background: #007acc; color: white; padding: 2px 4px; border-radius: 3px";
const getColorStyle = (method) => {
switch (method) {
Expand Down
14 changes: 14 additions & 0 deletions src/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const logger = getLogger({ module: "Main" });
* 'tooltip': (event, { label, value }) => {},
* -- top label handler
* label: (event, { index, value, label, subLabel, sectionIndex }) => {}
* tooltipLabel: (event, { value, label, sectionIndex }) => {}
* },
*
* format: {
Expand All @@ -50,6 +51,9 @@ const logger = getLogger({ module: "Main" });
* -- display the OOTB tooltip - on / off
* tooltip: true,
*
* -- display the OOTB tooltip label - on / off
* tooltipLabel: false,
*
* -- remove the text - only graph will be display
* details: false,
*
Expand Down Expand Up @@ -81,6 +85,7 @@ class FunnelGraph {

this.setDetails(options.hasOwnProperty('details') ? options.details : true);
this.setTooltip(options.hasOwnProperty('tooltip') ? options.tooltip : true);
this.setTooltipLabel(options.hasOwnProperty('tooltipLabel') ? options.tooltip : false);
this.getDirection(options?.direction);
this.setValues(options?.data?.values || []);
this.setLabels(options?.data?.labels || []);
Expand Down Expand Up @@ -140,6 +145,10 @@ class FunnelGraph {
return this.tooltip;
}

showTooltipLabel() {
return this.tooltipLabel;
}

showDetails() {
return this.details;
}
Expand Down Expand Up @@ -345,6 +354,10 @@ class FunnelGraph {
this.tooltip = bool;
}

setTooltipLabel(bool) {
this.tooltipLabel = bool;
}

setDetails(bool) {
this.details = bool;
}
Expand Down Expand Up @@ -625,6 +638,7 @@ class FunnelGraph {
{ key: "margin", fn: (arg) => this.setMargin(arg) },
{ key: "details", fn: (arg) => this.setDetails(arg) },
{ key: "tooltip", fn: (arg) => this.setTooltip(arg) },
{ key: "tooltipLabel", fn: (arg) => this.setTooltipLabel(arg) },
{ key: "values", fn: (arg) => this.setValues(arg) },
{ key: "labels", fn: (arg) => this.setLabels(arg) },
{ key: "subLabels", fn: (arg) => this.setSubLabels(arg) },
Expand Down