// BlogBridge -- RSS feed reader, manager, and web based service // Copyright (C) 2002-2007 by R. Pito Salas // // This program is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free Software Foundation; // either version 2 of the License, or (at your option) any later version. // // This program 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 Public License for more details. // // You should have received a copy of the GNU General Public License along with this program; // if not, write to the Free Software Foundation, Inc., 59 Temple Place, // Suite 330, Boston, MA 02111-1307 USA // // Contact: R. Pito Salas // mailto:pitosalas@users.sourceforge.net // More information: about BlogBridge // http://www.blogbridge.com // http://sourceforge.net/projects/blogbridge // // $Id: LineChart.java,v 1.5 2007/10/15 08:57:43 spyromus Exp $ // package com.salas.bb.utils.uif.charts; import javax.swing.*; import java.awt.*; /** * Shows data on a line chart. Each chart has two axes and the values (dots) * connected by lines. * * The chart is static meaning that the data is given upon initialization * and can't be changed later. The chart has several properties for customization * that also can't be changed in real time. This is the simplification * assumption. */ public class LineChart extends JComponent { /** 360 degrees. */ private static final int ANGLE_360 = 360; /** Configuration. */ private final LineChartConfig config; /** Data to plot. */ private final LineChartData data; /** Preferred size. */ private final Dimension prefSize; /** Maximum value among all given data values. */ private final int maxValue; /** Minimum value among all given data values. */ private final int minValue; /** Median between the max and min. */ private final int medianValue; /** * Creates a line chart with default configuration. * * @param data data to plot. */ public LineChart(LineChartData data) { this(data, new LineChartConfig()); } /** * Creates a line chart with the given data and configuration. * * @param data data to plot. * @param config configuration to use. */ public LineChart(LineChartData data, LineChartConfig config) { if (data == null) throw new IllegalArgumentException("Data can't be NULL"); if (config == null) throw new IllegalArgumentException("Config can't be NULL"); this.config = config; this.data = data; // Find maximum and median within data values int max = Integer.MIN_VALUE; for (int i = 0; i < data.getValuesCount(); i++) { int val = data.getValue(i); max = Math.max(max, val); } maxValue = max; minValue = 0; medianValue = (maxValue - minValue) / 2; // Calculate a preferred size Dimension min = config.getMinGraphSize(); int step = config.getValueXStep(); int radius = config.getDotRadius(); int hIL = config.getIndexLabelHeight(); int wILB = config.getIndexLabelBorderWidth(); int wVS = config.getValueScaleWidth(); // Calculate // Width: label padding + dot radius + graph itself + dot radius + label padding // Height: minimum one dot radius + 2px padding + border width + padding int w = Math.max(min.width, wVS + (radius + (step * (data.getValuesCount() - 1)) + radius) + wVS); int h = Math.max(min.height, radius * 2 + wILB + hIL); prefSize = new Dimension(w, h); } @Override public Dimension getPreferredSize() { return prefSize; } @Override protected void paintComponent(Graphics g) { // Configuration properties int dotRadiusI = config.getDotRadius(); int dotRadiusO = dotRadiusI + 2; int valueXStep = config.getValueXStep(); int valueScaleWidth = config.getValueScaleWidth(); Font valueScaleFont = config.getValueScaleFont(); int indexLabelHeight = config.getIndexLabelHeight(); int indexLabelBorderWidth = config.getIndexLabelBorderWidth(); Color indexLabelBorderColor = config.getIndexLabelBorderColor(); Font indexLabelFont = config.getIndexLabelFont(); int indexLabelStep = config.getIndexLabelStep(); Color gridLineColor = config.getGridLineColor(); Color labelColor = config.getGetLabelColor(); Color medianLineColor = config.getMedianLineColor(); int mainLineWidth = config.getMainLineWidth(); Color mainLineColor = config.getMainLineColor(); Color backGroundColor = config.getBackgroundColor(); Color fillColor = config.getFillColor(); boolean stepStretching = config.getStepStretching(); // Data properties int valuesCount = data.getValuesCount(); // Calculate sizes Dimension total = getSize(); int width = total.width; int height = total.height; // Legend: // il -- index label area // ma -- main area // vl -- value label area // bo -- border // me -- median // // X, Y, W, H -- x, y, width, height // Minimal main area width and height int maMinW = 2 * dotRadiusI + valueXStep * (valuesCount - 1); int maMinH = 2 * dotRadiusI; // X coordinates of vl 1/2 and the main area int vl1X = 0; int maX = vl1X + valueScaleWidth; int vl2X = Math.max(width - valueScaleWidth, maX + maMinW); // Calculate steps if (stepStretching) { valueXStep = Math.max((vl2X - maX) / (valuesCount - 1), valueXStep); } // Heights int maH = Math.max(height - Math.max(1, indexLabelHeight - indexLabelBorderWidth), maMinH); int tH = maH + indexLabelBorderWidth + indexLabelHeight; // Widths int tW = vl2X + valueScaleWidth; // Y coordinates of vl 1/2, the main area, median, border, and the il int maY = 0; int boY = Math.max(maH, maY + maMinH); int meY = dotRadiusI + (boY - maY - dotRadiusI) / 2; Graphics2D g2d = (Graphics2D)g; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Fill the area g2d.setColor(backGroundColor); g2d.fillRect(0, 0, tW, tH); // Prepare a poly coordinates for the main line int[] fpolyX = new int[valuesCount + 2]; int[] fpolyY = new int[valuesCount + 2]; int[] polyX = new int[valuesCount]; int[] polyY = new int[valuesCount]; double k = (double)(maH - dotRadiusI) / maxValue; for (int i = 0; i < valuesCount; i++) { polyX[i] = maX + i * valueXStep; polyY[i] = maH - (int)(k * data.getValue(i)); fpolyX[i + 1] = polyX[i]; fpolyY[i + 1] = polyY[i]; } fpolyX[0] = maX; fpolyX[fpolyX.length - 1] = polyX[polyX.length - 1]; fpolyY[0] = maH; fpolyY[fpolyX.length - 1] = maH; // Fill poly of the main area if (fillColor != null) { g2d.setColor(fillColor); g2d.fillPolygon(fpolyX, fpolyY, fpolyX.length); } if (gridLineColor != null) { // Paint top grid line g2d.setColor(gridLineColor); g2d.drawLine(vl1X, maY, tW, maY); // Paint a vertical grid line g2d.setFont(indexLabelFont); for (int i = valuesCount - indexLabelStep; i >= 0; i -= indexLabelStep) { int x = maX + i * valueXStep; g2d.setColor(gridLineColor); g2d.drawLine(x, maY, x, boY); if (i < valuesCount - 1) { g2d.setColor(labelColor); g2d.drawLine(x, boY, x, tH); g2d.drawString(data.getIndexLabel(i), x + 3, tH - 4); } } } // Paint median line if (medianLineColor != null) { g2d.setColor(medianLineColor); g2d.setStroke(new BasicStroke(1, 0, 0, 1, new float[] { 1.5f, 1.5f }, 0)); g2d.drawLine(vl1X, meY, tW, meY); } // Paint border line if (indexLabelBorderWidth > 0) { g2d.setStroke(new BasicStroke(indexLabelBorderWidth)); g2d.setColor(indexLabelBorderColor); g2d.drawLine(vl1X, boY, tW, boY); } // Draw the main line BasicStroke stLine = new BasicStroke(mainLineWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); g2d.setStroke(stLine); g2d.setColor(mainLineColor); g2d.drawPolyline(polyX, polyY, polyX.length); // Draw the dots if (dotRadiusI > 0) { for (int i = 0; i < polyX.length; i++) { int x = polyX[i]; int y = polyY[i]; g2d.setColor(backGroundColor); g2d.fillArc(x - dotRadiusO, y - dotRadiusO, 2 * dotRadiusO, 2 * dotRadiusO, 0, ANGLE_360); g2d.setColor(mainLineColor); g2d.fillArc(x - dotRadiusI, y - dotRadiusI, 2 * dotRadiusI, 2 * dotRadiusI, 0, ANGLE_360); } } // Draw value labels if (valueScaleWidth > 0) { String maxVS = Integer.toString(maxValue); String medVS = Integer.toString(medianValue); float fhMax = valueScaleFont.getLineMetrics(maxVS, g2d.getFontRenderContext()).getHeight(); float fhMed = valueScaleFont.getLineMetrics(medVS, g2d.getFontRenderContext()).getHeight(); g2d.setFont(valueScaleFont); g2d.setColor(labelColor); g2d.drawString(maxVS, vl1X + 3, dotRadiusI + fhMax + 1); if (medianValue > 0) g2d.drawString(medVS, vl1X + 3, meY + fhMed + 1); g2d.drawString(maxVS, vl2X + 3, dotRadiusI + fhMax + 1); if (medianValue > 0) g2d.drawString(medVS, vl2X + 3, meY + fhMed + 1); } } }