Program Listing for File CairoDrawer.cpp#
↰ Return to documentation for file (src/main/cpp/lib/CairoDrawer.cpp)
#include "CairoDrawer.h"
const double CairoDrawer::RADIAL_TEXT_FACTOR = 0.3;
const double CairoDrawer::COORDGRID_ADJUSTMENT = 0.5,
CairoDrawer::COORDPOINT_ANGLE = 0.005;
const double CairoDrawer::END_RADIUS_MAJOR_FACTOR = 0.25,
CairoDrawer::END_RADIUS_MINOR_FACTOR = 0.125,
CairoDrawer::RADIUS_TICK_LABEL_FACTOR = 0.75;
const double CairoDrawer::IO_LINE_WIDTH = 0.1,
CairoDrawer::CONNECTOR_ARC_RATIO = 0.6,
CairoDrawer::CONNECTOR_ARROW_HEIGHT = 3,
CairoDrawer::CONNECTOR_ARROW_ANGLE = 0.012;
const double CairoDrawer::INPUT_RADIUS_DELTA = 2.5, CairoDrawer::CONNECTOR_DELTA = 10,
CairoDrawer::TEXT_DELTA = 0.01, CairoDrawer::ANGLE_DELTA_SMALL = 0.001,
CairoDrawer::ANGLE_DELTA_MEDIUM = 0.01,
CairoDrawer::RADIUS_DELTA = 10,
CairoDrawer::OUTPUT_EXTREME_RADIUS_DELTA = 2,
CairoDrawer::OUTPUT_LABEL_LINE_END_DELTA = 3,
CairoDrawer::OUTPUT_LABEL_FONT_FACTOR = 0.1;
const CairoDrawer::TextAlignment CairoDrawer::TextAlignment::LEFT (1),
CairoDrawer::TextAlignment::CENTERED (0.5),
CairoDrawer::TextAlignment::RIGHT (0);
CairoDrawer::TextAlignment::TextAlignment (double ratio)
{
if (0 > ratio)
{
ratio = std::abs (ratio);
}
if (ratio > 1)
{
ratio /= std::floor (std::log10 (ratio));
}
this->ratio = ratio;
}
CairoDrawer::CairoDrawer (const std::string& fpath, int width, int height,
std::size_t num_inputs) :
Drawer (width, height, num_inputs)
{
set_surface (fpath, width, height);
}
void
CairoDrawer::draw_output_grid (const OutputGrid& grid)
{
cairo_context->set_identity_matrix ();
const Configuration & conf = Configuration::get_instance ();
// Draw ring with colored segments which are used to color and distinguish the connectors
draw_segment_axis (
grid.get_radius (),
conf.get_output_thickness (),
grid.get_start (),
grid.get_end (),
DrawerProperties<std::array<Color, 10>> (1, Color::BLACK,
Configuration::GLOW_10),
grid.get_direction ());
// Calculate the inner and outer radius of the OutputGrid
double min_radius = grid.get_radius () + conf.get_output_thickness ();
double max_radius = grid.get_radius () + conf.get_output_thickness ()
+ grid.get_height ();
// Radian distance (absolute!) between start and end angle
double span = angle_helper::rad_dist (grid.get_start ().value (),
grid.get_end ().value ());
// Calculate how the OutputGrid is separated into thin and thick lines (ticks)
const std::size_t NUM_SEGMENTS = grid.get_scale ().get_major_intersections ()
* grid.get_scale ().get_minor_intersections ();
const std::size_t NUM_THICK_LINES =
grid.get_scale ().get_minor_intersections ();
// Draw the ticks of the OutputGrid
for (size_t i = 0; i <= NUM_SEGMENTS; ++i)
{
Angle a ((grid.get_start ().value () + i * span / NUM_SEGMENTS));
if (i % NUM_THICK_LINES)
draw_line (Polar (min_radius, a), Polar (max_radius, a),
conf.get_prop_thin ());
else
draw_line (Polar (min_radius, a), Polar (max_radius, a),
conf.get_prop_thick ());
}
// Adjusted difference between the radial lines of the OutputGrid (outputs)
double y_dist = grid.get_height ()
/ (grid.get_num_outputs () - COORDGRID_ADJUSTMENT);
// Draw the description of the first output
const TextProperties& name_prop = conf.get_prop_axis_label();
Label first_label (grid.get_var (0).name, name_prop);
Cairo::TextExtents extends = get_text_extents(first_label);
draw_output_label (first_label, max_radius,
grid.get_radius () + conf.get_output_thickness () / 2,
grid.get_end (),
M_PI_2);
const MultiScale& scale = grid.get_scale ();
// Draw the scale of the OutputGrid
std::vector<Label> labels = scale.make_labels(0);
if (grid.get_direction () == Direction::CLOCKWISE)
{
std::reverse(labels.begin(), labels.end());
}
draw_text_orthogonal (
labels.front(),
Polar (min_radius, grid.get_start () - TEXT_DELTA), TextAlignment::RIGHT);
draw_text_orthogonal (
labels.back(),
Polar (min_radius, grid.get_end () + TEXT_DELTA), TextAlignment::LEFT);
// Draw the output lines of the OutputGrid
for (size_t i = 1; i < grid.get_num_outputs (); ++i)
{
// Draw the description of the i-th output
draw_output_label (Label (grid.get_var (i).name, name_prop),
max_radius + i * extends.height * name_prop.font_size * OUTPUT_LABEL_FONT_FACTOR,
min_radius + i * y_dist, grid.get_end (),
M_PI_2 - i * 2 * ANGLE_DELTA_MEDIUM);
labels = scale.make_labels(i);
if (grid.get_direction () == Direction::CLOCKWISE)
{
std::reverse(labels.begin(), labels.end());
}
draw_text_orthogonal (
labels.front(),
Polar (min_radius + i * y_dist + OUTPUT_EXTREME_RADIUS_DELTA,
grid.get_start () - TEXT_DELTA),
TextAlignment::RIGHT);
draw_text_orthogonal (
labels.back(),
Polar (min_radius + i * y_dist + OUTPUT_EXTREME_RADIUS_DELTA,
grid.get_end () + TEXT_DELTA),
TextAlignment::LEFT);
cairo_context->begin_new_path ();
draw_arc (min_radius + i * y_dist, grid.get_start (), grid.get_end (),
Direction::COUNTER_CLOCKWISE);
cairo_context->set_source_rgba (conf.get_prop_thin ().line_color.r (),
conf.get_prop_thin ().line_color.g (),
conf.get_prop_thin ().line_color.b (),
conf.get_prop_thin ().line_color.a ());
cairo_context->set_line_width (conf.get_prop_thin ().line_width);
cairo_context->stroke ();
}
}
void
CairoDrawer::draw_input_axis (const InputAxis& axis)
{
cairo_context->set_identity_matrix ();
const Configuration& conf = Configuration::get_instance ();
// Draw the base of the InputAxis: a filled ring segment
draw_ring_segment (axis.get_radius (), axis.get_height (), axis.get_start (),
axis.get_end (), axis.get_prop (),
Direction::COUNTER_CLOCKWISE);
// Radian distance (absolute!) between start and end angle
double span = angle_helper::rad_dist (axis.get_start ().value (),
axis.get_end ().value ());
// Tick values as strings
std::vector<Label> tick_labels = axis.get_scale ().make_labels ();
std::size_t label_pos = 0;
// Calculate radii for axis and ticks
double start_radius = axis.get_radius () + axis.get_height ();
double end_radius_major = start_radius
+ END_RADIUS_MAJOR_FACTOR * axis.get_height ();
double end_radius_minor = start_radius
+ END_RADIUS_MINOR_FACTOR * axis.get_height ();
// Get width of longest tick description
const Label& longestTickDiscr = *std::max_element(tick_labels.begin(), tick_labels.end(),
[](const Label& l0, const Label& l1) {
return l0.get_text().length() < l1.get_text().length();
});
set_font_face (longestTickDiscr);
Cairo::TextExtents tick_ext = get_text_extents (longestTickDiscr);
set_font_face (axis.make_label (conf.get_prop_axis_label ()));
Cairo::TextExtents label_ext = get_text_extents (
axis.make_label (conf.get_prop_axis_label ()));
// Calculate radii at that tick labels, axis label and histogram can start
double radius_tick_label = end_radius_major + tick_ext.width
+ RADIUS_TICK_LABEL_FACTOR * axis.get_height ();
double radius_label = radius_tick_label + label_ext.height + INPUT_RADIUS_DELTA;
double radius_histogram = radius_label + label_ext.height
+ INPUT_RADIUS_DELTA;
// Calculate how the InputAxis' ticks is separated into thin and thick lines (ticks)
const std::size_t NUM_SEGMENTS = axis.get_scale ().get_major_intersections ()
* axis.get_scale ().get_minor_intersections ();
const std::size_t NUM_THIN_LINES =
axis.get_scale ().get_minor_intersections ();
// Draw the ticks and the associated values labels
for (size_t i = 0; i <= NUM_SEGMENTS; ++i)
{
Angle a (axis.get_start () + span * (double (i) / double (NUM_SEGMENTS)));
if (i % NUM_THIN_LINES)
{
draw_line (
Polar (start_radius, a),
Polar (end_radius_minor, a),
DrawerProperties<> (axis.get_prop ().line_width / 2,
axis.get_prop ().line_color,
axis.get_prop ().fill_color));
}
else
{
const Label & tick_label = tick_labels[label_pos++];
draw_line (Polar (start_radius, a), Polar (end_radius_major, a),
axis.get_prop ());
draw_text_parallel (tick_label, Polar (radius_tick_label, a),
TextAlignment::RIGHT);
}
}
// Draw the name of the Variable
draw_text_orthogonal (
axis.make_label (conf.get_prop_axis_label ()),
Polar (radius_label, Angle::center (axis.get_start (), axis.get_end ())));
if (conf.is_histograms_enabled ())
{
draw_histogram (axis.get_histogram (), radius_histogram,
axis.get_start (), axis.get_end ());
}
}
void
CairoDrawer::draw_io_vector (const IOVector& iov)
{
// Calculate target from connector coordinate
std::size_t connector_pos = m_num_inputs;
const Point from = iov[connector_pos];
Polar target1 (from.coord.radius () - CONNECTOR_ARROW_HEIGHT,
from.coord.angle () - ANGLE_DELTA_SMALL), target2 (
from.coord.radius () - CONNECTOR_ARROW_HEIGHT,
from.coord.angle () + ANGLE_DELTA_SMALL);
// Draw links
for (std::size_t i = 0; i < m_num_inputs; ++i)
{
if (not std::isnan(iov[i].coord.radius ()) and not std::isnan(iov[i].coord.angle ().value ()))
{
Polar origin1 (iov[i].coord.radius () - RADIUS_DELTA,
iov[i].coord.angle () - ANGLE_DELTA_SMALL), origin2 (
iov[i].coord.radius () - RADIUS_DELTA,
iov[i].coord.angle () + ANGLE_DELTA_SMALL);
draw_link (origin1, origin2, target1, target2, iov[i].prop);
}
}
// Draw line from connector to first output
/*
double connector_distance = (iov[connector_pos + 1].coord.radius ()
- from.coord.radius ()) * 0.1
+ Configuration::get_instance ().get_output_thickness ();*/
if (iov.size() > connector_pos + 2)
{
draw_line(from.coord,
get_connector_start(iov[connector_pos + 1].coord,
iov[connector_pos + 2].coord), from.prop);
}
else
{
draw_line(from.coord, iov[connector_pos + 1].coord, from.prop);
}
draw_arrow (from.coord, from.prop);
// Draw connector on OutputGrid
for (size_t i = connector_pos + 1; i < iov.size () - 1; ++i)
{
Polar from = iov[i].coord, to = iov[i + 1].coord;
draw_connector (get_connector_start (from, to),
get_connector_end (from, to), iov[i].prop);
draw_coord_point (to, COORDPOINT_ANGLE,
(to.radius () - from.radius ()) * 0.2, iov[i].prop);
}
}
void
CairoDrawer::change_surface (const std::string& fpath, int width, int height,
std::size_t num_inputs_)
{
finish ();
set_surface (fpath, width, height);
m_coord_converter = CoordinateConverter(width, height);
m_num_inputs = num_inputs_;
}
void
CairoDrawer::finish ()
{
cairo_context->get_target ()->finish ();
}
void
CairoDrawer::draw_histogram (const InputAxis::Histogram& histogram,
double radius, const Angle& start,
const Angle& end)
{
cairo_context->set_identity_matrix ();
const Configuration & conf = Configuration::get_instance ();
// Create properties from Configuration
DrawerProperties<> histogram_background (conf.get_prop_thin ().line_width,
conf.get_histogram_background (),
conf.get_histogram_background ());
DrawerProperties<> histogram_fill (conf.get_prop_thin ().line_width,
conf.get_histogram_fill (),
conf.get_histogram_fill ());
const DrawerProperties<> & prop_thin = conf.get_prop_thin ();
// Draw background
draw_ring_segment (radius, conf.get_histogram_height (), start, end,
histogram_background, Direction::COUNTER_CLOCKWISE);
// Draw thin lines for histogram background
cairo_context->set_line_width (prop_thin.line_width);
for (std::size_t i = 1; i < 6; ++i)
{
cairo_context->begin_new_path ();
draw_arc (radius + conf.get_histogram_height () * (i / 6.), start, end,
Direction::COUNTER_CLOCKWISE);
cairo_context->stroke ();
}
// Draw the histogram graph filling
cairo_context->begin_new_path ();
double span = angle_helper::rad_dist (start.value (), end.value ());
for (std::size_t i = 0; i < histogram.get_num_intervals (); ++i)
{
draw_ring_segment (
radius,
conf.get_histogram_height () * histogram.get_section_frequency (i),
start + (span / histogram.get_num_intervals ()) * i,
start + (span / histogram.get_num_intervals ()) * (i + 1),
histogram_fill, Direction::COUNTER_CLOCKWISE);
}
cairo_context->stroke ();
// Draw the histogram graph outline
Cartesian start_point;
m_coord_converter.convert (Polar (radius, start), start_point);
cairo_context->begin_new_path ();
cairo_context->set_source_rgba (prop_thin.line_color.r (),
prop_thin.line_color.g (),
prop_thin.line_color.b (),
prop_thin.line_color.a ());
cairo_context->move_to (start_point.x (), start_point.y ());
for (std::size_t i = 0; i < histogram.get_num_intervals (); ++i)
{
// Create the lower right and upper left coordinates of the histogram column
Polar right_down (
radius, start + (span / histogram.get_num_intervals ()) * (i + 1)),
left_up (
radius
+ conf.get_histogram_height ()
* histogram.get_section_frequency (i),
start + (span / histogram.get_num_intervals ()) * i);
Cartesian right_down_c, left_up_c;
m_coord_converter.convert (right_down, right_down_c);
m_coord_converter.convert (left_up, left_up_c);
// Draw the histogram column
cairo_context->line_to (left_up_c.x (), left_up_c.y ());
draw_arc (left_up.radius (), left_up.angle (), right_down.angle (),
Direction::CLOCKWISE);
cairo_context->line_to (right_down_c.x (), right_down_c.y ());
}
cairo_context->stroke ();
}
void
CairoDrawer::draw_link (const Polar& origin1, const Polar& origin2,
const Polar& target1, const Polar& target2,
const DrawerProperties<>& prop)
{
cairo_context->set_identity_matrix ();
// Convert given Polar to Cartesian coordinates
Cartesian origin1_c, origin2_c, target1_c, target2_c;
m_coord_converter.convert (origin1, origin1_c);
m_coord_converter.convert (origin2, origin2_c);
m_coord_converter.convert (target1, target1_c);
m_coord_converter.convert (target2, target2_c);
// Calculate control points for Bezier curve
Cartesian CTRL_ORIG1 = create_link_control_point (origin1);
Cartesian CTRL_ORIG2 = create_link_control_point (origin2);
Cartesian CTRL_TARG1 = create_link_control_point (target1);
Cartesian CTRL_TARG2 = create_link_control_point (target2);
cairo_context->begin_new_path ();
cairo_context->move_to (origin1_c.x (), origin1_c.y ());
// Draw first Bezier curve from origin to target
cairo_context->curve_to (CTRL_ORIG1.x (), CTRL_ORIG1.y (), CTRL_TARG1.x (),
CTRL_TARG1.y (), target1_c.x (), target1_c.y ());
// draw line to second target point
cairo_context->line_to(target2_c.x (), target2_c.y ());
// Draw second Bezier curve from target to origin
cairo_context->curve_to (CTRL_TARG2.x (), CTRL_TARG2.y (), CTRL_ORIG2.x (),
CTRL_ORIG2.y (), origin2_c.x (), origin2_c.y ());
// Set line and fill style and apply drawing
cairo_context->set_source_rgba (prop.fill_color.r (), prop.fill_color.g (),
prop.fill_color.b (), prop.fill_color.a ());
cairo_context->fill_preserve ();
cairo_context->set_source_rgba (prop.fill_color.r (), prop.fill_color.g (),
prop.fill_color.b (), prop.fill_color.a ());
cairo_context->set_line_width (prop.line_width);
cairo_context->stroke ();
}
void
CairoDrawer::draw_connector (const Polar& from, const Polar& to,
const DrawerProperties<>& prop)
{
cairo_context->set_identity_matrix ();
cairo_context->begin_new_path ();
// Calculate distance factor
static const double dist_factor = (1 - CONNECTOR_ARC_RATIO) / 2;
// Calculate intermediate coordinates to draw the arc from
double radial_dist = to.radius () - from.radius ();
Polar curve_begin
{ from.radius () + dist_factor * radial_dist, from.angle () };
Polar curve_end
{ to.radius () - dist_factor * radial_dist, to.angle () };
// Convert to Cartesian coordinates
Cartesian from_c;
Cartesian to_c;
Cartesian curve_begin_c;
Cartesian curve_end_c;
m_coord_converter.convert (from, from_c);
m_coord_converter.convert (to, to_c);
m_coord_converter.convert (curve_begin, curve_begin_c);
m_coord_converter.convert (curve_end, curve_end_c);
// Line from start to the curve begin
cairo_context->move_to (from_c.x (), from_c.y ());
cairo_context->line_to (curve_begin_c.x (), curve_begin_c.y ());
// Adjust angles so that their difference is lower than PI
double begin_angle = curve_begin.angle ().value (), end_angle =
curve_end.angle ().value ();
double phi_diff_unadjusted = end_angle - begin_angle;
if (phi_diff_unadjusted > M_PI)
{
begin_angle += 2 * M_PI;
}
else if (phi_diff_unadjusted < -M_PI)
{
end_angle += 2 * M_PI;
}
// Decide wether to draw one or to connector segments
double phi_diff = end_angle - begin_angle;
if (std::abs (phi_diff) > M_PI_2)
{
// Calculate coordinate between the curve begin and the curve end
double mid_angle = (begin_angle + end_angle) / 2.0;
double mid_radius = curve_begin.radius ()
+ (curve_end.radius () - curve_begin.radius ()) / phi_diff
* (mid_angle - begin_angle);
draw_connector_segment (curve_begin.radius (), begin_angle, mid_radius,
mid_angle, prop);
draw_connector_segment (mid_radius, mid_angle, curve_end.radius (),
end_angle, prop);
}
else
{
draw_connector_segment (curve_begin.radius (), begin_angle,
curve_end.radius (), end_angle, prop);
}
// Line from the curve end to end
cairo_context->line_to (to_c.x (), to_c.y ());
// Set line style and apply drawing
cairo_context->set_source_rgba (prop.line_color.r (), prop.line_color.g (),
prop.line_color.b (), prop.line_color.a ());
cairo_context->set_line_width (prop.line_width);
cairo_context->stroke ();
}
void
CairoDrawer::draw_segment_axis (
double inner_radius, double thickness, const Angle& start, const Angle& end,
const DrawerProperties<std::array<Color, 10>>& prop, Direction dir)
{
cairo_context->set_identity_matrix ();
// Only draw axis with ten segments
const static std::size_t NUM_SPLITS = 10;
// Calculate segment size
double segment_size = angle_helper::rad_dist (start.value (), end.value ())
/ NUM_SPLITS;
// Draw segments
for (size_t i = 0; i < NUM_SPLITS; ++i)
{
DrawerProperties<> inner_prop (prop.line_width, prop.line_color,
prop.fill_color.at (i));
draw_ring_segment (inner_radius, thickness, start + segment_size * i,
start + segment_size * (i + 1), inner_prop,
Direction::COUNTER_CLOCKWISE);
}
}
void
CairoDrawer::draw_output_label (const Label& output_label, double radius_label,
double radius_output, const Angle& begin,
const Angle& end)
{
cairo_context->set_identity_matrix ();
cairo_context->begin_new_path ();
draw_arc (radius_output, begin, end, Direction::CLOCKWISE);
Cartesian tmp;
m_coord_converter.convert (Polar (radius_output, end), tmp);
Cartesian line_end (
tmp.x (),
tmp.y () - radius_label + radius_output - OUTPUT_LABEL_LINE_END_DELTA);
cairo_context->line_to (line_end.x (), line_end.y ());
cairo_context->line_to (m_coord_converter.get_center_x (), line_end.y ());
cairo_context->set_line_width (
Configuration::get_instance ().get_prop_thin ().line_width);
cairo_context->stroke ();
draw_text_orthogonal (output_label,
Polar (radius_label, M_PI_2 + 3 * ANGLE_DELTA_SMALL),
TextAlignment::LEFT);
}
void
CairoDrawer::draw_arrow (const Polar& start, const DrawerProperties<>& prop)
{
cairo_context->set_identity_matrix ();
cairo_context->begin_new_path ();
// Only draw arrows with height of 5
double height = CONNECTOR_ARROW_HEIGHT;
// Calculate arrow coordinates
Polar start_help (start.radius () - height, start.angle ()), direction_help (
start.radius () - height / 2, start.angle ()), left_help (
start.radius () - height, start.angle () - CONNECTOR_ARROW_ANGLE/2), right_help (
start.radius () - height, start.angle () + CONNECTOR_ARROW_ANGLE/2);
// Convert arrow coordinates into Cartesian coordinates
Cartesian start_c, direction_c, left, right;
m_coord_converter.convert (start_help, start_c);
m_coord_converter.convert (direction_help, direction_c);
m_coord_converter.convert (left_help, left);
m_coord_converter.convert (right_help, right);
// Calculate head coordinate h = p + height * (d - p) / ||d-p||
double p_x = start_c.x (), p_y = start_c.y (), d_x = direction_c.x (), d_y =
direction_c.y ();
double diff_len = std::sqrt (
std::pow (d_x - p_x, 2) + std::pow (d_y - p_y, 2));
Cartesian head (p_x + height * (d_x - p_x) / diff_len,
p_y + height * (d_y - p_y) / diff_len);
// Draw path and apply after setting line and fill style
cairo_context->move_to (head.x (), head.y ());
cairo_context->line_to (right.x (), right.y ());
cairo_context->line_to (left.x (), left.y ());
cairo_context->set_source_rgba (prop.line_color.r (), prop.line_color.g (),
prop.line_color.b (), 1);
cairo_context->set_line_width (IO_LINE_WIDTH);
cairo_context->fill_preserve ();
cairo_context->stroke ();
}
void
CairoDrawer::draw_coord_point (const Polar& coord, const Angle& width,
double height, const DrawerProperties<>& prop)
{
cairo_context->set_identity_matrix ();
// Calculate the radius and the angles between the box should be drawn
double inner_radius = coord.radius () - 0.5 * height;
Angle begin = coord.angle () - width * 0.5;
Angle end = coord.angle () + width * 0.5;
// Draw segment
draw_ring_segment (inner_radius, height, begin, end, prop,
Direction::COUNTER_CLOCKWISE);
}
void
CairoDrawer::draw_connector_segment (double begin_radius, double begin_angle,
double end_radius, double end_angle,
const DrawerProperties<> & prop)
{
// Calculate the Cartesian coordinates of start and end
Cartesian P0, P3;
m_coord_converter.convert (Polar (begin_radius, begin_angle), P0);
m_coord_converter.convert (Polar (end_radius, end_angle), P3);
double phi_diff = end_angle - begin_angle, r_diff = end_radius - begin_radius;
// Calculate the derivative vectors
double P1_x, P1_y, P2_x, P2_y;
if (std::abs (phi_diff) < DBL_MIN)
{
P1_x = 0;
P1_y = 0;
P2_x = 0;
P2_y = 0;
}
else
{
P1_x = 1;
P1_y = (begin_radius * std::cos (begin_angle)
+ (r_diff / phi_diff) * std::sin (begin_angle))
/ (begin_radius * std::sin (begin_angle)
- (r_diff / phi_diff) * std::cos (begin_angle));
P2_x = 1;
P2_y = (end_radius * std::cos (end_angle)
+ (r_diff / phi_diff) * std::sin (end_angle))
/ (end_radius * std::sin (end_angle)
- (r_diff / phi_diff) * std::cos (end_angle));
double norm_p1 = std::sqrt (1 + std::pow (P1_y, 2)), norm_p3 = std::sqrt (
1 + std::pow (P2_y, 2));
P1_x /= norm_p1;
P1_y /= norm_p1;
P2_x /= norm_p3;
P2_y /= norm_p3;
}
// Factor to scale the derivative vector
double k = 70 * std::abs (phi_diff);
// Calculate in which direction the derivative vector should be added
double phi_p1 = std::atan2 (
-(P0.y () + k * P1_y - m_coord_converter.get_center_y ()),
P0.x () + k * P1_x - m_coord_converter.get_center_x ());
if (phi_p1 < 0)
{
phi_p1 += 2 * M_PI;
}
if (phi_p1 < begin_angle && phi_p1 < end_angle)
{
phi_p1 += 2 * M_PI;
}
double phi_p2 = std::atan2 (
-(P3.y () + k * P2_y - m_coord_converter.get_center_y ()),
P3.x () + k * P2_x - m_coord_converter.get_center_x ());
if (phi_p2 < 0)
{
phi_p2 += 2 * M_PI;
}
if (phi_p2 < begin_angle && phi_p2 < end_angle)
{
phi_p2 += 2 * M_PI;
}
double sign_1 =
(begin_angle < phi_p1 && end_angle > phi_p1)
|| (begin_angle > phi_p1 && end_angle < phi_p1) ? 1 : -1;
double sign_2 =
(begin_angle < phi_p2 && end_angle > phi_p2)
|| (begin_angle > phi_p2 && end_angle < phi_p2) ? 1 : -1;
// Calculate the control points of the Bezier curve
Cartesian P1 (P0.x () + k * P1_x * sign_1, P0.y () + k * P1_y * sign_1), P2 (
P3.x () + k * P2_x * sign_2, P3.y () + k * P2_y * sign_2);
cairo_context->curve_to (P1.x (), P1.y (), P2.x (), P2.y (), P3.x (),
P3.y ());
}
void
CairoDrawer::draw_ring_segment (double inner_radius, double thickness,
const Angle& start, const Angle& end,
const DrawerProperties<>& prop, Direction dir)
{
cairo_context->set_identity_matrix ();
cairo_context->begin_new_path ();
// Draw first arc
draw_arc (inner_radius, start, end, Direction::COUNTER_CLOCKWISE);
// Draw second arc depending on first one
draw_arc (
inner_radius + thickness,
start,
end,
dir == Direction::COUNTER_CLOCKWISE ?
Direction::CLOCKWISE : Direction::COUNTER_CLOCKWISE);
cairo_context->close_path ();
// Set line and fill color and apply drawing
cairo_context->set_source_rgba (prop.fill_color.r (), prop.fill_color.g (),
prop.fill_color.b (), prop.fill_color.a ());
cairo_context->fill_preserve ();
cairo_context->set_source_rgba (prop.line_color.r (), prop.line_color.g (),
prop.line_color.b (), prop.line_color.a ());
cairo_context->set_line_width (prop.line_width);
cairo_context->stroke ();
}
void
CairoDrawer::draw_arc (double radius, const Angle& start, const Angle& end,
Direction dir)
{
cairo_context->set_identity_matrix ();
// Draw arc begin->end or end->begin depending on direction
switch (dir)
{
case Direction::COUNTER_CLOCKWISE:
cairo_context->arc (m_coord_converter.get_center_x (),
m_coord_converter.get_center_y (), radius,
get_cairo_angle (end).value (),
get_cairo_angle (start).value ());
break;
case Direction::CLOCKWISE:
cairo_context->arc_negative (m_coord_converter.get_center_x (),
m_coord_converter.get_center_y (), radius,
get_cairo_angle (start).value (),
get_cairo_angle (end).value ());
break;
}
}
void
CairoDrawer::draw_line (const Polar& from, const Polar& to,
const DrawerProperties<>& prop)
{
cairo_context->set_identity_matrix ();
cairo_context->begin_new_path ();
// Convert to Cartesian coordinates
Cartesian from_c, to_c;
m_coord_converter.convert (from, from_c);
m_coord_converter.convert (to, to_c);
// Draw path and apply after setting line style
cairo_context->move_to (from_c.x (), from_c.y ());
cairo_context->line_to (to_c.x (), to_c.y ());
cairo_context->set_source_rgba (prop.line_color.r (), prop.line_color.g (),
prop.line_color.b (), prop.line_color.a ());
cairo_context->set_line_width (prop.line_width);
cairo_context->stroke ();
}
void
CairoDrawer::draw_text_parallel (const Label& label, const Polar& start,
const TextAlignment& alignment)
{
cairo_context->set_identity_matrix ();
// Set font style
set_font_face (label);
// Calculate the width and height of the textbox
const Cairo::TextExtents & t_exts = get_text_extents (label);
// Calculate cairo angle
Angle cairo_angle = M_PI_2 - start.angle ().value ();
// Set cairo user-space origin and rotation
cairo_context->begin_new_path ();
cairo_context->translate (m_coord_converter.get_center_x (),
m_coord_converter.get_center_y ());
cairo_context->rotate (cairo_angle.value ());
cairo_context->translate (0, -start.radius ());
if (start.angle ().value () > M_PI_2 && start.angle ().value () < 3 * M_PI_2)
cairo_context->rotate_degrees (90);
else
cairo_context->rotate_degrees (270);
cairo_context->translate (-alignment.ratio * t_exts.width,
0.5 * t_exts.height);
cairo_context->close_path ();
cairo_context->text_path (label.get_text ());
cairo_context->fill ();
}
void
CairoDrawer::draw_text_orthogonal (const Label& label, const Polar& start,
const TextAlignment & alignment)
{
cairo_context->set_identity_matrix ();
// Set font style
set_font_face (label);
// Calculate the width and height of the textbox
Cairo::TextExtents t_exts = get_text_extents (label);
// Create cairo angle
Angle cairo_angle = M_PI_2 - start.angle ().value ();
// Set cairo user-space origin and rotation
cairo_context->begin_new_path ();
cairo_context->translate (m_coord_converter.get_center_x (),
m_coord_converter.get_center_y ());
cairo_context->rotate (cairo_angle.value ());
cairo_context->translate (0, -start.radius ());
cairo_context->translate (-alignment.ratio * t_exts.width,
(1 - alignment.ratio) * t_exts.height);
cairo_context->close_path ();
cairo_context->text_path (label.get_text ());
cairo_context->fill ();
}
void
CairoDrawer::set_font_face (const Label& label)
{
const TextProperties & prop = label.get_properties ();
// Set font styles
Cairo::RefPtr<Cairo::ToyFontFace> font = Cairo::ToyFontFace::create (
prop.font_name,
#if (CAIROMM_MINOR_VERSION == 15 && CAIROMM_MICRO_VERSION >= 4) || CAIROMM_MINOR_VERSION > 15
prop.italic ? Cairo::ToyFontFace::Slant::ITALIC : Cairo::ToyFontFace::Slant::NORMAL,
prop.bold ? Cairo::ToyFontFace::Weight::BOLD : Cairo::ToyFontFace::Weight::NORMAL);
#else
prop.italic ? Cairo::FONT_SLANT_ITALIC : Cairo::FONT_SLANT_NORMAL,
prop.bold ? Cairo::FONT_WEIGHT_BOLD : Cairo::FONT_WEIGHT_NORMAL);
#endif
cairo_context->set_font_face (font);
cairo_context->set_font_size (label.get_properties ().font_size);
cairo_context->set_source_rgba (prop.color.r (), prop.color.g (),
prop.color.b (), prop.color.a ());
}
Cairo::TextExtents
CairoDrawer::get_text_extents (const Label& label) const
{
// Calculate the width and height of the textbox
Cairo::TextExtents t_exts;
std::string message
{ label.get_text () };
cairo_context->get_text_extents (message, t_exts);
return t_exts;
}
void
CairoDrawer::set_surface (const std::string& fpath, int width, int height)
{
Cairo::RefPtr<Cairo::Surface> ptr = Cairo::SvgSurface::create (fpath,
width,
height);
cairo_context = Cairo::Context::create (ptr);
}