Saturday, April 24, 2010

Adding better Maptips to the ESRI Silverlight API Clusterer!

At work we have been having this issue.  We are building a Silverligh App with ESRI's API and we have several features that due to being in the same shopping complex stack on top of each other.  However, if using  ESRI's out of the box clusterer (flare or simple) the Maptip does not contain any information about the individual features that are in the cluster.  Originally, we put a custom symbol on each flare of the flare clusterer, but due to issues with how the objets are programmed we could not access the correct information to get a tooltip service to work.  However, since Friday I have been working to solve this issue for my specific project.  I noticed today while working on a custom maptip from the toolkit I decided to implement a custom graphics clusterer.  With a little assistance from a co-worker (Tater), I decided to take this approach, the code below is the override for the OnCreateGraphics method of the custom clusterer.  To get access to all the attributes of each graphic you need to add an attribute to the cluster graphic that you will then assign the list of graphics too:

protected override Graphic OnCreateGraphic(GraphicCollection cluster, MapPoint point, int maxClusterCount) 
{
  if (cluster.Count == 1)
  {
    return cluster[0]; 
  }
  Graphic graphic = null; 
  double sum = 0; 
  double size = (sum + 450) / 30; 
  size = (Math.Log(sum * SymbolScale / 10) * 10 + 20); 
  if (size < 12)
  {
    size = 12;
  } 
  graphic = new Graphic() { Symbol = new SimpleMarkerSymbol() { Size = 15 }, Geometry = point }; 
  graphic.Attributes.Add("Color", InterpolateColor(size - 12, 100)); 

  //Create a list to hold your graphics
  List GraphicsList = new List(); 
  
  foreach (Graphic g in cluster) 
  {
    //Loop through all the graphics in the cluster and add to your list
    GraphicsList.Add(g);    
  }
  //Now create a new attribute to hold your graphics list
  graphic.Attributes.Add("Graphics", GraphicsList); 
  
  return graphic; 
}


The second step after creating a List of graphics to pass to the maptip, the next problem arises.  After trying to bind to the datagrid with no luck.  I started thinking how does ESRI bind the collection of graphics to the Feature Data Grid in the toolkit.  After looking around ESRI makes an IEnumerable of Attribute Dictionaries, then use an extension method to then convert those dictionaries in to something the datagrid will bind to, the example is below you will need to use the CreateDataSource.cs file for the extension methods:
private void graphicsLayer_MouseEnter(object sender, Graphic graphic, MouseEventArgs args) 
{
  mouseIsOver = true; 
  if (currentFeature != graphic) //Mouse entered a new feature 
  {
    this.expanded = true; 
    currentFeature = graphic;
    Point p = args.GetPosition(null); 
    SetValue(Canvas.LeftProperty, p.X + HorizontalOffset); 
    SetValue(Canvas.TopProperty, p.Y + (VerticalOffset - 1000));
    
    //Check to see if a sinlge maptip or cluster was passed to the maptip 
    if (graphic.Attributes.ContainsKey("Graphics")) 
    {
      //Pull out your list of graphics
      List graphicsList = graphic.Attributes["Graphics"] as List; 
      
      //Select the attribute of each graphic and place in a list of dictionaries
      IEnumerable> dictList = (from a in graphicsList select a.Attributes).AsEnumerable>(); 
      
      //Use the ToDataSource extension method from the FeatureDataGrid in the ESRI Silverlight toolkit
      //and set that to your ItemsSource
      this.DataContext = this.ItemsSource = dictList.ToDataSource(out objectType); 
    }
    else 
    {
      this.DataContext = this.ItemsSource = graphic.Attributes; 
    }
    if (!string.IsNullOrEmpty(TitleMember)) 
    {
      object title = null; 
      if (graphic.Attributes.ContainsKey(TitleMember))
      { 
        title = string.Format("{0}", graphic.Attributes[TitleMember]);
      } 
      else 
      {
        string firstKey = null; 
        foreach (string key in graphic.Attributes.Keys) 
        {
          if (firstKey == null) firstKey = key; 
          if (graphic.Attributes[key].GetType() == typeof(string)) 
          {
            title = graphic.Attributes[key] as string; 
            break; 
          }
      }
      if (title == null && !string.IsNullOrEmpty(firstKey))
      { 
        title = string.Format("{0}", graphic.Attributes[firstKey]);
      } 
    }
    this.Title = title; 
  }
  ChangeVisualState(false); 
  Visibility = Visibility.Collapsed; 
  }
  if (Visibility == Visibility.Collapsed) 
  {
    timer.Start(); 
    //Delay showing maptip 
  }
}

1 comment:

  1. Hi Jamie,

    You also mentioned that you did create custom graphics for the elements of the flare cluster.

    Is this something that is available to have a look at?

    We are looking to achieve exactly the same behavior.

    Sincerely
    Dan

    ReplyDelete