/*
 * This code is part of Project Waterloo from King's College London
 * 
 *
 * Copyright King's College London  2011-
 *
 * @author Malcolm Lidierth, King's College London  [Contact]
 *
 * Project Waterloo 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
 * (at your option) any later version.
 *
 * Project Waterloo 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 *
 */
package kcl.waterloo.graphics.plots2D;

import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import static kcl.waterloo.graphics.plots2D.BarExtra.MODE.GROUPED;
import static kcl.waterloo.graphics.plots2D.BarExtra.MODE.HIST;
import static kcl.waterloo.graphics.plots2D.BarExtra.MODE.HISTC;
import static kcl.waterloo.graphics.plots2D.BarExtra.MODE.STACKED;
import static kcl.waterloo.graphics.plots2D.BarExtra.ORIENTATION.HORIZONTAL;
import static kcl.waterloo.graphics.plots2D.BarExtra.ORIENTATION.VERTICAL;
import kcl.waterloo.math.ArrayMath;

/**
 * GJBar supports bar plots and histograms.
 *
 * GJBar is unusual in supporting swapping of data displayed along the x- and
 * y-axes to support both vertical and horizontal bar charts.
 *
 * 
    *
  • For vertical bars.
  • *
      *
    • The XData represent the offsets on the x-axis.
    • *
    • the YData represent their heights.
    • *
    *
  • For horizontal bars.
  • *
      *
    • The XData represent the offsets on the y-axis.
    • *
    • the YData represent their horizontal lengths.
    • *
    *
* * This support is limited to the display of the bars through the x,y data * pairs. If you want e.g. to add categories to an axis, you need to address * those calls to the axis appropriate to the alignment so with horizontal bars, * you should still call getYData().setCategory(...). * * However, the behaviour of the {@code getDataRange()} has been overridden to * account for the alignment (which facilitates auto ranging of the axes). * * @author ML */ public class GJBar extends GJAbstractPlot { public GJBar() { super(); } public GJBar(GJAbstractPlot p) { super(p); } /** * Factory method for creating a bar chart. * * @return the GJBar instance */ public static GJPlotInterface createInstance() { GJPlotInterface instance = GJAbstractPlot.createInstance(new GJBar()); instance.getDataModel().setExtraObject(new BarExtra()); return instance; } @Override public final void paintPlot(Graphics2D g2) { if (getParentGraph() == null || getDataModel() == null) { return; } super.paintPlot(g2); getScreenDataArray().clear(); final double[] x = this.getXData().getRawDataValues(); double[] y = this.getYData().getRawDataValues(); ArrayList rArray = createBoxes(x, y); int N = getMultiplexLength(); FontMetrics metrics = g2.getFontMetrics(); BarExtra extra = (BarExtra) getDataModel().getExtraObject(); ArrayList labels = extra.getLabels(); AffineTransform af = (AffineTransform) g2.getTransform().clone(); Font font = g2.getFont(); if (extra.getFont() != null) { g2.setFont(extra.getFont()); } for (int idx = 0; idx < N; idx++) { Path2D drawPath = new Path2D.Double(); for (int i = idx; i < rArray.size(); i += N) { Shape path = rArray.get(i); if (path != null) { if (getFill() != null) { g2.setPaint(getVisualModel().getFill().get(i)); g2.fill(path); } g2.setPaint(getVisualModel().getEdgeColor().get(i)); g2.setStroke(getVisualModel().getEdgeStroke().get(i)); g2.draw(path); if (i < labels.size()) { String s = labels.get(i); if (s != null) { /** * Add any label */ if (extra.getLabelOrientation() == BarExtra.ORIENTATION.HORIZONTAL) { if (extra.getFontBackground().size() > 0) { Paint p = g2.getPaint(); g2.setPaint(extra.getFontBackground(i)); g2.fillRect(path.getBounds().x + path.getBounds().width / 2 - metrics.stringWidth(s) / 2 - 1, path.getBounds().y + path.getBounds().height / 2 - metrics.getHeight() / 2 - 1, metrics.stringWidth(s) + 2, metrics.getHeight() + 2); g2.setPaint(p); } int xl = path.getBounds().x + path.getBounds().width / 2 - metrics.stringWidth(s) / 2; int yl = path.getBounds().y + path.getBounds().height / 2 + metrics.getAscent() / 2; if (extra.getFontForeground().size() > 0) { Paint p = g2.getPaint(); g2.setPaint(extra.getFontForeground(i)); g2.drawString(s, xl, yl); g2.setPaint(p); } } else { if (extra.getFontBackground().size() > 0) { Paint p = g2.getPaint(); g2.setPaint(extra.getFontBackground(i)); g2.fillRect(path.getBounds().x + path.getBounds().width / 2 - metrics.getHeight() / 2 - 1, path.getBounds().y + path.getBounds().height / 2 - metrics.stringWidth(s) / 2 - 1, metrics.getHeight() + 2, metrics.stringWidth(s) + 2); g2.setPaint(p); } int xl = path.getBounds().x + path.getBounds().width / 2 + metrics.getAscent() / 2; int yl = path.getBounds().y + path.getBounds().height / 2 + metrics.stringWidth(s) / 2; g2.rotate(-Math.PI / 2d, xl, yl); if (extra.getFontForeground().size() > 0) { Paint p = g2.getPaint(); g2.setPaint(extra.getFontForeground(i)); g2.drawString(s, xl, yl); g2.setPaint(p); } g2.setTransform(af); } } } drawPath.append(path, false); } } getScreenDataArray().add(drawPath); } g2.setFont(font); } /** * Returns an ArrayList of rectangles as Shape instances. * * The position and dimensions of each rectangle is set by the values in the * XData and YData values. * * @return an ArrayList, each element of which describes a rectangle */ private ArrayList createBoxes(final double[] x, final double[] y) { ArrayList array = new ArrayList(); double[] right = new double[x.length]; double[] upper = new double[x.length]; double[] left = new double[x.length]; double[] lower = new double[x.length]; BarExtra extra = (BarExtra) getDataModel().getExtraObject(); if (extra == null) { return null; } double baseValue = extra.getBaseValue(); switch (extra.getOrientation()) { case HORIZONTAL: left = ArrayMath.min(y, baseValue); right = ArrayMath.max(y, baseValue); break; case VERTICAL: default: lower = ArrayMath.min(y, baseValue); upper = ArrayMath.max(y, baseValue); } final double[] dXl = new double[x.length]; final double[] dXr = new double[x.length]; switch (extra.getMode()) { case HIST: dXr[0] = (x[1] - x[0]) / 2d; dXl[0] = dXr[0]; for (int k = 1; k < x.length - 1; k++) { dXl[k] = (x[k] - x[k - 1]) / 2d; dXr[k] = (x[k + 1] - x[k]) / 2d; } dXl[x.length - 1] = dXr[x.length - 2]; dXr[x.length - 1] = dXl[x.length - 1]; break; case HISTC: dXl[0] = 0d; dXr[0] = x[1] - x[0]; for (int k = 1; k < x.length - 1; k++) { dXl[k] = 0d; dXr[k] = x[k + 1] - x[k]; } dXl[x.length - 1] = 0; dXr[x.length - 1] = x[x.length - 1] - x[x.length - 2]; break; case GROUPED: case STACKED: default: ArrayMath.filli(dXl, extra.getBarWidth() / 2d); ArrayMath.filli(dXr, extra.getBarWidth() / 2d); } int N = getMultiplexLength(); // Deal with stacking if (N > 1 && ((BarExtra) getDataModel().getExtraObject()).getMode() == BarExtra.MODE.STACKED) { double[] posLimit = new double[x.length / N + 1]; double[] negLimit = new double[x.length / N + 1]; ArrayMath.filli(posLimit, 0d); ArrayMath.filli(negLimit, 0d); for (int idx = 0; idx < x.length; idx++) { int ref = (int) Math.floor((double) idx / (double) N); switch (extra.getOrientation()) { case HORIZONTAL: if (y[idx] >= 0) { left[idx] += posLimit[ref]; right[idx] += posLimit[ref]; } else { left[idx] += negLimit[ref]; right[idx] += negLimit[ref]; } break; case VERTICAL: if (y[idx] >= 0) { lower[idx] += posLimit[ref]; upper[idx] += posLimit[ref]; } else { lower[idx] += negLimit[ref]; upper[idx] += negLimit[ref]; } } if (y[idx] >= 0) { posLimit[ref] += y[idx]; } else { negLimit[ref] += y[idx]; } } } switch (extra.getOrientation()) { case HORIZONTAL: left = getParentGraph().xPositionToPixeli(getParentGraph().getXTransform().getData(left)); right = getParentGraph().xPositionToPixeli(getParentGraph().getXTransform().getData(right)); if (N > 1 && ((BarExtra) getDataModel().getExtraObject()).getMode() == BarExtra.MODE.STACKED) { // Align stacked bars for (int k = 0; k < x.length; k++) { int ref = (int) Math.floor((double) k / (double) N) * N; lower[k] = getParentGraph().yPositionToPixel(getParentGraph().getYTransform().getData(x[ref] - dXl[k])); upper[k] = getParentGraph().yPositionToPixel(getParentGraph().getYTransform().getData(x[ref] + dXr[k])); } } else { lower = getParentGraph().yPositionToPixeli(getParentGraph().getYTransform().getData(ArrayMath.sub(x, dXl))); upper = getParentGraph().yPositionToPixeli(getParentGraph().getYTransform().getData(ArrayMath.add(x, dXr))); } break; case VERTICAL: default: lower = getParentGraph().yPositionToPixeli(getParentGraph().getYTransform().getData(lower)); upper = getParentGraph().yPositionToPixeli(getParentGraph().getYTransform().getData(upper)); if (N > 1 && ((BarExtra) getDataModel().getExtraObject()).getMode() == BarExtra.MODE.STACKED) { // Align stacked bars for (int k = 0; k < x.length; k++) { int ref = (int) Math.floor((double) k / (double) N) * N; left[k] = getParentGraph().xPositionToPixel(getParentGraph().getXTransform().getData(x[ref] - dXl[k])); right[k] = getParentGraph().xPositionToPixel(getParentGraph().getXTransform().getData(x[ref] + dXr[k])); } } else { left = getParentGraph().xPositionToPixeli(getParentGraph().getXTransform().getData(ArrayMath.sub(x, dXl))); right = getParentGraph().xPositionToPixeli(getParentGraph().getXTransform().getData(ArrayMath.add(x, dXr))); } } for (int i = 0; i < x.length; i++) { if (((Double.isNaN(left[i]) || Double.isNaN(right[i])) || Double.isNaN(upper[i])) || Double.isNaN(lower[i])) { array.add(null); } else { array.add(new Rectangle2D.Double(left[i], upper[i], right[i] - left[i], lower[i] - upper[i])); } } return array; } /** * This overrides the super class method providing support for horizontal * bars too by swapping the x- and y- data. * * The behaviour of other methods that call getDataRange will change as a * consequence. * * @return */ @Override public final Rectangle2D getDataRange() { if (this.getXDataValues() != null && this.getYDataValues() != null) { double[] x, y; if (((BarExtra) getDataModel().getExtraObject()).getOrientation() == BarExtra.ORIENTATION.VERTICAL) { x = ArrayMath.minmax(this.getXDataValues()); y = ArrayMath.minmax(this.getYDataValues()); } else { y = ArrayMath.minmax(this.getXDataValues()); x = ArrayMath.minmax(this.getYDataValues()); } Rectangle2D r = new Rectangle.Double(x[0], y[0], x[1] - x[0], y[1] - y[0]); Rectangle2D r2; for (GJPlotInterface p : getPlotList()) { r2 = p.getDataRange(); r = r.createUnion(r2); } return r; } else { return new Rectangle.Double(0, 0, 0, 0); } } }

Ads help maintain this website.