Commit 92eaff23 authored by Tiago Peixoto's avatar Tiago Peixoto
Browse files

Significant improvement of edge splines in graph_draw()

parent 66d10524
<
......@@ -527,7 +527,84 @@ void get_surface_size(Cairo::RefPtr<Cairo::Surface> sfc,
height = y2 - y1;
}
double dist(const pos_t& p1, const pos_t& p2)
{
return sqrt(pow(p1.first - p2.first, 2) + pow(p1.second - p2.second, 2));
};
double get_spline_len(const vector<double>& cts)
{
double len = 0;
for (size_t i = 0; i + 7 < cts.size(); i += 6)
{
double dx = cts[i + 6] - cts[i];
double dy = cts[i + 6 + 1] - cts[i + 1];
len += sqrt(dx * dx + dy * dy);
}
return len;
}
pos_t get_spline_point(const vector<double>& cts, double d)
{
pos_t p;
double pos = 0;
for (size_t i = 0; i + 7 < cts.size(); i += 6)
{
double dx = cts[i + 6] - cts[i];
double dy = cts[i + 6 + 1] - cts[i + 1];
double l = sqrt(dx * dx + dy * dy);
if (l < 1e-8)
continue;
if (pos + l >= d || i + 13 >= cts.size())
{
double t = 1 - (pos + l - d) / l;
p.first = pow(1 - t, 3) * cts[i] +
3 * t * pow(1 - t, 2) * cts[i + 2] +
3 * t * t * (1 - t) * cts[i + 4] +
t * t * t * cts[i + 6];
p.second = pow(1 - t, 3) * cts[i + 1] +
3 * t * pow(1 - t, 2) * cts[i + 3] +
3 * t * t * (1 - t) * cts[i + 5] +
t * t * t * cts[i + 7];
break;
}
pos += l;
}
return p;
}
pos_t get_spline_diff(const vector<double>& cts, double d)
{
pos_t diff;
double pos = 0;
for (size_t i = 0; i + 7 < cts.size(); i += 6)
{
double dx = cts[i + 6] - cts[i];
double dy = cts[i + 6 + 1] - cts[i + 1];
double l = sqrt(dx * dx + dy * dy);
if (l < 1e-8)
continue;
if (pos + l >= d || i + 13 >= cts.size())
{
double t = 1 - (pos + l - d) / l;
diff.first = -3 * pow(1 - t, 2) * cts[i] +
(3 * pow(1 - t, 2) - 6 * t * (1-t)) * cts[i + 2] +
(-3 * t * t + 6 * t * (1 - t)) * cts[i + 4] +
3 * t * t * cts[i + 6];
diff.second= -3 * pow(1 - t, 2) * cts[i + 1] +
(3 * pow(1 - t, 2) - 6 * t * (1-t)) * cts[i + 3] +
(-3 * t * t + 6 * t * (1 - t)) * cts[i + 5] +
3 * t * t * cts[i + 7];
break;
}
pos += l;
}
return diff;
}
template <class Descriptor>
class VertexShape
......@@ -629,6 +706,68 @@ public:
return anchor;
}
std::pair<pos_t, double> get_anchor_spline(const vector<double>& cts,
Cairo::Context& cr,
bool loop = false,
bool src = false)
{
double len = get_spline_len(cts);
double x, one;
pos_t p0 = get_spline_point(cts, 0);
pos_t p1 = get_spline_point(cts, len);
if (dist(p0, _pos) < dist(p1, _pos) || (loop && src))
{
x = 1;
one = 1;
}
else
{
x = 0;
one = -1;
}
int anchor_type =_attrs.template get<int32_t>(VERTEX_ANCHOR);
if (anchor_type == 0)
return make_pair(_pos, x);
if (loop)
x = 0.5;
double dl = 0.5;
pos_t pos, anchor;
pos = get_spline_point(cts, len * x);
anchor = get_anchor(pos, cr);
if (dist(anchor, _pos) == 0 || dist(pos, _pos) < dist(anchor, _pos))
return make_pair(_pos, x * len);
double r = min((get_size(cr) / 10) / len, .5);
size_t i = 0;
while (abs(dist(pos, anchor)) > 1e-6)
{
double nx = min(max(x - dl * one, r), 1. - r);;
pos = get_spline_point(cts, len * nx);
anchor = get_anchor(pos, cr);
double j = 0;
while (dist(pos, _pos) < dist(anchor, _pos)) // x is inside
{
dl /= 2;
nx = min(max(x - dl * one, r), 1. - r);
pos = get_spline_point(cts, len * nx);
anchor = get_anchor(pos, cr);
j++;
if (j > 10000)
break;
}
x = nx;
dl = dist(pos, anchor) / len;
assert(dist(pos, _pos) > dist(anchor, _pos));
i++;
if (i > 1000)
break;
}
return make_pair(pos, x * len);
}
pos_t get_pos()
{
return _pos;
......@@ -892,12 +1031,12 @@ public:
marker_size = get_user_dist(cr, marker_size);
bool sloppy = _attrs.template get<uint8_t>(EDGE_SLOPPY);
pos_begin = _s.get_anchor(_t.get_pos(), cr);
pos_end = _t.get_anchor(_s.get_pos(), cr);
pos_begin = _s.get_pos();
pos_end = _t.get_pos();
cr.save();
if (controls.size() >= 4)
if (controls.size() >= 8)
{
double angle = 0;
double len = 0;
......@@ -912,67 +1051,26 @@ public:
cr.translate(pos_begin.first, pos_begin.second);
cr.rotate(angle);
cr.scale(len, 1.);
for (size_t i = 0; i < controls.size() / 2; ++i)
cr.user_to_device(controls[2 * i], controls[2 * i + 1]);
cr.restore();
for (size_t i = 0; i < controls.size() / 2; ++i)
cr.device_to_user(controls[2 * i], controls[2 * i + 1]);
}
else
{
pos_begin = _s.get_pos();
len = M_PI * _s.get_size(cr);
if (start_marker == MARKER_SHAPE_NONE &&
end_marker == MARKER_SHAPE_NONE)
len = M_PI * _s.get_size(cr);
else
len = max(M_PI * _s.get_size(cr), 6 * marker_size);
cr.save();
cr.translate(pos_begin.first, pos_begin.second);
cr.scale(len / sqrt(2), len / sqrt(2));
for (size_t i = 0; i < controls.size() / 2; ++i)
cr.user_to_device(controls[2 * i], controls[2 * i + 1]);
cr.restore();
for (size_t i = 0; i < controls.size() / 2; ++i)
cr.device_to_user(controls[2 * i], controls[2 * i + 1]);
}
size_t N = controls.size();
pos_begin = _s.get_anchor(make_pair(controls[0],
controls[1]), cr);
pos_end = _t.get_anchor(make_pair(controls[N - 2],
controls[N - 1]), cr);
if (_s.get_pos() == _t.get_pos())
{
double ox = pos_begin.first - _s.get_pos().first +
pos_end.first - _t.get_pos().first;
double oy = pos_begin.second - _s.get_pos().second +
pos_end.second - _t.get_pos().second;
for (size_t i = 0; i < controls.size() / 2; ++i)
{
controls[2 * i] += ox / 2;
controls[2 * i + 1] += oy / 2;
}
if ((start_marker != MARKER_SHAPE_NONE && start_marker != MARKER_SHAPE_BAR) ||
(end_marker != MARKER_SHAPE_NONE && end_marker != MARKER_SHAPE_BAR))
{
for (size_t i = 0; i < controls.size() / 2; ++i)
{
pos_t cpos;
cpos.first = controls[2 * i];
cpos.second = controls[2 * i + 1];
move_radially(cpos, _s.get_pos(), marker_size / 2);
controls[2 * i] = cpos.first;
controls[2 * i + 1] = cpos.second;
}
}
for (size_t i = 0; i < controls.size() / 2; ++i)
cr.user_to_device(controls[2 * i], controls[2 * i + 1]);
cr.restore();
pos_begin = _s.get_anchor(make_pair(controls[0],
controls[1]), cr);
pos_end = _t.get_anchor(make_pair(controls[N - 2],
controls[N - 1]), cr);
}
for (size_t i = 0; i < controls.size() / 2; ++i)
cr.device_to_user(controls[2 * i], controls[2 * i + 1]);
}
......@@ -982,15 +1080,26 @@ public:
pw = get_user_dist(cr, pw);
cr.set_line_width(pw);
pos_t pos_begin_c = pos_begin;
pos_t pos_end_c = pos_end;
if ((start_marker != MARKER_SHAPE_NONE && start_marker != MARKER_SHAPE_BAR))
move_radially(pos_begin_c, _s.get_pos(), marker_size / 2);
if ((end_marker != MARKER_SHAPE_NONE && end_marker != MARKER_SHAPE_BAR))
move_radially(pos_end_c, _t.get_pos(), marker_size / 2);
pos_t pos_begin_marker = pos_begin;
pos_t pos_end_marker = pos_end;
double pos_begin_d = 0, pos_end_d = 0;
if (controls.size() >= 8)
{
if (start_marker != MARKER_SHAPE_NONE)
tie(pos_begin_marker, pos_begin_d) =
_s.get_anchor_spline(controls, cr, pos_begin == pos_end, true);
if (end_marker != MARKER_SHAPE_NONE)
tie(pos_end_marker, pos_end_d) =
_t.get_anchor_spline(controls, cr, pos_begin == pos_end, false);
}
else
{
if (start_marker != MARKER_SHAPE_NONE)
pos_begin_marker = _s.get_anchor(pos_end, cr);
if (end_marker != MARKER_SHAPE_NONE)
pos_end_marker = _t.get_anchor(pos_begin, cr);
}
double a = get<3>(color);
a *= get<3>(_s._attrs.template get<color_t>(VERTEX_COLOR));
......@@ -998,47 +1107,88 @@ public:
a *= get<3>(_t._attrs.template get<color_t>(VERTEX_COLOR));
a *= get<3>(_t._attrs.template get<color_t>(VERTEX_FILL_COLOR));
if (!sloppy && a < 1 && !has_gradient)
if (!sloppy && a < 1)
{
// set the clip region to the correct size for better push/pop_group
// performance
draw_edge_markers(pos_begin, pos_end, controls, marker_size, cr);
draw_edge_line(pos_begin_c, pos_end_c, controls, cr);
draw_edge_markers(pos_begin_marker, pos_begin_d, pos_end_marker,
pos_end_d, controls, marker_size, cr);
draw_edge_line(pos_begin, pos_end, controls, cr);
double sx1, sy1, sx2, sy2;
cr.get_stroke_extents(sx1, sy1, sx2, sy2);
cr.begin_new_path();
cr.rectangle(sx1, sy1, sx2 - sx1, sy2 - sy1);
_s.draw(cr, true);
_t.draw(cr, true);
if (pos_begin != pos_end)
_t.draw(cr, true);
cr.set_fill_rule(Cairo::FILL_RULE_EVEN_ODD);
cr.clip();
// seamlessly blend in separate surface
cr.push_group();
draw_edge_markers(pos_begin, pos_end, controls, marker_size, cr);
cr.set_source_rgba(get<0>(color), get<1>(color), get<2>(color), 1);
cr.set_operator(Cairo::OPERATOR_SOURCE);
draw_edge_markers(pos_begin_marker, pos_begin_d, pos_end_marker,
pos_end_d, controls, marker_size, cr);
if (has_gradient)
{
auto gd = Cairo::LinearGradient::create(pos_begin.first,
pos_begin.second,
pos_end.first,
pos_end.second);
for (size_t i = 0; i < gradient.size() / 5; ++i)
{
size_t pos = i * 5;
gd->add_color_stop_rgba(gradient[pos],
gradient[pos + 1],
gradient[pos + 2],
gradient[pos + 3],
gradient[pos + 4]);
}
cr.set_source(gd);
}
else
{
cr.set_source_rgba(get<0>(color), get<1>(color), get<2>(color), 1);
}
cr.fill();
draw_edge_line(pos_begin_c, pos_end_c, controls, cr);
cr.rectangle(sx1, sy1, sx2 - sx1, sy2 - sy1);
if (start_marker != MARKER_SHAPE_NONE && start_marker != MARKER_SHAPE_BAR)
{
cr.arc(pos_begin_marker.first, pos_begin_marker.second,
marker_size / 2, 0, 2 * M_PI);
}
if (end_marker != MARKER_SHAPE_NONE && end_marker != MARKER_SHAPE_BAR)
{
cr.arc(pos_end_marker.first, pos_end_marker.second,
marker_size / 2, 0, 2 * M_PI);
}
cr.clip();
draw_edge_line(pos_begin, pos_end, controls, cr);
cr.set_line_width(pw);
cr.stroke();
vector<double> empty;
cr.set_dash(empty, 0);
cr.pop_group_to_source();
cr.set_operator(Cairo::OPERATOR_OVER);
cr.reset_clip();
cr.paint_with_alpha(get<3>(color));
if (!has_gradient)
cr.paint_with_alpha(get<3>(color));
else
cr.paint();
}
else
{
Cairo::RefPtr<Cairo::LinearGradient> gd =
Cairo::LinearGradient::create(pos_begin.first, pos_begin.second,
pos_end.first, pos_end.second);
if (!has_gradient)
{
cr.set_source_rgba(get<0>(color), get<1>(color), get<2>(color), get<3>(color));
}
else
{
auto gd = Cairo::LinearGradient::create(pos_begin.first,
pos_begin.second,
pos_end.first,
pos_end.second);
for (size_t i = 0; i < gradient.size() / 5; ++i)
{
size_t pos = i * 5;
......@@ -1050,9 +1200,10 @@ public:
}
cr.set_source(gd);
}
draw_edge_markers(pos_begin, pos_end, controls, marker_size, cr);
draw_edge_markers(pos_begin_marker, pos_begin_d, pos_end_marker,
pos_end_d, controls, marker_size, cr);
cr.fill();
draw_edge_line(pos_begin_c, pos_end_c, controls, cr);
draw_edge_line(pos_begin, pos_end, controls, cr);
cr.stroke();
}
......@@ -1073,13 +1224,33 @@ public:
cr.get_text_extents(text, extents);
pos_t origin;
origin.first = (pos_begin.first + pos_end.first) / 2;
origin.second = (pos_begin.second + pos_end.second) / 2;
if (controls.size() < 8)
{
origin.first = (pos_begin.first + pos_end.first) / 2;
origin.second = (pos_begin.second + pos_end.second) / 2;
}
else
{
double len = get_spline_len(controls);
origin = get_spline_point(controls, len / 2);
}
cr.translate(origin.first, origin.second);
if (text_parallel)
{
double angle = atan2(pos_end.second - pos_begin.second,
pos_end.first - pos_begin.first);
double angle;
if (controls.size() < 8)
{
angle = atan2(pos_end.second - pos_begin.second,
pos_end.first - pos_begin.first);
}
else
{
double len = get_spline_len(controls);
pos_t diff = get_spline_diff(controls, len / 2);
angle = atan2(diff.second, diff.first);
}
if (angle > M_PI / 2)
angle -= M_PI;
if (angle < -M_PI / 2)
......@@ -1104,21 +1275,19 @@ public:
cr.restore();
}
void draw_edge_markers(pos_t& pos_begin, pos_t& pos_end,
vector<double>& controls,
double marker_size,
Cairo::Context& cr)
void draw_edge_markers(pos_t& pos_begin, double pos_begin_d, pos_t& pos_end,
double pos_end_d, vector<double>& controls,
double marker_size, Cairo::Context& cr)
{
double len = sqrt(pow(pos_end.first - pos_begin.first, 2) +
pow(pos_end.second - pos_begin.second, 2));
double len = dist(pos_end, pos_begin);
double angle_b, angle_e;
if (controls.size() >= 4)
if (controls.size() >= 8)
{
size_t N = controls.size();
angle_b = atan2(controls[1] - pos_begin.second,
controls[0] - pos_begin.first);
angle_e = atan2(pos_end.second - controls[N - 1],
pos_end.first - controls[N - 2]);
pos_t diff = get_spline_diff(controls, pos_begin_d + marker_size / 4);
angle_b = atan2(diff.second, diff.first);
diff = get_spline_diff(controls, pos_end_d - marker_size / 4);
angle_e = atan2(diff.second, diff.first);
}
else
{
......@@ -1126,89 +1295,59 @@ public:
pos_end.first - pos_begin.first);
}
cr.save();
cr.translate(pos_end.first, pos_end.second);
cr.rotate(angle_e);
draw_marker(EDGE_END_MARKER, marker_size, cr);
cr.restore();
cr.save();
cr.translate(pos_begin.first, pos_begin.second);
cr.rotate(angle_b);
cr.translate(marker_size, 0);
draw_marker(EDGE_START_MARKER, marker_size, cr);
cr.restore();
double mid_point = _attrs.template get<double>(EDGE_MID_MARKER_POSITION);
cr.save();
edge_marker_t marker = _attrs.template get<edge_marker_t>(EDGE_MID_MARKER);
if (controls.size() < 4)
auto shape = _attrs.template get<edge_marker_t>(EDGE_END_MARKER);
if (shape != MARKER_SHAPE_NONE)
{
cr.save();
cr.translate(pos_end.first, pos_end.second);
cr.rotate(angle_e);
draw_marker(EDGE_END_MARKER, marker_size, cr);
cr.restore();
}
shape = _attrs.template get<edge_marker_t>(EDGE_START_MARKER);
if (shape != MARKER_SHAPE_NONE)
{
cr.save();
cr.translate(pos_begin.first, pos_begin.second);
cr.rotate(angle_b);
if (marker == MARKER_SHAPE_BAR)
cr.translate(-len/2., 0);
else
cr.translate(-len/2. + marker_size / 2, 0);
cr.translate(marker_size, 0);
draw_marker(EDGE_START_MARKER, marker_size, cr);
cr.restore();
}
else
shape = _attrs.template get<edge_marker_t>(EDGE_MID_MARKER);
if (shape != MARKER_SHAPE_NONE)
{
vector<double> cts = controls;
cts.insert(cts.begin(), pos_begin.second);
cts.insert(cts.begin(), pos_begin.first);
cts.push_back(pos_end.first);
cts.push_back(pos_end.second);
double mid_point = _attrs.template get<double>(EDGE_MID_MARKER_POSITION);
double len = 0;
for (size_t i = 0; i + 7 < cts.size(); i += 6)
cr.save();
edge_marker_t marker = _attrs.template get<edge_marker_t>(EDGE_MID_MARKER);
if (controls.size() < 8)
{
double dx = cts[i + 6] - cts[i];
double dy = cts[i + 6 + 1] - cts[i + 1];
len += sqrt(dx * dx + dy * dy);
cr.translate(pos_end.first, pos_end.second);
cr.rotate(angle_b);
if (marker == MARKER_SHAPE_BAR)
cr.translate(-len/2., 0);
else
cr.translate(-len/2. + marker_size / 2, 0);
}
pos_t mid_pos;
double pos = 0;
for (size_t i = 0; i + 7 < cts.size(); i += 6)
else
{
double dx = cts[i + 6] - cts[i];
double dy = cts[i + 6 + 1] - cts[i + 1];
double l = sqrt(dx * dx + dy * dy);
if (pos + l >= len * mid_point)
{
double t = 1 - (pos + l - len * mid_point) / l;
mid_pos.first = pow(1 - t, 3) * cts[i] +
3 * t * pow(1 - t, 2) * cts[i + 2] +
3 * t * t * (1 - t) * cts[i + 4] +
t * t * t * cts[i + 6];
mid_pos.second = pow(1 - t, 3) * cts[i + 1] +
3 * t * pow(1 - t, 2) * cts[i + 3] +
3 * t * t * (1 - t) * cts[i + 5] +
t * t * t * cts[i + 7];
dx = -3 * pow(1 - t, 2) * cts[i] +
(3 * pow(1 - t, 2) - 6 * t * (1-t)) * cts[i + 2] +
(-3 * t * t + 6 * t * (1 - t)) * cts[i + 4] +
3 * t * t * cts[i + 6];
dy = -3 * pow(1 - t, 2) * cts[i + 1] +
(3 * pow(1 - t, 2) - 6 * t * (1-t)) * cts[i + 3] +
(-3 * t * t + 6 * t * (1 - t)) * cts[i + 5] +
3 * t * t * cts[i + 7];
double angle_t = atan2(dy, dx);
cr.translate(mid_pos.first, mid_pos.second);
cr.rotate(angle_t);
if (marker != MARKER_SHAPE_BAR)
cr.translate(marker_size / 2, 0);