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 static org.omnetpp.scave.charting.properties.PlotProperty.PROP_AXIS_LABEL_FONT;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_AXIS_TITLE_FONT;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_BAR_BASELINE;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_BAR_BASELINE_COLOR;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_BAR_COLOR;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_BAR_OUTLINE_COLOR;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_BAR_PLACEMENT;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_GRID;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_GRID_COLOR;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_WRAP_LABELS;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_X_AXIS_SHOW_LABELS;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_X_AXIS_SHOW_TITLE;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_X_AXIS_TITLE;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_X_LABELS_ROTATE_BY;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_Y_AXIS_LOGARITHMIC;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_Y_AXIS_SHOW_LABELS;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_Y_AXIS_SHOW_TITLE;
  import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_Y_AXIS_TITLE;
  
  import java.util.HashMap;
  import java.util.Map;
  
  import org.apache.commons.lang3.ArrayUtils;
  import org.apache.commons.lang3.StringEscapeUtils;
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.draw2d.Graphics;
  import org.eclipse.draw2d.geometry.Insets;
  import org.eclipse.draw2d.geometry.Rectangle;
  import org.eclipse.swt.events.MouseAdapter;
  import org.eclipse.swt.events.MouseEvent;
  import org.eclipse.swt.graphics.Font;
  import org.eclipse.swt.graphics.RGB;
  import org.eclipse.swt.graphics.TextLayout;
  import org.eclipse.swt.widgets.Composite;
  import org.omnetpp.common.canvas.ICoordsMapping;
  import org.omnetpp.common.canvas.RectangularArea;
  import org.omnetpp.common.util.Converter;
  import org.omnetpp.common.util.GraphicsUtils;
  import org.omnetpp.scave.charting.dataset.IDataset;
  import org.omnetpp.scave.charting.dataset.IGroupsSeriesDataset;
  import org.omnetpp.scave.charting.plotter.IPlotSymbol;
  import org.omnetpp.scave.charting.plotter.SquareSymbol;
  import org.omnetpp.scave.charting.properties.PlotProperty;
  import org.omnetpp.scave.charting.properties.PlotProperty.BarPlacement;
  import org.omnetpp.scave.charting.properties.PlotProperty.ShowGrid;
  import org.omnetpp.scave.python.GroupsSeriesDataset;
  
  /**
   * Bar plot widget.
   */
  public class BarPlot extends PlotBase {
      //private static final boolean debug = false;
  
      protected final static PlotProperty[] BARPLOT_PROPERTIES = ArrayUtils.addAll(PLOTBASE_PROPERTIES, new PlotProperty[] {
              PROP_X_AXIS_TITLE,
              PROP_X_LABELS_ROTATE_BY,
              PROP_X_AXIS_SHOW_TITLE,
              PROP_X_AXIS_SHOW_LABELS,
              PROP_Y_AXIS_TITLE,
              PROP_Y_AXIS_LOGARITHMIC,
              PROP_Y_AXIS_SHOW_TITLE,
              PROP_Y_AXIS_SHOW_LABELS,
              PROP_AXIS_TITLE_FONT,
              PROP_AXIS_LABEL_FONT,
              PROP_WRAP_LABELS,
              PROP_BAR_BASELINE,
              PROP_BAR_BASELINE_COLOR,
              PROP_BAR_PLACEMENT,
              PROP_BAR_COLOR,
              PROP_BAR_OUTLINE_COLOR,
              PROP_GRID,
              PROP_GRID_COLOR,
      });
  
      private RGB defaultBarColor;
      private RGB defaultBarOutlineColor;
  
      private IGroupsSeriesDataset dataset;
  
      private LinearAxis valueAxis = new LinearAxis(true, false, false);
      private DomainAxis domainAxis = new DomainAxis(this);
      private Bars bars;
  
      static class BarProperties {
          RGB color;
          public RGB outlineColor;
      }
  
      private Map<String,BarProperties> barProperties = new HashMap<>();
  
      static class BarSelection implements IPlotSelection {
          // TODO selection on ScalarCharts
      }
  
      public BarPlot(Composite parent, int style) {
          super(parent, style);
          bars = new Bars(this);
          new Tooltip(this);
  
          this.addMouseListener(new MouseAdapter() {
              @Override
              public void mouseDown(MouseEvent e) {
                  setSelection(new BarSelection());
              }
          });
  
          addDisposeListener((e) -> { // Overriding dispose() does not work, see Widget.dispose()
              domainAxis.dispose();
          });
  
          resetProperties();
      }
  
      @Override
      protected double transformY(double y) {
          return valueAxis.transform(y);
      }
  
      @Override
      protected double inverseTransformY(double y) {
          return valueAxis.inverseTransform(y);
      }
  
      @Override
      public void doSetDataset(IDataset dataset) {
          if (dataset != null && !(dataset instanceof IGroupsSeriesDataset))
              throw new IllegalArgumentException("must be an IScalarDataset");
  
          this.dataset = (IGroupsSeriesDataset)dataset;
          updateLegends();
          chartArea = calculatePlotArea();
          updateArea();
          chartChanged();
      }
  
      public IGroupsSeriesDataset getDataset() {
          return dataset;
      }
  
      public Bars getPlot() {
          return bars;
      }
  
      @Override
      protected RectangularArea calculatePlotArea() {
          return bars.calculatePlotArea();
      }
  
      @Override
      protected void updateLegend(ILegend legend) {
          legend.clearItems();
          IPlotSymbol symbol = new SquareSymbol(6);
          if (dataset != null) {
              for (int i = 0; i < dataset.getSeriesCount(); ++i) {
                  legend.addItem(bars.getBarColor(i), dataset.getSeriesTitle(i), symbol, false);
              }
          }
      }
  
      /*=============================================
       *               Properties
       *=============================================*/
  
      public PlotProperty[] getProperties() {
          return BARPLOT_PROPERTIES;
      }
  
      @Override
      public void setProperty(PlotProperty prop, String value) {
          switch (prop) {
          // Titles
          case PROP_X_AXIS_TITLE: setXAxisTitle(value); break;
          case PROP_Y_AXIS_TITLE: setYAxisTitle(value); break;
          case PROP_AXIS_TITLE_FONT: setAxisTitleFont(Converter.stringToSwtfont(value)); break;
          case PROP_AXIS_LABEL_FONT: setLabelFont(Converter.stringToSwtfont(value)); break;
          case PROP_X_LABELS_ROTATE_BY: setXAxisLabelsRotatedBy(Converter.stringToDouble(value)); break;
          case PROP_WRAP_LABELS: setWrapLabels(Converter.stringToBoolean(value)); break;
          // Bars
          case PROP_BAR_BASELINE: setBarBaseline(Converter.stringToDouble(value)); break;
          case PROP_BAR_BASELINE_COLOR: setBarBaselineColor(Converter.stringToRGB(value)); break;
          case PROP_BAR_PLACEMENT: setBarPlacement(Converter.stringToEnum(value, BarPlacement.class)); break;
          case PROP_BAR_COLOR: setBarColor(Converter.stringToRGB(value)); break;
          case PROP_BAR_OUTLINE_COLOR: setBarOutlineColor(Converter.stringToRGB(value)); break;
          // Axes
          case PROP_GRID: setShowGrid(Converter.stringToEnum(value, ShowGrid.class)); break;
          case PROP_GRID_COLOR: setGridColor(Converter.stringToRGB(value)); break;
          case PROP_Y_AXIS_LOGARITHMIC: setLogarithmicY(Converter.stringToBoolean(value)); break;
          case PROP_X_AXIS_SHOW_TITLE:  setXAxisShowTitle(Converter.stringToBoolean(value)); break;
          case PROP_X_AXIS_SHOW_LABELS:  setXAxisShowLabels(Converter.stringToBoolean(value)); break;
          case PROP_Y_AXIS_SHOW_TITLE:  setYAxisShowTitle(Converter.stringToBoolean(value)); break;
          case PROP_Y_AXIS_SHOW_LABELS:  setYAxisShowLabels(Converter.stringToBoolean(value)); break;
          default: super.setProperty(prop, value);
          }
      }
  
      @Override
      public void setProperty(PlotProperty prop, String key, String value) {
          switch (prop) {
          case PROP_BAR_COLOR: setBarColor(key, Converter.stringToOptionalRGB(value)); break;
          case PROP_BAR_OUTLINE_COLOR: setBarOutlineColor(key, Converter.stringToOptionalRGB(value)); break;
          default: super.setProperty(prop, key, value);
          }
      }
  
      @Override
      public void clear() {
          super.clear();
          barProperties.clear();
          setDataset(new GroupsSeriesDataset());
      }
  
      public String getTitle() {
          return title.getText();
      }
  
      public Font getTitleFont() {
          return title.getFont();
      }
  
      public String getXAxisTitle() {
          return domainAxis.title;
      }
  
      public void setXAxisTitle(String title) {
          Assert.isNotNull(title);
          domainAxis.title = title;
          chartChanged();
      }
  
      public String getYAxisTitle() {
          return valueAxis.getTitle();
      }
  
      public void setYAxisTitle(String title) {
          Assert.isNotNull(title);
          valueAxis.setTitle(title);
          chartChanged();
      }
  
      public Font getAxisTitleFont() {
          return domainAxis.titleFont;
      }
  
      public void setAxisTitleFont(Font font) {
          Assert.isNotNull(font);
          domainAxis.titleFont = font;
          valueAxis.setTitleFont(font);
          chartChanged();
      }
  
      public void setLabelFont(Font font) {
          Assert.isNotNull(font);
          domainAxis.labelsFont = font;
          valueAxis.setTickFont(font);
          chartChanged();
      }
  
      public void setXAxisLabelsRotatedBy(double angle) {
          domainAxis.rotation = Math.max(-90, Math.min(90, angle));
          chartChanged();
      }
  
      public void setWrapLabels(boolean value) {
          domainAxis.wrapLabels = value;
          chartChanged();
      }
  
      public Double getBarBaseline() {
          return bars.barBaseline;
      }
  
      public void setBarBaseline(double value) {
          bars.barBaseline = value;
          chartArea = calculatePlotArea();
          updateArea();
          chartChanged();
      }
  
      public void setBarBaselineColor(RGB value) {
          Assert.isNotNull(value);
          bars.barBaselineColor = value;
          updateArea();
          chartChanged();
      }
  
      public BarPlacement getBarPlacement() {
          return bars.barPlacement;
      }
  
      public void setBarPlacement(BarPlacement value) {
          Assert.isNotNull(value);
          bars.barPlacement = value;
          chartArea = calculatePlotArea();
          updateArea();
          chartChanged();
      }
  
      public void setBarColor(RGB color) {
          defaultBarColor = color;
          updateLegends();
          chartChanged();
      }
  
      public void setBarOutlineColor(RGB color) {
          defaultBarOutlineColor = color;
          updateLegends();
          chartChanged();
      }
  
      public void setLogarithmicY(boolean logarithmic) {
          valueAxis.setLogarithmic(logarithmic);
          chartArea = calculatePlotArea();
          updateArea();
          chartChanged();
      }
  
      public void setXAxisShowTitle(boolean show) {
          domainAxis.setDrawTitle(show);
          chartArea = calculatePlotArea();
          updateArea();
          chartChanged();
      }
  
      public void setXAxisShowLabels(boolean show) {
          domainAxis.setLabels(show);
          chartArea = calculatePlotArea();
          updateArea();
          chartChanged();
      }
  
      public void setYAxisShowTitle(boolean show) {
          valueAxis.setDrawTitle(show);
          chartArea = calculatePlotArea();
          updateArea();
          chartChanged();
      }
  
      public void setYAxisShowLabels(boolean show) {
          valueAxis.setDrawTickLabels(show);
          chartArea = calculatePlotArea();
          updateArea();
          chartChanged();
      }
  
      public void setShowGrid(ShowGrid value) {
          Assert.isNotNull(value);
          valueAxis.setShowGrid(value);
          chartChanged();
      }
  
      public void setGridColor(RGB color) {
          Assert.isNotNull(color);
          valueAxis.setGridColor(color);
          chartChanged();
      }
  
      public RGB getBarColor(String key) {
          BarProperties barProps = barProperties.get(key);
          return barProps == null ? null : barProps.color;
      }
  
      public RGB getEffectiveBarColor(String key) {
          BarProperties barProps = barProperties.get(key);
          return (barProps == null || barProps.color == null) ? defaultBarColor : barProps.color;
      }
  
      public void setBarColor(String key, RGB color) {
          BarProperties barProps = getOrCreateBarProperties(key);
          barProps.color = color;
          updateLegends();
          chartChanged();
      }
  
      public void setBarOutlineColor(String key, RGB color) {
          BarProperties barProps = getOrCreateBarProperties(key);
          barProps.outlineColor = color;
          updateLegends();
          chartChanged();
      }
  
      public RGB getEffectiveBarOutlineColor(String key) {
          BarProperties barProps = barProperties.get(key);
          return (barProps == null || barProps.outlineColor == null) ? defaultBarOutlineColor : barProps.outlineColor;
      }
  
      public String getKeyFor(int seriesIndex) {
          if (seriesIndex >= 0 && seriesIndex < dataset.getSeriesCount())
              return dataset.getSeriesKey(seriesIndex);
          else
              return null;
      }
  
      private BarProperties getOrCreateBarProperties(String key) {
          BarProperties properties = barProperties.get(key);
          if (properties == null) {
              properties = new BarProperties();
              barProperties.put(key, properties);
          }
          return properties;
      }
  
      /*=============================================
       *               Drawing
       *=============================================*/
      Rectangle mainArea; // containing plots and axes
      Insets axesInsets; // space occupied by axes
  
      @Override
      protected Rectangle doLayoutChart(Graphics graphics, int pass) {
          ICoordsMapping mapping = getOptimizedCoordinateMapper();
  
          if (pass == 1) {
              // Calculate space occupied by title and legend and set insets accordingly
              Rectangle area = new Rectangle(getClientArea());
              Rectangle remaining = legendTooltip.layout(graphics, area);
              remaining = title.layout(graphics, area);
              remaining = legend.layout(graphics, remaining, 1);
  
              mainArea = remaining.getCopy();
              axesInsets = domainAxis.layout(graphics, mainArea, new Insets(), mapping, pass);
              axesInsets = valueAxis.layout(graphics, mainArea, axesInsets, mapping, pass);
  
              // tentative plotArea calculation (y axis ticks width missing from the picture yet)
              Rectangle plotArea = mainArea.getCopy().shrink(axesInsets);
              return plotArea;
          }
          else if (pass == 2) {
              // now the coordinate mapping is set up, so the y axis knows what tick labels
              // will appear, and can calculate the occupied space from the longest tick label.
              valueAxis.layout(graphics, mainArea, axesInsets, mapping, pass);
              domainAxis.layout(graphics, mainArea, axesInsets, mapping, pass);
              Rectangle remaining = mainArea.getCopy().shrink(axesInsets);
              remaining = legend.layout(graphics, remaining, pass);
              //FIXME how to handle it when plotArea.height/width comes out negative??
              Rectangle plotArea = bars.layout(graphics, remaining);
              return plotArea;
          }
          else
              return null;
      }
  
      @Override
      protected void doPaintCachableLayer(Graphics graphics, ICoordsMapping coordsMapping) {
          graphics.fillRectangle(GraphicsUtils.getClip(graphics));
          valueAxis.drawGrid(graphics, coordsMapping);
          bars.draw(graphics, coordsMapping);
      }
  
      @Override
      protected void doPaintNoncachableLayer(Graphics graphics, ICoordsMapping coordsMapping) {
          int highlightedItem = legend.getHighlightedItem();
  
          if (highlightedItem >= 0) {
              graphics.setAlpha(veilAlpha);
              graphics.setBackgroundColor(backgroundColor);
              graphics.fillRectangle(getViewportRectangle());
              graphics.setAlpha(255);
              valueAxis.drawGrid(graphics, coordsMapping);
  
              if (legend.isItemEnabled(highlightedItem))
                  bars.drawSingle(graphics, coordsMapping, highlightedItem);
          }
  
          graphics.setAlpha(255);
          graphics.setLineWidth(1);
          paintInsets(graphics);
          bars.drawBaseline(graphics, coordsMapping);
          title.draw(graphics);
          legend.draw(graphics);
          valueAxis.drawAxis(graphics, coordsMapping);
          domainAxis.draw(graphics, coordsMapping);
          drawRubberband(graphics);
          legendTooltip.draw(graphics);
          drawStatusText(graphics);
      }
  
      @Override
      public void setZoomX(double zoomX) {
          super.setZoomX(zoomX);
          chartChanged();
      }
  
      @Override
      public void setZoomY(double zoomY) {
          super.setZoomY(zoomY);
          chartChanged();
      }
  
      @Override
      String getHoverHtmlText(int x, int y) {
          int groupSeries = bars.findGroupSeries(fromCanvasX(x), fromCanvasY(y));
          if (groupSeries != -1) {
              int numSeries = dataset.getSeriesCount();
              int group = groupSeries / numSeries;
              int series = groupSeries % numSeries;
              String valueStr = dataset.getValueAsString(series, group);
              String title = dataset.getSeriesTitle(series);
  
              String line1 = StringEscapeUtils.escapeHtml4(title);
              String line2 = "value: " + valueStr;
  
              TextLayout textLayout = new TextLayout(getDisplay());
              textLayout.setText(line1 + "\n" + line2);
              textLayout.setWidth(320);
  
              return line1 + "<br>" + line2;
          }
          return null;
      }
  
      @Override
      public void setDisplayAxesDetails(boolean value) {
          valueAxis.setDrawTickLabels(value);
          valueAxis.setDrawTitle(value);
          domainAxis.setLabels(value);
          domainAxis.setDrawTitle(value);
          chartChanged();
      }
  }
  