LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
tikz_edge_router.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_IO_TIKZ_TIKZ_EDGE_ROUTER_H
6#define LINE_IO_TIKZ_TIKZ_EDGE_ROUTER_H
7
8/**
9 * @file
10 * @ingroup line_io
11 * Port of `jline.io.tikz.TikZEdgeRouter`: orthogonal waypoints that keep an
12 * edge off the nodes it would otherwise cross.
13 *
14 * A forward edge is straight unless a node's box (plus margin) lies on it, and
15 * is then routed above or below the obstacles' mean height. A BACKWARD edge
16 * (feedback) is routed around the whole drawing, above it when its source is
17 * not below its target, and each one takes the next channel out, so the
18 * waypoints of a backward edge depend on how many were routed before it: the
19 * router is STATEFUL and the order edges are routed in is part of the output.
20 * An edge between two nodes of one layer is offset to the right.
21 *
22 * The JAR's bounds start at `Double.MIN_VALUE` for the maxima (the smallest
23 * POSITIVE double, not the most negative); that is kept, since every drawing
24 * has a node at y >= 0 and the two starts then give the same bounds anyway.
25 */
26
27#include <algorithm>
28#include <cmath>
29#include <limits>
30#include <string>
31#include <vector>
32
35
36namespace line {
37namespace io {
38
39/** A waypoint in cm. */
40struct TikzPoint {
41 double x = 0, y = 0;
42};
43
45public:
46 static constexpr double NODE_HALF_WIDTH = 1.2; ///< cm
47 static constexpr double NODE_HALF_HEIGHT = 0.9; ///< cm
48 static constexpr double MARGIN = 0.25;
49 static constexpr double ROUTE_SPACING = 0.5;
50 static constexpr double ENTRY_OFFSET = 0.2;
51
52 /**
53 * @param x,y positions of every node of the graph, indexed by node
54 * @param visible the nodes that are drawn; only they bound the drawing and count as obstacles
55 */
56 TikzEdgeRouter(const std::vector<double>& x, const std::vector<double>& y,
57 const std::vector<std::size_t>& visible)
58 : x_(x), y_(y), visible_(visible) {
59 min_x_ = std::numeric_limits<double>::max();
60 max_x_ = std::numeric_limits<double>::denorm_min();
61 min_y_ = std::numeric_limits<double>::max();
62 max_y_ = std::numeric_limits<double>::denorm_min();
63 for (std::size_t v : visible_) {
64 min_x_ = std::min(min_x_, x_[v] - NODE_HALF_WIDTH);
65 max_x_ = std::max(max_x_, x_[v] + NODE_HALF_WIDTH);
66 min_y_ = std::min(min_y_, y_[v] - NODE_HALF_HEIGHT);
67 max_y_ = std::max(max_y_, y_[v] + NODE_HALF_HEIGHT);
68 }
69 }
70
71 /** `computeWaypoints`: empty when the straight segment is clear, and for a self-loop. */
72 std::vector<TikzPoint> compute_waypoints(std::size_t from, std::size_t to) {
73 std::vector<TikzPoint> wp;
74 if (from == to) return wp;
75 const TikzPoint f{x_[from], y_[from]}, t{x_[to], y_[to]};
76 if (t.x < f.x - NODE_HALF_WIDTH) return route_backward(f, t);
77 if (std::fabs(t.x - f.x) < NODE_HALF_WIDTH) {
78 const double off = NODE_HALF_WIDTH + MARGIN + 0.5;
79 wp.push_back(TikzPoint{f.x + off, f.y});
80 wp.push_back(TikzPoint{t.x + off, t.y});
81 return wp;
82 }
83 std::vector<std::size_t> obstacles;
84 for (std::size_t v : visible_) {
85 if (v == from || v == to) continue;
86 if (line_intersects_node(f, t, v)) obstacles.push_back(v);
87 }
88 std::stable_sort(obstacles.begin(), obstacles.end(),
89 [&](std::size_t a, std::size_t b) { return x_[a] < x_[b]; });
90 if (obstacles.empty()) return wp;
91 return route_forward(f, t, obstacles);
92 }
93
94private:
95 std::vector<TikzPoint> route_backward(const TikzPoint& f, const TikzPoint& t) {
96 std::vector<TikzPoint> wp;
97 ++backward_;
98 const bool above = f.y >= t.y;
99 const int ch = backward_;
100 const double route_y = above ? max_y_ + MARGIN + (ROUTE_SPACING * ch)
101 : min_y_ - MARGIN - (ROUTE_SPACING * ch);
102 const double exit_x = f.x + NODE_HALF_WIDTH + MARGIN + (backward_ - 1) * 0.3;
103 const double entry_x = t.x - NODE_HALF_WIDTH - MARGIN - (backward_ - 1) * 0.3;
104 double dy = (backward_ - 1) * ENTRY_OFFSET;
105 if (!above) dy = -dy;
106 const double target_y = t.y + dy;
107 wp.push_back(TikzPoint{exit_x, f.y});
108 wp.push_back(TikzPoint{exit_x, route_y});
109 wp.push_back(TikzPoint{entry_x, route_y});
110 wp.push_back(TikzPoint{entry_x, target_y});
111 return wp;
112 }
113
114 std::vector<TikzPoint> route_forward(const TikzPoint& f, const TikzPoint& t,
115 const std::vector<std::size_t>& obstacles) const {
116 std::vector<TikzPoint> wp;
117 double avg = 0;
118 for (std::size_t v : obstacles) avg += y_[v];
119 avg /= static_cast<double>(obstacles.size());
120 const double mid = (f.y + t.y) / 2.0;
121 const bool above = mid >= avg;
122 const double offset = (NODE_HALF_HEIGHT + MARGIN + 0.15) * (above ? 1 : -1);
123 const double route_y = avg + offset;
124 double minx = std::numeric_limits<double>::max();
125 double maxx = std::numeric_limits<double>::denorm_min();
126 for (std::size_t v : obstacles) {
127 minx = std::min(minx, x_[v] - NODE_HALF_WIDTH - MARGIN);
128 maxx = std::max(maxx, x_[v] + NODE_HALF_WIDTH + MARGIN);
129 }
130 const double entry_x = std::max(f.x + 0.3, minx - 0.3);
131 wp.push_back(TikzPoint{entry_x, route_y});
132 const double exit_x = std::min(t.x - 0.3, maxx + 0.3);
133 if (exit_x > entry_x + 0.1) wp.push_back(TikzPoint{exit_x, route_y});
134 return wp;
135 }
136
137 bool line_intersects_node(const TikzPoint& s, const TikzPoint& e, std::size_t v) const {
138 const double hw = NODE_HALF_WIDTH + MARGIN, hh = NODE_HALF_HEIGHT + MARGIN;
139 const double left = x_[v] - hw, right = x_[v] + hw;
140 const double bottom = y_[v] - hh, top = y_[v] + hh;
141 const double x1 = s.x, y1 = s.y, x2 = e.x, y2 = e.y;
142 if ((x1 < left && x2 < left) || (x1 > right && x2 > right)) return false;
143 if ((y1 < bottom && y2 < bottom) || (y1 > top && y2 > top)) return false;
144 if (in_box(x1, y1, left, right, bottom, top) || in_box(x2, y2, left, right, bottom, top))
145 return true;
146 return seg(x1, y1, x2, y2, left, bottom, left, top) ||
147 seg(x1, y1, x2, y2, right, bottom, right, top) ||
148 seg(x1, y1, x2, y2, left, bottom, right, bottom) ||
149 seg(x1, y1, x2, y2, left, top, right, top);
150 }
151
152 static bool in_box(double x, double y, double l, double r, double b, double t) {
153 return x >= l && x <= r && y >= b && y <= t;
154 }
155
156 static bool seg(double x1, double y1, double x2, double y2, double x3, double y3, double x4,
157 double y4) {
158 const double den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
159 if (std::fabs(den) < 1e-10) return false;
160 const double t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den;
161 const double u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den;
162 return t >= 0 && t <= 1 && u >= 0 && u <= 1;
163 }
164
165 const std::vector<double>& x_;
166 const std::vector<double>& y_;
167 std::vector<std::size_t> visible_;
168 double min_x_, max_x_, min_y_, max_y_;
169 int backward_ = 0;
170};
171
172/**
173 * `TikZEdgeRouter.renderRoutedEdge`. `prob` is NaN for an unlabelled edge, which
174 * is every edge the exporter draws: the JAR passes NaN unconditionally.
175 */
176inline std::string tikz_render_routed_edge(const std::string& from_id, const std::string& to_id,
177 const std::string& from_anchor,
178 const std::string& to_anchor,
179 const std::vector<TikzPoint>& wp, double prob,
180 const TikzOptions& opt) {
182 const bool label = opt.show_routing_prob && !std::isnan(prob) &&
183 prob >= opt.min_prob_to_show && prob < 1.0 - opt.min_prob_to_show;
184 std::string s = "\\draw[conn] (" + from_id + from_anchor + ")";
185 for (const TikzPoint& p : wp) s += " -- (" + java_fixed(p.x, 2) + "," + java_fixed(p.y, 2) + ")";
186 if (label) s += " -- node[problabel] {" + java_fixed(prob, 2) + "}";
187 else s += " --";
188 s += " (" + to_id + to_anchor + ");\n";
189 return s;
190}
191
192} // namespace io
193} // namespace line
194
195#endif // LINE_IO_TIKZ_TIKZ_EDGE_ROUTER_H
static constexpr double ENTRY_OFFSET
static constexpr double ROUTE_SPACING
static constexpr double MARGIN
static constexpr double NODE_HALF_WIDTH
cm
std::vector< TikzPoint > compute_waypoints(std::size_t from, std::size_t to)
computeWaypoints: empty when the straight segment is clear, and for a self-loop.
static constexpr double NODE_HALF_HEIGHT
cm
TikzEdgeRouter(const std::vector< double > &x, const std::vector< double > &y, const std::vector< std::size_t > &visible)
std::string java_fixed(double v, int prec)
Java's String.format("%." + prec + "f", v).
Definition tikz_graph.h:79
std::string tikz_render_routed_edge(const std::string &from_id, const std::string &to_id, const std::string &from_anchor, const std::string &to_anchor, const std::vector< TikzPoint > &wp, double prob, const TikzOptions &opt)
TikZEdgeRouter.renderRoutedEdge.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Layout and rendering options of the network TikZ exporter (TikZOptions).
A waypoint in cm.
The part of a network the TikZ exporter draws, and the text helpers the JAR's exporter uses to write ...
Port of jline.io.tikz.TikZOptions: the knobs of the network TikZ exporter.