arcgissamples\cartography\FilledLeaderCallout.java—ArcObjects 10.4 Help for Java | ArcGIS for Desktop
Filled leader callout
arcgissamples\cartography\FilledLeaderCallout.java
/* Copyright 2015 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 at <your ArcGIS install location>/DeveloperKit10.4/userestrictions.txt.
* 
*/
package arcgissamples.cartography;

import java.io.IOException;

import com.esri.arcgis.display.DisplayTransformation;
import com.esri.arcgis.display.ICallout;
import com.esri.arcgis.display.IDisplayName;
import com.esri.arcgis.display.IDisplayTransformation;
import com.esri.arcgis.display.IFillSymbol;
import com.esri.arcgis.display.IQueryGeometry;
import com.esri.arcgis.display.ISymbol;
import com.esri.arcgis.display.ITextBackground;
import com.esri.arcgis.display.ITextBackground2;
import com.esri.arcgis.display.ITextSymbol;
import com.esri.arcgis.display.RgbColor;
import com.esri.arcgis.display.SimpleFillSymbol;
import com.esri.arcgis.display.SimpleLineSymbol;
import com.esri.arcgis.display.esriSimpleFillStyle;
import com.esri.arcgis.geometry.Envelope;
import com.esri.arcgis.geometry.IEnvelope;
import com.esri.arcgis.geometry.IGeometry;
import com.esri.arcgis.geometry.IPoint;
import com.esri.arcgis.geometry.IPolygon;
import com.esri.arcgis.geometry.ISegmentCollection;
import com.esri.arcgis.geometry.ITransformation;
import com.esri.arcgis.geometry.Line;
import com.esri.arcgis.geometry.Point;
import com.esri.arcgis.geometry.Polygon;
import com.esri.arcgis.interop.AutomationException;
import com.esri.arcgis.system.IClone;

/**
 * This sample shows how you can extend ArcObjects to build a new custom textbackground.
 * ArcMap ships with several textbackgrounds including: Balloon Callout, Line Callout,
 * Marker Text Background, and Simple Line Callout.
 * This new textbackground is a Filled Leader Callout and it is very similar to the Balloon
 * callout except, like its name suggests, the leader can be filled with a symbol
 * that differs from the balloon.
 */
public class FilledLeaderCallout implements IClone, ICallout, ITextBackground, IQueryGeometry, IDisplayName {

  private static final long serialVersionUID = 1L;
  double right;
  double left;
  double top;
  double bottom;
  IFillSymbol leaderSym;
  IFillSymbol backSym;
  double leaderTolerance;
  Envelope textBox;
  IPoint anchorPoint;
  IGeometry geometry;
  Envelope callOutBox;
  Polygon callOutPoly;
  IPolygon toundRect;
  double ldrDist;

  /**
   * Default Constructor
   */
  FilledLeaderCallout() {
    try {
      this.top = 5;
      this.bottom = 5;
      this.left = 5;
      this.right = 5;
      this.leaderTolerance = 15;
      //
      SimpleLineSymbol outLine = new SimpleLineSymbol();
      outLine.setWidth(0.4);
      // default Leader Symbol
      SimpleFillSymbol leaderSymbol = new SimpleFillSymbol();
      RgbColor leaderColor = new RgbColor();
      leaderColor.setRGB(0x000000FF);
      leaderSymbol.setColor(leaderColor);
      leaderSymbol.setStyle(esriSimpleFillStyle.esriSFSSolid);
      leaderSymbol.setOutline(outLine);
      this.backSym = leaderSymbol;
      // default Back Symbol
      SimpleFillSymbol backSymbol = new SimpleFillSymbol();
      RgbColor backColor = new RgbColor();
      backColor.setRGB(0x00FF0000);
      backSymbol.setColor(backColor);
      backSymbol.setStyle(esriSimpleFillStyle.esriSFSSolid);
      backSymbol.setOutline(outLine);
      this.leaderSym = backSymbol;
    } catch (java.lang.Exception e) {
      e.printStackTrace(); // never happened
    }
  }

  // IClone interface

  /**
   * @see IClone#esri_clone
   * @return IClone
   */
  public IClone esri_clone() {
    FilledLeaderCallout filledLeaderCallout = new FilledLeaderCallout();
    filledLeaderCallout.bottom = this.bottom;
    filledLeaderCallout.left = this.left;
    filledLeaderCallout.right = this.right;
    filledLeaderCallout.top = this.top;
    filledLeaderCallout.backSym = this.backSym;
    filledLeaderCallout.leaderSym = this.leaderSym;
    filledLeaderCallout.anchorPoint = this.anchorPoint;
    filledLeaderCallout.leaderTolerance = this.leaderTolerance;
    return filledLeaderCallout;
  }

  /**
   * @see IClone#assign
   * @param clone
   */
  public void assign(IClone clone) {
    // no implemented
  }

  /**
   * @see IClone#isEqual
   * @param clone
   * @return boolean
   */
  public boolean isEqual(IClone clone) {
    return false;
  }

  /**
   * @see IClone#isIdentical
   * @param clone
   * @return boolean
   */
  public boolean isIdentical(IClone clone) {
    return false;
  }

  // ICallout interface

  /**
   * @see ICallout#getAnchorPoint
   * @return IPoint
   */
  public IPoint getAnchorPoint() {
    return this.anchorPoint;
  }

  /**
   * @see ICallout#setAnchorPoint
   * @param point
   */
  public void setAnchorPoint(IPoint point) {
    this.anchorPoint = point;
  }

  /**
   * @see ICallout#getLeaderTolerance
   * @return double
   */
  public double getLeaderTolerance() {
    return this.leaderTolerance;
  }

  /**
   * @see ICallout#setLeaderTolerance
   * @param v
   */
  public void setLeaderTolerance(double v) {
    this.leaderTolerance = v;
  }

  // ITextBackground interface

  /**
   * @see ITextBackground#getTextSymbol
   * @return ITextSymbol
   */
  public ITextSymbol getTextSymbol() {
    return null; // no implemented
  }

  /**
   * @see ITextBackground#setTextSymbolByRef
   * @param textSymbol
   */
  public void setTextSymbolByRef(ITextSymbol textSymbol) {
    // no implemented
  }

  /**
   * @see ITextBackground#setTextBoxByRef
   * @param envelope
   * @throws IOException
   * @throws AutomationException
   */
  
  @SuppressWarnings("deprecation")
  public void setTextBoxByRef(IEnvelope envelope) throws IOException, AutomationException {
    this.textBox = (Envelope) envelope;
    this.callOutPoly = new Polygon();
    this.callOutPoly.setRectangle(this.textBox);
    this.callOutBox = (Envelope) this.callOutPoly.getEnvelope();
  }

  /**
   * @see ITextBackground#queryBoundary
   */
  public void queryBoundary(int hDC, ITransformation transformation, IPolygon iPolygonBoundary) {
    Polygon boundary = (Polygon) iPolygonBoundary;
    try {
      IDisplayTransformation displayTransformation = (IDisplayTransformation) transformation;
      //
      double dLeft = displayTransformation.fromPoints(this.left);
      double dRight = displayTransformation.fromPoints(this.right);
      double dTop = displayTransformation.fromPoints(this.top);
      double dBottom = displayTransformation.fromPoints(this.bottom);
      //
      Point centerPt = new Point();
      centerPt.setX(this.callOutBox.getXMin() + this.callOutBox.getWidth() / 2);
      centerPt.setY(this.callOutBox.getYMin() + this.callOutBox.getHeight() / 2);
      //
      Envelope newEnv = (Envelope) this.callOutPoly.getEnvelope();
      newEnv.setXMin(newEnv.getXMin() - dLeft);
      newEnv.setXMax(newEnv.getXMax() + dRight);
      newEnv.setYMin(newEnv.getYMin() - dBottom);
      newEnv.setYMax(newEnv.getYMax() + dTop);
      //
      // This will populate Boundary with a polygon based on the envelope
      SimpleFillSymbol simpleFillSymbol = null;
      if (this.backSym instanceof SimpleFillSymbol) {
        simpleFillSymbol = (SimpleFillSymbol) this.backSym;
        simpleFillSymbol.queryBoundary(hDC, transformation,
            createCallout(newEnv, 0.125, displayTransformation).getEnvelope(), boundary);
      }
      boundary.simplify();

      Polygon leaderBound = createLeader(newEnv, transformation);
      leaderBound.simplify();
      IGeometry geom = boundary.union(leaderBound);

      // Don't want to pass back a different Boundary reference
      // Set our new geometry into the passed in Boundary reference - performance!
      boundary.assign((IClone) geom);
    } catch(IOException e) {
      // never happens
    }
  }

  /**
   * @see ITextBackground#draw
   */
  public void draw(int hDC, ITransformation transformation) throws IOException, AutomationException {
    // Create the callout
    DisplayTransformation displayTransformation = (DisplayTransformation) transformation;
    double lleft = displayTransformation.fromPoints(this.left);
    double lright = displayTransformation.fromPoints(this.right);
    double ltop = displayTransformation.fromPoints(this.top);
    double lbottom = displayTransformation.fromPoints(this.bottom);
    Point centerPt = new Point();
    centerPt.setX(this.callOutBox.getXMin() + this.callOutBox.getWidth() / 2);
    centerPt.setY(this.callOutBox.getYMin() + this.callOutBox.getHeight() / 2);
    Envelope newEnv = (Envelope) this.callOutPoly.getEnvelope();
    newEnv.setXMin(newEnv.getXMin() - lleft);
    newEnv.setXMax(newEnv.getXMax() + lright);
    newEnv.setYMin(newEnv.getYMin() - lbottom);
    newEnv.setYMax(newEnv.getYMax() + ltop);
    this.geometry = createCallout(newEnv, 0.125, displayTransformation);
    Polygon leaderPoly = createLeader(this.callOutBox, transformation);
    // Draw the leader
    ISymbol symbol2 =(ISymbol) this.leaderSym;
    symbol2.setupDC(hDC, transformation);
    symbol2.draw(leaderPoly);
    symbol2.resetDC();
    // Draw the callout
    ISymbol symbol = (ISymbol) this.backSym;
    symbol.setupDC(hDC, transformation);
    symbol.draw(this.geometry);
    symbol.resetDC();
  }


  /**
   * @see ITextBackground2#setTextBoundaryByRef
   */
  public void setTextBoundaryByRef(Polygon polygon) throws IOException, AutomationException {
    // If pTextBox Is RHS Then Exit Property 'same, do nothing
    this.textBox = (Envelope) polygon.getEnvelope();
    Polygon plgl = new Polygon();
    this.callOutPoly = plgl;
    ISegmentCollection segColl = plgl;
    segColl.setRectangle(this.textBox);
    // When they set the text box, let's create the geometry for out symbol used by Draw
    this.callOutBox = (Envelope) this.callOutPoly.getEnvelope();
  }


  /**
   * @see IQueryGeometry#getGeometry
   */
  public IGeometry getGeometry(int i, ITransformation transformation, IGeometry geom) throws IOException, AutomationException {
    return createLeader(this.callOutBox, transformation);
  }

  /**
   * @see IQueryGeometry#queryEnvelope
   */
  @SuppressWarnings("deprecation")
  public void queryEnvelope(int i, ITransformation transformation, IGeometry geom, IEnvelope envelope) {
    // no implemented
  }

  // IDisplayName interface

  /**
   * @see IDisplayName#getNameString
   */
  public String getNameString() {
    return "Filled Leader Callout";
  }

  // own public method

  /**
   * set left margin
   */
  public void setLeft(double v) {
    this.left = v;
  }

  /**
   * @return left margin as double
   */
  public double getLeft() {
    return this.left;
  }

  /**
   * set right margin
   * @param v double
   */
  public void setRight(double v) {
    this.right = v;
  }

  /**
   * return right margin
   * @return as double
   */
  public double getRight() {
    return this.right;
  }

  /**
   * set top margin
   * @param v double
   */
  public void setTop(double v) {
    this.top = v;
  }

  /**
   * return top margin
   * @return as double
   */
  public double getTop() {
    return this.top;
  }

  /**
   * set bottom margin
   * @param v double
   */
  public void setBottom(double v) {
    this.bottom = v;
  }

  /**
   * return bottom margin
   * @return as double
   */
  public double getBottom() {
    return this.bottom;
  }

  /**
   * set leader symbol
   * @param v is FillSymbol object
   */
  public void setLeaderSym(IFillSymbol v) {
    this.leaderSym = v;
  }

  /**
   * return leader sybmol
   * @return as FillSymbol object
   */
  public IFillSymbol getLeaderSym() {
    return this.leaderSym;
  }

  /**
   * set background symbol
   * @param v is FillSymbol object
   */
  public void setBackSym(IFillSymbol v) {
    this.backSym = v;
  }

  /**
   * return background symbol
   * @return as FillSymbol object
   */
  public IFillSymbol getBackSym() {
    return this.backSym;
  }

  // private methods
  /**
   * create balloon geometry
   */
  private Polygon createCallout(Envelope env, double roundRatio, IDisplayTransformation displayTransformation) throws IOException, AutomationException {
    this.ldrDist = displayTransformation.fromPoints(this.leaderTolerance);
    double offset;
    if (env.getHeight() > env.getWidth())
      offset = env.getWidth() * roundRatio;
    else
      offset = env.getHeight() * roundRatio;
    double xmin = env.getXMin();
    double ymin = env.getYMin();
    double xmax = env.getXMax();
    double ymax = env.getYMax();
    Polygon polygon = new Polygon();
    //
    Point p1 = new Point();
    p1.setX(xmin + offset);
    p1.setY(ymax);
    polygon.addPoint(p1, null, null);
    //
    Point p2 = new Point();
    p2.setX(xmax - offset);
    p2.setY(ymax);
    polygon.addPoint(p2, null, null);
    //
    Point p3 = new Point();
    p3.setX(xmax);
    p3.setY(ymax - offset);
    polygon.addPoint(p3, null, null);
    //
    Point p4 = new Point();
    p4.setX(xmax);
    p4.setY(ymin + offset);
    polygon.addPoint(p4, null, null);
    //
    Point p5 = new Point();
    p5.setX(xmax - offset);
    p5.setY(ymin);
    polygon.addPoint(p5, null, null);
    //
    Point p6 = new Point();
    p6.setX(xmin + offset);
    p6.setY(ymin);
    polygon.addPoint(p6, null, null);
    //
    Point p7 = new Point();
    p7.setX(xmin);
    p7.setY(ymin + offset);
    polygon.addPoint(p7, null, null);
    //
    Point p8 = new Point();
    p8.setX(xmin);
    p8.setY(ymax - offset);
    polygon.addPoint(p8, null, null);
    //
    Point p9 = new Point();
    p9.setX(xmin + offset);
    p9.setY(ymax);
    polygon.addPoint(p9, null, null);
    //
    return polygon;
  }

  /**
   * create leader geometry
   */
  private Polygon createLeader(Envelope env, ITransformation transformation) throws IOException, AutomationException {
    DisplayTransformation displayTransformation = (DisplayTransformation) transformation;
    if (this.anchorPoint == null || this.callOutBox == null)
      return null;

    double lleft = displayTransformation.fromPoints(this.left);
    double lright = displayTransformation.fromPoints(this.right);
    double ltop = displayTransformation.fromPoints(this.top);
    double lbottom = displayTransformation.fromPoints(this.bottom);

    env.setXMin(env.getXMin() - lleft);
    env.setXMax(env.getXMax() + lright);
    env.setYMin(env.getYMin() - lbottom);
    env.setYMax(env.getYMax() + ltop);

    Point centerPt = new Point();
    centerPt.setX(env.getXMin() + env.getWidth() / 2);
    centerPt.setY(env.getYMin() + env.getHeight() / 2);

    Line newLine = new Line();
    newLine.putCoords(centerPt, this.anchorPoint);
    double angle = newLine.getAngle() * 180.0 / Math.PI;

    // Create a new point angled from the center point
    Point p0 = new Point();
    Point p1 = new Point();
    double d = (env.getYMin() - env.getXMax()) / 8;
    if ((angle > 45 && angle < 135) || (angle > -135 && angle < -45)) {  // top || bottom
      p0.setX(centerPt.getX() - d);
      p0.setY(centerPt.getY() + 0);
      p1.setX(centerPt.getX() + d);
      p1.setY(centerPt.getY() + 0);
    } else // left || right
    {
      p0.setX(centerPt.getX() + 0);
      p0.setY(centerPt.getY() - d);
      p1.setX(centerPt.getX() + 0);
      p1.setY(centerPt.getY() + d);
    }
    Polygon polygon = new Polygon();
    polygon.addPoint(this.anchorPoint, null, null);
    polygon.addPoint(p0, null, null);
    polygon.addPoint(p1, null, null);
    return polygon;
  }
}