2

Closed

How to display an R Graph in a .NET Winform

description

I include an example project for Visual Studio 2010 that shows how one can embed an R Graphi in a .NET Winform. It uses Dino Esposito's Hook framework to catch the Window after it is activated, and make it a child of an embedded panel (or other windows control).

The main issue with this approach is not related to embedding, but with the application hanging long time once a graph has been generated. This also occurs when a graph is not embedded.
The application has two three menu items that demonstrated this and another problem when closing the engine.

Dieter Menne
Menne Biomed Consulting Tübingen

file attachments

Closed Jan 27 at 9:31 PM by jperraud
Closing this issue; it is worth revisiting for documentation purposes, contributions in comments

comments

dmenne wrote Apr 14, 2011 at 4:29 PM

I have added a new version of the Demo Program using 5fe02ee8e9ff. It has two menu items that shows that the dispose mechanism still does not work. Note especially that one exceptions only turn up after the error was correctly caught ones.

Please test your modified versions either with my demo program, or even better, create a test suite.

This library is very useful, but I suggest that it should not be marked "stable", because very basic things do not work yet.

Dieter Menne

kos59125 wrote Apr 15, 2011 at 10:38 AM

I committed a new version.
I fixed terminating operation.

That the exception is thrown when the second click on the last menu is performed is not unchanged, but this is correct behavior. This is because the engine named "RDotNewNet" is not correctly closed. In such senarios creating REngine instances with one name, programmer should call Close or Dispose before creating a new instance with the same name again (use using or try-finally). Note that an exception is thrown during the second instantiation is another issue (#8). If programmer doesn't know whether the instance with the specified name exists or not, the programmer should write initialization statement like

REngine engine = REngine.GetInstanceFromID("foo") ?? REngine.CreateInstance("foo");

If REngine named "foo" exists and is not disposed, the instance is assigned to 'engine'; otherwise a new instance is.

aoldevstat wrote Jul 11, 2011 at 2:54 AM

There is another simple method of catching images, embedding it in control and scaling.
  1. Make an instance of REngine and load the Cairo library (need to be installed first):
    REngine.SetDllDirectory(@"c:\Program Files\R\R-2.13.0\bin\i386\");
    rEngine = REngine.CreateInstance("abc", new[] { "-q" });
    rEngine.EagerEvaluate("library(Cairo);");
  2. Prepare a method that generates plot and saves in into a file. Then load generated image into Image property of desired control. Note, that you cannot load it directly (from file) into object of the Bitmap class, because it will hold access to the file and prevent R from update it! One should load image through a stream and close it as soon as possible.
Just an example:
void drawChart(REngine engine)
{
  StringBuilder plotCommmand = new StringBuilder();

  plotCommmand.Append(@"CairoPNG('c:\\tmp\\r.png', width=" + this.pictureBox1.Width.ToString() + ", height=" + this.pictureBox1.Height.ToString() + ", bg='transparent');");
  plotCommmand.Append("plot(1:" + (this.pictureBox1.Width / 10).ToString() + ",col='blue', main='Rozmiary wykresu: "+ this.pictureBox1.Width.ToString() + " x " + this.pictureBox1.Height.ToString() + "');");
  plotCommmand.Append("graphics.off();");
  Console.WriteLine(plotCommmand.ToString());
  engine.EagerEvaluate(plotCommmand.ToString());

  using (System.IO.StreamReader str = new System.IO.StreamReader("c:\\tmp\\r.png"))
  {
        this.pictureBox1.Image = new Bitmap(str.BaseStream);
        str.Close();
  }
  this.pictureBox1.Invalidate();
}

Now you have to handle changes of the WindowsState and size of the main form your picturebox is placed.
There are two events: Resize and ResizeEnd. ResizeEnd allows to generate desired plot on demand (after the window has been resized), and Resize allows to generate plot after the window state has been changed.

FormWindowState oldFormWindowState;
private void Form1_Resize(object sender, EventArgs e)
{
  if (this.WindowState == FormWindowState.Maximized)
  {
        this.OnResizeEnd(null);
        this.oldFormWindowState = FormWindowState.Maximized;
  }

  if (this.WindowState == FormWindowState.Normal && this.oldFormWindowState == FormWindowState.Maximized)
  {
        this.oldFormWindowState = FormWindowState.Normal;
        this.OnResizeEnd(null);
  }
}

private void Form1_ResizeEnd(object sender, EventArgs e)
{
  drawChart(this.rEngine);           
}

This is enogugh to obtain scalable plots in your application.

Of course one can generate PDF instead of PNG and load it into the WebBrowser control as well.
You can see it at work on youtube: http://www.youtube.com/watch?v=K2h-wfR5txY (turn into the fullscreen mode).

Best regards

aoldevstat wrote Jul 24, 2011 at 2:31 PM

It is also possible to catch "mutipage" plots. Let's assume one needs to plot output of the lm --> plot( lm(y~y) )
The "magic key" is a special form of the destination file path, which tells the png() function to generate each plot in a separate file.
This statement --> png(file = "c:\tmp\r.net.%02d.png") will produce 4 files: r.net.01.png, r.net.02.png ..... r.net.04.png
All you need is to grab these files into a collection and display it.

There are some code snippets presenting the idea above:

1. Create plots and add them into collection:

List<string> multipageImage = new List<string>();
int currIndex = 0;
void createDrawMultipageChart(REngine engine)
{
  StringBuilder plotCommmand = new StringBuilder();

  plotCommmand.Append(@"a <- c(1,3,5,7,9,11,14,15);");
  plotCommmand.Append(@"b <- 20-a;");
  plotCommmand.Append(@"png('c:\\tmp\\r.net.%02d.png', width=" + this.pictureBox1.Width.ToString() + ", height=" + this.pictureBox1.Height.ToString() + ", bg='transparent');");
  plotCommmand.Append(@"plot(lm(a~b));");
  plotCommmand.Append("graphics.off();");
  Console.WriteLine(plotCommmand.ToString());
  engine.EagerEvaluate(plotCommmand.ToString());

  multipageImage.Clear();
  multipageImage.AddRange( Directory.GetFiles("c:\\tmp\\", "r.net*.png"));

  this.currIndex = 0;
  this.drawCurrentChart();
}

2. Display the current plot from the set:

void drawCurrentChart()
{
 if (this.multipageImage.Count > 0)
 {
      using (StreamReader str = new StreamReader(this.multipageImage[this.currIndex]))
      {
           this.pictureBox1.Image = new Bitmap(str.BaseStream);
           str.Close();
      }
      this.pictureBox1.Invalidate();
 }
}

3. Navigate through the plots:

bool drawPrevChart()
{
 if (this.currIndex > 0)
 {
      this.currIndex--;
      this.drawCurrentChart();
 }

 return !(this.currIndex == 0);
}

bool drawNextChart()
{
 if (this.currIndex < this.multipageImage.Count - 1)
 {
      this.currIndex++;
      this.drawCurrentChart();
 }

 return !(this.currIndex == this.multipageImage.Count - 1);
}

4. Use the above functions in handlers of navigation buttons:

private void btnPreviousImg_Click(object sender, EventArgs e)
{
 this.btnPreviousImg.Enabled = this.drawPrevChart();
 this.btnNextImg.Enabled = true;
}

private void btnNextImg_Click(object sender, EventArgs e)
{
 this.btnNextImg.Enabled = this.drawNextChart();
 this.btnPreviousImg.Enabled = true;
}

Works like a charm :) Result (turn into fullscreen): http://www.youtube.com/watch?v=HIaojtiQXro