27 July 2009

Drawing a circular genome. Chapter 2: java swing

This post follows my previous post Ajax/PHP/Mysql/Canvas Drawing a circular genome, my notebook. The problem here, is drawing a circular genomic map that might contain a huge number of data and using an asynchronous method to fetch and display the data. Here, the server returning some JSON data is the same as in the last post but I now use a Java Swing client to fetch and display the data. Here again, the code is just a draft and I wouldn't write my final code like that.

The client is a javax.swing.JFrame. When the frame is opened, it opens a new Thread calling the server and fetching the JSON data (I previously described a JSON parser here). Once the data have been fetched, it can be only drawn in the Swing-Thread (all code that might affect or depend on the state of that component should be executed in this event-dispatching thread), that's why the drawing area is painted inside a SwingUtilities.invokeLater call.

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.RenderingHints;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


import org.lindenb.json.Parser;
import org.lindenb.swing.SwingUtils;

/**
* JAVA-Swing implementation of http://plindenbaum.blogspot.com/2009/07/ajaxphpmysqlcanvas-drawing-circular.html
* This is just a proof of Concept
*
* @author lindenb
*
*/
public class CircularGenome extends JFrame
{
private static final double CHR1_LENGTH =248000000.0;
private static final long serialVersionUID = 1L;

/**
* Drawing thread. call the JSON server and draw the density
*
*/
private class ParseMapping
implements Runnable
{
//json url to be called
private String url;
private int step;//UGLY, are we drawing snp of genes ?
private List<Integer> counts=null;
ParseMapping(int step,String url)
{
this.url=url;
this.step=step;
}

@Override
public void run()
{
try
{
Parser parser= new Parser();
//call the PHP server and retrieve the density of objects for this track
Object o=parser.parse(new URL(this.url).openStream());
Map<?,?> map=Map.class.cast(o);
List<?> L=List.class.cast(map.get("counts"));
this.counts= new ArrayList<Integer>(L.size());
for(Object c:L)
{
this.counts.add(Number.class.cast(Map.class.cast(c).get("count")).intValue());
}
/*
* SwingThread: Once a Swing component has been realized,
* all code that might affect or depend on the state of that component
* should be executed in the event-dispatching thread.
*/
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{

Graphics2D g= offscreen.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

double radius= area.getWidth()/2.0;
double r1= radius/2.0;
if(step==1)
{
r1+= 2+radius/4.0;
}
//get max densisty
double max=0;
for(int i=0;i< counts.size();++i)
{
if(counts.get(i) > max) max= counts.get(i);
}

//loop over the items
for(int i=0;i< counts.size();++i)
{
double a1= Math.PI*2.0*i/counts.size();
double a2= Math.PI*2.0*(i+1)/counts.size();

double r2= r1+(counts.get(i)/max)*(radius/4.0);
//draw the item
GeneralPath path= new GeneralPath();
path.moveTo( radius + Math.cos(a1)*r1, radius + Math.sin(a1)*r1);
path.lineTo( radius + Math.cos(a1)*r2, radius + Math.sin(a1)*r2);
path.lineTo( radius + Math.cos(a2)*r2, radius + Math.sin(a2)*r2);
path.lineTo( radius + Math.cos(a2)*r1, radius + Math.sin(a2)*r1);
path.closePath();
g.setColor(step==0?Color.RED:Color.YELLOW);
g.fill(path);
g.setColor(Color.BLACK);
g.draw(path);
}
g.dispose();
//repaint the drawing area
area.repaint();

if(step!=0) return;
//call a new Thread for the Gene
Thread t= new Thread(new ParseMapping(1,
"http://localhost/lindenb/ucsc.php?length="+windowLength+
"&database=knownGene")
);
t.start();
}
});
}
catch (Exception e)
{
e.printStackTrace();
}
}
}

/** offscreen image where we paint the tracks */
private BufferedImage offscreen=null;
/** drawing area */
private JPanel area=null;
/** step size */
private int windowLength;

public CircularGenome()
{
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setBounds(50, 50, 800, 800);
setResizable(false);

area=new JPanel(null)
{
private static final long serialVersionUID = 1L;
//paint the drawing area in this panel
@Override
protected void paintComponent(Graphics g) {
g.drawImage(getOffscreen(),0, 0,area);
}
};
area.setOpaque(true);
setContentPane(area);
/* once the window is opened,
* call the first thread
*/
addWindowListener(new WindowAdapter()
{
@Override
public void windowOpened(WindowEvent e)
{

double perimeter= 2*Math.PI*(area.getWidth()/4.0);
windowLength = (int)Math.round(CHR1_LENGTH/perimeter)*4;
Thread t= new Thread(new ParseMapping(0,
"http://localhost/lindenb/ucsc.php?length="+windowLength+"&database=snp129")
);
t.start();
}
});
}

/** get the offscreen picture, create it, if it doesn't exist */
private BufferedImage getOffscreen()
{
if(this.offscreen==null)
{
this.offscreen=new BufferedImage(
area.getWidth(),
area.getHeight(),
BufferedImage.TYPE_INT_RGB
);
//prepare the picture, add a gradient for the background
LinearGradientPaint paint= new LinearGradientPaint(
this.getWidth()/2f,0,this.getWidth()/2f,this.getHeight(),
new float[]{0f,1f},
new Color[]{Color.WHITE,Color.BLACK}
);
Graphics2D g= this.offscreen.createGraphics();
g.setPaint(paint);
g.fillRect(0, 0, area.getWidth(), area.getHeight());
g.dispose();
}
return this.offscreen;
}



public static void main(String[] args) {
try {
CircularGenome g= new CircularGenome();
SwingUtils.show(g);
} catch (Exception e) {
e.printStackTrace();
}
}

}



The result looks like the same as in the previous javascript client.

No I wonder it it would be worth trying to implement this using Java-FX.

That's it.
Pierre

No comments: