code: /*--------------------------------------------------------------*
    Copyright (C) 2006-2015 OpenSim Ltd.
  
    This file is distributed WITHOUT ANY WARRANTY. See the file
    'License' for details on this and other legal matters.
  *--------------------------------------------------------------*/
  
  package org.omnetpp.scave.charting;
  
  import java.math.BigDecimal;
  import java.text.DecimalFormat;
  import java.text.DecimalFormatSymbols;
  
  import org.eclipse.draw2d.Graphics;
  import org.eclipse.draw2d.geometry.Insets;
  import org.eclipse.draw2d.geometry.Rectangle;
  import org.eclipse.swt.SWT;
  import org.eclipse.swt.graphics.Color;
  import org.eclipse.swt.graphics.Font;
  import org.eclipse.swt.graphics.Point;
  import org.eclipse.swt.graphics.RGB;
  import org.omnetpp.common.canvas.ICoordsMapping;
  import org.omnetpp.common.canvas.LargeGraphics;
  import org.omnetpp.common.color.ColorFactory;
  import org.omnetpp.common.util.GraphicsUtils;
  import org.omnetpp.scave.charting.properties.PlotProperty.ShowGrid;
  
  /**
   * Draws a (horizontal or vertical) chart axis, with the corresponding axis
   * title, ticks and tick labels, and (vertical or horizontal) grid lines.
   *
   * @author andras
   */
  public class LinearAxis {
      /* Properties */
      private boolean vertical;    // horizontal or vertical axis
      private boolean logarithmic; // logarithmic axis
      private boolean drawAxisToPlot;
      private ShowGrid showGrid;
      private RGB gridColor;
      private boolean drawTickLabels = true;
      private boolean drawTitle = true;
      private String title;
      private Font titleFont;
      private Font tickFont;
  
      DecimalFormat decimalFormat;
      DecimalFormat scientificFormat;
  
      /* Layout parameters */
      private int gap = 2;  // space between axis line and plot area (usually 0)
      private int majorTickLength = 4;
      private int minorTickLength = 2;
      private Rectangle bounds;
      private Insets insets;  // plot area = bounds minus insets
  
      public LinearAxis(boolean vertical, boolean logarithmic, boolean drawAxisToPlot) {
          this.vertical = vertical;
          this.logarithmic = logarithmic;
          this.drawAxisToPlot = drawAxisToPlot;
  
          DecimalFormatSymbols sym = new DecimalFormatSymbols();
          sym.setGroupingSeparator(' ');
          sym.setExponentSeparator("e");
  
          decimalFormat  = new DecimalFormat("#.#", sym);
  
          decimalFormat.setMaximumFractionDigits(1000);
          decimalFormat.setGroupingSize(3);
          decimalFormat.setGroupingUsed(true);
  
          scientificFormat  = new DecimalFormat("0.#E0", sym);
  
          scientificFormat.setMaximumFractionDigits(1000);
          scientificFormat.setGroupingSize(3);
          scientificFormat.setGroupingUsed(true);
      }
  
      public double transform(double coord) {
          return logarithmic ? Math.log10(coord) : coord;
      }
  
      public double inverseTransform(double coord) {
          return logarithmic ? Math.pow(10.0, coord) : coord;
      }
  
      /**
       * Modifies insets to accommodate room for axis title, ticks, tick labels etc.
       * Also returns insets for convenience.
       */
      public Insets layout(Graphics graphics, Rectangle bounds, Insets insets, ICoordsMapping mapping, int pass) {
          if (pass == 1) {
              if (vertical) {
                  // postpone layoutHint() as it wants to use coordinate mapping which is not yet set up (to calculate ticks)
                  insets.left = 0;
                  insets.right = 0;
              }
              else {
                  graphics.setFont(tickFont);
                  int labelHeight = calculateTickLabelHeight(graphics);
                  int titleHeight = calculateTitleSize(graphics).y;
                  insets.top = Math.max(insets.top, gap + majorTickLength + labelHeight + 4);
                  insets.bottom = Math.max(insets.bottom, gap + majorTickLength + labelHeight + titleHeight + 4);
              }
          }
          else if (pass == 2) {
              if (vertical) {
                  graphics.setFont(tickFont);
                  int labelWidth = calculateTickLabelLength(graphics, bounds, mapping);
                  int titleHeight = calculateTitleSize(graphics).y;  // but will be drawn 90 deg rotated
                  insets.left = Math.max(insets.left, gap + majorTickLength + labelWidth + titleHeight + 4);
                  insets.right = Math.max(insets.right, gap + majorTickLength + labelWidth + 4);
              }
          }
  
          this.bounds = bounds.getCopy();
          this.insets = new Insets(insets);
  
          return insets;
      }
  
      private Point calculateTitleSize(Graphics graphics) {
          graphics.setFont(titleFont);
          return (!drawTitle || title.equals("")) ? new Point(0,0) : GraphicsUtils.getTextExtent(graphics, title);
      }
  
      private String getTickLabel(BigDecimal tick) {
          if (tick.scale() < 0)
              tick = tick.setScale(0);
          tick = tick.stripTrailingZeros();
  
          if (tick.equals(BigDecimal.ZERO))
              return "0";
  
          DecimalFormat format = tick.abs().compareTo(new BigDecimal(0.001)) < 0
                  ? scientificFormat : decimalFormat;
  
          return format.format(tick);
      }
  
      private int calculateTickLabelHeight(Graphics graphics) {
          if (!drawTickLabels)
              return 0;
          graphics.setFont(tickFont);
          int labelHeight = GraphicsUtils.getTextExtent(graphics, "999").y;
          return labelHeight;
      }
  
      private int calculateTickLabelLength(Graphics graphics, Rectangle bounds, ICoordsMapping mapping) {
          // calculate longest tick label length
          if (!drawTickLabels)
              return 0;
          ITicks ticks = createTicks(bounds, mapping);
          int labelWidth = 0;
          if (ticks != null) {
              graphics.setFont(tickFont);
              for (BigDecimal tick : ticks) {
                  if (ticks.isMajorTick(tick))
                      labelWidth = Math.max(labelWidth, GraphicsUtils.getTextExtent(graphics, getTickLabel(tick)).x);
              }
          }
          return labelWidth;
      }
  
      public void drawGrid(Graphics graphics, ICoordsMapping mapping) {
          if (showGrid == ShowGrid.None)
              return;
  
          // Note: when canvas caching is on, graphics is the cached image, so the grid must be drawn
          // to the whole clipping region (cached image area) not just the plot area
          Rectangle rect = GraphicsUtils.getClip(graphics);
          ITicks ticks = createTicks(rect, mapping);
          if (ticks != null) {
              graphics.pushState();
              graphics.setLineStyle(Graphics.LINE_DOT);
              graphics.setForegroundColor(new Color(gridColor));
              for (BigDecimal tick : ticks) {
                  if (showGrid == ShowGrid.All || ticks.isMajorTick(tick)) {
                      if (vertical) {
                          long y = mapping.toCanvasY(transform(tick.doubleValue()));
                          if (y >= rect.y && y <= rect.bottom()) {
                              LargeGraphics.drawLine(graphics, rect.x, y, rect.right(), y);
                          }
                      }
                      else {
                          long x = mapping.toCanvasX(transform(tick.doubleValue()));
                          if (x >= rect.x && x <= rect.right()) {
                              LargeGraphics.drawLine(graphics, x, rect.y, x, rect.bottom());
                          }
                      }
                  }
              }
              graphics.popState();
          }
      }
  
      public void drawAxis(Graphics graphics, ICoordsMapping mapping) {
          Rectangle plotArea = bounds.getCopy().shrink(insets);
  
          // draw axis line and title
          graphics.setLineWidth(1);
          graphics.setLineStyle(SWT.LINE_SOLID);
          graphics.setForegroundColor(ColorFactory.BLACK);
          graphics.setFont(titleFont);
  
          Point titleSize = GraphicsUtils.getTextExtent(graphics, title);
          if (vertical) {
              if (drawAxisToPlot && !logarithmic && mapping.fromCanvasY(plotArea.bottom()) < 0 && mapping.fromCanvasY(plotArea.y) > 0)
                  LargeGraphics.drawLine(graphics, plotArea.x, mapping.toCanvasY(0), plotArea.right(), mapping.toCanvasY(0)); // x axis
              graphics.drawLine(plotArea.x - gap, plotArea.y, plotArea.x - gap, plotArea.bottom());
              graphics.drawLine(plotArea.right() + gap, plotArea.y, plotArea.right() + gap, plotArea.bottom());
              if (drawTitle) {
                  // TODO: SVGGraphics does not support rotation yet
                  boolean isSVGGraphics = GraphicsUtils.isSVGGraphics(graphics);
                  if (!isSVGGraphics) {
                      graphics.pushState();
                      graphics.rotate(-90);
                      graphics.drawText(title, -(plotArea.y + plotArea.height / 2 + titleSize.x / 2), bounds.x);
                      graphics.popState();
                  }
              }
          }
          else {
              if (drawAxisToPlot && !logarithmic && mapping.fromCanvasX(plotArea.x) < 0 && mapping.fromCanvasX(plotArea.right()) > 0)
                  LargeGraphics.drawLine(graphics, mapping.toCanvasX(0), plotArea.y, mapping.toCanvasX(0), plotArea.bottom()); // y axis
              graphics.drawLine(plotArea.x, plotArea.y - gap, plotArea.right(), plotArea.y - gap);
              graphics.drawLine(plotArea.x, plotArea.bottom() + gap, plotArea.right(), plotArea.bottom() + gap);
              if (drawTitle)
                  graphics.drawText(title, plotArea.x + plotArea.width / 2 - titleSize.x / 2, bounds.bottom() - titleSize.y);
          }
  
          // draw ticks and labels
          ITicks ticks = createTicks(plotArea, mapping);
          if (ticks != null) {
              graphics.setFont(tickFont);
              for (BigDecimal tick : ticks) {
                  String label = getTickLabel(tick);
                  Point size = GraphicsUtils.getTextExtent(graphics, label);
                  int tickLen = ticks.isMajorTick(tick) ? majorTickLength : minorTickLength;
                  if (vertical) {
                      long y = mapping.toCanvasY(transform(tick.doubleValue()));
                      if (y >= plotArea.y && y <= plotArea.bottom()) {
                          LargeGraphics.drawLine(graphics, plotArea.x - gap - tickLen, y, plotArea.x - gap, y);
                          LargeGraphics.drawLine(graphics, plotArea.right() + gap + tickLen, y, plotArea.right() + gap, y);
                          if (drawTickLabels && ticks.isMajorTick(tick)) {
                              LargeGraphics.drawText(graphics, label, plotArea.x - gap - tickLen - size.x - 1, y - size.y / 2);
                              LargeGraphics.drawText(graphics, label, plotArea.right() + gap + tickLen + 3, y - size.y / 2);
                          }
                      }
                  }
                  else {
                      long x = mapping.toCanvasX(transform(tick.doubleValue()));
                      if (x >= plotArea.x && x <= plotArea.right()) {
                          LargeGraphics.drawLine(graphics, x, plotArea.y - gap - tickLen, x, plotArea.y - gap);
                          LargeGraphics.drawLine(graphics, x, plotArea.bottom() + gap + tickLen, x, plotArea.bottom() + gap);
                          if (drawTickLabels && ticks.isMajorTick(tick)) {
                              LargeGraphics.drawText(graphics, label, x - size.x / 2 + 1, plotArea.y - gap - tickLen - size.y - 1);
                              LargeGraphics.drawText(graphics, label, x - size.x / 2 + 1, plotArea.bottom() + gap + tickLen + 1);
                          }
                      }
                  }
              }
          }
      }
  
      protected ITicks createTicks(Rectangle plotArea, ICoordsMapping mapping) {
          if (vertical)
              return createTicks(mapping.fromCanvasY(plotArea.bottom()), mapping.fromCanvasY(plotArea.y), plotArea.height);
          else
              return createTicks(mapping.fromCanvasX(plotArea.x), mapping.fromCanvasX(plotArea.right()), plotArea.width);
      }
  
      protected ITicks createTicks(double start, double end, int pixels) {
          if (logarithmic)
              return new LogarithmicTicks(Math.pow(10.0, start), Math.pow(10.0, end));
          else
              return pixels != 0 && start != end ? new LinearTicks(start, end, 50 * (end-start) / pixels ) : null;
      }
  
      public Rectangle getBounds() {
          return bounds;  // note: no setter! use setLayout()
      }
  
      public Insets getInsets() {
          return insets;  // note: no setter! use setLayout()
      }
  
      public boolean isVertical() {
          return vertical;
      }
  
      public void setVertical(boolean vertical) {
          this.vertical = vertical;
      }
  
      public boolean isLogarithmic() {
          return logarithmic;
      }
  
      public void setLogarithmic(boolean logarithmic) {
          this.logarithmic = logarithmic;
      }
  
      public int getGap() {
          return gap;
      }
  
      public void setGap(int gap) {
          this.gap = gap;
      }
  
      public Font getTickFont() {
          return tickFont;
      }
  
      public void setTickFont(Font tickFont) {
          this.tickFont = tickFont;
      }
  
      public String getTitle() {
          return title;
      }
  
      public void setTitle(String title) {
          this.title = title;
      }
  
      public Font getTitleFont() {
          return titleFont;
      }
  
      public void setTitleFont(Font titleFont) {
          this.titleFont = titleFont;
      }
  
      public ShowGrid getShowGrid() {
          return showGrid;
      }
  
      public void setShowGrid(ShowGrid showGrid) {
          this.showGrid = showGrid;
      }
  
      public void setGridColor(RGB color) {
          this.gridColor = color;
      }
  
      public boolean isDrawTickLabels() {
          return drawTickLabels;
      }
  
      public void setDrawTickLabels(boolean drawTickLabels) {
          this.drawTickLabels = drawTickLabels;
      }
  
      public boolean isDrawTitle() {
          return drawTitle;
      }
  
      public void setDrawTitle(boolean drawTitle) {
          this.drawTitle = drawTitle;
      }
  }
  