public void updateColorAttr(String colorAttrName) {
   // pull applicable values from ML module
   List<String> colorCategories =
       MainController.getMainController().getValuesForAttribute(colorAttrName);
   // update colorAction
   this.colorAttrName = colorAttrName;
   docColorAction.updateColorAttr(colorAttrName, colorCategories);
   //        m_vis.run("repaint");  // (don't yet run update actions, since the Controller may be
   // modifying multiple axes; let controller call updateView() itself once it's done updating the
   // attributes of the view)
 }
  public void updateYAxis(String yAxisAttrName) {
    // pull applicable values from ML module
    List<String> yAxisCategories =
        MainController.getMainController().getValuesForAttribute(yAxisAttrName);

    documentGridLayout.updateYAxis(yAxisAttrName, yAxisCategories);
    // update the visual axis indicator as well
    docGridAxisLayout.docGridLayoutUpdated();
    //        m_vis.run("repaint");  // (don't yet run update actions, since the Controller may be
    // modifying multiple axes; let controller call updateView() itself once it's done updating the
    // attributes of the view)
  }
 @Override
 public void itemPressed(VisualItem item, MouseEvent e) {
   // on right-mouse press, start dragging the document
   if (SwingUtilities.isRightMouseButton(e)) {
     // debug
     System.out.println("debug: " + this.getClass().getName() + ": item pressed w/ right mouse");
     // drag, set the focus to the current node
     Visualization vis = item.getVisualization();
     vis.getFocusGroup(Visualization.FOCUS_ITEMS).setTuple(item);
     item.setFixed(true);
     dragged = false;
     Display d = controller.getDocumentGrid();
     down = d.getAbsoluteCoordinate(e.getPoint(), down);
   }
 }
    @Override
    public void itemDragged(VisualItem item, MouseEvent e) {
      if (!SwingUtilities.isRightMouseButton(e)) {
        return;
      }
      if (item.getGroup().equals(m_group)) {
        dragged = true;
        //            Display d = (Display) e.getComponent();
        Display d = controller.getDocumentGrid();
        d.getAbsoluteCoordinate(e.getPoint(), temp);
        double dx = temp.getX() - down.getX();
        double dy = temp.getY() - down.getY();
        double x = item.getX();
        double y = item.getY();
        double w = item.getDouble(WIDTH);
        double h = item.getDouble(HEIGHT);

        item.setStartX(x);
        item.setStartY(y);
        item.setX(x + dx);
        item.setY(y + dy);
        item.setEndX(x + dx);
        item.setEndY(y + dy);

        item.setBounds(x + dx, y + dy, w, h);

        if (repaint) {
          item.getVisualization().repaint();
        }

        down.setLocation(temp);
        if (action != null) {
          d.getVisualization().run(action);
        }
      }
    }
    @Override
    public void itemReleased(VisualItem item, MouseEvent e) {
      // when right-mouse released, release the dragged document glyph
      if (!SwingUtilities.isRightMouseButton(e)) {
        return;
      }
      // debug
      System.out.println("debug: " + this.getClass().getName() + ": item released");
      if (dragged) {
        activeItem = null;
        item.setFixed(wasFixed);
        dragged = false;
      }
      // clear the focus
      Visualization vis = item.getVisualization();
      vis.getFocusGroup(Visualization.FOCUS_ITEMS).clear();

      // determine whether item is in same region or new region;
      //  if new region, call controller to update attr vals
      double x = item.getX();
      double y = item.getY();
      double w = item.getDouble(WIDTH);
      double h = item.getDouble(HEIGHT);
      int newRegionX = -1;
      int newRegionY = -1;
      String xAttrName = docGridLayout.getXAttr();
      String yAttrName = docGridLayout.getYAttr();
      List<String> xCats = docGridLayout.getXCats();
      List<String> yCats = docGridLayout.getYCats();
      List<Integer> xCatRegionSizes = docGridLayout.getXCatRegionSizes();
      List<Integer> yCatRegionSizes = docGridLayout.getYCatRegionSizes();
      List<Integer> xCatPositions = docGridLayout.getXCatPositions();
      List<Integer> yCatPositions = docGridLayout.getYCatPositions();
      // for each region, get start and range;
      for (int i = 0; i < xCats.size(); i++) {
        int xRegionStart = xCatPositions.get(i);
        int xRegionEnd = xRegionStart + xCatRegionSizes.get(i);
        if (xRegionStart < x + (w / 2.) && x + (w / 2.) < xRegionEnd) {
          newRegionX = i;
        }
      }
      for (int i = 0; i < yCats.size(); i++) {
        int yRegionStart = yCatPositions.get(i);
        int yRegionEnd = yRegionStart + yCatRegionSizes.get(i);
        if (yRegionStart < y + (h / 2.) && y + (h / 2.) < yRegionEnd) {
          newRegionY = i;
        }
      }

      int docID = item.getInt(DocumentGridTable.NODE_ID);

      // debug
      //            System.out.println("debug: item moved:
      // docID="+docID+"xOrig="+xCats.get(origRegionX)+", xNew="+xCats.get(newRegionX)+",
      // yOrig="+yCats.get(origRegionY)+", yNew="+yCats.get(newRegionY));

      // update for x and y separately
      //            if (origRegionX != newRegionX && newRegionX != -1) {
      String newCatX = xCats.get(newRegionX);
      controller.updateDocumentAttr(docID, xAttrName, newCatX);
      controller.documentAttributesUpdated(docID);
      //            }
      //            if (origRegionY != newRegionY && newRegionY != -1) {
      String newCatY = yCats.get(newRegionY);
      controller.updateDocumentAttr(docID, yAttrName, newCatY);
      controller.documentAttributesUpdated(docID);
      //            }

    }
  public DocumentGrid(
      final DocumentGridTable t, String xAxisInitName, String yAxisInitName, String colorInitName) {
    super(new Visualization());
    display = this;
    this.t = t;
    this.controller = MainController.getMainController();
    // add data to visualization (tables, ...)
    VisualTable vt = m_vis.addTable(DATA_GROUP, t);
    colorAttrName = colorInitName;

    // init actionlist: performs initial positioning of the glyphs, axes
    ActionList init = new ActionList();
    // add document layout action
    // rather than taking only the values which are present, let's pull the valid values directly
    // from the ML module, since some applicable values might not be present within the current
    // documents, but we will need these regions to be present in the visualization for dragging?
    //        List<String> xAxisInitCategories = t.getValueListForAttribute(xAxisInitName);
    List<String> xAxisInitCategories = controller.getValuesForAttribute(xAxisInitName);
    //        List<String> yAxisInitCategories = t.getValueListForAttribute(yAxisInitName);
    List<String> yAxisInitCategories = controller.getValuesForAttribute(yAxisInitName);
    documentGridLayout =
        new DocumentGridLayoutNested(
            DATA_GROUP, xAxisInitName, yAxisInitName, xAxisInitCategories, yAxisInitCategories);
    init.add(documentGridLayout);
    // add axes layout action
    docGridAxisLayout = new DocumentGridAxisLayout(ALL_LABEL, documentGridLayout);
    documentGridLayout.setAxisLayout(docGridAxisLayout);
    //        init.add(docGridAxisLayout);  // because of race conditions (DGAL requiring info from
    // DGL which it doesn't have until its layout proceedure has finished), DGAL is now called
    // directly by DGL, no longer as a separate member of the actionlist
    // add init actionlist to vis
    m_vis.putAction("init", init);

    // set up renderer for nodes, set rendererFactory
    nodeRenderer = new DocGlyphRenderer();
    // perform additional optional renderer setup here
    // add primary renderer to visualization
    DefaultRendererFactory rf = new DefaultRendererFactory();
    rf.setDefaultRenderer(nodeRenderer);
    // add auxiliary renderer for axes
    rf.add(new InGroupPredicate(ALL_LABEL), new DocumentGridAxisRenderer(documentGridLayout));
    m_vis.setRendererFactory(rf);

    // ActionList for simple repaint (for simple controls)
    ActionList repaint = new ActionList();
    repaint.add(new RepaintAction());
    m_vis.putAction("repaint", repaint);

    // update actionlist: performs coloration, sizing
    ActionList updateOnce = new ActionList();
    // size action
    // note: sizing is now controlled by the layout action
    //        SizeAction sizeActionUpdate = new DocGlyphSizeAction(DATA_GROUP);
    //        updateOnce.add(sizeActionUpdate);
    // shape action
    // note: now using a constant square shape
    ShapeAction squareShapeAction = new SquareShapeAction();
    updateOnce.add(squareShapeAction);
    // color action(s)
    List<String> colorInitCategories = t.getValueListForAttribute(colorInitName);
    docColorAction = new DocumentColorAction(DATA_GROUP, colorInitName, colorInitCategories);
    updateOnce.add(docColorAction);
    docBorderColorAction = new DocumentBorderColorAction(DATA_GROUP);
    updateOnce.add(docBorderColorAction);
    // visibility filter
    docGlyphVisiblePredicate = new InGroupPredicate(DATA_GROUP);
    docGlyphVisibleFilter = new GlyphVisibilityFilter(DATA_GROUP, docGlyphVisiblePredicate);
    updateOnce.add(docGlyphVisibleFilter);
    // repaint action
    updateOnce.add(new RepaintAction());
    // add update actionlist to vis
    m_vis.putAction("updateOnce", updateOnce);

    // TODO : enable proper animation
    //        ActionList animate = new ActionList(1250);
    //        animate.setPacingFunction(new SlowInSlowOutPacer());
    //        animate.add(new LocationAnimator(DATA_GROUP));
    //        animate.add(new SizeAction(DATA_GROUP));
    //        animate.add(new RepaintAction());
    //        m_vis.putAction("animate", animate);

    // get reference to glasspane
    glassPane = controller.getGlassPane();

    // set initial / basic properties of the display
    setSize(700, 600);
    setBackground(Color.LIGHT_GRAY);
    setBorder(BorderFactory.createEmptyBorder(30, 20, 5, 20));

    // for doc highlighting on search (partially adapted from TreeMap.java in Prefuse demo gallery)
    searchQ = new SearchQueryBinding(t, DocumentGridTable.NODE_TEXT);
    m_vis.addFocusGroup(Visualization.SEARCH_ITEMS, searchQ.getSearchSet());
    searchQ
        .getPredicate()
        .addExpressionListener(
            new UpdateListener() {
              @Override
              public void update(Object src) {
                //                // debug
                //                System.out.println("\n\ndebug: "+this.getClass().getName()+": in
                // SEARCH_ITEMS group: ");
                // update focus text for all items in visualtable, wrt. query
                // TODO : improve efficiency of this predicate updating method
                String queryStr = searchQ.getSearchSet().getQuery();
                Scanner querySplitter = new Scanner(queryStr);
                List<String> terms = new ArrayList<>();
                while (querySplitter.hasNext()) {
                  String term = querySplitter.next();
                  terms.add(term.trim());
                }

                // debug
                //                System.out.println("debug: "+this.getClass().getName()+": query
                // string: "+queryStr);
                int numRows = t.getRowCount();
                for (int i = 0; i < numRows; i++) {
                  t.setString(i, DocumentGridTable.NODE_FOCUS_TEXT, "");

                  String text = t.getString(i, DocumentGridTable.NODE_TEXT).toLowerCase();
                  boolean containsAllTerms = true;
                  for (String term : terms) {
                    if (!text.contains(term)) {
                      containsAllTerms = false;
                      break;
                    }
                  }
                  //                    if (!containsAllTerms) {
                  // TODO properly remove non-matching items from search set?
                  //                        searchQ.getSearchSet().removeTuple(t.getTuple(i));  //
                  // throws UnsupportedOperationException
                  //                    } else {
                  // set highlight text
                  // do very coarse "sentence-splitting"
                  // TODO : proper sentence parsing
                  List<String> focusSents = new ArrayList<>();
                  Scanner sentSplitter = new Scanner(text);
                  //                        sentSplitter.useDelimiter("[\\.\n]");  // split on
                  // period or newline
                  sentSplitter.useDelimiter("[\\.]"); // split on period only
                  while (sentSplitter.hasNext()) {
                    String sent = sentSplitter.next();
                    for (String term : terms) {
                      if (sent.contains(term)) {
                        focusSents.add(sent);
                      }
                    }
                  }
                  if (focusSents.size() > 0) {
                    StringBuilder focusText = new StringBuilder();
                    focusText.append(FOCUS_SENT_SPLITTER);
                    for (String focusSent : focusSents) {
                      focusText.append(focusSent);
                      focusText.append(FOCUS_SENT_SPLITTER + "\n");
                    }
                    t.setString(i, DocumentGridTable.NODE_FOCUS_TEXT, focusText.toString());
                  }
                  //                    }
                }

                // run repaint actions
                m_vis.run("updateOnce");
              }
            });

    // set up control listeners
    // zoom with wheel
    //        addControlListener(new WheelZoomControl());
    // zoom with background right-drag
    //        addControlListener(new ZoomControl(Control.RIGHT_MOUSE_BUTTON));
    // pan with background left-drag
    //        addControlListener(new PanControl(Control.RIGHT_MOUSE_BUTTON));
    // drag control for moving items to new cells
    docDragControl = new DocGridDragControl(DATA_GROUP, documentGridLayout, controller);
    addControlListener(docDragControl);
    // control for loading document details in glasspane
    docSelectControl = new DocumentSelectControl(glassPane);
    addControlListener(docSelectControl);

    // run actionlists

    m_vis.alwaysRunAfter("init", "updateOnce");

    m_vis.run("init");
  }