package org.hwo.ui; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.MouseInfo; import java.awt.Point; import java.awt.Toolkit; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.geom.Point2D; import java.util.LinkedList; import javax.swing.BoundedRangeModel; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.hwo.ui.diagram.DiagramListener; import org.hwo.ui.diagram.DiagramViewEvent; import org.hwo.ui.diagram.LinePlotPainter; import org.hwo.ui.diagram.LinearScaler; import org.hwo.ui.diagram.PlotLabeler; import org.hwo.ui.diagram.PlotPainter; import org.hwo.ui.diagram.PlotProvider2; import org.hwo.ui.diagram.PlotProviderListener; import org.hwo.ui.diagram.Scaler; import org.hwo.ui.diagram.SimplePlotLabeler; import org.hwo.ui.diagram.SimplePlotProvider; import java.awt.Rectangle; import java.awt.Stroke; import javax.swing.border.LineBorder; import java.awt.FlowLayout; public class JDiagram extends JComponent implements PlotProviderListener, BoundedRangeModel { private enum DDragMode { NONE, PAN, CROSSHAIR } private LinkedList diagramListeners = new LinkedList<>(); private LinkedList changeListeners = new LinkedList<>(); private PlotProvider2 plotProvider; private PlotLabeler defaultLabeler, abszissLabeler; // Innenabstand zu Zeichnungselementen private int bTop, bBottom, bLeft, bRight; // Länge der Achsenmarkierungen private int axMarkerLength; // Anzahl der Achsenmarkierungen - 1 private int nOrdinateLabels, nAbszissLabels; // Layout Merker private int plotHeight, plotWidth; private double abszissMinimum, abszissWindow; private int screenDPI; private int fontLineHeight; private boolean autoOrdinateLabeling; private boolean drawHorizontalGrid; private boolean drawVerticalGrid; private boolean logarithmic; private boolean preferLabelHints; private boolean autoScale; private boolean autoScaleMargins; private Color verticalGridColor; private OrdinateView[] ordinateViews; private PlotPainter[] plotPainters; private Integer selectedPlot; private DDragMode dragMode; private Point2D dragPoint; private Point dragMousePoint; private double dragPanStart; private Point mousePos; private double mouseWheelZoom = 1.03; public JDiagram(){ setMinimumSize(new Dimension(80, 80)); setDoubleBuffered(true); defaultLabeler = new SimplePlotLabeler(); abszissLabeler = defaultLabeler; bTop = bBottom = bLeft = 10; bRight = 30; axMarkerLength = 3; nOrdinateLabels = 11; nAbszissLabels = 0; drawHorizontalGrid = true; this.dragMode = DDragMode.NONE; verticalGridColor = new Color(192, 192, 192); setBackground(Color.black); setForeground(Color.WHITE); setPlotProvider(new SimplePlotProvider(1, 0)); addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { dragMousePoint = e.getPoint(); dragPoint = mapMouseToDiagram(dragMousePoint); switch (e.getButton()) { case MouseEvent.BUTTON2: setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); dragMode = DDragMode.PAN; dragPanStart = getAbszissMinimum(); break; case MouseEvent.BUTTON1: break; } } @Override public void mouseReleased(MouseEvent e) { dragMode = DDragMode.NONE; setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); repaint(); } @Override public void mouseExited(MouseEvent e) { mousePos = null; } }); addMouseMotionListener(new MouseAdapter() { @Override public void mouseDragged(MouseEvent e) { if(dragMousePoint != null) { Point2D p = mapMouseToDiagram(dragMousePoint); Point2D np = mapMouseToDiagram(e.getPoint()); double dx = np.getX() - p.getX(); switch (dragMode) { case PAN: double nmin = dragPanStart - dx; nmin = limitAbszissMinimum(nmin); setAbszissMinimum(nmin); repaint(); break; case CROSSHAIR: break; default: } } } @Override public void mouseMoved(MouseEvent e) { if(e.isControlDown()) { mousePos = e.getPoint(); } else { mousePos = null; } repaint(); } }); addMouseWheelListener(new MouseWheelListener() { @Override public void mouseWheelMoved(MouseWheelEvent e) { double newwindow = getAbszissWindow() * Math.pow(mouseWheelZoom,e.getPreciseWheelRotation()); Point2D mp = mapMouseToDiagram(e.getPoint()); double pmin = plotProvider.getPositionMinimum(); double pmax = plotProvider.getPositionMaximum(); double maxwindow = pmax - pmin; newwindow = newwindow > maxwindow ? maxwindow : newwindow; setAbszissWindow(newwindow); Point2D mp2 = mapMouseToDiagram(e.getPoint()); double nmin = getAbszissMinimum() + mp.getX() - mp2.getX(); nmin = limitAbszissMinimum(nmin); setAbszissMinimum( nmin ); repaint(); } }); addKeyListener(new KeyListener() { @Override public void keyTyped(KeyEvent e) { // TODO Auto-generated method stub } @Override public void keyReleased(KeyEvent e) { // TODO Auto-generated method stub } @Override public void keyPressed(KeyEvent e) { System.out.println("CONTROL"); if((e.getModifiers() & KeyEvent.ALT_DOWN_MASK) != 0) { setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); dragMode = DDragMode.CROSSHAIR; } } }); } private void fireViewWindowChanged(){ for (DiagramListener l: diagramListeners) { l.ViewWindowChanged(new DiagramViewEvent(this)); } for (ChangeListener l: changeListeners) { l.stateChanged(new ChangeEvent(this)); } } public void addDiagramListener(DiagramListener l) { this.diagramListeners.add(l); } public void removeDiagramListener(DiagramListener l) { this.diagramListeners.remove(l); } public void setPlotProvider(PlotProvider2 plotProvider) { if (this.plotProvider != null) this.plotProvider.removePlotProviderListener(this); this.plotProvider = plotProvider; if (this.plotProvider != null) this.plotProvider.addPlotProviderListener(this); fundamentalsChanged(); } public PlotProvider2 getPlotProvider() { return plotProvider; } public boolean isAutoOrdinateLabeling() { return autoOrdinateLabeling; } public void setAutoOrdinateLabeling(boolean autoOrdinateLabeling) { this.autoOrdinateLabeling = autoOrdinateLabeling; } public boolean isDrawHorizontalGrid() { return drawHorizontalGrid; } public void setDrawHorizontalGrid(boolean drawHorizontalGrid) { this.drawHorizontalGrid = drawHorizontalGrid; } public boolean isDrawVerticalGrid() { return drawVerticalGrid; } public void setDrawVerticalGrid(boolean drawVerticalGrid) { this.drawVerticalGrid = drawVerticalGrid; } public Integer getSelectedPlot() { return selectedPlot; } public void setSelectedPlot(Integer selectedPlot) { this.selectedPlot = selectedPlot; } private void fundamentalsChanged(){ if (plotProvider != null){ ordinateViews = new OrdinateView[ plotProvider.getMaxOrdinate() + 1 ]; for (int n=0; n < plotProvider.getMaxOrdinate() + 1; n++) ordinateViews[n] = new OrdinateView(n); PlotPainter pp = new LinePlotPainter(); plotPainters = new PlotPainter[plotProvider.getNumGraphs()]; for (int n=0;n max[ordinate])) max[ordinate] = value.doubleValue(); }; } } for (int i=0;i 0) && ((scalerHints == null) || (!preferLabelHints))) { if (nHints < 2) nHints = 2; hints = new double[nHints]; for (n = 0; n < hints.length; n++){ hints[n] = this.ordinateViews[ordinate].scaler.getMinValue() + (this.ordinateViews[ordinate].scaler.getWindow() * n / (hints.length-1)); } } else { hints = scalerHints; } return hints; } public void paintOrdinates(Graphics2D g){ double[][] labelValues = new double[ ordinateViews.length ][]; Integer last_y = null, y = null, d = null; for (int ordinate=0; ordinate < ordinateViews.length; ordinate++){ labelValues[ ordinate ] = getordinateLabelHints(ordinate); int nMarkers = labelValues[ ordinate ].length - 1; int maxWidth = 0; String[] labels = new String[ nMarkers + 1 ]; int[] labelWidths = new int[ nMarkers + 1 ]; this.ordinateViews[ ordinate ].scaler.setHeight( plotHeight ); g.setColor( this.ordinateViews[ ordinate ].colDraw ); for (int i=0;i<=nMarkers;i++){ labels[i] = this.defaultLabeler.getOrdinateLabel( this, ordinate, labelValues[ordinate][i] ); labelWidths[i] = g.getFontMetrics().stringWidth(labels[i]); if (labelWidths[i] > maxWidth) maxWidth = labelWidths[i]; } plotWidth -= maxWidth + 5; last_y = null; for (int i=0; i<=nMarkers ;i++){ y = bTop + plotHeight - this.ordinateViews[ ordinate ].scaler.getPosition(labelValues[ordinate][i]) + (fontLineHeight/4); if (last_y == null){ d = null; } else { d = y -last_y; if (d<0) d = -d; }; if ((d == null) || (d > fontLineHeight )) { g.drawString( labels[i], getWidth() - bRight - plotWidth - labelWidths[i], y ); last_y = y; } } plotWidth -= axMarkerLength; }; g.drawLine( getWidth() - bRight - plotWidth, bTop, getWidth() - bRight - plotWidth, bTop + plotHeight ); for (int ordinate=0; ordinate < ordinateViews.length; ordinate++){ int nMarkers = labelValues[ ordinate ].length - 1; for (int i=0;i<=nMarkers;i++){ int yp = bTop + plotHeight - this.ordinateViews[ ordinate ].scaler.getPosition(labelValues[ordinate][i]); g.drawLine( getWidth() - bRight - plotWidth, yp, getWidth() - bRight - plotWidth - axMarkerLength, yp ); if (drawHorizontalGrid){ g.setColor( this.ordinateViews[ ordinate ].colGrid ); g.drawLine( getWidth() - bRight - plotWidth + 1, yp, getWidth() - bRight, yp ); g.setColor( this.ordinateViews[ ordinate ].colDraw ); } } } } void paintAbszisse(Graphics2D g){ int nMarker = nAbszissLabels; PlotLabeler labeler = this.defaultLabeler; if (this.abszissLabeler != null) labeler = this.abszissLabeler; if (nMarker == 0){ int w = g.getFontMetrics().stringWidth( labeler.getAbzisseLabel(this, (double)this.plotProvider.getPositionMaximum())); nMarker = plotWidth / (w*8/7); } // TODO change for zoom //abszissMinimum = this.plotProvider.getPositionMinimum(); //abszissMaximum = this.plotProvider.getPositionMaximum(); //abszissWindow = abszissMaximum - abszissMinimum; g.setColor(getForeground()); g.drawLine( getWidth() - bRight - plotWidth, bTop + plotHeight, getWidth() - bRight, bTop + plotHeight ); if (nMarker > 0) for (int n=0;n <= nMarker; n++){ int xpos = plotWidth * n / nMarker; double pos = abszissMinimum + (abszissWindow * n / nMarker); String xlabel = labeler.getAbzisseLabel(this, pos); int xlwidth = g.getFontMetrics().stringWidth(xlabel); g.drawString(xlabel, getWidth() - bRight - plotWidth + xpos - (xlwidth / 2) , getHeight() - bBottom); g.drawLine( getWidth() - bRight - plotWidth + xpos, bTop + plotHeight, getWidth() - bRight - plotWidth + xpos, bTop + plotHeight + axMarkerLength ); if (drawVerticalGrid){ g.setColor(verticalGridColor); g.drawLine( getWidth() - bRight - plotWidth + xpos, bTop, getWidth() - bRight - plotWidth + xpos, bTop + plotHeight ); g.setColor(getForeground()); }; } } void paintGraphs(Graphics2D g){ int ordinate; Color graphColor; Color[] graphColors = this.plotProvider.getColors(); double amax = this.getAbszissMaximum(); for (int graph=0; graph < this.plotProvider.getNumGraphs(); graph++){ boolean isSelected = ((selectedPlot != null) && selectedPlot.equals(graph)); this.plotPainters[ graph ].reset(); ordinate = this.plotProvider.getOrdinate(graph); graphColor = graphColors[graph]; if (graphColor == null){ graphColor = this.ordinateViews[ordinate].colDraw; } if (graphColor == null){ graphColor = Color.BLACK; } for (int n=0;n= this.abszissMinimum) && (position <= amax)) { x = getWidth() - bRight - plotWidth + (int)((position - abszissMinimum) * plotWidth / abszissWindow); y = bTop + plotHeight - this.ordinateViews[ ordinate ].scaler.getPosition(value); this.plotPainters[ graph ].paintPoint(g, graphColor, x, y, isSelected); }; } } } void paintMouseCrosshair(Graphics2D g) { if(mousePos != null) { g.setColor(Color.WHITE); int x = getWidth() - bRight - plotWidth; int y = bTop + plotHeight; int xmax = getWidth() - bRight; int ymax = bTop; int mx = mousePos.x <= x? x : mousePos.x >= xmax? xmax : mousePos.x; int my = mousePos.y >= y? y : mousePos.y <= ymax? ymax : mousePos.y; Point mp = new Point(mx,my); drawDashedLine(g, new Point(x, my),mp); drawDashedLine(g, new Point(mx, y), mp); Point2D mmd = mapMouseToDiagram(mp); String display = String.format("[%.2f/%.2f]", mmd.getX(), mmd.getY()); int strSize = g.getFontMetrics().stringWidth(display); mx = mx + strSize + 10 >= getWidth()? mx - (strSize + 10) : mx + 10; g.setColor(Color.BLACK); g.drawString(display, mx, my); } } public void drawDashedLine(Graphics2D g, Point a, Point b){ //float dash[] = {10.0f}; Stroke dashed = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{9}, 0); g.setStroke(dashed); g.drawLine(a.x, a.y, b.x, b.y); } /* * Pan and Zoom */ public double getAbszissWindow() { return this.abszissWindow; } public void setAbszissWindow(double width){ setAbszissWindow(width, true); } private void setAbszissWindow(double width,boolean fireEvent){ this.abszissWindow = width; if (fireEvent) { fireViewWindowChanged(); } repaint(); } public double getAbszissMinimum() { return this.abszissMinimum; } public void setAbszissMinimum(double x) { this.abszissMinimum = x; fireViewWindowChanged(); repaint(); } public double getAbszissMaximum() { return this.abszissMinimum + this.abszissWindow; } public void setAbszissMaximum(double x) { this.abszissWindow = x - this.abszissMinimum; fireViewWindowChanged(); repaint(); } public double getMouseWheelZoom() { return mouseWheelZoom; } public void setMouseWheelZoom(double mouseWheelZoom) { this.mouseWheelZoom = mouseWheelZoom; } /* * Tool methods */ public Point2D mapMouseToDiagram(Point p) { return this.mapMouseToDiagram(p,0); } public Point2D mapMouseToDiagram(Point p,int ordinate) { double x,y; int xl = (getWidth() - bRight - plotWidth); ordinate = ordinate >= ordinateViews.length? ordinateViews.length - 1 : ordinate; x = this.abszissMinimum + (((p.getX() - xl) / plotWidth) * this.abszissWindow); // TODO implement reverse mapping into scaler interface ??!! y = this.ordinateViews[ ordinate ].scaler.getMinValue() - ((p.getY() - bTop - plotHeight) / plotHeight * this.ordinateViews[ ordinate ].scaler.getWindow()); return new Point2D.Double(x, y); } public Point mapDiagramToMouse(Point2D p){ return mapDiagramToMouse(p, 0); } public Point mapDiagramToMouse(Point2D p,int ordinate) { int x,y; x = getWidth() - bRight - plotWidth + (int)((p.getX() - abszissMinimum) * plotWidth / abszissWindow); y = bTop + plotHeight - this.ordinateViews[ ordinate ].scaler.getPosition(p.getY()); return new Point(x, y); } public double limitAbszissMinimum(double nmin) { double pmin,pmax; pmin = plotProvider.getPositionMinimum(); pmax = plotProvider.getPositionMaximum(); nmin = nmin < pmin ? pmin : nmin; nmin = (nmin + getAbszissWindow()) > pmax ? pmax - getAbszissWindow() : nmin; return nmin; } class OrdinateView { int num; Color colDraw, colGrid; int ordinateLabelWidth; Scaler scaler; public OrdinateView(int num) { this.num = num; this.colDraw = Color.BLACK; this.colGrid = Color.LIGHT_GRAY; this.scaler = new LinearScaler(); } Scaler getScaler(){ return this.scaler; } void setScaler(Scaler scaler){ this.scaler = scaler; } } @Override public void fundamentalsChanged(PlotProvider2 plotProvider) { fundamentalsChanged(); } /*+++++++++++++++++++++++++BOUNDEND RANGE MODEL+++++++++++++++++++++++++++++*/ @Override public int getMinimum() { return (int)(plotProvider.getPositionMinimum() * 1000.0f); } @Override public void setMinimum(int newMinimum) { } @Override public int getMaximum() { return (int)(plotProvider.getPositionMaximum() * 1000.0f); } @Override public void setMaximum(int newMaximum) { } @Override public int getValue() { return (int)(getAbszissMinimum() * 1000.0f); } @Override public void setValue(int newValue) { setAbszissMinimum( ((double)newValue / 1000.0f) ); } @Override public void setValueIsAdjusting(boolean b) { } @Override public boolean getValueIsAdjusting() { return false; } @Override public int getExtent() { return (int)(getAbszissWindow() * 1000.0f); } @Override public void setExtent(int newExtent) { setAbszissWindow( ((double)newExtent / 1000.0f) ); } @Override public void setRangeProperties(int value, int extent, int min, int max, boolean adjusting) { setMinimum(min); setMaximum(max); setExtent(extent); setValue(value); } @Override public void addChangeListener(ChangeListener x) { this.changeListeners.add(x); } @Override public void removeChangeListener(ChangeListener x) { this.changeListeners.remove(x); } }