package com.amper.graph;
import java.awt.*;
import java.awt.geom.GeneralPath;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.text.SimpleDateFormat;
import java.util.SimpleTimeZone;
import java.text.Format;
import java.awt.event.InputEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.io.*;
/** Very simple and easy graph class
* @author de-nos
*
*/
/**
* @author de-nos
*
*/
@SuppressWarnings("serial")
public class SimpleGraph extends JPanel{
private Series series;
private Gridline gridx;
private Gridline gridy;
private static final int INDENT_TOP = 0;
private static final int INDENT_RIGHT = 1;
private static final int INDENT_BOTTOM = 2;
private static final int INDENT_LEFT = 3;
private int plot_indent[] = {5, 5, 14, 5};
private int plot_x = plot_indent[INDENT_LEFT];
private int plot_y = plot_indent[INDENT_BOTTOM];
private int plot_w = 20;
private int plot_h = 10;
private double domain_indent;// = (domain_end - domain_begin) / 20;
private int MAX_NUMBER_OF_GRIDLINES = 21;
private int MAX_NUMBER_OF_GRIDLINES_Y = 11;
private int max_text_width = 0; // максимальная ширина текстовых меток по оси Y
private boolean auto_scroll_enabled = false;
private JSlider domain_slider;
private boolean needToRedraw = false;
private boolean antialias_enabled = true;
private DecimalFormat range_stringformat = new DecimalFormat("0");
private DecimalFormat chooser_stringformat = new DecimalFormat("0.000");
private Format domain_stringformat;
private int mouse_x_pos;
private boolean is_paint_chooser = false;
private double domain_chooser;
private boolean filter_enabled = false;
private double filter_value = 0.1;
private String[] info = new String[3];
public static final int FORMAT_DECIMAL = 0;
public static final int FORMAT_TIME = 1;
//public static final int FORMAT_DECIMAL_LOG = 2;
public SimpleGraph(Series series) {
this(series, FORMAT_DECIMAL);
}
public SimpleGraph(Series series, int domain_format) {
DecimalFormatSymbols dfs = new DecimalFormatSymbols();
dfs.setDecimalSeparator('.');
range_stringformat.setDecimalFormatSymbols(dfs);
chooser_stringformat.setDecimalFormatSymbols(dfs);
this.series = series;
gridx = new Gridline(domain_format, MAX_NUMBER_OF_GRIDLINES);
gridy = new Gridline(Gridline.FORMAT_DECIMAL, MAX_NUMBER_OF_GRIDLINES_Y);
domain_indent = (gridx.getEnd() - gridx.getBegin()) / 20;
err = new double[series.getNumberOfSeries()-1];
series.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Series sr = (Series) e.getSource();
if (filter_enabled) {
applyFilterForLast();
}
if (auto_scroll_enabled) {
autoScroll();
} else {
// Repaint if new data not behaind the plot
double dd[] = sr.getElements(sr.getLength()-2);
if (dd != null && dd.length > 0) {
if ((dd[0] < gridx.getEnd()) && (dd[0] > gridx.getBegin())) {
repaint();
}
}
}
if (sr.getMaxX() - sr.getMinX() + domain_indent*2 < gridx.getRange()) {
if (domain_slider != null) domain_slider.setEnabled(false);
//setDomain(sr.getMinX() - domain_indent, sr.getMinX() - domain_indent + domain);
} else {
if (domain_slider != null) domain_slider.setEnabled(true);
//calcSlider();
}
info[0] = sr.getLength() + "x" + sr.getNumberOfSeries();
}
});
this.addMouseListener(new MouseListener(){
public void mouseClicked(MouseEvent e) {}
public void mousePressed(MouseEvent e) {
mouse_x_pos = e.getX();
if (e.getModifiersEx() == InputEvent.BUTTON1_DOWN_MASK) {
is_paint_chooser = true;
domain_chooser = gridx.getBegin() + convertPixelToX(e.getX() - plot_x);
repaint();
}
if (e.getModifiersEx() == InputEvent.BUTTON2_DOWN_MASK) {
//System.out.print("2");
domain_slider.setValue(domain_slider.getMaximum());
setAutoScroll(true);
//savePlot("qq.png");
}
}
public void mouseReleased(MouseEvent e) {
repaint();
is_paint_chooser = false;
}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
});
this.addMouseWheelListener(new MouseWheelListener(){
public void mouseWheelMoved(MouseWheelEvent e) {
if (e.getWheelRotation() > 0) {
zoomOutDomain();
}
if (e.getWheelRotation() < 0) {
zoomInDomain();
}
//System.out.print("w:" + e.getWheelRotation() + " ");
}
});
this.addMouseMotionListener(new MouseMotionListener(){
public void mouseDragged(MouseEvent e) {
if (e.getModifiersEx() == InputEvent.BUTTON3_DOWN_MASK) {
setAutoScroll(false);
double x = gridx.getBegin() + convertPixelToX(mouse_x_pos - e.getX());
//setDomain(x, x + gridx.getRange());
shiftDomainByBegin(x);
}
if (is_paint_chooser) {
domain_chooser = gridx.getBegin() + convertPixelToX(e.getX() - plot_x);
repaint();
}
mouse_x_pos = e.getX();
}
public void mouseMoved(MouseEvent e) {}
});
(new Thread() {
public void run() {
while(true) {
if (needToRedraw) {
redraw();
needToRedraw = false;
}
try {
Thread.sleep(50);
} catch (InterruptedException ex) {
}
}
}
}).start();
}
public void savePlotToPNG(String filename) {
BufferedImage image = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
this.paint(g);
g.dispose();
File file = new File(filename);
try {
javax.imageio.ImageIO.write(image, "png", file);
//System.out.println("Image is saved in file: " + filename);
} catch (IOException ex) {
}
}
private double err[];
// Применение фильтра к последним добавленным элементам
private void applyFilterForLast() {
applyFilterByIndex(series.getLength() - 2, err);
}
// Применение фильтра к элементу с номером index.
private double[] applyFilterByIndex(int index, double e[]) {
if (series.getLength() < 3) return null;
if (index < 0) return null;
if (index > series.getLength()-1) return null;
boolean bb[] = new boolean[series.getNumberOfSeries()];
if (index == 0 || index == series.getLength()-1) {
for (int i = 0; i < series.getNumberOfSeries(); i++) {
bb[i] = true;
series.setElementsFlags(index, bb);
}
return null;
}
double dd[] = getAngle(series.getElements(index - 1),
series.getElements(index),
series.getElements(index + 1));
bb[0] = true;
if (e == null || e.length < series.getNumberOfSeries()-1) {
e = new double[series.getNumberOfSeries()-1];
}
for (int i = 1; i < series.getNumberOfSeries(); i++) {
if (Math.abs(dd[i-1]) > filter_value) {
bb[i] = true;
} else {
e[i-1] += dd[i-1];
if (Math.abs(e[i-1]) > filter_value) {
bb[i] = true;
e[i-1] = 0;
} else {
bb[i] = false;
}
}
}
series.setElementsFlags(index, bb);
return e;
}
// Применение фильтра ко всем элементам
private void applyFilterForAll() {
double e[] = null;
for (int i = 0; i < series.getLength(); i++) {
e = applyFilterByIndex(i, e);
}
}
// Возвращает угловую разницу отрезков (p1[i];p2[i]) и (p2[i];p3[i])
private double[] getAngle(double[] p1, double[] p2, double[] p3) {
if ((p1.length < series.getNumberOfSeries()) ||
(p2.length < series.getNumberOfSeries()) ||
(p3.length < series.getNumberOfSeries())) return new double[0];
double res[] = new double[series.getNumberOfSeries()-1];
double x1, x2, x3;
x1 = p1[0];
x2 = p2[0];
x3 = p3[0];
for (int i = 1; i < series.getNumberOfSeries(); i++ ) {
res[i-1] = Math.atan(((p2[i] - p1[i])/(x2 - x1) - (p3[i] - p2[i])/(x3 - x2))*gridx.getRange()/(gridy.getEnd()-gridy.getBegin())) * 180 / Math.PI;
}
return res;
}
private void redraw(){
int value = domain_slider.getValue();
if (value == domain_slider.getMaximum()) {
setAutoScroll(true);
} else {
setAutoScroll(false);
int slider_steps = domain_slider.getMaximum() - domain_slider.getMinimum();
double x = (series.getMaxX() - series.getMinX() - gridx.getRange() +
domain_indent * 2) /
slider_steps * value + series.getMinX() -
domain_indent;
shiftDomainByBegin(x);
}
}
public void setDomainSlider(JSlider slider) {
domain_slider = slider;
domain_slider.addChangeListener(new sliderChangeListener());
if (domain_slider.getValue() == domain_slider.getMaximum()) {
setAutoScroll(true);
} else {
setAutoScroll(false);
}
}
class sliderChangeListener implements ChangeListener {
public void stateChanged(ChangeEvent e) {
needToRedraw = true;
}
}
private void autoScroll() {
if (series.getMaxX() - series.getMinX() + domain_indent*2 >= gridx.getRange()) {
shiftDomainByBegin(series.getMaxX() + domain_indent - gridx.getRange());
} else {
shiftDomainByBegin(series.getMinX() - domain_indent);
}
}
public void setAutoScroll(boolean enabled) {
auto_scroll_enabled = enabled;
if (auto_scroll_enabled) autoScroll();
}
/** Метод позволяет установить отображаемый диапазон графика на оси абсцисс.
* @param x1 - левая граница отображаемого диапазона.
* @param x2 - правая граница отображаемого диапазона.
* Координата x1 должна быть не больше координаты x2.
*/
public void setDomain(double x1, double x2) {
gridx.setRange(x1, x2);
domain_indent = gridx.getRange() / 20;
if (is_paint_chooser) {
domain_chooser = gridx.getBegin() + convertPixelToX(mouse_x_pos - plot_x);
}
repaint();
if (filter_enabled) applyFilterForAll();
domain_stringformat = getStringFormat(gridx.getStep());
}
public void shiftDomainByBegin(double begin) {
gridx.shiftRangeByBegin(begin);
repaint();
}
private Format getStringFormat(double grid_step) {
if (gridx.getFormat() == FORMAT_TIME) { // time format ----------------
SimpleDateFormat format;
if (grid_step > 500*60) {
format = new SimpleDateFormat("HH:mm''");
} else if (grid_step > 500) {
format = new SimpleDateFormat("HH:mm''ss''''");
} else {
format = new SimpleDateFormat("mm''ss.SSS''''");
}
format.setTimeZone(new SimpleTimeZone(0, ""));
return format;
//} else if (domain_format == FORMAT_DECIMAL_LOG) {
} else { // decimal and decimal_log formats ---------------
DecimalFormat format = new DecimalFormat("0.0");
DecimalFormatSymbols dfs = new DecimalFormatSymbols();
dfs.setDecimalSeparator('.');
format.setDecimalFormatSymbols(dfs);
if (gridx.getStep() >= 1) {
format.setMaximumFractionDigits(0);
} else {
//System.out.println("gdln " + gridline_step);
int digs = (int) Math.ceil(Math.abs(Math.log10(gridx.getStep())));
//digs++;
format.setMaximumFractionDigits(digs);
format.setMinimumFractionDigits(digs);
}
return format;
}
}
/** Возвращает ранг заданного значения val. Рангом считается значимость при отображении на
* графике (например, значение "100" более значимое чем "120" или "80", поскольку на графике лучше
* будет отобразить штрих и подпись со значением "100" нежели со значением "120" или "80").
* @param val - заданное значение из некоторого диапазона.
* @param step - минимальный шаг штрихов
* @param factor - коэффициент шага, фактически это первая значимая цифра минимального шага штрихов (step).
* @return ранг заданного числа.
*/
private static int getRang(double val, double step, int factor) {
double d = Math.round(val / step * factor);
int k;
for (int i = idec.length - 1; i >= 0; i--) {
k = idec[i];
if (d % k == 0) return k;
}
return 1;
}
public double[] getDomain() {
return new double[] {gridx.getBegin(), gridx.getEnd()};
}
public void setRange(double y1, double y2) {
gridy.setRange(y1, y2);
repaint();
}
public void update(Graphics g) {
}
private void calcPlotBaunds(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
max_text_width = (int)getMaxTextWidth(g2d, gridy.getSteps());
plot_x = plot_indent[INDENT_LEFT] + max_text_width;
plot_y = plot_indent[INDENT_TOP];
plot_w = getSize().width - plot_indent[INDENT_LEFT] - max_text_width - plot_indent[INDENT_RIGHT];
plot_h = getSize().height - plot_indent[INDENT_TOP] - plot_indent[INDENT_BOTTOM];
}
public void paint (Graphics g) {
super.paint(g);
long time = System.currentTimeMillis();
Graphics2D g2d = (Graphics2D) g;
if (antialias_enabled) {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
calcPlotBaunds(g);
paintGridlines(g2d);
paintData(g2d);
paintFrame(g2d);
paintChoosers(g2d);
time = System.currentTimeMillis() - time;
debug_filterTime(time);
info[2] = String.valueOf((long)(1000/debug_time)) + " fps";
paintInfo(g2d);
//System.out.println("Draw time in mills: " + time);
}
private void paintInfo(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setFont(new Font("Verdana", Font.PLAIN, 10));
for (int i = 0; i < info.length; i++) {
if (info[i] == null) continue;
int wdt = (int)getTextWidth(g2d, info[i]);
g2d.setColor(new Color(0.9f, 0.9f, 0.9f, 0.7f));
g2d.fill(new java.awt.geom.RoundRectangle2D.Double(
plot_x + plot_w - wdt - 5 - 1,
plot_y + 12 + i*12 + 2 - 11,
wdt+2, 11, 4, 4));
g2d.setColor(Color.GRAY);
g2d.drawString(info[i],
plot_x + plot_w - wdt - 5,
plot_y + 12 + i*12);
}
}
private double debug_time = 0;
private int KALMAN_LENGTH = 10;
private void debug_filterTime(long time) {
//if (Math.abs(debug_time - time) > 20) debug_time = time;
debug_time = debug_time - debug_time/KALMAN_LENGTH + (double)time/KALMAN_LENGTH;
}
private static int idec[] = new int[]{1, 2, 5, 10, 20, 50, 100};
private static final int SIGNATURES_GAP = 10;
private void paintGridlines(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
// -----------------------------------------------------------------------------------------
// domain gridlines and signatures
double wdt, wdt2; // ширина текущей подписи и следующей
//double x = (Math.ceil(domain_begin/gridline_step))*gridline_step; // координата рисуемого штриха
double x1; // x-пиксел рисуемого штриха
String sval, sval2; // текст текущей и следующей подписей
double step_len_pix = gridx.getStep() / gridx.getRange() * plot_w; // gridline_step в пикселах
int xleft; // левая граница текущей подписи
long dev = 1;
double steps[];
if (gridx.getType() == Gridline.FORMAT_DECIMAL) {
if (gridx.getRange() > 20000000) { /** @todo доделать*/
dev = 1000000;
} else if (gridx.getRange() > 20000) {
dev = 1000;
}
}
steps = gridx.getSteps();
/* Нахождение наиболее широкой (в пикселах) подписи */ {
sval = domain_stringformat.format(steps[0]/dev);
wdt = g2d.getFont().getStringBounds(sval, g2d.getFontRenderContext()).getWidth();
sval2 = domain_stringformat.format(steps[steps.length-1]/dev);
wdt2 = g2d.getFont().getStringBounds(sval2, g2d.getFontRenderContext()).getWidth();
if (wdt < wdt2) wdt = wdt2;
}
//System.out.println("wdt: " + wdt);
int i = 0;
for (; i < idec.length; i++) {
if (gridx.getFactor() > idec[i]) continue;
if (wdt < step_len_pix/gridx.getFactor()*idec[i] - SIGNATURES_GAP) {
break;
}
}
if (i == idec.length) i = idec.length-1;
for(int k = 0; k < steps.length; k++) {
x1 = plot_x + (steps[k]-gridx.getBegin())/gridx.getRange()*plot_w;
int rang = getRang(steps[k], gridx.getStep(), gridx.getFactor());
/*draw gridlines (штрихи)*/ {
if (rang >= 100) {
g2d.setColor(Color.DARK_GRAY);
} else if (rang >= 10) {
g2d.setColor(Color.GRAY);
} else {
g2d.setColor(Color.LIGHT_GRAY);
}
g2d.drawLine((int) x1, plot_y, (int) x1, plot_y + plot_h);
}
/*draw signatures (подписи)*/ {
if (rang >= idec[i] && (!(idec[i] == 2 && rang == 5) && !(idec[i] == 20 && rang == 50))) {
sval = domain_stringformat.format(steps[k]/dev);
wdt = g2d.getFont().getStringBounds(sval, g2d.getFontRenderContext()).getWidth();
xleft = (int)(x1 - wdt / 2);
g2d.setColor(Color.GRAY);
g2d.drawString(sval, xleft, plot_y + plot_h + 12);
}
if (dev > 1) { // вывод множителя на график
g2d.setColor(Color.GRAY);
sval = "x" + Long.toString(dev);
wdt = g2d.getFont().getStringBounds(sval, g2d.getFontRenderContext()).getWidth();
g2d.drawString(sval, plot_x + plot_w - (int)wdt - 3, plot_y + plot_h - 4);
}
}
}
// -----------------------------------------------------------------------------------------
// range gridlines and signatures
double y1;
steps = gridy.getSteps();
//double max_text_width = getMaxTextBounds(g2d, steps).getWidth();
for(int k = 0; k < steps.length; k++) {
y1 = plot_y + plot_h - (steps[k]-gridy.getBegin())/gridy.getRange()*plot_h;
// draw gridline
{
g2d.setColor(Color.LIGHT_GRAY);
g2d.drawLine(plot_x, (int) y1, plot_x + plot_w, (int) y1);
}
// draw signatures
{
wdt = (int)getTextWidth(g2d, range_stringformat.format(steps[k]));
g2d.setColor(Color.GRAY);
g2d.drawString(range_stringformat.format(steps[k]), (int) (plot_x - wdt - 2), (int) (y1 + 4));
}
}
}
/*private Dimension getMaxTextBounds(Graphics2D g2d, double arr[]) {
if (arr.length == 0) return new Dimension(0, 0);
int width, height;
width = Math.max((int)getTextWidth(g2d, arr[0]), (int)getTextWidth(g2d, arr[arr.length-1]));
height = Math.max((int)getTextHeight(g2d, arr[0]), (int)getTextHeight(g2d, arr[arr.length-1]));
return new Dimension(width, height);
}*/
private double getMaxTextWidth(Graphics2D g2d, double arr[]) {
if (arr.length == 0) return 0;
return Math.max(getTextWidth(g2d, range_stringformat.format(arr[0])),
getTextWidth(g2d, range_stringformat.format(arr[arr.length-1])));
}
/*private double getMaxTextHeight(Graphics2D g2d, double arr[]) {
if (arr.length == 0) return 0;
return Math.max(getTextHeight(g2d, range_stringformat.format(arr[0])),
getTextHeight(g2d, range_stringformat.format(arr[arr.length-1])));
}*/
private double getTextWidth(Graphics2D g2d, String text) {
return g2d.getFont().getStringBounds(text, g2d.getFontRenderContext()).getWidth();
}
/*private double getTextHeight(Graphics2D g2d, String text) {
return g2d.getFont().getStringBounds(text, g2d.getFontRenderContext()).getHeight();
}*/
private void paintFrame(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER));
g2d.setColor(Color.BLACK);
//g2d.setColor(UIManager.getColor("TitledBorder.titleColor"));
g2d.draw(new Rectangle(plot_x, plot_y, plot_w, plot_h));
}
private void paintData(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
int point_count = 0;
int point_all_count = 0;
int number_of_series = series.getNumberOfSeries();
g2d.setStroke(new BasicStroke(series.getLinesWidth(), BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER));
GeneralPath polyLines[] = new GeneralPath[number_of_series-1];
for (int j = 0; j < number_of_series-1; j++) {
polyLines[j] = new GeneralPath(GeneralPath.WIND_EVEN_ODD, 1);
}
int index_begin = series.getIndex(gridx.getBegin());
int index_end = series.getIndex(gridx.getEnd());
if (index_begin > 0) index_begin--;
//System.out.println(index_begin + " " + index_end);
if (series.getLength() > 1) {
float tx, ty;
for (int j = 0; j < number_of_series-1; j++) {
tx = calcTX(index_begin, 0);
ty = calcTY(index_begin, j+1);
//angles_err[j] = 0;
polyLines[j].moveTo(tx, ty);
point_count++;
point_all_count++;
}
boolean bb[];
for (int i = index_begin + 1; i <= index_end; i++) {
bb = series.getElementsFlags(i);
for (int j = 0; j < number_of_series-1; j++) {
if (filter_enabled) {
if (!bb[j + 1] && i < index_end) {
point_all_count++;
continue;
}
}
tx = calcTX(i, 0);
ty = calcTY(i, j+1);
polyLines[j].lineTo(tx, ty);
//g2d.drawLine((int)tx, (int)ty, (int)tx, (int)ty);
point_count++;
point_all_count++;
}
}
Shape shape = g2d.getClip();
g2d.setClip(plot_x+1, plot_y+1, plot_w-1, plot_h-1);
for (int j = 0; j < number_of_series-1; j++) {
g2d.setColor(series.getColor(j));
g2d.draw(polyLines[j]);
}
info[1] = String.valueOf(point_count) + "/" + String.valueOf(point_all_count) + " points";
g2d.setClip(shape);
}
}
// Возвращает наклон отрезка в градусах
/*private float getAngle(float x1, float y1, float x2, float y2) {
return (float)Math.atan((y2 - y1)/(x2 - x1)) * 180 / (float)Math.PI;
}*/
// Прорисовка дополнительной информации (вертикальной черты и значений графиков)
private void paintChoosers(Graphics g) {
if (!is_paint_chooser) return;
Graphics2D g2d = (Graphics2D) g;
g2d.setStroke(new BasicStroke(series.getLinesWidth(), BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER));
g2d.setFont(new Font("Verdana", Font.BOLD, 12));
//double dd[] = series.getElements(series.getIndex(domain_chooser));
double dd[] = getYbyX(domain_chooser);
double dd_velocity[] = getVelocityByX(domain_chooser);
if (dd_velocity.length <= 0) return; //?? кто это написал
g2d.setColor(Color.BLACK);
float x1 = (float)(plot_x + (domain_chooser-gridx.getBegin())/gridx.getRange()*plot_w);
if ((x1 < plot_x) || (x1 > plot_x + plot_w)) return; // дабы не выходить за границы
g2d.drawLine((int)x1, plot_y, (int)x1, plot_y + plot_h);
//g2d.draw(new java.awt.geom.Line2D.Float(x1, plot_y, x1, plot_y + plot_h));
if (velocity_enable) {
g2d.setColor(Color.LIGHT_GRAY);
x1 = (float)(plot_x + (series.getElements(begin)[0]-gridx.getBegin())/gridx.getRange()*plot_w);
if ((x1 < plot_x) || (x1 > plot_x + plot_w)) {
} else {
g2d.drawLine((int) x1, plot_y, (int) x1, plot_y + plot_h);
}
x1 = (float)(plot_x + (series.getElements(end)[0]-gridx.getBegin())/gridx.getRange()*plot_w);
if ((x1 < plot_x) || (x1 > plot_x + plot_w)) {
} else {
g2d.drawLine((int) x1, plot_y, (int) x1, plot_y + plot_h);
}
}
//float diam = series.getLinesWidth()*5;
if (dd.length > 0) {
String str = domain_stringformat.format(dd[0]);
if (velocity_enable) {
str += " (" + chooser_stringformat.format(dd_velocity[0]) + ")";
}
int wdt = (int)getTextWidth(g2d, str);
g2d.setColor(new Color(0.9f, 0.9f, 0.9f, 0.7f));
g2d.fill(new java.awt.geom.RoundRectangle2D.Double(
plot_x + 7 - 2,
plot_y + 0*14 + 2,
wdt+4, 14, 7, 7));
g2d.setColor(Color.GRAY);
g2d.drawString(str, plot_x + 7, plot_y + 14 + 0 * 14);
for (int i = 1; i < series.getNumberOfSeries(); i++) {
str = chooser_stringformat.format(dd[i]);
if (velocity_enable) {
str += " (" + chooser_stringformat.format(dd_velocity[i]*velocity_factor) + ")";
}
wdt = (int)getTextWidth(g2d, str);
g2d.setColor(new Color(0.9f, 0.9f, 0.9f, 0.7f));
g2d.fill(new java.awt.geom.RoundRectangle2D.Double(
plot_x + 7 - 2,
plot_y + i*14 + 2,
wdt+4, 14, 7, 7));
g2d.setColor(series.getColor(i-1));
g2d.drawString(str, plot_x + 7, plot_y + 14 + i * 14);
/*g2d.fillOval((int)Math.round(x1-diam/2),
(int)(plot_y + plot_h - ((dd[i] - range_begin) * plot_h/(range_end - range_begin)) - diam/2),
(int)diam,
(int)diam);*/
}
}
}
private boolean velocity_enable = false;
private int velocity_length = 1;
private double velocity_factor = 1;
private int begin;
private int end;
/** @todo оптимизировать вычисление скорости (наклона кривых) */
private double[] getVelocityByX(double x) {
if (series.getLength() <= 0) return new double[0];
int idx = series.getIndex(domain_chooser);
begin = idx - velocity_length;
end = idx + velocity_length - 1;
if (begin < 0) {
begin = 0;
}
if (end > series.getLength() - 1) {
end = series.getLength() - 1;
}
double dd1[] = series.getElements(begin);
double dd2[] = series.getElements(end);
double ret[] = new double[series.getNumberOfSeries()];
ret[0] = dd2[0] - dd1[0];
for (int i = 1; i < ret.length; i++) {
ret[i] = (dd2[i] - dd1[i]) / ret[0];
}
return ret;
}
public void setVelocityEnable(boolean enabled) {
velocity_enable = enabled;
}
// Устанавливает диапазон, по которому измеряется скорость (наклон кривой)
public void setVelocityLength(int length) {
velocity_length = length;
}
// Устанавливает множитель скорости при отображении на графике
public void setVelocityFactor(double factor) {
velocity_factor = factor;
}
private double[] getYbyX(double x) {
int idx = series.getIndex(domain_chooser);
double dd2[] = series.getElements(idx);
if (idx == 0) {
return dd2;
}
if (series.getLength() < 2 || x > series.getElements(series.getLength()-1)[0]) {
return dd2;
}
double dd1[] = series.getElements(idx-1);
double k;
k = (x - dd1[0]) / (dd2[0] - dd1[0]);
double ddr[] = new double[dd1.length];
ddr[0] = x;
for (int i = 1; i < dd1.length; i++) {
ddr[i] = dd1[i] + (dd2[i] - dd1[i]) * k;
}
return ddr;
}
/** Sets minimal and maximal doomain range by zooming
* @param min_range
* @param max_range
*/
public void setZoomBounds(double min_range, double max_range) {
if (min_range > max_range) return;
this.min_range = min_range;
this.max_range = max_range;
}
private double ZOOM_FACTOR_DOMAIN = 1.1;
private double max_range = 1000000000000d;
private double min_range = 0.000001d;
public void zoomInDomain() {
double d = (gridx.getRange() - gridx.getRange() / ZOOM_FACTOR_DOMAIN)/2;
if (gridx.getEnd() - gridx.getBegin() - d * 2 > min_range) {
setDomain(gridx.getBegin() + d, gridx.getEnd() - d);
} else {
setDomain(gridx.getBegin() + gridx.getRange()/2 - min_range/2,
gridx.getEnd() - gridx.getRange()/2 + min_range/2);
}
if (auto_scroll_enabled) autoScroll();
}
public void zoomOutDomain() {
double d = (gridx.getRange() - gridx.getRange() * ZOOM_FACTOR_DOMAIN)/2;
if (gridx.getEnd() - gridx.getBegin() - d * 2 < max_range) {
setDomain(gridx.getBegin() + d, gridx.getEnd() - d);
} else {
setDomain(gridx.getBegin() + gridx.getRange()/2 - max_range/2,
gridx.getEnd() - gridx.getRange()/2 + max_range/2);
}
if (auto_scroll_enabled) autoScroll();
}
private double convertXToPixel(double x_unit) {
double pix_per_xunit = plot_w / gridx.getRange();
return x_unit * pix_per_xunit;
}
private double convertPixelToX(double pix) {
double pix_per_xunit = plot_w / gridx.getRange();
return pix / pix_per_xunit;
}
private float calcTX(int i, int j) {
//if (domain_format == FORMAT_DECIMAL_LOG) {
// return (float) (plot_x + Math.log10(convertXToPixel(series.getElements(i)[j] - domain_begin))*plot_w/Math.log10(plot_w));
//} else {
return (float) (plot_x + convertXToPixel(series.getElements(i)[j] - gridx.getBegin()));
//}
}
private float calcTY(int i, int j) {
return (float) (plot_y + plot_h - (series.getElements(i)[j] - gridy.getBegin())
/ (gridy.getEnd() - gridy.getBegin()) * plot_h);
}
public void setAntialiasing(boolean enabled) {
antialias_enabled = enabled;
}
public void setFilterEnable(boolean enabled) {
filter_enabled = enabled;
}
public void setFilterValue(double value) {
filter_value = value;
}
}