JFreeChart And Dragging Points Around
Sometime ago when I was first began writing SpiderSynth I took a look at JFreeChart with the aim of using to display various waveforms and for the frequency and amplitude editors. I struggled to say the least and ended up coding my own. They never looked as good but I managed to convince myself that JFreeChart was a dependency I didn’t want and was not suited to what I wanted to do.
Rewriting SpiderSynth in scala has given me the change to review these conclusions with a lot more experience of how things work in Java land. JFreeChart not having a great deal of free documentation really steepens the learning curve when you are new to Java and not used to rummaging around JavaDoc documentation. A year on and I find JFreeChart can be made to do what I want it to do. No surprise there really! Experience seems to count.
To test out JFreeChart I wrote a small example program in scala that lets you click on the nodes of a line and move them around. It may be possible to write this a better way and I will go over the code again when I extend it to behave the way I want for spidersynth.
As I could find no examples out on the web using JFreeChart in this way I decided to publish this example. Any comments welcome but keep in mind it is really a quickly put together prototype bit of code.
// JFreeChart Example
import org.jfree.data.general._
import org.jfree.data._
import org.jfree.data.xy._
import org.jfree.chart._
import org.jfree.chart.plot._
import org.jfree.chart.renderer._
import org.jfree.chart.renderer.category._
import org.jfree.chart.renderer.xy._
import org.jfree.ui._
import org.jfree.chart._
import org.jfree.chart.entity._
import javax.swing.JFrame
import javax.swing.JPanel
import java.awt.event._
import java.awt.Color
// Assume you have the two jars in the same directory as you source code file
// compile: scalac ChartExample.scala -cp "jcommon-1.0.14.jar;jfreechart-1.0.11.jar;."
// run: scala -cp "jfreechart-1.0.11.jar;jcommon-1.0.14.jar;." Main
class DraggingModel(panel:ChartPanel, series:XYSeries) extends MouseMotionAdapter {
var selected:Option[XYDataItem] = null
override
def mouseMoved(e:MouseEvent) = {
val entity = panel.getEntityForPoint(e.getX, e.getY)
selected = if(entity.isInstanceOf[XYItemEntity]) Some(series.getDataItem(entity.asInstanceOf[XYItemEntity].getItem))
else None
}
override def mouseDragged(e:MouseEvent) = {
selected match {
case Some(_) => adjustEntityPositionToMouse(e.getX, e.getY)
case None => ()
}
}
def adjustEntityPositionToMouse(x:Int, y:Int) = {
val (mx,my) = convertToNormalisedCoords(x,y)
val chart = panel.getChart
val plot = chart.getXYPlot
val range = plot.getDomainAxis.getRange // x-axis
val domain = plot.getRangeAxis.getRange
series.remove(selected.get.getX)
val item = new XYDataItem(range.getLowerBound +mx*range.getLength, domain.getLowerBound+my*domain.getLength)
series.add(item, true) // true will notify listeners and cause a repaint
selected = Some(item)
}
private def convertToNormalisedCoords(x:Int, y:Int) = {
val dataArea = panel.getScreenDataArea
val dx = dataArea.getMaxX - dataArea.getMinX
val dy = dataArea.getMaxY - dataArea.getMinY
val normalX = (x-dataArea.getMinX)/dx
val normalY = 1.0-(y-dataArea.getMinY)/dy
(normalX, normalY)
}
}
object Main {
def main(args:Array[String]) = {
// Create a simple line chart
val series = new XYSeries("A Line", true, true);
for(x <-10 to 30) series.add(x, 5.0+Math.random * 3)
val dataset = new XYSeriesCollection(series);
val chart = ChartFactory.createXYLineChart( "Test",
"X",
"Y",
dataset,
PlotOrientation.VERTICAL,
false, // include legend
false, // tooltips
false // urls
);
// Make it look nice
val plot = chart.getPlot().asInstanceOf[XYPlot];
plot.setBackgroundPaint(Color.lightGray);
plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
plot.setDomainGridlinePaint(Color.white);
plot.setRangeGridlinePaint(Color.white);
val renderer = plot.getRenderer().asInstanceOf[XYLineAndShapeRenderer];
renderer.setShapesVisible(true);
renderer.setShapesFilled(true);
// Set up swing with our chart
val frame = new JFrame("Hello Moving World")
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE )
frame.setSize(640,420)
val chartPanel = new ChartPanel(chart)
chartPanel.setMouseZoomable(false)
val draggingModel = new DraggingModel(chartPanel, series)
chartPanel.addMouseMotionListener(draggingModel)
frame.add(chartPanel)
frame.pack()
frame.setVisible(true)
}
}