Render graphics

Download Sample Viewer

Description

This application shows a class breaks renderer implemented using graphics on the client. Features from a service layer are loaded into the application using a QueryTask and displayed as Graphic objects. Based on input options, the features are classified based on the chosen attribute, number of classes, and classification method. The color scheme is also chosen by the user.

Sample Code

/* Copyright 2014 Esri

All rights reserved under the copyright laws of the United States
and applicable international laws, treaties, and conventions.

You may freely redistribute and use this sample code, with or
without modification, provided you include the original copyright
notice and use restrictions.

See the use restrictions.*/
package com.esri.client.samples.graphics;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

import javax.swing.BoxLayout;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;

import com.esri.core.geometry.Envelope;
import com.esri.core.geometry.SpatialReference;
import com.esri.core.map.Feature;
import com.esri.core.map.FeatureResult;
import com.esri.core.map.Graphic;
import com.esri.core.symbol.SimpleFillSymbol;
import com.esri.core.symbol.SimpleLineSymbol;
import com.esri.core.tasks.query.QueryParameters;
import com.esri.core.tasks.query.QueryTask;
import com.esri.map.ArcGISTiledMapServiceLayer;
import com.esri.map.GraphicsLayer;
import com.esri.map.JMap;
import com.esri.map.LayerList;

/**
 * This application shows a class breaks renderer implemented using graphics on the client. 
 * Features from a service layer are loaded into the application using a QueryTask and 
 * displayed as Graphic objects. Based on input options, the features are classified based 
 * on the chosen attribute, number of classes, and classification method. The color scheme 
 * is also chosen by the user.
 */
public class RenderingApp {

  // symbology
  final static SimpleLineSymbol SYM_STATE_BORDER = new SimpleLineSymbol(Color.WHITE, 1);
  final static SimpleFillSymbol SYM_STATE_MIN = new SimpleFillSymbol(
      Color.GRAY, SYM_STATE_BORDER);
  final static SimpleFillSymbol   SYM_STATE_MAX = new SimpleFillSymbol(
      Color.DARK_GRAY, SYM_STATE_BORDER);
  private static final int PANEL_HEIGHT = 235;
  private static final int PANEL_WIDTH = 220;
  private static final int BUTTON_HEIGHT = 25;
  private static final Color BG_COLOR = new Color(0, 0, 0, 80);

  // resources
  final String URL_LIGHT_GRAY = 
      "http://services.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer";
  final String URL_STATES   = 
      "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Demographics/ESRI_Census_USA/MapServer/5";

  // UI related constants
  final String INVALID_ITEM_STATES_LIST = "Select State...";

  // JMap
  private JMap map;

  // JMap related constants
  private GraphicsLayer graphicsLayer;

  // ------------------------------------------------------------------------
  // Constructor
  // ------------------------------------------------------------------------
  public RenderingApp() { }

  // ------------------------------------------------------------------------
  // Core functionality
  // ------------------------------------------------------------------------
  /**
   * Classifies and renders based on input options.
   * @param jMap map on which result will be rendered.
   * @param legendPanel legend that will be modified based on this execution.
   * @param theme attribute of a feature that is of interest. this feature is used to perform
   * the classification.
   * @param classCount number of classification of the features to be done.
   * @param classificationType method of classification.
   * @param colorTheme color theme to be applied for the classified result.
   */
  private void onClickOfRender(
      JMap jMap,
      JPanel legendPanel,
      Theme theme,
      ClassCount classCount,
      ClassificationType classificationType,
      ColorTheme colorTheme) {

    // create a query that gets all states name.
    QueryParameters query = new QueryParameters();
    // specify that all attributes are to be fetched
    query.setOutFields(new String[] {"*"});
    // exclude states with smaller area (such as D.C.) to avoid skewness in data
    query.setWhere("SQMI > 70");

    // since the coordinates system differs between base map and the feature layer,
    // specify necessary spatial reference for conversion.
    query.setInSpatialReference(SpatialReference.create(4269));
    query.setOutSpatialReference(jMap.getSpatialReference());

    FeatureResult queryResult = null;
    try {
      // execute the query.
      QueryTask task = new QueryTask(URL_STATES);
      queryResult = task.execute(query);
      if (queryResult.featureCount() < 1) {
        return;
      }

      ArrayList<Feature> features = new ArrayList<>();
      Iterator<Object> it = queryResult.iterator();
      while (it.hasNext()) {
        Object o = it.next();
        if (o instanceof Feature) {
          features.add((Feature) o);
        }
      }

      // calculate min/max of a theme - this information is required later for classification
      calculateMinMaxOfATheme(theme, features);
      // based on classification method and number of classification, determine the
      // max value of the theme in each of its classification.
      int numClassification = classCount.getCount();
      String attrName = theme.getAttributeName();
      double[] sortedMaxOfRanges = classificationType.calculateRangesForClassification(
          ((Double) theme.getMinFeature().getAttributeValue(attrName)).doubleValue(),
          ((Double) theme.getMaxFeature().getAttributeValue(attrName)).doubleValue(),
          features.toArray(new Feature[0]),
          theme.getAttributeName(),
          numClassification);

      Color[] colorPaletteTrans = colorTheme.getColorPalette(numClassification);
      Color[] colorPalette = new Color[colorPaletteTrans.length];

      for (int i = 0; i < colorPaletteTrans.length; i++) {
        Color current = colorPaletteTrans[i];
        colorPalette[i] = new Color(current.getRed(), current.getGreen(), current.getBlue());
      }

      // based on information about the ranges, classify every feature into corresponding range.
      // and, render those changes to the graphics layer.
      classifyAndApply(
          jMap,
          features,
          theme.getAttributeName(),
          sortedMaxOfRanges,
          colorPaletteTrans);

      // add a legend
      updateLegend(legendPanel, theme.toString(), colorPaletteTrans, sortedMaxOfRanges);

      // also add to legend - min and max value of the theme
      JTextField minFeature = new JTextField(
          "Min : " + theme.getMinFeature().getAttributeValue(theme.getAttributeName()) +
          " (" + theme.getMinFeature().getAttributeValue("STATE_NAME") + ")");
      minFeature.setHorizontalAlignment(SwingConstants.CENTER);
      minFeature.setAlignmentX(Component.CENTER_ALIGNMENT);
      minFeature.setMaximumSize(new Dimension(250, 30));
      minFeature.setForeground(Color.BLACK);
      minFeature.setEditable(false);
      legendPanel.add(minFeature);

      JTextField maxFeature = new JTextField(
          "Max : " + theme.getMaxFeature().getAttributeValue(theme.getAttributeName()) +
          " (" + theme.getMaxFeature().getAttributeValue("STATE_NAME") + ")");
      maxFeature.setHorizontalAlignment(SwingConstants.CENTER);
      maxFeature.setMaximumSize(new Dimension(250, 30));
      maxFeature.setForeground(Color.BLACK);
      maxFeature.setEditable(false);
      legendPanel.add(maxFeature);
      legendPanel.revalidate();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  /**
   * Classifies every feature and applies them to the graphics layer.
   * @param jMap map that has the graphics layer.
   * @param features features that need to be classified.
   * @param attrName the value of this attribute on the feature is used for classification.
   * @param sortedMaxOfRanges ranges that will be compared to with the feature value to decide
   * the classification.
   * @param colorPalette color to be used based on classification.
   */
  private void classifyAndApply(
      JMap jMap,
      ArrayList<Feature> features,
      String attrName,
      double[] sortedMaxOfRanges,
      Color[] colorPalette) {

    // remove previously highlighted state and highlight the current one
    if (graphicsLayer != null) {
      graphicsLayer.removeAll();
    }

    // classify the features based on its attribute value and the range information
    for (Feature feature : features) {
      int classification = 0;
      for (int r = 0; r < sortedMaxOfRanges.length; r++) {
        if (((Double) feature.getAttributeValue(attrName)).doubleValue() < sortedMaxOfRanges[r]) {
          classification = r;
          break;
        }
      }

      // apply corresponding color and add to the graphics layer.
      Graphic newGraphic = new Graphic(feature.getGeometry(),
          new SimpleFillSymbol(colorPalette[classification], SYM_STATE_BORDER));
      graphicsLayer.addGraphic(newGraphic);
    }
  }

  // ------------------------------------------------------------------------
  // Static methods
  // ------------------------------------------------------------------------
  /**
   * Starting point of this application.
   * @param args arguments to this application.
   */
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        try {
          // instance of this application
          RenderingApp renderingApp = new RenderingApp();

          // create the UI, including the map, for the application.
          JFrame appWindow = renderingApp.createWindow();
          appWindow.add(renderingApp.createUI());
          appWindow.setVisible(true);
        } catch (Exception e) {
          // on any error, display the stack trace.
          e.printStackTrace();
        }
      }
    });
  }

  // ------------------------------------------------------------------------
  // Public methods
  // ------------------------------------------------------------------------


  /**
   * Creates and displays the UI, including the map, for this application.
   */
  public JComponent createUI() throws Exception {
    // application content
    JComponent contentPane = createContentPane();

    // map
    map = createMap();

    // legend panel
    final JPanel legendPanel = new JPanel();
    BoxLayout boxLayout = new BoxLayout(legendPanel, BoxLayout.Y_AXIS);
    legendPanel.setLayout(boxLayout);
    legendPanel.setLocation(10, 10 + PANEL_HEIGHT + (3*BUTTON_HEIGHT));
    legendPanel.setBackground(null);
    legendPanel.setDoubleBuffered(true);
    legendPanel.setBorder(new LineBorder(Color.BLACK, 5, false));
    contentPane.add(legendPanel);

    // control panel
    final JPanel controlPanel = createControlPanel(map, legendPanel);
    controlPanel.setLocation(10, 10 + BUTTON_HEIGHT);
    controlPanel.setSize(PANEL_WIDTH, PANEL_HEIGHT);
    contentPane.add(controlPanel);

    // add buttons to toggle panels
    final JButton btnLegendToggle = new JButton("Toggle Legend");
    btnLegendToggle.setLocation(10, 10 + PANEL_HEIGHT + (2*BUTTON_HEIGHT));
    btnLegendToggle.setSize(PANEL_WIDTH, BUTTON_HEIGHT);
    btnLegendToggle.setVisible(true);
    btnLegendToggle.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        legendPanel.setVisible(!legendPanel.isVisible());
      }
    });
    contentPane.add(btnLegendToggle);

    final JButton btnControlToggle = new JButton("Toggle Properties");
    btnControlToggle.setLocation(10, 10);
    btnControlToggle.setSize(PANEL_WIDTH, BUTTON_HEIGHT);
    btnControlToggle.setVisible(true);
    btnControlToggle.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        controlPanel.setVisible(!controlPanel.isVisible());
      }
    });
    contentPane.add(btnControlToggle);

    contentPane.add(map);

    return contentPane;
  }

  // ------------------------------------------------------------------------
  // Private methods
  // ------------------------------------------------------------------------
  /**
   * Creates a window.
   * @return a window.
   */
  private JFrame createWindow() {
    JFrame window = new JFrame("Graphics Layer Rendering Application");
    window.setBounds(100, 100, 1000, 700);
    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    window.getContentPane().setLayout(new BorderLayout(0, 0));
    window.addWindowListener(new WindowAdapter() {
      @Override
      public void windowClosing(WindowEvent windowEvent) {
        super.windowClosing(windowEvent);
        map.dispose();
      }
    });
    return window;
  }

  /**
   * Creates a content pane.
   * @return a content pane.
   */
  private static JLayeredPane createContentPane() {
    JLayeredPane contentPane = new JLayeredPane();
    contentPane.setLayout(new BorderLayout(0, 0));
    contentPane.setVisible(true);
    return contentPane;
  }

  /**
   * Creates a map.
   * @return a map.
   */
  private JMap createMap() throws Exception {
    final JMap jMap = new JMap();
    // -----------------------------------------------------------------------------------------
    // Base Layer - focus on U.S by default
    // -----------------------------------------------------------------------------------------
    final ArcGISTiledMapServiceLayer baseLayer = new ArcGISTiledMapServiceLayer(URL_LIGHT_GRAY);

    // Zoom to U.S.
    jMap.setExtent(new Envelope(-15000000, 2000000, -7000000, 8000000));

    LayerList layers = jMap.getLayers();
    layers.add(baseLayer);

    // -----------------------------------------------------------------------------------------
    // Graphics Layer
    // -----------------------------------------------------------------------------------------
    graphicsLayer = new GraphicsLayer();
    graphicsLayer.setName("graphicsLayer");
    layers.add(graphicsLayer);

    return jMap;
  }

  private JPanel createControlPanel(final JMap jMap, final JPanel legendPanel) {

    // label for title
    JTextField lblTitle = new JTextField("Thematic Properties");
    lblTitle.setHorizontalAlignment(SwingConstants.CENTER);
    lblTitle.setMaximumSize(new Dimension(250, 20));
    lblTitle.setForeground(Color.WHITE);
    lblTitle.setBackground(null);
    lblTitle.setEditable(false);

    // label for theme
    JTextField lblTheme = new JTextField("Theme:");
    lblTheme.setMaximumSize(new Dimension(250, 20));
    lblTheme.setForeground(Color.WHITE);
    lblTheme.setBackground(null);
    lblTheme.setEditable(false);

    // drop-down list for the theme
    final JComboBox<Theme> cbxTheme = new JComboBox<>();
    cbxTheme.setMaximumSize(new Dimension(250, 20));
    cbxTheme.setModel(new DefaultComboBoxModel<>(Theme.values()));

    // label for number of classification
    JTextField lblClassCount = new JTextField("Number of classes: ");
    lblClassCount.setMaximumSize(new Dimension(250, 20));
    lblClassCount.setForeground(Color.WHITE);
    lblClassCount.setBackground(null);
    lblClassCount.setEditable(false);

    // drop-down list for the number of classification
    final JComboBox<ClassCount> cbxClassCount = new JComboBox<>();
    cbxClassCount.setMaximumSize(new Dimension(250, 20));
    cbxClassCount.setModel(new DefaultComboBoxModel<>(ClassCount.values()));
    cbxClassCount.setSelectedIndex(2);

    // label for classification method
    JTextField lblClassificationType = new JTextField("Classification Type:");
    lblClassificationType.setMaximumSize(new Dimension(250, 20));
    lblClassificationType.setForeground(Color.WHITE);
    lblClassificationType.setBackground(null);
    lblClassificationType.setEditable(false);

    // drop-down list for the classification method
    final JComboBox<ClassificationType> cbxClassificationType = new JComboBox<>();
    cbxClassificationType.setMaximumSize(new Dimension(250, 20));
    cbxClassificationType.setModel(new DefaultComboBoxModel<>(ClassificationType.values()));
    cbxClassificationType.setSelectedIndex(1);

    // label for color theme
    JTextField lblColorTheme = new JTextField("Color Theme:");
    lblColorTheme.setMaximumSize(new Dimension(250, 20));
    lblColorTheme.setForeground(Color.WHITE);
    lblColorTheme.setBackground(null);
    lblColorTheme.setEditable(false);

    // drop-down list for the color theme
    final JComboBox<ColorTheme> cbxColorTheme = new JComboBox<>();
    cbxColorTheme.setMaximumSize(new Dimension(250, 20));
    cbxColorTheme.setModel(new DefaultComboBoxModel<>(ColorTheme.values()));

    // button - when clicked, render
    JButton btnRender = new JButton("Render");
    btnRender.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        onClickOfRender(
            jMap,
            legendPanel,
            (Theme) cbxTheme.getSelectedItem(),
            (ClassCount) cbxClassCount.getSelectedItem(),
            (ClassificationType) cbxClassificationType.getSelectedItem(),
            (ColorTheme) cbxColorTheme.getSelectedItem());
      }
    });
    btnRender.setAlignmentX(Component.CENTER_ALIGNMENT);

    // group the above UI items into a control panel
    final JPanel controlPanel = new JPanel();
    BoxLayout boxLayout = new BoxLayout(controlPanel, BoxLayout.Y_AXIS);
    controlPanel.setLayout(boxLayout);
    controlPanel.setBackground(BG_COLOR);
    controlPanel.setDoubleBuffered(true);
    controlPanel.setBorder(new LineBorder(Color.BLACK, 5, false));
    controlPanel.setVisible(true);

    controlPanel.add(lblTitle);
    controlPanel.add(lblTheme);
    controlPanel.add(cbxTheme);
    controlPanel.add(lblClassCount);
    controlPanel.add(cbxClassCount);
    controlPanel.add(lblClassificationType);
    controlPanel.add(cbxClassificationType);
    controlPanel.add(lblColorTheme);
    controlPanel.add(cbxColorTheme);
    controlPanel.add(btnRender);

    return controlPanel;
  }

  /**
   * Adds a legend to the map.
   * @param legendPanel legendUI that will me modified with required information.
   * @param colorPalette color palette.
   * @param sortedMaxOfRanges range values for the color palette. there is one-one
   * correspondence between this and the color palette.
   */
  private void updateLegend(
      JPanel legendPanel,
      String legendTitle,
      Color[] colorPalette,
      double[] sortedMaxOfRanges) {

    // remove existing content of the legend
    legendPanel.removeAll();
    legendPanel.setSize(PANEL_WIDTH, 60 + (30 * sortedMaxOfRanges.length));

    // legend title
    JTextField lblTitle = new JTextField(legendTitle);
    lblTitle.setHorizontalAlignment(SwingConstants.CENTER);
    lblTitle.setMaximumSize(new Dimension(PANEL_WIDTH, 40));
    lblTitle.setEditable(false);
    legendPanel.add(lblTitle);

    // legend
    final JPanel colorPanel = new JPanel();
    GridLayout gridLayout = new GridLayout(sortedMaxOfRanges.length, 2);
    gridLayout.setVgap(5);
    colorPanel.setLayout(gridLayout);
    colorPanel.setBackground(new Color(255, 255, 255, 255));
    colorPanel.setDoubleBuffered(true);
    colorPanel.setBorder(new LineBorder(Color.BLACK, 1, false));

    DecimalFormat decimalFormat = new DecimalFormat("##.##");

    for (int c = 0; c < sortedMaxOfRanges.length; c++) {
      // color box
      JPanel lblColorBox = new JPanel();
      lblColorBox.setForeground(colorPalette[c]);
      lblColorBox.setBackground(colorPalette[c]);
      lblColorBox.setBorder(new LineBorder(Color.BLACK, 1, false));
      colorPanel.add(lblColorBox);

      // description
      JTextField lblColorDescription = null;
      if (c == 0) {
        lblColorDescription = new JTextField(
            "Less than " + decimalFormat.format(sortedMaxOfRanges[c]));
      } else if (c < sortedMaxOfRanges.length - 1) {
        lblColorDescription = new JTextField(
            decimalFormat.format(sortedMaxOfRanges[c-1]) + " to " +
                decimalFormat.format(sortedMaxOfRanges[c]));
      } else {
        lblColorDescription = new JTextField(
            decimalFormat.format(sortedMaxOfRanges[c-1]) + " and above");
      }

      lblColorDescription.setMaximumSize(new Dimension(PANEL_WIDTH, 30));
      lblColorDescription.setForeground(Color.BLACK);
      lblColorDescription.setEditable(false);
      colorPanel.add(lblColorDescription);
    }
    legendPanel.add(colorPanel);
  }

  /**
   * Calculates min and max of a range of values.
   * @param theme this is useful to know the name of the attribute of interest.
   * @param features all featuress that have the attribute values.
   */
  private void calculateMinMaxOfATheme(Theme theme, ArrayList<Feature> features) {

    // if the theme already has min and max calculate, then no need to calculate again.
    if (theme.getMinFeature() != null && theme.getMaxFeature() != null) {
      return;
    }

    // get the feature with min and max attribute value.
    String attrName = theme.getAttributeName();
    Feature minFeature = null;
    Feature maxFeature = null;
    for (Feature feature : features) {
      double attrValue =  ((Double) feature.getAttributeValue(attrName)).doubleValue();
      if (minFeature == null) {
        minFeature = feature;
      } else if (attrValue < ((Double) minFeature.getAttributeValue(attrName)).doubleValue()) {
        minFeature = feature;
      }
      if (maxFeature == null) {
        maxFeature = feature;
      } else if (attrValue > ((Double) maxFeature.getAttributeValue(attrName)).doubleValue()) {
        maxFeature = feature;
      }
    }

    theme.setMinFeature(minFeature);
    theme.setMaxFeature(maxFeature);
  }
}

enum Theme {
  POPULATION("2000 Pop per Sq Mi", "POP00_SQMI"),
  POPULATION_DENSITY("2007 Pop per Sq Mi", "POP07_SQMI"),
  MEDIAN_AGE("Median age", "MED_AGE"),
  MEDIAN_AGE_MALE("Median age - Male", "MED_AGE_M"),
  MEDIAN_AGE_FEMALE("Median age - Female", "MED_AGE_F");

  private String displayName;
  private String attributeName;

  private Feature minFeature;
  private Feature maxFeature;

  Theme(String displayName, String attributeName) {
    this.displayName = displayName;
    this.attributeName = attributeName;
  }

  @Override
  public String toString() {
    return displayName;
  }

  public String getAttributeName() {
    return attributeName;
  }

  public Feature getMinFeature() {
    return minFeature;
  }

  public void setMinFeature(Feature minFeature) {
    this.minFeature = minFeature;
  }

  public Feature getMaxFeature() {
    return maxFeature;
  }

  public void setMaxFeature(Feature maxFeature) {
    this.maxFeature = maxFeature;
  }
}

enum ClassCount {
  THREE(3),
  FOUR(4),
  FIVE(5),
  SIX(6);

  private int count;

  ClassCount(int num) {
    this.count = num;
  }

  @Override
  public String toString() {
    return count + "";
  }

  public int getCount() {
    return count;
  }
}

enum ClassificationType {
  EQUAL_INTERVAL("Equal interval"),
  QUANTILE("Quantile");

  private String displayName;

  ClassificationType(String displayName) {
    this.displayName = displayName;
  }

  @Override
  public String toString() {
    return displayName;
  }

  /**
   * Based on classification method, calculate the max values of each range.
   * @param minValue min value of the domain of values of interest.
   * @param maxValue max value of the domain of values of interest.
   * @param features all featuress - from which the entire domain of values can be obtained.
   * @param attrName attribute name whose values are of interest.
   * @param numClasses number of classification.
   * @return a set of values that represents the max for that range. this set is in ascending
   * order.
   * e.g., double[0]=125;double[1]=250 indicates that any feature whose attribute value
   * is less than 125 belongs to classification 0; any feature whose attribute value is greater
   * or equal to 125 but less than 250 belongs to classification 1.
   */
  public double[] calculateRangesForClassification(
      double minValue,
      double maxValue,
      Feature[] features,
      String attrName,
      int numClasses) {

    double[] sortedMaxOfRanges = new double[numClasses];

    switch (this) {
      case EQUAL_INTERVAL:
        // each classification has almost the same interval.
        double range = maxValue - minValue;
        double interval = range / numClasses;
        sortedMaxOfRanges[0] = minValue;
        sortedMaxOfRanges[numClasses-1] = maxValue;
        for (int c = 1; c < numClasses - 1; c++) {
          sortedMaxOfRanges[c] =  sortedMaxOfRanges[c-1] + interval;
        }
        break;
      case QUANTILE:
        // each classification has almost equal number of elements.
        double[] allElementsOfAAttribute = new double[features.length];
        for (int i = 0; i < features.length; i++) {
          allElementsOfAAttribute[i] = ((Double) features[i].getAttributeValue(attrName)).doubleValue();
        }
        Arrays.sort(allElementsOfAAttribute);
        int numElementsInEachClassification = features.length / numClasses;
        for (int c = 0; c < numClasses - 1; c++) {
          int maxElementForThisClassification =
              (c * numElementsInEachClassification) + numElementsInEachClassification;
          sortedMaxOfRanges[c] = allElementsOfAAttribute[maxElementForThisClassification];
        }
        sortedMaxOfRanges[numClasses - 1] = allElementsOfAAttribute[features.length - 1];
        break;
    }
    return sortedMaxOfRanges;
  }
}

enum ColorTheme {
  BLUE("Blue"),
  GREEN("Green"),
  RED("Red"),
  MULTI("Multi Color");

  private String displayName;

  ColorTheme(String displayName) {
    this.displayName = displayName;
  }

  @Override
  public String toString() {
    return displayName;
  }

  public Color[] getColorPalette(int numColors) {
    Color[] palette = new Color[numColors];

    int increment = 255/numColors;
    int r = 0;
    int g = 0;
    int b = 0;
    palette[0] = new Color(170, 170, 170, 90);
    for (int paletteIndex = 1; paletteIndex < numColors; paletteIndex++) {
      switch (this) {
        case BLUE:
          b = b + increment;
          break;
        case GREEN:
          g = g + increment;
          break;
        case RED:
          r = r + increment;
          break;
        case MULTI:
          r = (paletteIndex & 1) == 1 ? 255 : 0;
          g = (paletteIndex & 2) == 2 ? 255 : 0;
          b = (paletteIndex & 4) == 4 ? 255 : 0;
          break;
        default:
          break;
      }
      palette[paletteIndex] = new Color(r, g, b, 90);
    }
    return palette;
  }
}
Feedback on this topic?