Problem: When using a Legend in a Force-Directed Tree, it overlaps content unnecessarily. How can I make the background transparent?
Demo: https://codepen.io/DavidDunham/pen/KKEeMPX (the code snipped on Stackoverflow does not seem to work hence this link)
// https://www.amcharts.com/docs/v5/charts/hierarchy/#Legend
var legend = container.children.push(
am5.Legend.new(root, {
centerX: am5.p100,
x: am5.p100,
layout: root.verticalLayout,
clickTarget: 'none'
})
);
console.clear();
var chartData = [
{
"name": "Root",
"value": 0,
"children": [
{
"name": "Rechtlicher Hintergrund",
"shortName": "Legal",
"radius": 90,
"children": [
{
"hidden": true,
"radius": 90,
"action": "chip",
"list": [
"[underline]IDType[/]",
"Identitätskarte",
"??BREAK??",
"[underline]IDAuthority[/]",
"Kanton Bern"
],
"_list": [
"Steuer-ID: XXX",
"Ausweisnummer: XXX",
"IDExpiryDate: 11.11.1111",
"IDType: TYPE",
"IDIssueDate: XXX",
"IDAuthority: XXX"
]
}
]
},
{
"name": "Vermögen",
"shortName": "Vermögen",
"radius": 90,
"children": [
{
"hidden": true,
"action": "table",
"data": [
{
"sizes": [
15,
5,
10,
5
],
"align": [
0,
0,
1,
1
]
},
[
"[underline]Vermögen[/]"
],
[
"??BREAK??"
],
[
"Depot",
"Bank",
"Assets",
"YTD"
],
[
"DISPL.CS-001_SIERRA_CHF",
"CREDIT SUISSE ZH",
"5’448’508",
"+0.06"
],
[
"UBS-001_SIERRA_CHF",
"UBS",
"3’904’721",
"-0.17"
],
[
"JB_001_SIERRA_CHF",
"JULIUS BAER",
"511’320",
"-0.07"
],
[
"",
"",
"9’864’549"
]
]
},
{
"hidden": true,
"action": "table",
"data": [
{
"sizes": [
15,
10,
10
],
"align": [
0,
1,
1
]
},
[
"[underline]Performance[/]"
],
[
"??BREAK??"
],
[
"Total Vermögen",
"29.12.2023",
"9’868’671"
],
[
"Mittelfluss",
"YTD 2024",
"0"
],
[
"Total Vermögen",
"19.01.2024",
"9’864’549"
],
[
"Erfolg in CHF",
"YTD 2024",
"-4’121"
],
[
"Erfolg in %",
"YTD 2024",
"-0.04"
]
]
}
]
},
{
"name": "CRM Todos",
"shortName": "CRM Todos",
"radius": 90,
"children": [
{
"hidden": true,
"action": "table",
"data": [
{
"sizes": [
4,
10,
10,
5
],
"align": [
0,
0,
0,
1
]
},
[
"[underline]CRM Feeds aktuell[/]"
],
[
"??BREAK??"
],
[
"ID",
"Betreff",
"Text",
"Wann"
],
[
4408,
"test 99",
"test 99",
"18.09.2023 12:19"
],
[
4407,
"test 77",
"test 77",
"18.09.2023 11:59"
],
[
4406,
"test 55",
"test 55",
"18.09.2023 11:58"
],
[
4405,
"test",
"test",
"18.09.2023 11:44"
]
]
}
]
}
]
}
];
console.log("chartData", JSON.stringify(chartData) );
// 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 wrapper container
var 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/#Adding
var 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.8,
//minRadius: 30,
//maxRadius: am5.percent(10),
legendLabelText: '[#FFFFFF fontSize: 20px] {shortName}'
})
);
// Make labels wrapped
series.labels.template.setAll({
oversizedBehavior: "wrap",
minScale: 0.5,
textAlign: "center",
fontSize: 18,
fill: am5.color(0x000000),
isMeasured: true
});
//
//
//
//
//
//
// Set up nodes
// - Tooltip text
// - Disable toggling
series.nodes.template.setAll({
tooltipText: "{category}",
toggleKey: "none",
cursorOverStyle: "default",
layout: root.verticalLayout
});
// https://www.amcharts.com/docs/v4/tutorials/different-tooltip-content-per-each-level-of-force-directed-nodes/
// series.nodes.template.tooltipText = "{name}: {value}";
//console.log('tooltip', series.nodes.template);
series.nodes.template.adapters.add('tooltipText', function(text, target) {
console.log('tooltip?', target);
return null; // disable all tooltips
if (target.dataItem) {
switch(target.dataItem.level) {
case 0:
return "#1: {name}";
case 1:
return "#2: {parent.name} > {name} ({value})";
case 2:
return "#3: {parent.parent.name} > {parent.name} > {name} ({value})";
}
}
return text;
});
// alter position for bezugspersonen
series.labels.template.adapters.add("y", function (y, target) {
var dataItem = target.dataItem;
var dataContext = target.dataItem.dataContext;
if ("action" in dataContext) {
if (dataContext.action == "icon") {
console.log("y dataItem", dataItem);
console.log("y dataContext", dataContext);
return 40;
}
}
});
// Add buttons to "actionable" nodes
series.nodes.template.setup = function (target) {
target.events.once("dataitemchanged", function (ev) {
var node = ev.target;
if (node.dataItem && node.dataItem.dataContext.action == "icon") {
var dataItem = node.dataItem;
var dataContext = dataItem.dataContext;
var icon = dataContext.icon;
console.log('icon type', icon);
console.log('icon dataItem', dataItem);
//console.log(dataItem.component.nodes.template);
console.log("series.labels", series);
// make text white
// ! this is an internal parameter, probably should not use it
dataItem._settings.category = '[#fff]' + dataItem._settings.category.replace(/\n/gm, '[/]\n[#fff]') + '[/]';
// = '[#fff]' + dataItem._settings.category + '[/]';
var iconSrc = icon;
let iconInject = target.children.push(
am5.Picture.new(root, {
width: 70,
height: 70,
centerX: am5.percent(50),
centerY: am5.percent(100), // at top
src: iconSrc
})
);
} else if (
node.dataItem &&
(node.dataItem.dataContext.action == "list" ||
node.dataItem.dataContext.action == "chip" ||
node.dataItem.dataContext.action == "table")
) {
var dataItem = node.dataItem;
var dataContext = dataItem.dataContext;
var type = node.dataItem.dataContext.action;
// Determine color
var color = node.dataItem.get("fill");
var list = node.dataItem.dataContext.list;
console.log("type!", type);
if (type == "table") {
var data = dataContext.data;
console.log("table", data);
var list = [];
var sizes = []; if('sizes' in data[0]){ sizes = data[0].sizes };
var align = []; if('align' in data[0]){ align = data[0].align };
console.log('vermoegen sizes', sizes);
data.forEach((line, i_row) => {
if(i_row==0){return;} // skip definition row
// if only 1 item in row don't break into columns, just print
if( line.length==1 ){
list.push(line);
console.log('line is 1 item', line)
return;
}
var row = [];
line.forEach((col,i_col) => {
var thisSize = sizes[i_col];
var thisAlign = align[i_col];
console.log('table col', i_col , col);
var len = thisSize;
var name = String(col).substring(0, len).padEnd(len, ' ');
// 0 = left, 1 = right
if(thisAlign==1){
name = String(col).substring(0, len).padStart(len, ' ');
}
row.push(name);
});
// table column seperator
//list.push(row.join('|')); // push line
//list.push(row.join('[#FF3FA5]|[/]')); // push line
list.push(row.join(' ')); // push line
});
} else {
// could cut text
var maxTextLength = 30;
list.forEach((x, i) => {
var text = x;
if (text.length >= maxTextLength) {
text = text.substring(0, maxTextLength - 4) + "...";
}
list[i] = text;
});
}
// add bullet icon
if (type == "list") {
list.forEach((x, i) => {
list[i] = "•" + x;
});
}
console.log("dataItem", node.dataItem);
console.log("dataItem list", list);
//
// join empty lines small
var text = '';
list.forEach((x,i,arr)=>{
var currentItem = x;
var nextItem = arr[i+1];
if(nextItem == '??BREAK??'){
list[i] = list[i] + '[fontSize: 6px]';
list[i+1] = '[/]';// + nextItem;
}
console.log('line break currentItem / next', i, currentItem, nextItem);
});
var text = list.join("\n");
var radius = dataItem.dataContext.radius;
var maxLength = 0;
list.forEach((x) => {
if (x.length > maxLength) {
maxLength = x.length;
}
});
console.log("maxLength", maxLength);
var maxTextLength = 0;
// calculate new radius
// one char in monospace uses 8 pixels (assuming 8pt idk haaa)
// then the radius is 50% of the diameter
var newRadius = (Math.max(maxTextLength, maxLength) * 8) / 2;
dataContext.newRadius = newRadius;
var button = node.children.push(
am5.Button.new(root, {
label: am5.Label.new(root, {
text: text, //"Open\nzwei",
fill: am5.color(0x000000),
position: "relative",
fontFamily: 'Monospace',
fontStyle: type == "chip" ? "italic" : "normal",
fontSize: 16
// width: 240
}),
centerX: am5.p50,
centerY: am5.p50
})
);
console.log("node element", node.children);
// https://www.amcharts.com/docs/v5/concepts/common-elements/buttons/
// https://www.amcharts.com/docs/v5/reference/roundedrectangle/#Settings
button.get("background").setAll({
fill: am5.color(0xf3f3f3),
fillOpacity: 0.8,
stroke: am5.color(0x000000),
strokeOpacity: 0.5,
cornerRadiusTL: 0,
cornerRadiusTR: 0,
cornerRadiusBR: 0,
cornerRadiusBL: 0
});
/*
button.get("background").states.create("hover", {
fill: am5.Color.lighten(color, 0.5)
});
// Add button click event
button.events.on("click", function(ev) {
console.log("Open", ev);
});
*/
// Reposition button at the bottom of the label
// disabled because of the connection line, when
// the box is below the circle the connection line seems long, because it connects to the top
// of the box, when the box is below it is short,
// the center of gravity must be the center of the box, not the top
/*
node.dataItem.on("label", function (label) {
label.events.on("boundschanged", function (ev) {
//button.set("dx", label.width());
button.set("dy", label.height() * -0.5);
});
});
*/
}
});
};
// ... except for central node
/*
series.circles.template.adapters.add('forceHidden', function(forceHidden, target) {
var dataItem = target.dataItem;
var dataContext = target.dataItem.dataContext;
if('hidden' in dataContext){
return true;
}
//console.log('circle', dataItem);
//console.log('circle', dataContext);
//return target.dataItem.get("depth") == 0 ? false: forceHidden;
});
*/
series.circles.template.adapters.add(
"fillOpacity",
function (strokeOpacity, target) {
var dataItem = target.dataItem;
var dataContext = target.dataItem.dataContext;
if ("hidden" in dataContext) {
return 0;
}
}
);
console.log('tooltip', series);
// Set custom radius for circles, based on data
series.circles.template.adapters.add("radius", function (radius, target) {
return target.dataItem.dataContext.radius || radius;
});
series.circles.template.adapters.add("radius", function (radius, target) {
var dataItem = target.dataItem;
var dataContext = target.dataItem.dataContext;
if ("newRadius" in dataContext) {
//console.log('newRadius', dataContext.newRadius);
// console.log('target', radius);
return dataContext.newRadius;
} else {
//console.log('oldRadius', radius);
}
return radius;
});
//
//
//
//
//
//
//
// Generate and set data
// https://www.amcharts.com/docs/v5/charts/hierarchy/#Setting_data
series.data.setAll(chartData);
series.set("selectedDataItem", series.dataItems[0]);
// Add legend
// https://www.amcharts.com/docs/v5/charts/hierarchy/#Legend
var legend = container.children.push(
am5.Legend.new(root, {
centerX: am5.p100,
x: am5.p100,
layout: root.verticalLayout,
clickTarget: 'none'
})
);
// Hide value labels
legend.valueLabels.template.set("forceHidden", true);
// Add handler for clicks
legend.itemContainers.template.events.on("click", function (ev) {
am5.array.each(series.dataItems[0].get("children"), function (item) {
if (item === ev.target.dataItem.dataContext || item.isHidden()) {
item.show();
} else {
item.hide();
}
});
});
// Set legend data
legend.data.setAll(series.dataItems[0].get("children"));
// Make stuff animate on load
series.appear(100, 100);
body {
font-family: "monospace";
background-color: #4C4E54;
}
#chartdiv {
width: 100%;
height: 95vh;
}
<script src="https://cdn.amcharts.com/lib/5/index.js"></script>
<script src="https://cdn.amcharts.com/lib/5/hierarchy.js"></script>
<script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
<div id="chartdiv"></div>
