/*
 * Licensed Materials - Property of Rogue Wave Software, Inc. 
 * © Copyright Rogue Wave Software, Inc. 2014, 2017 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */
package treemap;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.tree.TreePath;

import ilog.views.chart.IlvChart;
import ilog.views.chart.IlvChartLayout;
import ilog.views.chart.data.IlvTreeTableDataSource;
import ilog.views.chart.datax.IlvColumnUtilities;
import ilog.views.chart.datax.IlvDataColumnInfo;
import ilog.views.chart.datax.IlvDefaultDataColumnInfo;
import ilog.views.chart.datax.adapter.IlvClusterNode;
import ilog.views.chart.datax.adapter.IlvFlatTableToTableModel;
import ilog.views.chart.datax.adapter.partition.IlvPartitionerFactory;
import ilog.views.chart.datax.adapter.partition.IlvStringPartitionerFactory;
import ilog.views.chart.datax.adapter.partition.IlvUniformScalePartitionerFactory;
import ilog.views.chart.datax.flat.table.IlvDefaultFlatTableModel;
import ilog.views.chart.datax.flat.table.IlvFlatTableModel;
import ilog.views.chart.event.TreemapFocusEvent;
import ilog.views.chart.event.TreemapFocusListener;
import ilog.views.chart.renderer.IlvColorScheme;
import ilog.views.chart.renderer.IlvTreemapChartRenderer;
import ilog.views.util.data.IlvCSVReader;
import ilog.views.util.swing.IlvJSplitPane;
import ilog.views.util.swing.layout.IlvBetterFlowLayout;
import ilog.views.util.swing.table.IlvJTableHeaderWithToolTips;
import ilog.views.util.swing.table.IlvJTableWithToolTips;
import shared.AbstractChartExample;

/**
 * Demonstrates the treemap's main features.
 */
public class TreemapDemo extends AbstractChartExample {

  // The data source containing the data to be displayed.
  IlvTreeTableDataSource dataSource;

  // The table displaying the raw data.
  JTable rawTable;
  JScrollPane rawTableView;
  // The columns display used for partitioning.
  JTableHeader columnsTable;
  int separatorIndex;
  // The chooser for the area and color columns.
  JComboBox<String> areaColumnChooser;
  JComboBox<String> colorColumnChooser;
  // The chooser for the labeling mode.
  JComboBox<String> labelingChooser;
  // The combined treemap and tree-table.
  TreemapWithTreeTable trees;

  // Intermediate containers.
  JSplitPane treesPanel;
  static final double treesPanelDefaultDivision = 0.35;
  JSplitPane wholePanel;
  static final double wholePanelDefaultDivision = 0.6;

  // Flags for inhibiting selection propagation.
  boolean inhibitSelectionPropToRawTable;
  boolean inhibitSelectionPropToTreeTable;

  /**
   * Initializes the example user interface in the specified container.
   * 
   * @param container
   *          The <code>contentPane</code> of the <code>JFrame</code>.
   */
  Override
  public void init(Container container) {
    super.init(container);

    initDataSource();
    initRawTable();
    initTreemapWithTreeTable();
    initPartitioningControl();
    initAreaColumnControl();
    initColorColumnControl();
    initLabelingControl();

    // Initialize the status of toolbar buttons.
    trees.treemapPanel.updateEnabledStatuses();

    initTableSelectionSynchronization();

    // Create some auxiliary labels.
    JLabel columnsTableDescription = new JLabel(
        "Partition by: (Drag one or two items to the left, before the black separator.)");
    JLabel areaColumnChooserDescription = new JLabel("Show as important:");
    JLabel colorColumnChooserDescription = new JLabel("Show through color:");
    JLabel labelingChooserDescription = new JLabel("Labels:");

    // Put the pieces together.
    Box columnsTableBox = new Box(BoxLayout.PAGE_AXIS);
    {
      Box row = new Box(BoxLayout.LINE_AXIS);
      row.add(columnsTableDescription);
      row.add(Box.createHorizontalGlue());
      columnsTableBox.add(row);
    }
    columnsTableBox.add(columnsTable);
    Box areaColumnChooserBox = new Box(BoxLayout.PAGE_AXIS);
    {
      Box row = new Box(BoxLayout.LINE_AXIS);
      row.add(areaColumnChooserDescription);
      row.add(Box.createHorizontalGlue());
      areaColumnChooserBox.add(row);
    }
    areaColumnChooserBox.add(areaColumnChooser);
    Box colorColumnChooserBox = new Box(BoxLayout.PAGE_AXIS);
    {
      Box row = new Box(BoxLayout.LINE_AXIS);
      row.add(colorColumnChooserDescription);
      row.add(Box.createHorizontalGlue());
      colorColumnChooserBox.add(row);
    }
    colorColumnChooserBox.add(colorColumnChooser);
    Box labelingChooserBox = new Box(BoxLayout.PAGE_AXIS);
    {
      Box row = new Box(BoxLayout.LINE_AXIS);
      row.add(labelingChooserDescription);
      row.add(Box.createHorizontalGlue());
      labelingChooserBox.add(row);
    }
    labelingChooserBox.add(labelingChooser);
    Box chooserBoxes = new Box(BoxLayout.LINE_AXIS);
    chooserBoxes.add(areaColumnChooserBox);
    chooserBoxes.add(Box.createHorizontalStrut(5));
    chooserBoxes.add(colorColumnChooserBox);
    chooserBoxes.add(Box.createHorizontalStrut(5));
    chooserBoxes.add(labelingChooserBox);
    JPanel controlPanel = new JPanel();
    controlPanel.setLayout(new IlvBetterFlowLayout(FlowLayout.LEADING));
    controlPanel.add(columnsTableBox);
    controlPanel.add(chooserBoxes);
    treesPanel = new IlvJSplitPane(JSplitPane.HORIZONTAL_SPLIT, trees.treetableView, trees.treemapPanel);
    treesPanel.setResizeWeight(treesPanelDefaultDivision);
    JPanel upperHalf = new JPanel();
    upperHalf.setLayout(new BorderLayout());
    upperHalf.add(treesPanel, BorderLayout.CENTER);
    upperHalf.add(controlPanel, BorderLayout.NORTH);
    wholePanel = new IlvJSplitPane(JSplitPane.VERTICAL_SPLIT, upperHalf, rawTableView);
    wholePanel.setResizeWeight(wholePanelDefaultDivision);
    container.setLayout(new BorderLayout());
    container.add(wholePanel, BorderLayout.CENTER);

    treesPanel.setDividerLocation(treesPanelDefaultDivision);
    wholePanel.setDividerLocation(wholePanelDefaultDivision);
  }

  // The file containing the data.
  // private final String dataFileXML = "data/energy.xml";
  private final String dataFileCSV = "data/energy.csv";
  // Information about the columns in the Excel data.
  private final String[] columnNames = { "Country", "Region", "Population", "GDP", "GDP per Capita", "Ranking", "Year",
      "Energy Type", "Production", "Consumption" };
  private final Class<?>[] columnTypes = { String.class, String.class, Double.class, Double.class, Double.class,
      Integer.class, String.class, String.class, Double.class, Double.class };

  /**
   * Loads the data into memory and makes it accessible through a data source.
   */
  private void initDataSource() {
    // Load the data.
    IlvFlatTableModel tableData;
    // if (false) {
    // // Load from the XML file.
    // // This takes more memory, but is useful if the original file is an
    // // Excel file.
    // Workbook data;
    // try {
    // data = new Workbook(getResourceURL(dataFileXML));
    // } catch (IOException e) {
    // e.printStackTrace();
    // return;
    // }
    //
    // // Extract the first worksheet.
    // tableData = data.getWorksheet(0, columnNames, columnTypes, 1);
    // } else {
    // Load from the CSV file.
    // This takes less memory.
    TableModel table;
    try {
      Reader reader = new InputStreamReader(new BufferedInputStream(openStream(getResourceURL(dataFileCSV))), "UTF-8");
      try {
        IlvCSVReader parser = IlvCSVReader.getInstance('\t', 0);
        table = parser.read(reader);
      } finally {
        reader.close();
      }
    } catch (IOException e) {
      e.printStackTrace();
      return;
    }

    // Copy the data into an IlvFlatTableModel, with appropriate
    // conversion of values.
    int numColumns = table.getColumnCount();
    if (numColumns != columnNames.length)
      throw new RuntimeException("The columnNames array is not up-to-date.");
    IlvDataColumnInfo[] columns = new IlvDataColumnInfo[numColumns];
    for (int col = 0; col < numColumns; col++) {
      columns[col] = new IlvDefaultDataColumnInfo(columnNames[col], columnTypes[col]);
    }
    int numRows = table.getRowCount();
    tableData = new IlvDefaultFlatTableModel(numRows, columns);
    for (int col = 0; col < numColumns; col++) {
      if (columnTypes[col] == Double.class) {
        for (int row = 0; row < numRows; row++) {
          double value = Double.parseDouble(table.getValueAt(row, col).toString());
          tableData.setDoubleAt(value, row, col);
        }
      } else if (columnTypes[col] == Integer.class) {
        for (int row = 0; row < numRows; row++) {
          int value = (int) Double.parseDouble(table.getValueAt(row, col).toString());
          tableData.setValueAt(new Integer(value), row, col);
        }
      } else if (columnTypes[col] == String.class) {
        for (int row = 0; row < numRows; row++) {
          String value = table.getValueAt(row, col).toString();
          tableData.setValueAt(value, row, col);
        }
      } else {
        for (int row = 0; row < numRows; row++) {
          Object value = table.getValueAt(row, col);
          tableData.setValueAt(value, row, col);
        }
      }
    }
    // }

    // Set the types of some columns to enumerated. This is a hint to the
    // QUALITATIVE color scheme how to distribute the colors.
    ((IlvDefaultDataColumnInfo) IlvColumnUtilities.getColumnByName(tableData, "Country")).setEnumerated(true);
    ((IlvDefaultDataColumnInfo) IlvColumnUtilities.getColumnByName(tableData, "Region")).setEnumerated(true);

    // Create a data source taking the worksheet as input.
    dataSource = new IlvTreeTableDataSource(tableData);
  }

  /**
   * Initializes the raw table view of the data.
   */
  private void initRawTable() {
    // Create the table view.
    IlvFlatTableModel tableData = dataSource.getFlatTableModel();
    rawTable = new IlvJTableWithToolTips(new IlvFlatTableToTableModel(tableData));

    // Specify the names and proportions of the columns.
    TableColumnModel rawTableColumns = new DefaultTableColumnModel();
    for (int col = 0; col < columnNames.length; col++) {
      TableColumn column = new TableColumn(col, 80);
      column.setHeaderValue(columnNames[col]);
      rawTableColumns.addColumn(column);
    }
    rawTable.setColumnModel(rawTableColumns);

    // Enable tooltips on the header.
    rawTable.setTableHeader(new IlvJTableHeaderWithToolTips(rawTable.getColumnModel()));

    // Add scroll bars around it.
    rawTableView = new JScrollPane(rawTable);
  }

  /**
   * Initializes the treemap and tree-table views of the data.
   */
  private void initTreemapWithTreeTable() {
    trees = new TreemapWithTreeTable(dataSource, "Country", new String[0], getResourceURL("resources/treemap.css"));
  }

  /**
   * Creates the columns display used for partitioning.
   */
  private void initPartitioningControl() {
    columnsTable = new ReorderableTableHeader() {
      Override
      public void columnDragged(int fromIndex, int toIndex) {
        // Enforce a rule that at most 3 columns are used
        // for partitioning. More precisely, at most 2
        // enumerated columns and at most 1 numeric column.
        // Otherwise the partitioned tree model takes up
        // too much memory, leading to OutOfMemoryError.
        // The limits 2 and 1 are arbitrary.
        final int enumColumnsMax = 2;
        final int numericColumnsMax = 1;
        // We don't want to interfere with the column the user is
        // moving. Therefore we count how many columns of each type
        // to keep before the separator, excluding toIndex.
        int enumColumnsToKeep = enumColumnsMax;
        int numericColumnsToKeep = numericColumnsMax;
        IlvFlatTableModel model = dataSource.getFlatTableModel();
        int col;
        for (col = 0; col < getColumnModel().getColumnCount(); col++) {
          // Consider only the columns left of the separator.
          if ("".equals(getColumnModel().getColumn(col).getIdentifier()))
            break;
          // Look which column the user is moving.
          if (col == toIndex) {
            String columnName = (String) getColumnModel().getColumn(col).getIdentifier();
            IlvDataColumnInfo column = IlvColumnUtilities.getColumnByName(model, columnName);
            if (Number.class.isAssignableFrom(column.getType()))
              numericColumnsToKeep -= 1;
            else
              enumColumnsToKeep -= 1;
          }
        }
        int separator = col;
        int moveTarget = separator;
        for (col = 0; col < separator; col++) {
          // Don't interfere with the column the user is moving.
          if (col != toIndex) {
            String columnName = (String) getColumnModel().getColumn(col).getIdentifier();
            IlvDataColumnInfo column = IlvColumnUtilities.getColumnByName(model, columnName);
            if (Number.class.isAssignableFrom(column.getType())) {
              if (numericColumnsToKeep > 0) {
                numericColumnsToKeep--;
                continue;
              }
            } else {
              if (enumColumnsToKeep > 0) {
                enumColumnsToKeep--;
                continue;
              }
            }
            // Move col behind the separator.
            getColumnModel().moveColumn(col, moveTarget);
            if (col < toIndex)
              toIndex--;
            col--;
            separator--;
          }
        }
        // Now update the partitioning accordingly.
        updatePartitioners();
      }
    };
    final TableColumnModel columnsTableColumns = new DefaultTableColumnModel();
    {
      TableColumn separatorColumn = new TableColumn(-1, 10);
      separatorColumn.setHeaderValue("");
      DefaultTableCellRenderer separatorRenderer = new DefaultTableCellRenderer() {
        Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
            int row, int column) {
          return this;
        }
      };
      separatorRenderer.setBorder(BorderFactory.createLineBorder(Color.black, 3));
      separatorColumn.setHeaderRenderer(separatorRenderer);
      columnsTableColumns.addColumn(separatorColumn);
    }
    DefaultTableCellRenderer columnHeaderRenderer =
        // Similar to what JTableHeader.UIResourceTableCellRenderer does.
        new DefaultTableCellRenderer() {
          private Font plainFont = getFont().deriveFont(Font.PLAIN);
          private Font boldFont = getFont().deriveFont(Font.BOLD);

          Override
          public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
              boolean hasFocus, int row, int column) {
            setText(value.toString());
            setBorder(UIManager.getBorder("TableHeader.cellBorder"));
            // Emphasize the columns before the black separator block.
            int separator;
            for (separator = 0; separator < columnsTableColumns.getColumnCount(); separator++)
              if ("".equals(columnsTableColumns.getColumn(separator).getIdentifier()))
                break;
            setFont(column < separator ? boldFont : plainFont);
            return this;
          }
        };
    for (int col = 0; col < columnNames.length; col++) {
      String columnName = columnNames[col];
      int width = Math.max((new JLabel(columnName)).getPreferredSize().width + 5, 75);
      TableColumn column = new TableColumn(col, width);
      column.setHeaderValue(columnName);
      column.setHeaderRenderer(columnHeaderRenderer);
      columnsTableColumns.addColumn(column);
    }
    columnsTable.setColumnModel(columnsTableColumns);
    // Create an invisible JTable with 0 rows so that columnsTable becomes
    // functional in JDK >= 1.6.
    new JTable(new DefaultTableModel(), columnsTableColumns).setTableHeader(columnsTable);
    // Initially select the "Year" and "Country" columns.
    {
      int chosenColumnIndex = -1;
      for (int i = 0; i < columnsTableColumns.getColumnCount(); i++)
        if ("Year".equals(columnsTableColumns.getColumn(i).getIdentifier()))
          chosenColumnIndex = i;
      assert (chosenColumnIndex >= 0);
      if (chosenColumnIndex > 0)
        columnsTableColumns.moveColumn(chosenColumnIndex, 0);
    }
    {
      int chosenColumnIndex = -1;
      for (int i = 1; i < columnsTableColumns.getColumnCount(); i++)
        if ("Country".equals(columnsTableColumns.getColumn(i).getIdentifier()))
          chosenColumnIndex = i;
      assert (chosenColumnIndex >= 1);
      if (chosenColumnIndex > 1)
        columnsTableColumns.moveColumn(chosenColumnIndex, 1);
    }
    updatePartitioners();
  }

  /**
   * Creates the chooser for the area column.
   */
  private void initAreaColumnControl() {
    ArrayList<String> areaColumnChoices = new ArrayList<String>();
    areaColumnChoices.add("(all equal area)");
    for (int col = 0; col < columnNames.length; col++)
      if (columnTypes[col] == Double.class)
        areaColumnChoices.add(columnNames[col]);
    areaColumnChooser = new JComboBox<String>(areaColumnChoices.toArray(new String[areaColumnChoices.size()]));
    areaColumnChooser.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent event) {
        updateAreaColumn();
      }
    });
    areaColumnChooser.setSelectedItem("Consumption");
  }

  /**
   * Creates the chooser for the color column.
   */
  private void initColorColumnControl() {
    ArrayList<String> colorColumnChoices = new ArrayList<String>();
    colorColumnChoices.add("(all same color)");
    for (int col = 0; col < columnNames.length; col++)
      if (!columnNames[col].equals("Name"))
        colorColumnChoices.add(columnNames[col]);
    colorColumnChooser = new JComboBox<String>(colorColumnChoices.toArray(new String[colorColumnChoices.size()]));
    colorColumnChooser.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent event) {
        updateColorColumn();
      }
    });
    colorColumnChooser.setSelectedItem("Energy Type");
  }

  /**
   * Creates the chooser for the labeling mode.
   */
  private void initLabelingControl() {
    String[] labelingChoices = { "off", "smart", "1 level", "2 levels", "all" };
    labelingChooser = new JComboBox<String>(labelingChoices);
    labelingChooser.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent event) {
        updateLabeling();
      }
    });
    labelingChooser.setSelectedItem("smart");
    // When the labeling is set to "on", it actually depends on the
    // focus level.
    IlvChart chart = trees.treemapPanel.getChart();
    chart.addTreemapFocusListener(new TreemapFocusListener() {
      Override
      public void focusChanged(TreemapFocusEvent event) {
        updateLabeling();
      }
    });
  }

  /**
   * Initializes a synchronization between the selection of the table and the
   * selection of the tree-table (and thus also with the selection of the
   * treemap - it is synchronized through
   * <code>TreemapWithTreeTable.initSelectionSynchronization()</code>.
   */
  private void initTableSelectionSynchronization() {
    final ListSelectionModel tableSelectionModel = rawTable.getSelectionModel();
    final ListSelectionModel treetableSelectionModel = trees.treetable.getSelectionModel();

    // Ensure that the treetable's selection display is in sync with the
    // raw table's selection, as far as single-selection is concerned.
    tableSelectionModel.addListSelectionListener(new ListSelectionListener() {
      Override
      public void valueChanged(ListSelectionEvent event) {
        if (inhibitSelectionPropToTreeTable)
          return;
        if (tableSelectionModel.isSelectionEmpty())
          treetableSelectionModel.clearSelection();
        else {
          TreePath commonPath = null;
          int min = tableSelectionModel.getMinSelectionIndex();
          int max = tableSelectionModel.getMaxSelectionIndex();
          for (int i = min; i <= max; i++)
            if (tableSelectionModel.isSelectedIndex(i)) {
              // Get the object corresponding to this row by
              // accessing the flat-model view.
              Object object = dataSource.getFlatModel().getObjects().get(i);
              // Get the tree path from the root to that object.
              TreePath path = dataSource.getTreeModel().getPath(object);
              assert (path != null);
              if (commonPath == null)
                commonPath = path;
              else
                commonPath = computeCommonAncestor(commonPath, path);
            }
          assert (commonPath != null);
          TreePath newSelection = commonPath;
          try {
            inhibitSelectionPropToRawTable = true;
            trees.expandParents(newSelection);
            int row = trees.treetable.getTree().getRowForPath(newSelection);
            trees.ensureVisible(row);
            treetableSelectionModel.setSelectionInterval(row, row);
          } finally {
            inhibitSelectionPropToRawTable = false;
          }
        }
      }
    });

    // Ensure that the raw table's selection display is in sync with the
    // treetable's selection, projecting from cluster nodes onto the tree
    // leaves.
    treetableSelectionModel.addListSelectionListener(new ListSelectionListener() {
      Override
      public void valueChanged(ListSelectionEvent event) {
        if (inhibitSelectionPropToRawTable)
          return;
        if (treetableSelectionModel.isSelectionEmpty())
          tableSelectionModel.clearSelection();
        else {
          int selectedRow = treetableSelectionModel.getMinSelectionIndex();
          TreePath selectedObjectPath = trees.treetable.getTree().getPathForRow(selectedRow);
          assert (selectedObjectPath != null);
          try {
            inhibitSelectionPropToTreeTable = true;
            tableSelectionModel.clearSelection();
            // Recurse down to the tree leaves below selectedObjectPath.
            selectLeavesInTable(selectedObjectPath);
          } finally {
            inhibitSelectionPropToTreeTable = false;
          }
          // Now ensure at least the beginning of the selection is visible.
          int viewHeight = rawTableView.getViewport().getHeight();
          // = rawTableView.getViewport().getExtentSize().height;
          int minIndex = tableSelectionModel.getMinSelectionIndex();
          int maxIndex = tableSelectionModel.getMaxSelectionIndex();
          Rectangle topRect = rawTable.getCellRect(minIndex, 0, true);
          Rectangle bottomRect = rawTable.getCellRect(maxIndex, 0, true);
          int combinedHeight = bottomRect.y + bottomRect.height - topRect.y;
          topRect.height = Math.min(combinedHeight, viewHeight);
          rawTable.scrollRectToVisible(topRect);
        }
      }

      private void selectLeavesInTable(TreePath path) {
        Object object = path.getLastPathComponent();
        if (object instanceof IlvClusterNode) {
          // A tree node with children.
          for (Iterator<?> it = dataSource.getTreeModel().getChildren(object).iterator(); it.hasNext();) {
            Object child = it.next();
            selectLeavesInTable(path.pathByAddingChild(child));
          }
        } else {
          // A leaf.
          assert (dataSource.getTreeModel().getChildren(object).isEmpty());
          int i = dataSource.getFlatModel().getObjects().indexOf(object);
          tableSelectionModel.addSelectionInterval(i, i);
        }
      }
    });
  }

  /**
   * Updates the data source and with it also the treemap and tree-table when a
   * particular partitioning has been chosen.
   */
  private void updatePartitioners() {
    TableColumnModel columnsTableColumns = columnsTable.getColumnModel();
    IlvFlatTableModel model = dataSource.getFlatTableModel();
    // Take only the columns before the black separator block.
    int separator;
    for (separator = 0; separator < columnsTableColumns.getColumnCount(); separator++)
      if ("".equals(columnsTableColumns.getColumn(separator).getIdentifier()))
        break;
    separatorIndex = separator;
    int count = separator;
    IlvPartitionerFactory<?>[] newPartitionerFactories = new IlvPartitionerFactory[count];
    for (int col = 0; col < count; col++) {
      String columnName = (String) columnsTableColumns.getColumn(col).getIdentifier();
      IlvDataColumnInfo column = IlvColumnUtilities.getColumnByName(model, columnName);
      // Set the corresponding partitioner.
      newPartitionerFactories[col] = (Number.class.isAssignableFrom(column.getType())
          ? new IlvUniformScalePartitionerFactory(column, 4, 5) : new IlvStringPartitionerFactory(column));
    }
    // No need to disturb the data source if nothing has changed.
    if (!Arrays.equals(dataSource.getPartitionerFactories(), newPartitionerFactories)) {
      dataSource.setPartitionerFactories(newPartitionerFactories);
      StringBuffer headerLabel = new StringBuffer();
      if (count == 0) {
        headerLabel.append("No partitioning");
      } else {
        headerLabel.append("By ");
        for (int col = 0; col < count; col++) {
          if (col > 0)
            headerLabel.append("/");
          String columnName = (String) columnsTableColumns.getColumn(col).getIdentifier();
          headerLabel.append(columnName);
        }
      }
      trees.treeColumn.setHeaderValue(headerLabel.toString());
      trees.treetable.getTableHeader().repaint();
      // Clear the history, since it likely contains focus choices that are
      // assume the previous partitioning, not this one.
      trees.treemapPanel.clearHistory();
    }
  }

  /**
   * Updates the treemap when a particular area column has been chosen.
   */
  private void updateAreaColumn() {
    String columnName = (String) areaColumnChooser.getSelectedItem();
    IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) trees.treemapPanel.getChart().getRenderer(0);
    if (columnName != null && IlvColumnUtilities.getColumnByName(dataSource.getTreeModel(), columnName) != null)
      renderer.setAreaColumnName(columnName);
    else
      renderer.setAreaColumnName(null);
  }

  /**
   * Updates the treemap when a particular color column has been chosen.
   */
  private void updateColorColumn() {
    String columnName = (String) colorColumnChooser.getSelectedItem();
    IlvChart chart = trees.treemapPanel.getChart();
    IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) chart.getRenderer(0);
    // Reset the LegendLabelFormat, since the previous LegendLabelFormat
    // may be a DecimalFormat and therefore not suitable for non-numeric
    // columns.
    renderer.setLegendLabelFormat(null);
    // Set the color column and color scheme.
    // Also make a legend appear when suitable.
    IlvDataColumnInfo column;
    if (columnName != null
        && (column = IlvColumnUtilities.getColumnByName(dataSource.getTreeModel(), columnName)) != null) {
      renderer.setColorColumnName(columnName);
      if (column.getType() == String.class)
        // For non-numeric columns, use "qualitative" colors.
        renderer.setColorScheme(IlvTreemapChartRenderer.COLORSCHEME_QUALITATIVE);
      else if (column.getName().equals("Profits"))
        // For numeric columns with positive and negative values, use
        // red for negative and green for positive values. Take care
        // that frontier between red and green is exactly at 0.
        renderer.setColorScheme(
            IlvColorScheme.createAdjustedColorScheme(IlvTreemapChartRenderer.COLORSCHEME_DIVERGING_RED_GREEN, 0.0));
      else
        // For other numeric columns, use red for low and green for
        // high values.
        renderer.setColorScheme(
            IlvColorScheme.createAdjustedColorScheme(IlvTreemapChartRenderer.COLORSCHEME_DIVERGING_RED_GREEN));
      if (Number.class.isAssignableFrom(column.getType())) {
        // Ensure there is a legend.
        renderer.setLegendLabelFormat(new DecimalFormat("#.##"));
        chart.setLegendPosition(IlvChartLayout.EAST);
        chart.setLegendVisible(true);
      } else {
        // Ensure there is no legend.
        chart.setLegendVisible(false);
      }
    } else {
      renderer.setColorColumnName(null);
      renderer.setColorScheme(IlvTreemapChartRenderer.COLORSCHEME_DEPTH);
      // Ensure there is no legend.
      chart.setLegendVisible(false);
    }
  }

  /**
   * Updates the treemap when a particular labeling mode has been chosen.
   */
  private void updateLabeling() {
    String labeling = (String) labelingChooser.getSelectedItem();
    IlvChart chart = trees.treemapPanel.getChart();
    IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) chart.getRenderer(0);
    if (labeling == "off")
      renderer.setAnnotationVisibility(IlvTreemapChartRenderer.VISIBILITY_NONE);
    else if (labeling == "smart") {
      renderer.setAnnotationVisibility(IlvTreemapChartRenderer.VISIBILITY_SMART);
      renderer.setClippedAnnotationVisibility(false);
    } else if (labeling == "1 level") {
      renderer.setAnnotationVisibility(renderer.getFocusLevel() + 1);
      renderer.setClippedAnnotationVisibility(true);
    } else if (labeling == "2 levels") {
      renderer.setAnnotationVisibility(renderer.getFocusLevel() + 2);
      renderer.setClippedAnnotationVisibility(true);
    } else if (labeling == "all") {
      renderer.setAnnotationVisibility(Integer.MAX_VALUE);
      renderer.setClippedAnnotationVisibility(true);
    }
  }

  /**
   * Computes and returns the longest common initial segment of two paths
   * starting at the same root.
   */
  private static TreePath computeCommonAncestor(TreePath path1, TreePath path2) {
    int len1 = path1.getPathCount();
    int len2 = path2.getPathCount();
    if (len1 > len2)
      do
        path1 = path1.getParentPath();
      while (--len1 > len2);
    else if (len2 > len1)
      do
        path2 = path2.getParentPath();
      while (--len2 > len1);
    assert (path1.getPathCount() == path2.getPathCount());
    while (path1.getLastPathComponent() != path2.getLastPathComponent()) {
      path1 = path1.getParentPath();
      path2 = path2.getParentPath();
    }
    assert (path1 != null && path1.equals(path2));
    return path1;
  }

  /**
   * Opens an URL for read-only access. Like url.openStream(), except that it
   * also works for Windows UNC pathnames. See Sun BR #4671171.
   */
  private static InputStream openStream(URL url) throws IOException {
    if ("file".equals(url.getProtocol()) && !"".equals(url.getHost()))
      // Do it directly, avoiding a "Connection refused: connect" error.
      return new BufferedInputStream(new FileInputStream("//" + url.getHost() + url.getPath()));
    else {
      // But note that URLs of the form "jar:file://HOST/...!..." work
      // and don't need special treatment.
      return url.openStream();
    }
  }

  /**
   * Starts the demo, when run as an application.
   */
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        JFrame frame = new JFrame("Treemap sample");
        TreemapDemo demo = new TreemapDemo();
        demo.init(frame.getContentPane());
        demo.setFrameGeometry(1000, 900, true);
        frame.setVisible(true);
        demo.treesPanel.setDividerLocation(treesPanelDefaultDivision);
        demo.wholePanel.setDividerLocation(wholePanelDefaultDivision);
      }
    });
  }
}