You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

466 lines
15 KiB
Java

/*
* Apocalypse Laboratories
* Open Source License
*
* Source code can be used for any purpose, as long as:
* - Compiled binaries are rebranded and trademarks are not
* visible by the end user at any time, except to give
* credit to Apocalypse Laboratories, such as by showing
* "Based on <product> by Apocalypse Laboratories" or a
* similar notice;
* - You do not use the code for evil;
* - Rebranded compiled applications have significant
* differences in functionality;
* - and you provide your modified source code for download,
* under the terms of the GNU LGPL v3 or a comparable
* license.
*
* Compiled binaries cannot be redistributed or mirrored,
* unless:
* - You have written permission from Apocalypse Laboratories;
* - Downloads are not available from Apocalypse Laboratories,
* not even behind a paywall or other blocking mechanism;
* - or you have received a multi-computer license, in which
* case you should take measures to prevent unauthorized
* downloads, such as preventing download access from the
* Internet.
*/
package net.apocalypselabs.symat;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import static java.lang.Math.abs;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.math.plot.Plot2DPanel;
import org.matheclipse.core.eval.EvalUtilities;
import org.matheclipse.parser.client.math.MathException;
/**
*
* @author Skylar
*/
public class Graph extends javax.swing.JInternalFrame {
private final JFileChooser fc = new JFileChooser();
private boolean standalone = true;
private boolean customName = false;
// History, used for redrawing when scale changed.
private String history = "";
// If a graph is being drawn, set to true, else false
boolean graphing = false;
// Graph min and max
private double xmin = -10;
private double xmax = 10;
/**
* Creates new form Graph
*/
public Graph() {
init();
}
public Graph(boolean isInternal) {
init();
standalone = !isInternal;
}
private void init() {
initComponents();
FileFilter filter = new FileNameExtensionFilter("PNG image (.png)", "png");
fc.setFileFilter(filter);
fc.addChoosableFileFilter(filter);
plot.plotToolBar.remove(5);
plot.plotToolBar.remove(4);
plot.plotToolBar.remove(3);
}
@Override
public void doDefaultCloseAction() {
if (standalone) {
dispose();
} else {
hide();
}
}
public void forceClose() {
dispose();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
inBox = new javax.swing.JTextField();
jLabel1 = new javax.swing.JLabel();
plotBtn = new javax.swing.JButton();
plot = new org.math.plot.Plot2DPanel();
jMenuBar1 = new javax.swing.JMenuBar();
jMenu1 = new javax.swing.JMenu();
exportBtn = new javax.swing.JMenuItem();
jMenu2 = new javax.swing.JMenu();
clrGraphBtn = new javax.swing.JMenuItem();
setTitleBtn = new javax.swing.JMenuItem();
setClosable(true);
setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE);
setIconifiable(true);
setMaximizable(true);
setResizable(true);
setTitle("Graph");
setToolTipText("");
setFrameIcon(new javax.swing.ImageIcon(getClass().getResource("/net/apocalypselabs/symat/icons/graph.png"))); // NOI18N
setPreferredSize(new java.awt.Dimension(326, 402));
addComponentListener(new java.awt.event.ComponentAdapter() {
public void componentShown(java.awt.event.ComponentEvent evt) {
formComponentShown(evt);
}
});
inBox.addKeyListener(new java.awt.event.KeyAdapter() {
public void keyTyped(java.awt.event.KeyEvent evt) {
inBoxKeyTyped(evt);
}
});
jLabel1.setText("f(x)=");
plotBtn.setText(">>");
plotBtn.setToolTipText("");
plotBtn.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
plotBtnActionPerformed(evt);
}
});
jMenu1.setText("File");
exportBtn.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.CTRL_MASK));
exportBtn.setText("Export graph...");
exportBtn.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
exportBtnActionPerformed(evt);
}
});
jMenu1.add(exportBtn);
jMenuBar1.add(jMenu1);
jMenu2.setText("Edit");
clrGraphBtn.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_W, java.awt.event.InputEvent.CTRL_MASK));
clrGraphBtn.setText("Clear Graph");
clrGraphBtn.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
clrGraphBtnActionPerformed(evt);
}
});
jMenu2.add(clrGraphBtn);
setTitleBtn.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_T, java.awt.event.InputEvent.CTRL_MASK));
setTitleBtn.setText("Set Title...");
setTitleBtn.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
setTitleBtnActionPerformed(evt);
}
});
jMenu2.add(setTitleBtn);
jMenuBar1.add(jMenu2);
setJMenuBar(jMenuBar1);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(jLabel1)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(inBox, javax.swing.GroupLayout.DEFAULT_SIZE, 215, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(plotBtn)
.addGap(10, 10, 10))
.addComponent(plot, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(plot, javax.swing.GroupLayout.DEFAULT_SIZE, 311, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(inBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jLabel1)
.addComponent(plotBtn))
.addContainerGap())
);
pack();
}// </editor-fold>//GEN-END:initComponents
private void plotBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_plotBtnActionPerformed
new GraphThread(inBox.getText()).start();
}//GEN-LAST:event_plotBtnActionPerformed
/**
* Graph the graphs without holding up everything else.
*/
private class GraphThread extends Thread {
String[] frmlas;
public GraphThread(String frmla) {
frmlas = new String[1];
frmlas[0] = frmla;
}
public GraphThread(String[] frmla) {
frmlas = frmla;
}
@Override
public void run() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
inBox.setEnabled(false);
plotBtn.setEnabled(false);
for (Component mu : jMenuBar1.getComponents()) {
mu.setEnabled(false);
}
}
});
for (String formula : frmlas) {
String niceformula = formula;
CodeRunner cr = new CodeRunner();
formula = formula.replaceAll("x", "\\$x");
EvalUtilities solver = new EvalUtilities();
String xx = "";
String yy = "";
double x;
for (x = xmin; x <= xmax; x += ((xmax - xmin) / 40.0)) {
try {
cr.setVar("x", x);
yy += solver.evaluate("$x=" + x + ";N[" + formula + "]").toString() + " ";
xx += String.valueOf(x) + " ";
} catch (MathException | NumberFormatException ex) {
}
}
Debug.println(xx);
Debug.println(yy);
String[] xs = xx.trim().split(" ");
String[] ys = yy.trim().split(" ");
double[] xd = new double[xs.length];
double[] yd = new double[ys.length];
for (int i = 0; i < xs.length; i++) {
xd[i] = Double.parseDouble(xs[i]);
}
for (int i = 0; i < ys.length; i++) {
yd[i] = Double.parseDouble(ys[i]);
}
SwingUtilities.invokeLater(new Updater(niceformula, xd, yd));
}
SwingUtilities.invokeLater(new Finisher());
}
private class Updater implements Runnable {
final double[] xd;
final double[] yd;
final String formula;
public Updater(String frmla, double[] x, double[] y) {
xd = x;
yd = y;
formula = frmla;
}
@Override
public void run() {
plot.addLinePlot(formula, xd, yd);
history += formula + "\n";
}
}
private class Finisher implements Runnable {
@Override
public void run() {
inBox.setEnabled(true);
plotBtn.setEnabled(true);
for (Component mu : jMenuBar1.getComponents()) {
mu.setEnabled(true);
}
}
}
}
private void formComponentShown(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentShown
}//GEN-LAST:event_formComponentShown
/**
* Get the zoom ratio.
*
* @param zoomLevel The zoom level to calculate from.
* @return The ratio.
*/
public static double getScale(int zoomLevel) {
double gscale = 15.0;
if (zoomLevel >= 0) {
gscale = 1.0 / (zoomLevel + 1.0);
} else {
gscale = 1.0 * (abs(zoomLevel) + 1.0);
}
return gscale;
}
public void drawDot(double x, double y) {
// TODO: implement this
}
private void inBoxKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_inBoxKeyTyped
if (evt.getKeyChar() == '\n') {
plotBtnActionPerformed(null);
}
}//GEN-LAST:event_inBoxKeyTyped
private void clrGraphBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clrGraphBtnActionPerformed
clearDraw();
}//GEN-LAST:event_clrGraphBtnActionPerformed
private void exportBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportBtnActionPerformed
}//GEN-LAST:event_exportBtnActionPerformed
private void setTitleBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_setTitleBtnActionPerformed
String wintitle = JOptionPane.showInternalInputDialog(this,
"New window title:",
"Rename",
JOptionPane.QUESTION_MESSAGE);
if (wintitle != null && !wintitle.equals("")) {
setWindowTitle(wintitle);
}
}//GEN-LAST:event_setTitleBtnActionPerformed
/**
* Get the range of the graph.
*
* @return {xmin, xmax}
*/
public double[] getRange() {
double[] range = {xmin, xmax};
return range;
}
public void setRange(double min, double max) {
xmin = min;
xmax = max;
clearDraw(false);
if (!history.trim().equals("")) {
String temp = "";
for (String cmd : history.trim().split("\n")) {
cmd = cmd.trim();
if (!cmd.equals("")) {
temp += cmd + "\n";
}
}
history = temp.trim();
new GraphThread(history.split("\n")).start();
inBox.setText("");
}
}
/**
* Graph the given function. Same as typing into input box and pressing
* Enter.
*
* @param f f(x) = f
*/
public void graphFunction(String f) {
inBox.setText(f);
plotBtnActionPerformed(null);
}
private String addSaveExt(String path) {
if (!path.matches(".*\\.(png)")) {
path += ".png";
}
return path;
}
/**
* Set the wintitle of this graph window.
*
* @param t The wintitle.
*/
public void setWindowTitle(String t) {
setTitle(t);
customName = true;
}
/**
* Get the wintitle of the window.
*
* @return the wintitle, stupid!
*/
public String getWindowTitle() {
return getTitle();
}
/**
* Erase the graph.
*/
public void clearDraw() {
clearDraw(true);
}
/**
* Erase the graph.
*
* @param alsoHistory True if history should be removed
*/
public void clearDraw(boolean alsoHistory) {
if (alsoHistory) {
history = "";
}
plot.removeAllPlots();
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JMenuItem clrGraphBtn;
private javax.swing.JMenuItem exportBtn;
private javax.swing.JTextField inBox;
private javax.swing.JLabel jLabel1;
private javax.swing.JMenu jMenu1;
private javax.swing.JMenu jMenu2;
private javax.swing.JMenuBar jMenuBar1;
private org.math.plot.Plot2DPanel plot;
private javax.swing.JButton plotBtn;
private javax.swing.JMenuItem setTitleBtn;
// End of variables declaration//GEN-END:variables
}