Skip to content

Commit

Permalink
Item13612: added graphviz-from-table feature
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelDaum committed Aug 11, 2015
1 parent 84d14b0 commit 5cd0ece
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 20 deletions.
66 changes: 53 additions & 13 deletions data/System/GraphvizPlugin.txt
@@ -1,4 +1,4 @@
%META:TOPICINFO{author="ProjectContributor" comment="" date="1439247692" format="1.1" version="1"}%
%META:TOPICINFO{author="ProjectContributor" comment="" date="1439299038" format="1.1" version="1"}%
---+!! %TOPIC%
%SHORTDESCRIPTION%

Expand All @@ -11,7 +11,7 @@ in visual interfaces for many other domains.

This plugin uses [[http://www.graphviz.org/][Graphviz's]] applications to
create an image of a directed graph. The directed graph is described using a
simple text markup called "The _dot_ Language". This markup is written between
simple text markup called "The DOT Language". This markup is written between
=<graphviz>= and =</graphviz>= tags or using the =%GRAPHVIZ= macro.
By default, an svg image attachment is created and displayed inline, replacing the =<graphviz>= markup.

Expand All @@ -27,18 +27,18 @@ There are two ways to specify a graph:
1 using the =<graphviz>= xml tag
2 using the =%GRAPHVIZ= Foswiki macro

Both can be used to generate a graph inline, that is by specifying the =graphviz= code as part of the page. Below
Both can be used to generate a graph inline, that is by specifying the DOT language as part of the page. Below
examples are mostly equivalent:

<verbatim class="xml">
<graphviz renderer="dot" type="png">
digraph G {Hello->World}
digraph G {Hello[fontcolor="red"]; Hello->World}
</graphviz>
</verbatim>

<verbatim class="tml">
%GRAPHVIZ{
"digraph G {Hello->World}"
"digraph G {Hello[fontcolor=\"red\"]; Hello->World}"
renderer="dot"
type="png"
}%
Expand All @@ -48,7 +48,7 @@ Both should render as

%IF{"context GraphvizPluginEnabled"
then="$percntGRAPHVIZ{
\"digraph G {Hello->World}\"
\"digraph G {Hello[fontcolor=\"red\"]; Hello->World}\"
renderer=\"dot\"
type=\"png\"
}$percnt"
Expand All @@ -58,32 +58,72 @@ Both should render as
when installed.

The =&lt;graphviz>= xml syntax has got the advantage of being in line with other wikis, such as dokuwiki. Also,
the =graphviz= code does not have to escape any double-quotes (="=) as is the case using the Foswiki macro way.
the DOT code does not have to escape any double-quotes (="=) as is the case using the Foswiki macro way.
However using a proper =%GRAPHVIZ= macro has got the advantage of being well integrated into Foswiki's Topic Markup Language.
For instance it can be properly escaped when in a [[FormattedSearch]].

For compatibility with !DirectedGraphPlugin you may use =&lt;dot>= instead of =&lt;graphviz>=.

---++ Syntax

=&lt;graphviz [params]> ... graphviz code ... &lt;graphviz>=
=&lt;graphviz [params]> ... DOT code ... &lt;graphviz>=

=%<nop>GRAPHVIZ{" ... graphviz code ... " [params]}%=
=%<nop>GRAPHVIZ{" ... DOT code ... " [params]}%=

| *Parameter* | *Description* | *Default* |
| =graphviz code= | this is the graph specified in the graph description language | |
| =DOT code= | this is the graph specified in the graph description language | |
| =type="png&#124;jpeg&#124;gif&#124;svg&#124;svgz&#124;pdf"= | output format | svg |
| =renderer="dot&#124;neato&#124;twopi&#124;circle&#124;fdp&#124;sfdp&#124;patchwork"= | rendering engine | dot |
| =topic="[web.]topic"= | specify the topic where to store the graph image | current topic |
| =attachment=" ... "= | attachment that may hold a =.gv= graph file (only available in =%GRAPHVIZ=) | |
| =section=" ... "= | named section in =topic= that holds a graph description (only available in =%GRAPHVIZ=) | |
| =library="[web.]topic"= | specify the topic where to search for images to be used in the dot graph | =topic= |
| =inline="on&#124;off"= | boolean flag to specify the way how to add an svg result to the html page; if =inline="on"= then an =&lt;svg>= html tag is generated; otherwise an html =&lt;img ... >= tag is used; note that for properly linking URLs in the =graphviz= graph you will need to switch this on | off |
| =expand="on&#124;off"= | boolean flag, when switched on, will expand TML macros in the graphviz code before rendering it | off |
| =expand="on&#124;off"= | boolean flag, when switched on, will expand TML macros in the DOT code before rendering it | off |
| =style=" ... "= | css styles to be added to the output | |
| =width=" ... "= | width of the image result | |
| =height=" ... "= | height of the image result | |

---++ Graphviz from Table

Instead of having to learn the dot language, nodes and edges can also be specified in Foswiki tables:

| *Node* | *Attributes* |
| hello | color="red" |
| world | shape=box |

| *Source* | *Label* | *Target* | *Attributes* |
| hello | | world | |

Given these two tables where the only two tables in a topic then this would render the appropriate graph:

<verbatim class="tml">
%GRAPHVIZ{
topic="SomeTopic"

nodestable="0"
nodecol="0"
nodeattrcol="1"

edgestable="1"
sourcecol="0"
labelcol="1"
targetcol="2"
edgeattrcol="3"
}%
</verbatim>

| *Parameter* | *Description* | *Default* |
| =table="int"= / =edgestable="int"=| index of table in =topic= to extract edges from | |
| =nodestable="int"= | index of table in =topic= to extract nodes from | |
| =nodecol="int"= | index of column that holds the node name | 0 |
| =nodeattrcol="int"= | index of colum that holds node attributs |
| =sourcecol="int"= | index of the colum holding the source node name | 0 |
| =targetcol="int"= | index of the colum holding the target node name | 1 |
| =labelcol="int"= | index of column holding the edge label | |
| =edgeattrcol="int"= | index of column holding the edge attributes |
| =preamble= | some dot code to be added before the generated node and edge code | |

---++ Examples

---+++ Example 1
Expand Down Expand Up @@ -156,5 +196,5 @@ digraph G {
| Home page: | Foswiki:Extensions/%TOPIC% |
| Support: | Foswiki:Support/%TOPIC% |

%META:FILEATTACHMENT{name="hello_world.png" attachment="hello_world.png" attr="" comment="" date="1439247692" size="6201" user="ProjectContributor" version="1"}%
%META:FILEATTACHMENT{name="softmaint.txt" attachment="softmaint.gv" attr="h" comment="" date="1439247692" moveby="micha" movedto="System.GraphvizPlugin.softmaint.txt" movedwhen="1439247180" movefrom="System.GraphvizPlugin.softmaint.gv" size="14946" user="ProjectContributor" version="1"}%
%META:FILEATTACHMENT{name="hello_world.png" attachment="hello_world.png" attr="" comment="" date="1439299038" size="6201" user="ProjectContributor" version="1"}%
%META:FILEATTACHMENT{name="softmaint.txt" attachment="softmaint.gv" attr="h" comment="" date="1439299038" moveby="micha" movedto="System.GraphvizPlugin.softmaint.txt" movedwhen="1439247180" movefrom="System.GraphvizPlugin.softmaint.gv" size="14946" user="ProjectContributor" version="1"}%
4 changes: 2 additions & 2 deletions lib/Foswiki/Plugins/GraphvizPlugin.pm
Expand Up @@ -23,8 +23,8 @@ use Foswiki::Plugins ();
use Foswiki::Attrs ();
use Foswiki::Plugins::WysiwygPlugin ();

our $VERSION = '0.01';
our $RELEASE = '0.01';
our $VERSION = '0.02';
our $RELEASE = '11 Aug 2015';
our $SHORTDESCRIPTION = 'Draw graphs using the !GraphViz utility';
our $NO_PREFS_IN_TOPIC = 1;
our $core;
Expand Down
35 changes: 30 additions & 5 deletions lib/Foswiki/Plugins/GraphvizPlugin/Core.pm
Expand Up @@ -58,10 +58,23 @@ sub GRAPHVIZ {
my $text = "";
my $attachment = $params->{attachment};
my $section = $params->{section};
my $nodeTable = $params->{nodestable};
my $edgeTable = $params->{edgestable} || $params->{table};
my $doGraphFromTable = (defined $nodeTable || defined $edgeTable);
my $parser;

if ($doGraphFromTable) {
require Foswiki::Plugins::GraphvizPlugin::TableParser;
$parser = Foswiki::Plugins::GraphvizPlugin::TableParser->new();
my ($meta) = Foswiki::Func::readTopic($theWeb, $theTopic);
$parser->parse($meta->text, $meta);
$text = "digraph $theTopic {\n";
$text .= "\n".($params->{preamble} || '')."\n";
}

# .... read attachment
if (defined $attachment) {
return _inlineError("attachment '$attachment' not found at $theWeb.$theTopic")
return _inlineError("Error: attachment '$attachment' not found at $theWeb.$theTopic")
unless Foswiki::Func::attachmentExists($theWeb, $theTopic, $attachment);
$text = Foswiki::Func::readAttachment($theWeb, $theTopic, $attachment);
}
Expand All @@ -75,27 +88,39 @@ sub GRAPHVIZ {
$thisParams->{$key} = $val;
}
$thisParams->{_DEFAULT} = "$theWeb.$theTopic";
my ($obj) = Foswiki::Func::readTopic($web, $topic);
$text = $session->INCLUDE($thisParams, $obj)
my ($meta) = Foswiki::Func::readTopic($web, $topic);
$text = $session->INCLUDE($thisParams, $meta)
}

# ... from table
elsif (defined $nodeTable || defined $edgeTable) {
$text .= $parser->getNodes($nodeTable, $params) if defined $nodeTable;
$text .= $parser->getEdges($edgeTable, $params) if defined $edgeTable;
}

# ... from text param
else {
$text = $params->remove("_DEFAULT") || $params->remove("text");
}

if ($doGraphFromTable) {
$text .= "}";
}

# expand
$text = Foswiki::Func::expandCommonVariables($text)
if Foswiki::Func::isTrue($params->{expand}, 0);

return _inlineError("Error: no dot code") unless defined $text;

$text = Encode::encode_utf8($text);

my $type = $params->{type} || "svg";
return _inlineError("unknown type '$type'")
return _inlineError("Error: unknown type '$type'")
unless $type =~ /^(svgz?|png|gif|jpe?g|pdf)$/;

my $renderer = $params->{renderer} || $params->{engine} || "dot";
return _inlineError("unknown renderer '$renderer'")
return _inlineError("Error: unknown renderer '$renderer'")
unless $renderer =~ /^(dot|neato|twopi|circle|s?fdp|patchwork)$/;

my $library = $params->{library};
Expand Down
98 changes: 98 additions & 0 deletions lib/Foswiki/Plugins/GraphvizPlugin/TableParser.pm
@@ -0,0 +1,98 @@
# Plugin for Foswiki - The Free and Open Source Wiki, http://foswiki.org/
#
# GraphvizPlugin is Copyright (C) 2015 Michael Daum http://michaeldaumconsulting.com
#
# 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 2
# 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, published at
# http://www.gnu.org/copyleft/gpl.html

package Foswiki::Plugins::GraphvizPlugin::TableParser;

use strict;
use warnings;

use Foswiki::Tables::Reader ();
use Data::Dump qw(dump);
our @ISA = ('Foswiki::Tables::Reader');

sub parse {
my ($this, $text, $meta) = @_;

$this->SUPER::parse($text, $meta);

return $this->{result};
}

sub getNodes {
my ($this, $tableIndex, $params) = @_;

my $table = $this->{result}[$tableIndex];

return unless $table;

$params->{nodecol} = 0 unless defined $params->{nodecol};

my @lines = ();

# generate nodes
my $index = 0;
foreach my $table ($table->getCellData) {
foreach my $row (@$table) {
$index++;
next if $index == 1;# skip header
my @attrs = ();
push @attrs, "$params->{nodeattrcol}]\"" if defined $params->{nodeattrcol};
my $attrs = '';
$attrs = "[".join(", ", @attrs)."]" if @attrs;
push @lines, " \"$row->[$params->{nodecol}]\" $attrs";
}
}

return join("\n", @lines);
}

sub getEdges {
my ($this, $tableIndex, $params) = @_;

my $table = $this->{result}[$tableIndex];

return unless $table;

$params->{sourcecol} = 0 unless defined $params->{sourcecol};
$params->{targetcol} = 1 unless defined $params->{targetcol};

my @lines = ();

# generate edges
my $index = 0;
foreach my $table ($table->getCellData) {
foreach my $row (@$table) {
$index++;
next if $index == 1;# skip header
my @attrs = ();
push @attrs, "xlabel=\"$row->[$params->{labelcol}]\"" if defined $params->{labelcol};
push @attrs, "$params->{edgeattrcol}]\"" if defined $params->{edgeattrcol};
my $attrs = '';
$attrs = "[".join(", ", @attrs)."]" if @attrs;
push @lines, " \"$row->[$params->{sourcecol}]\" -> \"$row->[$params->{targetcol}]\" $attrs";
}
}

return join("\n", @lines);
}

sub line {
my ( $this, $line ) = @_;

# ignore all non-table lines
}

1;

0 comments on commit 5cd0ece

Please sign in to comment.