Update JFreeChart from thread - java

I have to build a GPS parser. I need to parse the NMEA string in another thread, which will be parsing a single NMEA string and update chart at 1 Hz. For now I build part of my code, but I parse data in main thread in while loop; my teacher said that is wrong. I was programming some on Java but not in multi-threading aspects. How I could move parsing process and refreshing chart to background thread?
public class MainFrame extends JFrame {
private JButton btnWybPlik;
private JLabel jlDroga;
private JLabel jlPredkosc;
private JLabel jlCzas;
private JPanel mainjpanel;
private JPanel jpMenu;
private JPanel jpTablica;
//private String sciezkaPliku;
private SekwencjaGGA sekGGA = null;
private SekwencjaGGA popSekGGA = null;
private SekwencjaGSA sekGSA;
private SekwencjaGLL sekGLL;
private SekwencjaRMC sekRMC;
private double droga;
private double predkosc;
private XYSeries series1;
private XYSeriesCollection dataset;
public MainFrame() {
droga = 0;
btnWybPlik.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
int result = fileChooser.showOpenDialog(mainjpanel);
if (result == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile();
//System.out.println("Selected file: " + selectedFile.getAbsolutePath());
String sciezkaPliku = selectedFile.getAbsolutePath();
wczytaniePliku(sciezkaPliku);
}
}
});
jpTablica = new JPanel();
mainjpanel.add(jpTablica);
this.series1 = new XYSeries("Trasa", false);
final XYSeriesCollection dataset = new XYSeriesCollection(this.series1);
final JFreeChart chart = createChart(dataset);
final ChartPanel chartPanel = new ChartPanel(chart);
jpTablica.add(chartPanel);
}
private void wczytaniePliku(String sciezkaDoPliku) {
try (BufferedReader br = new BufferedReader(new FileReader(sciezkaDoPliku))) {
String line;
//series1.add(53.448, 14.4907);
while ((line = br.readLine()) != null) {
parseLine(line);
}
//series1.add(53.4485, 14.4910);
} catch (IOException e) {
e.printStackTrace();
}
}
private void parseLine(String line) {
String bezSumKont = line.substring(0, line.length() - 3);
List<String> podzSekw = Arrays.asList(bezSumKont.split(","));
if (podzSekw.get(0).equalsIgnoreCase("$GPGGA")) {
if (check(line)) {
if (sekGGA != null)
popSekGGA = sekGGA;
sekGGA = new SekwencjaGGA(podzSekw);
if (popSekGGA != null) {
droga += obliczOdleglosc(popSekGGA, sekGGA);
jlDroga.setText(String.valueOf(droga));
}
series1.add(sekGGA.getWspolzedne().getLongitude(), sekGGA.getWspolzedne().getLatitude());
System.out.println(sekGGA.getWspolzedne().getLatitude() + " " + sekGGA.getWspolzedne().getLongitude());
//System.out.println(series1.getMaxY() + " " + series1.getMinY());
} else {
//TODO: Zlicz błąd
}
}
if (podzSekw.get(0).equalsIgnoreCase("$GPGSA")) {
if (check(line)) {
sekGSA = new SekwencjaGSA(podzSekw);
} else {
//TODO: Zlicz błąd
}
}
if (podzSekw.get(0).equalsIgnoreCase("$GPGLL")) {
if (check(line)) {
sekGLL = new SekwencjaGLL(podzSekw);
} else {
//TODO: Zlicz błąd
}
}
if (podzSekw.get(0).equalsIgnoreCase("$GPRMC")) {
//TODO: Sekwencja RMC (Recommended minimum of data)
if (check(line)) {
sekRMC = new SekwencjaRMC(podzSekw);
} else {
//TODO: Zlicz błąd
}
}
}
private double obliczOdleglosc(SekwencjaGGA pkt1, SekwencjaGGA pkt2) {
double odleglosc = 0;
double earthRadius = 6371000; //meters
double dLat = Math.toRadians(pkt2.getWspolzedne().getLatitude() - pkt1.getWspolzedne().getLatitude());
double dLng = Math.toRadians(pkt2.getWspolzedne().getLongitude() - pkt1.getWspolzedne().getLongitude());
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(Math.toRadians(pkt1.getWspolzedne().getLatitude())) * Math.cos(Math.toRadians(pkt1.getWspolzedne().getLatitude())) *
Math.sin(dLng / 2) * Math.sin(dLng / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
odleglosc = earthRadius * c;
return odleglosc;
}
/**
* Funkcja sprawdzająca sume kontrolną
*
* #param tekst cała linia NMEA
* #return true jeśli się suma kontrolna zgadza
*/
private boolean check(String tekst) {
String suma = tekst.substring(tekst.length() - 2, tekst.length());
tekst = tekst.substring(1, tekst.length() - 3);
int checksum = 0;
for (int i = 0; i < tekst.length(); i++) {
checksum = checksum ^ tekst.charAt(i);
}
if (Integer.parseInt(suma, 16) == checksum) {
return true;
}
return false;
}
private JFreeChart createChart(final XYDataset dataset) { ... }
private void customizeChart(JFreeChart chart) { ... }
public static void main(String[] args) {
JFrame frame = new JFrame("MainFrame");
frame.setContentPane(new MainFrame().mainjpanel);
frame.setPreferredSize(new Dimension(640, 480));
frame.pack();
frame.setVisible(true);
}

To avoid blocking the event dispatch thread, construct an instance of SwingWorker. Collect data in your implementation of doInBackground(), publish() intermediate results, and update the XYSeries in your implementation of process(). The listening chart will update itself in response. A related example that uses jfreechart is seen below and examined here.

Related

JFreeChart - XYLineAndShapeRenderer getItemLineVisible() not working

I'm simulating dummy real-time data using DynamicTimeSeriesCollection, like this. During random intervals, the data being passed to the plot should 'dropout' to simulate a network connection loss. At this point, this plot should stop drawing and only start plotting the data after the dropout has subsided.
I subclassed XYLineAndShapeRenderer and overrode the getItemLineVisible() method:
#Override
public boolean getItemLineVisible(int series, int item){
if(offline){
return false;
}else{
return true;
}
}
However when offline is true, all points are still being drawn on the graph.
public class Test extends ApplicationFrame {
private static final String TITLE = "Dynamic Series";
private static final String START = "Start";
private static final String STOP = "Stop";
private static final int COUNT = 1000*60;
private static final int FAST = 1; //1000/FAST = occurrences per second real time
private static final int REALTIME = FAST * 1000;
private static final Random random = new Random();
private static final double threshold = 35;
private double gateStart = ThreadLocalRandom.current().nextInt(0, 101);
private boolean returning = false;
private boolean offline = false;
private Timer timer;
private Calendar startDate;
private static final int simulationSpeed = 1000/FAST;
private final TimeSeries seriesA = new TimeSeries("A");
public Test(final String title) throws ParseException {
super(title);
SimpleDateFormat formatter = new SimpleDateFormat("dd/mm/yyyy HH:mm", Locale.ENGLISH);
PriceParser parser = new PriceParser();
List<List<String>> priceData = parser.parse();
Date date = formatter.parse(priceData.get(0).get(0));
startDate = Calendar.getInstance();
startDate.setTime(date);
Calendar timeBaseStartDate = Calendar.getInstance();
timeBaseStartDate.setTime(startDate.getTime());
timeBaseStartDate.add(Calendar.SECOND, -COUNT);
final TimeSeriesCollection dataset = new TimeSeriesCollection();
dataset.addSeries(this.seriesA);
JFreeChart chart = createChart(dataset);
final JComboBox combo = new JComboBox();
combo.addItem("Fast");
combo.addItem("Real-time");
combo.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if ("Fast".equals(combo.getSelectedItem())) {
timer.setDelay(FAST);
} else {
timer.setDelay(REALTIME);
}
}
});
JFrame frame = new JFrame("Test");
JLabel label = new JLabel("Network connectivity lost.");
this.add(new ChartPanel(chart), BorderLayout.CENTER);
JPanel btnPanel = new JPanel(new FlowLayout());
btnPanel.add(combo);
JPanel test = new JPanel();
test.add(label);
this.add(btnPanel, BorderLayout.SOUTH);
// frame.add(btnPanel);
//frame.add(test);
timer = new Timer(FAST, new ActionListener() {
Date timeToCheck = formatter.parse(priceData.get(0).get(0));
Calendar pauseResume = Calendar.getInstance();
Calendar offlineTime = Calendar.getInstance();
boolean paused = false;
boolean waiting = false;
//boolean offline = false;
double currentPrice;
float[] newData = new float[1];
PopupFactory pf = PopupFactory.getSharedInstance();
Popup popup;
#Override
public void actionPerformed(ActionEvent e) {
Date datasetTime = new Date();
if(offline){
System.out.println("Offline: "+offlineTime.getTime());
System.out.println("Current: "+datasetTime);
if(offlineTime.getTime().compareTo(datasetTime) == 0){
offline = false;
System.out.println("Im no longer offline");
popup.hide();
}
}
if(ThreadLocalRandom.current().nextInt(0, 1001) > 999 && !offline){
offline = true;
offlineTime.setTime(datasetTime);
offlineTime.add(Calendar.SECOND, ThreadLocalRandom.current().nextInt(1, 5)*10);
// dataset.addValue(0, 0, null);
popup = pf.getPopup(btnPanel, label, 900, 300);
popup.show();
}
if(timeToCheck.compareTo(datasetTime) == 0){
currentPrice = Double.valueOf(priceData.get(0).get(1));
paused = currentPrice >= threshold;
priceData.remove(0);
try {
timeToCheck = formatter.parse(priceData.get(0).get(0));
} catch (ParseException ex) {
ex.printStackTrace();
}
}
if(!paused) {
if (Math.round(gateStart) * 10 / 10.0 == 100d) {
returning = true;
} else if (Math.round(gateStart) * 10 / 10.0 == 0) {
returning = false;
}
if (returning) {
gateStart -= 0.1d;
} else {
gateStart += 0.1d;
}
}else{
if(datasetTime.compareTo(pauseResume.getTime()) == 0 && currentPrice < threshold){
paused = false;
waiting = false;
}else{
if(Math.round(gateStart)*10/10.0 == 0 || Math.round(gateStart)*10/10.0 == 100){
if(!waiting){
pauseResume.setTime(datasetTime);
pauseResume.add(Calendar.SECOND, 120);
}
waiting = true;
}else{
if(Math.round(gateStart)*10/10.0 >= 50){
gateStart += 0.1d;
}else if(Math.round(gateStart)*10/10.0 < 50){
gateStart -= 0.1d;
}
}
}
}
newData[0] = (float)gateStart;
seriesA.addOrUpdate(new Second(), gateStart);
}
});
}
private JFreeChart createChart(final XYDataset dataset) {
final JFreeChart result = ChartFactory.createTimeSeriesChart(
TITLE, "Time", "Shearer Position", dataset, true, true, false);
final XYPlot plot = result.getXYPlot();
plot.setDomainZeroBaselineVisible(false);
XYLineAndShapeRendererTest renderer = new XYLineAndShapeRendererTest(true, false);
plot.setRenderer(renderer);
DateAxis domain = (DateAxis)plot.getDomainAxis();
Calendar endDate = Calendar.getInstance();
endDate.setTime(new Date());
endDate.add(Calendar.HOUR_OF_DAY, 12);
System.out.println(new Date());
System.out.println(endDate.getTime());
domain.setRange(new Date(), endDate.getTime());
domain.setTickUnit(new DateTickUnit(DateTickUnitType.HOUR, 1));
domain.setDateFormatOverride(new SimpleDateFormat("HH:mm"));
ValueAxis range = plot.getRangeAxis();
range.setRange(0, 100);
return result;
}
private class XYLineAndShapeRendererTest extends XYLineAndShapeRenderer {
private boolean drawSeriesLineAsPath;
public XYLineAndShapeRendererTest(boolean line, boolean shapes){
super(line, shapes);
}
#Override
public Paint getItemPaint(int row, int col) {
if(!offline){
return super.getItemPaint(row, col);
}else{
return new Color(0, 0, 0);
}
}
}
private void start() {
timer.start();
}
public static void main(final String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Test demo = null; //pass date in from csv
try {
demo = new Test(TITLE);
} catch (ParseException e) {
e.printStackTrace();
}
demo.pack();
RefineryUtilities.centerFrameOnScreen(demo);
demo.setVisible(true);
demo.start();
}
});
}
}
What am I doing wrong?
One approach would be to append an appropriate baseline value when off line. Starting from this example, the range is centered on a value of zero millivolts. The modification below adds zeroes between 60 and 90 milliseconds:
private float[] gaussianData() {
float[] a = new float[COUNT];
for (int i = 0; i < a.length; i++) {
if (i > 60 && i < 90) a[i] = 0;
else a[i] = randomValue();
}
return a;
}
In my instance, a period of offline should effectively stop graphing.
Use the approach suggested here, which uses setMaximumItemAge() to limit the number of displayed records. Add null values, as suggested here, to interrupt the display. Starting from this example, I got this display with these changes:
seriesA.setMaximumItemCount(120);
seriesB.setMaximumItemCount(120);
…
int i;
…
public void addNull() {
this.seriesA.add(new Millisecond(), null);
this.seriesB.add(new Millisecond(), null);
}
#Override
public void actionPerformed(ActionEvent e) {
if (i > 60 && i < 90) {
demo.addNull();
} else {
…
}
i++;
}

Painting canvas and System.out.println() not working

I have JFrame with a start button, which triggers the calculation of a Julia Set.
The code that is executed when the start button is clicked is as follows:
public void actionPerformed(ActionEvent aActionEvent)
{
String strCmd = aActionEvent.getActionCommand();
if (strCmd.equals("Start"))
{
m_cCanvas.init();
m_cSMsg = "c = " + Double.toString(m_dReal) + " + " + "j*" + Double.toString(m_dImag);
m_bRunning = true;
this.handleCalculation();
}
else if (aActionEvent.getSource() == m_cTReal)
Which used to work fine, except that the application could not be closed anymore. So I tried to use m_bRunning in a separate method so that actionPerformed() isn't blocked all the time to see if that would help, and then set m_bRunning = false in the method stop() which is called when the window is closed:
public void run()
{
if(m_bRunning)
{
this.handleCalculation();
}
}
The method run() is called from the main class in a while(true) loop.
Yet unfortunately, neither did that solve the problem, nor do I now have any output to the canvas or any debug traces with System.out.println(). Could anyone point me in the right direction on this?
EDIT:
Here are the whole files:
// cMain.java
package juliaSet;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.Dimension;
public class cMain {
public static void main(String[] args)
{
int windowWidth = 1000;//(int)screenSize.getWidth() - 200;
int windowHeight = 800;//(int)screenSize.getHeight() - 50;
int plotWidth = 400;//(int)screenSize.getWidth() - 600;
int plotHeight = 400;//(int)screenSize.getHeight() - 150;
JuliaSet cJuliaSet = new JuliaSet("Julia Set", windowWidth, windowHeight, plotWidth, plotHeight);
cJuliaSet.setVisible(true);
while(true)
{
cJuliaSet.run();
}
}
}
// JuliaSet.java
package juliaSet;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import java.util.Random;
import java.io.*;
import java.lang.ref.*;
public class JuliaSet extends JFrame implements ActionListener
{
private JButton m_cBStart;
private JTextField m_cTReal;
private JTextField m_cTImag;
private JTextField m_cTDivergThresh;
private JLabel m_cLReal;
private JLabel m_cLImag;
private JLabel m_cLDivergThresh;
private int m_iDivergThresh = 10;
private String m_cMsgDivThresh = "Divergence threshold = " + m_iDivergThresh;
private JuliaCanvas m_cCanvas;
private int m_iPlotWidth; // number of cells
private int m_iPlotHeight; // number of cells
private Boolean m_bRunning = false;
private double m_dReal = 0.3;
private double m_dImag = -0.5;
private String m_cSMsg = "c = " + Double.toString(m_dReal) + " + " + "j*" + Double.toString(m_dImag);
private String m_cMsgIter = "x = 0, y = 0";
private Complex m_cCoordPlane[][];
private double m_dAbsSqValues[][];
private int m_iIterations[][];
private Complex m_cSummand;
private BufferedImage m_cBackGroundImage = null;
private FileWriter m_cFileWriter;
private BufferedWriter m_cBufferedWriter;
private String m_sFileName = "log.txt";
private Boolean m_bWriteLog = false;
private static final double PLOTMAX = 2.0; // we'll have symmetric axes
// ((0,0) at the centre of the
// plot
private static final int MAXITER = 0xff;
JuliaSet(String aTitle, int aFrameWidth, int aFrameHeight, int aPlotWidth, int aPlotHeight)
{
super(aTitle);
this.setSize(aFrameWidth, aFrameHeight);
m_iPlotWidth = aPlotWidth;
m_iPlotHeight = aPlotHeight;
m_cSummand = new Complex(m_dReal, m_dImag);
m_cBackGroundImage = new BufferedImage(aFrameWidth, aFrameHeight, BufferedImage.TYPE_INT_RGB);
this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
stop();
super.windowClosing(e);
System.exit(0);
}
});
GridBagLayout cLayout = new GridBagLayout();
GridBagConstraints cConstraints = new GridBagConstraints();
this.setLayout(cLayout);
m_cCanvas = new JuliaCanvas(m_iPlotWidth, m_iPlotHeight);
m_cCanvas.setSize(m_iPlotWidth, m_iPlotHeight);
m_cBStart = new JButton("Start");
m_cBStart.addActionListener(this);
m_cTReal = new JTextField(5);
m_cTReal.addActionListener(this);
m_cTImag = new JTextField(5);
m_cTImag.addActionListener(this);
m_cTDivergThresh = new JTextField(5);
m_cTDivergThresh.addActionListener(this);
m_cLReal = new JLabel("Re(c):");
m_cLImag = new JLabel("Im(c):");
m_cLDivergThresh = new JLabel("Divergence Threshold:");
cConstraints.insets.top = 3;
cConstraints.insets.bottom = 3;
cConstraints.insets.right = 3;
cConstraints.insets.left = 3;
// cCanvas
cConstraints.gridx = 0;
cConstraints.gridy = 0;
cLayout.setConstraints(m_cCanvas, cConstraints);
this.add(m_cCanvas);
// m_cLReal
cConstraints.gridx = 0;
cConstraints.gridy = 1;
cLayout.setConstraints(m_cLReal, cConstraints);
this.add(m_cLReal);
// m_cTReal
cConstraints.gridx = 1;
cConstraints.gridy = 1;
cLayout.setConstraints(m_cTReal, cConstraints);
this.add(m_cTReal);
// m_cLImag
cConstraints.gridx = 0;
cConstraints.gridy = 2;
cLayout.setConstraints(m_cLImag, cConstraints);
this.add(m_cLImag);
// m_cTImag
cConstraints.gridx = 1;
cConstraints.gridy = 2;
cLayout.setConstraints(m_cTImag, cConstraints);
this.add(m_cTImag);
// m_cLDivergThresh
cConstraints.gridx = 0;
cConstraints.gridy = 3;
cLayout.setConstraints(m_cLDivergThresh, cConstraints);
this.add(m_cLDivergThresh);
// m_cTDivergThresh
cConstraints.gridx = 1;
cConstraints.gridy = 3;
cLayout.setConstraints(m_cTDivergThresh, cConstraints);
this.add(m_cTDivergThresh);
// m_cBStart
cConstraints.gridx = 0;
cConstraints.gridy = 4;
cLayout.setConstraints(m_cBStart, cConstraints);
this.add(m_cBStart);
if (m_bWriteLog)
{
try
{
m_cFileWriter = new FileWriter(m_sFileName, false);
m_cBufferedWriter = new BufferedWriter(m_cFileWriter);
} catch (IOException ex) {
System.out.println("Error opening file '" + m_sFileName + "'");
}
}
this.repaint();
this.transformCoordinates();
}
public synchronized void stop()
{
if (m_bRunning)
{
m_bRunning = false;
boolean bRetry = true;
}
if (m_bWriteLog)
{
try {
m_cBufferedWriter.close();
m_cFileWriter.close();
} catch (IOException ex) {
System.out.println("Error closing file '" + m_sFileName + "'");
}
}
}
public void collectGarbage()
{
Object cObj = new Object();
WeakReference ref = new WeakReference<Object>(cObj);
cObj = null;
while(ref.get() != null) {
System.gc();
}
}
public void setSummand(Complex aSummand)
{
m_cSummand.setIm(aSummand.getIm());
m_dImag = aSummand.getIm();
m_cSummand.setRe(aSummand.getRe());
m_dReal = aSummand.getRe();
m_cSMsg = "c = " + Double.toString(m_dReal) + " + " + "j*" + Double.toString(m_dImag);
}
public void paint(Graphics aGraphics)
{
Graphics cScreenGraphics = aGraphics;
// render on background image
aGraphics = m_cBackGroundImage.getGraphics();
this.paintComponents(aGraphics);
// drawString() calls are debug code only....
aGraphics.setColor(Color.BLACK);
aGraphics.drawString(m_cSMsg, 10, 450);
aGraphics.drawString(m_cMsgIter, 10, 465);
aGraphics.drawString(m_cMsgDivThresh, 10, 480);
// rendering is done, draw background image to on screen graphics
cScreenGraphics.drawImage(m_cBackGroundImage, 0, 0, null);
}
public void actionPerformed(ActionEvent aActionEvent)
{
String strCmd = aActionEvent.getActionCommand();
if (strCmd.equals("Start"))
{
m_cCanvas.init();
m_cSMsg = "c = " + Double.toString(m_dReal) + " + " + "j*" + Double.toString(m_dImag);
m_bRunning = true;
}
else if (aActionEvent.getSource() == m_cTReal)
{
m_dReal = Double.parseDouble(m_cTReal.getText());
m_cSMsg = "c = " + Double.toString(m_dReal) + " + " + "j*" + Double.toString(m_dImag);
m_cSummand.setRe(m_dReal);
}
else if (aActionEvent.getSource() == m_cTImag)
{
m_dImag = Double.parseDouble(m_cTImag.getText());
m_cSMsg = "c = " + Double.toString(m_dReal) + " + " + "j*" + Double.toString(m_dImag);
m_cSummand.setIm(m_dImag);
}
else if (aActionEvent.getSource() == m_cTDivergThresh)
{
m_iDivergThresh = Integer.parseInt(m_cTDivergThresh.getText());
m_cMsgDivThresh = "Divergence threshold = " + m_iDivergThresh;
}
this.update(this.getGraphics());
}
public void transformCoordinates()
{
double dCanvasHeight = (double) m_cCanvas.getHeight();
double dCanvasWidth = (double) m_cCanvas.getWidth();
// init matrix with same amount of elements as pixels in canvas
m_cCoordPlane = new Complex[(int) dCanvasHeight][(int) dCanvasWidth];
double iPlotRange = 2 * PLOTMAX;
for (int i = 0; i < dCanvasHeight; i++)
{
for (int j = 0; j < dCanvasWidth; j++)
{
m_cCoordPlane[i][j] = new Complex((i - (dCanvasWidth / 2)) * iPlotRange / dCanvasWidth,
(j - (dCanvasHeight / 2)) * iPlotRange / dCanvasHeight);
}
}
}
public void calcAbsSqValues()
{
int iCanvasHeight = m_cCanvas.getHeight();
int iCanvasWidth = m_cCanvas.getWidth();
// init matrix with same amount of elements as pixels in canvas
m_dAbsSqValues = new double[iCanvasHeight][iCanvasWidth];
m_iIterations = new int[iCanvasHeight][iCanvasWidth];
Complex cSum = new Complex();
if (m_bWriteLog) {
try
{
m_cBufferedWriter.write("m_iIterations[][] =");
m_cBufferedWriter.newLine();
}
catch (IOException ex)
{
System.out.println("Error opening file '" + m_sFileName + "'");
}
}
for (int i = 0; i < iCanvasHeight; i++)
{
for (int j = 0; j < iCanvasWidth; j++)
{
cSum.setRe(m_cCoordPlane[i][j].getRe());
cSum.setIm(m_cCoordPlane[i][j].getIm());
m_iIterations[i][j] = 0;
do
{
m_iIterations[i][j]++;
cSum.square();
cSum.add(m_cSummand);
m_dAbsSqValues[i][j] = cSum.getAbsSq();
} while ((m_iIterations[i][j] < MAXITER) && (m_dAbsSqValues[i][j] < m_iDivergThresh));
this.calcColour(i, j, m_iIterations[i][j]);
m_cMsgIter = "x = " + i + " , y = " + j;
if(m_bWriteLog)
{
System.out.println(m_cMsgIter);
System.out.flush();
}
if (m_bWriteLog) {
try
{
m_cBufferedWriter.write(Integer.toString(m_iIterations[i][j]));
m_cBufferedWriter.write(" ");
}
catch (IOException ex) {
System.out.println("Error writing to file '" + m_sFileName + "'");
}
}
}
if (m_bWriteLog) {
try
{
m_cBufferedWriter.newLine();
}
catch (IOException ex) {
System.out.println("Error writing to file '" + m_sFileName + "'");
}
}
}
m_dAbsSqValues = null;
m_iIterations = null;
cSum = null;
}
private void calcColour(int i, int j, int aIterations)
{
Color cColour = Color.getHSBColor((int) Math.pow(aIterations, 4), 0xff,
0xff * ((aIterations < MAXITER) ? 1 : 0));
m_cCanvas.setPixelColour(i, j, cColour);
cColour = null;
}
private void handleCalculation()
{
Complex cSummand = new Complex();
for(int i = -800; i <= 800; i++)
{
for(int j = -800; j <= 800; j++)
{
cSummand.setRe(((double)i)/1000.0);
cSummand.setIm(((double)j)/1000.0);
this.setSummand(cSummand);
this.calcAbsSqValues();
this.getCanvas().paint(m_cCanvas.getGraphics());
this.paint(this.getGraphics());
}
}
cSummand = null;
this.collectGarbage();
System.gc();
System.runFinalization();
}
public boolean isRunning()
{
return m_bRunning;
}
public void setRunning(boolean aRunning)
{
m_bRunning = aRunning;
}
public Canvas getCanvas()
{
return m_cCanvas;
}
public void run()
{
if(m_bRunning)
{
this.handleCalculation();
}
}
}
class JuliaCanvas extends Canvas
{
private int m_iWidth;
private int m_iHeight;
private Random m_cRnd;
private BufferedImage m_cBackGroundImage = null;
private int m_iRed[][];
private int m_iGreen[][];
private int m_iBlue[][];
JuliaCanvas(int aWidth, int aHeight)
{
m_iWidth = aWidth;
m_iHeight = aHeight;
m_cRnd = new Random();
m_cRnd.setSeed(m_cRnd.nextLong());
m_cBackGroundImage = new BufferedImage(m_iWidth, m_iHeight, BufferedImage.TYPE_INT_RGB);
m_iRed = new int[m_iHeight][m_iWidth];
m_iGreen = new int[m_iHeight][m_iWidth];
m_iBlue = new int[m_iHeight][m_iWidth];
}
public void init() {
}
public void setPixelColour(int i, int j, Color aColour)
{
m_iRed[i][j] = aColour.getRed();
m_iGreen[i][j] = aColour.getGreen();
m_iBlue[i][j] = aColour.getBlue();
}
private int getRandomInt(double aProbability)
{
return (m_cRnd.nextDouble() < aProbability) ? 1 : 0;
}
#Override
public void paint(Graphics aGraphics)
{
// store on screen graphics
Graphics cScreenGraphics = aGraphics;
// render on background image
aGraphics = m_cBackGroundImage.getGraphics();
for (int i = 0; i < m_iWidth; i++)
{
for (int j = 0; j < m_iHeight; j++)
{
Color cColor = new Color(m_iRed[i][j], m_iGreen[i][j], m_iBlue[i][j]);
aGraphics.setColor(cColor);
aGraphics.drawRect(i, j, 0, 0);
cColor = null;
}
}
// rendering is done, draw background image to on screen graphics
cScreenGraphics.drawImage(m_cBackGroundImage, 1, 1, null);
}
#Override
public void update(Graphics aGraphics)
{
paint(aGraphics);
}
}
class Complex {
private double m_dRe;
private double m_dIm;
public Complex()
{
m_dRe = 0;
m_dIm = 0;
}
public Complex(double aRe, double aIm)
{
m_dRe = aRe;
m_dIm = aIm;
}
public Complex(Complex aComplex)
{
m_dRe = aComplex.m_dRe;
m_dIm = aComplex.m_dIm;
}
public double getRe() {
return m_dRe;
}
public void setRe(double adRe)
{
m_dRe = adRe;
}
public double getIm() {
return m_dIm;
}
public void setIm(double adIm)
{
m_dIm = adIm;
}
public void add(Complex acComplex)
{
m_dRe += acComplex.getRe();
m_dIm += acComplex.getIm();
}
public void square()
{
double m_dReSave = m_dRe;
m_dRe = (m_dRe * m_dRe) - (m_dIm * m_dIm);
m_dIm = 2 * m_dReSave * m_dIm;
}
public double getAbsSq()
{
return ((m_dRe * m_dRe) + (m_dIm * m_dIm));
}
}
I'm quoting a recent comment from #MadProgrammer (including links)
"Swing is single threaded, nothing you can do to change that, all events are posted to the event queue and processed by the Event Dispatching Thread, see Concurrency in Swing for more details and have a look at Worker Threads and SwingWorker for at least one possible solution"
There is only one thread in your code. That thread is busy doing the calculation and can not respond to events located in the GUI. You have to separate the calculation in another thread that periodically updates the quantities that appears in the window. More info about that in the links, courtesy of #MadProgrammer, I insist.
UPDATED: As pointed by #Yusuf, the proper way of launching the JFrame is
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new JuliaSet("Julia Set", windowWidth, windowHeight, plotWidth, plotHeight);
}
});
Set the frame visible on construction and start calculation when the start button is pressed.
First;
Endless loop is not a proper way to do this. This part is loops and taking CPU and never give canvas to refresh screen. if you add below code your code will run as expected. but this is not the proper solution.
cMain.java:
while (true) {
cJuliaSet.run();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Second: you could call run method when start button clicked. But you should create a thread in run method to not freeze screen.
public static void main(String[] args) {
int windowWidth = 1000;// (int)screenSize.getWidth() - 200;
int windowHeight = 800;// (int)screenSize.getHeight() - 50;
int plotWidth = 400;// (int)screenSize.getWidth() - 600;
int plotHeight = 400;// (int)screenSize.getHeight() - 150;
JuliaSet cJuliaSet = new JuliaSet("Julia Set", windowWidth, windowHeight, plotWidth, plotHeight);
cJuliaSet.setVisible(true);
//While loop removed
}
actionPerformed:
if (strCmd.equals("Start")) {
m_cCanvas.init();
m_cSMsg = "c = " + Double.toString(m_dReal) + " + " + "j*" + Double.toString(m_dImag);
m_bRunning = true;
this.run(); // added call run method.
} else if (aActionEvent.getSource() == m_cTReal) {
run method:
public void run()
{
if(m_bRunning)
{
new Thread(){ //Thread to release screen
#Override
public void run() {
JuliaSet.this.handleCalculation();
}
}.start(); //also thread must be started
}
}
As said by #RubioRic, SwingUtilities.invokeLater method is also a part of solution. But you need to check whole of your code and you should learn about Threads.

drawing a graph using an updating JSlider

I'm making a grapher that draws a graph from a mathematical expression given by user .additionally the program has a slider and play button that user can choose a variable for the slider and its range so upon clicking the play button the slider will start from minimum range to maximum and the graph will be drawn.
for the timer I'm using the answer from a previous question.
for the grapher I'm using This code. (I'm working specifically on Plotter.java file)
The edited Plotter.java file with the playable slider(timer) code :
import java.util.*;
public class Plotter extends JFrame implements ActionListener
{
private JMenuBar menuBar = new JMenuBar();
private JMenu fileMenu = new JMenu("File");
private JMenuItem openMenuItem = new JMenuItem("Open");
private JMenuItem saveMenuItem = new JMenuItem("Save");
private JMenuItem exitMenuItem = new JMenuItem("Exit");
private int value;
private JComboBox eqCombo = new JComboBox();
private JButton addButton, removeButton, clearButton ;
private Graph graph;
private JPanel userPanel , sliderPanel;
private JSlider slider_1 = new JSlider();
private JButton playButton = new JButton();
private JTextField textField = new JTextField();
private Timer timer ;
private ImageIcon playIcon = new ImageIcon(getClass().getResource("/images/play.png"));
private ImageIcon pauseIcon = new ImageIcon(getClass().getResource("/images/pause.png"));
public Plotter(double lowX, double highX, double frequency, String file) throws GraphArgumentsException, IOException
{
super("Plotter");
textField.setBounds(266, 11, 134, 28);
textField.setColumns(10);
createNewGraph(lowX, highX, frequency, file);
createLayout();
//createsliderpanel();
}
private void createLayout() throws GraphArgumentsException
{
Container c = getContentPane();
setSize(850,600);
getContentPane().setLayout(null);
c.add(graph);
c.add(userPanel);
JPanel down = new JPanel();
down.setBounds(0, 437, 650, 119);
getContentPane().add(down);
down.setLayout(null);
slider_1.setValue(0);
slider_1.setMajorTickSpacing(5);
slider_1.setMinorTickSpacing(1);
slider_1.setPaintLabels(true);
slider_1.setPaintTicks(true);
slider_1.setBounds(145, 51, 467, 42);
down.add(slider_1);
playButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
}
});
playButton.setBounds(16, 51, 117, 29);
playButton.setIcon(playIcon);
down.add(playButton);
down.add(textField);
JPanel panel_1 = new JPanel();
panel_1.setBounds(649, 39, 201, 517);
getContentPane().add(panel_1);
panel_1.setLayout(new BorderLayout(0, 0));
// c.add(sliderPanel , BorderLayout.SOUTH);
createMenuBar();
}
/**
* Creates a new Graph instance and adds equations from file into Graph
* #param eqFile file where equations are stored
* #throws IOException
*/
private void createNewGraph(double minX, double maxX, double freq, String eqFile) throws GraphArgumentsException, IOException
{
Equation[] eq = null;
graph = new Graph(minX, maxX, freq);
graph.setBounds(0, 39, 650, 396);
eq = readEquationsFromFile(eqFile);
if (eq != null)
addEquation(eq);
graph.setBackground(Color.WHITE);
userPanel = createUserPanel(eq);
}
private void createMenuBar()
{
menuBar.add(fileMenu);
fileMenu.add(openMenuItem);
fileMenu.add(saveMenuItem);
fileMenu.addSeparator();
fileMenu.add(exitMenuItem);
openMenuItem.addActionListener(this);
saveMenuItem.addActionListener(this);
exitMenuItem.addActionListener(this);
setJMenuBar(menuBar);
}
/**
* Create user panel at top of the GUI for adding and editing functions
* #param eq equation list to add into the combo box
* #return panel containing buttons and an editable combo box
*/
private JPanel createUserPanel(Equation[] eq)
{
JPanel up = new JPanel(new FlowLayout(FlowLayout.LEFT));
up.setBounds(0, 0, 844, 39);
eqCombo.setEditable(true);
if (eq != null)
{
//Add all equations into the combo box
for (int i = 0; i < eq.length; i++)
eqCombo.addItem(eq[i].getPrefix());
}
addButton = new JButton("Add");
removeButton = new JButton("Remove");
clearButton = new JButton("Clear");
addButton.addActionListener(this);
removeButton.addActionListener(this);
clearButton.addActionListener(this);
up.add(eqCombo);
up.add(addButton);
up.add(removeButton);
up.add(clearButton);
return up;
}
// slider panel
/*private JPanel createsliderpanel()
{
JPanel down = new JPanel(new FlowLayout(FlowLayout.LEFT));
playbutton = new JButton("Play");
slider = new JSlider();
field = new JTextField();
down.add(playbutton);
down.add(slider);
down.add(field);
return down;
}*/
/**
* Check action lister for button and menu events
*/
public void actionPerformed(ActionEvent e)
{
timer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int value = slider_1.getValue() + 1;
if (value >= slider_1.getMaximum()) {
stopTheClock();
} else {
slider_1.setValue(value);
}
}
});
slider_1.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
textField.setText(Integer.toString(slider_1.getValue()));
}
});
slider_1.setValue(0);
playButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (timer.isRunning()) {
stopTheClock();
} else {
startTheClock();
}
}
});
if (e.getSource() == addButton)
addEquation((String)eqCombo.getSelectedItem());
else if (e.getSource() == removeButton)
removeEquation(eqCombo.getSelectedIndex());
else if (e.getSource() == saveMenuItem)
saveEquationList();
else if (e.getSource() == openMenuItem)
loadEquations();
else if (e.getSource() == clearButton)
clearEquations();
else if (e.getSource() == exitMenuItem)
System.exit(0);
}
/**
* Save equations to file
*
*/
private void saveEquationList()
{
try
{
PrintWriter out = new PrintWriter(new FileWriter("myeq.txt"));
for (int i = 0; i < eqCombo.getItemCount(); i++)
out.println(eqCombo.getItemAt(i));
out.close();
}
catch (IOException e)
{
System.out.println(e);
}
}
private void clearEquations()
{
graph.removeAllEquations();
eqCombo.removeAllItems();
}
/**
* Load equations from file into graph
*
*/
private void loadEquations()
{
String file=null;
JFileChooser fc = new JFileChooser();
fc.showOpenDialog(null);
if (fc.getSelectedFile() != null)
{
file = fc.getSelectedFile().getPath();
try
{
Equation[] eq = readEquationsFromFile(file);
if (eq != null)
{
clearEquations();
addEquation(eq);
//Restock combo box with new equations
for (int i = 0; i < eq.length; i++)
eqCombo.addItem(eq[i].getPrefix());
}
}
catch (IOException e)
{
JOptionPane.showMessageDialog(null, "ERR4: Unable to read or access file", "alert", JOptionPane.ERROR_MESSAGE);
}
}
}
/**
* Add an equation to the Graph
* #param eq equation
*/
private void addEquation(String eq)
{
try
{
if (eq != null && !eq.equals(""))
{
Equation equation = new Equation(eq);
eqCombo.addItem(eq);
graph.addEquation(equation);
}
}
catch (EquationSyntaxException e)
{
JOptionPane.showMessageDialog(null, "ERR2: Equation is not well-formed", "alert", JOptionPane.ERROR_MESSAGE);
}
}
/**
* Add multiple equations to Graph
* #param eq equation array
*/
private void addEquation(Equation[] eq)
{
for (int i = 0; i < eq.length; i++)
{
graph.addEquation(eq[i]);
}
}
/**
* Remove equation from Graph
* #param index index to remove
*/
private void removeEquation(int index)
{
if (index >= 0)
{
graph.removeEquation(index);
eqCombo.removeItem(eqCombo.getSelectedItem());
}
}
// Timer methods
protected void startTheClock() {
slider_1.setValue(0);
timer.start();
playButton.setIcon(pauseIcon);;
}
protected void stopTheClock() {
timer.stop();
playButton.setIcon(playIcon);
}
/**
* Read file and extract equations into an array. Any errors on an equation halt the loading of the entire file
* #param file name of file containing equations
* #return array of equations
* #throws IOException
*/
public Equation[] readEquationsFromFile(String file) throws IOException
{
ArrayList<Equation> eqList = new ArrayList<Equation>(20);
if (file == null)
return null;
String line;
int lineCount = 1;
try
{
BufferedReader br = new BufferedReader(new FileReader(file));
while ((line = br.readLine()) != null)
{
Equation eq = new Equation(line);
eqList.add(eq);
lineCount++;
}
br.close();
return ((Equation[])(eqList.toArray(new Equation[0])));
}
catch (EquationSyntaxException e)
{
JOptionPane.showMessageDialog(null, "ERR2.1: Equation on line " + lineCount + " is not well-formed", "alert", JOptionPane.ERROR_MESSAGE);
return null;
}
}
/**
* Set up Plotter object and draw the graph.
* #param args command line arguments for Plotter
*/
public static void main(String[] args)
{
Scanner s = new Scanner(System.in);
String expr = JOptionPane.showInputDialog("Enter your expression");
String maxInput = JOptionPane.showInputDialog("Enter max value of x");
String minInput = JOptionPane.showInputDialog("Enter min value of x");
double frequency = 0.01;
double maxX = Double.parseDouble(maxInput);
double minX = Double.parseDouble(minInput);
String eqFile = null;
try
{
///double minX = Double.parseDouble(args[0]);
// double maxX = Double.parseDouble(args[1]);
// double frequency = Double.parseDouble(args[2]);
//double minX = -10;
//double maxX = 10;
//double frequency = 0.01;
if (args.length > 3)
eqFile = args[3];
Plotter plotterGUI = new Plotter(minX, maxX, frequency, eqFile);
plotterGUI.setVisible(true);
plotterGUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
catch (NumberFormatException e)
{
System.out.println("ERR: Invalid arguments");
}
catch (GraphArgumentsException e)
{
System.out.println(e.getMessage());
}
catch (IOException e)
{
System.out.print("ERR4: Unable to read or access file");
}
}
}
general look of program :
now I want to change the code so the slider will affect the grapher .it means when user chooses the range of x from -10 to 10 , by clicking the play button the grapher starts to draw the graph as slider changes the x from -10 to 10
Gapminder chart is an example about slider and grapher should work.
The question is how can I do this ?
here is a small code which might help you get the insight of JSlider.
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
class demo extends JFrame
{
JFrame f=new JFrame("G");
JSlider slide=new JSlider();
JTextField field=new JTextField();
JTextField d=new JTextField();
final double value=0;
demo()
{
setSize(500,500);
f.setSize(600,600);
f.setVisible(true);
slide.setBounds(100,100,200,50);
field.setBounds(150,150,100,30);
d.setBounds(0,0,0,0);
add(slide);
add(field);
add(d);
}
public static void main(String args[])
{
demo m=new demo();
m.setVisible(true);
m.setLocationRelativeTo(null);
m.slidec();
}
public double slidec()
{
slide.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
double value=((JSlider)e.getSource()).getValue();
field.setText(Double.toString(value));
}
});
return value;
}
}

How to get Note On/Off messages from a MIDI sequence?

I am hoping to get notifications of note on/off events in a playing MIDI sequence to show the notes on a screen based (piano) keyboard.
The code below adds a MetaEventListener and a ControllerEventListener when playing a MIDI file, but only shows a few messages at the start and end of the track.
How can we listen for note on & note off MIDI events?
import java.io.File;
import javax.sound.midi.*;
import javax.swing.JOptionPane;
class PlayMidi {
public static void main(String[] args) throws Exception {
/* This MIDI file can be found at..
https://drive.google.com/open?id=0B5B9wDXIGw9lR2dGX005anJsT2M&authuser=0
*/
File path = new File("I:\\projects\\EverLove.mid");
Sequence sequence = MidiSystem.getSequence(path);
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
MetaEventListener mel = new MetaEventListener() {
#Override
public void meta(MetaMessage meta) {
final int type = meta.getType();
System.out.println("MEL - type: " + type);
}
};
sequencer.addMetaEventListener(mel);
int[] types = new int[128];
for (int ii = 0; ii < 128; ii++) {
types[ii] = ii;
}
ControllerEventListener cel = new ControllerEventListener() {
#Override
public void controlChange(ShortMessage event) {
int command = event.getCommand();
if (command == ShortMessage.NOTE_ON) {
System.out.println("CEL - note on!");
} else if (command == ShortMessage.NOTE_OFF) {
System.out.println("CEL - note off!");
} else {
System.out.println("CEL - unknown: " + command);
}
}
};
int[] listeningTo = sequencer.addControllerEventListener(cel, types);
for (int ii : listeningTo) {
System.out.println("Listening To: " + ii);
}
sequencer.setSequence(sequence);
sequencer.start();
JOptionPane.showMessageDialog(null, "Exit this dialog to end");
sequencer.stop();
sequencer.close();
}
}
Here is an implementation of the first suggestion of the accepted answer. It will present an option pane confirm dialog as to whether or not to add a new track to hold meta events corresponding to the NOTE_ON & NOTE_OFF messages of each of the existing tracks.
If the user chooses to do that, they'll see Meta events throughout the playback of the MIDI sequence.
import java.io.File;
import javax.sound.midi.*;
import javax.swing.JOptionPane;
class PlayMidi {
/** Iterates the MIDI events of the first track and if they are a
* NOTE_ON or NOTE_OFF message, adds them to the second track as a
* Meta event. */
public static final void addNotesToTrack(
Track track,
Track trk) throws InvalidMidiDataException {
for (int ii = 0; ii < track.size(); ii++) {
MidiEvent me = track.get(ii);
MidiMessage mm = me.getMessage();
if (mm instanceof ShortMessage) {
ShortMessage sm = (ShortMessage) mm;
int command = sm.getCommand();
int com = -1;
if (command == ShortMessage.NOTE_ON) {
com = 1;
} else if (command == ShortMessage.NOTE_OFF) {
com = 2;
}
if (com > 0) {
byte[] b = sm.getMessage();
int l = (b == null ? 0 : b.length);
MetaMessage metaMessage = new MetaMessage(com, b, l);
MidiEvent me2 = new MidiEvent(metaMessage, me.getTick());
trk.add(me2);
}
}
}
}
public static void main(String[] args) throws Exception {
/* This MIDI file can be found at..
https://drive.google.com/open?id=0B5B9wDXIGw9lR2dGX005anJsT2M&authuser=0
*/
File path = new File("I:\\projects\\EverLove.mid");
Sequence sequence = MidiSystem.getSequence(path);
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
MetaEventListener mel = new MetaEventListener() {
#Override
public void meta(MetaMessage meta) {
final int type = meta.getType();
System.out.println("MEL - type: " + type);
}
};
sequencer.addMetaEventListener(mel);
int[] types = new int[128];
for (int ii = 0; ii < 128; ii++) {
types[ii] = ii;
}
ControllerEventListener cel = new ControllerEventListener() {
#Override
public void controlChange(ShortMessage event) {
int command = event.getCommand();
if (command == ShortMessage.NOTE_ON) {
System.out.println("CEL - note on!");
} else if (command == ShortMessage.NOTE_OFF) {
System.out.println("CEL - note off!");
} else {
System.out.println("CEL - unknown: " + command);
}
}
};
int[] listeningTo = sequencer.addControllerEventListener(cel, types);
StringBuilder sb = new StringBuilder();
for (int ii = 0; ii < listeningTo.length; ii++) {
sb.append(ii);
sb.append(", ");
}
System.out.println("Listenning to: " + sb.toString());
int mirror = JOptionPane.showConfirmDialog(
null,
"Add note on/off messages to another track as meta messages?",
"Confirm Mirror",
JOptionPane.OK_CANCEL_OPTION);
if (mirror == JOptionPane.OK_OPTION) {
Track[] tracks = sequence.getTracks();
Track trk = sequence.createTrack();
for (Track track : tracks) {
addNotesToTrack(track, trk);
}
}
sequencer.setSequence(sequence);
sequencer.start();
JOptionPane.showMessageDialog(null, "Exit this dialog to end");
sequencer.stop();
sequencer.close();
}
}
Implementation of keyboard
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.sound.midi.*;
import java.util.ArrayList;
import java.io.*;
import java.net.URL;
public class MidiPianola {
private JComponent ui = null;
public static final int OTHER = -1;
public static final int NOTE_ON = 1;
public static final int NOTE_OFF = 2;
private OctaveComponent[] octaves;
Sequencer sequencer;
int startOctave = 0;
int numOctaves = 0;
MidiPianola(int startOctave, int numOctaves)
throws MidiUnavailableException {
this.startOctave = startOctave;
this.numOctaves = numOctaves;
initUI();
}
public void openMidi(URL url)
throws InvalidMidiDataException, IOException {
openMidi(url.openStream());
}
public void openMidi(InputStream is)
throws InvalidMidiDataException, IOException {
Sequence sequence = MidiSystem.getSequence(is);
Track[] tracks = sequence.getTracks();
Track trk = sequence.createTrack();
for (Track track : tracks) {
addNotesToTrack(track, trk);
}
sequencer.setSequence(sequence);
startMidi();
}
public void startMidi() {
sequencer.start();
}
public void stopMidi() {
sequencer.stop();
}
public void closeSequencer() {
sequencer.close();
}
private void handleNote(final int command, int note) {
OctaveComponent octave = getOctaveForNote(note);
PianoKey key = octave.getKeyForNote(note);
if (command == NOTE_ON) {
key.setPressed(true);
} else if (command == NOTE_OFF) {
key.setPressed(false);
}
ui.repaint();
}
private OctaveComponent getOctaveForNote(int note) {
return octaves[(note / 12) - startOctave];
}
public void initUI() throws MidiUnavailableException {
if (ui != null) {
return;
}
sequencer = MidiSystem.getSequencer();
MetaEventListener mel = new MetaEventListener() {
#Override
public void meta(MetaMessage meta) {
final int type = meta.getType();
byte b = meta.getData()[1];
int i = (int) (b & 0xFF);
handleNote(type, i);
}
};
sequencer.addMetaEventListener(mel);
sequencer.open();
ui = new JPanel(new BorderLayout(4, 4));
ui.setBorder(new EmptyBorder(4, 4, 4, 4));
JPanel keyBoard = new JPanel(new GridLayout(1, 0));
ui.add(keyBoard, BorderLayout.CENTER);
int end = startOctave + numOctaves;
octaves = new OctaveComponent[end - startOctave];
for (int i = startOctave; i < end; i++) {
octaves[i - startOctave] = new OctaveComponent(i);
keyBoard.add(octaves[i - startOctave]);
}
JToolBar tools = new JToolBar();
tools.setFloatable(false);
ui.add(tools, BorderLayout.PAGE_START);
tools.setFloatable(false);
Action open = new AbstractAction("Open") {
JFileChooser fileChooser = new JFileChooser();
#Override
public void actionPerformed(ActionEvent e) {
int result = fileChooser.showOpenDialog(ui);
if (result == JFileChooser.APPROVE_OPTION) {
File f = fileChooser.getSelectedFile();
try {
openMidi(f.toURI().toURL());
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
};
tools.add(open);
Action rewind = new AbstractAction("Rewind") {
#Override
public void actionPerformed(ActionEvent e) {
sequencer.setTickPosition(0);
}
};
tools.add(rewind);
Action play = new AbstractAction("Play") {
#Override
public void actionPerformed(ActionEvent e) {
startMidi();
}
};
tools.add(play);
Action stop = new AbstractAction("Stop") {
#Override
public void actionPerformed(ActionEvent e) {
stopMidi();
}
};
tools.add(stop);
}
public JComponent getUI() {
return ui;
}
/**
* Iterates the MIDI events of the first track, and if they are a NOTE_ON or
* NOTE_OFF message, adds them to the second track as a Meta event.
*/
public static final void addNotesToTrack(
Track track,
Track trk) throws InvalidMidiDataException {
for (int ii = 0; ii < track.size(); ii++) {
MidiEvent me = track.get(ii);
MidiMessage mm = me.getMessage();
if (mm instanceof ShortMessage) {
ShortMessage sm = (ShortMessage) mm;
int command = sm.getCommand();
int com = OTHER;
if (command == ShortMessage.NOTE_ON) {
com = NOTE_ON;
} else if (command == ShortMessage.NOTE_OFF) {
com = NOTE_OFF;
}
if (com > OTHER) {
byte[] b = sm.getMessage();
int l = (b == null ? 0 : b.length);
MetaMessage metaMessage = new MetaMessage(
com,
b,
l);
MidiEvent me2 = new MidiEvent(metaMessage, me.getTick());
trk.add(me2);
}
}
}
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
try {
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
} catch (Exception useDefault) {
}
SpinnerNumberModel startModel =
new SpinnerNumberModel(2,0,6,1);
JOptionPane.showMessageDialog(
null,
new JSpinner(startModel),
"Start Octave",
JOptionPane.QUESTION_MESSAGE);
SpinnerNumberModel octavesModel =
new SpinnerNumberModel(5,5,11,1);
JOptionPane.showMessageDialog(
null,
new JSpinner(octavesModel),
"Number of Octaves",
JOptionPane.QUESTION_MESSAGE);
final MidiPianola o = new MidiPianola(
startModel.getNumber().intValue(),
octavesModel.getNumber().intValue());
WindowListener closeListener = new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
o.closeSequencer();
}
};
JFrame f = new JFrame("MIDI Pianola");
f.addWindowListener(closeListener);
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setLocationByPlatform(true);
f.setContentPane(o.getUI());
f.setResizable(false);
f.pack();
f.setVisible(true);
} catch (MidiUnavailableException ex) {
ex.printStackTrace();
} catch (InvalidMidiDataException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}
};
SwingUtilities.invokeLater(r);
}
}
class OctaveComponent extends JPanel {
int octave;
ArrayList<PianoKey> keys;
PianoKey selectedKey = null;
public OctaveComponent(int octave) {
this.octave = octave;
init();
}
public PianoKey getKeyForNote(int note) {
int number = note % 12;
return keys.get(number);
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
for (PianoKey key : keys) {
key.draw(g2);
}
}
public static final Shape
removeArrayFromShape(Shape shape, Shape[] shapes) {
Area a = new Area(shape);
for (Shape sh : shapes) {
a.subtract(new Area(sh));
}
return a;
}
public final Shape getEntireBounds() {
Area a = new Area();
for (PianoKey key : keys) {
a.add(new Area(key.keyShape));
}
return a;
}
#Override
public Dimension getPreferredSize() {
Shape sh = getEntireBounds();
Rectangle r = sh.getBounds();
Dimension d = new Dimension(r.x + r.width, r.y + r.height + 1);
return d;
}
public void init() {
keys = new ArrayList<PianoKey>();
int w = 30;
int h = 200;
int x = 0;
int y = 0;
int xs = w - (w / 3);
Shape[] sharps = new Shape[5];
int hs = h * 3 / 5;
int ws = w * 2 / 3;
sharps[0] = new Rectangle2D.Double(xs, y, ws, hs);
xs += w;
sharps[1] = new Rectangle2D.Double(xs, y, ws, hs);
xs += 2 * w;
sharps[2] = new Rectangle2D.Double(xs, y, ws, hs);
xs += w;
sharps[3] = new Rectangle2D.Double(xs, y, ws, hs);
xs += w;
sharps[4] = new Rectangle2D.Double(xs, y, ws, hs);
Shape[] standards = new Shape[7];
for (int ii = 0; ii < standards.length; ii++) {
Shape shape = new Rectangle2D.Double(x, y, w, h);
x += w;
standards[ii] = removeArrayFromShape(shape, sharps);
}
int note = 0;
int ist = 0;
int ish = 0;
keys.add(new PianoKey(standards[ist++], (octave * 12) + note++, "C", this));
keys.add(new PianoKey(sharps[ish++], (octave * 12) + note++, "C#", this));
keys.add(new PianoKey(standards[ist++], (octave * 12) + note++, "D", this));
keys.add(new PianoKey(sharps[ish++], (octave * 12) + note++, "D#", this));
keys.add(new PianoKey(standards[ist++], (octave * 12) + note++, "E", this));
keys.add(new PianoKey(standards[ist++], (octave * 12) + note++, "F", this));
keys.add(new PianoKey(sharps[ish++], (octave * 12) + note++, "F#", this));
keys.add(new PianoKey(standards[ist++], (octave * 12) + note++, "G", this));
keys.add(new PianoKey(sharps[ish++], (octave * 12) + note++, "G#", this));
keys.add(new PianoKey(standards[ist++], (octave * 12) + note++, "A", this));
keys.add(new PianoKey(sharps[ish++], (octave * 12) + note++, "A#", this));
keys.add(new PianoKey(standards[ist++], (octave * 12) + note++, "B", this));
}
}
class PianoKey {
Shape keyShape;
int number;
String name;
Component component;
boolean pressed = false;
PianoKey(Shape keyShape, int number, String name, Component component) {
this.keyShape = keyShape;
this.number = number;
this.name = name;
this.component = component;
}
public void draw(Graphics2D g) {
if (name.endsWith("#")) {
g.setColor(Color.BLACK);
} else {
g.setColor(Color.WHITE);
}
g.fill(keyShape);
g.setColor(Color.GRAY);
g.draw(keyShape);
if (pressed) {
Rectangle r = keyShape.getBounds();
GradientPaint gp = new GradientPaint(
r.x,
r.y,
new Color(255, 225, 0, 40),
r.x,
r.y + (int) r.getHeight(),
new Color(255, 225, 0, 188));
g.setPaint(gp);
g.fill(keyShape);
g.setColor(Color.GRAY);
g.draw(keyShape);
}
}
public boolean isPressed() {
return pressed;
}
public void setPressed(boolean pressed) {
this.pressed = pressed;
}
}
I'll be watching to see if there is a better answer than either of the two suggestions that I have, which are clearly less than ideal.
edit the midi file to include meta events that match the existing key on/off
write you own event system
I don't do a lot yet with MIDI, myself. I only occasionally import MIDI scores and strip out most of the info, converting it to use with an event-system I wrote for my own audio needs (triggering an FM synthesizer I wrote).
An answer here. Something like:
class MidiPlayer implements Receiver {
private Receiver myReceiver;
void play() {
Sequencer sequencer = MidiSystem.getSequencer();
...
// Save the original receiver
this.myReceiver = sequencer.getReceiver();
// Override the receiver
sequencer.getTransmitter().setReceiver(this);
sequencer.start();
}
#Override
public void send(MidiMessage msg, long tstamp) {
// Send the message to the original receiver
this.myReceiver.send(msg, tstamp);
// Process the message
if (msg instanceof ShortMessage) {
ShortMessage shortMsg = (ShortMessage) msg;
if (shortMsg.getCommand() == ShortMessage.NOTE_ON) {
System.out.printf("NOTE ON\n");
}
}
...
}

Reading in a CSV file, and plotting the values in a graph using MVC

I am trying to plot values from a csv file into a graph, using Java, JFreeChart and using the MVC concept. At the minute, I have created a button, and when this button is clicked it adds a new plot to the graph, however instead of doing this, I would like it to read in the values from the csv file, which is read in by a csv file adapter and stored in the model. I wondering if anyone could help me with this. I would appreciate any help that could be given. Thank you
//Main Class
public class Main
{
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Model model = new Model(0);
Controller controller = new Controller(model);
View view = new View(controller, "-");
view.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
view.setVisible(true);
}
});
}
}
//csv file adapter
public List<Double> readFile(final String filename)
{
List<Double> result = new ArrayList<Double>();
String csvFile = filename;
BufferedReader br = null;
String line = "";
String splitBy = ",";
try {
br = new BufferedReader(new FileReader(csvFile));
while((line = br.readLine()) != null){
String[] test = line.split(splitBy);
System.out.println("csvFile [Value= " + test[0] + ", Value=" + test[1] + ", Value=" + test[2] + "]");
try
{
for (String val : test)
{
final Double valueToAdd = Double.parseDouble(val);
result.add(valueToAdd);
}
}
catch (NumberFormatException nfe)
{
System.out.println("Failed to parse line: " + line);
}
}
}
catch(FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}finally{
if (br != null){
try{
br.close();
} catch (IOException e){
e.printStackTrace();
}
}
}
return result;
}
}
//model
public class Model {
private int x;
public Model(){
x = 0;
}
public Model(int x){
this.x = x;
}
public void incX(){
x++;
}
public int getX(){
return x;
}
public void addDataset(final List<Double> data)
{
System.out.println("Added data to model");
for (Double d : data)
{
System.out.println("Value: " + d.toString());
}
}
}
//view
public class View extends JFrame
{
private Controller controller;
private JFrame frame;
private JLabel label;
private JButton button;
private ChartDisplayWidget myChart;
public View(Controller c, String text){
this.controller = c;
getContentPane().setLayout(new BorderLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(1000,1000);
//label = new JLabel(text);
//getContentPane().add(label, BorderLayout.CENTER);
button = new JButton("Button");
getContentPane().add(button, BorderLayout.SOUTH);
button.addActionListener(new Action());
myChart = new ChartDisplayWidget();
getContentPane().add(myChart, BorderLayout.CENTER);
}
public class Action implements ActionListener {
public void actionPerformed (ActionEvent e){
System.out.println("I was clicked");
controller.control();
myChart.addTimeSeriesPerformancePlot("NewPlot", new Double[] {60.0, 40.0, 500.0, 10.0});
/*this is where I would like to plot the values from the csv file*/
}
}
public JButton getButton(){
return button;
}
public void setText(String text){
label.setText(text);
}
}
//controller
public class Controller {
public Model model;
public ActionListener actionListener;
public Controller(Model model){
this.model = model;
}
public void control(){
CSVFileAdapter c = new CSVFileAdapter();
model.addDataset(c.readFile("C:/dstat.csv"));
}
}
//Chart Display Widget
public class ChartDisplayWidget extends JPanel
{
/**
*
*/
private static final long serialVersionUID = 1L;
private TimeSeriesCollection chartData;
private JFreeChart chart;
public ChartDisplayWidget()
{
init();
}
public void init()
{
final XYDataset dataset = getSampleData();
chart = ChartFactory.createTimeSeriesChart(
"Our test chart",
"Time",
"Some Value",
dataset);
final ChartPanel chartPanel = new ChartPanel(chart);
add(chartPanel);
}
public void addTimeSeriesPerformancePlot(final String plotName, final Double[] values)
{
final TimeSeries newSeries = new TimeSeries(plotName);
int arrLen = values.length;
int monthIndex = 2;
int yearIndex = 2001;
for (int index = 0; index < arrLen; index++)
{
newSeries.add(new Month(monthIndex++, yearIndex++), values[index]);
}
chartData.addSeries(newSeries);
}
private XYDataset getSampleData()
{
TimeSeries s1 = new TimeSeries("Max CPU");
s1.add(new Month(2, 2001), 181.5);
s1.add(new Month(3, 2001), 20.5);
s1.add(new Month(4, 2001), 1.1);
s1.add(new Month(5, 2001), 81.5);
s1.add(new Month(6, 2001), 1181.5);
s1.add(new Month(7, 2001), 1081.5);
TimeSeries s2 = new TimeSeries("Disk I/O");
s2.add(new Month(2, 2001), 50.0);
s2.add(new Month(3, 2001), 55.0);
s2.add(new Month(4, 2001), 60.6);
s2.add(new Month(5, 2001), 70.8);
s2.add(new Month(6, 2001), 1000.1);
s2.add(new Month(7, 2001), 1081.5);
chartData = new TimeSeriesCollection();
chartData.addSeries(s1);
chartData.addSeries(s2);
return chartData;
}
}
Use the observer pattern: update the model, an unspecified implementation XYDataset, and the listening view will update itself in response. Examples are seen here and here. Because file latency is inherently unpredictable, read the file in the background thread of a SwingWorker, publish() intermediate results, and update the model in process(); a JFreeChart example is shown here.

Categories