Skip to content

Commit

Permalink
support for custom link labels
Browse files Browse the repository at this point in the history
  • Loading branch information
FloSch62 committed Jun 24, 2024
1 parent 4e063ce commit 0916796
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 56 deletions.
152 changes: 111 additions & 41 deletions clab2drawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,12 @@ def add_ports(diagram, styles, verbose=True):
spacing = styles["node_width"] / (num_links + 1)
for i, link in enumerate(sorted_links):
port_x = (
node.pos_x
+ (i + 1) * spacing
- styles["connector_width"] / 2
node.pos_x + (i + 1) * spacing - styles["port_width"] / 2
)
port_y = (
node.pos_y
+ styles["node_height"]
- styles["connector_height"] / 2
- styles["port_height"] / 2
)
link.port_pos = (port_x, port_y)
elif direction == "upstream":
Expand All @@ -58,11 +56,9 @@ def add_ports(diagram, styles, verbose=True):
spacing = styles["node_width"] / (num_links + 1)
for i, link in enumerate(sorted_links):
port_x = (
node.pos_x
+ (i + 1) * spacing
- styles["connector_width"] / 2
node.pos_x + (i + 1) * spacing - styles["port_width"] / 2
)
port_y = node.pos_y - styles["connector_height"] / 2
port_y = node.pos_y - styles["port_height"] / 2
link.port_pos = (port_x, port_y)
else:
# Sort lateral links by y position of source and target
Expand Down Expand Up @@ -143,8 +139,8 @@ def add_ports(diagram, styles, verbose=True):
source_cID = f"{link.source.name}:{link.source_intf}:{link.target.name}:{link.target_intf}"
source_label = re.findall(r"\d+", link.source_intf)[-1]
source_connector_pos = link.port_pos
connector_width = styles["connector_width"]
connector_height = styles["connector_height"]
port_width = styles["port_width"]
port_height = styles["port_height"]

# Add the source connector ID to the source connector dictionary
if link.source.name not in connector_dict:
Expand Down Expand Up @@ -209,8 +205,8 @@ def add_ports(diagram, styles, verbose=True):
label=source_label,
x_pos=source_connector_pos[0],
y_pos=source_connector_pos[1],
width=connector_width,
height=connector_height,
width=port_width,
height=port_height,
style=styles["port_style"],
)

Expand All @@ -219,19 +215,19 @@ def add_ports(diagram, styles, verbose=True):
label=target_label,
x_pos=target_connector_pos[0],
y_pos=target_connector_pos[1],
width=connector_width,
height=connector_height,
width=port_width,
height=port_height,
style=styles["port_style"],
)

# Calculate center positions
source_center = (
source_connector_pos[0] + connector_width / 2,
source_connector_pos[1] + connector_height / 2,
source_connector_pos[0] + port_width / 2,
source_connector_pos[1] + port_height / 2,
)
target_center = (
target_connector_pos[0] + connector_width / 2,
target_connector_pos[1] + connector_height / 2,
target_connector_pos[0] + port_width / 2,
target_connector_pos[1] + port_height / 2,
)

# Calculate the real middle between the centers for the midpoint connector
Expand Down Expand Up @@ -272,8 +268,8 @@ def add_ports(diagram, styles, verbose=True):
label="\u200b",
x_pos=midpoint_top_left_x,
y_pos=midpoint_top_left_y,
width=4,
height=4,
width=styles["connector_width"],
height=styles["connector_height"],
style=styles["connector_style"],
)

Expand Down Expand Up @@ -353,15 +349,54 @@ def add_links(diagram, styles):
entryY = exitY = step
style = f"{styles['link_style']}entryY={entryY};exitY={exitY};entryX={entryX};exitX={exitX};"

diagram.add_link(
source=link.source.name,
target=link.target.name,
src_label=link.source_intf,
trgt_label=link.target_intf,
src_label_style=styles["src_label_style"],
trgt_label_style=styles["trgt_label_style"],
style=style,
)
# Create label nodes for source and target interfaces
source_label_id = f"label:{link.source.name}:{link.source_intf}"
target_label_id = f"label:{link.target.name}:{link.target_intf}"

if not styles["default_labels"]:
# Calculate label positions using the get_label_positions method
(
(source_label_x, source_label_y),
(target_label_x, target_label_y),
) = link.get_label_positions(entryX, entryY, exitX, exitY, styles)

diagram.add_link(
source=link.source.name,
target=link.target.name,
style=style,
)

# Add label nodes
diagram.add_node(
id=source_label_id,
# label should node name + interface name
label=f"{link.source_intf}",
x_pos=source_label_x,
y_pos=source_label_y,
width=styles["label_width"],
height=styles["label_height"],
style=styles["src_label_style"],
)

diagram.add_node(
id=target_label_id,
label=f"{link.target_intf}",
x_pos=target_label_x,
y_pos=target_label_y,
width=styles["label_width"],
height=styles["label_height"],
style=styles["trgt_label_style"],
)
else:
diagram.add_link(
source=link.source.name,
target=link.target.name,
src_label=link.source_intf,
trgt_label=link.target_intf,
src_label_style=styles["src_label_style"],
trgt_label_style=styles["trgt_label_style"],
style=style,
)


def add_nodes(diagram, nodes, styles):
Expand Down Expand Up @@ -787,7 +822,13 @@ def load_styles_from_config(config_path):


def interactive_mode(
nodes, icon_to_group_mapping, containerlab_data, output_file, processor, prefix, lab_name
nodes,
icon_to_group_mapping,
containerlab_data,
output_file,
processor,
prefix,
lab_name,
):
# Initialize previous summary with existing node labels
previous_summary = {"Levels": {}, "Icons": {}}
Expand Down Expand Up @@ -836,8 +877,13 @@ def interactive_mode(
unformatted_node_name = node_name.replace(f"{prefix}-{lab_name}-", "")

# Check if 'labels' section exists, create it if necessary
if "labels" not in containerlab_data["topology"]["nodes"][unformatted_node_name]:
containerlab_data["topology"]["nodes"][unformatted_node_name]["labels"] = {}
if (
"labels"
not in containerlab_data["topology"]["nodes"][unformatted_node_name]
):
containerlab_data["topology"]["nodes"][unformatted_node_name][
"labels"
] = {}

# Update containerlab_data with graph-level
containerlab_data["topology"]["nodes"][unformatted_node_name]["labels"][
Expand Down Expand Up @@ -876,8 +922,13 @@ def interactive_mode(
unformatted_node_name = node_name.replace(f"{prefix}-{lab_name}-", "")

# Check if 'labels' section exists, create it if necessary
if "labels" not in containerlab_data["topology"]["nodes"][unformatted_node_name]:
containerlab_data["topology"]["nodes"][unformatted_node_name]["labels"] = {}
if (
"labels"
not in containerlab_data["topology"]["nodes"][unformatted_node_name]
):
containerlab_data["topology"]["nodes"][unformatted_node_name][
"labels"
] = {}

# Update containerlab_data with graph-icon
containerlab_data["topology"]["nodes"][unformatted_node_name]["labels"][
Expand Down Expand Up @@ -965,14 +1016,30 @@ def main(
print(error_message)
exit()

if theme in ["nokia_bright", "nokia_dark", "grafana_dark"]:
config_path = os.path.join(script_dir, f"styles/{theme}.yaml")
else:
# Assume the user has provided a custom path
config_path = theme
# Load the theme file
try:
if os.path.isabs(theme):
theme_path = theme
else:
theme_path = os.path.join(script_dir, "styles", f"{theme}.yaml")

# Check if the theme file exists
if not os.path.exists(theme_path):
raise FileNotFoundError(
f"The specified theme file '{theme_path}' does not exist."
)

except FileNotFoundError as e:
error_message = str(e)
print(error_message)
exit()
except Exception as e:
error_message = f"An error occurred while loading the theme: {e}"
print(error_message)
exit()

# Load styles
styles = load_styles_from_config(config_path)
styles = load_styles_from_config(theme_path)

diagram = CustomDrawioDiagram()
diagram.layout = layout
Expand Down Expand Up @@ -1087,7 +1154,7 @@ def main(
input_file,
processor,
prefix,
lab_name
lab_name,
)

assign_graphlevels(diagram, verbose=False)
Expand Down Expand Up @@ -1127,6 +1194,9 @@ def main(
add_nodes(diagram, diagram.nodes, styles)

if grafana:
styles["ports"] = True

if styles["ports"]:
add_ports(diagram, styles)
if not output_file:
grafana_output_file = os.path.splitext(input_file)[0] + ".grafana.json"
Expand Down
54 changes: 54 additions & 0 deletions lib/Link.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,59 @@ def generate_style_string(self):
style += f"entryX={self.entryX};exitX={self.exitX};"
return style

def get_label_positions(self, entryX, entryY, exitX, exitY, styles):
source_x, source_y = self.source.pos_x, self.source.pos_y
target_x, target_y = self.target.pos_x, self.target.pos_y
node_width = styles["node_width"]
node_height = styles["node_height"]

# Calculate absolute positions for exit and entry based on normalized values
source_exit_x = source_x + node_width * exitX
source_exit_y = source_y + node_height * exitY
target_entry_x = target_x + node_width * entryX
target_entry_y = target_y + node_height * entryY

# Vector from source exit to target entry
dx = target_entry_x - source_exit_x
dy = target_entry_y - source_exit_y

# Normalize the vector
vector_length = (dx**2 + dy**2) ** 0.5
unit_dx = dx / vector_length if vector_length != 0 else 0
unit_dy = dy / vector_length if vector_length != 0 else 0

# Apply the label offset along the vector
label_offset = styles["label_offset"]
source_label_x = source_exit_x + unit_dx * label_offset
source_label_y = source_exit_y + unit_dy * label_offset
target_label_x = target_entry_x - unit_dx * label_offset
target_label_y = target_entry_y - unit_dy * label_offset

# Adjust labels based on width, height, and alignment
label_width = styles["label_width"]
label_height = styles["label_height"]

if styles["label_alignment"] == "left":
source_label_x -= (
label_width + 2
) # Move left by full width for left alignment
target_label_x -= label_width + 2
elif styles["label_alignment"] == "right":
source_label_x += (
label_width / 2
) # Move right by half width for right alignment
target_label_x += label_width / 2
elif styles["label_alignment"] == "center":
# No additional adjustment for x-axis; the label is already centered along the vector
source_label_x -= (
label_width / 2
) # Move right by half width for right alignment
target_label_x -= label_width / 2

source_label_y -= label_height / 2
target_label_y -= label_height / 2

return (source_label_x, source_label_y), (target_label_x, target_label_y)

def __repr__(self):
return f"Link(source='{self.source}', target='{self.target}')"
20 changes: 15 additions & 5 deletions styles/grafana_dark.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@ grid: "0"
pagew: "auto"
pageh: "auto"

node_width: 75
node_height: 75

# Base style for nodes; defines the general appearance for all nodes.
base_style: "shape=image;imageAlign=center;imageVerticalAlign=middle;labelPosition=left;align=right;verticalLabelPosition=top;spacingLeft=0;verticalAlign=bottom;spacingTop=0;spacing=0;fontColor=#F0F0F0;"

#Style for grafana themes
#Nodes with ports if ports:true (-g option for grafana will set always to true)
ports: true
port_style: "ellipse;whiteSpace=wrap;html=1;aspect=fixed;fontColor=#FFFFFF;fontSize=6;strokeColor=#98A2AE;fillColor=#BEC8D2;"
connector_style: "ellipse;whiteSpace=wrap;html=1;aspect=fixed;fontColor=#FFFFFF;fontSize=6;fillColor=#BEC8D2;strokeColor=none;noLabel=1;"

connector_width: 12
connector_height: 12
port_width: 12
port_height: 12

connector_width: 4
connector_height: 4

# Style for links between nodes
link_style: "rounded=0;orthogonalLoop=1;html=1;startSize=6;endArrow=classicThin;endFill=1;endSize=2;fontSize=14;strokeColor=#98A2AE;fontColor=#FFFFFF;textOpacity=60;labelBackgroundColor=#4D5766;jumpStyle=gap;strokeWidth=3;"
Expand All @@ -22,8 +29,11 @@ link_style: "rounded=0;orthogonalLoop=1;html=1;startSize=6;endArrow=classicThin;
src_label_style: "edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=6;fontColor=#FFFFFF;textOpacity=60;labelBackgroundColor=#4D5766;"
trgt_label_style: "edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=6;fontColor=#FFFFFF;textOpacity=60;labelBackgroundColor=#4D5766;"

node_width: 75
node_height: 75
default_labels: true
label_offset: 20
label_height: 10
label_width: 20
label_alignment: center

# Custom styles for different types of nodes, allowing for unique visual representation based on node role or function
custom_styles:
Expand Down
Loading

0 comments on commit 0916796

Please sign in to comment.