mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-17 20:46:30 -06:00
Introduce Node class for SVG boxes
This commit is contained in:
parent
0b0a646f87
commit
ecba9699ed
@ -20,6 +20,52 @@ LINE_HEIGHT = 20
|
|||||||
TERMINATION_WIDTH = 80
|
TERMINATION_WIDTH = 80
|
||||||
|
|
||||||
|
|
||||||
|
class Node(Hyperlink):
|
||||||
|
"""
|
||||||
|
Create a node to be represented in the SVG document as a rectangular box with a hyperlink.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
position: (x, y) coordinates of the box's top left corner
|
||||||
|
width: Box width
|
||||||
|
url: Hyperlink URL
|
||||||
|
color: Box fill color (RRGGBB format)
|
||||||
|
labels: An iterable of text strings. Each label will render on a new line within the box.
|
||||||
|
radius: Box corner radius, for rounded corners (default: 10)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, position, width, url, color, labels, radius=10, **extra):
|
||||||
|
super(Node, self).__init__(href=url, target='_blank', **extra)
|
||||||
|
|
||||||
|
x, y = position
|
||||||
|
|
||||||
|
# Add the box
|
||||||
|
dimensions = (width - 2, PADDING + LINE_HEIGHT * len(labels) + PADDING)
|
||||||
|
box = Rect((x + OFFSET, y), dimensions, rx=radius, class_='parent-object', style=f'fill: #{color}')
|
||||||
|
self.add(box)
|
||||||
|
|
||||||
|
cursor = y + PADDING
|
||||||
|
|
||||||
|
# Add text label(s)
|
||||||
|
for i, label in enumerate(labels):
|
||||||
|
cursor += LINE_HEIGHT
|
||||||
|
text_coords = (x + width / 2, cursor - LINE_HEIGHT / 2)
|
||||||
|
text_color = f'#{foreground_color(color, dark="303030")}'
|
||||||
|
text = Text(label, insert=text_coords, fill=text_color, class_='bold' if not i else [])
|
||||||
|
self.add(text)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def box(self):
|
||||||
|
return self.elements[0] if self.elements else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def top_center(self):
|
||||||
|
return self.box['x'] + self.box['width'] / 2, self.box['y']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bottom_center(self):
|
||||||
|
return self.box['x'] + self.box['width'] / 2, self.box['y'] + self.box['height']
|
||||||
|
|
||||||
|
|
||||||
class CableTraceSVG:
|
class CableTraceSVG:
|
||||||
"""
|
"""
|
||||||
Generate a graphical representation of a CablePath in SVG format.
|
Generate a graphical representation of a CablePath in SVG format.
|
||||||
@ -86,73 +132,42 @@ class CableTraceSVG:
|
|||||||
# Other parent object
|
# Other parent object
|
||||||
return 'e0e0e0'
|
return 'e0e0e0'
|
||||||
|
|
||||||
def _draw_box(
|
def draw_parent_object(self, obj):
|
||||||
self,
|
node = Node(
|
||||||
x,
|
position=(0, self.cursor),
|
||||||
width,
|
width=self.width,
|
||||||
color,
|
url=obj.get_absolute_url(),
|
||||||
url,
|
color=self._get_color(obj),
|
||||||
labels,
|
labels=self._get_labels(obj)
|
||||||
reset_cursor=False,
|
)
|
||||||
radius=10
|
self.parent_objects.append(node)
|
||||||
):
|
self.cursor += node.box['height']
|
||||||
"""
|
|
||||||
Return an SVG Link element containing a Rect and one or more text labels representing a
|
|
||||||
parent object or cable termination point.
|
|
||||||
|
|
||||||
:param x: X axis position
|
|
||||||
:param width: Box width
|
|
||||||
:param color: Box fill color
|
|
||||||
:param url: Hyperlink URL
|
|
||||||
:param labels: Iterable of text labels
|
|
||||||
:param radius: Box corner radius (default: 10)
|
|
||||||
"""
|
|
||||||
_orig_cursor = self.cursor
|
|
||||||
|
|
||||||
# Create a hyperlink
|
|
||||||
link = Hyperlink(href=f'{self.base_url}{url}', target='_blank')
|
|
||||||
|
|
||||||
# Add the box
|
|
||||||
position = (x + OFFSET, self.cursor)
|
|
||||||
height = PADDING \
|
|
||||||
+ LINE_HEIGHT * len(labels) \
|
|
||||||
+ PADDING
|
|
||||||
box = Rect(position, (width - 2, height), rx=radius, class_='parent-object', style=f'fill: #{color}')
|
|
||||||
link.add(box)
|
|
||||||
self.cursor += PADDING
|
|
||||||
|
|
||||||
# Add text label(s)
|
|
||||||
for i, label in enumerate(labels):
|
|
||||||
self.cursor += LINE_HEIGHT
|
|
||||||
text_coords = (x + width / 2, self.cursor - LINE_HEIGHT / 2)
|
|
||||||
text_color = f'#{foreground_color(color, dark="303030")}'
|
|
||||||
text = Text(label, insert=text_coords, fill=text_color, class_='bold' if not i else [])
|
|
||||||
link.add(text)
|
|
||||||
|
|
||||||
if reset_cursor:
|
|
||||||
self.cursor = _orig_cursor
|
|
||||||
else:
|
|
||||||
self.cursor += PADDING
|
|
||||||
|
|
||||||
return link
|
|
||||||
|
|
||||||
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
|
||||||
are attached to the same end of a cable.
|
are attached to the same end of a cable.
|
||||||
"""
|
"""
|
||||||
|
nodes = []
|
||||||
|
nodes_height = 0
|
||||||
x = self.width / 2 - len(terminations) * TERMINATION_WIDTH / 2
|
x = self.width / 2 - len(terminations) * TERMINATION_WIDTH / 2
|
||||||
|
|
||||||
for i, term in enumerate(terminations):
|
for i, term in enumerate(terminations):
|
||||||
t = self._draw_box(
|
node = Node(
|
||||||
x=x + i * TERMINATION_WIDTH,
|
position=(x + i * TERMINATION_WIDTH, self.cursor),
|
||||||
width=TERMINATION_WIDTH,
|
width=TERMINATION_WIDTH,
|
||||||
color=self._get_color(term),
|
|
||||||
url=term.get_absolute_url(),
|
url=term.get_absolute_url(),
|
||||||
|
color=self._get_color(term),
|
||||||
labels=self._get_labels(term),
|
labels=self._get_labels(term),
|
||||||
radius=5,
|
radius=5
|
||||||
reset_cursor=bool(i + 1 != len(terminations))
|
|
||||||
)
|
)
|
||||||
self.terminations.append(t)
|
nodes_height = max(nodes_height, node.box['height'])
|
||||||
|
nodes.append(node)
|
||||||
|
|
||||||
|
self.cursor += nodes_height
|
||||||
|
self.terminations.extend(nodes)
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
|
||||||
def draw_cable(self, color, url, labels):
|
def draw_cable(self, color, url, labels):
|
||||||
"""
|
"""
|
||||||
@ -257,14 +272,7 @@ class CableTraceSVG:
|
|||||||
# 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
|
||||||
parent_object = self._draw_box(
|
self.draw_parent_object(near_ends[0].parent_object)
|
||||||
x=0,
|
|
||||||
width=self.width,
|
|
||||||
color=self._get_color(near_ends[0].parent_object),
|
|
||||||
url=near_ends[0].parent_object.get_absolute_url(),
|
|
||||||
labels=self._get_labels(near_ends[0].parent_object)
|
|
||||||
)
|
|
||||||
self.parent_objects.append(parent_object)
|
|
||||||
|
|
||||||
# Near end termination
|
# Near end termination
|
||||||
self.draw_terminations(near_ends)
|
self.draw_terminations(near_ends)
|
||||||
@ -308,14 +316,7 @@ class CableTraceSVG:
|
|||||||
self.draw_terminations(far_ends)
|
self.draw_terminations(far_ends)
|
||||||
|
|
||||||
# Far end parent
|
# Far end parent
|
||||||
parent_object = self._draw_box(
|
self.draw_parent_object(far_ends[0].parent_object)
|
||||||
x=0,
|
|
||||||
width=self.width,
|
|
||||||
color=self._get_color(far_ends[0].parent_object),
|
|
||||||
url=far_ends[0].parent_object.get_absolute_url(),
|
|
||||||
labels=self._get_labels(far_ends[0].parent_object),
|
|
||||||
)
|
|
||||||
self.parent_objects.append(parent_object)
|
|
||||||
|
|
||||||
elif far_ends:
|
elif far_ends:
|
||||||
|
|
||||||
@ -324,14 +325,7 @@ class CableTraceSVG:
|
|||||||
self.connectors.append(attachment)
|
self.connectors.append(attachment)
|
||||||
|
|
||||||
# ProviderNetwork
|
# ProviderNetwork
|
||||||
parent_object = self._draw_box(
|
self.draw_parent_object(far_ends[0])
|
||||||
x=0,
|
|
||||||
width=self.width,
|
|
||||||
color=self._get_color(far_ends[0]),
|
|
||||||
url=far_ends[0].get_absolute_url(),
|
|
||||||
labels=self._get_labels(far_ends[0])
|
|
||||||
)
|
|
||||||
self.parent_objects.append(parent_object)
|
|
||||||
|
|
||||||
# Determine drawing size
|
# Determine drawing size
|
||||||
self.drawing = svgwrite.Drawing(
|
self.drawing = svgwrite.Drawing(
|
||||||
|
Loading…
Reference in New Issue
Block a user