swish/commit

New upstream files

authorJan Wielemaker
Fri Aug 19 15:40:00 2016 +0200
committerJan Wielemaker
Fri Aug 19 15:40:00 2016 +0200
commit14b6ce7103f206f18414c4d796f26ec969163b93
tree506758bcec9352d21395438e330190b64f596f7e
parente8cde04e641642fd7879ce71f4a5980e9ae320c2
Diff style: patch stat
diff --git a/client/README.md b/client/README.md
index e769a44..f666370 100644
--- a/client/README.md
+++ b/client/README.md
@@ -41,3 +41,16 @@ The Pengines infrastructure is  designed  to   make  JavaScript  talk to
 Prolog servers. The file   [sin-table.html](sin-table.html)  illustrates
 this.
 
+There an __NPM__ [package](https://www.npmjs.com/package/pengines)
+
+## Extracting results using Java
+
+Check out [JavaPengines](https://github.com/Anniepoo/JavaPengine)
+
+## Extracting results using Ruby
+
+Check out [RubyPengines](https://github.com/simularity/RubyPengine)
+
+---
+If you write or find another client, please make a pull request for this
+page!
diff --git a/client/swish-ask.sh b/client/swish-ask.sh
index 5d19b6a..204ce6e 100755
--- a/client/swish-ask.sh
+++ b/client/swish-ask.sh
@@ -7,6 +7,7 @@
 
 server=${SWISH_SERVER-http://localhost:3020}
 srctext=
+curlarg=
 format=${SWISH_FORMAT-rdf}
 program=$(basename $0)
 
@@ -57,6 +58,10 @@ while [ $done = false ]; do
 	    esac
             shift
             ;;
+	https://*.pl|http://*.pl)
+	    curlarg+=" -d src_url=$1"
+	    shift
+	    ;;
 	*.pl)
             script=$(echo $1 | sed 's/.*=//')
 	    srctext+=":- include('$script'). "
@@ -84,4 +89,5 @@ curl -s \
      -d format=csv \
      -d chunk=10 \
      -d solutions=all \
+     $curlarg \
      $server/pengine/create
diff --git a/examples/Rdataframe.swinb b/examples/Rdataframe.swinb
new file mode 100644
index 0000000..5a3a33f
--- /dev/null
+++ b/examples/Rdataframe.swinb
@@ -0,0 +1,73 @@
+<div class="notebook">
+
+<div class="nb-cell markdown">
+# Dealing with R data frames
+
+Data frames are a central concept in R.  A data frame is a 2-dimensional matrix with optional
+column and row names.  The data is (normally) column-oriented.  This differs from the row-oriented
+view on data in Prolog, for example as a set of solutions for a predicate.  The library(r_data) provides predicates for creating and accessing R data frames.
+
+## Creating a data frame from solutions
+
+Below we define a relation sin/3 based on the sine function and turn the resulting solutions into a data frame called `df`.  The subsequent examples plot the relation using _ggplot2_ and provide some timing for exchanging large datasets.
+</div>
+
+<div class="nb-cell program">
+% Y is sin(X) for X in 0..Max
+sin(Max, X, Y) :-
+    between(0, Max, X),
+    Y is sin(X*pi/180).
+</div>
+
+<div class="nb-cell query">
+r_data_frame(df, [x=X,y=Y], sin(10, X, Y)),
+&lt;- df.
+</div>
+
+<div class="nb-cell query">
+time(r_data_frame(df, [x=X,y=Y], sin(360, X, Y))),
+time(&lt;- library("ggplot2")),
+time(&lt;- ggplot(data=df, aes(x=x, y=y)) + geom_line()).
+</div>
+
+<div class="nb-cell query">
+time(r_data_frame(df, [x=X,y=Y], sin(1 000 000, X, Y))).
+</div>
+
+<div class="nb-cell query">
+time(r_data_frame(df, [x=X,y=Y], sin(1 000 000, X, Y))),
+time(r_data_frame_to_dicts(df, _Dicts)).
+</div>
+
+<div class="nb-cell markdown">
+## Importing data frames to Prolog
+
+The predicates r_data_frame_to_dicts/2 and r_data_frame_to_rows/3 translate the column-oriented data frame to a list of row oriented dicts or terms.  We use the example on the predefined R `mtcars` data frame.
+</div>
+
+<div class="nb-cell program">
+:- use_rendering(table).
+</div>
+
+<div class="nb-cell query" data-tabled="true">
+r_data_frame_to_dicts(mtcars, Dicts).
+</div>
+
+<div class="nb-cell query" data-tabled="true">
+r_data_frame_rownames(mtcars, Rows).
+</div>
+
+<div class="nb-cell markdown">
+## Documentation
+
+Below is the reference documentation for the predicates in library(r_data).
+
+  - [[r_data_frame/3]]
+  - [[r_data_frame_from_rows/2]]
+  - [[r_data_frame_to_dicts/2]]
+  - [[r_data_frame_to_rows/3]]
+  - [[r_data_frame_colnames/2]]
+  - [[r_data_frame_rownames/2]]
+</div>
+
+</div>
diff --git a/examples/Rdownload.swinb b/examples/Rdownload.swinb
new file mode 100644
index 0000000..215149c
--- /dev/null
+++ b/examples/Rdownload.swinb
@@ -0,0 +1,56 @@
+<div class="notebook">
+
+<div class="nb-cell markdown">
+# Downloading (graphics) files
+
+The SWISH R interface defines the predicates below for downloading files.  This can be combined with the normal R device manipulation for downloading images.
+
+  - [[r_download/0]]
+  - [[r_download/1]]
+  
+## Download a simple plot
+
+The example illustrates the options using the sine function as defined below.
+</div>
+
+<div class="nb-cell program">
+% Y is sin(X) for X in 0..Max
+sin(Max, X, Y) :-
+    between(0, Max, X),
+    Y is sin(X*pi/180).
+</div>
+
+<div class="nb-cell markdown">
+First, we create an [R dataframe](example/Rdataframe.swinb), load the "ggplot2" library and show the inline SVG.
+</div>
+
+<div class="nb-cell query">
+r_data_frame(df, [x=X,y=Y], sin(360, X, Y)),
+&lt;- library("ggplot2"),
+&lt;- ggplot(data=df, aes(x=x, y=y)) + geom_line().
+</div>
+
+<div class="nb-cell markdown">
+In the next examples we use r_download/0.  This displays the graphics inline, but also provides a download button that allows you to download the SVG to your computer. 
+</div>
+
+<div class="nb-cell query">
+r_data_frame(df, [x=X,y=Y], sin(360, X, Y)),
+&lt;- library("ggplot2"),
+&lt;- ggplot(data=df, aes(x=x, y=y)) + geom_line(),
+r_download.
+</div>
+
+<div class="nb-cell markdown">
+And finally, we save the graphics to a device (in this example PDF) that we create explicitly.  Note that r_download/0 calls `graphics.off()` before trying to download the generated files.
+</div>
+
+<div class="nb-cell query">
+&lt;- pdf("sine.pdf"),
+r_data_frame(df, [x=X,y=Y], sin(360, X, Y)),
+&lt;- library("ggplot2"),
+&lt;- ggplot(data=df, aes(x=x, y=y)) + geom_line(),
+r_download.
+</div>
+
+</div>
diff --git a/examples/Rserve.swinb b/examples/Rserve.swinb
new file mode 100644
index 0000000..3973352
--- /dev/null
+++ b/examples/Rserve.swinb
@@ -0,0 +1,169 @@
+<div class="notebook">
+
+<div class="nb-cell markdown">
+# R/SWISH demos
+
+R for SWISH provides an interface to [Rserve](https://rforge.net/Rserve/) where the access predicates are inspired by [Real](http://stoics.org.uk/~nicos/sware/real/) by [Nicos Angelopoulos](http://stoics.org.uk/~nicos/).  The interface defines two predicates and a [quasi quotation](http://www.swi-prolog.org/pldoc/man?section=quasiquotations):
+
+  $ Var &lt;- Expression :
+  Evaluate _Expression_ and bind the result to _Var_.  _Var_ can both be a Prolog
+  variable, which causes the R expression to be converted into Prolog or an
+  R variable (atom), which causes the result to be bound to an R variable.
+  $ &lt;- Expression :
+  Evaluate _Expression_ and display the resulting R console output.
+  $ {|r(Arg ...)||R-code|} :
+  In this quasi quotation, `r` specifies the syntax.  `Arg ...` is a list of
+  Prolog terms that are translated to R and bound to an R variable with the
+  same name.  `R-code` is arbitrary R code.  This may contain any characters
+  except for the `|}` termination sequence.
+
+Above, *Expression* is either a Prolog term or a quasi quotation.  If it is a
+Prolog term, it is translated into R using these rules:
+
+  - An identifier (atom with chars valid for R identifiers) are emitted verbatim.
+  - The Prolog atoms `true` and `false` are translated into the R expressions
+    =TRUE= and =FALSE=.
+  - A Prolog string is translated into an R string.  Note that R strings may be
+    written as `"string"` or `'string'`, while in Prolog `'string'` is an atom,
+    which is treated as an identifier.  Thus, use double quoted Prolog strings
+    to create a string in R.
+  - Numbers are translated into R numbers.
+  - A term =|functor(Arg ...)|= is translated into an R-function call.
+  - The R operators are supported:  =|+, -, *, /, mod, '%%', ^,
+	&gt;=, &gt;, ==, &lt;, &lt;=, =&lt;, \=, '!=', :, &lt;-|=
+  - =|A$B|= and =|A[I]|= are supported
+  - Goal expansion is provided to turn =|a.b|= and =|a.b()|= into valid syntax.
+</div>
+
+<div class="nb-cell markdown">
+## Exchange data between Prolog and R
+
+Data may be transferred using the assignment operator `Target &lt;- Source`, where `Source` is the Prolog representation of an R expression.  `Target` is either a Prolog variable, transferring the value of an R expression to Prolog, or an R variable or expression, transferring a Prolog expression to R.
+
+First we illustrate exchanging R data to Prolog.  Note that the expression can both be the Prolog representation of an R expression or R source represented as a quasi quotation.
+</div>
+
+<div class="nb-cell query">
+A &lt;- 1:10.
+</div>
+
+<div class="nb-cell query">
+A &lt;- {|r||1:10|}.
+</div>
+
+<div class="nb-cell markdown">
+Next, we transfer data from Prolog to R.  We compute the mean and return it to show that `a &lt;- List` actuall binds the R variable `a`.
+</div>
+
+<div class="nb-cell query">
+numlist(0, 10, List),
+a &lt;- List,
+Mean &lt;- mean(a).
+</div>
+
+<div class="nb-cell markdown">
+The left side of the assignment can be any valid R term as illustrated below, where we assign the column names of a data frame.  See [Data frame](example/Rdataframe.swinb) for more high level operations on data frames.
+</div>
+
+<div class="nb-cell query">
+df &lt;- data.frame([100,200,300]),
+colnames(df) &lt;- ["hundreds"],
+&lt;- df.
+</div>
+
+<div class="nb-cell markdown">
+## Simple plot examples
+
+First, we make some trivial plots using an R vector, Prolog list and a _quasi quotation_.
+</div>
+
+<div class="nb-cell query">
+&lt;- plot(c(1,2,3)).
+</div>
+
+<div class="nb-cell query">
+&lt;- plot([1,2,3,4]).
+</div>
+
+<div class="nb-cell query">
+{|r||plot(c(1,2,3))|}.
+</div>
+
+<div class="nb-cell query">
+numlist(1, 25, Data),
+{|r(Data)||plot(Data)|}.
+</div>
+
+<div class="nb-cell markdown">
+## Performance tests for transferring data
+
+Below we compare computing the mean from a list of 1M integers through R as well as natively in Prolog.  It turns out R does the job faster on a large number of integers than Prolog.  Why?  Once in R, the array is a simple C array of integers, causing a blindly fast addition.  The Prolog counterpart adds the numbers one-by-one and does rigid overflow checking and handling.
+</div>
+
+<div class="nb-cell query">
+numlist(1, 1 000 000, _L),
+time(A &lt;- mean(_L)).
+</div>
+
+<div class="nb-cell query">
+numlist(1, 1 000 000, _L),
+time(sum_list(_L, Sum)).
+</div>
+
+<div class="nb-cell markdown">
+## Show error handling
+</div>
+
+<div class="nb-cell query">
+A &lt;- {|r||a b|}.
+</div>
+
+<div class="nb-cell markdown">
+## Using ggplot2 to render plots
+
+The library("ggplot2") can be used for rendering plots.  Unlike native plot() however, the call should be made with &lt;-/1 because ggplot relies on console output.  We first give the example using an R quasi quotation, followed by using a Prolog term.  Note that
+
+  - Quasi quotations can be used to copy/paste R code into your Prolog
+    program without changing it (except when it contains =||}|=).
+  - Prolog code may need minor modifications.  In the example, =|I(.5)|=
+    must be changed to `'I'(0.5)` because Prolog floats cannot start with
+    a =|.|= and functors (used as function symbols) cannot start with a
+    capital.  The Prolog approach however
+    - Is better portable.
+    - Profits from Prolog syntax checking.
+    - Makes it easier to compose R expressions from smaller elements.
+</div>
+
+<div class="nb-cell program">
+:- &lt;- library("ggplot2").
+</div>
+
+<div class="nb-cell query">
+&lt;- {|r||qplot(mpg, data=mtcars, geom="density", fill=gear, alpha=I(.5), main="Distribution of Gas Milage", xlab="Miles Per Gallon", ylab="Density")|}.
+</div>
+
+<div class="nb-cell query">
+&lt;- qplot(mpg, data=mtcars, geom="density", fill=gear, alpha='I'(0.5), main="Distribution of Gas Milage", xlab="Miles Per Gallon", ylab="Density").
+</div>
+
+<div class="nb-cell markdown">
+## Query and handle data frames
+
+This shows how to print a data frame and how to get R data into Prolog.  Note that the interface merely fetches the matrix as a nested list of columns.  The library(r/r_data) provides utilites for creating and fetching R data frames from Prolog.  This [notebook](example/Rdataframe.swinb) illustrates the library.
+</div>
+
+<div class="nb-cell program" data-background="true">
+:- use_rendering(table).
+</div>
+
+<div class="nb-cell query">
+&lt;- mtcars.
+</div>
+
+<div class="nb-cell query">
+MtCars &lt;- mtcars,
+Cols &lt;- colnames(mtcars),
+Rows &lt;- rownames(mtcars).
+</div>
+
+</div>
diff --git a/examples/swish_tutorials.swinb b/examples/swish_tutorials.swinb
index e118ec1..6678834 100644
--- a/examples/swish_tutorials.swinb
+++ b/examples/swish_tutorials.swinb
@@ -10,4 +10,20 @@ This notebook provides an overview of tutorials about using SWISH.
   - [Access the SWISH interface from Prolog](example/jquery.swinb)
 </div>
 
+<div class="nb-cell markdown">
+## Embedded R support
+
+The [R project](https://www.r-project.org/) provides statistical computing and data vizualization.  SWISH can access R through [Rserve](https://rforge.net/Rserve/).  The *prototype* client for Rserve is available as the _pack_ [rserve_client](http://www.swi-prolog.org/pack/list?p=rserve_client).  The GitHub repository [rserve-sandbox](https://github.com/JanWielemaker/rserve-sandbox) provides the matching Rserve server as a [Docker](https://www.docker.com/) specification.
+
+The notebooks below explain the basics of using R from SWISH.  You can test whether R is available from this server by running the query below.
+
+  - [Basic access to R from SWISH](example/Rserve.swinb)
+  - [Exchanging R data frames](example/Rdataframe.swinb)
+  - [Downloading (graphics) files](example/Rdownload.swinb)
+</div>
+
+<div class="nb-cell query">
+&lt;- 'R.Version'().
+</div>
+
 </div>
diff --git a/lib/swish/download.pl b/lib/swish/download.pl
new file mode 100644
index 0000000..ff87a55
--- /dev/null
+++ b/lib/swish/download.pl
@@ -0,0 +1,76 @@
+/*  Part of SWI-Prolog
+
+    Author:        Jan Wielemaker
+    E-mail:        J.Wielemaker@cs.vu.nl
+    WWW:           http://www.swi-prolog.org
+    Copyright (C): 2016, VU University Amsterdam
+
+    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.
+
+    You should have received a copy of the GNU General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+    As a special exception, if you link this library with other files,
+    compiled with a Free Software compiler, to produce an executable, this
+    library does not by itself cause the resulting executable to be covered
+    by the GNU General Public License. This exception does not however
+    invalidate any other reasons why the executable file might be covered by
+    the GNU General Public License.
+*/
+
+:- module(download,
+	  [ download_button/2			% +Data, +Options
+	  ]).
+:- use_module(library(pengines)).
+:- use_module(library(option)).
+:- use_module(library(http/mimetype)).
+
+/** <module> Provide data for downloading
+*/
+
+%%	download_button(+Data:string, +Options)
+%
+%	Emit a button in the SWISH   output window for downloading Data.
+%	The provided data is associated with   the button and (thus) not
+%	stored on the server.  A small tests indicates this works fairly
+%	well up to several tens of megabytes.
+%
+%	Options:
+%
+%	  - name(+Name)
+%	  (Base-)Name of the file created (default: 'swish-download')
+%	  - ext(+Ext)
+%	  Extension for the file (default: 'dat')
+%	  - encoding(+Enc)
+%	  Encoding to use.  One of `utf8` or `octet`.  default is `utf8`
+%
+%	@see https://en.wikipedia.org/wiki/Data_URI_scheme
+
+download_button(Data, Options) :-
+	option(filename(FileName), Options, 'swish-download.dat'),
+	file_mime_type(FileName, Major/Minor),
+	atomics_to_string([Major, Minor], /, ContentType),
+	option(encoding(Enc), Options, utf8),
+	encode_data(Enc, Data, CharSet, EncData),
+	pengine_output(
+	    json{action:downloadButton,
+		 content_type:ContentType,
+		 data:EncData,
+		 filename:FileName,
+		 charset:CharSet
+		}).
+
+encode_data(utf8,  Data, "charset=UTF-8", Data).
+encode_data(octet, Data, "base64", Data64) :-
+	string_codes(Data, Codes),
+	phrase(base64(Codes), Codes64),
+	string_codes(Data64, Codes64).
diff --git a/lib/swish/r_swish.pl b/lib/swish/r_swish.pl
new file mode 100644
index 0000000..1ec8e59
--- /dev/null
+++ b/lib/swish/r_swish.pl
@@ -0,0 +1,231 @@
+/*  Part of SWI-Prolog
+
+    Author:        Jan Wielemaker
+    E-mail:        J.Wielemaker@cs.vu.nl
+    WWW:           http://www.swi-prolog.org
+    Copyright (C): 2016, VU University Amsterdam
+
+    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.
+
+    You should have received a copy of the GNU General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+    As a special exception, if you link this library with other files,
+    compiled with a Free Software compiler, to produce an executable, this
+    library does not by itself cause the resulting executable to be covered
+    by the GNU General Public License. This exception does not however
+    invalidate any other reasons why the executable file might be covered by
+    the GNU General Public License.
+*/
+
+:- module(r_swish,
+	  [ r_download/0,			% Download all
+	    r_download/1			% +File
+	  ]).
+:- use_module(library(pengines)).
+:- use_module(library(debug)).
+:- use_module(library(error)).
+:- use_module(library(apply)).
+:- use_module(library(http/html_write)).
+:- use_module(library(http/js_write)).
+
+% We publish to the user module to avoid autoloading `real'.
+:- use_module(user:library(r/r_call)).
+:- use_module(user:library(r/r_data)).
+
+:- use_module(download).
+
+/** <Module> Bind Rserve to SWISH
+
+The user must provide the  file  search   path  =rserve=  to local the R
+connection library.
+*/
+
+:- multifile
+	r_call:r_console/2,
+	r_call:r_display_images/1.
+
+%%	r_call:r_console(+Stream, ?Data)
+%
+%	Relay Rserve captured output to SWISH using writeln.
+
+r_call:r_console(stdout, []) :- !.
+r_call:r_console(stdout, Strings) :-
+	atomics_to_string(Strings, "\n", String),
+	send_html(pre(class(['R', console]), String)).
+
+send_html(HTML) :-
+	phrase(html(HTML), Tokens),
+	with_output_to(string(HTMlString), print_html(Tokens)),
+	pengine_output(HTMlString).
+
+%%	r_call:r_display_images(+Images)
+%
+%	Relay   received   images   to   the     SWISH   console   using
+%	pengine_output/1.
+
+r_call:r_display_images(Images) :-
+	svg_html(Images, HTMlString),
+	pengine_output(HTMlString).
+
+%%	svg_html(+Images, -HTMlString) is det.
+%
+%	Turn a list of SVG images into an HTML string.
+
+svg_html(Images, HTMlString) :-
+	phrase(svg_html(Images), Tokens),
+	with_output_to(string(HTMlString), print_html(Tokens)).
+
+svg_html(Images) -->
+	html(div(class('Rplots'), \rplots(Images))).
+
+rplots([]) --> [].
+rplots([H|T]) -->
+	html(div(class(['reactive-size', 'R', svg]), \plot(H, []))),
+	rplots(T).
+
+
+plot(svg(SVG), _Options) --> !,
+	html(\[SVG]),
+	pan_zoom,
+	"".
+plot(Term, _Options) --> !,
+	{ domain_error(image, Term) }.
+
+%%	pan_zoom
+%
+%	Add pan and soom behaviour to embedded SVG.  This function also
+%	renames the `id` attribute and their references.
+%
+%	@bug	We need a generic way to fix all references to the ID.
+%		Is there a list of such attributes?
+%	@bug	Instead of `"use"`, we should use `"[xlink\\:href]"`,
+%		but this does not seem to work!?
+%	@bug	When generalised, this could move into runner.js.
+
+pan_zoom -->
+	html(\js_script({|javascript||
+var svg  = node.node().find("svg");
+var data = { w0: svg.width(),
+	     h0: svg.height()
+	   };
+var pan;
+
+function fixIDs(node, prefix1) {
+  var i=0;
+  node.each(function() {
+    var prefix = prefix1+(i++)+"_";
+    var img = $(this);
+    var hprefix = "#"+prefix;
+    var re = /(url\()#([^)]*)(\))/;
+
+    img.find("[id]").each(function() {
+      var elem = $(this);
+      elem.attr("id", prefix+elem.attr("id"));
+    });
+    img.find("use").each(function() {
+      var elem = $(this);
+      var r = elem.attr("xlink:href");
+      if ( r.charAt(0) == "#" )
+	elem.attr("xlink:href", hprefix+r.slice(1));
+    });
+    img.find("[clip-path]").each(function() {
+      var elem = $(this);
+      var r = elem.attr("clip-path").match(re);
+      if ( r.length == 4 )
+	elem.attr("clip-path", r[1]+hprefix+r[2]+r[3]);
+    });
+  });
+}
+
+fixIDs(svg, "N"+node.unique_id()+"_");
+
+function updateSize() {
+  var w = svg.closest("div.Rplots").innerWidth();
+  console.log(data.w0, w);
+
+  function reactive() {
+    if ( !data.reactive ) {
+      var div = svg.closest("div.reactive-size");
+      data.reactive = true;
+      div.on("reactive-resize", updateSize);
+    }
+  }
+
+  reactive();
+  w = Math.max(w*0.95, 100);
+  if ( w < data.w0 ) {
+    svg.width(w);
+    svg.height(w = Math.max(w*data.h0/data.w0, w/4));
+    if ( pan ) {
+      pan.resize();
+      pan.fit();
+      pan.center();
+    }
+  }
+}
+
+require(["svg-pan-zoom"], function(svgPanZoom) {
+  updateSize()
+  pan = svgPanZoom(svg[0], {
+    maxZoom: 50
+  });
+});
+		      |})).
+
+
+%%	r_download
+%
+%	Provide download buttons for all created  files. First calls the
+%	R function `graphics.off()` to close all graphics devices.
+
+r_download :-
+	nb_current('R', _), !,
+	<- graphics.off(),
+	Files <- list.files(),
+	maplist(r_download, Files).
+r_download.
+
+%%	r_download(File)
+%
+%	Provide a download button for the indicates file.
+
+r_download(File) :-
+	nb_current('R', _), !,
+	catch(r_read_file($, File, Content), E,
+	      r_error(E, File)),
+	(   debugging(r(file))
+	->  string_length(Content, Len),
+	    debug(r(file), 'Got ~D bytes from ~p', [Len, File])
+	;   true
+	),
+	file_name_extension(_Name, Ext, File),
+	download_encoding(Ext, Enc),
+	download_button(Content,
+			[ filename(File),
+			  encoding(Enc)
+			]).
+r_download(File) :-
+	existence_error(r_file, File).
+
+r_error(error(r_error(70),_), File) :- !,
+	existence_error(r_file, File).
+r_error(Error, _) :- throw(Error).
+
+download_encoding(svg, utf8) :- !.
+download_encoding(csv, utf8) :- !.
+download_encoding(_,   octet).
+
+:- multifile sandbox:safe_primitive/1.
+
+sandbox:safe_primitive(r_swish:r_download).
+sandbox:safe_primitive(r_swish:r_download(_)).
diff --git a/lib/swish/render/graphviz.pl b/lib/swish/render/graphviz.pl
index 74d4329..1a93fcc 100644
--- a/lib/swish/render/graphviz.pl
+++ b/lib/swish/render/graphviz.pl
@@ -28,7 +28,8 @@
 */
 
 :- module(swish_render_graphviz,
-	  [ term_rendering//3			% +Term, +Vars, +Options
+	  [ term_rendering//3,			% +Term, +Vars, +Options
+	    svg//2				% +String, +Options
 	  ]).
 :- use_module(library(http/html_write)).
 :- use_module(library(http/js_write)).
@@ -147,8 +148,19 @@ render_dot(DOTString, Program, _Options) -->	% <svg> rendering
 	->  html(div([ class(['render-graphviz', 'reactive-size']),
 		       'data-render'('As Graphviz graph')
 		     ],
-		     [ \[SVG],
-		       \js_script({|javascript||
+		     \svg(SVG, [])))
+	;   html(div(style('color:red;'),
+		     [ '~w'-[Program], ': ', Error]))
+	).
+
+%%	svg(+SVG:string, +Options:list)//
+%
+%	Include SVG as pan/zoom image. Must be  embedded in a <div> with
+%	class 'reactive-size'.
+
+svg(SVG, _Options) -->
+	html([ \[SVG],
+	       \js_script({|javascript||
 (function() {
    if ( $.ajaxScript ) {
      var div  = $.ajaxScript.parent();
@@ -191,10 +203,8 @@ render_dot(DOTString, Program, _Options) -->	% <svg> rendering
    }
  })();
 		      |})
-		     ]))
-	;   html(div(style('color:red;'),
-		     [ '~w'-[Program], ': ', Error]))
-	).
+	     ]).
+
 
 %%	data_to_graphviz_string(+Data, -DOTString, -Program) is semidet.
 %
diff --git a/lib/swish/swish_debug.pl b/lib/swish/swish_debug.pl
index 0e80797..978ce93 100644
--- a/lib/swish/swish_debug.pl
+++ b/lib/swish/swish_debug.pl
@@ -204,9 +204,17 @@ stats_ring(year,   5).
 
 swish_stats(Name, Ring, Stats) :-
 	thread_self(Me),
-	thread_send_message(Name, Me-get_stats(Ring)),
+	catch(thread_send_message(Name, Me-get_stats(Ring)), E,
+	      stats_died(Name, E)),
 	thread_get_message(get_stats(Ring, Stats)).
 
+stats_died(Alias, E) :-
+	print_message(error, E),
+	thread_join(Alias, Status),
+	print_message(error, swish_stats(died, Status)),
+	start_swish_stat_collector,
+	fail.
+
 stat_collect(Dims, Interval) :-
 	new_sliding_stats(Dims, SlidingStat),
 	get_time(Now),