# coding: utf-8
import types
from lxml import etree
from .connections import extract_adjacent_shapes
from .draw import draw_lines_svg_layer
from . import INKSCAPE_NSMAP, compute_shape_centers, svg_shapes_to_df
[docs]def auto_detect_adjacent_shapes(svg_source, shape_i_attr='id',
layer_name='Connections',
shapes_xpath='//svg:path | //svg:polygon',
extend=1.5):
'''
Attempt to automatically find "adjacent" shapes in a SVG layer.
In a layer within a new SVG document, draw each detected connection between
the center points of the corresponding shapes.
Parameters
----------
svg_source : str
Input SVG file as a filepath (or file-like object).
shape_i_attr : str, optional
Attribute of each shape SVG element that uniquely identifies the shape.
layer_name : str, optional
Name to use for the output layer where detected connections are drawn.
.. note:: Any existing layer with the same name will be overwritten.
shapes_xpath : str, optional
XPath path expression to select shape nodes.
By default, all ``svg:path`` and ``svg:polygon`` elements are selected.
extend : float, optional
Extend ``x``/``y`` coords by the specified number of absolute units
from the center point of each shape.
Each shape is stretched independently in the ``x`` and ``y`` direction.
In each direction, a shape is considered adjacent to all other shapes
that are overlapped by the extended shape.
Returns
-------
StringIO.StringIO
File-like object containing SVG document with layer named according to
:data:`layer_name` with the detected connections drawn as ``svg:line``
instances.
'''
# Read SVG polygons into dataframe, one row per polygon vertex.
df_shapes = svg_shapes_to_df(svg_source, xpath=shapes_xpath)
df_shapes = compute_shape_centers(df_shapes, shape_i_attr)
df_shape_connections = extract_adjacent_shapes(df_shapes, shape_i_attr,
extend=extend)
# Parse input file.
xml_root = etree.parse(svg_source)
svg_root = xml_root.xpath('/svg:svg', namespaces=INKSCAPE_NSMAP)[0]
# Get the center coordinate of each shape.
df_shape_centers = (df_shapes.drop_duplicates(subset=[shape_i_attr])
[[shape_i_attr] + ['x_center', 'y_center']]
.set_index(shape_i_attr))
# Get the center coordinate of the shapes corresponding to the two
# endpoints of each connection.
df_connection_centers = (df_shape_centers.loc[df_shape_connections.source]
.reset_index(drop=True)
.join(df_shape_centers.loc[df_shape_connections
.target]
.reset_index(drop=True), lsuffix='_source',
rsuffix='_target'))
# Remove existing connections layer from source, in-memory XML (source file
# remains unmodified). A new connections layer will be added below.
connections_xpath = '//svg:g[@inkscape:label="%s"]' % layer_name
connections_groups = svg_root.xpath(connections_xpath,
namespaces=INKSCAPE_NSMAP)
if connections_groups:
for g in connections_groups:
g.getparent().remove(g)
# Create in-memory SVG
svg_output = \
draw_lines_svg_layer(df_connection_centers
.rename(columns={'x_center_source': 'x_source',
'y_center_source': 'y_source',
'x_center_target': 'x_target',
'y_center_target': 'y_target'}),
layer_name=layer_name)
return svg_output