/* Copyright 2008-2010 Gephi Authors : Mathieu Bastian <mathieu.bastian@gephi.org> Website : http://www.gephi.org This file is part of Gephi. Gephi is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gephi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Gephi. If not, see <http://www.gnu.org/licenses/>. */ package org.gephi.ui.components.SplineEditor.equation; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.geom.GeneralPath; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.NumberFormat; import java.util.LinkedList; import java.util.List; import javax.swing.JComponent; public class EquationDisplay extends JComponent implements PropertyChangeListener { private static final Color COLOR_BACKGROUND = Color.WHITE; private static final Color COLOR_MAJOR_GRID = Color.GRAY.brighter(); private static final Color COLOR_MINOR_GRID = new Color(220, 220, 220); private static final Color COLOR_AXIS = Color.BLACK; private static final float STROKE_AXIS = 1.2f; private static final float STROKE_GRID = 1.0f; private static final float COEFF_ZOOM = 1.1f; private List<DrawableEquation> equations; protected double minX; protected double maxX; protected double minY; protected double maxY; private double originX; private double originY; private double majorX; private int minorX; private double majorY; private int minorY; private boolean drawText = true; private Point dragStart; private NumberFormat formatter; private ZoomHandler zoomHandler; private PanMotionHandler panMotionHandler; private PanHandler panHandler; public EquationDisplay(double originX, double originY, double minX, double maxX, double minY, double maxY, double majorX, int minorX, double majorY, int minorY) { if (minX >= maxX) { throw new IllegalArgumentException("minX must be < to maxX"); } if (originX < minX || originX > maxX) { throw new IllegalArgumentException("originX must be between minX and maxX"); } if (minY >= maxY) { throw new IllegalArgumentException("minY must be < to maxY"); } if (originY < minY || originY > maxY) { throw new IllegalArgumentException("originY must be between minY and maxY"); } if (minorX <= 0) { throw new IllegalArgumentException("minorX must be > 0"); } if (minorY <= 0) { throw new IllegalArgumentException("minorY must be > 0"); } if (majorX <= 0.0) { throw new IllegalArgumentException("majorX must be > 0.0"); } if (majorY <= 0.0) { throw new IllegalArgumentException("majorY must be > 0.0"); } this.originX = originX; this.originY = originY; this.minX = minX; this.maxX = maxX; this.minY = minY; this.maxY = maxY; this.majorX = majorX; this.minorX = minorX; this.majorY = majorY; this.minorY = minorY; this.equations = new LinkedList<DrawableEquation>(); this.formatter = NumberFormat.getInstance(); this.formatter.setMaximumFractionDigits(2); panHandler = new PanHandler(); addMouseListener(panHandler); panMotionHandler = new PanMotionHandler(); addMouseMotionListener(panMotionHandler); zoomHandler = new ZoomHandler(); addMouseWheelListener(zoomHandler); } @Override public void setEnabled(boolean enabled) { if (isEnabled() != enabled) { //super.setEnabled(enabled); if (enabled) { addMouseListener(panHandler); addMouseMotionListener(panMotionHandler); addMouseWheelListener(zoomHandler); } else { removeMouseListener(panHandler); removeMouseMotionListener(panMotionHandler); removeMouseWheelListener(zoomHandler); } } } public boolean isDrawText() { return drawText; } public void setDrawText(boolean drawText) { this.drawText = drawText; } public void addEquation(AbstractEquation equation, Color color) { if (equation != null && !equations.contains(equation)) { equation.addPropertyChangeListener(this); equations.add(new DrawableEquation(equation, color)); repaint(); } } public void removeEquation(AbstractEquation equation) { if (equation != null) { DrawableEquation toRemove = null; for (DrawableEquation drawable : equations) { if (drawable.getEquation() == equation) { toRemove = drawable; break; } } if (toRemove != null) { equation.removePropertyChangeListener(this); equations.remove(toRemove); repaint(); } } } @Override public Dimension getPreferredSize() { return new Dimension(400, 400); } public void propertyChange(PropertyChangeEvent evt) { repaint(); } protected double yPositionToPixel(double position) { double height = getHeight(); return height - ((position - minY) * height / (maxY - minY)); } protected double xPositionToPixel(double position) { return (position - minX) * getWidth() / (maxX - minX); } protected double xPixelToPosition(double pixel) { double axisV = xPositionToPixel(originX); return (pixel - axisV) * (maxX - minX) / getWidth(); } protected double yPixelToPosition(double pixel) { double axisH = yPositionToPixel(originY); return (getHeight() - pixel - axisH) * (maxY - minY) / getHeight(); } @Override protected void paintComponent(Graphics g) { if (!isVisible()) { return; } Graphics2D g2 = (Graphics2D) g; setupGraphics(g2); paintBackground(g2); drawGrid(g2); drawAxis(g2); drawEquations(g2); paintInformation(g2); } protected void paintInformation(Graphics2D g2) { } private void drawEquations(Graphics2D g2) { for (DrawableEquation drawable : equations) { g2.setColor(drawable.getColor()); drawEquation(g2, drawable.getEquation()); } } private void drawEquation(Graphics2D g2, AbstractEquation equation) { float x = 0.0f; float y = (float) yPositionToPixel(equation.compute(xPixelToPosition(0.0))); GeneralPath path = new GeneralPath(); path.moveTo(x, y); for (x = 0.0f; x < getWidth(); x += 1.0f) { double position = xPixelToPosition(x); y = (float) yPositionToPixel(equation.compute(position)); path.lineTo(x, y); } g2.draw(path); } private void drawGrid(Graphics2D g2) { Stroke stroke = g2.getStroke(); drawVerticalGrid(g2); drawHorizontalGrid(g2); if (drawText) { drawVerticalLabels(g2); drawHorizontalLabels(g2); } g2.setStroke(stroke); } private void drawHorizontalLabels(Graphics2D g2) { double axisV = xPositionToPixel(originX); g2.setColor(COLOR_AXIS); for (double y = originY + majorY; y < maxY + majorY; y += majorY) { int position = (int) yPositionToPixel(y); g2.drawString(formatter.format(y), (int) axisV + 5, position); } for (double y = originY - majorY; y > minY - majorY; y -= majorY) { int position = (int) yPositionToPixel(y); g2.drawString(formatter.format(y), (int) axisV + 5, position); } } private void drawHorizontalGrid(Graphics2D g2) { double minorSpacing = majorY / minorY; double axisV = xPositionToPixel(originX); Stroke gridStroke = new BasicStroke(STROKE_GRID); Stroke axisStroke = new BasicStroke(STROKE_AXIS); for (double y = originY + majorY; y < maxY + majorY; y += majorY) { g2.setStroke(gridStroke); g2.setColor(COLOR_MINOR_GRID); for (int i = 0; i < minorY; i++) { int position = (int) yPositionToPixel(y - i * minorSpacing); g2.drawLine(0, position, getWidth(), position); } int position = (int) yPositionToPixel(y); g2.setColor(COLOR_MAJOR_GRID); g2.drawLine(0, position, getWidth(), position); g2.setStroke(axisStroke); g2.setColor(COLOR_AXIS); g2.drawLine((int) axisV - 3, position, (int) axisV + 3, position); } for (double y = originY - majorY; y > minY - majorY; y -= majorY) { g2.setStroke(gridStroke); g2.setColor(COLOR_MINOR_GRID); for (int i = 0; i < minorY; i++) { int position = (int) yPositionToPixel(y + i * minorSpacing); g2.drawLine(0, position, getWidth(), position); } int position = (int) yPositionToPixel(y); g2.setColor(COLOR_MAJOR_GRID); g2.drawLine(0, position, getWidth(), position); g2.setStroke(axisStroke); g2.setColor(COLOR_AXIS); g2.drawLine((int) axisV - 3, position, (int) axisV + 3, position); } } private void drawVerticalLabels(Graphics2D g2) { double axisH = yPositionToPixel(originY); FontMetrics metrics = g2.getFontMetrics(); g2.setColor(COLOR_AXIS); for (double x = originX + majorX; x < maxX + majorX; x += majorX) { int position = (int) xPositionToPixel(x); g2.drawString(formatter.format(x), position, (int) axisH + metrics.getHeight()); } for (double x = originX - majorX; x > minX - majorX; x -= majorX) { int position = (int) xPositionToPixel(x); g2.drawString(formatter.format(x), position, (int) axisH + metrics.getHeight()); } } private void drawVerticalGrid(Graphics2D g2) { double minorSpacing = majorX / minorX; double axisH = yPositionToPixel(originY); Stroke gridStroke = new BasicStroke(STROKE_GRID); Stroke axisStroke = new BasicStroke(STROKE_AXIS); for (double x = originX + majorX; x < maxX + majorX; x += majorX) { g2.setStroke(gridStroke); g2.setColor(COLOR_MINOR_GRID); for (int i = 0; i < minorX; i++) { int position = (int) xPositionToPixel(x - i * minorSpacing); g2.drawLine(position, 0, position, getHeight()); } int position = (int) xPositionToPixel(x); g2.setColor(COLOR_MAJOR_GRID); g2.drawLine(position, 0, position, getHeight()); g2.setStroke(axisStroke); g2.setColor(COLOR_AXIS); g2.drawLine(position, (int) axisH - 3, position, (int) axisH + 3); } for (double x = originX - majorX; x > minX - majorX; x -= majorX) { g2.setStroke(gridStroke); g2.setColor(COLOR_MINOR_GRID); for (int i = 0; i < minorX; i++) { int position = (int) xPositionToPixel(x + i * minorSpacing); g2.drawLine(position, 0, position, getHeight()); } int position = (int) xPositionToPixel(x); g2.setColor(COLOR_MAJOR_GRID); g2.drawLine(position, 0, position, getHeight()); g2.setStroke(axisStroke); g2.setColor(COLOR_AXIS); g2.drawLine(position, (int) axisH - 3, position, (int) axisH + 3); } } private void drawAxis(Graphics2D g2) { double axisH = yPositionToPixel(originY); double axisV = xPositionToPixel(originX); g2.setColor(COLOR_AXIS); Stroke stroke = g2.getStroke(); g2.setStroke(new BasicStroke(STROKE_AXIS)); g2.drawLine(0, (int) axisH, getWidth(), (int) axisH); g2.drawLine((int) axisV, 0, (int) axisV, getHeight()); FontMetrics metrics = g2.getFontMetrics(); g2.drawString(formatter.format(0.0), (int) axisV + 5, (int) axisH + metrics.getHeight()); g2.setStroke(stroke); } protected void setupGraphics(Graphics2D g2) { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } protected void paintBackground(Graphics2D g2) { g2.setColor(COLOR_BACKGROUND); g2.fill(g2.getClipBounds()); } private class DrawableEquation { private AbstractEquation equation; private Color color; DrawableEquation(AbstractEquation equation, Color color) { this.equation = equation; this.color = color; } AbstractEquation getEquation() { return equation; } Color getColor() { return color; } } private class ZoomHandler implements MouseWheelListener { public void mouseWheelMoved(MouseWheelEvent e) { double distanceX = maxX - minX; double distanceY = maxY - minY; double cursorX = minX + distanceX / 2.0; double cursorY = minY + distanceY / 2.0; int rotation = e.getWheelRotation(); if (rotation < 0) { distanceX /= COEFF_ZOOM; distanceY /= COEFF_ZOOM; } else { distanceX *= COEFF_ZOOM; distanceY *= COEFF_ZOOM; } minX = cursorX - distanceX / 2.0; maxX = cursorX + distanceX / 2.0; minY = cursorY - distanceY / 2.0; maxY = cursorY + distanceY / 2.0; repaint(); } } private class PanHandler extends MouseAdapter { @Override public void mousePressed(MouseEvent e) { dragStart = e.getPoint(); } } private class PanMotionHandler extends MouseMotionAdapter { @Override public void mouseDragged(MouseEvent e) { Point dragEnd = e.getPoint(); double distance = xPixelToPosition(dragEnd.getX()) - xPixelToPosition(dragStart.getX()); minX -= distance; maxX -= distance; distance = yPixelToPosition(dragEnd.getY()) - yPixelToPosition(dragStart.getY()); minY -= distance; maxY -= distance; repaint(); dragStart = dragEnd; } } public List<DrawableEquation> getEquations() { return equations; } }