Commit 4914c902 authored by Tiago Peixoto's avatar Tiago Peixoto

Implement neighbourhood highlight in interactive graph_draw()

parent ecd308f1
......@@ -811,7 +811,7 @@ public:
}
boost::python::object osrc = _attrs.template get<boost::python::object>(VERTEX_SURFACE);
if (osrc == boost::python::object())
if (osrc == boost::python::object() || outline)
{
pw =_attrs.template get<double>(VERTEX_PENWIDTH);
pw = get_user_dist(cr, pw);
......@@ -919,33 +919,30 @@ public:
cr.fill_preserve();
cr.set_source_rgba(get<0>(color), get<1>(color), get<2>(color),
get<3>(color));
get<3>(color));
cr.stroke();
}
}
else
{
if (!outline)
{
double swidth, sheight;
PycairoSurface* src = (PycairoSurface*) osrc.ptr();
Cairo::RefPtr<Cairo::Surface> surface(new Cairo::Surface(src->surface));
get_surface_size(surface, swidth, sheight);
Cairo::RefPtr<Cairo::SurfacePattern> pat(Cairo::SurfacePattern::create(surface));
//pat->set_extend(Cairo::EXTEND_REPEAT);
double r = size / sqrt(2);
double scale = r / max(swidth / aspect, sheight);
Cairo::Matrix m = Cairo::identity_matrix();
m.translate(swidth / 2, sheight / 2);
m.scale(1. / scale, 1. / scale);
pat->set_matrix(m);
cr.set_source(pat);
cr.rectangle(-r * aspect / 2, -r / 2, r * aspect, r);
cr.fill();
}
double swidth, sheight;
PycairoSurface* src = (PycairoSurface*) osrc.ptr();
Cairo::RefPtr<Cairo::Surface> surface(new Cairo::Surface(src->surface));
get_surface_size(surface, swidth, sheight);
Cairo::RefPtr<Cairo::SurfacePattern> pat(Cairo::SurfacePattern::create(surface));
//pat->set_extend(Cairo::EXTEND_REPEAT);
double r = size / sqrt(2);
double scale = r / max(swidth / aspect, sheight);
Cairo::Matrix m = Cairo::identity_matrix();
m.translate(swidth / 2, sheight / 2);
m.scale(1. / scale, 1. / scale);
pat->set_matrix(m);
cr.set_source(pat);
cr.rectangle(-r * aspect / 2, -r / 2, r * aspect, r);
cr.fill();
}
if (!outline)
......
......@@ -111,14 +111,20 @@ void transform(vector<point_t>& cp)
}
template <class PosProp>
void get_control_points(vector<size_t>& path, PosProp pos, double beta,
void get_control_points(vector<size_t>& path, PosProp& pos, double beta,
vector<point_t>& ncp)
{
size_t L = path.size();
vector<point_t> cp(L);
for (size_t i = 0; i < L; ++i)
cp[i] = make_pair(double(pos[path[i]][0]),
double(pos[path[i]][1]));
{
auto& p = pos[path[i]];
if (p.size() < 2)
p.resize(2);
cp[i].first = p[0];
cp[i].second = p[1];
}
ncp.resize(L);
for (size_t i = 0; i < L; ++i)
{
......
......@@ -64,9 +64,6 @@ struct copy_property
tie(vt, vt_end) = IteratorSel::range(tgt);
for (tie(vs, vs_end) = IteratorSel::range(*src); vs != vs_end; ++vs)
{
if (vt == vt_end)
throw ValueException("Error copying properties: "
"graphs not compatible");
dst_map[*vt] = get(src_map, *vs);
++vt;
}
......
......@@ -49,7 +49,7 @@ struct do_edge_endpoint
typename graph_traits<Graph>::vertex_descriptor v = vertex(i, g);
if (v == graph_traits<Graph>::null_vertex())
continue;
for (auto e : out_edges_range(v, g))
for (const auto& e : out_edges_range(v, g))
{
auto s = v;
auto t = target(e, g);
......
......@@ -72,7 +72,7 @@ struct SumOp
convert<vval_t, eval_t> conv;
size_t count = 0;
for (auto e : out_edges_range(v, g))
for (const auto& e : out_edges_range(v, g))
{
if (count == 0)
vprop[v] = conv(eprop[e]);
......@@ -93,7 +93,7 @@ struct ProdOp
convert<vval_t, eval_t> conv;
size_t count = 0;
for (auto e : out_edges_range(v, g))
for (const auto& e : out_edges_range(v, g))
{
if (count == 0)
vprop[v] = conv(eprop[e]);
......@@ -115,13 +115,13 @@ struct MinOp
convert<vval_t, eval_t> conv;
for (auto e : out_edges_range(v, g))
for (const auto& e : out_edges_range(v, g))
{
vprop[v] = conv(eprop[e]);
break;
}
for (auto e : out_edges_range(v, g))
for (const auto& e : out_edges_range(v, g))
vprop[v] = std::min(vprop[v], conv(eprop[e]));
}
};
......@@ -136,13 +136,13 @@ struct MaxOp
convert<vval_t, eval_t> conv;
for (auto e : out_edges_range(v, g))
for (const auto& e : out_edges_range(v, g))
{
vprop[v] = conv(eprop[e]);
break;
}
for (auto e : out_edges_range(v, g))
for (const auto& e : out_edges_range(v, g))
vprop[v] = std::max(vprop[v], conv(eprop[e]));
}
};
......
......@@ -146,6 +146,60 @@ _edefaults = {
"seamless": False
}
_vtypes = {
"shape": "int",
"color": "vector<double>",
"fill_color": "vector<double>",
"size": "double",
"aspect": "double",
"anchor": "double",
"pen_width": "double",
"halo": "bool",
"halo_color": "vector<double>",
"halo_size": "double",
"text": "string",
"text_color": "vector<double>",
"text_position": "double",
"text_rotation": "double",
"text_offset": "vector<double>",
"font_family": "string",
"font_slant": "int",
"font_weight": "int",
"font_size": "float",
"surface": "object",
"pie_fractions": "vector<double>",
"pie_colors": "vector<double>"
}
_etypes = {
"color": "vector<double>",
"pen_width": "double",
"start_marker": "int",
"mid_marker": "int",
"end_marker": "int",
"marker_size": "double",
"mid_marker_pos": "double",
"control_points": "vector<double>",
"gradient": "vector<double>",
"dash_style": "vector<double>",
"text": "string",
"text_color": "vector<double>",
"text_distance": "double",
"text_parallel": "bool",
"font_family": "string",
"font_slant": "int",
"font_weight": "int",
"font_size": "double",
"sloppy": "bool",
"seamless": "bool"
}
for k in list(_vtypes.keys()):
_vtypes[getattr(vertex_attrs, k)] = _vtypes[k]
for k in list(_etypes.keys()):
_etypes[getattr(edge_attrs, k)] = _etypes[k]
def shape_from_prop(shape, enum):
if isinstance(shape, PropertyMap):
......@@ -171,7 +225,7 @@ def shape_from_prop(shape, enum):
prop.fa += rg[0]
return prop
if isinstance(shape, str):
return int(enum.__dict__[shape])
return int(getattr(enum, shape))
else:
return shape
......@@ -226,7 +280,7 @@ def surface_from_prop(surface):
elif surface.value_type() == "python::object":
if isinstance(surface[v], cairo.Surface):
prop[v] = surface[v]
else:
elif surface[v] is not None:
raise ValueError("Invalid value type for surface property: " +
str(type(surface[v])))
else:
......@@ -384,17 +438,12 @@ def _convert(attr, val, cmap, pmap_default=False, g=None, k=None):
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())
if k == "v":
new_val = g.new_vertex_property(_vtypes[attr], val=val)
else:
if k == "v":
new_val = g.new_vertex_property("double", val)
else:
new_val = g.new_edge_property("double", val)
new_val = g.new_edge_property(_etypes[attr], val=val)
return new_val
return val
......@@ -404,10 +453,10 @@ def _attrs(attrs, d, g, cmap):
for k, v in attrs.items():
try:
if d == "v":
attr = vertex_attrs.__dict__[k]
attr = getattr(vertex_attrs, k)
else:
attr = edge_attrs.__dict__[k]
except KeyError:
attr = getattr(edge_attrs, k)
except AttributeError:
warnings.warn("Unknown attribute: " + str(k), UserWarning)
continue
if isinstance(v, PropertyMap):
......@@ -421,12 +470,12 @@ def _convert_props(props, d, g, cmap, pmap_default=False):
for k, v in props.items():
try:
if d == "v":
attr = vertex_attrs.__dict__[k]
attr = getattr(vertex_attrs, k)
else:
attr = edge_attrs.__dict__[k]
attr = getattr(edge_attrs, k)
nprops[k] = _convert(attr, v, cmap, pmap_default=pmap_default,
g=g, k=d)
except KeyError:
except AttributeError:
warnings.warn("Unknown attribute: " + str(k), UserWarning)
continue
return nprops
......@@ -480,7 +529,7 @@ def parse_props(prefix, args):
def cairo_draw(g, pos, cr, vprops=None, eprops=None, vorder=None, eorder=None,
nodesfirst=False, vcmap=default_cm, ecmap=default_cm,
loop_angle=float("nan"), parallel_distance=None, fit_view=False,
loop_angle=numpy.nan, parallel_distance=None, fit_view=False,
res=0, render_offset=0, max_render_time=-1, **kwargs):
r"""
Draw a graph to a :mod:`cairo` context.
......@@ -638,10 +687,7 @@ def auto_colors(g, bg, pos, back):
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)
bg = g.new_vertex_property("vector<double>", val=bg)
if not isinstance(pos, PropertyMap):
if pos == "centered":
pos = 0
......@@ -1694,9 +1740,26 @@ def draw_hierarchy(state, pos=None, layout="radial", beta=0.8, vprops=None,
vprops.setdefault("fill_color", b)
vprops.setdefault("color", b)
vprops.setdefault("shape", _vdefaults["shape"] if not state.overlap else "pie")
s = max(200 / numpy.sqrt(g.num_vertices()), 5)
vprops.setdefault("size", prop_to_size(g.degree_property_map("total"), s/5, s))
if vprops.get("text_position", None) == "centered":
angle, text_pos = centered_rotation(g, pos, text_pos=True)
vprops["text_position"] = text_pos
vprops["text_rotation"] = angle
self_loops = label_self_loops(g, mark_only=True)
if self_loops.fa.max() > 0:
parallel_distance = vprops.get("size", _vdefaults["size"])
if isinstance(parallel_distance, PropertyMap):
parallel_distance = parallel_distance.fa.mean()
cts_p = position_parallel_edges(g, pos, numpy.nan,
parallel_distance)
gu = GraphView(g, efilt=self_loops)
for e in gu.edges():
cts[e] = cts_p[e]
vprops = _convert_props(vprops, "v", g, kwargs.get("vcmap", default_cm),
pmap_default=True)
......@@ -1705,6 +1768,7 @@ def draw_hierarchy(state, pos=None, layout="radial", beta=0.8, vprops=None,
eprops.setdefault("control_points", cts)
eprops.setdefault("pen_width", _edefaults["pen_width"])
eprops.setdefault("color", _edefaults["color"])
eprops.setdefault("end_marker", "arrow" if g.is_directed() else "none")
eprops = _convert_props(eprops, "e", g, kwargs.get("ecmap", default_cm),
pmap_default=True)
......@@ -1721,6 +1785,11 @@ def draw_hierarchy(state, pos=None, layout="radial", beta=0.8, vprops=None,
hvprops.setdefault("shape", "square")
hvprops.setdefault("size", 10)
if hvprops.get("text_position", None) == "centered":
angle, text_pos = centered_rotation(t, tpos, text_pos=True)
hvprops["text_position"] = text_pos
hvprops["text_rotation"] = angle
hvprops = _convert_props(hvprops, "v", t, kwargs.get("vcmap", default_cm),
pmap_default=True)
......@@ -1879,16 +1948,21 @@ def draw_hierarchy(state, pos=None, layout="radial", beta=0.8, vprops=None,
widget.regenerate_surface(reset=True)
widget.queue_draw()
if kwargs.get("output", None) is None:
kwargs["layout_callback"] = update_cts
kwargs["key_press_callback"] = draw_branch
pos = graph_draw(u, pos, vprops=t_vprops, eprops=t_eprops, vorder=vorder,
layout_callback=update_cts, key_press_callback=draw_branch,
**kwargs)
if isinstance(pos, PropertyMap):
pos = g.own_property(pos)
t_orig.copy_property(pos, tpos, g=u)
else:
pos = (g.own_property(pos[0]),
g.own_property(pos[1]))
return pos, t, tpos
t_orig.copy_property(pos[0], tpos, g=u)
return pos, t_orig, tpos
def get_bip_hierachy_pos(state):
......
......@@ -23,7 +23,8 @@ from __future__ import division, absolute_import, print_function
import numpy
from .. import GraphView, PropertyMap, ungroup_vector_property,\
group_vector_property, _prop
group_vector_property, infect_vertex_property, edge_endpoint_property, \
_prop
from .cairo_draw import *
from .cairo_draw import _vdefaults, _edefaults
from .. draw import sfdp_layout, random_layout, _avg_edge_distance, \
......@@ -134,7 +135,7 @@ class GraphWidget(Gtk.DrawingArea):
layout_K=1., multilevel=False, display_props=None,
display_props_size=11, fit_area=0.95, bg_color=None,
max_render_time=300, layout_callback=None,
key_press_callback=None, **kwargs):
key_press_callback=None, highlight_color=None, **kwargs):
r"""Interactive GTK+ widget displaying a given graph.
Parameters
......@@ -266,7 +267,9 @@ class GraphWidget(Gtk.DrawingArea):
self.pointer = [0, 0]
self.picked = False
self.selected = g.new_vertex_property("bool", False)
self.highlight = g.new_vertex_property("bool", False)
self.sel_edge_filt = g.new_edge_property("bool", False)
self.highlight_color = highlight_color
self.srect = None
self.drag_begin = None
self.moved_picked = False
......@@ -545,12 +548,60 @@ class GraphWidget(Gtk.DrawingArea):
cr.restore()
if self.picked is not None or self.picked is not False:
vprops = {}
vprops.update(self.vprops)
# draw immediate neighbourhood
if self.selected.fa.sum() == 1:
vprops = dict(**self.vprops)
vprops["halo"] = self.highlight
vprops["halo_color"] = (0.9372549019607843, 0.1607843137254902, 0.1607843137254902, .9)
vprops["halo_size"] = 1.3
if self.highlight_color is not None:
vprops["halo_color"] = self.highlight_color
eprops = {}
eprops["color"] = (0.9372549019607843, 0.1607843137254902, 0.1607843137254902, .9)
if "control_points" in self.eprops:
eprops["control_points"] = self.eprops["control_points"]
if self.highlight_color is not None:
eprops["color"] = self.highlight_color
self.highlight.fa = self.selected.fa
infect_vertex_property(GraphView(self.g, directed=False),
self.highlight, [True])
self.highlight.fa = numpy.logical_xor(self.selected.fa,
self.highlight.fa)
hsrc = edge_endpoint_property(self.g, self.selected, "source")
htgt = edge_endpoint_property(self.g, self.selected, "target")
self.sel_edge_filt.fa = numpy.logical_or(hsrc.fa, htgt.fa)
u = GraphView(self.g,
vfilt=numpy.logical_or(self.highlight.fa,
self.selected.fa),
efilt=self.sel_edge_filt)
self.sel_edge_filt.fa = False
eprops["pen_width"] = self.eprops.get("pen_width",
_edefaults["pen_width"])
if isinstance(eprops["pen_width"], PropertyMap):
pw = eprops["pen_width"]
pw = u.own_property(pw.copy())
pw.fa *= 1.1
else:
eprops["pen_width"] *= 1.1
cr.save()
cr.set_matrix(self.tmatrix * self.smatrix)
cairo_draw(u, self.pos, cr, vprops, eprops, self.vorder,
self.eorder, self.nodesfirst)
cr.restore()
# draw selected edges
vprops = dict(**self.vprops)
vprops["halo"] = True
vprops["color"] = [1., 1., 1., 0.]
vprops["fill_color"] = [1., 1., 1., 0.]
vprops["text_color"] = [1., 1., 1., 0.]
eprops = {}
......@@ -858,14 +909,20 @@ class GraphWidget(Gtk.DrawingArea):
if event.direction == Gdk.ScrollDirection.UP:
if state & Gdk.ModifierType.CONTROL_MASK:
angle = 0.1
if state & Gdk.ModifierType.SHIFT_MASK:
angle = 0.01
else:
angle = 0.1
else:
zoom = 1. / 0.9
if state & Gdk.ModifierType.SHIFT_MASK:
scale_ink(1. / 0.9, self.vprops, self.eprops)
elif event.direction == Gdk.ScrollDirection.DOWN:
if state & Gdk.ModifierType.CONTROL_MASK:
angle = -0.1
if state & Gdk.ModifierType.SHIFT_MASK:
angle = -0.01
else:
angle = -0.1
else:
zoom = 0.9
if state & Gdk.ModifierType.SHIFT_MASK:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment