Package com.positive.charting

Source Code of com.positive.charting.Chart

package com.positive.charting;

import java.awt.Paint;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.ListenerList;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;

import com.positive.charts.block.BlockParams;
import com.positive.charts.block.EntityBlockResult;
import com.positive.charts.block.LengthConstraintType;
import com.positive.charts.block.LineBorder;
import com.positive.charts.block.RectangleConstraint;
import com.positive.charts.block.RectangleInsets;
import com.positive.charts.common.RectangleEdge;
import com.positive.charts.data.Range;
import com.positive.charts.entity.EntityCollection;
import com.positive.charts.event.ChartChangeEvent;
import com.positive.charts.event.ChartChangeListener;
import com.positive.charts.event.PlotChangeEvent;
import com.positive.charts.event.PlotChangeListener;
import com.positive.charts.event.TitleChangeEvent;
import com.positive.charts.event.TitleChangeListener;
import com.positive.charts.plot.Plot;
import com.positive.charts.plot.PlotRenderingInfo;
import com.positive.charts.title.LegendTitle;
import com.positive.charts.title.TextTitle;
import com.positive.charts.title.Title;
import com.positive.charts.util.HorizontalAlignment;
import com.positive.charts.util.RectangleUtil;
import com.positive.charts.util.Size2D;
import com.positive.charts.util.Stroke;
import com.positive.charts.util.VerticalAlignment;
import com.positive.colorchecker.StaticColorChecker;

/**
* A chart class implemented using the SWT APIs.
*/
public class Chart implements PlotChangeListener, TitleChangeListener {

  /** The chart title (optional). */
  private TextTitle title;

  /** Listeners to chart change events. */
  private final ListenerList changeListeners = new ListenerList();

  /** The padding between the chart border and the chart drawing area. */
  private RectangleInsets padding;

  private Color borderPaint;

  /**
   * The chart subtitles (zero, one or many). This field should never be
   * <code>null</code>.
   */
  private final List subtitles = new ArrayList();

  /**
   * A flag that can be used to enable/disable notification of chart change
   * events.
   */
  private boolean notify = true;

  /** Draws the visual representation of the data. */
  private final Plot plot;

  private Color backgroundPaint;

  private boolean borderVisible;

  private Stroke borderStroke = new Stroke(1);

  /**
   * Creates a new instance of a chart drawer with a given plot.
   *
   * @param plot
   */
  public Chart(final Plot plot, final boolean createLegend) {
    this.plot = plot;
    plot.addChangeListener(this);
    // create a legend, if requested...
    if (createLegend) {
      final LegendTitle legend = new LegendTitle(this.plot);
      // legend.setMargin(new RectangleInsets(0, 0, 0, 0));
      // legend.setPadding(5, 1, 1,1);
      final LineBorder frame = new LineBorder();
      legend.setFrame(frame);
      legend.setBackgroundPaint(StaticColorChecker.dublicateColor(SWT.COLOR_WHITE));// Display.getCurrent().getSystemColor(SWT.COLOR_WHITE));
      legend.setPosition(RectangleEdge.BOTTOM);
      this.subtitles.add(legend);
      legend.addChangeListener(this);
    }
  }

  /**
   * Registers an object for notification of changes to the chart.
   *
   * @param listener
   *            the listener (<code>null</code> not permitted).
   */
  public void addChangeListener(final ChartChangeListener listener) {
    this.changeListeners.add(listener);
  }

  /**
   * Adds a legend to the plot and sends a {@link ChartChangeEvent} to all
   * registered listeners.
   *
   * @param legend
   *            the legend (<code>null</code> not permitted).
   *
   * @see #removeLegend()
   */
  public void addLegend(final LegendTitle legend) {
    this.addSubtitle(legend);
  }

  /**
   * Adds a subtitle at a particular position in the subtitle list, and sends
   * a {@link ChartChangeEvent} to all registered listeners.
   *
   * @param index
   *            the index (in the range 0 to {@link #getSubtitleCount()}).
   * @param subtitle
   *            the subtitle to add (<code>null</code> not permitted).
   *
   * @since 1.0.6
   */
  public void addSubtitle(final int index, final Title subtitle) {
    if ((index < 0) || (index > this.getSubtitleCount())) {
      throw new IllegalArgumentException(
          "The 'index' argument is out of range.");
    }
    if (subtitle == null) {
      throw new IllegalArgumentException("Null 'subtitle' argument.");
    }
    this.subtitles.add(index, subtitle);
    subtitle.addChangeListener(this);
    this.fireChartChanged();
  }

  /**
   * Adds a chart subtitle, and notifies registered listeners that the chart
   * has been modified.
   *
   * @param subtitle
   *            the subtitle (<code>null</code> not permitted).
   *
   * @see #getSubtitle(int)
   */
  public void addSubtitle(final Title subtitle) {
    if (subtitle == null) {
      throw new IllegalArgumentException("Null 'subtitle' argument.");
    }
    this.subtitles.add(subtitle);
    subtitle.addChangeListener(this);
    this.fireChartChanged();
  }

  /**
   * Clears all subtitles from the chart and sends a {@link ChartChangeEvent}
   * to all registered listeners.
   *
   * @see #addSubtitle(Title)
   */
  public void clearSubtitles() {
    final Iterator iterator = this.subtitles.iterator();
    while (iterator.hasNext()) {
      final Title t = (Title) iterator.next();
      t.removeChangeListener(this);
    }
    this.subtitles.clear();
    this.fireChartChanged();
  }

  /**
   * Creates a rectangle that is aligned to the frame.
   *
   * @param dimensions
   *            the dimensions for the rectangle.
   * @param frame
   *            the frame to align to.
   * @param hAlign
   *            the horizontal alignment.
   * @param vAlign
   *            the vertical alignment.
   *
   * @return A rectangle.
   */
  private Rectangle createAlignedRectangle2D(final Size2D dimensions,
      final Rectangle frame, final HorizontalAlignment hAlign,
      final VerticalAlignment vAlign) {
    double x = Double.NaN;
    double y = Double.NaN;
    if (hAlign == HorizontalAlignment.LEFT) {
      x = frame.x;
    } else if (hAlign == HorizontalAlignment.CENTER) {
      x = RectangleUtil.getCenterX(frame) - (dimensions.width / 2.0);
    } else if (hAlign == HorizontalAlignment.RIGHT) {
      x = RectangleUtil.getMaxX(frame) - dimensions.width;
    }
    if (vAlign == VerticalAlignment.TOP) {
      y = frame.y;
    } else if (vAlign == VerticalAlignment.CENTER) {
      y = RectangleUtil.getCenterY(frame) - (dimensions.height / 2.0);
    } else if (vAlign == VerticalAlignment.BOTTOM) {
      y = RectangleUtil.getMaxY(frame) - dimensions.height;
    }

    return new Rectangle((int) x, (int) y, (int) dimensions.width,
        (int) dimensions.height);
  }

  /**
   * Disposes the chart and associated plot.
   */
  public void dispose() {
    this.plot.dispose();
  }

  /**
   * Draws the chart on a SWT graphics device (such as the screen or a
   * printer).
   *
   * @param gc
   *            the SWT graphic context.
   * @param area
   *            the area within which the chart should be drawn.
   */
  public void draw(final GC gc, final Rectangle area) {
    this.draw(gc, area, null, null);
  }

  /**
   * Draws the chart on a SWT graphics device (such as the screen or a
   * printer).
   *
   * @param gc
   *            the SWT graphic context.
   * @param area
   *            the area within which the chart should be drawn.
   * @param info
   *            records info about the drawing (null means collect no info).
   */
  public void draw(final GC gc, final Rectangle area,
      final ChartRenderingInfo info) {
    this.draw(gc, area, null, info);
  }

  /**
   * Draw the chart using supplied SWT graphic context.
   *
   * @param gc
   *            the SWT graphic context.
   * @param chartArea
   *            the area within which the chart should be drawn
   * @param anchor
   *            the anchor point (in GC space) for the chart (
   *            <code>null</code> permitted).
   * @param info
   *            records info about the drawing (<code>null</code> means
   *            collect no info).
   */
  public void draw(final GC gc, final Rectangle chartArea,
      final Point anchor, final ChartRenderingInfo info) {
    // record the chart area, if info is requested...
    if (info != null) {
      info.clear();
      info.setChartArea(chartArea);
    }

    // ensure no drawing occurs outside chart area...
    // Shape savedClip = g2.getClip();
    // g2.clip(chartArea);
    //
    // g2.addRenderingHints(this.renderingHints);
    //
    // // draw the chart background...
    if (this.backgroundPaint != null) {
      gc.setBackground(this.backgroundPaint);
      gc.fillRectangle(chartArea);
    }
    //
    // if (this.backgroundImage != null) {
    // Composite originalComposite = g2.getComposite();
    // g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
    // this.backgroundImageAlpha));
    // Rectangle2D dest = new Rectangle2D.Double(0.0, 0.0,
    // this.backgroundImage.getWidth(null),
    // this.backgroundImage.getHeight(null));
    // Align.align(dest, chartArea, this.backgroundImageAlignment);
    // g2.drawImage(this.backgroundImage, (int) dest.getX(),
    // (int) dest.getY(), (int) dest.getWidth(),
    // (int) dest.getHeight(), null);
    // g2.setComposite(originalComposite);
    // }
    //
    if (this.isBorderVisible()) {
      final Color paint = this.getBorderPaint();
      final Stroke stroke = this.getBorderStroke();
      if ((paint != null) && (stroke != null)) {
        final Rectangle borderArea = new Rectangle(chartArea.x,
            chartArea.y, (chartArea.width - 1),
            (int) (chartArea.height - 1.0));
        gc.setForeground(paint);
        stroke.set(gc);
        gc.drawRectangle(borderArea);
        // gc.draw(borderArea);
      }
    }

    // draw the title and subtitles...
    // Rectangle2D nonTitleArea = new Rectangle2D.Double();
    final Rectangle nonTitleArea = new Rectangle(chartArea.x, chartArea.y,
        chartArea.width, chartArea.height);
    if (this.padding != null) {
      this.padding.trim(nonTitleArea);
    }

    EntityCollection entities = null;
    if (info != null) {
      entities = info.getEntityCollection();
    }
    if (this.title != null) {
      final EntityCollection e = this.drawTitle(this.title, gc,
          nonTitleArea, (entities != null));
      if (e != null) {
        entities.addAll(e);
      }
    }

    final Iterator iterator = this.subtitles.iterator();
    while (iterator.hasNext()) {
      final Title currentTitle = (Title) iterator.next();
      final EntityCollection e = this.drawTitle(currentTitle, gc,
          nonTitleArea, (entities != null));
      if (e != null) {
        entities.addAll(e);
      }
    }

    final Rectangle plotArea = nonTitleArea;

    this.drawPlot(gc, plotArea, anchor, info);
  }

  /**
   * Draw the plot (axes and data visualization).
   */
  private void drawPlot(final GC gc, final Rectangle plotArea,
      final Point anchor, final ChartRenderingInfo info) {
    PlotRenderingInfo plotInfo = null;
    if (info != null) {
      plotInfo = info.getPlotInfo();
    }
    this.plot.draw(gc, plotArea, anchor, null, plotInfo);
  }

  /**
   * Draws a title. The title should be drawn at the top, bottom, left or
   * right of the specified area, and the area should be updated to reflect
   * the amount of space used by the title.
   *
   * @param t
   *            the title (<code>null</code> not permitted).
   * @param g2
   *            the graphics device (<code>null</code> not permitted).
   * @param area
   *            the chart area, excluding any existing titles (
   *            <code>null</code> not permitted).
   * @param entities
   *            a flag that controls whether or not an entity collection is
   *            returned for the title.
   *
   * @return An entity collection for the title (possibly <code>null</code>).
   */
  protected EntityCollection drawTitle(final Title t, final GC g2,
      final Rectangle area, final boolean entities) {

    if (t == null) {
      throw new IllegalArgumentException("Null 't' argument.");
    }
    if (area == null) {
      throw new IllegalArgumentException("Null 'area' argument.");
    }
    Rectangle titleArea = null;
    final RectangleEdge position = t.getPosition();
    final double ww = area.width;
    if (ww <= 0.0) {
      return null;
    }
    final double hh = area.height;
    if (hh <= 0.0) {
      return null;
    }
    final RectangleConstraint constraint = new RectangleConstraint(ww,
        new Range(0.0, ww), LengthConstraintType.RANGE, hh, new Range(
            0.0, hh), LengthConstraintType.RANGE);
    Object retValue = null;
    final BlockParams p = new BlockParams();
    p.setGenerateEntities(entities);
    if (position == RectangleEdge.TOP) {
      final Size2D size = t.arrange(g2, constraint);
      titleArea = this.createAlignedRectangle2D(size, area, t
          .getHorizontalAlignment(), VerticalAlignment.TOP);
      retValue = t.draw(g2, titleArea, p);
      RectangleUtil.setRect(area, area.x, (int) Math.min(area.y
          + size.height, RectangleUtil.getMaxY(area)), area.width,
          (int) Math.max(area.height - size.height, 0));
    } else if (position == RectangleEdge.BOTTOM) {
      final Size2D size = t.arrange(g2, constraint);
      titleArea = this.createAlignedRectangle2D(size, area, t
          .getHorizontalAlignment(), VerticalAlignment.BOTTOM);
      retValue = t.draw(g2, titleArea, p);
      RectangleUtil.setRect(area, area.x, area.y, area.width,
          (int) (area.height - size.height));
    } else if (position == RectangleEdge.RIGHT) {
      final Size2D size = t.arrange(g2, constraint);
      titleArea = this.createAlignedRectangle2D(size, area,
          HorizontalAlignment.RIGHT, t.getVerticalAlignment());
      retValue = t.draw(g2, titleArea, p);
      RectangleUtil.setRect(area, area.x, area.y,
          area.width - size.width, area.height);
    }

    else if (position == RectangleEdge.LEFT) {
      final Size2D size = t.arrange(g2, constraint);
      titleArea = this.createAlignedRectangle2D(size, area,
          HorizontalAlignment.LEFT, t.getVerticalAlignment());
      retValue = t.draw(g2, titleArea, p);
      RectangleUtil.setRect(area, area.x + size.width, area.y, area.width
          - size.width, area.height);
    } else {
      throw new RuntimeException("Unrecognised title position.");
    }
    EntityCollection result = null;
    if (retValue instanceof EntityBlockResult) {
      final EntityBlockResult ebr = (EntityBlockResult) retValue;
      result = ebr.getEntityCollection();
    }
    return result;
  }

  /**
   * Sends a default {@link ChartChangeEvent} to all registered listeners.
   * <P>
   * This method is for convenience only.
   */
  public void fireChartChanged() {
    this.notifyListeners(new ChartChangeEvent(this));
  }

  public Color getBackgroundPaint() {
    return this.backgroundPaint;
  }

  /**
   * Returns the paint used to draw the chart border (if visible).
   *
   * @return The border paint.
   *
   * @see #setBorderPaint(Paint)
   */
  public Color getBorderPaint() {
    return this.borderPaint;
  }

  /**
   * Returns the stroke used to draw the chart border (if visible).
   *
   * @return The border stroke.
   *
   * @see #setBorderStroke(Stroke)
   */
  public Stroke getBorderStroke() {
    return this.borderStroke;
  }

  /**
   * Returns the legend for the chart, if there is one. Note that a chart can
   * have more than one legend - this method returns the first.
   *
   * @return The legend (possibly <code>null</code>).
   *
   * @see #getLegend(int)
   */
  public LegendTitle getLegend() {
    return this.getLegend(0);
  }

  /**
   * Returns the nth legend for a chart, or <code>null</code>.
   *
   * @param index
   *            the legend index (zero-based).
   *
   * @return The legend (possibly <code>null</code>).
   *
   * @see #addLegend(LegendTitle)
   */
  public LegendTitle getLegend(final int index) {
    int seen = 0;
    final Iterator iterator = this.subtitles.iterator();
    while (iterator.hasNext()) {
      final Title subtitle = (Title) iterator.next();
      if (subtitle instanceof LegendTitle) {
        if (seen == index) {
          return (LegendTitle) subtitle;
        } else {
          seen++;
        }
      }
    }
    return null;
  }

  /**
   * Returns the padding between the chart border and the chart drawing area.
   *
   * @return The padding (never <code>null</code>).
   *
   * @see #setPadding(RectangleInsets)
   */
  public RectangleInsets getPadding() {
    return this.padding;
  }

  public Plot getPlot() {
    return this.plot;
  }

  /**
   * Returns a chart subtitle.
   *
   * @param index
   *            the index of the chart subtitle (zero based).
   *
   * @return A chart subtitle.
   *
   * @see #addSubtitle(Title)
   */
  public Title getSubtitle(final int index) {
    if ((index < 0) || (index >= this.getSubtitleCount())) {
      throw new IllegalArgumentException("Index out of range.");
    }
    return (Title) this.subtitles.get(index);
  }

  /**
   * Returns the number of titles for the chart.
   *
   * @return The number of titles for the chart.
   *
   * @see #getSubtitles()
   */
  public int getSubtitleCount() {
    return this.subtitles.size();
  }

  /**
   * Returns the list of subtitles for the chart.
   *
   * @return The subtitle list (possibly empty, but never <code>null</code>).
   *
   * @see #setSubtitles(List)
   */
  public List getSubtitles() {
    return new ArrayList(this.subtitles);
  }

  /**
   * Returns the main chart title. Very often a chart will have just one
   * title, so we make this case simple by providing accessor methods for the
   * main title. However, multiple titles are supported - see the
   * {@link #addSubtitle(Title)} method.
   *
   * @return The chart title (possibly <code>null</code>).
   *
   * @see #setTitle(TextTitle)
   */
  public TextTitle getTitle() {
    return this.title;
  }

  /**
   * Returns a flag that controls whether or not a border is drawn around the
   * outside of the chart.
   *
   * @return A boolean.
   *
   * @see #setBorderVisible(boolean)
   */
  public boolean isBorderVisible() {
    return this.borderVisible;
  }

  /**
   * Returns the current state of sending the {@link ChartChangeEvent}s.
   *
   * @return <code>true</code> iff the chart chage events are sent.
   */
  public boolean isNotify() {
    return this.notify;
  }

  /**
   * Sends a {@link ChartChangeEvent} to all registered listeners.
   *
   * @param event
   *            information about the event that triggered the notification.
   */
  protected void notifyListeners(final ChartChangeEvent event) {
    if (this.notify) {
      final Object[] listeners = this.changeListeners.getListeners();
      for (int i = 0; i < listeners.length; i++) {
        final ChartChangeListener listener = (ChartChangeListener) listeners[i];
        listener.chartChanged(event);
      }
    }
  }

  /**
   * Receives notification that the plot has changed, and passes this on to
   * registered listeners.
   *
   * @param event
   *            information about the plot change.
   */
  public void plotChanged(final PlotChangeEvent event) {
    event.setChart(this);
    this.notifyListeners(event);
  }

  /**
   * Deregisters an object for notification of changes to the chart.
   *
   * @param listener
   *            the listener (<code>null</code> not permitted)
   */
  public void removeChangeListener(final ChartChangeListener listener) {
    this.changeListeners.remove(listener);
  }

  /**
   * Removes the first legend in the chart and sends a
   * {@link ChartChangeEvent} to all registered listeners.
   *
   * @see #getLegend()
   */
  public void removeLegend() {
    this.removeSubtitle(this.getLegend());
  }

  /**
   * Removes the specified subtitle and sends a {@link ChartChangeEvent} to
   * all registered listeners.
   *
   * @param title
   *            the title.
   *
   * @see #addSubtitle(Title)
   */
  public void removeSubtitle(final Title title) {
    this.subtitles.remove(title);
    this.fireChartChanged();
  }

  public void setBackgroundPaint(final Color backgroundPaint) {
    this.backgroundPaint = backgroundPaint;
  }

  /**
   * Sets the paint used to draw the chart border (if visible).
   *
   * @param paint
   *            the paint.
   *
   * @see #getBorderPaint()
   */
  public void setBorderPaint(final Color paint) {
    this.borderPaint = paint;
    this.fireChartChanged();
  }

  /**
   * Sets the stroke used to draw the chart border (if visible).
   *
   * @param stroke
   *            the stroke.
   *
   * @see #getBorderStroke()
   */
  public void setBorderStroke(final Stroke stroke) {
    this.borderStroke = stroke;
    this.fireChartChanged();
  }

  /**
   * Sets a flag that controls whether or not a border is drawn around the
   * outside of the chart.
   *
   * @param visible
   *            the flag.
   *
   * @see #isBorderVisible()
   */
  public void setBorderVisible(final boolean visible) {
    this.borderVisible = visible;
    this.fireChartChanged();
  }

  /**
   * Enables/disables sending of {@link ChartChangeEvent}s. Can be used to
   * force sending an event if flag is set to <code>true</code>.
   *
   * @param enable
   *            a state of notification sends.
   */
  public void setNotify(final boolean enable) {
    this.notify = enable;
    if (enable) {
      this.notifyListeners(new ChartChangeEvent(this));
    }
  }

  /**
   * Sets the padding between the chart border and the chart drawing area, and
   * sends a {@link ChartChangeEvent} to all registered listeners.
   *
   * @param padding
   *            the padding (<code>null</code> not permitted).
   *
   * @see #getPadding()
   */
  public void setPadding(final RectangleInsets padding) {
    if (padding == null) {
      throw new IllegalArgumentException("Null 'padding' argument.");
    }
    this.padding = padding;
    this.notifyListeners(new ChartChangeEvent(this));
  }

  /**
   * Sets the title list for the chart (completely replaces any existing
   * titles) and sends a {@link ChartChangeEvent} to all registered listeners.
   *
   * @param subtitles
   *            the new list of subtitles (<code>null</code> not permitted).
   *
   * @see #getSubtitles()
   */
  public void setSubtitles(final List subtitles) {
    if (subtitles == null) {
      throw new NullPointerException("Null 'subtitles' argument.");
    }
    this.setNotify(false);
    this.clearSubtitles();
    final Iterator iterator = subtitles.iterator();
    while (iterator.hasNext()) {
      final Title t = (Title) iterator.next();
      if (t != null) {
        this.addSubtitle(t);
      }
    }
    this.setNotify(true); // this fires a ChartChangeEvent
  }

  /**
   * Sets the chart title and sends a {@link ChartChangeEvent} to all
   * registered listeners. This is a convenience method that ends up calling
   * the {@link #setTitle(TextTitle)} method. If there is an existing title,
   * its text is updated, otherwise a new title using the default font is
   * added to the chart. If <code>text</code> is <code>null</code> the chart
   * title is set to <code>null</code>.
   *
   * @param text
   *            the title text (<code>null</code> permitted).
   *
   * @see #getTitle()
   */
  public void setTitle(final String text) {
    if (text != null) {
      if (this.title == null) {
        this.setTitle(new TextTitle(text, TextTitle.DEFAULT_FONT));
      } else {
        this.title.setText(text);
      }
    } else {
      this.setTitle((TextTitle) null);
    }
  }

  /**
   * Sets the main title for the chart and sends a {@link ChartChangeEvent} to
   * all registered listeners. If you do not want a title for the chart, set
   * it to <code>null</code>. If you want more than one title on a chart, use
   * the {@link #addSubtitle(Title)} method.
   *
   * @param title
   *            the title (<code>null</code> permitted).
   *
   * @see #getTitle()
   */
  public void setTitle(final TextTitle title) {
    if (this.title != null) {
      this.title.removeChangeListener(this);
    }
    this.title = title;
    if (title != null) {
      title.addChangeListener(this);
    }
    this.fireChartChanged();
  }

  public void titleChanged(final TitleChangeEvent event) {
    event.setChart(this);
    this.notifyListeners(event);
  }
}
TOP

Related Classes of com.positive.charting.Chart

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.