Commit 76eec1e5 authored by Tiago Peixoto's avatar Tiago Peixoto
Browse files

graph_draw(): Use boost::coroutines for interactive drawing

parent 01f579ac
Pipeline #229 failed with stage
in 171 minutes and 24 seconds
......@@ -34,6 +34,9 @@
#include "hash_map_wrap.hh"
#include "demangle.hh"
#include "coroutine.hh"
#include "graph_python_interface.hh"
#include <cairommconfig.h>
#include <cairomm/context.h>
#include <cairomm/surface.h>
......@@ -1540,22 +1543,14 @@ private:
AttrDict<Descriptor> _attrs;
};
template <class Graph, class VertexIterator, class PosMap, class Time>
template <class Graph, class VertexIterator, class PosMap, class Time,
class Yield>
void draw_vertices(Graph&, pair<VertexIterator, VertexIterator> v_range,
PosMap pos_map, attrs_t& attrs, attrs_t& defaults,
size_t offset, Time max_time, size_t& count,
Cairo::Context& cr)
Time max_time, int64_t dt, size_t& count, Cairo::Context& cr,
Yield&& yield)
{
typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;
if (offset > count)
{
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;
......@@ -1569,33 +1564,23 @@ void draw_vertices(Graph&, pair<VertexIterator, VertexIterator> v_range,
count++;
if (chrono::high_resolution_clock::now() > max_time)
break;
{
yield(boost::python::object(count));
max_time = chrono::high_resolution_clock::now() +
chrono::milliseconds(dt);
}
}
}
template <class Graph, class EdgeIterator, class PosMap, class Time>
template <class Graph, class EdgeIterator, class PosMap, class Time,
class Yield>
void draw_edges(Graph& g, pair<EdgeIterator, EdgeIterator> e_range,
PosMap pos_map, attrs_t& eattrs, attrs_t& edefaults,
attrs_t& vattrs, attrs_t& vdefaults, double res, size_t offset,
Time max_time, size_t& count, Cairo::Context& cr)
attrs_t& vattrs, attrs_t& vdefaults, double res, Time max_time,
int64_t dt, size_t& count, Cairo::Context& cr, Yield && yield)
{
typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;
typedef typename graph_traits<Graph>::edge_descriptor edge_t;
if (offset > count)
{
size_t E = num_edges(g);
if (offset - count >= E)
{
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;
......@@ -1627,10 +1612,13 @@ void draw_edges(Graph& g, pair<EdgeIterator, EdgeIterator> e_range,
AttrDict<edge_t>(*e, eattrs,
edefaults));
es.draw(cr, res);
count++;
if (chrono::high_resolution_clock::now() > max_time)
break;
{
yield(boost::python::object(count));
max_time = chrono::high_resolution_clock::now() +
chrono::milliseconds(dt);
}
}
}
......@@ -1679,37 +1667,6 @@ struct ordered_range
vector<val_t> _ordered;
};
struct do_cairo_draw_edges
{
template <class Graph, class PosMap, class EdgeOrder, class Time>
void operator()(Graph& g, PosMap pos, EdgeOrder edge_order, attrs_t& vattrs,
attrs_t& eattrs, attrs_t& vdefaults, attrs_t& edefaults,
double res, size_t offset, Time max_time, size_t& count,
Cairo::Context& cr) const
{
ordered_range<typename graph_traits<Graph>::edge_iterator>
edge_range(edges(g));
draw_edges(g, edge_range.get_range(edge_order), pos, eattrs, edefaults,
vattrs, vdefaults, res, offset, max_time, count, cr);
}
};
struct do_cairo_draw_vertices
{
template <class Graph, class PosMap, class VertexOrder, class Time>
void operator()(Graph& g, PosMap pos, VertexOrder vertex_order,
attrs_t& vattrs, attrs_t&, attrs_t& vdefaults, attrs_t&,
size_t offset, Time max_time, size_t& count,
Cairo::Context& cr) const
{
ordered_range<typename graph_traits<Graph>::vertex_iterator>
vertex_range(vertices(g));
draw_vertices(g, vertex_range.get_range(vertex_order), pos, vattrs,
vdefaults, offset, max_time, count, cr);
}
};
template <class Descriptor, class PropMaps>
struct get_pmap
{
......@@ -1792,90 +1749,117 @@ struct populate_edge_attrs
}
};
size_t cairo_draw(GraphInterface& gi,
boost::any pos,
boost::any vorder,
boost::any eorder,
bool nodesfirst,
boost::python::dict ovattrs,
boost::python::dict oeattrs,
boost::python::dict ovdefaults,
boost::python::dict oedefaults,
double res,
size_t offset,
int64_t max_time,
boost::python::object ocr)
struct do_cairo_draw_edges
{
attrs_t vattrs, eattrs, vdefaults, edefaults;
typedef graph_traits<GraphInterface::multigraph_t>::vertex_descriptor vertex_t;
populate_attrs<vertex_t, vertex_properties>(ovattrs, vattrs);
populate_defaults(ovdefaults, vdefaults);
run_action<graph_tool::detail::always_directed>()
(gi, std::bind(populate_edge_attrs(), std::placeholders::_1, oeattrs,
std::ref(eattrs), oedefaults, std::ref(edefaults)))();
typedef boost::mpl::push_back<vertex_scalar_properties, no_order>::type
vorder_t;
typedef boost::mpl::push_back<edge_scalar_properties, no_order>::type
eorder_t;
if (vorder.empty())
vorder = no_order();
if (eorder.empty())
eorder = no_order();
size_t count = 0;
auto mtime = chrono::high_resolution_clock::now();
if (max_time < 0)
mtime = chrono::high_resolution_clock::time_point::max();
else
mtime += chrono::milliseconds(max_time);
Cairo::Context cr(PycairoContext_GET(ocr.ptr()));
if (nodesfirst)
run_action<graph_tool::detail::always_directed>()
(gi, std::bind(do_cairo_draw_vertices(), std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3,
std::ref(vattrs), std::ref(eattrs), std::ref(vdefaults),
std::ref(edefaults), offset, mtime, std::ref(count),
std::ref(cr)),
vertex_scalar_vector_properties(),
vorder_t())(pos, vorder);
if (chrono::high_resolution_clock::now() > mtime)
return count;
run_action<graph_tool::detail::always_directed>()
(gi, std::bind(do_cairo_draw_edges(), std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::ref(vattrs), std::ref(eattrs),
std::ref(vdefaults), std::ref(edefaults), res, offset,
mtime, std::ref(count), std::ref(cr)),
vertex_scalar_vector_properties(),
eorder_t())(pos, eorder);
if (!nodesfirst)
template <class Graph, class PosMap, class EdgeOrder, class Time,
class Yield>
void operator()(Graph& g, PosMap pos, EdgeOrder edge_order, attrs_t& vattrs,
attrs_t& eattrs, attrs_t& vdefaults, attrs_t& edefaults,
double res, Time max_time, int64_t dt, size_t& count,
Cairo::Context& cr, Yield&& yield) const
{
if (chrono::high_resolution_clock::now() > mtime)
return count;
run_action<graph_tool::detail::always_directed>()
(gi, std::bind(do_cairo_draw_vertices(), std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3,
std::ref(vattrs), std::ref(eattrs), std::ref(vdefaults),
std::ref(edefaults), offset, mtime, std::ref(count),
std::ref(cr)),
vertex_scalar_vector_properties(),
vorder_t())(pos, vorder);
ordered_range<typename graph_traits<Graph>::edge_iterator>
edge_range(edges(g));
draw_edges(g, edge_range.get_range(edge_order), pos, eattrs,
edefaults, vattrs, vdefaults, res, max_time, dt, count, cr,
yield);
}
};
if (chrono::high_resolution_clock::now() < mtime)
count = 0;
struct do_cairo_draw_vertices
{
template <class Graph, class PosMap, class VertexOrder, class Time,
class Yield>
void operator()(Graph& g, PosMap pos, VertexOrder vertex_order,
attrs_t& vattrs, attrs_t&, attrs_t& vdefaults, attrs_t&,
Time max_time, int64_t dt, size_t& count,
Cairo::Context& cr, Yield&& yield) const
{
ordered_range<typename graph_traits<Graph>::vertex_iterator>
vertex_range(vertices(g));
draw_vertices(g, vertex_range.get_range(vertex_order), pos, vattrs,
vdefaults, max_time, dt, count, cr, yield);
}
};
return count;
boost::python::object cairo_draw(GraphInterface& gi,
boost::any pos,
boost::any vorder,
boost::any eorder,
bool nodesfirst,
boost::python::dict ovattrs,
boost::python::dict oeattrs,
boost::python::dict ovdefaults,
boost::python::dict oedefaults,
double res,
int64_t max_time,
boost::python::object ocr)
{
auto dispatch = [=,&gi] (auto& yield) mutable
{
attrs_t vattrs, eattrs, vdefaults, edefaults;
typedef graph_traits<GraphInterface::multigraph_t>::vertex_descriptor vertex_t;
populate_attrs<vertex_t, vertex_properties>(ovattrs, vattrs);
populate_defaults(ovdefaults, vdefaults);
run_action<graph_tool::detail::always_directed>()
(gi, std::bind(populate_edge_attrs(), std::placeholders::_1,
oeattrs, std::ref(eattrs), oedefaults,
std::ref(edefaults)))();
typedef boost::mpl::push_back<vertex_scalar_properties, no_order>::type
vorder_t;
typedef boost::mpl::push_back<edge_scalar_properties, no_order>::type
eorder_t;
if (vorder.empty())
vorder = no_order();
if (eorder.empty())
eorder = no_order();
size_t count = 0;
auto mtime = chrono::high_resolution_clock::now();
if (max_time < 0)
mtime = chrono::high_resolution_clock::time_point::max();
else
mtime += chrono::milliseconds(max_time);
int64_t dt = max_time;
Cairo::Context cr(PycairoContext_GET(ocr.ptr()));
if (nodesfirst)
run_action<graph_tool::detail::always_directed>()
(gi, std::bind(do_cairo_draw_vertices(), std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3,
std::ref(vattrs), std::ref(eattrs), std::ref(vdefaults),
std::ref(edefaults), mtime, dt, std::ref(count),
std::ref(cr), std::ref(yield)),
vertex_scalar_vector_properties(),
vorder_t())(pos, vorder);
run_action<graph_tool::detail::always_directed>()
(gi, std::bind(do_cairo_draw_edges(), std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3,
std::ref(vattrs), std::ref(eattrs),
std::ref(vdefaults), std::ref(edefaults), res,
mtime, dt, std::ref(count), std::ref(cr), std::ref(yield)),
vertex_scalar_vector_properties(),
eorder_t())(pos, eorder);
if (!nodesfirst)
{
run_action<graph_tool::detail::always_directed>()
(gi, std::bind(do_cairo_draw_vertices(), std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3,
std::ref(vattrs), std::ref(eattrs), std::ref(vdefaults),
std::ref(edefaults), mtime, dt, std::ref(count),
std::ref(cr), std::ref(yield)),
vertex_scalar_vector_properties(),
vorder_t())(pos, vorder);
}
};
return boost::python::object(CoroGenerator(dispatch));
}
struct do_apply_transforms
......
......@@ -546,7 +546,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=numpy.nan, parallel_distance=None, fit_view=False,
res=0, render_offset=0, max_render_time=-1, **kwargs):
res=0, max_render_time=-1, **kwargs):
r"""
Draw a graph to a :mod:`cairo` context.
......@@ -593,9 +593,6 @@ def cairo_draw(g, pos, cr, vprops=None, eprops=None, vorder=None, eorder=None,
Background color. The default is transparent.
res : float (optional, default: ``0.``):
If shape sizes fall below this value, simplified drawing is used.
render_offset : int (optional, default: ``0``):
If supplied, the rendering will skip the specified initial amount of
vertices / edges.
max_render_time : int (optional, default: ``-1``):
Maximum allowed time (in milliseconds) for rendering. If exceeded, the
rendering will return unfinished. If negative values are given, the
......@@ -685,12 +682,17 @@ def cairo_draw(g, pos, cr, vprops=None, eprops=None, vorder=None, eorder=None,
eprops["control_points"] = position_parallel_edges(g, pos, loop_angle,
parallel_distance)
g = GraphView(g, directed=True)
count = libgraph_tool_draw.cairo_draw(g._Graph__graph, _prop("v", g, pos),
_prop("v", g, vorder), _prop("e", g, eorder),
nodesfirst, vattrs, eattrs, vdefs, edefs, res,
render_offset, max_render_time, cr)
generator = libgraph_tool_draw.cairo_draw(g._Graph__graph, _prop("v", g, pos),
_prop("v", g, vorder), _prop("e", g, eorder),
nodesfirst, vattrs, eattrs, vdefs, edefs, res,
max_render_time, cr)
if max_render_time >= 0:
for count in generator:
yield count
else:
for count in generator:
pass
cr.restore()
return count
def color_contrast(color):
c = np.asarray(color)
......@@ -701,7 +703,6 @@ def color_contrast(color):
c[:3] = 0
return c
def auto_colors(g, bg, pos, back):
if not isinstance(bg, PropertyMap):
if isinstance(bg, (str, unicode)):
......
......@@ -285,7 +285,7 @@ class GraphWidget(Gtk.DrawingArea):
self.base_geometry = None
self.background = None
self.bg_color = bg_color if bg_color is not None else [1, 1, 1, 1]
self.regenerate_offset = 0
self.regenerate_generator = None
self.regenerate_max_time = max_render_time
self.max_render_time = max_render_time
self.lazy_regenerate = False
......@@ -452,7 +452,8 @@ class GraphWidget(Gtk.DrawingArea):
r"""Redraw the graph surface."""
if reset:
self.regenerate_offset = 0
self.regenerate_generator = None
self.regen_context = None
geometry = [self.get_allocated_width() * 3,
self.get_allocated_height() * 3]
......@@ -467,7 +468,7 @@ class GraphWidget(Gtk.DrawingArea):
self.base = w.create_similar_surface(cairo.CONTENT_COLOR_ALPHA,
*geometry)
self.base_geometry = geometry
self.regenerate_offset = 0
self.regenerate_generator = None
m = cairo.Matrix()
m.translate(self.get_allocated_width(),
......@@ -478,18 +479,23 @@ class GraphWidget(Gtk.DrawingArea):
self.smatrix.translate(-self.get_allocated_width(),
-self.get_allocated_height())
cr = cairo.Context(self.base)
if self.regenerate_offset == 0:
if self.regenerate_generator is None:
cr = cairo.Context(self.base)
cr.set_source_rgba(*self.bg_color)
cr.paint()
cr.set_matrix(self.tmatrix)
mtime = -1 if complete else self.regenerate_max_time
res = 5 * self.get_scale_factor()
count = cairo_draw(self.g, self.pos, cr, self.vprops, self.eprops,
self.vorder, self.eorder, self.nodesfirst, res=res,
render_offset=self.regenerate_offset,
max_render_time=mtime, **self.kwargs)
self.regenerate_offset = count
cr.set_matrix(self.tmatrix)
mtime = -1 if complete else self.regenerate_max_time
res = 5 * self.get_scale_factor()
gen = cairo_draw(self.g, self.pos, cr, self.vprops, self.eprops,
self.vorder, self.eorder, self.nodesfirst, res=res,
max_render_time=mtime, **self.kwargs)
self.regenerate_generator = gen
self.regen_context = cr
try:
next(self.regenerate_generator)
except StopIteration:
self.regenerate_generator = None
self.regen_context = None
self.lazy_regenerate = False
def draw(self, da, cr):
......@@ -519,7 +525,7 @@ class GraphWidget(Gtk.DrawingArea):
ul[1] > 0 or lr[1] < geometry[1]) or
self.lazy_regenerate):
self.regenerate_surface(reset=True)
elif self.regenerate_offset > 0:
elif self.regenerate_generator is not None:
self.regenerate_surface()
if self.background is None:
......@@ -626,7 +632,7 @@ class GraphWidget(Gtk.DrawingArea):
cr.set_source_rgba(0, 0, 1, 0.3)
cr.fill()
if self.regenerate_offset > 0:
if self.regenerate_generator is not None:
icon = self.render_icon(Gtk.STOCK_EXECUTE, Gtk.IconSize.BUTTON)
Gdk.cairo_set_source_pixbuf(cr, icon, 10, 10)
cr.paint()
......@@ -652,7 +658,7 @@ class GraphWidget(Gtk.DrawingArea):
cr.set_source_rgba(0, 0, 0, 1.0)
cr.show_text(txt)
if self.regenerate_offset > 0:
if self.regenerate_generator is not None:
self.queue_draw()
return False
......
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