/*
* Copyright (c) 2016 Frank Fischer <frank-fischer@shadow-soft.de>
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef __TIKZ_HXX__
#define __TIKZ_HXX__
#include <iomanip>
#include <iostream>
#include <map>
#include <ostream>
#include <utility>
#include "Digraph.hxx"
#include "Graph.hxx"
namespace fifr
{
namespace graph
{
enum class Shape {
Circle,
Disc,
};
struct Color {
uint8_t red = 0;
uint8_t green = 0;
uint8_t blue = 0;
double opacity = 1.0;
bool is_black() const { return red == 0 && green == 0 && blue == 0; }
};
/**
* Simple class to draw graphs in Tikz format.
*
* This class takes a reference to the graph to be drawn. Therefore
* the graph must exist as long as the TikzDrawer is used.
*
* TikzDrawer can be used with directed and undirected graphs.
*/
template <class Graph>
class TikzDrawer
{
private:
/// Internal class for accessing graph items.
template <class G_>
class Graph_;
template <class G, class N, class A, class ID>
class Graph_<Digraph<G, N, A, ID>>
{
public:
typedef typename Digraph<G, N, A, ID>::Node Node;
typedef typename Digraph<G, N, A, ID>::Arc Arc;
template <class F>
static void each_arc(const Digraph<G, N, A, ID>& g, F f)
{
for (auto a : g.arcs()) {
f(a, g.src(a), g.snk(a));
}
}
static const bool directed = true;
};
template <class G, class N, class A, class ID>
class Graph_<fifr::graph::Graph<G, N, A, ID>>
{
public:
typedef typename fifr::graph::Graph<G, N, A, ID>::Node Node;
typedef typename fifr::graph::Graph<G, N, A, ID>::Edge Arc;
template <class F>
static void each_arc(const fifr::graph::Graph<G, N, A, ID>& g, F f)
{
for (auto e : g.edges()) {
auto uv = g.edge_nodes(e);
f(e, uv.first, uv.second);
}
}
static const bool directed = false;
};
/// Internal graph accessor.
typedef Graph_<Graph> Impl_;
public:
/// Type of nodes.
typedef typename Impl_::Node Node;
/// Type of arcs/edges.
typedef typename Impl_::Arc Arc;
/// Coordinates of a node to be drawn.
typedef std::pair<double, double> Point;
/// Bend configuration of an edge/arc.
enum class Bend { None, Left, Right };
/// Information associated with a node.
struct NodeInfo {
Point point = {0, 0}; ///< node coordinates
Shape shape = Shape::Circle;
Color color;
};
/// Information associated with a arc.
struct ArcInfo {
Bend bend = Bend::None; ///< bend attribute
double width = 0.5; ///< line width
Color color;
};
public:
TikzDrawer(const Graph& graph) : graph_(graph), indent_(0), doc_(false) {}
/// Set whether document boiler plate should be drawn.
void set_document(bool doc) { doc_ = doc; }
/// Return the node information.
NodeInfo& node(Node u) { return node_infos_[u]; }
/// Return the node information.
const NodeInfo& node(Node u) const { return node_infos_[u]; }
/// Return the node information.
ArcInfo& arc(Arc a) { return arc_infos_[a]; }
/// Return the node information.
const ArcInfo& arc(Arc a) const { return arc_infos_[a]; }
/// Sets the point associated with a node.
void set_point(Node u, Point x) { node_infos_[u].point = std::move(x); }
/// Sets the bend attribute associated with an arc.
void set_bend(Arc a, Bend bend) { arc_infos_[a].bend = bend; }
/// Sets the line width of an arc.
void set_width(Arc a, double width)
{
assert(width >= 0);
arc_infos_[a].width = width;
}
/// Write TeX document preamble.
static void write_begin(std::ostream& out)
{
out << "\\documentclass{article}\n";
out << "\\usepackage[utf8]{inputenc}\n";
out << "\\usepackage[T1]{fontenc}\n";
out << "\\usepackage{tikz}\n";
out << "\\begin{document}\n";
}
/// Write TeX document closing.
static void write_end(std::ostream& out) { out << "\\end{document}\n"; }
/// Write Tikz graph to output stream.
std::ostream& write_tikz(std::ostream& out) const
{
if (doc_) write_begin(out);
write_indent(out);
out << "\\begin{tikzpicture}[>=stealth,inner sep=5pt]\n";
out << std::setprecision(2);
auto node_info_default = NodeInfo{};
auto arc_info_default = ArcInfo{};
for (auto u : graph_.nodes()) {
auto it = node_infos_.find(u);
auto info = it != node_infos_.end() ? it->second : node_info_default;
if (!info.color.is_black()) {
write_indent(out, 1);
out << "\\definecolor{currentcolor}{RGB}{" << (int)info.color.red << ","
<< (int)info.color.green << "," << (int)info.color.blue << "}\n";
}
write_indent(out, 1);
out << "\\node[circle,";
switch (info.shape) {
case Shape::Circle:
out << "draw";
break;
case Shape::Disc:
out << "fill";
break;
}
if (!info.color.is_black()) {
out << "=currentcolor";
}
if (info.color.opacity != 1) {
out << ",opacity=" << std::fixed << info.color.opacity;
}
out << "] (" << (unsigned)u << ") "
<< "at (" << std::fixed << info.point.first << "," << std::fixed
<< info.point.second << ") "
<< "{};\n";
//<< "{" << (unsigned)(u+1) << "};\n";
}
Impl_::each_arc(graph_, [&](Arc e, Node u, Node v) {
auto it = arc_infos_.find(e);
auto info = it != arc_infos_.end() ? it->second : arc_info_default;
if (!info.color.is_black()) {
write_indent(out, 1);
out << "\\definecolor{currentcolor}{RGB}{" << (int)info.color.red << ","
<< (int)info.color.green << "," << (int)info.color.blue << "}\n";
}
write_indent(out, 1);
out << "\\draw[";
out << "line width=" << info.width << "pt";
if (Impl_::directed) out << ",->";
if (!info.color.is_black()) {
out << ",color=currentcolor";
}
if (info.color.opacity != 1) {
out << ",opacity=" << info.color.opacity;
}
out << "]";
out << " (" << (unsigned)u << ") ";
out << "to";
switch (info.bend) {
case Bend::Left:
out << "[bend left]";
break;
case Bend::Right:
out << "[bend right]";
break;
case Bend::None:
break;
}
out << " (" << (unsigned)v << ");"
<< "\n";
});
write_indent(out);
out << "\\end{tikzpicture}\n";
if (doc_) write_end(out);
return out;
}
inline void write_indent(std::ostream& out, unsigned offset = 0) const
{
for (auto i = 0u; i < 2 * (indent_ + offset); i++) out << " ";
}
private:
const Graph& graph_;
std::map<Node, NodeInfo> node_infos_;
std::map<Arc, ArcInfo> arc_infos_;
unsigned indent_;
bool doc_;
};
}
}
#endif