I'm very interested in doing a "Traceable Sankey Diagram" using AmCharts exactly like the one that can be experienced here: https://www.amcharts.com/demos/traceable-sankey-diagram/
When you move the mouse over a link (for instance C-F) you can see other links highlighted (i.e. F-I + I-Q). That's a GREAT feature but I'd like it to be slightly different. I would like that all "compatible" links to be highlighted (F-I but also F-M. Then I-Q, I-R, I-S, I-T and M-U) because all those can be following C-F.
Is it feasible ? I couldn't find any option in Amchart's doc for that. Thanks,
What is required is a new algorithm to select items that are to be highlighted on link hover.
To select all compatible targets and their targets, ... and so on, one may:
itemsToHighlight
with the item under the pointer, and store in an array allTargetIds
initially the target id of that itemitemsToHighlight
all those items whose source id is equal to one of the target ids in the allTargetIds
array.itemsToHighlight
.Now, if one also wants to also highlight compatible items to the left, that is all possible sources and all their possible sources, ... and so on, the algorithm is the same, but invert source and target positions.
Here's that part in code:
var itemsToHighlight = [];
series.links.template.events.on("pointerover", function(event) {
var dataItem = event.target.dataItem;
itemsToHighlight = [dataItem];
var allTargetIds = [dataItem.get("targetId")];
var allSourceIds = [dataItem.get("sourceId")];
// all compatible (possible) targets, and their possible
// targets, and so on until nothing new is found
var newItems = true;
while (newItems) {
newItems = false;
am5.array.each(series.dataItems, function(dataItem) {
if (itemsToHighlight.indexOf(dataItem) < 0 &&
allTargetIds.indexOf(dataItem.get("sourceId")) >= 0) {
allTargetIds.push(dataItem.get("targetId"));
itemsToHighlight.push(dataItem);
newItems = true;
}
});
}
// all compatible (possible) sources, and their possible
// sources, and so on until nothing new is found
newItems = true;
while (newItems) {
newItems = false;
am5.array.each(series.dataItems, function(dataItem) {
if (itemsToHighlight.indexOf(dataItem) < 0 &&
allSourceIds.indexOf(dataItem.get("targetId")) >= 0) {
allSourceIds.push(dataItem.get("sourceId"));
itemsToHighlight.push(dataItem);
newItems = true;
}
});
}
am5.array.each(itemsToHighlight, function(dataItem) {
dataItem.get("link").hover();
});
});
series.links.template.events.on("pointerout", function(event) {
am5.array.each(itemsToHighlight, function(dataItem) {
dataItem.get("link").unhover();
});
itemsToHighlight = [];
});
This is implemented for the links only, as in the example we started from. Nodes keep their default behaviour to highlight only their direct sources and targets. However, a behaviour similar to links can be extended to the nodes too, with minimal adaptations.
The adapted example in a snippet:
/**
* ---------------------------------------
* This demo was created using amCharts 5.
*
* For more information visit:
* https://www.amcharts.com/
*
* Documentation is available at:
* https://www.amcharts.com/docs/v5/
* ---------------------------------------
*/
// Create root element
// https://www.amcharts.com/docs/v5/getting-started/#Root_element
var root = am5.Root.new("chartdiv");
// Set themes
// https://www.amcharts.com/docs/v5/concepts/themes/
root.setThemes([am5themes_Animated.new(root)]);
// Create series
// https://www.amcharts.com/docs/v5/charts/flow-charts/
var series = root.container.children.push(
am5flow.Sankey.new(root, {
sourceIdField: "from",
targetIdField: "to",
valueField: "value",
paddingRight: 50,
idField: "id"
})
);
series.links.template.setAll({
fillStyle: "solid",
fillOpacity: 0.15
});
var itemsToHighlight = [];
series.links.template.events.on("pointerover", function(event) {
var dataItem = event.target.dataItem;
itemsToHighlight = [dataItem];
var allTargetIds = [dataItem.get("targetId")];
var allSourceIds = [dataItem.get("sourceId")];
// all compatible (possible) targets, and their possible
// targets, and so on until nothing new is found
var newItems = true;
while (newItems) {
newItems = false;
am5.array.each(series.dataItems, function(dataItem) {
if (itemsToHighlight.indexOf(dataItem) < 0 &&
allTargetIds.indexOf(dataItem.get("sourceId")) >= 0) {
allTargetIds.push(dataItem.get("targetId"));
itemsToHighlight.push(dataItem);
newItems = true;
}
});
}
// all compatible (possible) sources, and their possible
// sources, and so on until nothing new is found
newItems = true;
while (newItems) {
newItems = false;
am5.array.each(series.dataItems, function(dataItem) {
if (itemsToHighlight.indexOf(dataItem) < 0 &&
allSourceIds.indexOf(dataItem.get("targetId")) >= 0) {
allSourceIds.push(dataItem.get("sourceId"));
itemsToHighlight.push(dataItem);
newItems = true;
}
});
}
am5.array.each(itemsToHighlight, function(dataItem) {
dataItem.get("link").hover();
});
});
series.links.template.events.on("pointerout", function(event) {
am5.array.each(itemsToHighlight, function(dataItem) {
dataItem.get("link").unhover();
});
itemsToHighlight = [];
});
// Set data
// https://www.amcharts.com/docs/v5/charts/flow-charts/#Setting_data
series.data.setAll([{
from: "A",
to: "E",
value: 1,
id: "A0-0"
},
{
from: "A",
to: "F",
value: 1,
id: "A1-0"
},
{
from: "A",
to: "G",
value: 1,
id: "A2-0"
},
{
from: "B",
to: "E",
value: 1,
id: "B0-0"
},
{
from: "B",
to: "F",
value: 1,
id: "B1-0"
},
{
from: "B",
to: "G",
value: 1,
id: "B2-0"
},
{
from: "C",
to: "F",
value: 1,
id: "C0-0"
},
{
from: "C",
to: "G",
value: 1,
id: "C1-0"
},
{
from: "C",
to: "H",
value: 1,
id: "C2-0"
},
{
from: "D",
to: "E",
value: 1,
id: "D0-0"
},
{
from: "D",
to: "F",
value: 1,
id: "D1-0"
},
{
from: "D",
to: "G",
value: 1,
id: "D2-0"
},
{
from: "D",
to: "H",
value: 1,
id: "D3-0"
},
{
from: "E",
to: "I",
value: 1,
id: "A0-1"
},
{
from: "E",
to: "I",
value: 1,
id: "B0-1"
},
{
from: "E",
to: "L",
value: 1,
id: "D0-1"
},
{
from: "F",
to: "I",
value: 1,
id: "A1-1"
},
{
from: "F",
to: "I",
value: 1,
id: "C0-1"
},
{
from: "F",
to: "I",
value: 1,
id: "D1-1"
},
{
from: "F",
to: "M",
value: 1,
id: "B1-1"
},
{
from: "G",
to: "I",
value: 1,
id: "A2-1"
},
{
from: "G",
to: "I",
value: 1,
id: "B2-1"
},
{
from: "G",
to: "J",
value: 1,
id: "C1-1"
},
{
from: "G",
to: "N",
value: 1,
id: "D2-1"
},
{
from: "H",
to: "K",
value: 1,
id: "C2-1"
},
{
from: "H",
to: "N",
value: 1,
id: "D3-1"
},
{
from: "I",
to: "O",
value: 1,
id: "A0-2"
},
{
from: "I",
to: "O",
value: 1,
id: "B2-2"
},
{
from: "I",
to: "Q",
value: 1,
id: "A1-2"
},
{
from: "I",
to: "R",
value: 1,
id: "A2-2"
},
{
from: "I",
to: "S",
value: 1,
id: "D1-2"
},
{
from: "I",
to: "T",
value: 1,
id: "B0-2"
},
{
from: "I",
to: "Q",
value: 1,
id: "C0-2"
},
{
from: "J",
to: "U",
value: 1,
id: "C1-2"
},
{
from: "K",
to: "V",
value: 1,
id: "C2-2"
},
{
from: "M",
to: "U",
value: 1,
id: "B1-2"
},
{
from: "N",
to: "Q",
value: 1,
id: "D2-2"
},
{
from: "N",
to: "Q",
value: 1,
id: "D3-2"
},
{
from: "L",
to: "W",
value: 1,
id: "D0-2"
}
]);
// Make stuff animate on load
series.appear(1000, 100);
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
#chartdiv {
width: 100%;
height: 500px;
}
<script src="https://cdn.amcharts.com/lib/5/index.js"></script>
<script src="https://cdn.amcharts.com/lib/5/flow.js"></script>
<script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
<div id="chartdiv"></div>
or in a jsFiddle.