The Web ADF allows you to go beyond the default symbols for graphics layers by creating custom renderers. This post will show how to create custom renderers and apply them to FeatureGraphicsLayers using the IRenderer interface. The IRenderer interface allows you to take full control of how a specific feature is rendered on the map by using .NET’s System.Drawing classes.
The IRenderer interface consists of five methods, but if you can do without support for the Table of Contents (TOC) control, only one of them is really needed, and you can use some very simple implementations of the remaining four methods.
The five methods are:
- GenerateSwatches – Generates swatches for this renderer for display in a table of contents
- GetAllSymbols – Returns all FeatureSymbols used by this object, if any
- GetMaxSwatchDimensions – Gets the maximum dimensions of the swatches generated by this renderer
- Clone – Returns a clone of this renderer
- Render – Draws the specified geometry to the specified graphics
For the simplest renderer, the following is sufficient for the first four methods:
public SwatchCollection GenerateSwatches(SwatchInfo swatchInfo, string fileName, string minScale, string maxScale)
{
return new SwatchCollection();
}
public void GetAllSymbols(List< FeatureSymbol> symbols) { }
public void GetMaxSwatchDimensions(ref int width, ref int height) { }
public object Clone() { return this.MemberwiseClone(); }
This implementation will not support swatches, but we will later get back to how to also support this.
The main method we need to look at is “Render”. As input you get three parameters: The row with attributes you are rendering, a reference to the graphics instance that you should render to, and the name of the column in the datarow that contains the geometry data. The geometry data that is parsed in here is not in map units, but has already been converted to screen coordinates, so you don’t have to worry about translating your feature to the screen.
So the very first thing we do in our renderer is to fetch the geometry that we want to render. In most cases the first few lines of a renderer will look something like this:
public override void Render(DataRow row, Graphics graphics, DataColumn geometryColumn)
{
if (row == null || graphics == null || geometryColumn == null)
return;
Geometry geometry = row[geometryColumn] as Geometry;
In this first part, we will only support rendering points, so the next stop would be to limit by geometry type:
if (geometry == null || !geometry is ESRI.ArcGIS.ADF.Web.Geometry.Point)
return;
Next we can use System.Drawing classes to draw an image at the point, which completes our very simple point renderer:
ESRI.ArcGIS.ADF.Web.Geometry.Point p = geometry as ESRI.ArcGIS.ADF.Web.Geometry.Point;
using(System.Drawing.Image img = System.Drawing.Image.FromFile(@"C:inetpubwwwrootmyAppimagesicon.png"))
{
graphics.DrawImageUnscaled(img,Convert.ToInt32(p.X),Convert.ToInt32(p.Y));
img.Dispose();
}
Here’s the complete renderer implementation:
using System;
using System.Collections.Generic;
using System.Text;
using ESRI.ArcGIS.ADF.Web.Geometry;
using ESRI.ArcGIS.ADF.Web.Display.Swatch;
using ESRI.ArcGIS.ADF.Web.Display.Symbol;
namespace WebADF.Datasources.Renderers
{
public class SimplePointRenderer : ESRI.ArcGIS.ADF.Web.Display.Renderer.IRenderer
{
public SwatchCollection GenerateSwatches(SwatchInfo swatchInfo, string fileName,
string minScale, string maxScale)
{
return new SwatchCollection();
}
public void GetAllSymbols(List<FeatureSymbol> symbols) { }
public void GetMaxSwatchDimensions(ref int width, ref int height) { }
public void Render(System.Data.DataRow row, System.Drawing.Graphics graphics,
System.Data.DataColumn geometryColumn)
{
if (row == null || graphics == null || geometryColumn == null)
return;
Geometry geometry = row[geometryColumn] as Geometry;
if (geometry == null || !(geometry is ESRI.ArcGIS.ADF.Web.Geometry.Point))
return;
ESRI.ArcGIS.ADF.Web.Geometry.Point p = geometry as ESRI.ArcGIS.ADF.Web.Geometry.Point;
using (System.Drawing.Image img = System.Drawing.Image.FromFile(ImageIcon))
{
graphics.DrawImageUnscaled(img, Convert.ToInt32(p.X – img.Width/2),
Convert.ToInt32(p.Y) - img.Height/2);
img.Dispose();
}
}
public object Clone() { return this.MemberwiseClone(); }
private string imageIcon = @"C:inetpubwwwrootmyAppimagesicon.png";
public string ImageIcon
{
get { return imageIcon; }
set { imageIcon = value; }
}
}
}
If you create a graphics dataset, you can now apply this renderer to your individual FeatureGraphicsLayers. You can easily extend it to use any of the existing attributes in the DataRow to determine which image icon to use, making it a simple thematic map renderer.
Points are simple to render, but it gets slightly trickier with lines and polygons. We will have to convert these to a System.Drawing.GraphicsPath instance that the graphics object knows how to render. This is fairly straightforward, but to save you the trouble here are the methods needed to perform this conversion:
public static GraphicsPath PolygonToPath(Polygon polygon)
{
GraphicsPath path = new GraphicsPath();
foreach (Ring p in polygon.Rings)
{
path.AddPolygon(pointCollectionToPointArray(p.Points));
foreach (Hole ring in p.Holes)
path.AddPolygon(pointCollectionToPointArray(ring.Points));
}
return path;
}
public static GraphicsPath PolylineToPath(Polyline polyline)
{
GraphicsPath path = new GraphicsPath();
foreach (Path line in polyline.Paths)
{
path.AddLines(pointCollectionToPointArray(line.Points));
}
return path;
}
private static Point[] pointCollectionToPointArray(PointCollection points)
{
Point[] pointArr = new Point[points.Count];
for (int i = 0; i < points.Count; i++)
{
pointArr[i] = new Point(Convert.ToInt32(points[i].X), Convert.ToInt32(points[i].Y));
}
return pointArr;
}
This is essentially all we need to make a renderer supporting all geometry types. Example:
using (GraphicsPath path = PolygonToPath(polygon)); //or PolylinetoPath for polylines
{
using (Pen pen = new Pen(System.Drawing.Colors.Black,1))
{
graphics.DrawPath(pen, path);
pen.Dispose();
}
path.Dispose();
}
If you want to show the feature with a fill, remember to also use FillPath prior to drawing the outline as shown above:
using (SolidBrush brush = new SolidBrush(System.Drawing.Colors.Red))
{
graphics.FillPath(brush, path);
brush.Dispose();
}
Using the principles described in this article, I’ve created a 2.5D renderer that can extrude polygons and lines based on a numeric attribute. The effect is very much like what you might have seen Google Maps using to show simple building outlines in metropolitan areas. You can indeed use it for this, or you can choose to extrude polygons based on for instance population density or whatever you want to show. I won’t go through the details of the code here, but most of the code is used for building a 3D-like mesh of a polygon and figuring out how to shade it depending on the direction of an imaginary light-source.
Adding support for legends
As mentioned in the beginning, we skipped implementing support for the table of contents control, and in many use cases of custom renderers (like displaying results) this is not needed. However adding support for TOC is fairly straightforward.
We can reuse existing functionality in the ADF to render our symbols. Let’s say we have a point layer using three image symbols, 1.gif, 2.gif and 3.gif. We use the RasterMarkerSymbol and the SwatchUtility to render our swatches.
public
SwatchCollection GenerateSwatches(SwatchInfo swatchInfo, string fileName, string minScale, string maxScale) {
SwatchCollection
swatches = new SwatchCollection(); SwatchUtility
swatchUtil = new SwatchUtility(swatchInfo); for(int
i= 1;i<=3;i++) {
CartoImage img = swatchUtil.DrawNewSwatch(new RasterMarkerSymbol(
Server.MapPath(string.Format("~/images/{0}.gif", i))), null);
swatches.Add(new Swatch(img, "Marker #" + i.ToString(), null, null));
}
return swatches;
}
This will return a collection of 3 images to use for the legend. When adding a TOC to the page, you’ll see something like the following:

Get the Custom Renderer sample from the Code Gallery
Contributed by Morten Nielsen of the ArcGIS Server .NET software development team.