Artifact 9240b5f05039c22b6c5308ac10f104da82274f8e856a7861972680aa998bf9a9:

  • File src/fifr/graph/Tikz.hxx — part of check-in [9f4eb28e1f] at 2017-09-20 06:49:17 on branch trunk — Format sources with clang-format (user: fifr size: 8180)

/*
 * 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