ForceDirected with custom nodes/buttons/legend
This demo creates a custom ForceDirected tree chart with a few advanced features, e.g.:
- Custom radius and color for each node via data.
- Buttons and icons added to nodes.
- Legend, which toggles nodes.
Open in:New tab
/** * --------------------------------------- * 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/ * --------------------------------------- */
// Define datavar data = [ { name: "Root", children: [ { name: "Sed et nulla a enim tincidunt gravida", shortName: "Gravida", color: am5.color(0x86a5d9), radius: 90, action: "open", children: [ { name: "First", value: 46 }, { name: "Second", value: 96, radius: 80 }, { name: "Third", value: 100 }, { name: "New", value: 50, action: "add", color: am5.color(0xd0dcf0), radius: 35 } ] },
{ name: "Fusce ut nisi eu nibh laoreet scelerisque", shortName: "Fusce ut nisi", color: am5.color(0x7e6ec4), radius: 90, action: "open", children: [ { name: "First", value: 46 }, { name: "Second", value: 96, radius: 25 }, { name: "Third", value: 100 }, { name: "New", value: 50, action: "add", color: am5.color(0xd8d4ed), radius: 35 } ] },
{ name: "Test 3", shortName: "Test 3", color: am5.color(0xfec700), radius: 90, action: "open", children: [ { name: "First", value: 46 }, { name: "Second", value: 96, radius: 25 }, { name: "Third", value: 100 }, { name: "New", value: 50, action: "add", color: am5.color(0xd8d4ed), radius: 35 } ] },
{ name: "Test 4", shortName: "Test 4", color: am5.color(0x00fe17), radius: 90, action: "open", children: [ { name: "First", value: 46 }, { name: "Second", value: 96, radius: 25 }, { name: "Third", value: 100 }, { name: "New", value: 50, action: "add", color: am5.color(0xd8d4ed), radius: 35 } ] } ] }];
// Create root element// https://www.amcharts.com/docs/v5/getting-started/#Root_elementvar root = am5.Root.new("chartdiv");
// Set themes// https://www.amcharts.com/docs/v5/concepts/themes/root.setThemes([am5themes_Animated.new(root)]);
// Create wrapper containervar container = root.container.children.push( am5.Container.new(root, { width: am5.percent(100), height: am5.percent(100), layout: root.horizontalLayout }));
// Create series// https://www.amcharts.com/docs/v5/charts/hierarchy/#Addingvar series = container.children.push( am5hierarchy.ForceDirected.new(root, { singleBranchOnly: false, topDepth: 1, downDepth: 1, initialDepth: 2, valueField: "value", categoryField: "name", childDataField: "children", fillField: "color", centerStrength: 0.5, minRadius: 30, maxRadius: am5.percent(10), legendLabelText: "{shortName}" }));
// Make labels wrappedseries.labels.template.setAll({ oversizedBehavior: "wrap", minScale: 0.5, textAlign: "center", isMeasured: true});
// Set custom radius for circles, based on dataseries.circles.template.adapters.add("radius", function (radius, target) { return target.dataItem.dataContext.radius || radius;});
// Set up nodes// - Tooltip text// - Disable togglingseries.nodes.template.setAll({ tooltipText: "{category}", toggleKey: "none", cursorOverStyle: "default", layout: root.verticalLayout});
// Add buttons to "actionable" nodesseries.nodes.template.setup = function (target) { target.events.once("dataitemchanged", function (ev) { var node = ev.target; if (node.dataItem && node.dataItem.dataContext.action == "add") { // === Add new node =====================================================
// Determine color var color = node.dataItem.get("parent").get("fill");
// Add icon var icon = node.children.push( am5.Graphics.new(root, { width: 24, height: 24, centerX: am5.p50, centerY: am5.p100, fill: color, svgPath: "M12 1C5.9 1 1 5.9 1 12s4.9 11 11 11 11-4.9 11-11S18.1 1 12 1zm5 13h-3v3c0 1.1-.9 2-2 2s-2-.9-2-2v-3H7c-1.1 0-2-.9-2-2s.9-2 2-2h3V7c0-1.1.9-2 2-2s2 .9 2 2v3h3c1.1 0 2 .9 2 2s-.9 2-2 2z" }) );
// Disable label node.dataItem.on("label", function (label) { label.setAll({ centerY: am5.p0, fill: color }); });
// Set cursor on node node.setAll({ cursorOverStyle: "pointer" });
// Click event node.events.on("click", function (ev) { console.log("Add", ev); }); } else if (node.dataItem && node.dataItem.dataContext.action == "open") { // Determine color var color = node.dataItem.get("fill");
// Add "open" button var button = node.children.push( am5.Button.new(root, { label: am5.Label.new(root, { text: "Open", fill: color, position: "relative" }),
centerX: am5.p50, centerY: am5.p0 }) );
button.get("background").setAll({ fill: am5.color(0xffffff), cornerRadiusTL: 30, cornerRadiusTR: 30, cornerRadiusBR: 30, cornerRadiusBL: 30 });
button.get("background").states.create("hover", { fill: am5.Color.lighten(color, 0.5) });
// Reposition button at the bottom of the label node.dataItem.on("label", function (label) { label.events.on("boundschanged", function (ev) { button.set("dy", label.height() - 5); }); });
// Add button click event button.events.on("click", function (ev) { console.log("Open", ev); }); } });};
// Generate and set data// https://www.amcharts.com/docs/v5/charts/hierarchy/#Setting_dataseries.data.setAll(data);series.set("selectedDataItem", series.dataItems[0]);
// Add legend// https://www.amcharts.com/docs/v5/charts/hierarchy/#Legendvar legend = container.children.push( am5.Legend.new(root, { centerX: am5.p100, x: am5.p100, layout: root.verticalLayout, clickTarget: "none" }));
// Hide value labelslegend.valueLabels.template.set("forceHidden", true);
// Add handler for clickslegend.itemContainers.template.events.on("click", function (ev) { var clickedItem = ev.target.dataItem.dataContext; var itemCount = legend.itemContainers.length;
const visibleItemCount = getVisibleItems();
if (visibleItemCount === itemCount) { console.log("a"); // All items are visible hideAllExcept(clickedItem); } else if (visibleItemCount === 1 && !clickedItem.isHidden()) { console.log("b"); // Only the clicked item is visible showAllItems(); } else { console.log("c"); // Some item is visible, and it's not the clicked one hideAllExcept(clickedItem); }});
function getVisibleItems() { var visibleItemCount = 0; am5.array.each(series.dataItems[0].get("children"), function (item) { if (!item.isHidden()) { visibleItemCount++; } }); return visibleItemCount;}
function showAllItems() { am5.array.each(series.dataItems[0].get("children"), function (item) { item.show(); });}
function hideAllExcept(target) { am5.array.each(series.dataItems[0].get("children"), function (item) { if (item != target) { item.hide(); } }); target.show();}
// Set legend datalegend.data.setAll(series.dataItems[0].get("children"));
// Make stuff animate on loadseries.appear(1000, 100);<div id="chartdiv"></div>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: 95vh;}forcedirected-with-custom-nodesbuttonslegend