Add support for cable fan-outs

This commit is contained in:
jeremystretch 2022-06-07 16:43:22 -04:00
parent ecba9699ed
commit 8d92ec2007

View File

@ -16,8 +16,8 @@ __all__ = (
OFFSET = 0.5 OFFSET = 0.5
PADDING = 10 PADDING = 10
LINE_HEIGHT = 20 LINE_HEIGHT = 20
FANOUT_HEIGHT = 25
TERMINATION_WIDTH = 80 TERMINATION_WIDTH = 100
class Node(Hyperlink): class Node(Hyperlink):
@ -66,6 +66,47 @@ class Node(Hyperlink):
return self.box['x'] + self.box['width'] / 2, self.box['y'] + self.box['height'] return self.box['x'] + self.box['width'] / 2, self.box['y'] + self.box['height']
class Connector(Group):
"""
Return an SVG group containing a line element and text labels representing a Cable.
Arguments:
color: Cable (line) color
url: Hyperlink URL
labels: Iterable of text labels
"""
def __init__(self, start, url, color, labels=[], **extra):
super().__init__(class_='connector', **extra)
self.start = start
self.height = PADDING * 2 + LINE_HEIGHT * len(labels) + PADDING * 2
self.end = (start[0], start[1] + self.height)
self.color = color or '000000'
# Draw a "shadow" line to give the cable a border
cable_shadow = Line(start=self.start, end=self.end, class_='cable-shadow')
self.add(cable_shadow)
# Draw the cable
cable = Line(start=self.start, end=self.end, style=f'stroke: #{self.color}')
self.add(cable)
# Add link
link = Hyperlink(href=url, target='_blank')
# Add text label(s)
cursor = start[1]
cursor += PADDING * 2
for i, label in enumerate(labels):
cursor += LINE_HEIGHT
text_coords = (start[0] + PADDING * 2, cursor - LINE_HEIGHT / 2)
text = Text(label, insert=text_coords, class_='bold' if not i else [])
link.add(text)
self.add(link)
class CableTraceSVG: class CableTraceSVG:
""" """
Generate a graphical representation of a CablePath in SVG format. Generate a graphical representation of a CablePath in SVG format.
@ -136,13 +177,15 @@ class CableTraceSVG:
node = Node( node = Node(
position=(0, self.cursor), position=(0, self.cursor),
width=self.width, width=self.width,
url=obj.get_absolute_url(), url=f'{self.base_url}{obj.get_absolute_url()}',
color=self._get_color(obj), color=self._get_color(obj),
labels=self._get_labels(obj) labels=self._get_labels(obj)
) )
self.parent_objects.append(node) self.parent_objects.append(node)
self.cursor += node.box['height'] self.cursor += node.box['height']
return node
def draw_terminations(self, terminations): def draw_terminations(self, terminations):
""" """
Draw a row of terminating objects (e.g. interfaces) belonging to the same parent object, all of which Draw a row of terminating objects (e.g. interfaces) belonging to the same parent object, all of which
@ -156,7 +199,7 @@ class CableTraceSVG:
node = Node( node = Node(
position=(x + i * TERMINATION_WIDTH, self.cursor), position=(x + i * TERMINATION_WIDTH, self.cursor),
width=TERMINATION_WIDTH, width=TERMINATION_WIDTH,
url=term.get_absolute_url(), url=f'{self.base_url}{term.get_absolute_url()}',
color=self._get_color(term), color=self._get_color(term),
labels=self._get_labels(term), labels=self._get_labels(term),
radius=5 radius=5
@ -169,53 +212,51 @@ class CableTraceSVG:
return nodes return nodes
def draw_cable(self, color, url, labels): def draw_fanin(self, node, connector):
""" self.connectors.extend((
Return an SVG group containing a line element and text labels representing a Cable. Line(start=node.bottom_center, end=connector.start, class_='cable-shadow'),
Line(start=node.bottom_center, end=connector.start, style=f'stroke: #{connector.color}'),
))
:param color: Cable (line) color def draw_fanout(self, node, connector):
:param url: Hyperlink URL self.connectors.extend((
:param labels: Iterable of text labels Line(start=connector.end, end=node.top_center, class_='cable-shadow'),
""" Line(start=connector.end, end=node.top_center, style=f'stroke: #{connector.color}')
group = Group(class_='connector') ))
# Draw a "shadow" line to give the cable a border def draw_cable(self, cable):
start = (OFFSET + self.center, self.cursor) labels = [
height = PADDING * 2 + LINE_HEIGHT * len(labels) + PADDING * 2 f'Cable {cable}',
end = (start[0], start[1] + height) cable.get_status_display()
cable_shadow = Line(start=start, end=end, class_='cable-shadow') ]
group.add(cable_shadow) if cable.type:
labels.append(cable.get_type_display())
if cable.length and cable.length_unit:
labels.append(f'{cable.length} {cable.get_length_unit_display()}')
connector = Connector(
start=(self.center + OFFSET, self.cursor),
color=cable.color or '000000',
url=f'{self.base_url}{cable.get_absolute_url()}',
labels=labels
)
# Draw the cable self.cursor += connector.height
cable = Line(start=start, end=end, style=f'stroke: #{color}')
group.add(cable)
self.cursor += PADDING * 2 return connector
# Add link def draw_wirelesslink(self, wirelesslink):
link = Hyperlink(href=f'{self.base_url}{url}', target='_blank')
# Add text label(s)
for i, label in enumerate(labels):
self.cursor += LINE_HEIGHT
text_coords = (self.center + PADDING * 2, self.cursor - LINE_HEIGHT / 2)
text = Text(label, insert=text_coords, class_='bold' if not i else [])
link.add(text)
group.add(link)
self.cursor += PADDING * 2
return group
def draw_wirelesslink(self, url, labels):
""" """
Draw a line with labels representing a WirelessLink. Draw a line with labels representing a WirelessLink.
:param url: Hyperlink URL
:param labels: Iterable of text labels
""" """
group = Group(class_='connector') group = Group(class_='connector')
labels = [
f'Wireless link {wirelesslink}',
wirelesslink.get_status_display()
]
if wirelesslink.ssid:
labels.append(wirelesslink.ssid)
# Draw the wireless link # Draw the wireless link
start = (OFFSET + self.center, self.cursor) start = (OFFSET + self.center, self.cursor)
height = PADDING * 2 + LINE_HEIGHT * len(labels) + PADDING * 2 height = PADDING * 2 + LINE_HEIGHT * len(labels) + PADDING * 2
@ -226,7 +267,7 @@ class CableTraceSVG:
self.cursor += PADDING * 2 self.cursor += PADDING * 2
# Add link # Add link
link = Hyperlink(href=f'{self.base_url}{url}', target='_blank') link = Hyperlink(href=f'{self.base_url}{wirelesslink.get_absolute_url()}', target='_blank')
# Add text label(s) # Add text label(s)
for i, label in enumerate(labels): for i, label in enumerate(labels):
@ -267,53 +308,48 @@ class CableTraceSVG:
# Iterate through each (terms, cable, terms) segment in the path # Iterate through each (terms, cable, terms) segment in the path
for i, segment in enumerate(traced_path): for i, segment in enumerate(traced_path):
near_ends, connector, far_ends = segment near_ends, link, far_ends = segment
# Near end parent # Near end parent
if i == 0: if i == 0:
# If this is the first segment, draw the originating termination's parent object # If this is the first segment, draw the originating termination's parent object
self.draw_parent_object(near_ends[0].parent_object) self.draw_parent_object(near_ends[0].parent_object)
# Near end termination # Near end termination(s)
self.draw_terminations(near_ends) terminations = self.draw_terminations(near_ends)
# Connector (a Cable or WirelessLink) # Connector (a Cable or WirelessLink)
connector = connector[0] # Remove Cable from list link = link[0] # Remove Cable from list
if connector is not None: if link is not None:
# Cable # Cable
if type(connector) is Cable: if type(link) is Cable:
connector_labels = [
f'Cable {connector}', # Account for fan-ins height
connector.get_status_display() if len(near_ends) > 1:
] self.cursor += FANOUT_HEIGHT
if connector.type:
connector_labels.append(connector.get_type_display()) cable = self.draw_cable(link)
if connector.length and connector.length_unit:
connector_labels.append(f'{connector.length} {connector.get_length_unit_display()}')
cable = self.draw_cable(
color=connector.color or '000000',
url=connector.get_absolute_url(),
labels=connector_labels
)
self.connectors.append(cable) self.connectors.append(cable)
# Draw fan-ins
if len(near_ends) > 1:
for term in terminations:
self.draw_fanin(term, cable)
# WirelessLink # WirelessLink
elif type(connector) is WirelessLink: elif type(link) is WirelessLink:
connector_labels = [ wirelesslink = self.draw_wirelesslink(link)
f'Wireless link {connector}',
connector.get_status_display()
]
if connector.ssid:
connector_labels.append(connector.ssid)
wirelesslink = self.draw_wirelesslink(
url=connector.get_absolute_url(),
labels=connector_labels
)
self.connectors.append(wirelesslink) self.connectors.append(wirelesslink)
# Far end termination # Far end termination(s)
self.draw_terminations(far_ends) if len(far_ends) > 1:
self.cursor += FANOUT_HEIGHT
terminations = self.draw_terminations(far_ends)
for term in terminations:
self.draw_fanout(term, cable)
else:
self.draw_terminations(far_ends)
# Far end parent # Far end parent
self.draw_parent_object(far_ends[0].parent_object) self.draw_parent_object(far_ends[0].parent_object)