1 //-------------------------------------------------------------
2 // <copyright company=
\92Microsoft Corporation
\92>
3 // Copyright © Microsoft Corporation. All Rights Reserved.
5 //-------------------------------------------------------------
6 // @owner=alexgor, deliant
7 //=================================================================
10 // Namespace: DataVisualization.Charting
12 // Classes: MapArea, MapAreasCollection
14 // Purpose: Collection of MapArea classes is used to generate
15 // Chart image map, which provides functionality like
16 // tooltip, drilldown and client-side scripting.
18 // Reviewed: AG - Jul 31, 2002
19 // AG - Microsoft 14, 2007
21 //===================================================================
24 #region Used namespaces
28 using System.Collections;
29 using System.Collections.Specialized;
30 using System.ComponentModel;
31 using System.ComponentModel.Design;
33 using System.Drawing.Drawing2D;
34 using System.Drawing.Design;
35 using System.Collections.ObjectModel;
36 using System.Diagnostics.CodeAnalysis;
38 using System.Windows.Forms.DataVisualization.Charting;
39 using System.Windows.Forms.DataVisualization.Charting.Data;
40 using System.Windows.Forms.DataVisualization.Charting.ChartTypes;
41 using System.Windows.Forms.DataVisualization.Charting.Utilities;
42 using System.Windows.Forms.DataVisualization.Charting.Borders3D;
47 using System.Web.UI.DataVisualization.Charting;
48 using System.Web.UI.DataVisualization.Charting.Utilities;
49 using System.Text.RegularExpressions;
58 namespace System.Windows.Forms.DataVisualization.Charting
61 namespace System.Web.UI.DataVisualization.Charting
66 #if ! Microsoft_CONTROL
68 #region Map area shape enumeration
71 /// An enumeration of map areas shapes.
73 public enum MapAreaShape
76 /// The shape of the map area is rectangular.
81 /// The shape of the map area is circular.
86 /// The shape of the map area is polygonal.
94 #region IMapArea interface defenition
97 /// Interface which defines common properties for the map area
100 [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
101 [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
103 public interface IChartMapArea
108 /// <value>The tooltip.</value>
116 /// <value>The map area Href.</value>
117 [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")]
123 /// Map area other custom attributes
125 /// <value>The map area attributes.</value>
126 string MapAreaAttributes
132 /// Map area custom data
134 /// <value>The tag.</value>
142 /// Map area post back value.
144 /// <value>The post back value.</value>
145 string PostBackValue { get; set; }
151 /// The MapArea class represents an area of the chart with end-user
152 /// interactivity like tooltip, HREF or custom attributes.
155 DefaultProperty("ToolTip"),
156 SRDescription("DescriptionAttributeMapArea_MapArea")
159 [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
160 [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
162 public class MapArea : ChartNamedElement, IChartMapArea
165 #region Member variables
167 private string _toolTip = String.Empty;
168 private string _url = String.Empty;
169 private string _attributes = String.Empty;
170 private string _postBackValue = String.Empty;
171 private bool _isCustom = true;
172 private MapAreaShape _shape = MapAreaShape.Rectangle;
173 private float[] _coordinates = new float[4];
174 private static Regex _mapAttributesRegex;
180 /// Initializes a new instance of the <see cref="MapArea"/> class.
188 /// Initializes a new instance of the <see cref="MapArea"/> class.
190 /// <param name="url">The destination URL or anchor point of the map area.</param>
191 /// <param name="path">A GraphicsPath object that defines the shape of the map area.</param>
192 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#")]
193 public MapArea(string url, GraphicsPath path)
194 : this(String.Empty, url, String.Empty, String.Empty, path, null)
199 /// Initializes a new instance of the <see cref="MapArea"/> class.
201 /// <param name="url">The destination URL or anchor point of the map area.</param>
202 /// <param name="rect">A RectangleF structure that defines shape of the rectangular map area.</param>
203 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#")]
204 public MapArea(string url, RectangleF rect)
205 : this(String.Empty, url, String.Empty, String.Empty, rect, null)
210 /// Initializes a new instance of the <see cref="MapArea"/> class.
212 /// <param name="shape">Area shape.</param>
213 /// <param name="url">The destination URL or anchor point of the map area.</param>
214 /// <param name="coordinates">Coordinates array that determines the location of the circle, rectangle or polygon.
215 /// The type of shape that is being used determines the type of coordinates required.</param>
216 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#")]
217 public MapArea(MapAreaShape shape, string url, float[] coordinates)
218 : this(shape, String.Empty, url, String.Empty, String.Empty, coordinates, null)
223 /// Initializes a new instance of the <see cref="MapArea"/> class.
225 /// <param name="toolTip">Tool tip.</param>
226 /// <param name="url">Jump URL.</param>
227 /// <param name="attributes">Other area attributes.</param>
228 /// <param name="postBackValue">The postback value.</param>
229 /// <param name="path">Area coordinates as graphic path</param>
230 /// <param name="tag">The tag.</param>
231 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#")]
232 public MapArea(string toolTip, string url, string attributes, string postBackValue, GraphicsPath path, object tag)
235 if(path.PointCount == 0)
237 throw new ArgumentException(SR.ExceptionImageMapPolygonShapeInvalid);
240 // Flatten all curved lines
243 // Allocate array of floats
244 PointF[] pathPoints = path.PathPoints;
245 float[] coord = new float[pathPoints.Length * 2];
247 // Transfer path points
249 foreach(PointF point in pathPoints)
251 coord[index++] = point.X;
252 coord[index++] = point.Y;
256 Initialize(MapAreaShape.Polygon, toolTip, url, attributes, postBackValue, coord, tag);
260 /// Initializes a new instance of the <see cref="MapArea"/> class.
262 /// <param name="toolTip">Tool tip.</param>
263 /// <param name="url">Jump URL.</param>
264 /// <param name="attributes">Other area attributes.</param>
265 /// <param name="postBackValue">The postback value.</param>
266 /// <param name="rect">Rect coordinates</param>
267 /// <param name="tag">The tag.</param>
268 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#")]
269 public MapArea(string toolTip, string url, string attributes, string postBackValue, RectangleF rect, object tag)
272 float[] coord = new float[4];
275 coord[2] = rect.Right;
276 coord[3] = rect.Bottom;
278 Initialize(MapAreaShape.Rectangle, toolTip, url, attributes, postBackValue, coord, tag);
282 /// Initializes a new instance of the <see cref="MapArea"/> class.
284 /// <param name="shape">The shape.</param>
285 /// <param name="toolTip">The tool tip.</param>
286 /// <param name="url">The URL.</param>
287 /// <param name="attributes">The attributes.</param>
288 /// <param name="postBackValue">The postback value.</param>
289 /// <param name="coordinates">The coordinates.</param>
290 /// <param name="tag">The tag.</param>
291 [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#")]
292 public MapArea(MapAreaShape shape, string toolTip, string url, string attributes, string postBackValue, float[] coordinates, object tag)
295 Initialize(shape, toolTip, url, attributes, postBackValue, coordinates, tag);
299 private void Initialize(MapAreaShape shape, string toolTip, string url, string attributes, string postBackValue, float[] coordinates, object tag)
301 // Check number of coordinates depending on the area shape
302 if (shape == MapAreaShape.Circle && coordinates.Length != 3)
304 throw (new InvalidOperationException(SR.ExceptionImageMapCircleShapeInvalid));
306 if (shape == MapAreaShape.Rectangle && coordinates.Length != 4)
308 throw (new InvalidOperationException(SR.ExceptionImageMapRectangleShapeInvalid));
310 if (shape == MapAreaShape.Polygon && (coordinates.Length % 2f) != 0f)
312 throw (new InvalidOperationException(SR.ExceptionImageMapPolygonShapeInvalid));
315 // Create new area object
316 this._toolTip = toolTip;
318 this._attributes = attributes;
320 this._coordinates = new float[coordinates.Length];
321 this._postBackValue = postBackValue;
323 coordinates.CopyTo(this._coordinates, 0);
327 #region Map area HTML tag generation methods
330 /// Gets the name of the shape.
332 /// <returns></returns>
333 private string GetShapeName()
335 //*****************************************
337 //*****************************************
338 if (_shape == MapAreaShape.Circle)
342 else if (_shape == MapAreaShape.Rectangle)
346 else if (_shape == MapAreaShape.Polygon)
355 /// Gets the coordinates.
357 /// <param name="graph">The graph.</param>
358 /// <returns></returns>
359 private string GetCoordinates(ChartGraphics graph)
361 // Transform coordinates from relative to pixels
362 float[] transformedCoord = new float[this.Coordinates.Length];
363 if (this.Shape == MapAreaShape.Circle)
365 PointF p = graph.GetAbsolutePoint(new PointF(this.Coordinates[0], this.Coordinates[1]));
366 transformedCoord[0] = p.X;
367 transformedCoord[1] = p.Y;
368 p = graph.GetAbsolutePoint(new PointF(this.Coordinates[2], this.Coordinates[1]));
369 transformedCoord[2] = p.X;
371 else if (this.Shape == MapAreaShape.Rectangle)
373 PointF p = graph.GetAbsolutePoint(new PointF(this.Coordinates[0], this.Coordinates[1]));
374 transformedCoord[0] = p.X;
375 transformedCoord[1] = p.Y;
376 p = graph.GetAbsolutePoint(new PointF(this.Coordinates[2], this.Coordinates[3]));
377 transformedCoord[2] = p.X;
378 transformedCoord[3] = p.Y;
380 // Check if rectangle has width and height
381 if ((int)Math.Round(transformedCoord[0]) == (int)Math.Round(transformedCoord[2]))
383 transformedCoord[2] = (float)Math.Round(transformedCoord[2]) + 1;
385 if ((int)Math.Round(transformedCoord[1]) == (int)Math.Round(transformedCoord[3]))
387 transformedCoord[3] = (float)Math.Round(transformedCoord[3]) + 1;
392 PointF pConverted = Point.Empty;
393 PointF pOriginal = Point.Empty;
394 for (int index = 0; index < this.Coordinates.Length - 1; index += 2)
396 pOriginal.X = this.Coordinates[index];
397 pOriginal.Y = this.Coordinates[index + 1];
398 pConverted = graph.GetAbsolutePoint(pOriginal);
399 transformedCoord[index] = pConverted.X;
400 transformedCoord[index + 1] = pConverted.Y;
404 StringBuilder tagStringBuilder = new StringBuilder();
405 // Store transformed coordinates in the string
406 bool firstElement = true;
407 foreach (float f in transformedCoord)
411 tagStringBuilder.Append(",");
413 firstElement = false;
414 tagStringBuilder.Append((int)Math.Round(f));
417 return tagStringBuilder.ToString();
420 private static bool IsJavaScript(string value)
422 string checkValue = value.Trim().Replace("\r", String.Empty).Replace("\n", String.Empty);
423 if (checkValue.StartsWith("javascript:", StringComparison.OrdinalIgnoreCase))
432 /// Encodes the value.
434 /// <param name="chart">The chart.</param>
435 /// <param name="name">The name.</param>
436 /// <param name="value">The value.</param>
437 /// <returns></returns>
438 private static string EncodeValue(Chart chart, string name, string value)
440 if (chart.IsMapAreaAttributesEncoded)
442 if (IsJavaScript(value) ||
443 name.Trim().StartsWith("on", StringComparison.OrdinalIgnoreCase))
445 return HttpUtility.UrlEncode(value);
454 /// <param name="writer">The writer.</param>
455 /// <param name="chart">The chart.</param>
456 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification="We use lower case to generate html attributes.")]
457 internal void RenderTag(HtmlTextWriter writer, Chart chart)
459 StringBuilder excludedAttributes = new StringBuilder();
463 writer.AddAttribute(HtmlTextWriterAttribute.Shape, this.GetShapeName(), false);
464 writer.AddAttribute(HtmlTextWriterAttribute.Coords, this.GetCoordinates(chart.chartPicture.ChartGraph));
466 if (!String.IsNullOrEmpty(this.ToolTip))
468 excludedAttributes.Append("title,");
469 writer.AddAttribute(HtmlTextWriterAttribute.Title, EncodeValue(chart, "title", this.ToolTip));
472 bool postbackRendered = false;
473 if (!String.IsNullOrEmpty(this.Url))
475 excludedAttributes.Append("href,");
476 string resolvedUrl = chart.ResolveClientUrl(this.Url);
477 writer.AddAttribute(HtmlTextWriterAttribute.Href, EncodeValue(chart, "href", resolvedUrl));
479 else if (!String.IsNullOrEmpty(this.PostBackValue) && chart.Page != null)
481 postbackRendered = true;
482 excludedAttributes.Append("href,");
483 writer.AddAttribute(HtmlTextWriterAttribute.Href, chart.Page.ClientScript.GetPostBackClientHyperlink(chart, this.PostBackValue));
486 if (!postbackRendered && !String.IsNullOrEmpty(this.PostBackValue) && chart.Page != null)
488 excludedAttributes.Append("onclick,");
489 writer.AddAttribute(HtmlTextWriterAttribute.Onclick, chart.Page.ClientScript.GetPostBackEventReference(chart, this.PostBackValue));
492 if (!String.IsNullOrEmpty(this._attributes))
494 string excludedAttr = excludedAttributes.ToString();
496 // matches name1="value1" name2="value2", don't match name1="val"ue1" or name1="value1" />
497 if (_mapAttributesRegex == null)
499 _mapAttributesRegex = new Regex(@"\s?(?<name>(\w+))\s?=\s?""(?<value>[^""]+)""\s?", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
502 foreach (Match match in _mapAttributesRegex.Matches(this._attributes))
504 Group names = match.Groups["name"];
505 Group values = match.Groups["value"];
506 for (int i = 0; i < names.Captures.Count || i < values.Captures.Count; i++)
508 string name = names.Captures[i].Value.ToLowerInvariant();
509 string value = values.Captures[i].Value;
511 // skip already rendered attributes
512 if (!excludedAttr.Contains(name + ","))
515 if ("src,href,longdesc,background,".Contains(name + ",") && !IsJavaScript(value))
517 value = chart.ResolveClientUrl(value);
521 value = HttpUtility.HtmlAttributeEncode(value);
523 value = EncodeValue(chart, name, value);
524 writer.AddAttribute(name, value, false);
530 if (this._attributes.IndexOf(" alt=", StringComparison.OrdinalIgnoreCase) == -1)
532 if (!String.IsNullOrEmpty(this.ToolTip))
534 writer.AddAttribute(HtmlTextWriterAttribute.Alt, EncodeValue(chart, "title", this.ToolTip));
538 writer.AddAttribute(HtmlTextWriterAttribute.Alt, "");
542 writer.RenderBeginTag(HtmlTextWriterTag.Area);
543 writer.RenderEndTag();
548 #region MapArea Properties
551 /// Gets or sets a flag which indicates whether the map area is custom.
555 SRDescription("DescriptionAttributeMapArea_Custom"),
557 DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden),
558 SerializationVisibilityAttribute(SerializationVisibility.Hidden)
573 /// Gets or sets the coordinates of of the map area.
576 SRCategory("CategoryAttributeShape"),
578 SRDescription("DescriptionAttributeMapArea_Coordinates"),
580 #if !Microsoft_CONTROL
581 PersistenceMode(PersistenceMode.Attribute),
583 TypeConverter(typeof(MapAreaCoordinatesConverter))
585 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
586 public float[] Coordinates
594 _coordinates = value;
599 /// Gets or sets the shape of the map area.
602 SRCategory("CategoryAttributeShape"),
604 SRDescription("DescriptionAttributeMapArea_Shape"),
605 DefaultValue(typeof(MapAreaShape), "Rectangle"),
606 #if !Microsoft_CONTROL
607 PersistenceMode(PersistenceMode.Attribute)
610 public MapAreaShape Shape
623 /// Gets or sets the name of the map area.
626 SRCategory("CategoryAttributeData"),
627 SRDescription("DescriptionAttributeMapArea_Name"),
628 DefaultValue("Map Area"),
630 DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden),
631 SerializationVisibilityAttribute(SerializationVisibility.Hidden)
633 public override string Name
646 #region IMapAreaAttributesutes Properties implementation
649 /// Gets or sets the tooltip of the map area.
653 SRCategory("CategoryAttributeMapArea"),
655 SRDescription("DescriptionAttributeToolTip"),
657 #if !Microsoft_CONTROL
658 PersistenceMode(PersistenceMode.Attribute)
661 public string ToolTip
674 /// Gets or sets the URL of the map area.
677 SRCategory("CategoryAttributeMapArea"),
679 SRDescription("DescriptionAttributeUrl"),
681 #if !Microsoft_CONTROL
682 PersistenceMode(PersistenceMode.Attribute),
683 Editor(Editors.UrlValueEditor.Editor, Editors.UrlValueEditor.Base)
699 /// Gets or sets the attributes of the map area.
702 SRCategory("CategoryAttributeMapArea"),
704 SRDescription("DescriptionAttributeMapAreaAttributes"),
706 #if !Microsoft_CONTROL
707 PersistenceMode(PersistenceMode.Attribute)
710 public string MapAreaAttributes
723 /// Gets or sets the postback value which can be processed on click event.
725 /// <value>The value which is passed to click event as argument.</value>
727 [SRCategory(SR.Keys.CategoryAttributeMapArea)]
728 [SRDescription(SR.Keys.DescriptionAttributePostBackValue)]
729 public string PostBackValue
733 return this._postBackValue;
737 this._postBackValue = value;
748 /// The MapAreasCollection class is a strongly typed collection of MapAreas.
751 SRDescription("DescriptionAttributeMapAreasCollection_MapAreasCollection")
754 [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
755 [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
757 public class MapAreasCollection : ChartElementCollection<MapArea>
763 /// Public constructor.
765 public MapAreasCollection()
775 /// Insert new map area items into the collection.
777 /// <param name="index">Index to insert at.</param>
778 /// <param name="toolTip">Tool tip.</param>
779 /// <param name="url">Jump URL.</param>
780 /// <param name="attributes">Other area attributes.</param>
781 /// <param name="postBackValue">The post back value associated with this item.</param>
782 /// <param name="path">Area coordinates as graphics path.</param>
783 /// <param name="absCoordinates">Absolute coordinates in the graphics path.</param>
784 /// <param name="graph">Chart graphics object.</param>
785 internal void InsertPath(
790 string postBackValue,
796 // If there is more then one graphical path split them and create
797 // image maps for every graphical path separately.
798 GraphicsPathIterator iterator = new GraphicsPathIterator(path);
800 // There is more then one path.
801 if( iterator.SubpathCount > 1 )
803 GraphicsPath subPath = new GraphicsPath();
804 while(iterator.NextMarker(subPath) > 0)
806 InsertSubpath(index, toolTip, url, attributes, postBackValue, subPath, absCoordinates, graph);
810 // There is only one path
813 InsertSubpath(index, toolTip, url, attributes, postBackValue, path, absCoordinates, graph);
818 /// Insert new map area item into the collection.
820 /// <param name="index">Index to insert at.</param>
821 /// <param name="toolTip">Tool tip.</param>
822 /// <param name="url">Jump URL.</param>
823 /// <param name="attributes">Other area attributes.</param>
824 /// <param name="postBackValue">The post back value associated with this item.</param>
825 /// <param name="path">Area coordinates as graphics path.</param>
826 /// <param name="absCoordinates">Absolute coordinates in the graphics path.</param>
827 /// <param name="graph">Chart graphics object.</param>
828 private void InsertSubpath(
833 string postBackValue,
838 if(path.PointCount > 0)
840 // Flatten all curved lines
843 // Allocate array of floats
844 PointF[] pathPoints = path.PathPoints;
845 float[] coord = new float[pathPoints.Length * 2];
847 // Convert absolute coordinates to relative
850 for(int pointIndex = 0; pointIndex < pathPoints.Length; pointIndex++)
852 pathPoints[pointIndex] = graph.GetRelativePoint( pathPoints[pointIndex] );
856 // Transfer path points
858 foreach(PointF point in pathPoints)
860 coord[i++] = point.X;
861 coord[i++] = point.Y;
865 MapArea area = new MapArea(MapAreaShape.Polygon, toolTip, url, attributes, postBackValue, coord, null);
866 area.IsCustom = false;
867 this.Insert(index, area);
872 /// Removes all non custom map areas items from the collection.
874 internal void RemoveNonCustom()
876 for(int index = 0; index < this.Count; index++)
878 // Check the custom flag
879 if(!this[index].IsCustom)
881 this.RemoveAt(index);