using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using DevComponents.DotNetBar.Charts.Style; namespace DevComponents.DotNetBar.Charts { /// /// Represents a Crosshair element. /// [TypeConverter(typeof(BlankExpandableObjectConverter))] public class ChartCrosshair : ChartVisualElement { #region Private variables private States _States; private PointIntersectMode _PointIntersectMode = PointIntersectMode.Edge; private int _PointIntersectMargin = 2; private AxisOrientation _AxisOrientation = AxisOrientation.X; private CrosshairLabelMode _CrosshairLabelMode = CrosshairLabelMode.Common; private CrosshairVisualStyle _CrosshairVisualStyle; private EffectiveStyle _EffectiveCrosshairStyle; private CrosshairValueVisualStyle _CrosshairLabelVisualStyle; private EffectiveStyle _EffectiveCrosshairLabelStyle; #endregion public ChartCrosshair() { InitDefaultStates(); _EffectiveCrosshairStyle = new EffectiveStyle(this); _EffectiveCrosshairLabelStyle = new EffectiveStyle(this); } #region InitDefaultStates private void InitDefaultStates() { SetState(States.ShowCrosshairLabelMarkers, true); SetState(States.ShowGroupHeader, true); } #endregion #region Public properties #region AxisOrientation /// /// Gets or sets the axis orientation driving the crosshair display. /// [DefaultValue(AxisOrientation.X), Category("Appearance")] [Description("Indicates the axis orientation driving the crosshair display.")] public AxisOrientation AxisOrientation { get { return (_AxisOrientation); } set { if (value != _AxisOrientation) { _AxisOrientation = value; OnPropertyChanged("AxisOrientation"); } } } #endregion #region CrosshairLabelMode /// /// Gets or sets the mode used to display the Crosshair label. /// [DefaultValue(CrosshairLabelMode.Common), Category("Behavior")] [Description("Indicates the mode used to display the Crosshair label.")] public CrosshairLabelMode CrosshairLabelMode { get { return (_CrosshairLabelMode); } set { if (value != _CrosshairLabelMode) { _CrosshairLabelMode = value; OnPropertyChangedEx("CrosshairLabelMode", VisualChangeType.Render); } } } #endregion #region PointIntersectMode /// /// Gets or sets the the Crosshair point intersection mode. /// [DefaultValue(PointIntersectMode.Edge), Category("Behavior")] [Description("Indicates the Crosshair point intersection mode.")] public PointIntersectMode PointIntersectMode { get { return (_PointIntersectMode); } set { if (value != _PointIntersectMode) { _PointIntersectMode = value; OnPropertyChangedEx("PointIntersectMode", VisualChangeType.Render); } } } #endregion #region PointIntersectMargin /// /// Gets or sets the the Crosshair point intersection margin. /// [DefaultValue(2), Category("Layout")] [Description("Indicates the Crosshair point intersection margin.")] public int PointIntersectMargin { get { return (_PointIntersectMargin); } set { if (value != _PointIntersectMargin) { _PointIntersectMargin = value; OnPropertyChangedEx("PointIntersectMargin", VisualChangeType.Render); } } } #endregion #region CrosshairLabelVisualStyle /// /// Gets or sets the default visual style to be used for Crosshair /// values rendered on the X and Y axes. /// [Category("Style")] [Description("Indicates visual style to be used for Crosshair values rendered on the X and Y axes")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] public CrosshairValueVisualStyle CrosshairLabelVisualStyle { get { if (_CrosshairLabelVisualStyle == null) { _CrosshairLabelVisualStyle = new CrosshairValueVisualStyle(); StyleVisualChangeHandler(null, _CrosshairLabelVisualStyle); } return (_CrosshairLabelVisualStyle); } set { if (_CrosshairLabelVisualStyle != value) { CrosshairValueVisualStyle oldValue = _CrosshairLabelVisualStyle; _CrosshairLabelVisualStyle = value; OnVisualStyleChanged("CrosshairLabelVisualStyle", oldValue, value); } } } #endregion #region CrosshairVisualStyle /// /// Gets or sets the visual style for the Crosshair. /// [Category("Style")] [Description("Indicates the visual style for the Crosshair.")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] public CrosshairVisualStyle CrosshairVisualStyle { get { if (_CrosshairVisualStyle == null) { _CrosshairVisualStyle = new CrosshairVisualStyle(); StyleVisualChangeHandler(null, _CrosshairVisualStyle); } return (_CrosshairVisualStyle); } set { if (_CrosshairVisualStyle != value) { CrosshairVisualStyle oldValue = _CrosshairVisualStyle; _CrosshairVisualStyle = value; OnVisualStyleChanged("CrosshairVisualStyle", oldValue, value); if (oldValue != null) oldValue.Dispose(); } } } #endregion #region EffectiveCrosshairLabelStyle /// /// Gets a reference to the Crosshair label's Effective (cached, composite) style. /// [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public CrosshairValueVisualStyle EffectiveCrosshairLabelStyle { get { return (_EffectiveCrosshairLabelStyle.Style); } } #endregion #region EffectiveCrosshairStyle /// /// Gets a reference to the Crosshair's Effective (cached, composite) style. /// [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public CrosshairVisualStyle EffectiveCrosshairStyle { get { return (_EffectiveCrosshairStyle.Style); } } #endregion #region HighlightPoints /// /// Gets or sets whether series points are highlighted /// when the Crosshair cursor intersects with them. /// [DefaultValue(false), Category("Appearance")] [Description("Indicates whether series points are highlighted when the Crosshair cursor intersects with them.")] public bool HighlightPoints { get { return (TestState(States.HighlightPoints)); } set { if (value != HighlightPoints) { SetState(States.HighlightPoints, value); OnPropertyChangedEx("HighlightPoints", VisualChangeType.Render); } } } #endregion #region HighlightSinglePoint /// /// Gets or sets whether only single series points are highlighted /// when the Crosshair cursor intersects with them. /// [DefaultValue(false), Category("Appearance")] [Description("Indicates whether only single series points are highlighted when the Crosshair cursor intersects with them.")] public bool HighlightSinglePoint { get { return (TestState(States.HighlightSinglePoint)); } set { if (value != HighlightSinglePoint) { SetState(States.HighlightSinglePoint, value); OnPropertyChangedEx("HighlightSinglePoint", VisualChangeType.Render); } } } #endregion #region ShowValueXLabels /// /// Gets or sets whether ValueX Labels are shown. /// [DefaultValue(false), Category("Appearance")] [Description("Indicates whether ValueX Labels are shown.")] public bool ShowValueXLabels { get { return (TestState(States.ShowValueXLabels)); } set { if (value != ShowValueXLabels) { SetState(States.ShowValueXLabels, value); OnPropertyChangedEx("ShowValueXLabels", VisualChangeType.Render); } } } #endregion #region ShowValueXLine /// /// Gets or sets whether a ValueX line is shown. /// [DefaultValue(false), Category("Appearance")] [Description("Indicates whether a ValueX line is shown.")] public bool ShowValueXLine { get { return (TestState(States.ShowValueXLine)); } set { if (value != ShowValueXLine) { SetState(States.ShowValueXLine, value); OnPropertyChangedEx("ShowValueXLine", VisualChangeType.Render); } } } #endregion #region ShowCrosshairLabelMarkers /// /// Gets or sets whether Crosshair label markers are shown. /// [DefaultValue(true), Category("Appearance")] [Description("Indicates whether Crosshair label markers are shown.")] public bool ShowCrosshairLabelMarkers { get { return (TestState(States.ShowCrosshairLabelMarkers)); } set { if (value != ShowCrosshairLabelMarkers) { SetState(States.ShowCrosshairLabelMarkers, value); OnPropertyChangedEx("ShowCrosshairLabelMarkers", VisualChangeType.Render); } } } #endregion #region ShowCrosshairLabels /// /// Gets or sets whether Crosshair labels are shown. /// [DefaultValue(false), Category("Appearance")] [Description("Indicates whether Crosshair labels are shown.")] public bool ShowCrosshairLabels { get { return (TestState(States.ShowCrosshairLabels)); } set { if (value != ShowCrosshairLabels) { SetState(States.ShowCrosshairLabels, value); OnPropertyChangedEx("ShowCrosshairLabels", VisualChangeType.Render); } } } #endregion #region ShowGroupHeaders /// /// Gets or sets whether Group Headers are shown for each series. /// [DefaultValue(true), Category("Appearance")] [Description("Indicates whether Group Headers are shown for each series.")] public bool ShowGroupHeaders { get { return (TestState(States.ShowGroupHeader)); } set { if (value != ShowGroupHeaders) { SetState(States.ShowGroupHeader, value); OnPropertyChangedEx("ShowGroupHeaders", VisualChangeType.Layout); } } } #endregion #region ShowValueYLabels /// /// Gets or sets whether a ValueY label is shown. /// [DefaultValue(false), Category("Appearance")] [Description("Indicates whether a ValueY label is shown.")] public bool ShowValueYLabels { get { return (TestState(States.ShowValueYLabels)); } set { if (value != ShowValueYLabels) { SetState(States.ShowValueYLabels, value); OnPropertyChangedEx("ShowValueYLabels", VisualChangeType.Render); } } } #endregion #region ShowValueYLine /// /// Gets or sets whether a ValueY line is shown. /// [DefaultValue(false), Category("Appearance")] [Description("Indicates whether a ValueY line is shown.")] public bool ShowValueYLine { get { return (TestState(States.ShowValueYLine)); } set { if (value != ShowValueYLine) { SetState(States.ShowValueYLine, value); OnPropertyChangedEx("ShowValueYLine", VisualChangeType.Render); } } } #endregion #endregion #region MeasureOverride protected override void MeasureOverride(ChartLayoutInfo layoutInfo) { } #endregion #region ArrangeOverride protected override void ArrangeOverride(ChartLayoutInfo layoutInfo) { } #endregion #region RenderOverride protected override void RenderOverride(ChartRenderInfo renderInfo) { } #endregion #region Style support #region ApplyStyles public override void ApplyStyles(BaseVisualStyle style) { base.ApplyStyles(style); CrosshairVisualStyle cstyle = style as CrosshairVisualStyle; if (cstyle != null) { ApplyParentStyles(cstyle, Parent as ChartContainer); cstyle.ApplyStyle(CrosshairVisualStyle); ApplyDefaultLineStyles(cstyle.ValueXLineStyle); ApplyDefaultLineStyles(cstyle.ValueYLineStyle); if (cstyle.GroupHeaderFont == null) cstyle.GroupHeaderFont = SystemFonts.CaptionFont; if (cstyle.GroupHeaderTextColor.IsEmpty) cstyle.GroupHeaderTextColor = Color.Black; if (cstyle.Padding.IsEmpty == true) cstyle.Padding = new Style.Padding(4); if (cstyle.BorderThickness <= 0) cstyle.BorderThickness = 1; if (cstyle.BorderPattern == LinePattern.NotSet) cstyle.BorderPattern = LinePattern.Solid; if (cstyle.BorderColor.IsEmpty == true) cstyle.BorderColor = Color.Black; if (cstyle.Background.IsEmpty == true) cstyle.Background = new Background(Color.White); if (cstyle.Font == null) cstyle.Font = SystemFonts.DefaultFont; } } #region ApplyParentStyles private void ApplyParentStyles( CrosshairVisualStyle pstyle, ChartContainer item) { if (item != null) { ApplyParentStyles(pstyle, item.Parent as ChartContainer); if (item is ChartPanel) pstyle.ApplyStyle(((ChartPanel)item).DefaultVisualStyles.CrosshairVisualStyle); } else { pstyle.ApplyStyle(ChartControl.BaseVisualStyles.CrosshairVisualStyle); pstyle.ApplyStyle(ChartControl.DefaultVisualStyles.CrosshairVisualStyle); } } #endregion #region ApplyDefaultLineStyles private void ApplyDefaultLineStyles(ChartLineVisualStyle style) { if (style.LineColor.IsEmpty == true) style.LineColor = Color.Fuchsia; if (style.LinePattern == LinePattern.NotSet) style.LinePattern = LinePattern.Solid; if (style.LineWidth < 0) style.LineWidth = 1; } #endregion #region InvalidateStyle /// ///Invalidate the cached Styles /// public void InvalidateStyle() { ClearEffectiveStyles(); } #endregion #region ClearEffectiveStyles protected override void ClearEffectiveStyles() { _EffectiveCrosshairStyle.InvalidateStyle(); } #endregion #endregion #endregion #region Crosshair States [Flags] private enum States : uint { HighlightPoints = (1U << 0), HighlightSinglePoint = (1U << 1), ShowValueXLabels = (1U << 2), ShowValueXLine = (1U << 3), ShowCrosshairLabels = (1U << 4), ShowGroupHeader = (1U << 5), ShowCrosshairLabelMarkers = (1U << 6), ShowValueYLabels = (1U << 7), ShowValueYLine = (1U << 8), } #region TestState private bool TestState(States state) { return ((_States & state) == state); } #endregion #region SetState private void SetState(States state, bool value) { if (value == true) _States |= state; else _States &= ~state; } #endregion #endregion #region Copy/CopyTo public override ChartVisualElement Copy() { ChartCrosshair copy = new ChartCrosshair(); CopyTo(copy); return (copy); } public override void CopyTo(ChartVisualElement copy) { ChartCrosshair c = copy as ChartCrosshair; if (c != null) { base.CopyTo(c); c.AxisOrientation = AxisOrientation; c.CrosshairLabelMode = CrosshairLabelMode; c.PointIntersectMode = PointIntersectMode; c.PointIntersectMargin = PointIntersectMargin; c.CrosshairLabelVisualStyle = (_CrosshairLabelVisualStyle != null) ? CrosshairLabelVisualStyle.Copy() : null; c.CrosshairVisualStyle = (_CrosshairVisualStyle != null) ? CrosshairVisualStyle.Copy() : null; c.HighlightPoints = HighlightPoints; c.HighlightSinglePoint = HighlightSinglePoint; c.ShowValueXLabels = ShowValueXLabels; c.ShowValueXLine = ShowValueXLine; c.ShowCrosshairLabels = ShowCrosshairLabels; c.ShowGroupHeaders = ShowGroupHeaders; c.ShowValueYLabels = ShowValueYLabels; c.ShowValueYLine = ShowValueYLine; } } #endregion #region GetSerialData internal override SerialElementCollection GetSerialData(string serialName) { SerialElementCollection sec = new SerialElementCollection(); if (serialName != null) { if (serialName.Equals("") == true) serialName = "ChartCrosshair"; sec.AddStartElement(serialName); } sec.AddValue("AxisOrientation", AxisOrientation, AxisOrientation.X); sec.AddValue("CrosshairLabelMode", CrosshairLabelMode, CrosshairLabelMode.Common); sec.AddValue("PointIntersectMode", PointIntersectMode, PointIntersectMode.Edge); sec.AddValue("PointIntersectMargin", PointIntersectMargin, 2); if (_CrosshairLabelVisualStyle != null && _CrosshairLabelVisualStyle.IsEmpty == false) sec.AddElement(_CrosshairLabelVisualStyle.GetSerialData("CrosshairLabelVisualStyle")); if (_CrosshairVisualStyle != null && _CrosshairVisualStyle.IsEmpty == false) sec.AddElement(_CrosshairVisualStyle.GetSerialData("CrosshairVisualStyle")); sec.AddValue("HighlightPoints", HighlightPoints, false); sec.AddValue("HighlightSinglePoint", HighlightSinglePoint, false); sec.AddValue("ShowValueXLabels", ShowValueXLabels, false); sec.AddValue("ShowValueXLine", ShowValueXLine, false); sec.AddValue("ShowCrosshairLabels", ShowCrosshairLabels, false); sec.AddValue("ShowGroupHeaders", ShowGroupHeaders, true); sec.AddValue("ShowValueYLabels", ShowValueYLabels, false); sec.AddValue("ShowValueYLine", ShowValueYLine, false); sec.AddElement(base.GetSerialData(null)); if (serialName != null) sec.AddEndElement(serialName); return (sec); } #endregion #region PutSerialData #region ProcessValue internal override void ProcessValue(SerialElement se) { switch (se.Name) { case "AxisOrientation": AxisOrientation = (AxisOrientation)se.GetValueEnum(typeof(AxisOrientation)); break; case "CrosshairLabelMode": CrosshairLabelMode = (CrosshairLabelMode)se.GetValueEnum(typeof(CrosshairLabelMode)); break; case "PointIntersectMode": PointIntersectMode = (PointIntersectMode)se.GetValueEnum(typeof(PointIntersectMode)); break; case "PointIntersectMargin": PointIntersectMargin = int.Parse(se.StringValue); break; case "HighlightPoints": HighlightPoints = bool.Parse(se.StringValue); break; case "HighlightSinglePoint": HighlightSinglePoint = bool.Parse(se.StringValue); break; case "ShowValueXLabels": ShowValueXLabels = bool.Parse(se.StringValue); break; case "ShowValueXLine": ShowValueXLine = bool.Parse(se.StringValue); break; case "ShowCrosshairLabels": ShowCrosshairLabels = bool.Parse(se.StringValue); break; case "ShowGroupHeaders": ShowGroupHeaders = bool.Parse(se.StringValue); break; case "ShowValueYLabels": ShowValueYLabels = bool.Parse(se.StringValue); break; case "ShowValueYLine": ShowValueYLine = bool.Parse(se.StringValue); break; default: base.ProcessValue(se); break; } } #endregion #region ProcessCollection internal override void ProcessCollection(SerialElement se) { SerialElementCollection sec = se.Sec; switch (se.Name) { case "CrosshairLabelVisualStyle": sec.PutSerialData(CrosshairLabelVisualStyle); break; case "CrosshairVisualStyle": sec.PutSerialData(CrosshairVisualStyle); break; default: base.ProcessCollection(se); break; } } #endregion #endregion #region IDisposable public override void Dispose() { CrosshairLabelVisualStyle = null; CrosshairVisualStyle = null; base.Dispose(); } #endregion } #region enums #region CrosshairLabelMode public enum CrosshairLabelMode { /// /// A common Crosshair label is shown for all series /// Common, /// /// A crosshair label is shown for the nearest series /// NearestSeries, /// /// A crosshair label is shown for each series /// EachSeries, } #endregion #region CrosshairLabelPosition public enum CrosshairLabelPosition { /// /// Crosshair labels positioned relative to the Chart /// ChartRelative, /// /// Crosshair labels positioned relative to the Mouse /// MouseRelative, } #endregion #region PointIntersectMode public enum PointIntersectMode { /// /// From center. /// Center, /// /// From edge. /// Edge, } #endregion #endregion }