diff options
author | Yorhel <git@yorhel.nl> | 2009-09-25 18:37:54 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2009-09-25 18:37:54 +0200 |
commit | 9ba57996ed6d7fe3dd0d0c1fe5e1b3827b8a2b8a (patch) | |
tree | 3c1de7f256c35b1caa833610d27b06923f783fe5 /lib | |
parent | fcf1e7f020b267c0efd6f7fd0a973baf682bd075 (diff) |
Use inline SVG for relation graphs
The graphs are now stored in the DB in SVG format, the static/rg/
directory can be removed (not used anymore).
SVG data is stored using the xml data type, so now I can say for
sure you'd need at least PostgreSQL 8.3.
This feature still needs some tweaking, though. Current state isn't
perfect.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Multi/RG.pm | 91 | ||||
-rw-r--r-- | lib/VNDB/DB/VN.pm | 4 | ||||
-rw-r--r-- | lib/VNDB/Handler/VNPage.pm | 23 |
3 files changed, 82 insertions, 36 deletions
diff --git a/lib/Multi/RG.pm b/lib/Multi/RG.pm index e427caf2..4ae0516e 100644 --- a/lib/Multi/RG.pm +++ b/lib/Multi/RG.pm @@ -9,6 +9,8 @@ use strict; use warnings; use POE 'Wheel::Run', 'Filter::Stream'; use Encode 'encode_utf8'; +use XML::Parser; +use XML::Writer; use Time::HiRes 'time'; @@ -17,14 +19,13 @@ sub spawn { POE::Session->create( package_states => [ $p => [qw| - _start shutdown check_rg creategraph getrel builddot buildgraph savegraph + _start shutdown check_rg creategraph getrel builddot savegraph finish proc_stdin proc_stdout proc_stderr proc_closed proc_child |], ], heap => { font => 'Arial', fsize => [ 9, 7, 10 ], # nodes, edges, node_title - imgdir => '/www/vndb/static/rg', moy => [qw| Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec |], dot => '/usr/bin/dot', check_delay => 3600, @@ -104,12 +105,10 @@ sub builddot { # num, res my $gv = qq|graph rgraph {\n|. - qq|\tratio = "compress"\n|. - qq|\tgraph [ bgcolor="#ffffff00" ]\n|. qq|\tnode [ fontname = "$_[HEAP]{font}", shape = "plaintext",|. - qq| fontsize = $_[HEAP]{fsize}[0], style = "setlinewidth(0.5)", fontcolor = "#cccccc", color = "#225588" ]\n|. + qq| fontsize = $_[HEAP]{fsize}[0], fontcolor = "#333333", color = "#111111" ]\n|. qq|\tedge [ labeldistance = 2.5, labelangle = -20, labeljust = 1, minlen = 2, dir = "both",|. - qq| fontname = $_[HEAP]{font}, fontsize = $_[HEAP]{fsize}[1], arrowsize = 0.7, color = "#225588", fontcolor = "#cccccc" ]\n|; + qq| fontname = $_[HEAP]{font}, fontsize = $_[HEAP]{fsize}[1], arrowsize = 0.7, color = "#111111", fontcolor = "#333333" ]\n|; # insert all nodes, ordered by release date for (sort { $a->{date} <=> $b->{date} } @$vns) { @@ -128,7 +127,7 @@ sub builddot { # num, res $gv .= sprintf qq|\tv%d [ URL = "/v%d", tooltip = "%s" label=<|. - q|<TABLE CELLSPACING="0" CELLPADDING="1" BORDER="0" CELLBORDER="1" BGCOLOR="#00000033">|. + q|<TABLE CELLSPACING="0" CELLPADDING="1" BORDER="0" CELLBORDER="1" BGCOLOR="#222222">|. q|<TR><TD COLSPAN="2" ALIGN="CENTER" CELLPADDING="2"><FONT POINT-SIZE="%d"> %s </FONT></TD></TR>|. q|<TR><TD> %s </TD><TD> %s </TD></TR>|. qq|</TABLE>> ]\n|, @@ -161,49 +160,79 @@ sub builddot { # num, res $gv .= "}\n"; - # get ID - $_[KERNEL]->post(pg => query => 'INSERT INTO relgraph (cmap) VALUES (\'\') RETURNING id', undef, 'buildgraph', \$gv); -} - - -sub buildgraph { # num, res, \$gv - $_[HEAP]{gid} = $_[ARG1][0]{id}; - $_[HEAP]{graph} = sprintf('%s/%02d/%d.png', $_[HEAP]{imgdir}, $_[ARG1][0]{id} % 100, $_[ARG1][0]{id}); - $_[HEAP]{cmap} = ''; - - # roughly equivalent to: - # cat layout.txt | dot -Tpng -o graph.png -Tcmapx + # Pass our dot file to graphviz + $_[HEAP]{svg} = ''; $_[HEAP]{proc} = POE::Wheel::Run->new( Program => $_[HEAP]{dot}, - ProgramArgs => [ '-Tpng', '-o', $_[HEAP]{graph}, '-Tcmapx' ], + ProgramArgs => [ '-Tsvg' ], StdioFilter => POE::Filter::Stream->new(), StdinEvent => 'proc_stdin', StdoutEvent => 'proc_stdout', StderrEvent => 'proc_stderr', CloseEvent => 'proc_closed', ); - $_[HEAP]{proc}->put(${$_[ARG2]}); + $_[HEAP]{proc}->put($gv); } sub savegraph { - my $vids = join ',', sort map int, keys %{$_[HEAP]{nodes}}; + # Before saving the SVG output, we'll modify it a little: + # - Remove comments + # - Add svg: prefix to all tags + # - Remove xmlns declarations (this is set in the html) + # - Remove <title> elements (unused) + # - Remove id attributes (unused) + # - Remove first <polygon> element (emulates the background color) + # - Replace stroke and fill attributes with classes (so that coloring is done in CSS) + my $svg = ''; + my $w = XML::Writer->new(OUTPUT => \$svg); + my $p = XML::Parser->new; + $p->setHandlers( + Start => sub { + my($expat, $el, %attr) = @_; + return if $el eq 'title' || $expat->in_element('title'); + return if $el eq 'polygon' && $expat->depth == 2; + + $attr{class} = 'border' if $attr{stroke} && $attr{stroke} eq '#111111'; + $attr{class} = 'nodebg' if $attr{fill} && $attr{fill} eq '#222222'; + + delete @attr{qw|stroke fill id xmlns xmlns:xlink|}; + $el eq 'path' || $el eq 'polygon' + ? $w->emptyTag("svg:$el", %attr) + : $w->startTag("svg:$el", %attr); + }, + End => sub { + my($expat, $el) = @_; + return if $el eq 'title' || $expat->in_element('title'); + return if $el eq 'polygon' && $expat->depth == 2; + $w->endTag("svg:$el") if $el ne 'path' && $el ne 'polygon'; + }, + Char => sub { + my($expat, $str) = @_; + return if $expat->in_element('title'); + $w->characters($str) if $str !~ /^[\s\t\r\n]*$/s; + } + ); + $p->parsestring($_[HEAP]{svg}); + $w->end(); - # chmod graph - chmod 0666, $_[HEAP]{graph}; + # save the processed SVG in the database and fetch graph ID + $_[KERNEL]->post(pg => query => 'INSERT INTO vn_graphs (svg) VALUES (?) RETURNING id', [ $svg ], 'finish'); +} - # save the image map in the database - $_[KERNEL]->post(pg => do => 'UPDATE relgraph SET cmap = ? WHERE id = ?', - [ "<!-- V:$vids -->\n$_[HEAP]{cmap}", $_[HEAP]{gid} ]); + +sub finish { # num, res + my $id = $_[ARG1][0]{id}; + my $vids = join ',', sort map int, keys %{$_[HEAP]{nodes}}; # update the VN table - $_[KERNEL]->post(pg => do => "UPDATE vn SET rgraph = ? WHERE id IN($vids)", [ $_[HEAP]{gid} ]); + $_[KERNEL]->post(pg => do => "UPDATE vn SET rgraph = ? WHERE id IN($vids)", [ $id ]); # log - $_[KERNEL]->call(core => log => 'Generated relation graph in %.2fs, V: %s', time-$_[HEAP]{start}, $vids); + $_[KERNEL]->call(core => log => 'Generated VN relation graph #%d in %.2fs, V: %s', $id, time-$_[HEAP]{start}, $vids); # clean up - delete @{$_[HEAP]}{qw| start vid nodes rels gid graph cmap proc |}; + delete @{$_[HEAP]}{qw| start vid nodes rels svg proc |}; # check for more things to do $_[KERNEL]->yield('check_rg'); @@ -216,7 +245,7 @@ sub proc_stdin { $_[HEAP]{proc}->shutdown_stdin; } sub proc_stdout { - $_[HEAP]{cmap} .= $_[ARG0]; + $_[HEAP]{svg} .= $_[ARG0]; } sub proc_stderr { $_[KERNEL]->call(core => log => 'GraphViz STDERR: %s', $_[ARG0]); diff --git a/lib/VNDB/DB/VN.pm b/lib/VNDB/DB/VN.pm index b3603985..c6499350 100644 --- a/lib/VNDB/DB/VN.pm +++ b/lib/VNDB/DB/VN.pm @@ -76,7 +76,7 @@ sub dbVNGet { $o{what} =~ /changes/ ? 'JOIN users u ON u.id = c.requester' : (), $o{what} =~ /relgraph/ ? - 'JOIN relgraph rg ON rg.id = v.rgraph' : (), + 'JOIN vn_graphs vg ON vg.id = v.rgraph' : (), ); my $tag_ids = $o{tags_include} && join ',', @{$o{tags_include}[1]}; @@ -86,7 +86,7 @@ sub dbVNGet { qw|vr.alias vr.image vr.img_nsfw vr.length vr.desc vr.l_wp vr.l_encubed vr.l_renai vr.l_vnn| ) : (), $o{what} =~ /changes/ ? ( qw|c.requester c.comments v.latest u.username c.rev c.causedby|, q|extract('epoch' from c.added) as added|) : (), - $o{what} =~ /relgraph/ ? 'rg.cmap' : (), + $o{what} =~ /relgraph/ ? 'vg.svg' : (), $o{what} =~ /ranking/ ? '(SELECT COUNT(*)+1 FROM vn iv WHERE iv.hidden = false AND iv.c_popularity > v.c_popularity) AS ranking' : (), $tag_ids ? qq|(SELECT AVG(tvb.rating) FROM tags_vn_bayesian tvb WHERE tvb.tag IN($tag_ids) AND tvb.vid = v.id AND spoiler <= $o{tags_include}[0] GROUP BY tvb.vid) AS tagscore| : (), diff --git a/lib/VNDB/Handler/VNPage.pm b/lib/VNDB/Handler/VNPage.pm index 361963a8..5e0fc9dc 100644 --- a/lib/VNDB/Handler/VNPage.pm +++ b/lib/VNDB/Handler/VNPage.pm @@ -23,20 +23,37 @@ sub rand { sub rg { my($self, $vid) = @_; + # TODO: browser detection + notice, this trick gives some ugly results in IE + my $v = $self->dbVNGet(id => $vid, what => 'relgraph')->[0]; return 404 if !$v->{id} || !$v->{rgraph}; my $title = mt '_vnrg_title', $v->{title}; + $self->resHeader('Content-Type' => 'application/xhtml+xml'); + + # This is a REALLY ugly hack, need find a proper solution in YAWF + no warnings 'redefine'; + my $sub = \&YAWF::XML::html; + *YAWF::XML::html = sub () { + lit q|<!DOCTYPE html PUBLIC + "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" + "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">|; + tag 'html', + xmlns => "http://www.w3.org/1999/xhtml", + 'xmlns:svg' => 'http://www.w3.org/2000/svg', + 'xmlns:xlink' => 'http://www.w3.org/1999/xlink'; + }; $self->htmlHeader(title => $title); + *YAWF::XML::html = $sub; + $self->htmlMainTabs('v', $v, 'rg'); div class => 'mainbox'; h1 $title; - lit $v->{cmap}; p class => 'center'; - img src => sprintf('%s/rg/%02d/%d.png', $self->{url_static}, $v->{rgraph}%100, $v->{rgraph}), - alt => $title, usemap => '#rgraph'; + lit $v->{svg}; end; end; + $self->htmlFooter; } |