de.invation.code.toval.graphic.diagrams.panels.ScatterChartPanel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of TOVAL Show documentation
Show all versions of TOVAL Show documentation
TOVAL comprises a set of java classes for common programming issues. It includes utils for arrays, lists, sets and collections for convenient handling and modification, but also support for mathematic definitions concerning logic (clauses + resolution) together with some algorithms for permutations, powersets and resolution. Additionally it contains a number of types for multisets, matrices with object keys and much more.
The newest version!
package de.invation.code.toval.graphic.diagrams.panels;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.util.ArrayList;
import java.util.HashMap;
import javax.swing.JPanel;
import de.invation.code.toval.graphic.component.DisplayFrame;
import de.invation.code.toval.graphic.diagrams.models.ChartModel;
import de.invation.code.toval.graphic.diagrams.models.ChartModel.ValueDimension;
import de.invation.code.toval.graphic.util.GraphicUtils;
/**
* Basic panel for 2-dimensional diagrams.
* Values for different coordinate axes are kept separately.
* This gives users the chance to define different kinds of diagrams by changing the way
* values are depicted inside the diagram itself.
* The standard behavior defined in this class is that the coordinates of depicted points
* directly correspond to the given value sets.
* Diagram panels can basically operate in two different modes: zero-based or not zero-based.
* In zero-based mode the coordinate axes always start at zero and end near the highest value for the corresponding dimensions.
* In non-zero mode the axes start near the smallest values.
*
* @author Thomas Stocker
*/
@SuppressWarnings("serial")
public class ScatterChartPanel extends JPanel {
/**
* Spaces between the top, bottom, left and right side of the enclosing panel and the painting area of the diagram itself.
*/
protected Insets spacing = new Insets(40,40,40,40);
/**
* Default text distance (i.e. between text and coordinate axes).
*/
protected int textSpacing = 5;
/**
* Indicates if the diagram is painted in a zero-based way.
* If zero-based, the coordinates always start with zero (possibly discarding values lower than zero)
* and end approximately with the highest value for the corresponding dimension.
* Otherwise it will start with the lowest value.
*/
protected boolean zeroBased = true;
/**
* Indicates what kind of tick values are valid.
* The default behavior is to use floating-point values.
*/
private HashMap onlyIntegerTicks = new HashMap();
/**
* Defines the painting region of the diagram (where axes and values are drawed on).
* It is calculated out of the available space of the diagram panel and its spacing.
*/
private PaintingRegion paintingRegion = new PaintingRegion();
/**
* Tick information (description information) for coordinate axes.
* TickInfo-objects encapsulate helpful information about tick values,
* such as the first and last tick, tick range and the format used for printing/drawing tick values.
*/
protected HashMap tickInfo = new HashMap(2);
/**
* Indicates for which dimensions axes are painted.
*/
private HashMap paintDimAxis = new HashMap();
/**
* Indicates if lines are painted between values.
*/
private boolean paintLines = false;
/**
* Underlying diagram.
*/
protected ChartModel,?> diagram;
/**
* Generates a new diagram using the given values for coordinate axes X and Y.
*/
public ScatterChartPanel(ChartModel,?> diagram) {
this(diagram, true);
}
/**
* Generates a new diagram using the given values for coordinate axes X and Y.
* @param zeroBased Operation mode
* @see #zeroBased
*/
public ScatterChartPanel(ChartModel,?> diagram, boolean zeroBased) {
this(diagram, zeroBased, false, false);
}
/**
* Generates a new diagram using the given values for coordinate axes X and Y.
* @param zeroBased Operation mode
* @param onlyIntegerTicksX If true only integers are allowed as tick values
* @param onlyIntegerTicksY If true only integers are allowed as tick values
* @see #zeroBased
*/
public ScatterChartPanel(ChartModel,?> diagram, boolean zeroBased, boolean onlyIntegerTicksX, boolean onlyIntegerTicksY) {
this(diagram, zeroBased, onlyIntegerTicksX, onlyIntegerTicksY, true, true);
}
/**
* Generates a new diagram using the given values for coordinate axes X and Y.
* @param zeroBased Operation mode
* @param paintDimAxisX Indicates if an axis is painted for dimension X
* @param paintDimAxisY Indicates if an axis is painted for dimension Y
* @param onlyIntegerTicksX If true only integers are allowed as tick values
* @param onlyIntegerTicksY If true only integers are allowed as tick values
* @see #zeroBased
*/
public ScatterChartPanel(ChartModel,?> diagram, boolean zeroBased, boolean onlyIntegerTicksX, boolean onlyIntegerTicksY, boolean paintDimAxisX, boolean paintDimAxisY) {
this.diagram = diagram;
this.zeroBased = zeroBased;
onlyIntegerTicks.put(ValueDimension.X, onlyIntegerTicksX);
onlyIntegerTicks.put(ValueDimension.Y, onlyIntegerTicksY);
tickInfo.put(ValueDimension.X, new TickInfo(diagram.getValues(ValueDimension.X).min().doubleValue(), diagram.getValues(ValueDimension.X).max().doubleValue(), zeroBased));
tickInfo.put(ValueDimension.Y, new TickInfo(diagram.getValues(ValueDimension.Y).min().doubleValue(), diagram.getValues(ValueDimension.Y).max().doubleValue(), zeroBased));
paintDimAxis.put(ValueDimension.X, paintDimAxisX);
paintDimAxis.put(ValueDimension.Y, paintDimAxisY);
if(onlyIntegerTicksX){
setTickSpacing(ValueDimension.X, 1, false);
}
if(onlyIntegerTicksY){
setTickSpacing(ValueDimension.Y, 1, false);
}
setBackground(getBackground());
}
@Override
public Color getBackground() {
return Color.white;
}
/**
* Returns the range of the maintained values for the given dimension.
* @param dim Reference dimension for range extraction
* @return Range of the maintained values for the given dimension
*/
protected double getRange(ValueDimension dim) {
return zeroBased ? diagram.getValues(dim).max().doubleValue() : diagram.getValues(dim).range();
}
/**
* Returns the minimum value of the maintained values for the given dimension.
* @param dim Reference dimension for minimum extraction
* @return Minimum of the maintained values for the given dimension
*/
protected double getMinValue(ValueDimension dim) {
return zeroBased ? 0.0 : diagram.getValues(dim).min().doubleValue();
}
/**
* Returns the maximum value of the maintained values for the given dimension.
* @param key Reference dimension for maximum extraction
* @return Maximum of the maintained values for the given dimension
*/
protected double getMaxValue(ValueDimension key) {
return diagram.getValues(key).max().doubleValue();
}
/**
* Returns the painting region of the diagram (where axes and values are drawed on).
* It is calculated out of the available space of the diagram panel and its spacing.
* @return Region used for drawing axes and values
*/
protected PaintingRegion getPaintingRegion() {
return paintingRegion;
}
/**
* Returns the text distance (i.e. between text and coordinate axes).
* @return Distance between text and other diagram-components
*/
protected int getTextSpacing() {
return textSpacing;
}
/**
* Returns the preferred size of this diagram which is calculated on the basis of the
* required space for ticks and the diagram spacing.
*/
@Override
public Dimension getPreferredSize() {
return new Dimension(getRequiredPoints(ValueDimension.X) + spacing.left + spacing.right,
getRequiredPoints(ValueDimension.Y) + spacing.top + spacing.bottom);
}
/**
* Returns tick information for coordinate axes of the given dimension.
* @param dim Reference dimension for tick information
* @return tick information for the given dimension
*/
protected TickInfo getTickInfo(ValueDimension dim) {
return tickInfo.get(dim);
}
/**
* Sets the tick spacing for the coordinate axis of the given dimension.
* minorTickSpacing sets the minor tick spacing,
* major tick spacing is a multiple of minor tick spacing and determined with the help of a multiplicator.
* @param dim Reference dimension for calculation
* @param minorTickSpacing Minor tick spacing
* @see TickInfo#getTickMultiplicator()
*/
public void setTickSpacing(ValueDimension dim, double minorTickSpacing, boolean repaint) {
setTickSpacing(dim, minorTickSpacing, getTickInfo(dim).getTickMultiplicator(), repaint);
}
/**
* Sets the tick spacing for the coordinate axis of the given dimension.
* minorTickSpacing sets the minor tick spacing,
* major tick spacing is a multiple of minor tick spacing and determined with the help of multiplicator
* @param dim Reference dimension for calculation
* @param minorTickSpacing Minor tick spacing
* @param multiplicator Multiplicator for detrermining the major tick spacing.
*/
public void setTickSpacing(ValueDimension dim, double minorTickSpacing, int multiplicator, boolean repaint) {
double tickSpacing = minorTickSpacing;
if(onlyIntegerTicks.get(dim)) {
tickSpacing = (int) tickSpacing;
if (tickSpacing == 0) tickSpacing = 1;
}
getTickInfo(dim).setTickSpacing(tickSpacing, multiplicator);
if(repaint){
revalidate();
repaint();
}
}
/**
* Sets the operation mode.
* In zero-based mode coordinate axes always start at 0 and end near the maximum value of the corresponding dimension.
* Otherwise axes start at the minimum value.
* @param zeroBased Operation mode
*/
public void setZeroBased(boolean zeroBased) {
if(zeroBased != this.zeroBased) {
this.zeroBased = zeroBased;
for(ValueDimension dim: tickInfo.keySet())
tickInfo.get(dim).setZeroBased(zeroBased);
}
}
/**
* Sets the painting lines mode.
* If set to true, plines between values are painted.
* @param paintLines
*/
public void setPaintLines(boolean paintLines){
this.paintLines = paintLines;
}
/**
* Determines the required space (in points) for painting ticks for the coordinate axis of the given dimension.
* The basic assumption here is, that the first dimension (X) is drawed horizontally and the second dimension (Y) is drawed vertically.
* @param dim Reference dimension for space calculation
* @return Required points for painting tick information
*/
protected int getRequiredPoints(ValueDimension dim) {
int requiredPointsPerTick = getGraphics().getFontMetrics().getHeight();
if(dim == ValueDimension.X) {
int first = getGraphics().getFontMetrics().stringWidth(String.format(getTickInfo(dim).getFormat(), getTickInfo(dim).getFirstTick()));
int last = getGraphics().getFontMetrics().stringWidth(String.format(getTickInfo(dim).getFormat(), getTickInfo(dim).getLastTick()));
requiredPointsPerTick = Math.max(first, last)+2;
}
return requiredPointsPerTick*getTickInfo(dim).getTickNumber();
}
/**
* Overrides the super-method paintComponent to incorporate axes, ticks and values.
*/
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
getPaintingRegion().update();
paintAxes(g);
paintTicks(g);
paintValues(g, paintLines);
}
/**
* Paints axes for all maintained dimensions (in this case two).
* Has to be overridden by subclasses, if more than two dimensions have to be considered.
* @param g Graphics context.
*/
protected void paintAxes(Graphics g) {
Point origin = getPaintingRegion().getBottomLeft();
if(isAxisPaintedfor(ValueDimension.X)) {
Point xEnd = getXAxisEnd();
g.drawLine(origin.x, origin.y, xEnd.x, xEnd.y);
}
if(isAxisPaintedfor(ValueDimension.Y)) {
Point yEnd = getYAxisEnd();
g.drawLine(origin.x, origin.y, yEnd.x, yEnd.y);
}
}
public boolean isAxisPaintedfor(ValueDimension dim) {
return paintDimAxis.get(dim);
}
/**
* Paints tick information for all maintained dimensions (in this case two)
* @param g Graphics context
*/
protected void paintTicks(Graphics g) {
if(isAxisPaintedfor(ValueDimension.X))
paintTicks(g, ValueDimension.X);
if(isAxisPaintedfor(ValueDimension.Y))
paintTicks(g, ValueDimension.Y);
}
/**
* Paints tick information for the coordinate axis of the given dimension.
* @param g Graphics context
* @param dim Reference dimension for tick painting
*/
protected void paintTicks(Graphics g, ValueDimension dim) {
for(int i=0; i
* This number is equal for each dimension.
*/
protected int getValueCount() {
return diagram.getValueCount(ValueDimension.X);
}
/**
* Returns the end-point of the X-axis (axis for the first dimension).
* Starting-point is always the painting regions' bottom left edge.
* Default is the bottom right edge of the painting region. This method can be overridden
* to introduce additional spacing.
* @return End-point used for painting the X-axis
*/
protected Point getXAxisEnd() {
return getPaintingRegion().getBottomRight();
}
/**
* Returns the end-point of the Y-axis (axis for the second dimension).
* Starting-point is always the painting regions' bottom left edge.
* Default is the top left edge of the painting region. This method can be overridden
* to introduce additional spacing.
* @return End-point used for painting the X-axis
*/
protected Point getYAxisEnd() {
return getPaintingRegion().getTopLeft();
}
/**
* Determines the point coordinates for a given value-vector on the base of the maintained dimensions.
* Given two dimensions X and Y with values x1,...,xn and y1,...,yn this method just returns a point
* P(xi,yi) which matches the desired behavior of a standard 2-dimensional diagrams such as graph-plots.
* For changing the way these points are determined it is recommended to override the methods {@link #getXFor(Number)} and {@link #getXFor(Number)} rather than this method,
* since it simply combines the values of these two methods.
* @param index Index for values within the maintained value lists for different dimensions
* @return Point determined on the basis of maintained values
* @see #getXFor(Number)
* @see #getYFor(Number)
*/
protected Point getPointFor(int index) {
return new Point(getXFor(diagram.getValue(ValueDimension.X, index)), getYFor(diagram.getValue(ValueDimension.Y, index)));
}
/**
* Returns the X-coordinate for a given value of the first dimension (X),
* considering the actual operation-mode {@link #zeroBased}.
* This implementation simply returns a value that reflects the position of the given value with respect to the coordinate axis X.
* @param value Value of the maintained X-dimension
* @return X-coordinate for the given value
*/
protected Integer getXFor(Number value) {
double offset = zeroBased ? 0.0 : getTickInfo(ValueDimension.X).getFirstTick();
return getPaintingRegion().getBottomLeft().x + (int) Math.round((value.doubleValue()-offset)*getPaintingRegion().getUnit(ValueDimension.X));
}
/**
* Returns the Y-coordinate for a given value of the second dimension (Y),
* considering the actual operation-mode {@link #zeroBased}.
* This implementation simply returns a value that reflects the position of the given value with respect to the coordinate axis Y.
* @param value Value of the maintained Y-dimension
* @return Y-coordinate for the given value
*/
protected Integer getYFor(Number value) {
double offset = zeroBased ? 0.0 : getTickInfo(ValueDimension.Y).getFirstTick();
return getPaintingRegion().getBottomLeft().y - (int) Math.round((value.doubleValue()-offset)*getPaintingRegion().getUnit(ValueDimension.Y));
}
/**
* Displays the diagram as content of a newly generated JFrame-instance.
*/
public void asFrame() {
new DisplayFrame(this, false);
}
/**
* Returns an adjustable version of this chart.
* @return An adjustable version of this chart
* @see AdjustableDiagramPanel
*/
public AdjustableDiagramPanel adjustableVersion() {
return new AdjustableDiagramPanel(this);
}
/**
* Class for managing properties of the painting region of a diagram panel.
* @author Thomas Stocker
*/
protected class PaintingRegion {
/**
* Top left corner of the painting region.
*/
private Point topLeft;
/**
* Top right corner of the painting region.
*/
private Point topRight;
/**
* Bottom left corner of the painting region.
*/
private Point bottomLeft;
/**
* Bottom right corner of the painting region.
*/
private Point bottomRight;
/**
* Center of the painting region.
*/
private Point center;
/**
* Width of the painting region.
*/
private int width;
/**
* Height of the painting region.
*/
private int height;
/**
* Keeps measures depending on the actual size of the painting area.
*
* Index 0: unit
* Points(pixels) per value-unit
* Index 1: valueDistance
* Points(pixels) per value-entry
* Index 2: text position (for X a Y-value and vice versa)
*
* Index 1:
* Points(pixels) per value-entry
* Index 2: text position (for X a Y-value and vice versa)
*/
private HashMap> measures = new HashMap>(2);
/**
* Creates a new PaintingRegion.
*/
public PaintingRegion() {
measures.put(ValueDimension.X, new ArrayList(3));
measures.put(ValueDimension.Y, new ArrayList(3));
}
/**
* Updates the stored measures based on the actual size of the diagram.
* Because measures depend on diagram dimensions they have to be recalculated
* on resize events.
*/
public void update() {
width = getWidth()-(spacing.right+spacing.left);
height = getHeight()-(spacing.top+spacing.bottom);
topLeft = new Point(spacing.left, spacing.top);
topRight = new Point(topLeft.x+width, topLeft.y);
bottomLeft = new Point(topLeft.x, topLeft.y+height);
bottomRight = new Point(topRight.x, bottomLeft.y);
center = new Point(topLeft.x+(int)(width/2.0), topLeft.y+(int)(height/2.0));
measures.get(ValueDimension.X).clear();
measures.get(ValueDimension.Y).clear();
measures.get(ValueDimension.X).add(width/getTickInfo(ValueDimension.X).getTickRange());
measures.get(ValueDimension.Y).add(height/getTickInfo(ValueDimension.Y).getTickRange());
measures.get(ValueDimension.X).add(width/(getValueCount() - 1.0));
measures.get(ValueDimension.Y).add(height/(getValueCount() - 1.0));
measures.get(ValueDimension.X).add(bottomLeft.y + getTextSpacing() + getGraphics().getFontMetrics().getHeight() + 0.0);
measures.get(ValueDimension.Y).add(bottomLeft.x - getTextSpacing() + 0.0);
}
/**
* Returns the top left corner of the painting region.
* @return The top left corner of the painting region
*/
public Point getTopLeft() {
return topLeft;
}
/**
* Returns the top right corner of the painting region.
* @return The top right corner of the painting region
*/
public Point getTopRight() {
return topRight;
}
/**
* Returns the bottom left corner of the painting region.
* @return The bottom left corner of the painting region
*/
public Point getBottomLeft() {
return bottomLeft;
}
/**
* Returns the bottom right corner of the painting region.
* @return The bottom right corner of the painting region
*/
public Point getBottomRight() {
return bottomRight;
}
/**
* Returns the center of the painting region.
* @return The center of the painting region
*/
public Point getCenter() {
return center;
}
/**
* Returns the unit-measure (i.e. the points/pixels per value/unit) for the given dimension.
* The unit specifies the per-value space of a dimension in pixels and is based on the value range.
* If there is a value range of 10.2 and there are 200 pixels available for this dimension,
* the unit would be 200/10.2
* @param dim Reference dimension for calculation
* @return The per/value unit in pixels for the given dimension
*/
public double getUnit(ValueDimension dim) {
return measures.get(dim).get(0);
}
/**
* Returns the value distance in pixels for the given dimension.
* This measure describes how much space in pixels lies between one value and another
* if they are arranged with equal distance on a line with a total length of the
* available space for a dimension.
* If there are 200 pixels available for the given dimension and there are 20 values to display,
* the value distance is 200/(20-1).
* @param dim Reference dimension for calculation
* @return Value distance for the given dimension
*/
public int getValueDistance(ValueDimension dim) {
return (int) Math.round(measures.get(dim).get(1));
}
/**
* Returns the Position of the given description string according to the given dimension and reference point.
* If i.e. there is a value for the X-coordinate at position x0, that has a description (the value itself as string or another textual description),
* this method returns the x-Position where the description "desc" can be drawed.
* In this implementation it places the descriptions centered relating to the given reference point.
* @param dim Reference dimension for calculation
* @param str String that has to be drawn
* @param ref Reference position (dimension dependent)
* @return The position of str according to the given dimension dim .
*/
public Point getDescriptionPos(ValueDimension dim, String str, int ref) {
switch(dim) {
case X: return new Point(ref-getGraphics().getFontMetrics().stringWidth(str)/2, (int) Math.round(measures.get(dim).get(2)));
case Y: return new Point((int) Math.round(measures.get(dim).get(2)-getGraphics().getFontMetrics().stringWidth(str)), ref-getGraphics().getFontMetrics().getHeight()/2+8);
default: return null;
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy