/**
 * Copyright 2010-2012 Jingjing Li.
 *
 * This file is part of jplot2d.
 *
 * jplot2d is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or any later version.
 *
 * jplot2d 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 General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with jplot2d. If not, see .
 */
package org.jplot2d.element.impl;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.util.Map;

import org.jplot2d.data.XYGraphData;
import org.jplot2d.element.impl.XYGraphDataChunker.ChunkData;
import org.jplot2d.util.GraphicsUtil;
import org.jplot2d.util.SparseArray;
import org.jplot2d.util.SymbolShape;

/**
 * @author Jingjing Li
 * 
 */
public class XYGraphImpl extends GraphImpl implements XYGraphEx {

	private static Paint DEFAULT_FILL_PAINT = new Color(192, 192, 192, 128);

	/**
	 * The error arrow length in pt
	 */
	private static final float ARROW_LENGTH = 12;

	private static final float ARROW_HEAD_LENGTH = ARROW_LENGTH / 4;

	private final LegendItemEx legendItem;

	private XYGraphData data;

	private boolean symbolVisible = false;

	private boolean lineVisible = true;

	private ChartType chartType = ChartType.LINECHART;

	private SymbolShape symbolShape = SymbolShape.CIRCLE;

	private float symbolSize = 8.0f;

	private Color symbolColor;

	private final SparseArray symbolColors = new SparseArray();

	private BasicStroke lineStroke = new BasicStroke(0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);

	private boolean fillEnabled;

	private Paint fillPaint = DEFAULT_FILL_PAINT;

	private FillClosureType fillClosureType = FillClosureType.SELF;

	private float errorbarCapSize;

	public XYGraphImpl() {
		legendItem = new XYLegendItemImpl();
		legendItem.setParent(this);
	}

	public String getId() {
		if (getParent() != null) {
			return "XYGraph" + getParent().indexOf(this);
		} else {
			return "XYGraph@" + Integer.toHexString(System.identityHashCode(this));
		}
	}

	public XYLegendItemEx getLegendItem() {
		return (XYLegendItemEx) legendItem;
	}

	public String getName() {
		return ((XYLegendItemImpl) getLegendItem()).getText();
	}

	public void setName(String text) {
		((XYLegendItemImpl) getLegendItem()).setText(text);
	}

	public XYGraphData getData() {
		return data;
	}

	public void setData(XYGraphData data) {
		this.data = data;

		redraw(this);

		if (getParent() != null && this.canContribute()) {
			AxisTransformEx xarm = getParent().getXAxisTransform();
			AxisTransformEx yarm = getParent().getYAxisTransform();

			if (xarm != null && xarm.getLockGroup().isAutoRange()) {
				xarm.getLockGroup().reAutoRange();
			}
			if (yarm != null && yarm.getLockGroup().isAutoRange()) {
				yarm.getLockGroup().reAutoRange();
			}
		}
	}

	public boolean isSymbolVisible() {
		return symbolVisible;
	}

	public void setSymbolVisible(boolean symbolVisible) {
		this.symbolVisible = symbolVisible;
		redraw(this);
	}

	public boolean isLineVisible() {
		return lineVisible;
	}

	public void setLineVisible(boolean lineVisible) {
		this.lineVisible = lineVisible;
		redraw(this);
	}

	/**
	 * @return the chart type
	 */
	public ChartType getChartType() {
		return chartType;
	}

	/**
	 * Sets the chart type.
	 * 
	 * @param chartType
	 *            the chart type
	 */
	public void setChartType(ChartType chartType) {
		this.chartType = chartType;
		redraw(this);
	}

	public SymbolShape getSymbolShape() {
		return symbolShape;
	}

	public void setSymbolShape(SymbolShape symbolShape) {
		this.symbolShape = symbolShape;
		redraw(this);
	}

	public float getSymbolSize() {
		return symbolSize;
	}

	public void setSymbolSize(float size) {
		this.symbolSize = size;
		redraw(this);
		getLegendItem().graphSymbolSizeChanged();
	}

	public Color getSymbolColor() {
		return symbolColor;
	}

	public void setSymbolColor(Color color) {
		symbolColor = color;
		redraw(this);
	}

	public Color getIndSymbolColor(int idx) {
		if (symbolColors.size() > 0) {
			return symbolColors.get(idx);
		} else {
			return null;
		}
	}

	public void setIndSymbolColor(int idx, Color color) {
		Color oldColor = getIndSymbolColor(idx);
		if (color == oldColor) {
			return;
		}
		if (color == null || !color.equals(oldColor)) {
			symbolColors.put(idx, color);
			redraw(this);
		}
	}

	public void setIndSymbolColor(int[] idxes, Color color) {
		for (int idx : idxes) {
			symbolColors.put(idx, color);
		}
		redraw(this);
	}

	public void clearIndSymbolColor() {
		if (symbolColors.size() > 0) {
			symbolColors.clear();
			redraw(this);
		}
	}

	public Color getEffectiveSymbolColor() {
		if (getSymbolColor() != null) {
			return getSymbolColor();
		} else {
			return getEffectiveColor();
		}
	}

	public Color getEffectiveSymbolColor(int idx) {
		Color icolor = null;
		if (symbolColors.size() > 0) {
			icolor = symbolColors.get(idx);
		}
		if (icolor != null) {
			return icolor;
		} else {
			return getEffectiveSymbolColor();
		}
	}

	public void thisEffectiveColorChanged() {
		if (isLineVisible() || (isSymbolVisible() && getSymbolColor() == null)) {
			redraw(this);
		}
	}

	public BasicStroke getLineStroke() {
		return lineStroke;
	}

	public void setLineStroke(BasicStroke stroke) {
		this.lineStroke = stroke;
		redraw(this);
	}

	public boolean isFillEnabled() {
		return fillEnabled;
	}

	public void setFillEnabled(boolean fillEnabled) {
		this.fillEnabled = fillEnabled;
		redraw(this);
	}

	public Paint getFillPaint() {
		return fillPaint;
	}

	public void setFillPaint(Paint paint) {
		this.fillPaint = paint;
		redraw(this);
	}

	public FillClosureType getFillClosureType() {
		return fillClosureType;
	}

	public void setFillClosureType(FillClosureType type) {
		this.fillClosureType = type;
		redraw(this);
	}

	public float getErrorbarCapSize() {
		return errorbarCapSize;
	}

	public void setErrorbarCapSize(float size) {
		errorbarCapSize = size;
		redraw(this);
	}

	@Override
	public ComponentEx copyStructure(Map orig2copyMap) {
		XYGraphImpl result = (XYGraphImpl) super.copyStructure(orig2copyMap);

		if (orig2copyMap != null) {
			orig2copyMap.put(getLegendItem(), result.getLegendItem());
		}

		return result;
	}

	@Override
	public void copyFrom(ElementEx src) {
		super.copyFrom(src);

		XYGraphImpl dp = (XYGraphImpl) src;

		this.data = dp.data;
		this.symbolVisible = dp.symbolVisible;
		this.lineVisible = dp.lineVisible;
		this.chartType = dp.chartType;
		this.symbolShape = dp.symbolShape;
		this.symbolSize = dp.symbolSize;
		this.symbolColor = dp.symbolColor;
		this.lineStroke = dp.lineStroke;
		this.fillEnabled = dp.fillEnabled;
		this.fillPaint = dp.fillPaint;
		this.fillClosureType = dp.fillClosureType;
		this.errorbarCapSize = dp.errorbarCapSize;
	}

	public void draw(Graphics2D graphics) {

		if (getParent().getXAxisTransform() == null || getParent().getYAxisTransform() == null) {
			return;
		}
		if (getData() == null) {
			return;
		}

		Graphics2D g = (Graphics2D) graphics.create();
		Shape clip = getPaperTransform().getPtoD(getBounds());
		g.setClip(clip);

		if (isFillEnabled()) {
			if (Thread.interrupted()) {
				g.dispose();
				return;
			}
			fillLineAera((Graphics2D) g);
		}

		if (Thread.interrupted()) {
			g.dispose();
			return;
		}
		drawLineAndSymbol((Graphics2D) g);

		g.dispose();
	}

	private void fillLineAera(Graphics2D g) {
		XYGraphFiller filler = XYGraphFiller.getInstance(this, g.getClipBounds());

		Paint p = getFillPaint();
		filler.fill(g, p);
	}

	/**
	 * Drawing lines by iterating over DataChunker.
	 * 
	 * @param g
	 */
	private void drawLineAndSymbol(Graphics2D g) {

		double scale = getParent().getPaperTransform().getScale();

		XYGraphDataChunker chunker = XYGraphDataChunker.getInstance(this, g.getClipBounds());

		for (ChunkData data : chunker) {
			if (Thread.interrupted()) {
				return;
			}

			// draw lines
			g.setColor(getEffectiveColor());

			if (isLineVisible()) {
				switch (getChartType()) {
				case LINECHART:
					drawLine(g, data.xBuf, data.yBuf, data.size, this, scale);
					break;
				case HISTOGRAM:
					drawHistogram(g, data, this, scale);
					break;
				case HISTOGRAM_EDGE:
					drawEdgeHistogram(g, data.xBuf, data.yBuf, data.size, this, scale);
					break;
				}
			}

			// draw error bar with line color and new stroke
			g.setStroke(GraphicsUtil.scaleStroke(getLineStroke(), scale / 2));

			if (data.xErrorSize != 0) {
				for (int i = 0; i < data.xErrorSize; i++) {
					drawErrorBarX(g, data.xBuf[i], data.yBuf[i], data.xLowBuf[i], data.xHighBuf[i], (float) scale);
				}
			}
			if (data.yErrorSize != 0) {
				for (int i = 0; i < data.yErrorSize; i++) {
					drawErrorBarY(g, data.xBuf[i], data.yBuf[i], data.yLowBuf[i], data.yHighBuf[i], (float) scale);

				}
			}

			// drawing marks may change color and stroke
			if (isSymbolVisible()) {
				drawMarks(g, data.xBuf, data.yBuf, data.size, this, data.markColorBuf, scale);
			}

		}

	}

	/**
	 * Draw a horizontal errorbar for the given point (x,y)
	 * 
	 * @param g
	 *            the Graphics2D
	 * @param x
	 *            the point x
	 * @param y
	 *            the point y
	 * @param low
	 *            the low x of the errorbar
	 * @param high
	 *            the high x of the errorbar
	 * @param scale
	 *            the scale
	 */
	private void drawErrorBarX(Graphics2D g, float x, float y, float low, float high, float scale) {

		// errorbar with arrow
		if (!Float.isInfinite(low) && !Float.isInfinite(high)) {
			g.draw(new Line2D.Float(low, y, high, y));
		} else {
			if (low == Float.NEGATIVE_INFINITY) {
				float head, tail;
				if (high == Float.POSITIVE_INFINITY) {
					tail = x;
				} else {
					tail = high;
				}
				if (x < high) {
					head = x - ARROW_LENGTH * scale;
				} else {
					head = high - ARROW_LENGTH * scale;
				}
				// draw left arrow
				g.draw(new Line2D.Float(tail, y, head, y));
				float ah = ARROW_HEAD_LENGTH * scale;
				g.draw(new Line2D.Float(head, y, head + ah, y - ah));
				g.draw(new Line2D.Float(head, y, head + ah, y + ah));
			}
			if (high == Float.POSITIVE_INFINITY) {
				float head, tail;
				if (low == Float.NEGATIVE_INFINITY) {
					tail = x;
				} else {
					tail = low;
				}
				if (x > low) {
					head = x + ARROW_LENGTH * scale;
				} else {
					head = low + ARROW_LENGTH * scale;
				}
				// draw right arrow
				g.draw(new Line2D.Float(tail, y, head, y));
				float ah = ARROW_HEAD_LENGTH * scale;
				g.draw(new Line2D.Float(head, y, head - ah, y - ah));
				g.draw(new Line2D.Float(head, y, head - ah, y + ah));
			}
		}

		// errorbar cap
		if (errorbarCapSize > 0) {
			float halfcap = errorbarCapSize * scale / 2;
			if (low != x && !Float.isInfinite(low)) {
				g.draw(new Line2D.Float(low, y - halfcap, low, y + halfcap));
			}
			if (high != x && !Float.isInfinite(high)) {
				g.draw(new Line2D.Float(high, y - halfcap, high, y + halfcap));
			}
		}
	}

	/**
	 * Draw a vertical error bar for the given point (x,y)
	 * 
	 * @param g
	 *            the Graphics2D
	 * @param x
	 *            the point x
	 * @param y
	 *            the point y
	 * @param low
	 *            the low y of the errorbar
	 * @param high
	 *            the high y of the errorbar
	 * @param scale
	 *            the scale
	 */
	private void drawErrorBarY(Graphics2D g, float x, float y, float low, float high, float scale) {

		// errorbar with arrow
		if (!Float.isInfinite(low) && !Float.isInfinite(high)) {
			g.draw(new Line2D.Float(x, low, x, high));
		} else {
			if (low == Float.POSITIVE_INFINITY) {
				float head, tail;
				if (high == Float.NEGATIVE_INFINITY) {
					tail = y;
				} else {
					tail = high;
				}
				if (y > high) {
					head = y + ARROW_LENGTH * scale;
				} else {
					head = high + ARROW_LENGTH * scale;
				}
				// draw down arrow
				g.draw(new Line2D.Float(x, tail, x, head));
				float ah = ARROW_HEAD_LENGTH * scale;
				g.draw(new Line2D.Float(x, head, x - ah, head - ah));
				g.draw(new Line2D.Float(x, head, x + ah, head - ah));
			}
			if (high == Float.NEGATIVE_INFINITY) {
				float head, tail;
				if (low == Float.POSITIVE_INFINITY) {
					tail = y;
				} else {
					tail = low;
				}
				if (y < low) {
					head = y - ARROW_LENGTH * scale;
				} else {
					head = low - ARROW_LENGTH * scale;
				}
				// draw up arrow
				g.draw(new Line2D.Float(x, tail, x, head));
				float ah = ARROW_HEAD_LENGTH * scale;
				g.draw(new Line2D.Float(x, head, x - ah, head + ah));
				g.draw(new Line2D.Float(x, head, x + ah, head + ah));
			}
		}

		// errorbar cap
		if (errorbarCapSize > 0) {
			float halfcap = errorbarCapSize * scale / 2;
			if (low != y && !Float.isInfinite(low)) {
				g.draw(new Line2D.Float(x - halfcap, low, x + halfcap, low));
			}
			if (high != y && !Float.isInfinite(high)) {
				g.draw(new Line2D.Float(x - halfcap, high, x + halfcap, high));
			}
		}
	}

	static void drawLine(Graphics2D g, float[] xout, float[] yout, int lsize, XYGraphEx graph, double scale) {
		// set line stroke
		g.setStroke(GraphicsUtil.scaleStroke(graph.getLineStroke(), scale));
		// draw lines
		Path2D.Float gp = new Path2D.Float();
		gp.moveTo(xout[0], yout[0]);
		for (int i = 1; i < lsize; i++) {
			gp.lineTo(xout[i], yout[i]);
		}
		g.draw(gp);
	}

	/**
	 * Draw Histogram lines that data points is on the level center.
	 */
	private static void drawHistogram(Graphics2D g, ChunkData data, XYGraphEx graph, double scale) {

		float[] x = data.xBuf;
		float[] y = data.yBuf;
		int lsize = data.size;

		int nsize = lsize * 2;
		float[] xout = new float[nsize];
		float[] yout = new float[nsize];

		xout[0] = x[0];
		yout[0] = y[0];
		int j = 1; // the next idx of xout/yout
		/* the 2 idx of (x,y) array */
		for (int a = 0, b = 1; b < lsize; a++, b++) {
			float xm = (x[a] + x[b]) / 2;
			xout[j] = xm;
			yout[j] = y[a];
			j++;
			xout[j] = xm;
			yout[j] = y[b];
			j++;
		}
		xout[j] = x[lsize - 1];
		yout[j] = y[lsize - 1];

		drawLine(g, xout, yout, nsize, graph, scale);
	}

	/**
	 * Draw Histogram lines that data points is on the level edge.
	 * 
	 * @param g
	 * @param x
	 * @param y
	 * @param lsize
	 *            the number of points
	 * @param graph
	 */
	private static void drawEdgeHistogram(Graphics2D g, float[] x, float[] y, int lsize, XYGraphEx graph, double scale) {
		int nsize = lsize * 2 - 1;
		float[] xout = new float[nsize];
		float[] yout = new float[nsize];

		int hi = 0;
		for (int i = 0; i < lsize - 1; i++) {
			xout[hi] = x[i];
			yout[hi] = y[i];
			hi++;
			xout[hi] = x[i + 1];
			yout[hi] = y[i];
			hi++;
		}
		xout[hi] = x[lsize - 1];
		yout[hi] = y[lsize - 1];

		drawLine(g, xout, yout, nsize, graph, scale);
	}

	/**
	 * Draw a mark at the requested location. This routine is used by LineCartesianGraph and LineKey.
	 * 
	 * @param g
	 *            Graphics object
	 * @param xp
	 *            horizontal coordinate
	 * @param yp
	 *            vertical coordinate
	 * @param graph
	 *            line attribute
	 */
	static void drawMarks(Graphics2D g, float[] xp, float[] yp, int npoints, XYGraphEx graph, Color[] colors,
			double scale) {

		// use half of line stroke to draw marks
		double lw = graph.getLineStroke().getLineWidth() * scale / 2;
		g.setStroke(new BasicStroke((float) lw, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));

		SymbolShape ss = graph.getSymbolShape();
		for (int i = 0; i < npoints; i++) {
			/* not draw mark if the point is brought to clip border */
			if (colors[i] == null) {
				continue;
			}

			g.setColor(colors[i]);

			double safScale = scale * graph.getSymbolSize();
			AffineTransform maf = AffineTransform.getTranslateInstance(xp[i], yp[i]);
			maf.scale(safScale, -safScale);

			ss.draw(g, maf);
		}
	}

}

 

Ads help maintain this website.