Commit ecd308f1 authored by Tiago Peixoto's avatar Tiago Peixoto
Browse files

Improve performance of graph_draw() and draw_hierarchy()

parent 83186f00
......@@ -1040,7 +1040,7 @@ public:
_attrs.template get<vector<double> >(EDGE_CONTROL_POINTS);
vector<double> gradient =
_attrs.template get<vector<double> >(EDGE_GRADIENT);
if (gradient.size() == 1)
if (gradient.size() == 2)
{
auto e_color = _attrs.template get<color_t>(EDGE_COLOR);
auto s_color = _s._attrs.template get<color_t>(VERTEX_FILL_COLOR);
......@@ -1109,10 +1109,8 @@ public:
color_t color = _attrs.template get<color_t>(EDGE_COLOR);
double pw;
pw = _attrs.template get<double>(EDGE_PENWIDTH);
double pw = _attrs.template get<double>(EDGE_PENWIDTH);
pw = get_user_dist(cr, pw);
cr.set_line_width(pw);
pos_t pos_begin_marker = pos_begin;
pos_t pos_end_marker = pos_end;
......@@ -1155,6 +1153,7 @@ public:
double sx1, sy1, sx2, sy2;
draw_edge_markers(pos_begin_marker, pos_begin_d, pos_end_marker,
pos_end_d, controls, marker_size, cr);
cr.set_line_width(pw);
draw_edge_line(pos_begin, pos_end, controls, cr);
cr.get_stroke_extents(sx1, sy1, sx2, sy2);
cr.begin_new_path();
......@@ -1282,6 +1281,7 @@ public:
}
draw_edge_line(pos_begin, pos_end, controls, cr);
cr.set_line_width(pw);
cr.stroke();
cr.reset_clip();
draw_edge_markers(pos_begin_marker, pos_begin_d, pos_end_marker,
......@@ -1531,14 +1531,17 @@ void draw_vertices(Graph&, pair<VertexIterator, VertexIterator> v_range,
Cairo::Context& cr)
{
typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;
for(VertexIterator v = v_range.first; v != v_range.second; ++v)
if (offset > count)
{
if (count < offset)
{
count++;
continue;
}
size_t dist = std::distance(v_range.first, v_range.second);
size_t skip = std::min(offset - count, dist);
std::advance(v_range.first, skip);
count += skip;
}
for(VertexIterator v = v_range.first; v != v_range.second; ++v)
{
pos_t pos;
if (pos_map[*v].size() >= 2)
{
......@@ -1562,14 +1565,23 @@ void draw_edges(Graph& g, pair<EdgeIterator, EdgeIterator> e_range,
{
typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;
typedef typename graph_traits<Graph>::edge_descriptor edge_t;
for(EdgeIterator e = e_range.first; e != e_range.second; ++e)
if (offset > count)
{
if (count < offset)
size_t E = num_edges(g);
if (offset - count >= E)
{
count++;
continue;
count += E;
return;
}
size_t dist = std::distance(e_range.first, e_range.second);
size_t skip = std::min(offset - count, dist);
std::advance(e_range.first, skip);
count += skip;
}
for(EdgeIterator e = e_range.first; e != e_range.second; ++e)
{
vertex_t s, t;
s = source(*e, g);
t = target(*e, g);
......
......@@ -761,7 +761,7 @@ class PropertyMap(object):
return numpy.array(self.fa)
except ValueError:
p = ungroup_vector_property(self, pos)
return numpy.array([x.a for x in p])
return numpy.array([x.fa for x in p])
def set_2d_array(self, a, pos=None):
r"""Set the entries of the vector-valued property map from a
......
......@@ -58,6 +58,7 @@ try:
default_cm = matplotlib.colors.LinearSegmentedColormap.from_list("Set3",
default_clrs)
is_draw_inline = 'inline' in matplotlib.get_backend()
color_converter = matplotlib.colors.ColorConverter()
except ImportError:
msg = "Error importing matplotlib module. Graph drawing will not work."
warnings.warn(msg, RuntimeWarning)
......@@ -77,7 +78,7 @@ import io
from collections import defaultdict
from .. import Graph, GraphView, PropertyMap, ungroup_vector_property,\
group_vector_property, _prop, _check_prop_vector
group_vector_property, _prop, _check_prop_vector, map_property_values
from .. stats import label_parallel_edges, label_self_loops
......@@ -94,11 +95,6 @@ except ImportError:
from .. draw import sfdp_layout, random_layout, _avg_edge_distance, \
coarse_graphs, radial_tree_layout, prop_to_size
try:
from matplotlib.colors import colorConverter
except ImportError:
pass
from .. generation import graph_union
from .. topology import shortest_path
......@@ -153,26 +149,27 @@ _edefaults = {
def shape_from_prop(shape, enum):
if isinstance(shape, PropertyMap):
g = shape.get_graph()
if shape.key_type() == "v":
prop = shape.get_graph().new_vertex_property("int")
descs = shape.get_graph().vertices()
prop = g.new_vertex_property("int")
descs = g.vertices()
else:
descs = shape.get_graph().edges()
prop = shape.get_graph().new_edge_property("int")
offset = min(enum.values.keys())
vals = dict([(int(k - offset), v) for k, v in enum.values.items()])
for v in descs:
if shape.value_type() == "string":
prop[v] = int(enum.__dict__[shape[v]])
elif int(shape[v]) in vals:
prop[v] = int(vals[int(shape[v])])
elif int(shape[v]) - offset in vals:
prop[v] = int(vals[int(shape[v]) - offset])
else:
raise ValueError("Invalid value for attribute %s: %s" %
(repr(enum), repr(shape[v])))
descs = g.edges()
prop = g.new_edge_property("int")
if shape.value_type() == "string":
def conv(x):
return int(getattr(enum, x))
map_property_values(shape, prop, conv)
else:
rg = (min(enum.values.keys()),
max(enum.values.keys()))
g.copy_property(shape, prop)
if prop.fa.min() < rg[0]:
prop.fa += rg[0]
prop.fa -= rg[0]
prop.fa %= rg[1] - rg[0] + 1
prop.fa += rg[0]
return prop
if isinstance(shape, str):
return int(enum.__dict__[shape])
else:
......@@ -262,16 +259,24 @@ def centered_rotation(g, pos, text_pos=True):
return angle, tpos
return angle
def _convert(attr, val, cmap):
def _convert(attr, val, cmap, pmap_default=False, g=None, k=None):
if attr == vertex_attrs.shape:
return shape_from_prop(val, vertex_shape)
if attr == vertex_attrs.surface:
return surface_from_prop(val)
if attr in [edge_attrs.start_marker, edge_attrs.mid_marker,
edge_attrs.end_marker]:
return shape_from_prop(val, edge_marker)
if attr in [vertex_attrs.pie_colors]:
new_val = shape_from_prop(val, vertex_shape)
if pmap_default and not isinstance(val, PropertyMap):
new_val = g.new_vertex_property("int", new_val)
return new_val
elif attr == vertex_attrs.surface:
new_val = surface_from_prop(val)
if pmap_default and not isinstance(val, PropertyMap):
new_val = g.new_vertex_property("python::object", new_val)
return new_val
elif attr in [edge_attrs.start_marker, edge_attrs.mid_marker,
edge_attrs.end_marker]:
new_val = shape_from_prop(val, edge_marker)
if pmap_default and not isinstance(val, PropertyMap):
new_val = g.new_edge_property("int", new_val)
return new_val
elif attr in [vertex_attrs.pie_colors]:
if isinstance(val, PropertyMap):
if val.value_type() in ["vector<double>", "vector<long double>"]:
return val
......@@ -285,95 +290,125 @@ def _convert(attr, val, cmap):
rg[1] = max(x, rg[1])
if rg[0] == rg[1]:
rg[1] = 1
for v in g.vertices():
new_val[v] = flatten([cmap((x - rg[0]) / (rg[1] - rg[0])) for x in val[v]])
map_property_values(val, new_val,
lambda y: flatten([cmap((x - rg[0]) / (rg[1] - rg[0])) for x in y]))
return new_val
if val.value_type() == "vector<string>":
g = val.get_graph()
new_val = g.new_vertex_property("vector<double>")
for v in g.vertices():
new_val[v] = flatten([matplotlib.colors.ColorConverter().to_rgba(x) for x in val[v]])
map_property_values(val, new_val,
lambda y: flatten([color_converter.to_rgba(x) for x in y]))
return new_val
if val.value_type() == "python::object":
try:
g = val.get_graph()
new_val = g.new_vertex_property("vector<double>")
for v in g.vertices():
def conv(y):
try:
new_val[v] = [float(x) for x in flatten(val[v])]
new_val[v] = [float(x) for x in flatten(y)]
except ValueError:
new_val[v] = flatten([matplotlib.colors.ColorConverter().to_rgba(x) for x in val[v]])
new_val[v] = flatten([color_converter.to_rgba(x) for x in y])
map_property_values(val, new_val, conv)
return new_val
except ValueError:
pass
else:
try:
return [float(x) for x in flatten(val)]
new_val = [float(x) for x in flatten(val)]
except ValueError:
try:
new_val = flatten(matplotlib.colors.ColorConverter().to_rgba(x) for x in val)
return list(new_val)
new_val = flatten(color_convert.to_rgba(x) for x in val)
new_val = list(new_val)
except ValueError:
pass
if attr in [vertex_attrs.color, vertex_attrs.fill_color,
vertex_attrs.text_color, vertex_attrs.halo_color,
edge_attrs.color, edge_attrs.text_color]:
if pmap_default:
val_a = numpy.zeros((g.num_vertices(), len(new_val)))
for i in range(len(new_val)):
val_a[:, i] = new_val[i]
return g.new_vertex_property("vector<double>", val_a)
else:
return new_val
elif attr in [vertex_attrs.color, vertex_attrs.fill_color,
vertex_attrs.text_color, vertex_attrs.halo_color,
edge_attrs.color, edge_attrs.text_color]:
if isinstance(val, list):
return val
if isinstance(val, (tuple, np.ndarray)):
return list(val)
if isinstance(val, str):
return list(matplotlib.colors.ColorConverter().to_rgba(val))
if isinstance(val, PropertyMap):
new_val = val
elif isinstance(val, (tuple, np.ndarray)):
new_val = list(val)
elif isinstance(val, str):
new_val = list(color_converter.to_rgba(val))
elif isinstance(val, PropertyMap):
if val.value_type() in ["vector<double>", "vector<long double>"]:
return val
if val.value_type() in ["int32_t", "int64_t", "double",
"long double", "unsigned long", "bool"]:
new_val = val
elif val.value_type() in ["int32_t", "int64_t", "double",
"long double", "unsigned long", "bool"]:
g = val.get_graph()
try:
vrange = [val.fa.min(), val.fa.max()]
except ValueError:
vrange = val[val.get_graph().vertex(0)]
vrange = [vrange, vrange]
for v in val.get_graph().vertices():
vrange[0] = min(vrange[0], val[v])
vrange[1] = max(vrange[1], val[v])
val_ = val.copy("int64_t")
cnorm = matplotlib.colors.Normalize(vmin=vrange[0],
vmax=vrange[1])
g = val.get_graph()
if val.key_type() == "v":
prop = val.get_graph().new_vertex_property("vector<double>")
descs = val.get_graph().vertices()
prop = g.new_vertex_property("vector<double>")
else:
prop = val.get_graph().new_edge_property("vector<double>")
descs = val.get_graph().edges()
for v in descs:
prop[v] = cmap(cnorm(val[v]))
return prop
if val.value_type() == "string":
prop = g.new_edge_property("vector<double>")
map_property_values(val, prop, lambda x: cmap(cnorm(x)))
new_val = prop
elif val.value_type() == "string":
g = val.get_graph()
if val.key_type() == "v":
prop = val.get_graph().new_vertex_property("vector<double>")
for v in val.get_graph().vertices():
prop[v] = matplotlib.colors.ColorConverter().to_rgba(val[v])
elif val.key_type() == "e":
prop = val.get_graph().new_edge_property("vector<double>")
for e in val.get_graph().edges():
prop[e] = matplotlib.colors.ColorConverter().to_rgba(val[e])
return prop
raise ValueError("Invalid value for attribute %s: %s" %
(repr(attr), repr(val)))
prop = g.new_vertex_property("vector<double>")
else:
prop = g.new_edge_property("vector<double>")
map_property_values(val, prop,
lambda x: color_converter.to_rgba(x))
new_val = prop
else:
raise ValueError("Invalid value for attribute %s: %s" %
(repr(attr), repr(val)))
if pmap_default and not isinstance(val, PropertyMap):
if attr in [vertex_attrs.color, vertex_attrs.fill_color,
vertex_attrs.text_color, vertex_attrs.halo_color]:
val_a = numpy.zeros((g.num_vertices(),len(new_val)))
for i in range(len(new_val)):
val_a[:, i] = new_val[i]
return g.new_vertex_property("vector<double>", val_a)
else:
val_a = numpy.zeros((g.num_edges(), len(new_val)))
for i in range(len(new_val)):
val_a[:,i] = new_val[i]
return g.new_edge_property("vector<double>", val_a)
else:
return new_val
if pmap_default and not isinstance(val, PropertyMap):
if isinstance(val, str):
if k == "v":
new_val = g.new_vertex_property("string", [val] * g.num_vertices())
else:
new_val = g.new_edge_property("string", [val] * g.num_edges())
else:
if k == "v":
new_val = g.new_vertex_property("double", val)
else:
new_val = g.new_edge_property("double", val)
return new_val
return val
def _attrs(attrs, d, g, cmap):
nattrs = {}
defaults = {}
for k, v in list(attrs.items()):
for k, v in attrs.items():
try:
if d == "v":
attr = vertex_attrs.__dict__[k]
else:
attr = edge_attrs.__dict__[k]
except KeyError:
warnings.warn("Unknown attribute: " + k, UserWarning)
warnings.warn("Unknown attribute: " + str(k), UserWarning)
continue
if isinstance(v, PropertyMap):
nattrs[int(attr)] = _prop(d, g, _convert(attr, v, cmap))
......@@ -381,6 +416,21 @@ def _attrs(attrs, d, g, cmap):
defaults[int(attr)] = _convert(attr, v, cmap)
return nattrs, defaults
def _convert_props(props, d, g, cmap, pmap_default=False):
nprops = {}
for k, v in props.items():
try:
if d == "v":
attr = vertex_attrs.__dict__[k]
else:
attr = edge_attrs.__dict__[k]
nprops[k] = _convert(attr, v, cmap, pmap_default=pmap_default,
g=g, k=d)
except KeyError:
warnings.warn("Unknown attribute: " + str(k), UserWarning)
continue
return nprops
def get_attr(attr, d, attrs, defaults):
if attr in attrs:
......@@ -400,8 +450,7 @@ def position_parallel_edges(g, pos, loop_angle=float("nan"),
if isinstance(loop_angle, PropertyMap):
angle = loop_angle
else:
angle = g.new_vertex_property("double")
angle.a = float(loop_angle)
angle = g.new_vertex_property("double", float(loop_angle))
g = GraphView(g, directed=True)
if ((len(lp.fa) == 0 or lp.fa.max() == 0) and
......@@ -450,7 +499,7 @@ def cairo_draw(g, pos, cr, vprops=None, eprops=None, vorder=None, eorder=None,
given via the ``vertex_<prop-name>`` parameters, where ``<prop-name>`` is
the name of the property.
eprops : dict (optional, default: ``None``)
Dictionary with the vertex properties. Individual properties may also be
Dictionary with the edge properties. Individual properties may also be
given via the ``edge_<prop-name>`` parameters, where ``<prop-name>`` is
the name of the property.
vorder : :class:`~graph_tool.PropertyMap` (optional, default: ``None``)
......@@ -586,25 +635,32 @@ def color_contrast(color):
def auto_colors(g, bg, pos, back):
c = g.new_vertex_property("vector<double>")
for v in g.vertices():
if isinstance(bg, PropertyMap):
bgc = bg[v]
elif isinstance(bg, str):
bgc = matplotlib.colors.ColorConverter().to_rgba(bg)
else:
bgc = bg
if isinstance(pos, PropertyMap):
p = pos[v]
else:
if pos == "centered":
p = 0
else:
p = pos
if not isinstance(bg, PropertyMap):
if isinstance(bg, str):
bg = color_converter.to_rgba(bg)
bgc = numpy.zeros((g.num_vertices(), 4))
for i in range(4):
bgc[:, i] = bg[i]
bg = g.new_vertex_property("vector<double>", bgc)
if not isinstance(pos, PropertyMap):
if pos == "centered":
pos = 0
pos = g.new_vertex_property("double", pos)
bg_a = bg.get_2d_array(range(4))
bgc_pos = numpy.zeros((g.num_vertices(), 5))
for i in range(4):
bgc_pos[:, i] = bg_a[i, :]
bgc_pos[:, 4] = pos.fa
bgc_pos = g.new_vertex_property("vector<double>", bgc_pos)
def conv(x):
bgc = x[:4]
p = x[4]
if p < 0:
c[v] = color_contrast(bgc)
return color_contrast(bgc)
else:
c[v] = color_contrast(back)
return color_contrast(back)
c = g.new_vertex_property("vector<double>")
map_property_values(bgc_pos, c, conv)
return c
def graph_draw(g, pos=None, vprops=None, eprops=None, vorder=None, eorder=None,
......@@ -879,8 +935,10 @@ def graph_draw(g, pos=None, vprops=None, eprops=None, vorder=None, eorder=None,
eprops = eprops.copy() if eprops is not None else {}
props, kwargs = parse_props("vertex", kwargs)
props = _convert_props(props, "v", g, kwargs.get("vcmap", default_cm))
vprops.update(props)
props, kwargs = parse_props("edge", kwargs)
props = _convert_props(props, "e", g, kwargs.get("ecmap", default_cm))
eprops.update(props)
if pos is None:
......@@ -907,7 +965,7 @@ def graph_draw(g, pos=None, vprops=None, eprops=None, vorder=None, eorder=None,
pw = eprops["pen_width"]
if isinstance(pw, PropertyMap):
pw = pw.copy()
pw.a = pw.a * 2.75
pw.fa *= 2.75
eprops["marker_size"] = pw
else:
eprops["marker_size"] = pw * 2.75
......@@ -916,18 +974,17 @@ def graph_draw(g, pos=None, vprops=None, eprops=None, vorder=None, eorder=None,
pw = eprops["pen_width"]
if isinstance(pw, PropertyMap):
pw = pw.copy()
pw.a *= 2
pw.fa *= 2
eprops["text_distance"] = pw
else:
eprops["text_distance"] = pw * 2
if "text" in vprops and ("text_color" not in vprops or vprops["text_color"] == "auto"):
vcmap = kwargs.get("vcmap", matplotlib.cm.jet)
bg = _convert(vertex_attrs.fill_color, vprops.get("fill_color", _vdefaults["fill_color"]), vcmap)
if "bg_color" in kwargs:
bg_color = kwargs["bg_color"]
else:
bg_color = [1., 1., 1., 1.]
bg = _convert(vertex_attrs.fill_color,
vprops.get("fill_color", _vdefaults["fill_color"]),
vcmap)
bg_color = kwargs.get("bg_color", [1., 1., 1., 1.])
vprops["text_color"] = auto_colors(g, bg,
vprops.get("text_position",
_vdefaults["text_position"]),
......@@ -973,14 +1030,6 @@ def graph_draw(g, pos=None, vprops=None, eprops=None, vorder=None, eorder=None,
output = io.BytesIO()
if output is None:
for p, val in vprops.items():
if isinstance(val, PropertyMap):
vprops[p] = _convert(vertex_attrs.__dict__[p], val,
kwargs.get("vcmap", default_cm))
for p, val in eprops.items():
if isinstance(val, PropertyMap):
eprops[p] = _convert(edge_attrs.__dict__[p], val,
kwargs.get("ecmap", default_cm))
fit_area = fit_view if fit_view != True else 0.95
return interactive_window(g, pos, vprops, eprops, vorder, eorder,
nodesfirst, geometry=output_size,
......@@ -1169,7 +1218,7 @@ def get_bb(g, pos, size, pen_width, size_scale=1, text=None, font_family=None,
size[:] = size[i]
break
sl = label_self_loops(g)
slm = sl.a.max() * 0.75
slm = sl.fa.max() * 0.75
delta = (size * size_scale * (slm + 1)) / 2 + pen_width * 2
x_range = [pos_x.fa.min(), pos_x.fa.max()]
y_range = [pos_y.fa.min(), pos_y.fa.max()]
......@@ -1425,10 +1474,11 @@ class GraphArtist(matplotlib.artist.Artist):
# ===================
#
def draw_hierarchy(state, pos=None, layout="radial", beta=0.8, ealpha=0.4,
halpha=0.6, subsample_edges=None, deg_order=True,
deg_size=True, vsize_scale=1, hsize_scale=1, hshortcuts=0,
hide=0, empty_branches=True, verbose=False, **kwargs):
def draw_hierarchy(state, pos=None, layout="radial", beta=0.8, vprops=None,
eprops=None, hvprops=None, heprops=None,
subsample_edges=None, deg_order=True, deg_size=True,
vsize_scale=1, hsize_scale=1, hshortcuts=0, hide=0,
empty_branches=True, **kwargs):
r"""Draw a nested block model state in a circular hierarchy layout with edge
bundling.
......@@ -1448,20 +1498,39 @@ def draw_hierarchy(state, pos=None, layout="radial", beta=0.8, ealpha=0.4,
position of the hierarchy tree.
beta : ``float`` (optional, default: ``.8``)
Edge bundling strength.
ealpha : ``float`` (optional, default: ``.8``)
Alpha value of the edge colors.
halpha : ``float`` (optional, default: ``.8``)
Alpha value of hierarchy nodes and edges.
vprops : dict (optional, default: ``None``)
Dictionary with the vertex properties. Individual properties may also be
given via the ``vertex_<prop-name>`` parameters, where ``<prop-name>`` is
the name of the property. See :func:`~graph_tool.draw.graph_draw` for
details.
eprops : dict (optional, default: ``None``)
Dictionary with the edge properties. Individual properties may also be
given via the ``edge_<prop-name>`` parameters, where ``<prop-name>`` is
the name of the property. See :func:`~graph_tool.draw.graph_draw` for
details.
hvprops : dict (optional, default: ``None``)
Dictionary with the vertex properties for the *hierarchy tree*.
Individual properties may also be given via the ``hvertex_<prop-name>``
parameters, where ``<prop-name>`` is the name of the property. See
:func:`~graph_tool.draw.graph_draw` for details.
heprops : dict (optional, default: ``None``)
Dictionary with the edge properties for the *hierarchy tree*. Individual
properties may also be given via the ``hedge_<prop-name>`` parameters,
where ``<prop-name>`` is the name of the property. See
:func:`~graph_tool.draw.graph_draw` for details.
subsample_edges : ``int`` or list of :class:`~graph_tool.Edge` instances (optional, default: ``None``)