diff --git a/CommonLib/Axis/AxisDrawingSettings/AxisDrawingSettingsBase.cs b/CommonLib/Axis/AxisDrawingSettings/AxisDrawingSettingsBase.cs new file mode 100644 index 0000000..79bb201 --- /dev/null +++ b/CommonLib/Axis/AxisDrawingSettings/AxisDrawingSettingsBase.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using MvvmCharting.Common; + +namespace MvvmCharting.Axis +{ + public abstract class AxisDrawingSettingsBase : IAxisDrawingSettingsBase + { + protected double PlottingLength { get; } + protected double ActualTickInterval { get; set; } + protected int ActualTickCount { get; set; } + protected double PixelPerUnit { get; set; } + + protected AxisDrawingSettingsBase(double plottingLength) + { + this.PlottingLength = plottingLength; + } + + public abstract IEnumerable GetPlottingValues(); + + + public virtual bool CanUpdateAxisItems() + { + return !this.PlottingLength.IsNaNOrZero() && !this.ActualTickInterval.IsNaNOrZero(); + } + + public virtual bool CanUpdateAxisItemsCoordinate() + { + return true; + } + + public abstract double CalculateCoordinate(double value, AxisType axisType); + + + public override bool Equals(object obj) + { + return Equals(obj as AxisDrawingSettingsBase); + } + + public virtual bool Equals(AxisDrawingSettingsBase other) + { + + if (other == null) + { + return false; + } + + return this.PlottingLength.NearlyEqual(other.PlottingLength, 0.0001) && + this.ActualTickCount == other.ActualTickCount && + this.ActualTickInterval == other.ActualTickInterval && + this.PixelPerUnit == other.PixelPerUnit; + } + } +} \ No newline at end of file diff --git a/CommonLib/Axis/AxisDrawingSettings/CategoryAxisDrawingSettings.cs b/CommonLib/Axis/AxisDrawingSettings/CategoryAxisDrawingSettings.cs new file mode 100644 index 0000000..e0dfc7b --- /dev/null +++ b/CommonLib/Axis/AxisDrawingSettings/CategoryAxisDrawingSettings.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using MvvmCharting.Common; + +namespace MvvmCharting.Axis +{ + public class CategoryAxisDrawingSettings : AxisDrawingSettingsBase + { + + + private IList PlottingItemValues { get; } + + private Range ValuePadding { get; } + + public CategoryAxisDrawingSettings(int tickInterval, CategoryPlottingRange plottingRange, double plottingLength) + : base(plottingLength) + { + if (plottingRange == null) + { + throw new ArgumentException(); + } + + var list = plottingRange.PlottingItemValues; + if (list == null || list.Count == 0) + { + throw new ArgumentException(); + } + + this.PlottingItemValues = plottingRange.PlottingItemValues; + this.ValuePadding = plottingRange.ValuePadding; + + + + this.PixelPerUnit = this.PlottingLength / (this.PlottingItemValues.Count + this.ValuePadding.Min + this.ValuePadding.Max); + + + //calculated state: + this.ActualTickInterval = tickInterval; + + this.ActualTickCount = (int)Math.Floor(this.PlottingItemValues.Count / this.ActualTickInterval) + 1; + + + } + + + + + public override bool Equals(AxisDrawingSettingsBase obj) + { + if (!base.Equals(obj)) + { + return false; + } + + var other = obj as CategoryAxisDrawingSettings; + if (other == null) + { + return false; + } + + return this.PlottingItemValues?.Count == other.PlottingItemValues?.Count && + this.PlottingItemValues != null && + this.PlottingItemValues.SequenceEqual(other.PlottingItemValues); + } + + public override IEnumerable GetPlottingValues() + { + return this.PlottingItemValues; + } + + public override bool CanUpdateAxisItemsCoordinate() + { + return !this.PlottingLength.IsNaNOrZero() && this.PlottingItemValues?.Count > 0; + } + + public override double CalculateCoordinate(double i, AxisType axisType) + { + + var coordinate = (i + this.ValuePadding.Min) * this.PixelPerUnit; + + if (axisType == AxisType.Y) + { + coordinate = this.PlottingLength - coordinate; + } + + return coordinate; + } + + } +} \ No newline at end of file diff --git a/CommonLib/Axis/AxisDrawingSettings/IAxisDrawingSettingsBase.cs b/CommonLib/Axis/AxisDrawingSettings/IAxisDrawingSettingsBase.cs new file mode 100644 index 0000000..2215020 --- /dev/null +++ b/CommonLib/Axis/AxisDrawingSettings/IAxisDrawingSettingsBase.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace MvvmCharting.Axis +{ + public interface IAxisDrawingSettingsBase + { + + IEnumerable GetPlottingValues(); + + bool CanUpdateAxisItems(); + + bool CanUpdateAxisItemsCoordinate(); + + double CalculateCoordinate(double value, AxisType axisType); + } +} \ No newline at end of file diff --git a/CommonLib/Axis/AxisDrawingSettings/NumericAxisDrawingSettings.cs b/CommonLib/Axis/AxisDrawingSettings/NumericAxisDrawingSettings.cs new file mode 100644 index 0000000..35bc89d --- /dev/null +++ b/CommonLib/Axis/AxisDrawingSettings/NumericAxisDrawingSettings.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using MvvmCharting.Common; + +namespace MvvmCharting.Axis +{ + + + + + public class NumericAxisDrawingSettings : AxisDrawingSettingsBase + { + + + private Range FullPlottingRange { get; } + + private Range ActualPlottingRange { get; } + + public NumericAxisDrawingSettings(int tickCount, double tickInterval, + NumericPlottingRange plottingRange, double plottingLength) + : base(plottingLength) + { + var range = plottingRange.ActualRange; + if (range.IsInvalid || plottingLength.IsNaNOrZero()) + { + throw new ArgumentException(); + } + + if (tickCount < 0 || tickCount > ushort.MaxValue) + { + throw new NotSupportedException(); + } + + + this.ActualPlottingRange = plottingRange.ActualRange; + this.FullPlottingRange = plottingRange.FullRange; + + this.PixelPerUnit = plottingLength / plottingRange.FullRange.Span; + + //calculated state: + this.ActualTickInterval = !tickInterval.IsNaN() ? tickInterval : this.ActualPlottingRange.Span / tickCount; + this.ActualTickCount = (int)Math.Floor(this.ActualPlottingRange.Span / this.ActualTickInterval) + 1; + } + + + public override bool Equals(AxisDrawingSettingsBase obj) + { + if (!base.Equals(obj)) + { + return false; + } + var other = obj as NumericAxisDrawingSettings; + if (other == null) + { + return false; + } + + return this.FullPlottingRange == other.FullPlottingRange && + this.ActualPlottingRange == other.ActualPlottingRange; + } + + + + public override bool CanUpdateAxisItemsCoordinate() + { + return !this.PlottingLength.IsNaNOrZero() && !this.FullPlottingRange.Span.IsNaNOrZero(); + } + public override IEnumerable GetPlottingValues() + { + var startValue = this.ActualPlottingRange.Min; + + + for (int i = 0; i < this.ActualTickCount; i++) + { + + yield return startValue + i * this.ActualTickInterval; + } + } + + public override double CalculateCoordinate(double value, AxisType axisType) + { + var offset = (value - this.FullPlottingRange.Min) * this.PixelPerUnit; + + if (axisType == AxisType.Y) + { + offset = this.PlottingLength - offset; + } + + return offset; + } + + } +} \ No newline at end of file diff --git a/CommonLib/Axis/CategoryAxisDrawingSettings.cs b/CommonLib/Axis/CategoryAxisDrawingSettings.cs deleted file mode 100644 index 7b721d8..0000000 --- a/CommonLib/Axis/CategoryAxisDrawingSettings.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using MvvmCharting.Common; - -namespace MvvmCharting.Axis -{ - public class CategoryAxisDrawingSettings : ICategoryAxisDrawingSettings - { - - - #region input values - - public IList PlottingItemValues { get; } - - public double PlottingLength { get; } - #endregion - - #region generated values - - public double ActualTickInterval { get; } - - public int ActualTickCount { get; } - - #endregion - - public CategoryAxisDrawingSettings(int tickInterval, IList plottingItemValues, double plottingLength) - { - if (plottingItemValues == null) - { - return; - } - - this.PlottingItemValues = plottingItemValues; - - - this.PlottingLength = plottingLength; - - //calculated state: - this.ActualTickInterval = tickInterval; - - this.ActualTickCount = (int)Math.Floor(plottingItemValues.Count / this.ActualTickInterval) + 1; - } - - - public override bool Equals(object obj) - { - var other = obj as CategoryAxisDrawingSettings; - if (other == null) - { - return false; - } - - return this.PlottingLength.NearlyEqual(other.PlottingLength, 0.0001) && - this.PlottingItemValues?.Count == other.PlottingItemValues?.Count && - (this.PlottingItemValues != null && this.PlottingItemValues.SequenceEqual(other.PlottingItemValues)) && - this.ActualTickCount == other.ActualTickCount && - this.ActualTickInterval == other.ActualTickInterval; - } - - public bool CanUpdateAxisItems() - { - - return !this.PlottingLength.IsNaNOrZero() && !this.ActualTickInterval.IsNaNOrZero(); - } - - public bool CanUpdateAxisItemsCoordinate() - { - return !this.PlottingLength.IsNaNOrZero() && this.PlottingItemValues?.Count > 0; - } - - } -} \ No newline at end of file diff --git a/CommonLib/Axis/IAxisNS.cs b/CommonLib/Axis/IAxisNS.cs index b43563f..28ce111 100644 --- a/CommonLib/Axis/IAxisNS.cs +++ b/CommonLib/Axis/IAxisNS.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; -using MvvmCharting.Common; -using MvvmCharting.Axis; -using MvvmCharting.Axis; - + namespace MvvmCharting.Axis { diff --git a/CommonLib/Axis/IXAxisOwner.cs b/CommonLib/Axis/IXAxisOwner.cs index f56d3f9..4e94e8d 100644 --- a/CommonLib/Axis/IXAxisOwner.cs +++ b/CommonLib/Axis/IXAxisOwner.cs @@ -6,7 +6,7 @@ public interface IXAxisOwner : IAxisOwner { - event Action HorizontalSettingChanged; + event Action HorizontalSettingChanged; } diff --git a/CommonLib/Axis/IYAxisOwner.cs b/CommonLib/Axis/IYAxisOwner.cs index 462125d..799b250 100644 --- a/CommonLib/Axis/IYAxisOwner.cs +++ b/CommonLib/Axis/IYAxisOwner.cs @@ -6,6 +6,6 @@ public interface IYAxisOwner : IAxisOwner { - event Action VerticalSettingChanged; + event Action VerticalSettingChanged; } } \ No newline at end of file diff --git a/CommonLib/Axis/NumericAxisDrawingSettings.cs b/CommonLib/Axis/NumericAxisDrawingSettings.cs deleted file mode 100644 index dd5d5b7..0000000 --- a/CommonLib/Axis/NumericAxisDrawingSettings.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Collections.Generic; -using MvvmCharting.Common; - -namespace MvvmCharting.Axis -{ - public interface IAxisDrawingSettingsBase - { - double PlottingLength { get; } - - double ActualTickInterval { get; } - - int ActualTickCount { get; } - - bool CanUpdateAxisItems(); - - bool CanUpdateAxisItemsCoordinate(); - } - - public interface INumericAxisDrawingSettings : IAxisDrawingSettingsBase - { - Range PlottingDataRange { get; } - } - - public interface ICategoryAxisDrawingSettings : IAxisDrawingSettingsBase - { - IList PlottingItemValues { get; } - } - - - public class NumericAxisDrawingSettings : INumericAxisDrawingSettings - { - public override string ToString() - { - return string.Format($"Input values: PlottingDataRange={this.PlottingDataRange}, PlottingDataRange.Span={this.PlottingDataRange.Span}, PlottingLength={this.PlottingLength}" + Environment.NewLine - + $"Calculated values: ActualTickInterval={ActualTickInterval}, ActualTickCount={ActualTickCount}"); - } - - #region input values - public Range PlottingDataRange { get; } - - public double PlottingLength { get; } - #endregion - - #region generated values - - public double ActualTickInterval { get; } - - public int ActualTickCount { get; } - - #endregion - - public NumericAxisDrawingSettings(int tickCount, double tickInterval, Range range, double plottingLength) - { - - if (tickCount < 0 || tickCount > ushort.MaxValue) - { - throw new NotSupportedException(); - } - - - - this.PlottingDataRange = range; - - - this.PlottingLength = plottingLength; - - //calculated state: - this.ActualTickInterval = !tickInterval.IsNaN() ? tickInterval : this.PlottingDataRange.Span / tickCount; - this.ActualTickCount = (int)Math.Floor(this.PlottingDataRange.Span / this.ActualTickInterval) + 1; - } - - - public override bool Equals(object obj) - { - var other = obj as NumericAxisDrawingSettings; - if (other == null) - { - return false; - } - - return this.PlottingLength.NearlyEqual(other.PlottingLength, 0.0001) && - this.PlottingDataRange == other.PlottingDataRange && - this.ActualTickCount == other.ActualTickCount && - this.ActualTickInterval == other.ActualTickInterval; - } - - public bool CanUpdateAxisItems() - { - - return !this.PlottingLength.IsNaNOrZero() && !this.ActualTickInterval.IsNaNOrZero(); - } - - public bool CanUpdateAxisItemsCoordinate() - { - return !this.PlottingLength.IsNaNOrZero() && !this.PlottingDataRange.Span.IsNaNOrZero(); - } - - } -} \ No newline at end of file diff --git a/CommonLib/Axis/NumericPlottingSettings.cs b/CommonLib/Axis/NumericPlottingSettings.cs deleted file mode 100644 index 9a232bd..0000000 --- a/CommonLib/Axis/NumericPlottingSettings.cs +++ /dev/null @@ -1,266 +0,0 @@ - -using System.Collections.Generic; -using System.Linq; -using MvvmCharting.Common; - -using MvvmCharting.Drawing; -using MvvmCharting.Drawing; - -namespace MvvmCharting.Axis -{ - public interface IPlottingSettingsBase - { - AxisType Orientation { get; } - double RenderSize { get; } - - PointNS Margin { get; } - PointNS Padding { get; } - PointNS BorderThickness { get; } - - double GetAvailablePlottingSize(); - } - - public interface INumericPlottingSettings : IPlottingSettingsBase - { - Range PlottingDataRange { get; } - } - - public interface ICategoryPlottingSettings : IPlottingSettingsBase - { - IList PlottingItemValues { get; } - } - - public class PlottingSettingsBase : IPlottingSettingsBase - { - - public AxisType Orientation { get; } - public double RenderSize { get; } - - public PointNS Margin { get; } - public PointNS Padding { get; } - public PointNS BorderThickness { get; } - - //public Range PlottingDataRange { get; } - - public double GetAvailablePlottingSize() - { - return this.RenderSize - (this.Margin.X + this.Margin.Y) - - (this.Padding.X + this.Padding.Y) - - (this.BorderThickness.X + this.BorderThickness.Y); - } - - protected PlottingSettingsBase(AxisType orientation, - double renderSize, - PointNS margin, - PointNS padding, - PointNS borderThickness/*, - Range plottingDataRange*/) - { - this.RenderSize = renderSize; - this.Margin = margin; - this.Padding = padding; - this.BorderThickness = borderThickness; - //this.PlottingDataRange = plottingDataRange; - this.Orientation = orientation; - } - - public override bool Equals(object obj) - { - var other = obj as PlottingSettingsBase; - if (other == null) - { - return false; - } - return other.Equals(this); - } - - public bool Equals(PlottingSettingsBase obj) - { - return this.RenderSize.NearlyEqual(obj.RenderSize, 0.0001) && - this.Margin == obj.Margin && - this.Padding == obj.Padding && - this.BorderThickness == obj.BorderThickness - //&& - //this.PlottingDataRange == obj.PlottingDataRange - ; - } - - public static bool Validate(double length, - PointNS margin, - PointNS pading, - PointNS borderThickness/*, - Range plotingDataRange*/) - { - return !length.IsInvalid() && - !length.IsZero() && - !margin.IsInvalid() && - !pading.IsInvalid() && - !borderThickness.IsInvalid()/* && - !plotingDataRange.IsInvalid*/; - - } - } - - public class NumericPlottingSettings : PlottingSettingsBase, INumericPlottingSettings - { - - - - public Range PlottingDataRange { get; } - - - - public NumericPlottingSettings(AxisType orientation, - double renderSize, - PointNS margin, - PointNS padding, - PointNS borderThickness, - Range plottingDataRange) - : base(orientation, renderSize, margin, padding, borderThickness) - { - - this.PlottingDataRange = plottingDataRange; - - } - - public override bool Equals(object obj) - { - var other = obj as NumericPlottingSettings; - if (other == null) - { - return false; - } - return other.Equals(this); - } - - public bool Equals(NumericPlottingSettings obj) - { - var plottingSettingsBase = obj as PlottingSettingsBase; - if (obj == null) - { - return false; - } - - return plottingSettingsBase.Equals(this) && - this.PlottingDataRange == obj.PlottingDataRange; - } - - public static bool Validate(double length, - PointNS margin, - PointNS pading, - PointNS borderThickness, - Range plotingDataRange) - { - return !length.IsInvalid() && - !length.IsZero() && - !margin.IsInvalid() && - !pading.IsInvalid() && - !borderThickness.IsInvalid() && - !plotingDataRange.IsInvalid; - - } - - public static bool operator ==(NumericPlottingSettings settings, NumericPlottingSettings settings2) - { - if (object.ReferenceEquals(settings, null)) - { - return object.ReferenceEquals(settings2, null); - } - - return settings.Equals(settings2); - } - - public static bool operator !=(NumericPlottingSettings settings, NumericPlottingSettings settings2) - { - return !(settings == settings2); - } - } - - - public class CategoryPlottingSettings : PlottingSettingsBase, ICategoryPlottingSettings - { - - - public IList PlottingItemValues { get; } - - - - - public CategoryPlottingSettings(AxisType orientation, - double renderSize, - PointNS margin, - PointNS padding, - PointNS borderThickness, - IList plottingItemValues) - : base(orientation, renderSize, margin, padding, borderThickness) - { - - this.PlottingItemValues = plottingItemValues; - - } - - public override bool Equals(object obj) - { - var other = obj as CategoryPlottingSettings; - if (other == null) - { - return false; - } - return other.Equals(this); - } - - public bool Equals(CategoryPlottingSettings obj) - { - var plottingSettingsBase = obj as PlottingSettingsBase; - if (obj == null) - { - return false; - } - - if (!plottingSettingsBase.Equals(this)) - { - return false; - } - - if (this.PlottingItemValues == null) - { - return obj.PlottingItemValues == null; - } - - return this.PlottingItemValues.SequenceEqual(obj.PlottingItemValues); - } - - public static bool Validate(double length, - PointNS margin, - PointNS pading, - PointNS borderThickness, - Range plotingDataRange) - { - return !length.IsInvalid() && - !length.IsZero() && - !margin.IsInvalid() && - !pading.IsInvalid() && - !borderThickness.IsInvalid() && - !plotingDataRange.IsInvalid; - - } - - public static bool operator ==(CategoryPlottingSettings settings, CategoryPlottingSettings settings2) - { - if (object.ReferenceEquals(settings, null)) - { - return object.ReferenceEquals(settings2, null); - } - - return settings.Equals(settings2); - } - - public static bool operator !=(CategoryPlottingSettings settings, CategoryPlottingSettings settings2) - { - return !(settings == settings2); - } - } - - - -} \ No newline at end of file diff --git a/CommonLib/Axis/PlottingRangeSettings/CategoryPlottingRange.cs b/CommonLib/Axis/PlottingRangeSettings/CategoryPlottingRange.cs new file mode 100644 index 0000000..96094ad --- /dev/null +++ b/CommonLib/Axis/PlottingRangeSettings/CategoryPlottingRange.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; +using MvvmCharting.Common; + +namespace MvvmCharting.Axis +{ + + + public class CategoryPlottingRange : PlottingRangeBase + { + public CategoryPlottingRange(IList plottingItemValues, Range valuePadding) + :base(valuePadding) + { + this.PlottingItemValues = plottingItemValues; + } + + public IList PlottingItemValues { get; } + + + public override bool Equals(PlottingRangeBase obj) + { + if (!base.Equals(obj)) + { + return false; + } + + var other = obj as CategoryPlottingRange; + + return this.ValuePadding == obj.ValuePadding && this.PlottingItemValues.SequenceEqual(other.PlottingItemValues); + } + + + } +} \ No newline at end of file diff --git a/CommonLib/Axis/PlottingRangeSettings/NumericPlottingRange.cs b/CommonLib/Axis/PlottingRangeSettings/NumericPlottingRange.cs new file mode 100644 index 0000000..6a6d69a --- /dev/null +++ b/CommonLib/Axis/PlottingRangeSettings/NumericPlottingRange.cs @@ -0,0 +1,45 @@ +using MvvmCharting.Common; + +namespace MvvmCharting.Axis +{ + + public class NumericPlottingRange : PlottingRangeBase + { + public Range ActualRange { get; } + public Range FullRange + { + get + { + return new Range(this.ActualRange.Min - this.ValuePadding.Min, + this.ActualRange.Max + this.ValuePadding.Max); + } + } + + + public NumericPlottingRange(Range actualRange, Range valuePadding) + :base(valuePadding) + { + this.ActualRange = actualRange; + + } + + + + public override bool Equals(PlottingRangeBase obj) + { + if (!base.Equals(obj)) + { + return false; + } + + var other = obj as NumericPlottingRange; + if (other == null) + { + return false; + } + return this.ActualRange == other.ActualRange; + } + + + } +} \ No newline at end of file diff --git a/CommonLib/Axis/PlottingRangeSettings/PlottingRangeBase.cs b/CommonLib/Axis/PlottingRangeSettings/PlottingRangeBase.cs new file mode 100644 index 0000000..073b413 --- /dev/null +++ b/CommonLib/Axis/PlottingRangeSettings/PlottingRangeBase.cs @@ -0,0 +1,44 @@ +using MvvmCharting.Common; + +namespace MvvmCharting.Axis +{ + public class PlottingRangeBase + { + public Range ValuePadding { get; } + + protected PlottingRangeBase(Range valuePadding) + { + this.ValuePadding = valuePadding; + } + + public override bool Equals(object obj) + { + return Equals(obj as PlottingRangeBase); + } + + public virtual bool Equals(PlottingRangeBase obj) + { + if (obj == null) + { + return false; + } + + return this.ValuePadding == obj.ValuePadding; + } + + public static bool operator ==(PlottingRangeBase rangeSetting, PlottingRangeBase settings2) + { + if (object.ReferenceEquals(rangeSetting, null)) + { + return object.ReferenceEquals(settings2, null); + } + + return rangeSetting.Equals(settings2); + } + + public static bool operator !=(PlottingRangeBase rangeSetting, PlottingRangeBase settings2) + { + return !(rangeSetting == settings2); + } + } +} \ No newline at end of file diff --git a/CommonLib/CommonLib.csproj b/CommonLib/CommonLib.csproj index 8f07bd8..107b75c 100644 --- a/CommonLib/CommonLib.csproj +++ b/CommonLib/CommonLib.csproj @@ -5,4 +5,8 @@ MvvmCharting + + + + diff --git a/CommonLib/Drawing/PointNS.cs b/CommonLib/Drawing/PointNS.cs index c5ec171..0d0b4bb 100644 --- a/CommonLib/Drawing/PointNS.cs +++ b/CommonLib/Drawing/PointNS.cs @@ -9,7 +9,9 @@ public struct PointNS internal double _x; internal double _y; - public static bool operator ==(PointNS point1, PointNS point2) + public static readonly PointNS Empty = new PointNS(double.NaN, double.NaN); + + public static bool operator ==(PointNS point1, PointNS point2) { return point1.X == point2.X && point1.Y == point2.Y; } diff --git a/CommonLib/Drawing/PointNSHelper.cs b/CommonLib/Drawing/PointNSHelper.cs index ef2b747..8abcb1a 100644 --- a/CommonLib/Drawing/PointNSHelper.cs +++ b/CommonLib/Drawing/PointNSHelper.cs @@ -5,8 +5,6 @@ namespace MvvmCharting.Drawing { public static class PointNSHelper { - public static PointNS EmptyPoint = new PointNS(Double.NaN, double.NaN); - public static bool IsEmpty(this PointNS pt) { return pt.X.IsNaN() || pt.Y.IsNaN(); diff --git a/CommonLib/Series/ISeries.cs b/CommonLib/Series/ISeriesControl.cs similarity index 85% rename from CommonLib/Series/ISeries.cs rename to CommonLib/Series/ISeriesControl.cs index 0795ab7..f0481f4 100644 --- a/CommonLib/Series/ISeries.cs +++ b/CommonLib/Series/ISeriesControl.cs @@ -1,5 +1,6 @@ using System; using MvvmCharting.Common; +using MvvmCharting.Drawing; namespace MvvmCharting.Series { @@ -7,8 +8,9 @@ namespace MvvmCharting.Series /// This is the interface that a series should be implement before /// it can be used as the root element of SeriesTemplate /// - public interface ISeries + public interface ISeriesControl { + /// /// This is used to let its host chart see its X value range /// @@ -34,23 +36,23 @@ public interface ISeries void OnPlottingYValueRangeChanged(Range newValue); - bool IsHighLighted { get; set; } + bool IsHighlighted { get; set; } object DataContext { get; } - + PointNS[] GetCoordinates(); /// /// Fired when the X value range of a series is changed. /// The host chart need to know when the value range of its series changed /// - event Action XRangeChanged; + event Action XRangeChanged; /// /// Fired when the Y value range of a series is changed. /// The host chart need to know when the value range of its series changed /// - event Action YRangeChanged; + event Action YRangeChanged; //event Action PropertyChanged; } diff --git a/CommonLib/Series/Scatter/IScatter.cs b/CommonLib/Series/Scatter/IScatter.cs deleted file mode 100644 index 23fbde0..0000000 --- a/CommonLib/Series/Scatter/IScatter.cs +++ /dev/null @@ -1,13 +0,0 @@ -using MvvmCharting.Common; -using MvvmCharting.Drawing; - -namespace MvvmCharting.Series -{ - public interface IScatter: IPlottable_2D - { - - PointNS GetOffsetForSizeChangedOverride(); - - object DataContext { get; } - } -} \ No newline at end of file diff --git a/CommonLib/Series/SeriesType.cs b/CommonLib/Series/SeriesType.cs new file mode 100644 index 0000000..6d35c50 --- /dev/null +++ b/CommonLib/Series/SeriesType.cs @@ -0,0 +1,10 @@ +namespace MvvmCharting.Series +{ + public enum SeriesType + { + Line, + Area, + Scatter, + Bar + } +} \ No newline at end of file diff --git a/CommonLib/Series/StackMode.cs b/CommonLib/Series/StackMode.cs new file mode 100644 index 0000000..35aaaee --- /dev/null +++ b/CommonLib/Series/StackMode.cs @@ -0,0 +1,10 @@ +namespace MvvmCharting.Series +{ + public enum StackMode + { + Unspecified, + None, + Stacked, + Stacked100 + } +} \ No newline at end of file diff --git a/CommonLib/common/IValueConverterNS.cs b/CommonLib/common/IValueConverterNS.cs deleted file mode 100644 index 2b20dc5..0000000 --- a/CommonLib/common/IValueConverterNS.cs +++ /dev/null @@ -1,12 +0,0 @@ -//using System.Globalization; - -//namespace MvvmCharting.Common -//{ -// /// -// /// This is the .NET Standard version of WPF/UWP IValueConverter interface. -// /// -// public interface IValueConverterNS -// { -// object ConverterTo(object value, CultureInfo culture); -// } -//} diff --git a/CommonLib/common/MvvmChartException.cs b/CommonLib/common/MvvmChartException.cs index 002cfed..9ab7cfe 100644 --- a/CommonLib/common/MvvmChartException.cs +++ b/CommonLib/common/MvvmChartException.cs @@ -4,10 +4,17 @@ namespace MvvmCharting.Common { public class MvvmChartException : Exception { + public static MvvmChartException ThrowDataTemplateRootElementException(string dataTemplateName, Type baseType) + { + throw new MvvmChartException($"The root element in the {dataTemplateName} should inherit from {baseType}."); + } + + + public MvvmChartException(string msg) :base(msg) { - + } } diff --git a/CommonLib/common/Range.cs b/CommonLib/common/Range.cs index a58d8e0..348d26b 100644 --- a/CommonLib/common/Range.cs +++ b/CommonLib/common/Range.cs @@ -77,6 +77,16 @@ public override int GetHashCode() return !point1.Equals(point2); } + //public static Range operator +(Range point1, Range point2) + //{ + // return new Range(point1.Min + point2.Min, point1.Max + point2.Max); + //} + + //public static Range operator -(Range point1, Range point2) + //{ + // return new Range(point1.Min - point2.Min, point1.Max - point2.Max); + //} + public bool IsInRange(double d) { return d <= this.Max && d >= this.Min; diff --git a/MigrationBackup/b2e87e0c/DemoWpfFX/DemoWpfFX.csproj b/MigrationBackup/b2e87e0c/DemoWpfFX/DemoWpfFX.csproj new file mode 100644 index 0000000..135f001 --- /dev/null +++ b/MigrationBackup/b2e87e0c/DemoWpfFX/DemoWpfFX.csproj @@ -0,0 +1,192 @@ + + + + + + Debug + AnyCPU + {02960B4D-ACC3-4B6D-93D6-E578F33655E0} + WinExe + Demo.WpfFX + DemoWpfFX + v4.7.2 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + true + + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\..\packages\ModernWpfUI.0.9.2\lib\net462\ModernWpf.dll + + + ..\..\..\packages\ModernWpfUI.0.9.2\lib\net462\ModernWpf.Controls.dll + + + + + + ..\..\..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + CategoryAxisDemoView.xaml + + + + + SeriesCustomizingDemoView.xaml + + + + ScatterCustomizationDemoView.xaml + + + + + DateTimeDemoView.xaml + + + + BigDataTestView.xaml + + + + GeneralDemoView.xaml + + + + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + {28c40709-2e83-4a1f-be8a-eba5ab886012} + MvvmChartWpfFX + + + {2ac79084-def9-4164-8ace-bcb735047d96} + CommonLib + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + \ No newline at end of file diff --git a/MigrationBackup/b2e87e0c/DemoWpfFX/NuGetUpgradeLog.html b/MigrationBackup/b2e87e0c/DemoWpfFX/NuGetUpgradeLog.html new file mode 100644 index 0000000..a3e2a3a --- /dev/null +++ b/MigrationBackup/b2e87e0c/DemoWpfFX/NuGetUpgradeLog.html @@ -0,0 +1,164 @@ + + + + + NuGetMigrationLog +

+ NuGet Migration Report - DemoWpfFX

Overview

Migration to PackageReference was completed successfully. Please build and run your solution to verify that all packages are available.
+ If you run into any problems, have feedback, questions, or concerns, please + file an issue on the NuGet GitHub repository.
+ Changed files and this report have been backed up here: + C:\Users\simon\Documents\Projects_WPF\MvvmCharts\MigrationBackup\b2e87e0c\DemoWpfFX

Packages processed

Top-level dependencies:

Package IdVersion
ModernWpfUI + v0.9.2

Transitive dependencies:

Package IdVersion
Microsoft.Windows.SDK.Contracts + v10.0.18362.2005
System.Runtime.WindowsRuntime + v4.6.0
System.Runtime.WindowsRuntime.UI.Xaml + v4.6.0
System.ValueTuple + v4.5.0

Package compatibility issues

Description
+ No issues were found. +
\ No newline at end of file diff --git a/MigrationBackup/b2e87e0c/DemoWpfFX/packages.config b/MigrationBackup/b2e87e0c/DemoWpfFX/packages.config new file mode 100644 index 0000000..b9feda6 --- /dev/null +++ b/MigrationBackup/b2e87e0c/DemoWpfFX/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/WPF/WpfFX/Demo/App.xaml b/WPF/WpfFX/Demo/App.xaml index 368242a..f9afff2 100644 --- a/WPF/WpfFX/Demo/App.xaml +++ b/WPF/WpfFX/Demo/App.xaml @@ -3,19 +3,29 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Demo" xmlns:system="clr-namespace:System;assembly=mscorlib" - StartupUri="MainWindow.xaml"> + StartupUri="MainWindow.xaml" + xmlns:ui="http://schemas.modernwpf.com/2019"> - + + + + + + + - - + + + + + + - diff --git a/WPF/WpfFX/Demo/DemoWpfFX.csproj b/WPF/WpfFX/Demo/DemoWpfFX.csproj index 67baf50..5bedd2a 100644 --- a/WPF/WpfFX/Demo/DemoWpfFX.csproj +++ b/WPF/WpfFX/Demo/DemoWpfFX.csproj @@ -15,6 +15,8 @@ true true + + AnyCPU @@ -36,6 +38,7 @@ 4 + @@ -56,12 +59,11 @@ MSBuild:Compile Designer + CategoryAxisDemoView.xaml - - SeriesCustomizingDemoView.xaml @@ -160,9 +162,13 @@ CommonLib - + + + 0.9.2 + + \ No newline at end of file diff --git a/WPF/WpfFX/Demo/MainWindow.xaml b/WPF/WpfFX/Demo/MainWindow.xaml index 86f710b..184fbc1 100644 --- a/WPF/WpfFX/Demo/MainWindow.xaml +++ b/WPF/WpfFX/Demo/MainWindow.xaml @@ -6,8 +6,9 @@ xmlns:local="clr-namespace:Demo" xmlns:wpfFx="clr-namespace:Demo.WpfFX" mc:Ignorable="d" - Height="800" Width="1200" - Title="This a MvvmChart Demo for WPF(.NET Framework)" > + Height="900" Width="1200" + xmlns:ui="http://schemas.modernwpf.com/2019" + Title="MvvmChart Demo (WPF/.NET Framework)" > diff --git a/WPF/WpfFX/Demo/View/BigDataTestView.xaml b/WPF/WpfFX/Demo/View/BigDataTestView.xaml index 15af940..a70f972 100644 --- a/WPF/WpfFX/Demo/View/BigDataTestView.xaml +++ b/WPF/WpfFX/Demo/View/BigDataTestView.xaml @@ -6,6 +6,7 @@ xmlns:local="clr-namespace:Demo" xmlns:mvvmCharting="clr-namespace:MvvmCharting.WpfFX;assembly=MvvmChartWpfFX" xmlns:axis="clr-namespace:MvvmCharting.WpfFX.Axis;assembly=MvvmChartWpfFX" + xmlns:series="clr-namespace:MvvmCharting.WpfFX.Series;assembly=MvvmChartWpfFX" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> @@ -13,43 +14,38 @@ - - + - + - + + - - - - - - - - - + - - - + + + + + + + + + diff --git a/WPF/WpfFX/Demo/View/CategoryAxisDemoView.xaml b/WPF/WpfFX/Demo/View/CategoryAxisDemoView.xaml index 5d140a6..f7015b0 100644 --- a/WPF/WpfFX/Demo/View/CategoryAxisDemoView.xaml +++ b/WPF/WpfFX/Demo/View/CategoryAxisDemoView.xaml @@ -7,6 +7,7 @@ xmlns:demo="clr-namespace:Demo" xmlns:wpfFx="clr-namespace:MvvmCharting.WpfFX;assembly=MvvmChartWpfFX" xmlns:axis="clr-namespace:MvvmCharting.WpfFX.Axis;assembly=MvvmChartWpfFX" + xmlns:series="clr-namespace:MvvmCharting.WpfFX.Series;assembly=MvvmChartWpfFX" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> @@ -14,30 +15,36 @@ + + + + + - + - - + - - - + + + - - + + - + > + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + - + - + @@ -172,13 +265,38 @@ - - + + + + + + + + + + + + + + + + diff --git a/WPF/WpfFX/Demo/View/DateTimeDemoView.xaml b/WPF/WpfFX/Demo/View/DateTimeDemoView.xaml index 4e4c248..64a7ddc 100644 --- a/WPF/WpfFX/Demo/View/DateTimeDemoView.xaml +++ b/WPF/WpfFX/Demo/View/DateTimeDemoView.xaml @@ -6,6 +6,7 @@ xmlns:local="clr-namespace:Demo" xmlns:mvvmCharting="clr-namespace:MvvmCharting.WpfFX;assembly=MvvmChartWpfFX" xmlns:axis="clr-namespace:MvvmCharting.WpfFX.Axis;assembly=MvvmChartWpfFX" + xmlns:series="clr-namespace:MvvmCharting.WpfFX.Series;assembly=MvvmChartWpfFX" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> @@ -14,16 +15,20 @@ - - + + + + + + diff --git a/WPF/WpfFX/Demo/View/GeneralDemoView.xaml b/WPF/WpfFX/Demo/View/GeneralDemoView.xaml index 466d7c7..f8cd637 100644 --- a/WPF/WpfFX/Demo/View/GeneralDemoView.xaml +++ b/WPF/WpfFX/Demo/View/GeneralDemoView.xaml @@ -6,7 +6,9 @@ xmlns:local="clr-namespace:Demo" xmlns:mvvmCharting="clr-namespace:MvvmCharting.WpfFX;assembly=MvvmChartWpfFX" xmlns:axis="clr-namespace:MvvmCharting.WpfFX.Axis;assembly=MvvmChartWpfFX" - mc:Ignorable="d"> + xmlns:series="clr-namespace:MvvmCharting.WpfFX.Series;assembly=MvvmChartWpfFX" + mc:Ignorable="d" + xmlns:ui="http://schemas.modernwpf.com/2019"> @@ -15,28 +17,27 @@ - + - - + + - - - + + + - - + + + + - - - - + > + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -131,6 +220,9 @@ - + - + - - - - - + + + + - - - + @@ -216,6 +306,31 @@ + + + + + + + + + + + + + + + - - + - - + + - - + + - + - + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + - - + + + + + + + + + @@ -112,8 +142,8 @@ + SeriesTemplateSelector="{StaticResource SeriesTemplateSelector}" + SeriesItemsSource="{Binding ItemsSourceList, Source={StaticResource DemoDataViewModel}}"> @@ -125,16 +155,19 @@ - - - - + + + + diff --git a/WPF/WpfFX/Demo/View/ScatterCustomizationDemoView.xaml.cs b/WPF/WpfFX/Demo/View/ScatterCustomizationDemoView.xaml.cs index f5370a4..081c947 100644 --- a/WPF/WpfFX/Demo/View/ScatterCustomizationDemoView.xaml.cs +++ b/WPF/WpfFX/Demo/View/ScatterCustomizationDemoView.xaml.cs @@ -27,14 +27,12 @@ public override DataTemplate SelectTemplate(object item, DependencyObject contai { return null; } - - - + SomePoint pt = (SomePoint)item; int i = (int)pt.t; - int j = (int)pt.Y; + - if (i%2==0 && j%2==0) + if (i%2==0 ) { return DataTemplate1; } diff --git a/WPF/WpfFX/Demo/View/SeriesCustomizingDemoView.xaml b/WPF/WpfFX/Demo/View/SeriesCustomizingDemoView.xaml index 9f138d7..192da7f 100644 --- a/WPF/WpfFX/Demo/View/SeriesCustomizingDemoView.xaml +++ b/WPF/WpfFX/Demo/View/SeriesCustomizingDemoView.xaml @@ -7,18 +7,17 @@ xmlns:demo="clr-namespace:Demo" xmlns:wpfFx="clr-namespace:MvvmCharting.WpfFX;assembly=MvvmChartWpfFX" xmlns:axis="clr-namespace:MvvmCharting.WpfFX.Axis;assembly=MvvmChartWpfFX" + xmlns:series="clr-namespace:MvvmCharting.WpfFX.Series;assembly=MvvmChartWpfFX" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - - - - - - + + + + - + IsScatterSeriesVisible="{Binding ShowScatterSeries}" + IsLineSeriesVisible="{Binding ShowLineSeries}" + IsAreaSeriesVisible="{Binding ShowAreaSeries}" + ItemsSource="{Binding DataList}"> + + + + + + + + + + + + + + + + + + + + + - + IsScatterSeriesVisible="{Binding ShowScatterSeries}" + IsLineSeriesVisible="{Binding ShowLineSeries}" + IsAreaSeriesVisible="{Binding ShowAreaSeries}" + ItemsSource="{Binding DataList}"> + + + + + + + + + + + + + + + - + IsScatterSeriesVisible="{Binding ShowScatterSeries}" + IsLineSeriesVisible="{Binding ShowLineSeries}" + IsAreaSeriesVisible="{Binding ShowAreaSeries}" + ItemsSource="{Binding DataList}"> + + + + + + + + + + + + + + + - @@ -89,10 +145,9 @@ @@ -126,14 +181,14 @@ - + - - - + + + @@ -151,12 +206,12 @@ - + + SelectedItem="{Binding SelectedStackMode, Mode=TwoWay}"/> diff --git a/WPF/WpfFX/Demo/View/SeriesCustomizingDemoView.xaml.cs b/WPF/WpfFX/Demo/View/SeriesCustomizingDemoView.xaml.cs index aaee956..4d307a7 100644 --- a/WPF/WpfFX/Demo/View/SeriesCustomizingDemoView.xaml.cs +++ b/WPF/WpfFX/Demo/View/SeriesCustomizingDemoView.xaml.cs @@ -15,6 +15,7 @@ using System.Windows.Shapes; using MvvmCharting.Series; using MvvmCharting.WpfFX; +using MvvmCharting.WpfFX.Series; namespace Demo.WpfFX { diff --git a/WPF/WpfFX/Demo/ViewModel/Big Data/BigDataViewModel.cs b/WPF/WpfFX/Demo/ViewModel/Big Data/BigDataViewModel.cs index 35a7b6f..4f28d4a 100644 --- a/WPF/WpfFX/Demo/ViewModel/Big Data/BigDataViewModel.cs +++ b/WPF/WpfFX/Demo/ViewModel/Big Data/BigDataViewModel.cs @@ -23,7 +23,7 @@ public bool ShowSeriesLine { foreach (var sr in this.ItemsSourceList) { - sr.ShowSeriesLine = value; + sr.ShowLineSeries = value; } } } @@ -86,7 +86,7 @@ private void UpdateScatterVisible() { foreach (var sr in this.ItemsSourceList) { - sr.ShowSeriesPoints = this.ShowSeriesPointsGlobal; + sr.ShowScatterSeries = this.ShowSeriesPointsGlobal; } } diff --git a/WPF/WpfFX/Demo/ViewModel/Converters/IndexToScatterTemplateConverter.cs b/WPF/WpfFX/Demo/ViewModel/Converters/IndexToScatterTemplateConverter.cs index 6f8dd33..91b93ac 100644 --- a/WPF/WpfFX/Demo/ViewModel/Converters/IndexToScatterTemplateConverter.cs +++ b/WPF/WpfFX/Demo/ViewModel/Converters/IndexToScatterTemplateConverter.cs @@ -7,7 +7,30 @@ namespace Demo { - + public class IndexToAreaSeriesFillConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + + int index = (int)value; + switch (index) + { + case 1: + return Brushes.CadetBlue; + case 2: + return Brushes.Green; + case 0: + return Brushes.Blue; + default: + return Brushes.Green; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } public class IndexToStrokeConverter : IValueConverter { @@ -20,7 +43,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn case 0: return Brushes.CadetBlue; case 1: - return Brushes.Green; + return Brushes.YellowGreen; case 2: return Brushes.Blue; default: @@ -33,6 +56,32 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu throw new NotImplementedException(); } } + + public class IndexToBarBrushConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + int index = (int)value; + switch (index) + { + case 0: + return Brushes.LightSeaGreen; + case 1: + return Brushes.Crimson; + case 2: + return Brushes.DarkSeaGreen; + + } + + return Brushes.OrangeRed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + public class IndexToScatterTemplateConverter : IValueConverter { public DataTemplate ScatterDataTemplate { get; set; } diff --git a/WPF/WpfFX/Demo/ViewModel/Converters/SelectedSeriesShapeTypeToChartMaxConverter.cs b/WPF/WpfFX/Demo/ViewModel/Converters/SelectedSeriesShapeTypeToChartMaxConverter.cs deleted file mode 100644 index 6167a7a..0000000 --- a/WPF/WpfFX/Demo/ViewModel/Converters/SelectedSeriesShapeTypeToChartMaxConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Data; -using MvvmCharting.WpfFX; - -namespace Demo -{ - public class SelectedSeriesShapeTypeToChartMaxConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - SeriesMode t = (SeriesMode)value; - switch (t) - { - case SeriesMode.Line: - case SeriesMode.Area: - case SeriesMode.StackedArea: - return DependencyProperty.UnsetValue; - case SeriesMode.StackedArea100: - return 1.0; - default: - throw new ArgumentOutOfRangeException(); - } - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/WPF/WpfFX/Demo/ViewModel/Converters/SelectedSeriesShapeTypeToChartMinConverter.cs b/WPF/WpfFX/Demo/ViewModel/Converters/SelectedSeriesShapeTypeToChartMinConverter.cs deleted file mode 100644 index 0114486..0000000 --- a/WPF/WpfFX/Demo/ViewModel/Converters/SelectedSeriesShapeTypeToChartMinConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Data; -using MvvmCharting.WpfFX; - -namespace Demo -{ - public class SelectedSeriesShapeTypeToChartMinConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - SeriesMode t = (SeriesMode) value; - switch (t) - { - case SeriesMode.Line: - case SeriesMode.Area: - return DependencyProperty.UnsetValue; - case SeriesMode.StackedArea: - case SeriesMode.StackedArea100: - return 0.0; - default: - throw new ArgumentOutOfRangeException(); - } - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/WPF/WpfFX/Demo/ViewModel/General/ChartSettingTestViewModel.cs b/WPF/WpfFX/Demo/ViewModel/General/ChartSettingTestViewModel.cs index 7d63cfe..966a181 100644 --- a/WPF/WpfFX/Demo/ViewModel/General/ChartSettingTestViewModel.cs +++ b/WPF/WpfFX/Demo/ViewModel/General/ChartSettingTestViewModel.cs @@ -1,7 +1,9 @@ using System.Collections.ObjectModel; using MvvmCharting.Axis; using MvvmCharting.Common; +using MvvmCharting.Series; using MvvmCharting.WpfFX; +using MvvmCharting.WpfFX.Series; namespace Demo { @@ -17,15 +19,14 @@ public ChartSettingTestViewModel() this.YAxisPlacements.Add(AxisPlacement.Left); this.YAxisPlacements.Add(AxisPlacement.Right); - this.SeriesModeList = new ObservableCollection() + this.SeriesModeList = new ObservableCollection() { - SeriesMode.Line, - SeriesMode.Area, - SeriesMode.StackedArea, - SeriesMode.StackedArea100 + StackMode.None, + StackMode.Stacked, + StackMode.Stacked100 }; - SeriesGeometryBuilders=new ObservableCollection() + this.SeriesGeometryBuilders = new ObservableCollection() { "PolyLine","StepLine","Spline" }; @@ -34,16 +35,16 @@ public ChartSettingTestViewModel() public ObservableCollection SeriesGeometryBuilders { get; } - public ObservableCollection SeriesModeList { get; } - + public ObservableCollection SeriesModeList { get; } + public ObservableCollection XAxisPlacements { get; } - public ObservableCollection YAxisPlacements { get; } + public ObservableCollection YAxisPlacements { get; } - private SeriesMode _selectedSeriesMode; - public SeriesMode SelectedSeriesMode + private StackMode _selectedStackMode = StackMode.None; + public StackMode SelectedStackMode { - get { return this._selectedSeriesMode; } - set { SetProperty(ref this._selectedSeriesMode, value); } + get { return this._selectedStackMode; } + set { SetProperty(ref this._selectedStackMode, value); } } private string _selectedSeriesBuilder = "PolyLine"; @@ -53,7 +54,7 @@ public string SelectedSeriesBuilder set { SetProperty(ref this._selectedSeriesBuilder, value); } } - + private AxisPlacement _selectedXPlacement = AxisPlacement.Bottom; public AxisPlacement SelectedXPlacement diff --git a/WPF/WpfFX/Demo/ViewModel/General/ChartValuePaddingViewModel.cs b/WPF/WpfFX/Demo/ViewModel/General/ChartValuePaddingViewModel.cs new file mode 100644 index 0000000..031c847 --- /dev/null +++ b/WPF/WpfFX/Demo/ViewModel/General/ChartValuePaddingViewModel.cs @@ -0,0 +1,102 @@ +using System.Diagnostics; +using MvvmCharting.Common; + +namespace Demo +{ + public class ChartValuePaddingViewModel : BindableBase + { + private double _xMinValuePadding; + public double XMinValuePadding + { + get { return this._xMinValuePadding; } + set + { + if (SetProperty(ref this._xMinValuePadding, value)) + { + UpdateXValuePadding(); + } + + } + } + + private double _yMinValuePadding; + public double YMinValuePadding + { + get { return this._yMinValuePadding; } + set + { + if (SetProperty(ref this._yMinValuePadding, value)) + { + UpdateYValuePadding(); + } + + } + } + + private double _xMaxValuePadding; + public double XMaxValuePadding + { + get { return this._xMaxValuePadding; } + set + { + if (SetProperty(ref this._xMaxValuePadding, value)) + { + UpdateXValuePadding(); + } + + } + } + + private double _yMaxValuePadding; + public double YMaxValuePadding + { + get { return this._yMaxValuePadding; } + set + { + if (SetProperty(ref this._yMaxValuePadding, value)) + { + UpdateYValuePadding(); + } + + } + } + + private void UpdateXValuePadding() + { + this.XValuePadding = new Range(this.XMinValuePadding, this.XMaxValuePadding); + } + + private void UpdateYValuePadding() + { + this.YValuePadding = new Range(this.YMinValuePadding, this.YMaxValuePadding); + } + + private Range _yValuePadding; + public Range YValuePadding + { + get { return this._yValuePadding; } + set + { + if (SetProperty(ref this._yValuePadding, value)) + { + + } + + } + } + + private Range _xValuePadding; + public Range XValuePadding + { + get { return this._xValuePadding; } + set + { + if (SetProperty(ref this._xValuePadding, value)) + { + + } + + } + } + } +} \ No newline at end of file diff --git a/WPF/WpfFX/Demo/ViewModel/General/DemoDataViewModel.cs b/WPF/WpfFX/Demo/ViewModel/General/DemoDataViewModel.cs index ed6a366..6109b8a 100644 --- a/WPF/WpfFX/Demo/ViewModel/General/DemoDataViewModel.cs +++ b/WPF/WpfFX/Demo/ViewModel/General/DemoDataViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.ObjectModel; using System.Linq; +using System.Windows; using MvvmCharting.Common; using MvvmCharting; @@ -11,40 +12,88 @@ public class DemoDataViewModel : BindableBase public ObservableCollection ItemsSourceList { get; } - private bool _showSeriesLine = true; - public bool ShowSeriesLine + private bool _showLineSeries = true; + public bool ShowLineSeries { - get { return this._showSeriesLine; } + get { return this._showLineSeries; } set { - if (SetProperty(ref this._showSeriesLine, value)) + if (SetProperty(ref this._showLineSeries, value)) { foreach (var sr in this.ItemsSourceList) { - sr.ShowSeriesLine = value; + sr.ShowLineSeries = value; } } } } - private bool _showSeriesPoints = true; - public bool ShowSeriesPoints + private bool _showScatterSeries = true; + public bool ShowScatterSeries { - get { return this._showSeriesPoints; } + get { return this._showScatterSeries; } set { - if (SetProperty(ref this._showSeriesPoints, value)) + if (SetProperty(ref this._showScatterSeries, value)) { foreach (var sr in this.ItemsSourceList) { - sr.ShowSeriesPoints = value; + sr.ShowScatterSeries = value; } } } } + private bool _showAreaSeries = true; + public bool ShowAreaSeries + { + get { return this._showAreaSeries; } + set + { + if (SetProperty(ref this._showAreaSeries, value)) + { + foreach (var sr in this.ItemsSourceList) + { + sr.ShowAreaSeries = value; + } + } + + } + } + + private bool _showBarSeries = false; + public bool ShowBarSeries + { + get { return this._showBarSeries; } + set + { + if (SetProperty(ref this._showBarSeries, value)) + { + var chartValuePaddingViewModel = (ChartValuePaddingViewModel)Application.Current.Resources["ChartValuePaddingViewModel"]; + + + if (chartValuePaddingViewModel.XMinValuePadding < 0.5) + { + chartValuePaddingViewModel.XMinValuePadding = 0.5; + } + + if (chartValuePaddingViewModel.XMaxValuePadding < 0.5) + { + chartValuePaddingViewModel.XMaxValuePadding = 0.5; + } + + foreach (var sr in this.ItemsSourceList) + { + sr.ShowBarSeries = value; + } + } + + } + } + + public ObservableCollection AvailableScatterTemplates { get; } @@ -77,7 +126,7 @@ private SomePoint GetPoint(int i, double yOffset) { var v = i / 1.0; var y = Math.Abs(v) < 1e-10 ? 1 : Math.Sin(v) / v; - + var pt = new SomePoint(v, y + 0.25 + yOffset); return pt; @@ -176,7 +225,7 @@ public void RemoveItem() public DemoDataViewModel() { - AddListCommand = new DelegateCommand((o)=> AddList()); + AddListCommand = new DelegateCommand((o) => AddList()); RemoveListCommand = new DelegateCommand((o) => RemoveList()); AddItemCommand = new DelegateCommand((o) => AddItem()); RemoveItemCommand = new DelegateCommand((o) => RemoveItem()); diff --git a/WPF/WpfFX/Demo/ViewModel/General/SomePointList.cs b/WPF/WpfFX/Demo/ViewModel/General/SomePointList.cs index a6c58bc..824783a 100644 --- a/WPF/WpfFX/Demo/ViewModel/General/SomePointList.cs +++ b/WPF/WpfFX/Demo/ViewModel/General/SomePointList.cs @@ -38,20 +38,43 @@ public bool IsHighlighted set { SetProperty(ref _isHighlighted, value); } } - private bool _showSeriesLine = true; - public bool ShowSeriesLine + private bool _showLineSeries = true; + public bool ShowLineSeries { - get { return _showSeriesLine; } - set { SetProperty(ref _showSeriesLine, value); } + get { return this._showLineSeries; } + set { SetProperty(ref this._showLineSeries, value); } } - private bool _showSeriesPoints = true; - public bool ShowSeriesPoints + private bool _showAreaSeries = true; + public bool ShowAreaSeries { - get { return _showSeriesPoints; } + get { return this._showAreaSeries; } set { - SetProperty(ref _showSeriesPoints, value); + + SetProperty(ref this._showAreaSeries, value); + } + } + + private bool _showBarSeries = false; + public bool ShowBarSeries + { + get { return this._showBarSeries; } + set + { + + SetProperty(ref this._showBarSeries, value); + } + } + + + private bool _showScatterSeries = true; + public bool ShowScatterSeries + { + get { return this._showScatterSeries; } + set + { + SetProperty(ref this._showScatterSeries, value); } } diff --git a/WPF/WpfFX/MvvmChartWpfFX/Axis/AxisBase.cs b/WPF/WpfFX/MvvmChartWpfFX/Axis/AxisBase.cs index 4821b72..934d3f7 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/Axis/AxisBase.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/Axis/AxisBase.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; @@ -8,9 +9,12 @@ using System.Windows.Data; using MvvmCharting.Axis; using MvvmCharting.Common; +using MvvmCharting.Drawing; namespace MvvmCharting.WpfFX.Axis { + + /// /// The base class for and . /// @@ -26,6 +30,19 @@ static AxisBase() protected Grid PART_AxisItemsControl; + protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) + { + + base.OnRenderSizeChanged(sizeInfo); + + if (this.Orientation == AxisType.X && sizeInfo.WidthChanged || + this.Orientation == AxisType.Y && sizeInfo.HeightChanged) + { + UpdateAxisDrawingSettings(); + } + + } + public override void OnApplyTemplate() { base.OnApplyTemplate(); @@ -53,6 +70,7 @@ public override void OnApplyTemplate() } + protected IEnumerable GetAllAxisItems() { if (this.PART_AxisItemsControl == null) @@ -71,8 +89,6 @@ public string Title public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(AxisBase), new PropertyMetadata(null)); - - public Style TitleStyle { get { return (Style)GetValue(TitleStyleProperty); } @@ -97,8 +113,6 @@ private static void OnPlacementPropertyChange(DependencyObject d, DependencyProp private void OnPlacementChange() { - - this.AxisPlacementChanged?.Invoke(this); } @@ -131,9 +145,6 @@ public IValueConverter LabelTextConverter public static readonly DependencyProperty LabelTextConverterProperty = DependencyProperty.Register("LabelTextConverter", typeof(IValueConverter), typeof(AxisBase), new PropertyMetadata(null)); - - - public Style AxisItemStyle { get { return (Style)GetValue(AxisItemStyleProperty); } @@ -160,8 +171,6 @@ public IAxisOwner Owner } } - - private AxisType _orientation; public AxisType Orientation { @@ -200,52 +209,31 @@ private void AttachHandler() UpdateAxisDrawingSettings(); } - private void AxisBase_CanvasSettingChanged(IPlottingSettingsBase obj) + private void AxisBase_CanvasSettingChanged(PlottingRangeBase obj) { if (obj == null) { return; } - switch (obj.Orientation) - { - case AxisType.X: - this.RenderSize = new Size(obj.RenderSize, this.RenderSize.Height); - this.Margin = new Thickness(obj.Margin.X, this.Margin.Top, obj.Margin.Y, this.Margin.Bottom); - this.Padding = new Thickness(obj.Padding.X, this.Padding.Top, obj.Padding.Y, this.Padding.Bottom); - this.BorderThickness = new Thickness(obj.BorderThickness.X, this.BorderThickness.Top, obj.BorderThickness.Y, this.BorderThickness.Bottom); - - break; - case AxisType.Y: - this.RenderSize = new Size(this.RenderSize.Width, obj.RenderSize); - this.Margin = new Thickness(this.Margin.Left, obj.Margin.X, this.Margin.Right, obj.Margin.Y); - this.Padding = new Thickness(this.Padding.Left, obj.Padding.X, this.Padding.Right, obj.Padding.Y); - this.BorderThickness = new Thickness(this.BorderThickness.Left, obj.BorderThickness.X, this.BorderThickness.Right, obj.BorderThickness.Y); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - this.PlottingSetting = obj; - + this.PlottingRangeSetting = obj; } - private IPlottingSettingsBase _plottingSetting; - protected IPlottingSettingsBase PlottingSetting + private PlottingRangeBase _plottingRangeSetting; + protected PlottingRangeBase PlottingRangeSetting { - get { return this._plottingSetting; } + get { return this._plottingRangeSetting; } set { - if (this._plottingSetting != value ) + if (this._plottingRangeSetting != value ) { - this._plottingSetting = value; + this._plottingRangeSetting = value; UpdateAxisDrawingSettings(); } } } - private IAxisDrawingSettingsBase _drawingSettings; internal IAxisDrawingSettingsBase DrawingSettings { @@ -264,7 +252,6 @@ internal IAxisDrawingSettingsBase DrawingSettings } } - protected abstract void UpdateAxisDrawingSettings(); @@ -276,13 +263,8 @@ private void OnAxisItemCoordinateChanged() var coordinates = GetAxisItemCoordinates(); - this.Owner.OnAxisItemsCoordinateChanged(this.Orientation, coordinates); - - - - } protected IAxisDrawingSettingsBase _currentDrawingSettings; @@ -347,9 +329,7 @@ protected void DoUpdateAxisItems(IList source) item.SetBinding(AxisItem.LabelTextConverterProperty, new Binding(nameof(this.LabelTextConverter)){Source = this}); item.SetBinding(AxisItem.StyleProperty, new Binding(nameof(this.AxisItemStyle)) { Source = this }); item.SetBinding(AxisItem.PlacementProperty, new Binding(nameof(this.Placement)) { Source = this }); - //item.SetLabelTextConverter(this.LabelTextConverter); - //item.SetAxisPlacement(this.Placement); - //item.Style = this.AxisItemStyle; + this.PART_AxisItemsControl.Children.Add(item); } } diff --git a/WPF/WpfFX/MvvmChartWpfFX/Axis/AxisItem/AxisItem.cs b/WPF/WpfFX/MvvmChartWpfFX/Axis/AxisItem/AxisItem.cs index 8aa18d1..c37c3b4 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/Axis/AxisItem/AxisItem.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/Axis/AxisItem/AxisItem.cs @@ -26,6 +26,8 @@ static AxisItem() private static readonly string sPART_Label = "PART_Label"; protected FrameworkElement PART_Tick { get; private set; } private FrameworkElement PART_Label; + private double _coordinate; + public AxisItem() { UpdateLabelTextBinding(); @@ -84,24 +86,26 @@ public Brush TickStroke public double Coordinate { - get { return (double)this.GetValue(CoordinateProperty); } - set { this.SetValue(CoordinateProperty, value); } - } - public static readonly DependencyProperty CoordinateProperty = - DependencyProperty.Register("Coordinate", typeof(double), typeof(AxisItem), new PropertyMetadata(double.NaN, OnPositionPropertyChanged)); + get { return this._coordinate; } + set + { + if (this._coordinate != value) + { + this._coordinate = value; + OnPositionChanged(); + } - private static void OnPositionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((AxisItem)d).OnPositionChanged(); + } } + private void OnPositionChanged() { this.TryDoTranslateTransform(); } - + public IValueConverter LabelTextConverter { @@ -125,22 +129,6 @@ private void UpdateLabelTextBinding() this.SetBinding(AxisItem.LabelTextProperty, new Binding() { Converter = this.LabelTextConverter }); } - public object Value - { - get { return (object)GetValue(ValueProperty); } - set { SetValue(ValueProperty, value); } - } - public static readonly DependencyProperty ValueProperty = - DependencyProperty.Register("Value", typeof(object), typeof(AxisItem), new PropertyMetadata(null, OnValuePropertyChanged)); - - private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((AxisItem)d).UpdateLabelTextBinding(); - } - - - - public string LabelText { diff --git a/WPF/WpfFX/MvvmChartWpfFX/Axis/CategoryAxis.cs b/WPF/WpfFX/MvvmChartWpfFX/Axis/CategoryAxis.cs index d512203..b649cb3 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/Axis/CategoryAxis.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/Axis/CategoryAxis.cs @@ -10,6 +10,7 @@ namespace MvvmCharting.WpfFX.Axis { /// /// Represents a discrete axis for category data type. + /// Only x-axis(independent axis) can be CategoryAxis. /// public class CategoryAxis : AxisBase, ICategoryAxis { @@ -20,33 +21,27 @@ static CategoryAxis() protected override void UpdateAxisDrawingSettings() { - if (/*!this.IsLoaded ||*/ - this.PlottingSetting == null) + if (this.PlottingRangeSetting == null) { return; } - var plottingItemValues = ((ICategoryPlottingSettings)this.PlottingSetting).PlottingItemValues; - var length = this.PlottingSetting.GetAvailablePlottingSize(); - if (plottingItemValues == null || plottingItemValues.Count == 0) - { - throw new NotImplementedException(); - } - - var axisDrawingSettings = new CategoryAxisDrawingSettings(this.TickCount, plottingItemValues, length); + var length = this.Orientation == AxisType.X ? this.ActualWidth : this.ActualHeight; + var categoryPlottingSettings = (CategoryPlottingRange)this.PlottingRangeSetting; + var axisDrawingSettings = new CategoryAxisDrawingSettings(this.TickCount, categoryPlottingSettings, length); this.DrawingSettings = axisDrawingSettings; } public override IEnumerable GetAxisItemCoordinates() { - if (this.PlottingSetting == null) + if (this.PlottingRangeSetting == null) { return null; } - + var coordinates = this.PART_AxisItemsControl.Children.OfType() .Select(x => x.Coordinate).ToArray(); - + return coordinates; } @@ -65,27 +60,20 @@ protected override bool LoadAxisItems() this._currentDrawingSettings = drawingSettings; - IList dataValues = ((ICategoryAxisDrawingSettings) drawingSettings).PlottingItemValues;// GetItemValues(((ICategoryAxisDrawingSettings)drawingSettings).PlottingDataRange.Min, drawingSettings.ActualTickInterval, drawingSettings.ActualTickCount); + var dataValues = drawingSettings.GetPlottingValues().ToArray(); - DoUpdateAxisItems(dataValues.ToArray()); + DoUpdateAxisItems(dataValues); return true; } protected override void DoUpdateAxisItemsCoordinate() { - var list = ((ICategoryAxisDrawingSettings)this.DrawingSettings).PlottingItemValues; - var length = this.DrawingSettings.PlottingLength; - var uLen = length / list.Count; int i = 0; foreach (IAxisItem item in this.GetAllAxisItems()) { - var coordinate = (i + 0.5) * uLen; - if (this.Orientation == AxisType.Y) - { - coordinate = length - coordinate; - } + var coordinate = this.DrawingSettings.CalculateCoordinate(i + 0.5, this.Orientation); item.Coordinate = coordinate; diff --git a/WPF/WpfFX/MvvmChartWpfFX/Axis/NumericAxis.cs b/WPF/WpfFX/MvvmChartWpfFX/Axis/NumericAxis.cs index 11e41f7..2cd715c 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/Axis/NumericAxis.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/Axis/NumericAxis.cs @@ -6,8 +6,8 @@ using MvvmCharting.Common; namespace MvvmCharting.WpfFX.Axis -{ - +{ + /// /// Represents a numeric, linear axis. /// @@ -55,64 +55,46 @@ private void OnExplicitTicksChanged(IList oldValue, IList newVal protected override void UpdateAxisDrawingSettings() { - if (/*!this.IsLoaded ||*/ - this.PlottingSetting == null) + if (this.PlottingRangeSetting == null) { return; } - var range = ((INumericPlottingSettings) this.PlottingSetting).PlottingDataRange; - var length = this.PlottingSetting.GetAvailablePlottingSize(); - if (range.IsInvalid || length.IsNaNOrZero()) - { - throw new NotImplementedException(); - } + var numericPlottingSettings = (NumericPlottingRange)this.PlottingRangeSetting; + + + var length = this.Orientation == AxisType.X ? this.ActualWidth : this.ActualHeight; + - var axisDrawingSettings = new NumericAxisDrawingSettings(this.TickCount, this.TickInterval, range, length); + + var axisDrawingSettings = new NumericAxisDrawingSettings(this.TickCount, this.TickInterval, + numericPlottingSettings, length); this.DrawingSettings = axisDrawingSettings; } - + protected override void DoUpdateAxisItemsCoordinate() { - var span = ((INumericAxisDrawingSettings) this.DrawingSettings).PlottingDataRange.Span; - var length = this.DrawingSettings.PlottingLength; - var uLen = length / span; - + foreach (IAxisItem item in this.GetAllAxisItems()) { - var coordinate = ((double)item.DataContext - ((INumericAxisDrawingSettings) this.DrawingSettings).PlottingDataRange.Min) * uLen; - if (this.Orientation == AxisType.Y) - { - coordinate = length - coordinate; - } + var coordinate = this.DrawingSettings.CalculateCoordinate((double)item.DataContext, this.Orientation); item.Coordinate = coordinate; - - + } } - private IList GetItemValues(double startValue, double tickInterval, int tickCount) - { - var chartRange = ((INumericPlottingSettings) this.PlottingSetting).PlottingDataRange; - var arr = Enumerable.Range(0, tickCount) - .Select(i => startValue + i * tickInterval) - .Where(x => chartRange.IsInRange(x)) - .ToArray(); - - return arr; - } protected override bool LoadAxisItems() { - IList dataValues; + IList values; if (this.ExplicitTicks != null) { - dataValues = this.ExplicitTicks; + values = this.ExplicitTicks.Select(x=>(object)x).ToArray(); this._currentDrawingSettings = null; } else @@ -130,27 +112,25 @@ protected override bool LoadAxisItems() this._currentDrawingSettings = drawingSettings; - dataValues = GetItemValues(((INumericAxisDrawingSettings)drawingSettings).PlottingDataRange.Min, drawingSettings.ActualTickInterval, drawingSettings.ActualTickCount); - - + values = drawingSettings.GetPlottingValues().ToArray(); } - DoUpdateAxisItems(dataValues.Select(x=>(object)x).ToArray()); + DoUpdateAxisItems(values); return true; } public override IEnumerable GetAxisItemCoordinates() { - if (this.PlottingSetting == null) + if (this.PlottingRangeSetting == null) { return null; } - var range = ((INumericPlottingSettings) this.PlottingSetting).PlottingDataRange; - var coordinates = this.PART_AxisItemsControl.Children.OfType() - .Where(x => range.IsInRange((double)x.DataContext)).Select(x => x.Coordinate); - return coordinates; + return this.PART_AxisItemsControl.Children.OfType() + .Select(x => x.Coordinate); + + } } diff --git a/WPF/WpfFX/MvvmChartWpfFX/Chart.cs b/WPF/WpfFX/MvvmChartWpfFX/Chart.cs index 9c146f5..5ddf395 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/Chart.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/Chart.cs @@ -14,11 +14,14 @@ using MvvmCharting.Drawing; using MvvmCharting.GridLine; using MvvmCharting.Series; +using MvvmCharting.WpfFX.Series; namespace MvvmCharting.WpfFX { + + /// - /// A Cartesian 2D Chart + /// A Cartesian 2D Chart. /// This is the host for everything: SeriesChart, /// XAxis & YAxis, GridLineControl, Legend, CrossHair... /// @@ -45,7 +48,7 @@ static Chart() private static readonly string sPART_LegendHolder = "PART_LegendHolder"; - private SeriesControl PART_SeriesControl; + private SeriesCollectionControl Part_SeriesCollectionControl; private Grid PART_Root; private Grid PART_PlottingCanvas; //private SlimItemsControl PART_SeriesItemsControl; @@ -58,7 +61,7 @@ static Chart() public Chart() { - + } #region overrides @@ -66,22 +69,24 @@ public override void OnApplyTemplate() { base.OnApplyTemplate(); - this.PART_SeriesControl = (SeriesControl)this.GetTemplateChild("PART_SeriesControl"); - if (this.PART_SeriesControl != null) + this.Part_SeriesCollectionControl = (SeriesCollectionControl)this.GetTemplateChild("Part_SeriesCollectionControl"); + if (this.Part_SeriesCollectionControl != null) { - this.PART_SeriesControl.IsXAxisCategory = this.XAxis is ICategoryAxis; - - this.PART_SeriesControl.GlobalXValueRangeChanged += SeriesControlGlobalXValueRangeChanged; - this.PART_SeriesControl.GlobalYValueRangeChanged += SeriesControlGlobalYValueRangeChanged; + this.Part_SeriesCollectionControl.IsXAxisCategory = this.XAxis is ICategoryAxis; - this.PART_SeriesControl.SetBinding(SeriesControl.SeriesTemplateProperty, + this.Part_SeriesCollectionControl.GlobalXValueRangeChanged += SeriesCollectionControlGlobalXValueRangeChanged; + this.Part_SeriesCollectionControl.GlobalYValueRangeChanged += SeriesCollectionControlGlobalYValueRangeChanged; + + this.Part_SeriesCollectionControl.SetBinding(SeriesCollectionControl.SeriesTemplateProperty, new Binding(nameof(this.SeriesTemplate)) { Source = this }); - this.PART_SeriesControl.SetBinding(SeriesControl.SeriesTemplateSelectorProperty, + this.Part_SeriesCollectionControl.SetBinding(SeriesCollectionControl.SeriesTemplateSelectorProperty, new Binding(nameof(this.SeriesTemplateSelector)) { Source = this }); - this.PART_SeriesControl.SetBinding(SeriesControl.SeriesItemsSourceProperty, + this.Part_SeriesCollectionControl.SetBinding(SeriesCollectionControl.SeriesItemsSourceProperty, new Binding(nameof(this.SeriesItemsSource)) { Source = this }); - OnIsSeriesCollectionChangingChanged(); + this.Part_SeriesCollectionControl.StackMode = this.SeriesStackMode; + + OnIsChartUpdatingChanged(); } @@ -90,7 +95,7 @@ public override void OnApplyTemplate() OnXAxisPropertyChanged(null, this.XAxis); OnYAxisPropertyChanged(null, this.YAxis); - OnBackgroundImageChanged(null, this.BackgroundImage); + OnBackgroundImageChanged(null, this.BackgroundElement); this.PART_HorizontalCrossHair = (Line)GetTemplateChild(sPART_HorizontalCrossHair); if (this.PART_HorizontalCrossHair != null) @@ -124,14 +129,14 @@ public override void OnApplyTemplate() OnLegendChanged(); } - private void SeriesControlGlobalYValueRangeChanged(Range obj) + private void SeriesCollectionControlGlobalYValueRangeChanged(Range obj) { - UpdatePlottingYDataRange(); + UpdateActualPlottingYValueRange(); } - private void SeriesControlGlobalXValueRangeChanged(Range obj) + private void SeriesCollectionControlGlobalXValueRangeChanged(Range obj) { - UpdatePlottingXDataRange(); + UpdateActualPlottingXValueRange(); } protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) @@ -159,9 +164,9 @@ protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) //private void Sr_PropertyChanged(object sender, string propertyName) //{ // var sr = (ISeries)sender; - // if (propertyName == nameof(sr.IsHighLighted)) + // if (propertyName == nameof(sr.IsHighlighted)) // { - // this.Legend.OnItemHighlightChanged(sr.DataContext, sr.IsHighLighted); + // this.Legend.OnItemHighlightChanged(sr.DataContext, sr.IsHighlighted); // } //} @@ -215,7 +220,7 @@ public double XMaximum private static void OnXMinimumOrXMaximumPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - ((Chart)d).UpdatePlottingXDataRange(); + ((Chart)d).UpdateActualPlottingXValueRange(); } /// @@ -242,7 +247,7 @@ public double YMaximum private static void OnYMinimumOrYMaximumPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - ((Chart)d).UpdatePlottingYDataRange(); + ((Chart)d).UpdateActualPlottingYValueRange(); } #endregion @@ -254,7 +259,7 @@ public DataTemplate SeriesTemplate } public static readonly DependencyProperty SeriesTemplateProperty = - SeriesControl.SeriesTemplateProperty.AddOwner(typeof(Chart)); + SeriesCollectionControl.SeriesTemplateProperty.AddOwner(typeof(Chart)); public DataTemplateSelector SeriesTemplateSelector { @@ -262,12 +267,12 @@ public DataTemplateSelector SeriesTemplateSelector set { SetValue(SeriesTemplateSelectorProperty, value); } } public static readonly DependencyProperty SeriesTemplateSelectorProperty = - SeriesControl.SeriesTemplateSelectorProperty.AddOwner(typeof(Chart)); + SeriesCollectionControl.SeriesTemplateSelectorProperty.AddOwner(typeof(Chart)); #endregion #region SeriesItemsSource /// - /// Represents the data for a list of series(). + /// Represents the data for a list of series(). /// public IList SeriesItemsSource { @@ -275,120 +280,278 @@ public IList SeriesItemsSource set { SetValue(SeriesItemsSourceProperty, value); } } public static readonly DependencyProperty SeriesItemsSourceProperty = - SeriesControl.SeriesItemsSourceProperty.AddOwner(typeof(Chart)); + SeriesCollectionControl.SeriesItemsSourceProperty.AddOwner(typeof(Chart)); #endregion - #region Plotting Data Range - private Range _plottingXDataRange = Range.Empty; + #region SeriesStackMode + public StackMode SeriesStackMode + { + get { return (StackMode)GetValue(SeriesStackModeProperty); } + set { SetValue(SeriesStackModeProperty, value); } + } + public static readonly DependencyProperty SeriesStackModeProperty = + DependencyProperty.Register("SeriesStackMode", typeof(StackMode), typeof(Chart), new PropertyMetadata(StackMode.None, OnSeriesStackModePropertyChanged)); + + private static void OnSeriesStackModePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((Chart)d).OnSeriesStackModeChanged(); + } + + private void OnSeriesStackModeChanged() + { + BatchExecute(() => + { + + + Reset(); + + + + if (this.Part_SeriesCollectionControl != null) + { + this.Part_SeriesCollectionControl.StackMode = this.SeriesStackMode; + } + + + }); + } + + private void BatchExecute(Action action) + { + if (!this.IsChartUpdating) + { + this.SetCurrentValue(Chart.IsChartUpdatingProperty, true); + try + { + action.Invoke(); + } + finally + { + this.SetCurrentValue(Chart.IsChartUpdatingProperty, false); + } + } + else + { + action.Invoke(); + } + } + #endregion + + #region Plotting Range + /// + /// X value padding. excludes + /// is + /// + public Range PlottingXValuePadding + { + get { return (Range)GetValue(PlottingXValuePaddingProperty); } + set { SetValue(PlottingXValuePaddingProperty, value); } + } + public static readonly DependencyProperty PlottingXValuePaddingProperty = + DependencyProperty.Register("PlottingXValuePadding", typeof(Range), typeof(Chart), new PropertyMetadata(new Range(0, 0), OnPlottingXValuePaddingPropertyChanged)); + + private static void OnPlottingXValuePaddingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((Chart)d).UpdatePlottingXValueRange(); + } + + /// + /// Y value padding. excludes + /// is + /// + public Range PlottingYValuePadding + { + get { return (Range)GetValue(PlottingYValuePaddingProperty); } + set { SetValue(PlottingYValuePaddingProperty, value); } + } + public static readonly DependencyProperty PlottingYValuePaddingProperty = + DependencyProperty.Register("PlottingYValuePadding", typeof(Range), typeof(Chart), new PropertyMetadata(new Range(0, 0), OnPlottingYValuePaddingPropertyChanged)); + + private static void OnPlottingYValuePaddingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((Chart)d).UpdatePlottingYValueRange(); + } + + + private Range _plottingXValueRange = Range.Empty; /// - /// The final independent value range(min & max) used to plot chart + /// The entire independent value range(min & max) used to plot chart + /// including /// - public Range PlottingXDataRange + public Range PlottingXValueRange { get { - return this._plottingXDataRange; + return this._plottingXValueRange; } set { - if (this._plottingXDataRange != value) + if (this._plottingXValueRange != value) { - this._plottingXDataRange = value; - TryUpdatePlottingSettings(AxisType.X); + this._plottingXValueRange = value; + - this.PART_SeriesControl.SetPlottingValueRange(Orientation.Horizontal, value); - //OnPlottingXDataRangeChanged(); + this.Part_SeriesCollectionControl.SetPlottingValueRange(Orientation.Horizontal, + this.PlottingXValueRange); + + TryUpdatePlottingSettings(AxisType.X); } } } - private Range _plottingYDataRange = Range.Empty; + private Range _plottingYValueRange = Range.Empty; /// - /// The final dependent value range(min & max) used to plot chart + /// The entire dependent value range(min & max) used to plot chart + /// including /// - public Range PlottingYDataRange + public Range PlottingYValueRange { - get { return this._plottingYDataRange; } + get { return this._plottingYValueRange; } set { - if (this._plottingYDataRange != value) + if (this._plottingYValueRange != value) { - this._plottingYDataRange = value; + this._plottingYValueRange = value; TryUpdatePlottingSettings(AxisType.Y); - this.PART_SeriesControl.SetPlottingValueRange(Orientation.Vertical, value); + this.Part_SeriesCollectionControl.SetPlottingValueRange(Orientation.Vertical, + this.PlottingYValueRange); + + + } + } + } + + private Range _actualPlottingXValueRange = Range.Empty; + public Range ActualPlottingXValueRange + { + get { return _actualPlottingXValueRange; } + set + { + if (_actualPlottingXValueRange != value) + { + _actualPlottingXValueRange = value; + UpdatePlottingXValueRange(); + } + + } + } - // OnPlottingYDataRangeChanged(); + private Range _actualPlottingYValueRange = Range.Empty; + public Range ActualPlottingYValueRange + { + get { return _actualPlottingYValueRange; } + set + { + if (_actualPlottingYValueRange != value) + { + _actualPlottingYValueRange = value; + UpdatePlottingYValueRange(); } + + } + } + + private void UpdatePlottingXValueRange() + { + var range = new Range(this.ActualPlottingXValueRange.Min - this.PlottingXValuePadding.Min, + this.ActualPlottingXValueRange.Max + this.PlottingXValuePadding.Max); + + if (range.Span <= 0) + { + throw new MvvmChartException($"Invalid XValueRange: {range}"); + } + this.PlottingXValueRange = range; + } + + private void UpdatePlottingYValueRange() + { + + var range = new Range(this.ActualPlottingYValueRange.Min - this.PlottingYValuePadding.Min, + this.ActualPlottingYValueRange.Max + this.PlottingYValuePadding.Max); + + if (range.Span <= 0) + { + throw new MvvmChartException($"Invalid YValueRange: {range}"); } + this.PlottingYValueRange = range; } - private void UpdatePlottingXDataRange() + private void UpdateActualPlottingXValueRange() { - if (this.PART_SeriesControl == null) + if (this.Part_SeriesCollectionControl == null) { - this.PlottingXDataRange = Range.Empty; + this.PlottingXValueRange = Range.Empty; return; } - double min = !this.XMinimum.IsNaN() ? this.XMinimum : this.PART_SeriesControl.GlobalXValueRange.Min; - double max = !this.XMaximum.IsNaN() ? this.XMaximum : this.PART_SeriesControl.GlobalXValueRange.Max; + double min = !this.XMinimum.IsNaN() ? this.XMinimum : this.Part_SeriesCollectionControl.GlobalXValueRange.Min; + double max = !this.XMaximum.IsNaN() ? this.XMaximum : this.Part_SeriesCollectionControl.GlobalXValueRange.Max; if (min.IsNaN() && max.IsNaN()) { - this.PlottingXDataRange = Range.Empty; + this.ActualPlottingXValueRange = Range.Empty; return; } - if (this.PlottingXDataRange.IsEmpty || - !this.PlottingXDataRange.Min.NearlyEqual(min) || - !this.PlottingXDataRange.Max.NearlyEqual(max)) - { - this.PlottingXDataRange = new Range(min, max); + this.ActualPlottingXValueRange = new Range(min, max); - } } - protected virtual void UpdatePlottingYDataRange() + private void UpdateActualPlottingYValueRange() { - if (this.PART_SeriesControl == null) + if (this.Part_SeriesCollectionControl == null) { - this.PlottingXDataRange = Range.Empty; + this.PlottingXValueRange = Range.Empty; return; } - double min = !this.YMinimum.IsNaN() ? this.YMinimum : this.PART_SeriesControl.GlobalYValueRange.Min; - double max = !this.YMaximum.IsNaN() ? this.YMaximum : this.PART_SeriesControl.GlobalYValueRange.Max; + + double min = !this.YMinimum.IsNaN() ? this.YMinimum : this.Part_SeriesCollectionControl.GlobalYValueRange.Min; + double max = !this.YMaximum.IsNaN() ? this.YMaximum : this.Part_SeriesCollectionControl.GlobalYValueRange.Max; if (min.IsNaN() && max.IsNaN()) { - this.PlottingYDataRange = Range.Empty; + this.ActualPlottingYValueRange = Range.Empty; return; } - if (this.PlottingYDataRange.IsEmpty || - this.PlottingYDataRange.Min != min || - this.PlottingYDataRange.Max != max) + this.ActualPlottingYValueRange = GetProperPlottingYRange(new Range(min, max)); + + } + + private Range GetProperPlottingYRange(Range newRange) + { + + switch (this.SeriesStackMode) { + case StackMode.None: + return newRange; + + case StackMode.Stacked: + return new Range(0.00, newRange.Max); - this.PlottingYDataRange = new Range(min, max); + case StackMode.Stacked100: + return new Range(0.00, 1.00); + + default: + throw new ArgumentOutOfRangeException(); } } + #endregion #region PlottingSettings - public event Action HorizontalSettingChanged; - public event Action VerticalSettingChanged; - - private PlottingSettingsBase _horizontalPlottingSetting; - private PlottingSettingsBase _verticalPlottingSetting; + public event Action HorizontalSettingChanged; + public event Action VerticalSettingChanged; + private PlottingRangeBase _horizontalPlottingRangeSetting; + private PlottingRangeBase _verticalPlottingRangeSetting; - - private PlottingSettingsBase GetPlottingSettings(AxisType orientation) + private PlottingRangeBase GetPlottingSettings(AxisType orientation) { if (this.PART_PlottingCanvas == null) { @@ -396,22 +559,21 @@ private PlottingSettingsBase GetPlottingSettings(AxisType orientation) return null; } - double length; - PointNS magrin, pading, borderThickness; - Range plotingDataRange; + Range plotingDataRange, valuePadding; IList plottingItemValues = null; - bool isCategory = false; + bool isCategory; switch (orientation) { case AxisType.X: + if (this.XAxis == null) + { + return null; + } + valuePadding = this.PlottingXValuePadding; isCategory = this.XAxis is ICategoryAxis; - length = this.PART_PlottingCanvas.ActualWidth; - magrin = new PointNS(this.Margin.Left, this.Margin.Right); - pading = new PointNS(this.Padding.Left, this.Padding.Right); - borderThickness = new PointNS(this.BorderThickness.Left, this.BorderThickness.Right); - plotingDataRange = this.PlottingXDataRange; + plotingDataRange = this.ActualPlottingXValueRange; - var sr = this.PART_SeriesControl.GetSeries().FirstOrDefault(); + var sr = this.Part_SeriesCollectionControl.GetSeries().FirstOrDefault(); plottingItemValues = sr?.ItemsSource.OfType().Select(x => sr.GetXRawValueForItem(x)) .ToArray(); @@ -419,38 +581,36 @@ private PlottingSettingsBase GetPlottingSettings(AxisType orientation) case AxisType.Y: + if (this.YAxis == null) + { + return null; + } + valuePadding = this.PlottingYValuePadding; isCategory = this.YAxis is ICategoryAxis; - length = this.PART_PlottingCanvas.ActualHeight; - magrin = new PointNS(this.Margin.Top, this.Margin.Bottom); - pading = new PointNS(this.Padding.Top, this.Padding.Bottom); - borderThickness = new PointNS(this.BorderThickness.Top, this.BorderThickness.Bottom); - plotingDataRange = this.PlottingYDataRange; + plotingDataRange = this.ActualPlottingYValueRange; break; default: throw new ArgumentOutOfRangeException(nameof(orientation), orientation, null); } - - var isValid = NumericPlottingSettings.Validate(length, magrin, pading, borderThickness, plotingDataRange); - - if (isValid) + if (!plotingDataRange.IsInvalid) { if (isCategory) { - + if (plottingItemValues == null || plottingItemValues.Count == 0 || plottingItemValues.Count != plottingItemValues.Distinct().Count()) { return null; } - - return new CategoryPlottingSettings(orientation, length, magrin, pading, borderThickness, plottingItemValues); + + return new CategoryPlottingRange(plottingItemValues, valuePadding); } else { - return new NumericPlottingSettings(orientation, length, magrin, pading, borderThickness, plotingDataRange); + return new NumericPlottingRange(plotingDataRange, valuePadding); } } @@ -473,16 +633,16 @@ private void TryUpdatePlottingSettings(AxisType orientation) switch (orientation) { case AxisType.X: - if (this._horizontalPlottingSetting != newValue) + if (this._horizontalPlottingRangeSetting != newValue) { - this._horizontalPlottingSetting = newValue; + this._horizontalPlottingRangeSetting = newValue; this.HorizontalSettingChanged?.Invoke(newValue); } break; case AxisType.Y: - if (this._verticalPlottingSetting != newValue) + if (this._verticalPlottingRangeSetting != newValue) { - this._verticalPlottingSetting = newValue; + this._verticalPlottingRangeSetting = newValue; this.VerticalSettingChanged?.Invoke(newValue); } break; @@ -558,11 +718,11 @@ private void OnXAxisPropertyChanged(IAxisNS oldValue, IAxisNS newValue) if (newValue != null) { - if (this.PART_SeriesControl!=null) + if (this.Part_SeriesCollectionControl != null) { - this.PART_SeriesControl.IsXAxisCategory = newValue is ICategoryAxis; + this.Part_SeriesCollectionControl.IsXAxisCategory = newValue is ICategoryAxis; } - + if (this.PART_Root.Children.Contains(newValue as UIElement)) { @@ -678,13 +838,13 @@ private void OnAxisPlacementChanged(IAxisNS obj) /// Represents pluggable background UIElement. /// User can plug any UIElement(include image) here. /// - public UIElement BackgroundImage + public UIElement BackgroundElement { - get { return (UIElement)GetValue(BackgroundImageProperty); } - set { SetValue(BackgroundImageProperty, value); } + get { return (UIElement)GetValue(BackgroundElementProperty); } + set { SetValue(BackgroundElementProperty, value); } } - public static readonly DependencyProperty BackgroundImageProperty = - DependencyProperty.Register("BackgroundImage", typeof(UIElement), typeof(Chart), new PropertyMetadata(null, OnBackgroundImagePropertyChanged)); + public static readonly DependencyProperty BackgroundElementProperty = + DependencyProperty.Register("BackgroundElement", typeof(UIElement), typeof(Chart), new PropertyMetadata(null, OnBackgroundImagePropertyChanged)); private static void OnBackgroundImagePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { @@ -843,9 +1003,7 @@ private void OnLegendChanged() { if (this.Legend != null) { - this.Legend.ItemsSource = this.SeriesItemsSource; - this.Legend.LegendItemTemplate = this.LegendItemTemplate; - //this.Legend.LegendItemHighlighChanged += Legend_LegendItemHighlighChanged; + this.Legend.SetBinding(LegendControl.LegendItemTemplateProperty, new Binding(nameof(this.LegendItemTemplate)) { Source = this }); this.Legend.SetBinding(LegendControl.ItemsSourceProperty, new Binding(nameof(this.SeriesItemsSource)) { Source = this }); } @@ -862,7 +1020,7 @@ private void OnLegendChanged() // if (sr != null) // { - // sr.IsHighLighted = newValue; + // sr.IsHighlighted = newValue; // } //} @@ -872,20 +1030,9 @@ public DataTemplate LegendItemTemplate set { SetValue(LegendItemTemplateProperty, value); } } public static readonly DependencyProperty LegendItemTemplateProperty = - DependencyProperty.Register("LegendItemTemplate", typeof(DataTemplate), typeof(Chart), new PropertyMetadata(null, OnLegendItemTemplatePropertyChanged)); + DependencyProperty.Register("LegendItemTemplate", typeof(DataTemplate), typeof(Chart), new PropertyMetadata(null)); - private static void OnLegendItemTemplatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((Chart)d).OnLegendItemTemplateChanged(); - } - private void OnLegendItemTemplateChanged() - { - if (this.Legend != null) - { - this.Legend.LegendItemTemplate = this.LegendItemTemplate; - } - } @@ -902,35 +1049,33 @@ public Visibility LegendVisibility #endregion - - - public bool IsSeriesCollectionChanging + #region IsChartUpdating + /// + /// When collection or chart setting is changing, set this to true can + /// reduce some performance-hitting, duplicated operation. + /// + public bool IsChartUpdating { - get { return (bool)GetValue(IsSeriesCollectionChangingProperty); } - set { SetValue(IsSeriesCollectionChangingProperty, value); } + get { return (bool)GetValue(IsChartUpdatingProperty); } + set { SetValue(IsChartUpdatingProperty, value); } } - public static readonly DependencyProperty IsSeriesCollectionChangingProperty = - DependencyProperty.Register("IsSeriesCollectionChanging", typeof(bool), typeof(Chart), new PropertyMetadata(false, OnIsSeriesCollectionChangingPropertyChanged)); + public static readonly DependencyProperty IsChartUpdatingProperty = + DependencyProperty.Register("IsChartUpdating", typeof(bool), typeof(Chart), new PropertyMetadata(false, OnIsChartUpdatingPropertyChanged)); - private static void OnIsSeriesCollectionChangingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnIsChartUpdatingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - ((Chart)d).OnIsSeriesCollectionChangingChanged(); + ((Chart)d).OnIsChartUpdatingChanged(); } - private void OnIsSeriesCollectionChangingChanged() + private void OnIsChartUpdatingChanged() { - if (this.PART_SeriesControl != null) + if (this.Part_SeriesCollectionControl != null) { - this.PART_SeriesControl.IsSeriesCollectionChanging = this.IsSeriesCollectionChanging; - - if (!this.IsSeriesCollectionChanging) - { - this.PART_SeriesControl.UpdateSeriesCoordinates(); - - } + this.Part_SeriesCollectionControl.IsSeriesCollectionChanging = this.IsChartUpdating; + this.Part_SeriesCollectionControl.Refresh(); } } - + #endregion /// /// Called when x-axis or y-axis has updated the coordinates of its items. @@ -941,6 +1086,27 @@ public void OnAxisItemsCoordinateChanged(AxisType orientation, IEnumerable + /// Used to display the grid lines of a + /// [TemplatePart(Name = "sPART_HorizontalGridLines", Type = typeof(Grid))] [TemplatePart(Name = "PART_VerticalGridLines", Type = typeof(Grid))] public class GridLineControl : Control, IGridLineControl diff --git a/WPF/WpfFX/MvvmChartWpfFX/GridLine/GridLineStyle.xaml b/WPF/WpfFX/MvvmChartWpfFX/GridLineControl/GridLineStyle.xaml similarity index 100% rename from WPF/WpfFX/MvvmChartWpfFX/GridLine/GridLineStyle.xaml rename to WPF/WpfFX/MvvmChartWpfFX/GridLineControl/GridLineStyle.xaml diff --git a/WPF/WpfFX/MvvmChartWpfFX/Legend/LegendItemControl.cs b/WPF/WpfFX/MvvmChartWpfFX/Legend/LegendItemControl.cs deleted file mode 100644 index fd4c0c6..0000000 --- a/WPF/WpfFX/MvvmChartWpfFX/Legend/LegendItemControl.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; - -namespace MvvmCharting.WpfFX -{ - public class LegendItemControl : Control - { - static LegendItemControl() - { - DefaultStyleKeyProperty.OverrideMetadata(typeof(LegendItemControl), new FrameworkPropertyMetadata(typeof(LegendItemControl))); - } - - public event Action PropertyChanged; - - protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) - { - base.OnPropertyChanged(e); - if (e.Property == Control.IsMouseOverProperty) - { - this.IsHighlighted = this.IsMouseOver; - } - else if (e.Property == LegendItemControl.IsHighlightedProperty) - { - this.PropertyChanged?.Invoke(this, nameof(this.IsHighlighted)); - } - } - - - public Brush IndicatorBrush - { - get { return (Brush)GetValue(IndicatorBrushProperty); } - set { SetValue(IndicatorBrushProperty, value); } - } - public static readonly DependencyProperty IndicatorBrushProperty = - DependencyProperty.Register("IndicatorBrush", typeof(Brush), typeof(LegendItemControl), new PropertyMetadata(null)); - - - - public bool IsHighlighted - { - get { return (bool)GetValue(IsHighlightedProperty); } - set { SetValue(IsHighlightedProperty, value); } - } - public static readonly DependencyProperty IsHighlightedProperty = - DependencyProperty.Register("IsHighlighted", typeof(bool), typeof(LegendItemControl), new PropertyMetadata(false)); - - - public bool IsSelected - { - get { return (bool)GetValue(IsSelectedProperty); } - set { SetValue(IsSelectedProperty, value); } - } - public static readonly DependencyProperty IsSelectedProperty = - DependencyProperty.Register("IsSelected", typeof(bool), typeof(LegendItemControl), new PropertyMetadata(false)); - - } -} \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/Legend/LegendControl.cs b/WPF/WpfFX/MvvmChartWpfFX/LegendControl/LegendControl.cs similarity index 64% rename from WPF/WpfFX/MvvmChartWpfFX/Legend/LegendControl.cs rename to WPF/WpfFX/MvvmChartWpfFX/LegendControl/LegendControl.cs index ee519b4..8aa5713 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/Legend/LegendControl.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/LegendControl/LegendControl.cs @@ -1,23 +1,14 @@ using System; using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; namespace MvvmCharting.WpfFX { + /// + /// Used to display the legend of + /// [TemplatePart(Name = "PART_LegendItemsControl", Type = typeof(SlimItemsControl))] public class LegendControl : Control { @@ -26,38 +17,19 @@ static LegendControl() DefaultStyleKeyProperty.OverrideMetadata(typeof(LegendControl), new FrameworkPropertyMetadata(typeof(LegendControl))); } - public event Action LegendItemHighlighChanged; - + private SlimItemsControl PART_LegendItemsControl; public override void OnApplyTemplate() { base.OnApplyTemplate(); this.PART_LegendItemsControl = (SlimItemsControl)this.GetTemplateChild("PART_LegendItemsControl"); - if (this.PART_LegendItemsControl!=null) - { - this.PART_LegendItemsControl.ElementGenerated += PART_ItemsControl_ElementGenerated; - } + } - private void PART_ItemsControl_ElementGenerated(object arg1, FrameworkElement treeRoot, int index) - { - var legendItemControl = treeRoot as LegendItemControl; - if (legendItemControl != null) - { - legendItemControl.PropertyChanged += LegendItemControl_PropertyChanged; - } - } + - private void LegendItemControl_PropertyChanged(object sender, string propName) - { - if (propName == "IsHighlighted") - { - var legendItem = (LegendItemControl)sender; - - this.LegendItemHighlighChanged?.Invoke(legendItem, legendItem.IsHighlighted); - } - } + public IList ItemsSource { diff --git a/WPF/WpfFX/MvvmChartWpfFX/LegendControl/LegendItemControl.cs b/WPF/WpfFX/MvvmChartWpfFX/LegendControl/LegendItemControl.cs new file mode 100644 index 0000000..095f203 --- /dev/null +++ b/WPF/WpfFX/MvvmChartWpfFX/LegendControl/LegendItemControl.cs @@ -0,0 +1,31 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace MvvmCharting.WpfFX +{ + public class LegendItemControl : InteractiveControl + { + static LegendItemControl() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(LegendItemControl), new FrameworkPropertyMetadata(typeof(LegendItemControl))); + } + + + + public Brush IndicatorBrush + { + get { return (Brush)GetValue(IndicatorBrushProperty); } + set { SetValue(IndicatorBrushProperty, value); } + } + public static readonly DependencyProperty IndicatorBrushProperty = + DependencyProperty.Register("IndicatorBrush", typeof(Brush), typeof(LegendItemControl), new PropertyMetadata(null)); + + + + + + + } +} \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/Legend/LegendStyle.xaml b/WPF/WpfFX/MvvmChartWpfFX/LegendControl/LegendStyle.xaml similarity index 100% rename from WPF/WpfFX/MvvmChartWpfFX/Legend/LegendStyle.xaml rename to WPF/WpfFX/MvvmChartWpfFX/LegendControl/LegendStyle.xaml diff --git a/WPF/WpfFX/MvvmChartWpfFX/MvvmChartWpfFX.csproj b/WPF/WpfFX/MvvmChartWpfFX/MvvmChartWpfFX.csproj index 28fe503..df3244d 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/MvvmChartWpfFX.csproj +++ b/WPF/WpfFX/MvvmChartWpfFX/MvvmChartWpfFX.csproj @@ -72,54 +72,67 @@ - + - - - - + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - + + + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + + Designer + MSBuild:Compile + + Designer MSBuild:Compile - + Designer MSBuild:Compile - + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + Designer MSBuild:Compile - + Designer MSBuild:Compile @@ -127,7 +140,7 @@ MSBuild:Compile Designer - + Code diff --git a/WPF/WpfFX/MvvmChartWpfFX/Series/ISeriesHost.cs b/WPF/WpfFX/MvvmChartWpfFX/Series/ISeriesHost.cs deleted file mode 100644 index bbba038..0000000 --- a/WPF/WpfFX/MvvmChartWpfFX/Series/ISeriesHost.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; - -namespace MvvmCharting.WpfFX -{ - public interface ISeriesHost - { - IEnumerable GetSeries(); - int SeriesCount { get; } - - bool IsSeriesCollectionChanging { get; } - bool IsXAxisCategory { get;} - } -} \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/Series/PathSeries.cs b/WPF/WpfFX/MvvmChartWpfFX/Series/PathSeries.cs deleted file mode 100644 index 03d0f19..0000000 --- a/WPF/WpfFX/MvvmChartWpfFX/Series/PathSeries.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Windows; -using System.Windows.Media; -using System.Windows.Shapes; -using MvvmCharting.Common; -using MvvmCharting.Drawing; -using MvvmCharting.Series; - -namespace MvvmCharting.WpfFX -{ - /// - /// PathSeries just use a Path to draw the series. - /// This is the generic series type which can be customized to create almost any shape. - /// To achieve this, just simply pass a ISeriesGeometryBuilder object to the GeometryBuilder property. - /// By default, the GeometryBuilder property is set to a PolyLineGeometryBuilder. - /// - [TemplatePart(Name = "PART_Shape", Type = typeof(Shape))] - public class PathSeries : SeriesBase - { - static PathSeries() - { - DefaultStyleKeyProperty.OverrideMetadata(typeof(PathSeries), new FrameworkPropertyMetadata(typeof(PathSeries))); - } - - public override void OnApplyTemplate() - { - base.OnApplyTemplate(); - OnPathDataChanged(); - } - - - - /// - /// cache the created Geometry object - /// - private Geometry _pathData; - - - private void OnPathDataChanged() - { - if (this.PART_Shape != null) - { - ((Path)this.PART_Shape).Data = this._pathData; - } - - } - - public ISeriesGeometryBuilder GeometryBuilder - { - get { return (ISeriesGeometryBuilder)GetValue(GeometryBuilderProperty); } - set { SetValue(GeometryBuilderProperty, value); } - } - public static readonly DependencyProperty GeometryBuilderProperty = - DependencyProperty.Register("GeometryBuilder", typeof(ISeriesGeometryBuilder), typeof(PathSeries), new PropertyMetadata(new PolyLineGeometryBuilder(), OnGeometryProviderPropertyChanged)); - - private static void OnGeometryProviderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((PathSeries)d).UpdateLineOrArea(); - } - - - /// - /// This should be called when GeometryBuilder, Mode or coordinates changed - /// - protected override void UpdateLineOrArea() - { - if (this.Owner?.IsSeriesCollectionChanging == true) - { - return; - } - - if (this.GeometryBuilder == null || - this.PART_Shape == null || - this.ItemsSource == null || - this.ItemsSource.Count == 0 || - this.RenderSize.IsInvalid() || - !this.IsLoaded) - { - return; - } - - var coordinates = this.GetCoordinates(); - if (coordinates.Length < 2) - { - this._pathData = Geometry.Empty; - OnPathDataChanged(); - return; - } - - PointNS[] previous; - - switch (this.SeriesMode) - { - case WpfFX.SeriesMode.Line: - previous = null; - break; - case WpfFX.SeriesMode.Area: - previous = new[] { new PointNS(coordinates.First().X, 0), new PointNS(coordinates.Last().X, 0) }; - break; - case WpfFX.SeriesMode.StackedArea: - case WpfFX.SeriesMode.StackedArea100: - var ls = this.Owner.GetSeries().ToArray(); - var index = Array.IndexOf(ls, this); - if (index == 0) - { - previous = new[] { new PointNS(coordinates.First().X, 0), new PointNS(coordinates.Last().X, 0) }; - } - else - { - previous = ls[index - 1].GetCoordinates(); - - if (previous.Length != coordinates.Length) - { - throw new MvvmChartException($"previous.Length({previous.Length}) != coordinates.Length({coordinates.Length})"); - } - } - - break; - default: - throw new ArgumentOutOfRangeException(); - } - - var geometry = coordinates == null - ? Geometry.Empty - : (Geometry)this.GeometryBuilder.GetGeometry(coordinates, previous); - - - this._pathData = geometry; - OnPathDataChanged(); - } - } -} \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/Series/Scatter/Scatter2.cs b/WPF/WpfFX/MvvmChartWpfFX/Series/Scatter/Scatter2.cs deleted file mode 100644 index 830be3b..0000000 --- a/WPF/WpfFX/MvvmChartWpfFX/Series/Scatter/Scatter2.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System.Windows; -using System.Windows.Markup; -using System.Windows.Media; -using System.Windows.Shapes; -using MvvmCharting.Drawing; -using MvvmCharting.Series; -using Path = System.Windows.Shapes.Path; - -namespace MvvmCharting.WpfFX -{ - /// - /// Yet another Scatter type, which inherited from Shape directly. - /// It is effectively just a path, so it is lightweight and has better performance - /// compare to - /// - [ContentProperty(nameof(Data))] - public class Scatter2 : Shape, IScatter - { - #region overrides - protected override Geometry DefiningGeometry - { - get - { - return this.Data ?? Geometry.Empty; - } - } - - protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) - { - base.OnRenderSizeChanged(sizeInfo); - UpdateAdjustedCoordinate(); - } - #endregion - - public Scatter2() - { - - this.HorizontalAlignment = HorizontalAlignment.Left; - this.VerticalAlignment = VerticalAlignment.Top; - - UpdateScatterGeometry(); - } - - - /// - /// Gets or sets a that specifies the shape to be drawn. - /// - /// A description of the shape to be drawn. - public Geometry Data - { - get - { - return (Geometry)this.GetValue(Scatter2.DataProperty); - } - set - { - this.SetValue(Scatter2.DataProperty, (object)value); - } - } - public static readonly DependencyProperty DataProperty = - Path.DataProperty.AddOwner(typeof(Scatter2), new FrameworkPropertyMetadata(null, - FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender)); - - - - public IScatterGeometryBuilder GeometryBuilder - { - get { return (IScatterGeometryBuilder)GetValue(GeometryBuilderProperty); } - set { SetValue(GeometryBuilderProperty, value); } - } - public static readonly DependencyProperty GeometryBuilderProperty = - DependencyProperty.Register("GeometryBuilder", typeof(IScatterGeometryBuilder), typeof(Scatter2), new PropertyMetadata(null, OnGeometryBuilderPropertyChanged)); - - private static void OnGeometryBuilderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((Scatter2)d).UpdateScatterGeometry(); - } - - private void UpdateScatterGeometry() - { - if (this.GeometryBuilder == null) - { - return; - } - - - this.Data = (Geometry) this.GeometryBuilder?.GetGeometry(); - - - - } - - - public PointNS Coordinate - { - get { return (PointNS)GetValue(CoordinateProperty); } - set { SetValue(CoordinateProperty, value); } - } - public static readonly DependencyProperty CoordinateProperty = - DependencyProperty.Register("Coordinate", typeof(PointNS), typeof(Scatter2), new PropertyMetadata(PointNSHelper.EmptyPoint, OnCoordinatePropertyChanged)); - - private static void OnCoordinatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((Scatter2)d).OnCoordinateChanged((PointNS)e.NewValue); - } - private void OnCoordinateChanged(PointNS newValue) - { - UpdateAdjustedCoordinate(); - } - - /// - /// When the render size of a Scatter is changed, we should - /// adjust it coordinates by some offset. - /// - /// - public virtual PointNS GetOffsetForSizeChangedOverride() - { - return new PointNS(-ActualWidth / 2, -ActualHeight / 2); - } - - private void UpdateAdjustedCoordinate() - { - if (this.Coordinate.IsEmpty()) - { - return; - } - - var offset = GetOffsetForSizeChangedOverride(); - - if (offset.IsEmpty()) - { - return; - } - - var x = this.Coordinate.X + offset.X; - var y = this.Coordinate.Y + offset.Y; - - - //if (!double.IsInfinity(x)) - //{ - // Canvas.SetLeft(this, x); - //} - - - //if (!double.IsInfinity(y)) - //{ - // Canvas.SetTop(this, y); - //} - - var translateTransform = this.RenderTransform as TranslateTransform; - if (translateTransform == null) - { - this.RenderTransform = new TranslateTransform(x, y); - } - else - { - translateTransform.Y = y; - translateTransform.X = x; - } - } - - - } - - - - -} \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/Series/SeriesBase.cs b/WPF/WpfFX/MvvmChartWpfFX/Series/SeriesBase.cs deleted file mode 100644 index b8bed7d..0000000 --- a/WPF/WpfFX/MvvmChartWpfFX/Series/SeriesBase.cs +++ /dev/null @@ -1,923 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Specialized; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Media; -using System.Windows.Shapes; -using MvvmCharting.Common; -using MvvmCharting.Drawing; -using MvvmCharting.Series; - -namespace MvvmCharting.WpfFX -{ - /// - /// The base class for all series. It implements so it - /// can be put at the root of the SeriesTemplate of a - /// - [TemplatePart(Name = "PART_DataPointItemsControl", Type = typeof(SlimItemsControl))] - [TemplatePart(Name = "PART_Shape", Type = typeof(Shape))] - public abstract class SeriesBase : Control, ISeries - { - private static readonly string sPART_Shape = "PART_Shape"; - private static readonly string sPART_ScatterItemsControl = "PART_DataPointItemsControl"; - - public event Action XRangeChanged; - public event Action YRangeChanged; - - public event Action PropertyChanged; - - internal ISeriesHost Owner { get; set; } - - /// - /// used to draw Scatters - /// - private SlimItemsControl PART_ScatterItemsControl; - - /// - /// used to draw a curve or area - /// - protected Shape PART_Shape { get; private set; } - - protected SeriesBase() - { - this.Loaded += SeriesBase_Loaded; - - } - - private void SeriesBase_Loaded(object sender, RoutedEventArgs e) - { - - UpdatePixelPerUnit(Orientation.Horizontal); - UpdatePixelPerUnit(Orientation.Vertical); - - RecalculateCoordinate(); - } - - #region overrides - public override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - this.PART_Shape = (Shape)GetTemplateChild(sPART_Shape); - if (this.PART_Shape != null) - { - this.PART_Shape.Visibility = this.IsLineOrAreaVisible ? Visibility.Visible : Visibility.Collapsed; - UpdateFill(); - } - - - if (this.PART_ScatterItemsControl != null) - { - this.PART_ScatterItemsControl.ElementGenerated -= ScatterItemsControlScatterGenerated; - - } - - this.PART_ScatterItemsControl = (SlimItemsControl)GetTemplateChild(sPART_ScatterItemsControl); - if (this.PART_ScatterItemsControl != null) - { - this.PART_ScatterItemsControl.ElementGenerated += ScatterItemsControlScatterGenerated; - this.PART_ScatterItemsControl.Visibility = this.IsScatterVisible ? Visibility.Visible : Visibility.Collapsed; - this.PART_ScatterItemsControl.ItemsSource = this.ItemsSource; - this.PART_ScatterItemsControl.ItemTemplateSelector = this.ScatterTemplateSelector; - this.PART_ScatterItemsControl.ItemTemplate = this.ScatterTemplate; - - } - - - - } - - protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) - { - base.OnRenderSizeChanged(sizeInfo); - - if (sizeInfo.WidthChanged) - { - UpdatePixelPerUnit(Orientation.Horizontal); - } - - if (sizeInfo.HeightChanged) - { - UpdatePixelPerUnit(Orientation.Vertical); - } - - - RecalculateCoordinate(); - } - - protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) - { - base.OnPropertyChanged(e); - if (e.Property == Control.IsMouseOverProperty) - { - this.IsHighLighted = this.IsMouseOver; - } - else if (e.Property == SeriesBase.IsHighLightedProperty) - { - this.PropertyChanged?.Invoke(this, nameof(this.IsHighLighted)); - } - - - } - #endregion - - private void ScatterItemsControlScatterGenerated(object sender, FrameworkElement root, int index) - { - var scatter = (IScatter)root; - if (scatter == null) - { - throw new MvvmChartException("The root element in the ScatterTemplate must implement IScatter interface."); - } - - var item = scatter.DataContext; - - if (!this.Owner.IsSeriesCollectionChanging) - { - if (!this._xPixelPerUnit.IsNaN() && !this._yPixelPerUnit.IsNaN()) - { - scatter.Coordinate = GetPlotCoordinateForItem(item, index); - } - } - } - - #region SeriesShapeType - public SeriesMode SeriesMode - { - get { return (SeriesMode)GetValue(SeriesModeProperty); } - set { SetValue(SeriesModeProperty, value); } - } - public static readonly DependencyProperty SeriesModeProperty = - DependencyProperty.Register("SeriesMode", typeof(SeriesMode), typeof(SeriesBase), new PropertyMetadata(SeriesMode.Line, OnSeriesModePropertyChange)); - - private static void OnSeriesModePropertyChange(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((SeriesBase)d).OnSeriesModeChange((SeriesMode)e.OldValue, (SeriesMode)e.NewValue); - } - - private void OnSeriesModeChange(SeriesMode oldValue, SeriesMode newValue) - { - var valueRangeUnchanged = oldValue == SeriesMode.Line && newValue == SeriesMode.Area || - oldValue == SeriesMode.Area && newValue == SeriesMode.Line; - - this.IsAreaMode = newValue != SeriesMode.Line; - - UpdateFill(); - - if (valueRangeUnchanged) - { - UpdateLineOrArea(); - return; - } - - UpdateValueRange(); - UpdatePixelPerUnit(Orientation.Horizontal); - UpdatePixelPerUnit(Orientation.Vertical); - - - RecalculateCoordinate(); - - } - - private void EnsureYValuePositive(IList list) - { - if (list == null) - { - return; - } - - if (this.SeriesMode == SeriesMode.Line || - this.SeriesMode == SeriesMode.Area) - { - return; - } - - foreach (var item in list) - { - var y = GetYValueForItem(item); - if (y < 0) - { - throw new MvvmChartModelDataException($"Item value of {this.SeriesMode} series cannot be negative!"); - } - } - } - - private void ValidateData() - { - if (!this.Owner.IsXAxisCategory && (this.SeriesMode == SeriesMode.Line || this.SeriesMode == SeriesMode.Area)) - { - return; - } - - var seriesCount = this.Owner.SeriesCount; - var seriesList = this.Owner.GetSeries(); - int stackedAreaSrCt = 0; - int stackedArea100SrCt = 0; - foreach (var sr in seriesList) - { - if (sr.SeriesMode == SeriesMode.StackedArea) - { - stackedAreaSrCt++; - } - - if (sr.SeriesMode == SeriesMode.StackedArea100) - { - stackedArea100SrCt++; - } - } - - if (stackedAreaSrCt != 0 && stackedAreaSrCt != seriesCount) - { - throw new MvvmChartException($"Series in a Chart must all or no one in '{SeriesMode.StackedArea}' mode!"); - } - - if (stackedArea100SrCt != 0 && stackedArea100SrCt != seriesCount) - { - throw new MvvmChartException($"Series in a Chart must all or no one in '{SeriesMode.StackedArea100}' mode!"); - } - - SeriesMode? mode = (stackedAreaSrCt == seriesCount) - ? SeriesMode.StackedArea - : (stackedArea100SrCt == seriesCount ? SeriesMode.StackedArea100 : (SeriesMode?)null); - - string strReason = mode != null ? $"In {mode} mode" : "If the XAxis of a Chart is CategoryAxis"; - SeriesBase prev = null; - foreach (var sr in seriesList) - { - if (sr.ItemsSource == null) - { - continue; - } - - if (prev == null) - { - prev = sr; - continue; - } - - if (sr.ItemsSource.Count != this.ItemsSource.Count) - { - throw new MvvmChartModelDataException($"{strReason}, the item count of all series in a Chart must be same!"); - } - - for (int i = 0; i < this.ItemsSource.Count; i++) - { - if (this.Owner.IsXAxisCategory) - { - var x1 = GetXRawValueForItem(sr.ItemsSource[i]); - var x2 = GetXRawValueForItem(prev.ItemsSource[i]); - if (x1 == null || x2 == null) - { - throw new MvvmChartModelDataException("An Item's X value of the series ItemsSource cannot be null!"); - } - - if (!x1.Equals(x2)) - { - throw new MvvmChartModelDataException($"{strReason}, the item's x value in the same index of all series in a Chart must be same!"); - } - } - else - { - var x1 = GetXValueForItem(sr.ItemsSource[i]); - var x2 = GetXValueForItem(prev.ItemsSource[i]); - - if (!x1.NearlyEqual(x2)) - { - throw new MvvmChartModelDataException($"{strReason}, the item's x value in the same index of all series in a Chart must be same!"); - } - } - - } - - - } - } - - private void UpdateFill() - { - if (this.PART_Shape != null) - { - if (this.SeriesMode == SeriesMode.Line) - { - this.PART_Shape.Fill = null; - } - else - { - this.PART_Shape.SetBinding(Shape.FillProperty, new Binding(nameof(this.Stroke)) { Source = this }); - } - - } - } - - public bool IsAreaMode - { - get { return (bool)GetValue(IsAreaModeProperty); } - set { SetValue(IsAreaModeProperty, value); } - } - public static readonly DependencyProperty IsAreaModeProperty = - DependencyProperty.Register("IsAreaMode", typeof(bool), typeof(SeriesBase), new PropertyMetadata(false)); - #endregion - - #region IndependentValueProperty & DependentValueProperty properties - public string IndependentValueProperty - { - get { return (string)GetValue(IndependentValuePropertyProperty); } - set { SetValue(IndependentValuePropertyProperty, value); } - } - public static readonly DependencyProperty IndependentValuePropertyProperty = - DependencyProperty.Register("IndependentValueProperty", typeof(string), typeof(SeriesBase), new PropertyMetadata(null)); - - - public string DependentValueProperty - { - get { return (string)GetValue(DependentValuePropertyProperty); } - set { SetValue(DependentValuePropertyProperty, value); } - } - public static readonly DependencyProperty DependentValuePropertyProperty = - DependencyProperty.Register("DependentValueProperty", typeof(string), typeof(SeriesBase), new PropertyMetadata(null)); - #endregion - - #region IsScatterVisible & IsLineOrAreaVisible properties - public bool IsScatterVisible - { - get { return (bool)GetValue(IsScatterVisibleProperty); } - set { SetValue(IsScatterVisibleProperty, value); } - } - public static readonly DependencyProperty IsScatterVisibleProperty = - DependencyProperty.Register("IsScatterVisible", typeof(bool), typeof(SeriesBase), new PropertyMetadata(true, OnIsSeriesPointsVisiblePropertyChanged)); - - private static void OnIsSeriesPointsVisiblePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((SeriesBase)d).OnIsSeriesPointsVisibleChanged(); - } - - private void OnIsSeriesPointsVisibleChanged() - { - if (this.PART_ScatterItemsControl != null) - { - - this.PART_ScatterItemsControl.Visibility = this.IsScatterVisible ? Visibility.Visible : Visibility.Collapsed; - - } - } - - public bool IsLineOrAreaVisible - { - get { return (bool)GetValue(IsLineOrAreaVisibleProperty); } - set { SetValue(IsLineOrAreaVisibleProperty, value); } - } - public static readonly DependencyProperty IsLineOrAreaVisibleProperty = - DependencyProperty.Register("IsLineOrAreaVisible", typeof(bool), typeof(SeriesBase), new PropertyMetadata(true, OnIsLineOrAreaVisiblePropertyChanged)); - - private static void OnIsLineOrAreaVisiblePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((SeriesBase)d).OnIsLineOrAreaVisibleChanged(); - } - - private void OnIsLineOrAreaVisibleChanged() - { - if (this.PART_Shape != null) - { - this.PART_Shape.Visibility = this.IsLineOrAreaVisible ? Visibility.Visible : Visibility.Collapsed; - } - } - #endregion - - #region Shape properties - public Brush Stroke - { - get { return (Brush)GetValue(StrokeProperty); } - set { SetValue(StrokeProperty, value); } - } - public static readonly DependencyProperty StrokeProperty = - Shape.StrokeProperty.AddOwner(typeof(SeriesBase)); - - public double StrokeThickness - { - get { return (double)GetValue(StrokeThicknessProperty); } - set { SetValue(StrokeThicknessProperty, value); } - } - - public static readonly DependencyProperty StrokeThicknessProperty = - Shape.StrokeThicknessProperty.AddOwner(typeof(SeriesBase), new PropertyMetadata(1.0)); - - public bool IsHighLighted - { - get { return (bool)GetValue(IsHighLightedProperty); } - set { SetValue(IsHighLightedProperty, value); } - } - public static readonly DependencyProperty IsHighLightedProperty = - DependencyProperty.Register("IsHighLighted", typeof(bool), typeof(SeriesBase), new PropertyMetadata(false)); - - public bool IsSelected - { - get { return (bool)GetValue(IsSelectedProperty); } - set { SetValue(IsSelectedProperty, value); } - } - public static readonly DependencyProperty IsSelectedProperty = - DependencyProperty.Register("IsSelected", typeof(bool), typeof(SeriesBase), new PropertyMetadata(false)); - - - #endregion - - #region ItemsSource property and handlers - - - /// - /// Represents the data for a series. - /// Currently can only handle numerical(& DateTime, DataTimeOffset) data. - /// NOTE: It will be the user's responsibility to keep the ItemsSource sorted - /// by Independent property(x value) ascendingly! - /// - public IList ItemsSource - { - get { return (IList)GetValue(ItemsSourceProperty); } - set { SetValue(ItemsSourceProperty, value); } - } - public static readonly DependencyProperty ItemsSourceProperty = - DependencyProperty.Register("ItemsSource", typeof(IList), typeof(SeriesBase), new PropertyMetadata(null, OnItemsSourcePropertyChanged)); - - private static void OnItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - SeriesBase c = (SeriesBase)d; - - c.OnItemsSourceChanged((IList)e.OldValue, (IList)e.NewValue); - - } - - private void OnItemsSourceChanged(IList oldValue, IList newValue) - { - ValidateData(); - - if (this.PART_ScatterItemsControl != null) - { - this.PART_ScatterItemsControl.ItemsSource = this.ItemsSource; - } - - - if (oldValue is INotifyCollectionChanged oldItemsSource) - { - WeakEventManager - .RemoveHandler(oldItemsSource, "CollectionChanged", ItemsSource_CollectionChanged); - } - - if (newValue != null) - { - - UpdateValueRange(); - RecalculateCoordinate(); - - if (newValue is INotifyCollectionChanged newItemsSource) - { - WeakEventManager - .AddHandler(newItemsSource, "CollectionChanged", ItemsSource_CollectionChanged); - } - } - - } - - private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if (e.Action == NotifyCollectionChangedAction.Move) - { - return; - } - - if (!this.Owner.IsSeriesCollectionChanging) - { - //if (this.Owner.IsXAxisCategory) - //{ - // throw new MvvmChartModelDataException($"Collection change of ItemsSource is not allowed when XAxis is CategoryAxis and {nameof(this.Owner.IsSeriesCollectionChanging)} is false!"); - //} - - if (this.SeriesMode == SeriesMode.StackedArea || this.SeriesMode == SeriesMode.StackedArea100) - { - throw new MvvmChartModelDataException($"Collection change of ItemsSource is not allowed in {this.SeriesMode} mode when {nameof(this.Owner.IsSeriesCollectionChanging)} is false!"); - } - } - - if (e.Action == NotifyCollectionChangedAction.Reset) - { - //this._dataPointViewModels?.Clear(); - } - - HandleItemsSourceCollectionChange(e.OldItems, e.NewItems); - } - - private void HandleItemsSourceCollectionChange(IList oldValue, IList newValue) - { - EnsureYValuePositive(newValue); - - UpdateValueRange(); - RecalculateCoordinate(); - } - - public void UpdateValueRange() - { - if (this.Owner.IsSeriesCollectionChanging) - { - return; - } - - if (this.ItemsSource == null || - this.ItemsSource.Count == 0) - { - this.XValueRange = Range.Empty; - this.YValueRange = Range.Empty; - - return; - } - - double minY = double.MaxValue; - double maxY = double.MinValue; - - for (int i = 0; i < this.ItemsSource.Count; i++) - { - var item = this.ItemsSource[i]; - var y = GetAdjustYValueForItem(item, i); - - minY = Math.Min(minY, y); - maxY = Math.Max(maxY, y); - } - - double minX; - double maxX; - if (!this.Owner.IsXAxisCategory) - { - minX = GetXValueForItem(this.ItemsSource[0]); - maxX = GetXValueForItem(this.ItemsSource[this.ItemsSource.Count - 1]); - - } - else - { - minX = 0; - maxX = this.ItemsSource.Count; - } - - this.XValueRange = new Range(minX, maxX); - this.YValueRange = new Range(minY, maxY); - - //Debug.WriteLine(this.DataContext + $"...UpdateValueRange...XValueRange={XValueRange}, YValueRange={YValueRange}"); - } - - - private double GetXValueForItem(object item) - { - var t = item.GetType(); - var x = t.GetProperty(this.IndependentValueProperty).GetValue(item); - - return DoubleValueConverter.ObjectToDouble(x); - - } - - internal object GetXRawValueForItem(object item) - { - var t = item.GetType(); - var x = t.GetProperty(this.IndependentValueProperty).GetValue(item); - - return x; - - } - - private double GetYValueForItem(object item) - { - var t = item.GetType(); - var yProp = t.GetProperty(this.DependentValueProperty); - var y = yProp.GetValue(item); - - return DoubleValueConverter.ObjectToDouble(y); - - } - - protected virtual double GetAdjustYValueForItem(object item, int index) - { - switch (this.SeriesMode) - { - case SeriesMode.Line: - case SeriesMode.Area: - return GetYValueForItem(item); ; - case SeriesMode.StackedArea: - double total = 0; - foreach (var sr in this.Owner.GetSeries()) - { - var obj = sr.ItemsSource[index]; - var y = GetYValueForItem(obj); - total += y; - if (obj == item) - { - break; - } - - - } - - return total; - - case SeriesMode.StackedArea100: - bool meet = false; - total = 0; - double sum = 0; - foreach (var sr in this.Owner.GetSeries()) - { - var obj = sr.ItemsSource[index]; - total += GetYValueForItem(obj); - - if (!meet) - { - sum = total; - } - - if (obj == item) - { - meet = true; - } - - } - - return sum / total; - default: - throw new ArgumentOutOfRangeException(); - } - - - - - } - #endregion - - #region ItemTemplate & ItemTemplateSelector properties - public DataTemplate ScatterTemplate - { - get { return (DataTemplate)GetValue(ScatterTemplateProperty); } - set { SetValue(ScatterTemplateProperty, value); } - } - public static readonly DependencyProperty ScatterTemplateProperty = - DependencyProperty.Register("ScatterTemplate", typeof(DataTemplate), typeof(SeriesBase), new PropertyMetadata(null, OnScatterTemplatePropertyChanged)); - - private static void OnScatterTemplatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((SeriesBase)d).OnScatterTemplatePropertyChanged(); - - } - - private void OnScatterTemplatePropertyChanged() - { - - if (this.PART_ScatterItemsControl != null) - { - this.PART_ScatterItemsControl.ItemTemplate = this.ScatterTemplate; - } - - } - - public DataTemplateSelector ScatterTemplateSelector - { - get { return (DataTemplateSelector)GetValue(ScatterTemplateSelectorProperty); } - set { SetValue(ScatterTemplateSelectorProperty, value); } - } - public static readonly DependencyProperty ScatterTemplateSelectorProperty = - DependencyProperty.Register("ScatterTemplateSelector", typeof(DataTemplateSelector), typeof(SeriesBase), new PropertyMetadata(null, OnScatterDataTemplateSelectorPropertyChanged)); - - private static void OnScatterDataTemplateSelectorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((SeriesBase)d).OnScatterDataTemplateSelectorChanged(); - } - - private void OnScatterDataTemplateSelectorChanged() - { - if (this.PART_ScatterItemsControl != null) - { - this.PART_ScatterItemsControl.ItemTemplateSelector = this.ScatterTemplateSelector; - } - } - #endregion - - #region value range - private Range _yValueRange = Range.Empty; - /// - /// The min & max of the dependent value - /// - public Range YValueRange - { - get { return this._yValueRange; } - private set - { - if (this._yValueRange != value) - { - this._yValueRange = value; - - this.YRangeChanged?.Invoke(this, this.YValueRange); - - } - - } - } - - private Range _xValueRange = Range.Empty; - /// - /// The min & max of the dependent value - /// - public Range XValueRange - { - get { return this._xValueRange; } - private set - { - if (this._xValueRange != value) - { - this._xValueRange = value; - this.XRangeChanged?.Invoke(this, this.XValueRange); - - - } - } - } - #endregion - - #region plotting value range - private Range _plottingXValueRange = Range.Empty; - /// - /// The final X value range used to plot the chart - /// - protected Range PlottingXValueRange - { - get { return this._plottingXValueRange; } - set - { - if (this._plottingXValueRange != value) - { - this._plottingXValueRange = value; - UpdatePixelPerUnit(Orientation.Horizontal); - - RecalculateCoordinate(); - } - } - } - - private Range _plottingYValueRange = Range.Empty; - /// - /// The final Y value range used to plot the chart - /// - protected Range PlottingYValueRange - { - get { return this._plottingYValueRange; } - set - { - if (this._plottingYValueRange != value) - { - this._plottingYValueRange = value; - UpdatePixelPerUnit(Orientation.Vertical); - - RecalculateCoordinate(); - - } - } - } - - public virtual void OnPlottingXValueRangeChanged(Range newValue) - { - this.PlottingXValueRange = newValue; - } - - public virtual void OnPlottingYValueRangeChanged(Range newValue) - { - this.PlottingYValueRange = newValue; - } - #endregion - - #region Coordinates calculating - - private double _xPixelPerUnit { get; set; } - private double _yPixelPerUnit { get; set; } - - private void UpdatePixelPerUnit(Orientation orientation) - { - switch (orientation) - { - case Orientation.Horizontal: - - if (this.PlottingXValueRange.IsInvalid || - this.RenderSize.IsInvalid()) - { - this._xPixelPerUnit = double.NaN; - return; - } - - this._xPixelPerUnit = this.RenderSize.Width / this.PlottingXValueRange.Span; - break; - case Orientation.Vertical: - - if (this.PlottingYValueRange.IsInvalid || - this.RenderSize.IsInvalid()) - { - this._yPixelPerUnit = double.NaN; - - return; - } - - this._yPixelPerUnit = this.RenderSize.Height / this.PlottingYValueRange.Span; - break; - default: - throw new ArgumentOutOfRangeException(nameof(orientation), orientation, null); - } - - - } - - private PointNS GetPlotCoordinateForItem(object item, int itemIndex) - { - double x; - - if (!this.Owner.IsXAxisCategory) - { - x = GetXValueForItem(item); - - } - else - { - x = itemIndex + 0.5; - } - - var y = GetAdjustYValueForItem(item, itemIndex); - - var pt = new PointNS((x - this.PlottingXValueRange.Min) * this._xPixelPerUnit, - (y - this.PlottingYValueRange.Min) * this._yPixelPerUnit); - - return pt; - } - - /// - /// This should be call when: 1) ItemsSource; 2) x or y PixelPerUnit, and 3) RenderSize changed. - /// This method will first update the _coordinateCache and the Coordinate of each scatter, - /// then update the shape of the Line or Area - /// - internal void RecalculateCoordinate() - { - if (this.Owner.IsSeriesCollectionChanging) - { - return; - } - - UpdatePixelPerUnit(Orientation.Horizontal); - UpdatePixelPerUnit(Orientation.Vertical); - - if (this._xPixelPerUnit.IsNaN() || - this._yPixelPerUnit.IsNaN() || - this.ItemsSource == null || - this.ItemsSource.Count == 0 || - !this.IsLoaded) - { - return; - } - - Array.Resize(ref this._coordinateCache, this.ItemsSource.Count); - - for (int i = 0; i < this.ItemsSource.Count; i++) - { - var item = this.ItemsSource[i]; - - var pt = GetPlotCoordinateForItem(item, i); - - this._coordinateCache[i] = pt; - - var fe = this.PART_ScatterItemsControl?.TryGetChildForItem(item); - - if (fe != null) - { - var scatter = (IScatter)fe; - scatter.Coordinate = pt; - } - } - - //Debug.WriteLine(this.DataContext + "...RecalculateCoordinate..._coordinateCache=" + - //string.Join(",", this._coordinateCache.Select(x => x.Y.ToString("F0")))); - UpdateLineOrArea(); - } - - /// - /// cache the coordinate for performance - /// - private PointNS[] _coordinateCache; - internal PointNS[] GetCoordinates() - { - return this._coordinateCache; - } - #endregion - - /// - /// Update the Data of path(line or area). This should be called after - /// coordinates is calculated and is updated - /// - protected abstract void UpdateLineOrArea(); - - - - - } - - - -} diff --git a/WPF/WpfFX/MvvmChartWpfFX/Series/SeriesMode.cs b/WPF/WpfFX/MvvmChartWpfFX/Series/SeriesMode.cs deleted file mode 100644 index b5305c3..0000000 --- a/WPF/WpfFX/MvvmChartWpfFX/Series/SeriesMode.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MvvmCharting.WpfFX -{ - public enum SeriesMode - { - Line, - Area, - StackedArea, - StackedArea100 - } -} \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/Series/SpecificSeries/PolyLineSeries.cs b/WPF/WpfFX/MvvmChartWpfFX/Series/SpecificSeries/PolyLineSeries.cs deleted file mode 100644 index 17b7de6..0000000 --- a/WPF/WpfFX/MvvmChartWpfFX/Series/SpecificSeries/PolyLineSeries.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Windows; - -namespace MvvmCharting.WpfFX -{ - /// - /// A handy class to draw poly line series. - /// - public class PolyLineSeries : PathSeries - { - static PolyLineSeries() - { - DefaultStyleKeyProperty.OverrideMetadata(typeof(PolyLineSeries), new FrameworkPropertyMetadata(typeof(PolyLineSeries))); - } - - - } -} \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/Series/SpecificSeries/SplineSeries.cs b/WPF/WpfFX/MvvmChartWpfFX/Series/SpecificSeries/SplineSeries.cs deleted file mode 100644 index d619636..0000000 --- a/WPF/WpfFX/MvvmChartWpfFX/Series/SpecificSeries/SplineSeries.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Windows; - -namespace MvvmCharting.WpfFX -{ - /// - /// A handy class to draw spline series. - /// - public class SplineSeries : PathSeries - { - static SplineSeries() - { - DefaultStyleKeyProperty.OverrideMetadata(typeof(SplineSeries), new FrameworkPropertyMetadata(typeof(SplineSeries))); - } - - } -} \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/Series/SpecificSeries/StepLineSeries.cs b/WPF/WpfFX/MvvmChartWpfFX/Series/SpecificSeries/StepLineSeries.cs deleted file mode 100644 index cac7981..0000000 --- a/WPF/WpfFX/MvvmChartWpfFX/Series/SpecificSeries/StepLineSeries.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Windows; - -namespace MvvmCharting.WpfFX -{ - /// - /// A handy class to draw step poly line series. - /// - public class StepLineSeries : PathSeries - { - static StepLineSeries() - { - DefaultStyleKeyProperty.OverrideMetadata(typeof(StepLineSeries), new FrameworkPropertyMetadata(typeof(StepLineSeries))); - } - - - } -} \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/SeriesCollectionControl/SeriesCollectionControl.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesCollectionControl/SeriesCollectionControl.cs new file mode 100644 index 0000000..878549f --- /dev/null +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesCollectionControl/SeriesCollectionControl.cs @@ -0,0 +1,489 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using MvvmCharting.Axis; +using MvvmCharting.Common; +using MvvmCharting.Series; + +namespace MvvmCharting.WpfFX.Series +{ + + /// + /// Used to hold a collection of series + /// + [TemplatePart(Name = "PART_SeriesItemsControl", Type = typeof(SlimItemsControl))] + public class SeriesCollectionControl : Control, ISeriesControlOwner + { + private static readonly string sPART_SeriesItemsControl = "PART_SeriesItemsControl"; + static SeriesCollectionControl() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(SeriesCollectionControl), new FrameworkPropertyMetadata(typeof(SeriesCollectionControl))); + } + + private SlimItemsControl PART_SeriesItemsControl; + + private bool _isXAxisCategory; + public bool IsXAxisCategory + { + get { return this._isXAxisCategory; } + internal set + { + if (this._isXAxisCategory != value) + { + this._isXAxisCategory = value; + + if (value) + { + ValidateData(); + + } + } + } + } + + public bool IsSeriesCollectionChanging { get; internal set; } + + private int GetSeriesIndex(ISeriesControl seriesControl) + { + var item = seriesControl.DataContext; + if (this.itemIndexMap.TryGetValue(item, out int index)) + { + return index; + } + + return -1; + } + + private ISeriesControl GetSeriesHost(int index) + { + var item = this.SeriesItemsSource[index]; + return (ISeriesControl)this.PART_SeriesItemsControl?.TryGetChildForItem(item); + } + + public ISeriesControl GetPreviousSeriesHost(ISeriesControl current) + { + int index = GetSeriesIndex(current); + if (index <= 0) + { + return null; + } + + return GetSeriesHost(index - 1); + } + + private int SeriesCount => this.PART_SeriesItemsControl?.ItemCount ?? 0; + + + public IEnumerable GetSeries() + { + if (this.PART_SeriesItemsControl == null) + { + return Enumerable.Empty(); + } + + return this.PART_SeriesItemsControl.GetChildren().OfType(); + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (this.PART_SeriesItemsControl != null) + { + this.PART_SeriesItemsControl.ElementGenerated -= SeriesItemTemplateApplied; + } + + this.PART_SeriesItemsControl = (SlimItemsControl)GetTemplateChild(sPART_SeriesItemsControl); + + if (this.PART_SeriesItemsControl != null) + { + this.PART_SeriesItemsControl.ElementGenerated += SeriesItemTemplateApplied; + //this.PART_SeriesItemsControl.ItemAdded += PART_SeriesItemsControl_ItemAdded; + this.PART_SeriesItemsControl.ChildRemoved += PartSeriesChildrenControlChildRemoved; + //this.PART_SeriesItemsControl.ItemReplaced += PART_SeriesItemsControl_ItemReplaced; + this.PART_SeriesItemsControl.Reset += PART_SeriesItemsControl_Reset; + + this.PART_SeriesItemsControl.SetBinding(SlimItemsControl.ItemTemplateProperty, + new Binding(nameof(this.SeriesTemplate)) { Source = this }); + this.PART_SeriesItemsControl.SetBinding(SlimItemsControl.ItemTemplateSelectorProperty, + new Binding(nameof(this.SeriesTemplateSelector)) { Source = this }); + this.PART_SeriesItemsControl.SetBinding(SlimItemsControl.ItemsSourceProperty, + new Binding(nameof(this.SeriesItemsSource)) { Source = this }); + } + + + } + + private void PART_SeriesItemsControl_Reset(object obj) + { + UpdateGlobalValueRange(); + } + + private void PartSeriesChildrenControlChildRemoved(object arg1, FrameworkElement arg2) + { + UpdateGlobalValueRange(); + } + + private void SeriesItemTemplateApplied(object sender, DependencyObject root, int index) + { + if (root == null) + { + return; + } + + var sr = root as SeriesControl; + if (sr == null) + { + MvvmChartException.ThrowDataTemplateRootElementException(nameof(this.SeriesTemplate), typeof(SeriesControl)); + + } + + sr.Owner = this; + + sr.XRangeChanged += Sr_XValueRangeChanged; + sr.YRangeChanged += Sr_YValueRangeChanged; + + sr.OnPlottingXValueRangeChanged(this.XPlottingRange); + sr.OnPlottingYValueRangeChanged(this.YPlottingRange); + + //sr.UpdateValueRange(); + ((SeriesControl)sr).Refresh(); + + } + + private void ValidateData() + { + foreach (var seriesHost in this.GetSeries()) + { + seriesHost.ValidateData(); + } + + //if (!this.IsXAxisCategory && (this.StackMode == StackMode.None)) + //{ + // return; + //} + + //string strReason = this.StackMode != StackMode.None ? $"In {this.StackMode} mode" : "If the XAxis of a Chart is CategoryAxis"; + //SeriesHost prev = null; + //foreach (var seriesHost in this.GetSeries()) + //{ + // if (seriesHost.ItemsSource == null) + // { + // continue; + // } + + // if (prev == null) + // { + // prev = seriesHost; + // continue; + // } + + // if (seriesHost.ItemsSource.Count != prev.ItemsSource.Count) + // { + // throw new MvvmChartModelDataException($"{strReason}, the item count of all series in a Chart must be same!"); + // } + + // for (int i = 0; i < seriesHost.ItemsSource.Count; i++) + // { + // if (this.IsXAxisCategory) + // { + // var x1 = seriesHost.GetXRawValueForItem(seriesHost.ItemsSource[i]); + // var x2 = seriesHost.GetXRawValueForItem(prev.ItemsSource[i]); + // if (x1 == null || x2 == null) + // { + // throw new MvvmChartModelDataException("An Item's X value of the series ItemsSource cannot be null!"); + // } + + // if (!x1.Equals(x2)) + // { + // throw new MvvmChartModelDataException($"{strReason}, the item's x value in the same index of all series in a Chart must be same!"); + // } + // } + // else + // { + // var x1 = seriesHost.GetXValueForItem(seriesHost.ItemsSource[i]); + // var x2 = seriesHost.GetXValueForItem(prev.ItemsSource[i]); + + // if (!x1.NearlyEqual(x2)) + // { + // throw new MvvmChartModelDataException($"{strReason}, the item's x value in the same index of all series in a Chart must be same!"); + // } + // } + + // } + + + //} + } + + internal void Refresh() + { + foreach (var seriesHost in this.GetSeries()) + { + seriesHost.Refresh(); + } + } + + #region SeriesItemsSource + /// + /// Represents the data for a list of series(). + /// + public IList SeriesItemsSource + { + get { return (IList)GetValue(SeriesItemsSourceProperty); } + set { SetValue(SeriesItemsSourceProperty, value); } + } + public static readonly DependencyProperty SeriesItemsSourceProperty = + DependencyProperty.Register("SeriesItemsSource", typeof(IList), typeof(SeriesCollectionControl), new PropertyMetadata(null, OnSeriesItemsSourcePropertyChanged)); + + private static void OnSeriesItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((SeriesCollectionControl)d).OnSeriesItemsSourceChanged((IList)e.OldValue, (IList)e.NewValue); + } + + + + private void OnSeriesItemsSourceChanged(IList oldValue, IList newValue) + { + ValidateData(); + + RefreshItemIndexMap(); + + if (oldValue is INotifyCollectionChanged oldItemsSource) + { + WeakEventManager + .AddHandler(oldItemsSource, "CollectionChanged", SeriesItemsSource_CollectionChanged); + } + + if (newValue is INotifyCollectionChanged newItemsSource) + { + WeakEventManager + .AddHandler(newItemsSource, "CollectionChanged", SeriesItemsSource_CollectionChanged); + } + } + + private void SeriesItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + RefreshItemIndexMap(); + } + + + private readonly Dictionary itemIndexMap = new Dictionary(); + private void RefreshItemIndexMap() + { + this.itemIndexMap.Clear(); + for (int i = 0; i < this.SeriesItemsSource.Count; i++) + { + this.itemIndexMap.Add(this.SeriesItemsSource[i], i); + } + } + + #endregion + + #region StackMode + private StackMode _stackMode = StackMode.None; + public StackMode StackMode + { + get { return this._stackMode; } + internal set + { + if (this._stackMode != value) + { + this._stackMode = value; + Reset(); + ValidateData(); + Refresh(); + } + + } + } + #endregion + + #region Global Data Range + private Range _globalYValueRange = Range.Empty; + /// + /// The dependent value Range(min & max) of all series data + /// + public Range GlobalYValueRange + { + get { return this._globalYValueRange; } + set + { + if (this._globalYValueRange != value) + { + this._globalYValueRange = value; + + + this.GlobalYValueRangeChanged?.Invoke(value); + } + } + } + + private Range _globalXValueRange = Range.Empty; + /// + /// The independent value Range(min & max) of all series data + /// + public Range GlobalXValueRange + { + get { return this._globalXValueRange; } + set + { + if (this._globalXValueRange != value) + { + this._globalXValueRange = value; + + + this.GlobalXValueRangeChanged?.Invoke(value); + } + } + } + + public event Action GlobalXValueRangeChanged; + public event Action GlobalYValueRangeChanged; + + private void Sr_XValueRangeChanged(ISeriesControl sr, Range obj) + { + UpdateGlobalValueRange(); + } + + private void Sr_YValueRangeChanged(ISeriesControl sr, Range obj) + { + UpdateGlobalValueRange(); + } + + private void UpdateGlobalValueRange() + { + if (this.SeriesCount == 0) + { + this.GlobalXValueRange = Range.Empty; + this.GlobalYValueRange = Range.Empty; + return; + } + + double minX = double.MaxValue, minY = double.MaxValue, + maxX = double.MinValue, maxY = double.MinValue; + + foreach (var sr in this.GetSeries()) + { + if (sr.XValueRange.IsEmpty || sr.YValueRange.IsEmpty) + { + this.GlobalXValueRange = Range.Empty; + this.GlobalYValueRange = Range.Empty; + + return; + } + + minX = Math.Min(minX, sr.XValueRange.Min); + maxX = Math.Max(maxX, sr.XValueRange.Max); + + minY = Math.Min(minY, sr.YValueRange.Min); + maxY = Math.Max(maxY, sr.YValueRange.Max); + + } + + this.GlobalXValueRange = new Range(minX, maxX); + this.GlobalYValueRange = new Range(minY, maxY); + + + + } + #endregion + + #region Plotting Data value Range + + private Range _xPlottingRange; + public Range XPlottingRange + { + get { return _xPlottingRange; } + set + { + if (!_xPlottingRange.Equals(value)) + { + _xPlottingRange = value; + foreach (var sr in this.GetSeries()) + { + sr.OnPlottingXValueRangeChanged(value); + } + } + } + } + + private Range _yPlottingRange; + public Range YPlottingRange + { + get { return _yPlottingRange; } + set + { + if (!_yPlottingRange.Equals(value)) + { + _yPlottingRange = value; + + foreach (var sr in this.GetSeries()) + { + sr.OnPlottingYValueRangeChanged(value); + } + } + } + } + + internal virtual void SetPlottingValueRange(Orientation orientation, Range newValue) + { + + switch (orientation) + { + case Orientation.Horizontal: + this.XPlottingRange = newValue; + break; + case Orientation.Vertical: + this.YPlottingRange = newValue; + break; + default: + throw new ArgumentOutOfRangeException(nameof(orientation), orientation, null); + } + } + #endregion + + #region SeriesDataTemplate & SeriesTemplateSelector + public DataTemplate SeriesTemplate + { + get { return (DataTemplate)GetValue(SeriesTemplateProperty); } + set { SetValue(SeriesTemplateProperty, value); } + } + public static readonly DependencyProperty SeriesTemplateProperty = + DependencyProperty.Register("SeriesTemplate", typeof(DataTemplate), typeof(SeriesCollectionControl), new PropertyMetadata(null)); + + public DataTemplateSelector SeriesTemplateSelector + { + get { return (DataTemplateSelector)GetValue(SeriesTemplateSelectorProperty); } + set { SetValue(SeriesTemplateSelectorProperty, value); } + } + public static readonly DependencyProperty SeriesTemplateSelectorProperty = + DependencyProperty.Register("SeriesTemplateSelector", typeof(DataTemplateSelector), typeof(SeriesCollectionControl), new PropertyMetadata(null)); + #endregion + + internal void Reset() + { + foreach (var sr in this.GetSeries()) + { + sr.Reset(); + } + + Debug.Assert(this.GlobalXValueRange == Range.Empty); + Debug.Assert(this.GlobalYValueRange == Range.Empty); + } + } +} diff --git a/WPF/WpfFX/MvvmChartWpfFX/SeriesCollectionControl/SeriesCollectionControlStyle.xaml b/WPF/WpfFX/MvvmChartWpfFX/SeriesCollectionControl/SeriesCollectionControlStyle.xaml new file mode 100644 index 0000000..eeccfcc --- /dev/null +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesCollectionControl/SeriesCollectionControlStyle.xaml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/ISeriesControlOwner.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/ISeriesControlOwner.cs new file mode 100644 index 0000000..5a827e4 --- /dev/null +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/ISeriesControlOwner.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using MvvmCharting.Series; + +namespace MvvmCharting.WpfFX.Series +{ + public interface ISeriesControlOwner + { + StackMode StackMode { get; } + IEnumerable GetSeries(); + + ISeriesControl GetPreviousSeriesHost(ISeriesControl current); + + + bool IsSeriesCollectionChanging { get; } + bool IsXAxisCategory { get;} + } +} \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesControl.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesControl.cs index 3356d63..c446807 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesControl.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesControl.cs @@ -1,376 +1,975 @@ using System; using System.Collections; -using System.Collections.Generic; using System.Collections.Specialized; +using System.Configuration; using System.Diagnostics; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Reflection; using System.Windows; using System.Windows.Controls; using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; using System.Windows.Shapes; -using MvvmCharting.Axis; using MvvmCharting.Common; +using MvvmCharting.Drawing; using MvvmCharting.Series; +using MvvmCharting.WpfFX.Series; +using static System.Double; -namespace MvvmCharting.WpfFX +namespace MvvmCharting.WpfFX.Series { - /// - /// This is used to plot one series or a collection of series. - /// A series is composed of a curve(or a area) and a collection of Scatters + /// Represents a series control which can hold as many as four types of series: + /// , , , + /// . They can be added, removed or hid dynamically. + /// When overlapped one another, they together can compose a sophisticated series + /// as a whole. /// - [TemplatePart(Name = "PART_SeriesItemsControl", Type = typeof(SlimItemsControl))] - public class SeriesControl : Control, ISeriesHost + public class SeriesControl : InteractiveControl, ISeriesControl, + IScatterSeriesOwner, + ILineSeriesOwner, + IBarSeriesOwner { - private static readonly string sPART_SeriesItemsControl = "PART_SeriesItemsControl"; + private static IValueConverter B2v = new BooleanToVisibilityConverter(); static SeriesControl() { DefaultStyleKeyProperty.OverrideMetadata(typeof(SeriesControl), new FrameworkPropertyMetadata(typeof(SeriesControl))); } - private SlimItemsControl PART_SeriesItemsControl; + public event Action XRangeChanged; + public event Action YRangeChanged; + + public ISeriesControlOwner SeriesControlOwner=> this.Owner; - private bool _isXAxisCategory; - public bool IsXAxisCategory + private ISeriesControlOwner _owner; + internal ISeriesControlOwner Owner { - get { return this._isXAxisCategory; } - internal set + get { return this._owner; } + set { - if (this._isXAxisCategory != value) + if (this._owner != value) { - this._isXAxisCategory = value; + this._owner = value; - if (value) - { - EnsureXValueUniformity(); - } + //this.LineSeries?.UpdateShape(); + //this.AreaSeries?.UpdateShape(); + //this.ScatterSeries?.UpdateCoordinate(); + //this.BarSeries?.UpdateBarWidth(this.MinXValueGap, this.XPixelPerUnit); + //this.BarSeries?.UpdateBarItemCoordinateAndHeight(); } + } } - private void EnsureXValueUniformity() + private ContentControl PART_LineSeriesHolder; + private ContentControl PART_AreaSeriesHolder; + private ContentControl PART_ScatterSeriesHolder; + private ContentControl PART_BarSeriesHolder; + + public SeriesControl() { - SeriesBase firstSr = null; - foreach (var sr in this.GetSeries()) + this.Loaded += SeriesHost_Loaded; + + } + + private void SeriesHost_Loaded(object sender, RoutedEventArgs e) + { + RecalculateCoordinate(); + } + + #region overrides + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + this.PART_LineSeriesHolder = (ContentControl)this.GetTemplateChild("PART_LineSeriesHolder"); + if (this.PART_LineSeriesHolder != null) { - if (firstSr == null) - { - firstSr = sr; - continue; - } - if (sr.ItemsSource.Count != firstSr.ItemsSource.Count) + this.PART_LineSeriesHolder.Content = this.LineSeries; + this.PART_LineSeriesHolder.SetBinding(UIElement.VisibilityProperty, + new Binding(nameof(this.IsLineSeriesVisible)) { Source = this, Converter = B2v }); + } + + this.PART_AreaSeriesHolder = (ContentControl)this.GetTemplateChild("PART_AreaSeriesHolder"); + if (this.PART_AreaSeriesHolder != null) + { + this.PART_AreaSeriesHolder.Content = this.AreaSeries; + this.PART_AreaSeriesHolder.SetBinding(UIElement.VisibilityProperty, + new Binding(nameof(this.IsAreaSeriesVisible)) { Source = this, Converter = B2v }); + } + + this.PART_ScatterSeriesHolder = (ContentControl)this.GetTemplateChild("PART_ScatterSeriesHolder"); + if (this.PART_ScatterSeriesHolder != null) + { + this.PART_ScatterSeriesHolder.Content = this.ScatterSeries; + this.PART_ScatterSeriesHolder.SetBinding(UIElement.VisibilityProperty, + new Binding(nameof(this.IsScatterSeriesVisible)) { Source = this, Converter = B2v }); + } + + this.PART_BarSeriesHolder = (ContentControl)this.GetTemplateChild("PART_BarSeriesHolder"); + if (this.PART_BarSeriesHolder != null) + { + this.PART_BarSeriesHolder.Content = this.BarSeries; + this.PART_BarSeriesHolder.SetBinding(UIElement.VisibilityProperty, + new Binding(nameof(this.IsBarSeriesVisible)) { Source = this, Converter = B2v }); + } + } + + protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) + { + base.OnRenderSizeChanged(sizeInfo); + + if (sizeInfo.NewSize.NearlyEqual(sizeInfo.PreviousSize)) + { + return; + } + + if (sizeInfo.WidthChanged) + { + UpdateXPixelPerUnit(); + } + + if (sizeInfo.HeightChanged) + { + UpdateYPixelPerUnit(); + } + + RecalculateCoordinate(); + } + + + #endregion + + #region Validate data + private void EnsureYValuePositive(IList list) + { + if (list == null) + { + return; + } + + if (this.Owner.StackMode == StackMode.None) + { + return; + } + + foreach (var item in list) + { + var y = GetYValueForItem(item); + if (y < 0) { - throw new MvvmChartException("If the XAxis is CategoryAxis, the ItemsSource of all series should have the same length!"); + throw new MvvmChartModelDataException($"Item value of {this.Owner.StackMode} series cannot be negative!"); } + } + } + + internal void ValidateData() + { + EnsureYValuePositive(this.ItemsSource); + + if (!this.Owner.IsXAxisCategory && (this.Owner.StackMode == StackMode.None)) + { + return; + } + + var firstSh = this.Owner.GetSeries().FirstOrDefault(x => x != this && x.ItemsSource != null); + + if (firstSh == null) + { + return; + } + + string strReason = this.Owner.StackMode != StackMode.None ? $"In {this.Owner.StackMode} mode" : "If the XAxis of a Chart is CategoryAxis"; + + if (firstSh.ItemsSource.Count != this.ItemsSource.Count) + { + throw new MvvmChartModelDataException($"{strReason}, the item count of all series in a Chart must be same!"); + } - for (int i = 0; i < sr.ItemsSource.Count; i++) + for (int i = 0; i < this.ItemsSource.Count; i++) + { + if (this.Owner.IsXAxisCategory) { - if (sr.GetXRawValueForItem(sr.ItemsSource[i]) == firstSr.GetXRawValueForItem(firstSr.ItemsSource[i])) + var x1 = GetXRawValueForItem(this.ItemsSource[i]); + var x2 = GetXRawValueForItem(firstSh.ItemsSource[i]); + if (x1 == null || x2 == null) + { + throw new MvvmChartModelDataException("An Item's X value of the series ItemsSource cannot be null!"); + } + + if (!x1.Equals(x2)) { - throw new MvvmChartException("If the XAxis is CategoryAxis, the ItemsSource of all series should have the same x value at each index!"); + throw new MvvmChartModelDataException($"{strReason}, the item's x value in the same index of all series in a Chart must be same!"); } } + else + { + var x1 = GetXValueForItem(this.ItemsSource[i]); + var x2 = GetXValueForItem(firstSh.ItemsSource[i]); + if (!x1.NearlyEqual(x2)) + { + throw new MvvmChartModelDataException($"{strReason}, the item's x value in the same index of all series in a Chart must be same!"); + } + } } + + + + } + + #endregion + + #region IndependentValueProperty & DependentValueProperty properties + public string IndependentValueProperty + { + get { return (string)GetValue(IndependentValuePropertyProperty); } + set { SetValue(IndependentValuePropertyProperty, value); } + } + public static readonly DependencyProperty IndependentValuePropertyProperty = + DependencyProperty.Register("IndependentValueProperty", typeof(string), typeof(SeriesControl), new PropertyMetadata(null)); + + + public string DependentValueProperty + { + get { return (string)GetValue(DependentValuePropertyProperty); } + set { SetValue(DependentValuePropertyProperty, value); } + } + public static readonly DependencyProperty DependentValuePropertyProperty = + DependencyProperty.Register("DependentValueProperty", typeof(string), typeof(SeriesControl), new PropertyMetadata(null)); + #endregion + + #region IsScatterSeriesVisible & IsLineSeriesVisible & IsAreaSeriesVisible properties + public bool IsScatterSeriesVisible + { + get { return (bool)GetValue(IsScatterSeriesVisibleProperty); } + set { SetValue(IsScatterSeriesVisibleProperty, value); } } + public static readonly DependencyProperty IsScatterSeriesVisibleProperty = + DependencyProperty.Register("IsScatterSeriesVisible", typeof(bool), typeof(SeriesControl), new PropertyMetadata(true)); - public bool IsSeriesCollectionChanging { get; set; } + public bool IsLineSeriesVisible + { + get { return (bool)GetValue(IsLineSeriesVisibleProperty); } + set { SetValue(IsLineSeriesVisibleProperty, value); } + } + public static readonly DependencyProperty IsLineSeriesVisibleProperty = + DependencyProperty.Register("IsLineSeriesVisible", typeof(bool), typeof(SeriesControl), new PropertyMetadata(true)); + + public bool IsAreaSeriesVisible + { + get { return (bool)GetValue(IsAreaSeriesVisibleProperty); } + set { SetValue(IsAreaSeriesVisibleProperty, value); } + } + public static readonly DependencyProperty IsAreaSeriesVisibleProperty = + DependencyProperty.Register("IsAreaSeriesVisible", typeof(bool), typeof(SeriesControl), new PropertyMetadata(true)); - public int SeriesCount => this.PART_SeriesItemsControl?.ItemCount ?? 0; + public bool IsBarSeriesVisible + { + get { return (bool)GetValue(IsBarSeriesVisibleProperty); } + set { SetValue(IsBarSeriesVisibleProperty, value); } + } + public static readonly DependencyProperty IsBarSeriesVisibleProperty = + DependencyProperty.Register("IsBarSeriesVisible", typeof(bool), typeof(SeriesControl), new PropertyMetadata(true)); + + + + #endregion - public IEnumerable GetSeries() + + + #region ItemsSource property and handlers + + + /// + /// Represents the data for a series. + /// Currently can only handle numerical(& DateTime, DataTimeOffset) data. + /// NOTE: It will be the user's responsibility to keep the ItemsSource sorted + /// by Independent property(x value) ascendingly! + /// + public IList ItemsSource + { + get { return (IList)GetValue(ItemsSourceProperty); } + set { SetValue(ItemsSourceProperty, value); } + } + public static readonly DependencyProperty ItemsSourceProperty = + DependencyProperty.Register("ItemsSource", typeof(IList), typeof(SeriesControl), new PropertyMetadata(null, OnItemsSourcePropertyChanged)); + + private static void OnItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + SeriesControl c = (SeriesControl)d; + + c.OnItemsSourceChanged((IList)e.OldValue, (IList)e.NewValue); + + } + + private void OnItemsSourceChanged(IList oldValue, IList newValue) { - if (this.PART_SeriesItemsControl == null) + ValidateData(); + + if (oldValue is INotifyCollectionChanged oldItemsSource) + { + WeakEventManager + .RemoveHandler(oldItemsSource, "CollectionChanged", ItemsSource_CollectionChanged); + } + + if (newValue != null) { - return Enumerable.Empty(); + if (newValue is INotifyCollectionChanged newItemsSource) + { + WeakEventManager + .AddHandler(newItemsSource, "CollectionChanged", ItemsSource_CollectionChanged); + } } - return this.PART_SeriesItemsControl.GetChildren().OfType(); + Reset(); + + this.ScatterSeries?.UpdateItemsSource(); + + this.BarSeries?.UpdateItemsSource(); + + Refresh(); + } - public override void OnApplyTemplate() + private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - base.OnApplyTemplate(); + if (e.Action == NotifyCollectionChangedAction.Move) + { + return; + } - if (this.PART_SeriesItemsControl != null) + if (!this.Owner.IsSeriesCollectionChanging) { - this.PART_SeriesItemsControl.ElementGenerated -= SeriesItemTemplateApplied; + /* if (this.Owner.IsXAxisCategory) + { + throw new MvvmChartModelDataException($"Collection change of ItemsSource is not allowed when XAxis is CategoryAxis and {nameof(this.Owner.IsSeriesCollectionChanging)} is false!"); + }*/ + + if (this.Owner.StackMode != StackMode.None) + { + throw new MvvmChartModelDataException($"Collection change of ItemsSource is not allowed in {this.Owner.StackMode} mode when {nameof(this.Owner.IsSeriesCollectionChanging)} is false!"); + } } - this.PART_SeriesItemsControl = (SlimItemsControl)GetTemplateChild(sPART_SeriesItemsControl); + HandleItemsSourceCollectionChange(e.OldItems, e.NewItems); + } - if (this.PART_SeriesItemsControl != null) + private double _minXValueGap = NaN; + public double MinXValueGap + { + get { return this._minXValueGap; } + set { - this.PART_SeriesItemsControl.ElementGenerated += SeriesItemTemplateApplied; - //this.PART_SeriesItemsControl.ItemAdded += PART_SeriesItemsControl_ItemAdded; - this.PART_SeriesItemsControl.ChildRemoved += PartSeriesChildrenControlChildRemoved; - //this.PART_SeriesItemsControl.ItemReplaced += PART_SeriesItemsControl_ItemReplaced; - this.PART_SeriesItemsControl.Reset += PART_SeriesItemsControl_Reset; + if (this._minXValueGap != value) + { + this._minXValueGap = value; - this.PART_SeriesItemsControl.SetBinding(SlimItemsControl.ItemTemplateProperty, - new Binding(nameof(this.SeriesTemplate)) { Source = this }); - this.PART_SeriesItemsControl.SetBinding(SlimItemsControl.ItemTemplateSelectorProperty, - new Binding(nameof(this.SeriesTemplateSelector)) { Source = this }); - this.PART_SeriesItemsControl.SetBinding(SlimItemsControl.ItemsSourceProperty, - new Binding(nameof(this.SeriesItemsSource)) { Source = this }); + this.BarSeries?.UpdateBarWidth(this.MinXValueGap, this.XPixelPerUnit); + } } + } + private void UpdateMinXValueGap() + { + if (this.Owner.IsXAxisCategory) + { + this.MinXValueGap = 1.0; + this.BarSeries?.UpdateBarWidth(this.MinXValueGap, this.XPixelPerUnit); + return; + } + + double prev = NaN; + this.MinXValueGap = MaxValue; + foreach (var item in this.ItemsSource) + { + if (prev.IsNaN()) + { + + prev = GetXValueForItem(item); + continue; + } + + var current = GetXValueForItem(item); + var gap = current - prev; + if (gap < this.MinXValueGap) + { + this.MinXValueGap = gap; + } + } + } + + private void HandleItemsSourceCollectionChange(IList oldValue, IList newValue) + { + Reset(); + EnsureYValuePositive(newValue); + UpdateMinXValueGap(); + UpdateValueRange(); + RecalculateCoordinate(); + + } + + private double GetXValueForItem(object item) + { + var t = item.GetType(); + var x = t.GetProperty(this.IndependentValueProperty).GetValue(item); + + return DoubleValueConverter.ObjectToDouble(x); + + } + + internal object GetXRawValueForItem(object item) + { + var t = item.GetType(); + var x = t.GetProperty(this.IndependentValueProperty).GetValue(item); + return x; } - private void PART_SeriesItemsControl_Reset(object obj) + private double GetYValueForItem(object item) { - UpdateGlobalValueRange(); + var t = item.GetType(); + var yProp = t.GetProperty(this.DependentValueProperty); + var y = yProp.GetValue(item); + + return DoubleValueConverter.ObjectToDouble(y); + } - private void PartSeriesChildrenControlChildRemoved(object arg1, FrameworkElement arg2) + private double GetAdjustYValueForItem(object item, int index) { - UpdateGlobalValueRange(); + switch (this.Owner.StackMode) + { + case StackMode.None: + + return GetYValueForItem(item); + + case StackMode.Stacked: + double total = 0; + foreach (var sr in this.Owner.GetSeries()) + { + var obj = sr.ItemsSource[index]; + var y = sr.GetYValueForItem(obj); + total += y; + if (obj == item) + { + break; + } + + + } + + return total; + + case StackMode.Stacked100: + bool meet = false; + total = 0; + double sum = 0; + foreach (var sr in this.Owner.GetSeries()) + { + var obj = sr.ItemsSource[index]; + total += sr.GetYValueForItem(obj); + + if (!meet) + { + sum = total; + } + + if (obj == item) + { + meet = true; + } + + } + + return sum / total; + default: + throw new ArgumentOutOfRangeException(); + } + + + + } - private void SeriesItemTemplateApplied(object sender, DependencyObject root, int index) + public void UpdateValueRange() { - if (root == null) + if (this.Owner.IsSeriesCollectionChanging) { + this.XValueRange = Range.Empty; + this.YValueRange = Range.Empty; return; } - var sr = root as ISeries; - if (sr == null) + if (this.ItemsSource == null || + this.ItemsSource.Count == 0) { - throw new MvvmChartException("The root element in the SeriesDataTemplate should implement ISeries!"); + this.XValueRange = Range.Empty; + this.YValueRange = Range.Empty; + + return; } - (sr as SeriesBase).Owner = this; - sr.XRangeChanged += Sr_XValueRangeChanged; - sr.YRangeChanged += Sr_YValueRangeChanged; + + double minY = MaxValue; + double maxY = MinValue; + double minX = MaxValue; + double maxX = MinValue; + for (int i = 0; i < this.ItemsSource.Count; i++) + { + var item = this.ItemsSource[i]; - sr.OnPlottingXValueRangeChanged(this.PlottingXValueRange); - sr.OnPlottingYValueRangeChanged(this.PlottingYValueRange); + var y = GetAdjustYValueForItem(item, i); - sr.UpdateValueRange(); + minY = Math.Min(minY, y); + maxY = Math.Max(maxY, y); + if (!this.Owner.IsXAxisCategory) + { + var x = GetXValueForItem(item); - } + minX = Math.Min(minX, x); + maxX = Math.Max(maxX, x); + } + } + + + if (this.Owner.IsXAxisCategory) + { + minX = 0; + maxX = this.ItemsSource.Count; + } + + this.XValueRange = new Range(minX, maxX); + this.YValueRange = new Range(minY, maxY); - #region SeriesDataTemplate & SeriesTemplateSelector - public DataTemplate SeriesTemplate - { - get { return (DataTemplate)GetValue(SeriesTemplateProperty); } - set { SetValue(SeriesTemplateProperty, value); } } - public static readonly DependencyProperty SeriesTemplateProperty = - DependencyProperty.Register("SeriesTemplate", typeof(DataTemplate), typeof(SeriesControl), new PropertyMetadata(null)); - public DataTemplateSelector SeriesTemplateSelector + #endregion + + #region value range + private Range _yValueRange = Range.Empty; + /// + /// The min & max of the dependent value + /// + public Range YValueRange { - get { return (DataTemplateSelector)GetValue(SeriesTemplateSelectorProperty); } - set { SetValue(SeriesTemplateSelectorProperty, value); } + get { return this._yValueRange; } + private set + { + if (this._yValueRange != value) + { + this._yValueRange = value; + + + this.YRangeChanged?.Invoke(this, this.YValueRange); + + } + + } } - public static readonly DependencyProperty SeriesTemplateSelectorProperty = - DependencyProperty.Register("SeriesTemplateSelector", typeof(DataTemplateSelector), typeof(SeriesControl), new PropertyMetadata(null)); - #endregion - #region SeriesItemsSource + private Range _xValueRange = Range.Empty; /// - /// Represents the data for a list of series(). + /// The min & max of the dependent value /// - public IList SeriesItemsSource + public Range XValueRange { - get { return (IList)GetValue(SeriesItemsSourceProperty); } - set { SetValue(SeriesItemsSourceProperty, value); } + get { return this._xValueRange; } + private set + { + if (this._xValueRange != value) + { + this._xValueRange = value; + this.XRangeChanged?.Invoke(this, this.XValueRange); + + + } + } } - public static readonly DependencyProperty SeriesItemsSourceProperty = - DependencyProperty.Register("SeriesItemsSource", typeof(IList), typeof(SeriesControl)); #endregion - #region Global Data Range - private Range _globalYValueRange = Range.Empty; + #region plotting value range + private Range _plottingXValueRange = Range.Empty; /// - /// The dependent value Range(min & max) of all series data + /// The final X value range used to plot the chart /// - public Range GlobalYValueRange + protected Range PlottingXValueRange { - get { return this._globalYValueRange; } + get { return this._plottingXValueRange; } set { - if (this._globalYValueRange != value) + if (!this._plottingXValueRange.Equals(value) ) { - this._globalYValueRange = value; - this.GlobalYValueRangeChanged?.Invoke(value); + this._plottingXValueRange = value; + UpdateXPixelPerUnit(); + RecalculateCoordinate(); } } } - private Range _globalXValueRange = Range.Empty; + private Range _valuePlottingYValueRange = Range.Empty; /// - /// The independent value Range(min & max) of all series data + /// The final Y value range used to plot the chart /// - public Range GlobalXValueRange + protected Range PlottingYValueRange { - get { return this._globalXValueRange; } + get { return this._valuePlottingYValueRange; } set { - if (this._globalXValueRange != value) + if (!this._valuePlottingYValueRange.Equals(value) ) { - this._globalXValueRange = value; - - - this.GlobalXValueRangeChanged?.Invoke(value); + this._valuePlottingYValueRange = value; + UpdateYPixelPerUnit(); + RecalculateCoordinate(); } } } - public event Action GlobalXValueRangeChanged; - public event Action GlobalYValueRangeChanged; + public virtual void OnPlottingXValueRangeChanged(Range newValue) + { + this.PlottingXValueRange = newValue; + } - private void Sr_XValueRangeChanged(ISeries sr, Range obj) + public virtual void OnPlottingYValueRangeChanged(Range newValue) { - UpdateGlobalValueRange(); + this.PlottingYValueRange = newValue; } + #endregion - private void Sr_YValueRangeChanged(ISeries sr, Range obj) + #region Coordinates calculating + private double _xPixelPerUnit; + public double XPixelPerUnit { - UpdateGlobalValueRange(); + get { return this._xPixelPerUnit; } + set + { + if (this._xPixelPerUnit != value) + { + this._xPixelPerUnit = value; + this.BarSeries?.UpdateBarWidth(this.MinXValueGap, this.XPixelPerUnit); + + } + + } } - private void UpdateGlobalValueRange() + private double _yPixelPerUnit; + public double YPixelPerUnit { - if (this.SeriesCount == 0) + get { return this._yPixelPerUnit; } + set { - this.GlobalXValueRange = Range.Empty; - this.GlobalYValueRange = Range.Empty; - return; + if (this._yPixelPerUnit != value) + { + this._yPixelPerUnit = value; + } } + } - double minX = double.MaxValue, minY = double.MaxValue, maxX = double.MinValue, maxY = double.MinValue; - bool isXDataRangeEmplty = true; - bool isYDataRangeEmplty = true; - foreach (var sr in this.GetSeries()) + private void UpdateXPixelPerUnit() + { + if (this.PlottingXValueRange.IsEmpty || + this.RenderSize.IsInvalid()) { + this.XPixelPerUnit = NaN; + return; + } - if (!sr.XValueRange.IsEmpty) - { - minX = Math.Min(minX, sr.XValueRange.Min); - maxX = Math.Max(maxX, sr.XValueRange.Max); + this.XPixelPerUnit = this.ActualWidth / this.PlottingXValueRange.Span; + } - if (isXDataRangeEmplty) - { - isXDataRangeEmplty = false; - } + private void UpdateYPixelPerUnit() + { + if (this.PlottingYValueRange.IsEmpty || + this.RenderSize.IsInvalid()) + { + this.YPixelPerUnit = NaN; - } + return; + } - if (!sr.YValueRange.IsEmpty) - { - minY = Math.Min(minY, sr.YValueRange.Min); - maxY = Math.Max(maxY, sr.YValueRange.Max); - if (isYDataRangeEmplty) - { - isYDataRangeEmplty = false; - } - } + this.YPixelPerUnit = this.ActualHeight / this.PlottingYValueRange.Span; - } + } + public PointNS GetPlotCoordinateForItem(object item, int itemIndex) + { + double x; + if (!this.Owner.IsXAxisCategory) + { + x = GetXValueForItem(item); - if (!isXDataRangeEmplty) + } + else { - this.GlobalXValueRange = new Range(minX, maxX); + x = itemIndex + 0.5; } - if (!isYDataRangeEmplty) + var y = GetAdjustYValueForItem(item, itemIndex); + + var pt = new PointNS((x - this.PlottingXValueRange.Min) * this.XPixelPerUnit, + (y - this.PlottingYValueRange.Min) * this.YPixelPerUnit); + + + if (pt.Y < 0) { - this.GlobalYValueRange = new Range(minY, maxY); + throw new NotImplementedException(this.DataContext.ToString()); } - + return pt; } - #endregion - #region Plotting Data value Range - private Range _plottingXValueRange = Range.Empty; /// - /// The final independent value range(min & max) used to plot series chart + /// This should be call when: 1) ItemsSource; 2) x or y PixelPerUnit, and 3) RenderSize changed. + /// This method will first update the _coordinateCache and the Coordinate of each scatter, + /// then update the shape of the Line or Area /// - protected Range PlottingXValueRange + internal void RecalculateCoordinate() { - get + if (this.Owner.IsSeriesCollectionChanging) { + return; + } + + UpdateXPixelPerUnit(); + UpdateYPixelPerUnit(); - return this._plottingXValueRange; + if (this.XPixelPerUnit.IsNaN() || + this.YPixelPerUnit.IsNaN() || + !this.IsLoaded) + { + return; } - set + + + Array.Resize(ref this._coordinateCache, this.ItemsSource.Count); + var previous = this.GetPreviousSeriesCoordinates(false); + for (int i = 0; i < this.ItemsSource.Count; i++) { - if (this._plottingXValueRange != value) - { - this._plottingXValueRange = value; + var item = this.ItemsSource[i]; - foreach (var sr in this.GetSeries()) - { - sr.OnPlottingXValueRangeChanged(value); - } - } + var pt = GetPlotCoordinateForItem(item, i); + + this._coordinateCache[i] = pt; + + this.ScatterSeries?.UpdateScatterCoordinate(item, pt); + + double y = previous?[i].Y ?? 0; + this.BarSeries?.UpdateBarCoordinateAndHeight(item, pt, y); } + + this.LineSeries?.UpdateShape(); + this.AreaSeries?.UpdateShape(); + + this.BarSeries?.UpdateBarWidth(this.MinXValueGap, this.XPixelPerUnit); + + + } + + internal void Reset() + { + this.XPixelPerUnit = double.NaN; + this.YPixelPerUnit = double.NaN; + + this.XValueRange = Range.Empty; + this.YValueRange = Range.Empty; + } + + internal void Refresh() + { + UpdateValueRange(); + + RecalculateCoordinate(); + } - private Range _plottingYValueRange = Range.Empty; /// - /// The final dependent value range(min & max) used to plot series chart + /// cache the coordinate for performance /// - protected Range PlottingYValueRange + private PointNS[] _coordinateCache; + + public PointNS[] GetCoordinates() { - get { return this._plottingYValueRange; } - set - { - if (this._plottingYValueRange != value) - { - this._plottingYValueRange = value; + return this._coordinateCache; + } - foreach (var sr in this.GetSeries()) - { - sr.OnPlottingYValueRangeChanged(value); - } - } - } + + private ISeriesControl GetPreviousSeriesHost() + { + return this.Owner.GetPreviousSeriesHost(this); } - internal virtual void SetPlottingValueRange(Orientation orientation, Range newValue) + + public PointNS[] GetPreviousSeriesCoordinates(bool isAreaSeries) { - switch (orientation) + var coordinates = GetCoordinates(); + + PointNS[] previous = null; + + switch (this.Owner.StackMode) { - case Orientation.Horizontal: - this.PlottingXValueRange = newValue; + case StackMode.None: + if (isAreaSeries) + { + previous = new[] { new PointNS(coordinates.First().X, 0), new PointNS(coordinates.Last().X, 0) }; + } break; - case Orientation.Vertical: - this.PlottingYValueRange = newValue; + case StackMode.Stacked: + case StackMode.Stacked100: + ISeriesControl previousSeriesControl = GetPreviousSeriesHost(); + if (previousSeriesControl == null) + { + if (isAreaSeries) + { + previous = new[] { new PointNS(coordinates.First().X, 0), new PointNS(coordinates.Last().X, 0) }; + } + + } + else + { + previous = previousSeriesControl.GetCoordinates(); + + if (previous.Length != coordinates.Length) + { + throw new MvvmChartException($"previous.Length({previous.Length}) != coordinates.Length({coordinates.Length})"); + } + } + break; default: - throw new ArgumentOutOfRangeException(nameof(orientation), orientation, null); + throw new ArgumentOutOfRangeException(); } + + + return previous; } + + #endregion - internal void UpdateSeriesCoordinates() + #region series + public LineSeries LineSeries + { + get { return (LineSeries)GetValue(LineSeriesProperty); } + set { SetValue(LineSeriesProperty, value); } + } + public static readonly DependencyProperty LineSeriesProperty = + DependencyProperty.Register("LineSeries", typeof(LineSeries), typeof(SeriesControl), new PropertyMetadata(null, OnLineSeriesPropertyChanged)); + + private static void OnLineSeriesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((SeriesControl)d).OnLineSeriesChanged(); + } + + private void OnLineSeriesChanged() { - foreach (var sr in this.GetSeries()) + if (this.PART_LineSeriesHolder != null) + { + this.PART_LineSeriesHolder.Content = this.LineSeries; + } + + if (this.LineSeries != null) { - sr.UpdateValueRange(); - sr.RecalculateCoordinate(); + this.LineSeries.Owner = this; + this.LineSeries.UpdateShape(); } + } + + public AreaSeries AreaSeries + { + get { return (AreaSeries)GetValue(AreaSeriesProperty); } + set { SetValue(AreaSeriesProperty, value); } + } + public static readonly DependencyProperty AreaSeriesProperty = + DependencyProperty.Register("AreaSeries", typeof(AreaSeries), typeof(SeriesControl), new PropertyMetadata(null, OnAreaSeriesPropertyChanged)); + + private static void OnAreaSeriesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((SeriesControl)d).OnAreaSeriesChanged(); + } + + private void OnAreaSeriesChanged() + { + if (this.PART_AreaSeriesHolder != null) + { + this.PART_AreaSeriesHolder.Content = this.AreaSeries; + } + + if (this.AreaSeries != null) + { + this.AreaSeries.Owner = this; + this.AreaSeries.UpdateShape(); + } + } + + public ScatterSeries ScatterSeries + { + get { return (ScatterSeries)GetValue(ScatterSeriesProperty); } + set { SetValue(ScatterSeriesProperty, value); } + } + public static readonly DependencyProperty ScatterSeriesProperty = + DependencyProperty.Register("ScatterSeries", typeof(ScatterSeries), typeof(SeriesControl), new PropertyMetadata(null, OnScatterSeriesPropertyChanged)); + + private static void OnScatterSeriesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((SeriesControl)d).OnScatterSeriesChanged(); + } + + private void OnScatterSeriesChanged() + { + if (this.PART_ScatterSeriesHolder != null) + { + this.PART_ScatterSeriesHolder.Content = this.ScatterSeries; + } + + if (this.ScatterSeries != null) + { + this.ScatterSeries.Owner = this; + this.ScatterSeries.UpdateItemsSource(); + } + } + + + public BarSeries BarSeries + { + get { return (BarSeries)GetValue(BarSeriesProperty); } + set { SetValue(BarSeriesProperty, value); } + } + public static readonly DependencyProperty BarSeriesProperty = + DependencyProperty.Register("BarSeries", typeof(BarSeries), typeof(SeriesControl), new PropertyMetadata(null, OnBarSeriesPropertyChanged)); + + private static void OnBarSeriesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((SeriesControl)d).OnBarSeriesChanged(); + } + private void OnBarSeriesChanged() + { + if (this.PART_BarSeriesHolder != null) + { + + this.PART_BarSeriesHolder.Content = this.BarSeries; + } + + if (this.BarSeries != null) + { + this.BarSeries.Owner = this; + this.BarSeries.UpdateBarWidth(this.MinXValueGap, this.XPixelPerUnit); + this.BarSeries.UpdateItemsSource(); + } + } + #endregion + + } + + + } diff --git a/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesControlStyle.xaml b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesControlStyle.xaml index 728e856..7b87293 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesControlStyle.xaml +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesControlStyle.xaml @@ -1,19 +1,42 @@  + xmlns:system="clr-namespace:System;assembly=mscorlib" + xmlns:local="clr-namespace:MvvmCharting.WpfFX.Series"> + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/AreaSeries/AreaSeries.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/AreaSeries/AreaSeries.cs new file mode 100644 index 0000000..1698fd6 --- /dev/null +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/AreaSeries/AreaSeries.cs @@ -0,0 +1,81 @@ +using System.Diagnostics; +using System.Windows; +using System.Windows.Data; +using System.Windows.Shapes; + +namespace MvvmCharting.WpfFX.Series +{ + /// + /// Represents area series, in fact they are just which is filled + /// by color.It is also the base class + /// for three specific line types: , + /// and + /// + public class AreaSeries : LineSeriesBase + { + static AreaSeries() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(AreaSeries), new FrameworkPropertyMetadata(typeof(AreaSeries))); + } + + public AreaSeries() + { + this.IsAreaMode = true; + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (this.PART_Shape != null) + { + this.PART_Shape.SetBinding(Shape.FillProperty, new Binding(nameof(this.Stroke)) { Source = this }); + + } + } + + + } + + + /// + /// Represents a line series which use to + /// create its path geometry. + /// + public class PolyLineAreaSeries : AreaSeries + { + + + static PolyLineAreaSeries() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(PolyLineAreaSeries), new FrameworkPropertyMetadata(typeof(PolyLineAreaSeries))); + } + + + } + + /// + /// Represents a line series which use to + /// create its path geometry. + /// + public class StepLineAreaSeries : AreaSeries + { + static StepLineAreaSeries() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(StepLineAreaSeries), new FrameworkPropertyMetadata(typeof(StepLineAreaSeries))); + } + } + + /// + /// Represents a line series which use to + /// create its path geometry. + /// + public class SplineAreaSeries : AreaSeries + { + static SplineAreaSeries() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(SplineAreaSeries), new FrameworkPropertyMetadata(typeof(SplineAreaSeries))); + } + } + +} \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/AreaSeries/AreaSeriesStyle.xaml b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/AreaSeries/AreaSeriesStyle.xaml new file mode 100644 index 0000000..eaa024a --- /dev/null +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/AreaSeries/AreaSeriesStyle.xaml @@ -0,0 +1,55 @@ + + + 3.5 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/BarSeries/BarItem.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/BarSeries/BarItem.cs new file mode 100644 index 0000000..20cd8cc --- /dev/null +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/BarSeries/BarItem.cs @@ -0,0 +1,133 @@ +using System; +using System.Diagnostics; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; +using MvvmCharting.Common; +using MvvmCharting.Drawing; + +namespace MvvmCharting.WpfFX.Series +{ + + + public class BarItem : InteractiveControl, IPlottable_2D + { + static BarItem() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(BarItem), new FrameworkPropertyMetadata(typeof(BarItem))); + } + + private PointNS _coordinate = PointNS.Empty; + public PointNS Coordinate + { + get { return this._coordinate; } + set + { + if (this._coordinate != value) + { + this._coordinate = value; + UpdatePosition(); + } + + } + } + public BarItem() + { + this.HorizontalAlignment = HorizontalAlignment.Left; + this.VerticalAlignment = VerticalAlignment.Top; + } + protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) + { + base.OnRenderSizeChanged(sizeInfo); + UpdatePosition(); + + } + + private void UpdatePosition() + { + if (this.Coordinate.IsEmpty() || this.RenderSize.IsInvalid()) + { + return; + } + + double xOffset = GetXOffsetForSizeChangedOverride(); + if (xOffset.IsNaN()) + { + return; + } + + var x = this.Coordinate.X + xOffset; + + + var y = this.Coordinate.Y - this.ActualHeight; + + var translateTransform = this.RenderTransform as TranslateTransform; + if (translateTransform == null) + { + this.RenderTransform = new TranslateTransform(x, y); + } + else + { + translateTransform.X = x; + translateTransform.Y = y; + } + + } + + public double GetXOffsetForSizeChangedOverride() + { + return -(this.Margin.Left + this.Margin.Right + this.ActualWidth) * 0.5; + } + + public void SetBarHeight(double newValue) + { + if (newValue.IsInvalid()) + { + return; + } + + this.SetCurrentValue(HeightProperty, newValue); + + UpdatePosition(); + } + + + + public Brush Stroke + { + get { return (Brush)GetValue(StrokeProperty); } + set { SetValue(StrokeProperty, value); } + } + public static readonly DependencyProperty StrokeProperty = + Shape.StrokeProperty.AddOwner(typeof(BarItem)); + + public double StrokeThickness + { + get { return (double)GetValue(StrokeThicknessProperty); } + set { SetValue(StrokeThicknessProperty, value); } + } + public static readonly DependencyProperty StrokeThicknessProperty = + Shape.StrokeThicknessProperty.AddOwner(typeof(BarItem)); + + public Brush Fill + { + get { return (Brush)GetValue(FillProperty); } + set { SetValue(FillProperty, value); } + } + public static readonly DependencyProperty FillProperty = + Shape.FillProperty.AddOwner(typeof(BarItem)); + + public Style BarStyle + { + get { return (Style)GetValue(BarStyleProperty); } + set { SetValue(BarStyleProperty, value); } + } + public static readonly DependencyProperty BarStyleProperty = + DependencyProperty.Register("BarStyle", typeof(Style), typeof(BarItem), new PropertyMetadata(null)); + + + + + } +} \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/BarSeries/BarSeries.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/BarSeries/BarSeries.cs new file mode 100644 index 0000000..be252f6 --- /dev/null +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/BarSeries/BarSeries.cs @@ -0,0 +1,411 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using MvvmCharting.Common; +using MvvmCharting.Drawing; +using MvvmCharting.Series; + +namespace MvvmCharting.WpfFX.Series +{ + + public interface IBarSeriesOwner + { + object DataContext { get; } + ISeriesControlOwner SeriesControlOwner { get; } + IList ItemsSource { get; } + + double XPixelPerUnit { get; } + double YPixelPerUnit { get; } + + PointNS[] GetCoordinates(); + PointNS GetPlotCoordinateForItem(object item, int itemIndex); + PointNS[] GetPreviousSeriesCoordinates(bool isAreaSeries); + } + + public class BarSeries : InteractiveControl + { + static BarSeries() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(BarSeries), new FrameworkPropertyMetadata(typeof(BarSeries))); + } + + private SlimItemsControl PART_BarItemsControl; + + internal IBarSeriesOwner Owner { get; set; } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (this.PART_BarItemsControl != null) + { + this.PART_BarItemsControl.ElementGenerated -= BarItemsControlBarGenerated; + } + + this.PART_BarItemsControl = (SlimItemsControl)GetTemplateChild("PART_BarItemsControl"); + if (this.PART_BarItemsControl != null) + { + this.PART_BarItemsControl.ElementGenerated += BarItemsControlBarGenerated; + + this.PART_BarItemsControl.SetBinding(SlimItemsControl.ItemTemplateSelectorProperty, new Binding(nameof(this.BarItemTemplateSelector)) { Source = this }); + this.PART_BarItemsControl.SetBinding(SlimItemsControl.ItemTemplateProperty, new Binding(nameof(this.BarItemTemplate)) { Source = this }); + UpdateBarItemWidth(); + UpdateItemsSource(); + } + } + + protected virtual void BarItemsControlBarGenerated(object sender, FrameworkElement child, int childIndex) + { + var barItem = child as BarItem; + if (barItem == null) + { + MvvmChartException.ThrowDataTemplateRootElementException(nameof(this.BarItemTemplate), typeof(BarItem)); + } + + if (this.BarBrush != null && barItem.Fill == null) + { + barItem.SetCurrentValue(BarItem.FillProperty, this.BarBrush); + } + + if (this.BarBorderBrush != null && barItem.Stroke == null) + { + barItem.SetCurrentValue(BarItem.StrokeProperty, this.BarBorderBrush); + } + + if (!this.BarBorderThickness.IsNaN() && barItem.StrokeThickness.IsNaN()) + { + barItem.SetCurrentValue(BarItem.StrokeThicknessProperty, this.BarBorderThickness); + } + + if (this.BarStyle != null && barItem.Style == null) + { + barItem.SetCurrentValue(BarItem.StyleProperty, this.BarStyle); + } + + if (!this.Owner.SeriesControlOwner.IsSeriesCollectionChanging) + { + if (!this.Owner.XPixelPerUnit.IsNaN() && !this.Owner.YPixelPerUnit.IsNaN()) + { + + var privousCoordinates = this.Owner.GetPreviousSeriesCoordinates(false); + + var item = barItem.DataContext; + var coordinate = this.Owner.GetPlotCoordinateForItem(item, childIndex); + + var barHeight = privousCoordinates == null + ? coordinate.Y + : coordinate.Y - privousCoordinates[childIndex].Y; + + if (!this.BarWidth.IsNaN() && barItem.Width.IsInvalid()) + { + barItem.SetCurrentValue(BarItem.WidthProperty, this.BarWidth); + } + + barItem.SetBarHeight(barHeight); + barItem.Coordinate = coordinate; + } + } + } + + internal void UpdateItemsSource() + { + if (this.PART_BarItemsControl != null && this.Owner != null) + { + this.PART_BarItemsControl.ItemsSource = this.Owner.ItemsSource; + } + } + + public DataTemplate BarItemTemplate + { + get { return (DataTemplate)GetValue(BarItemTemplateProperty); } + set { SetValue(BarItemTemplateProperty, value); } + } + public static readonly DependencyProperty BarItemTemplateProperty = + DependencyProperty.Register("BarItemTemplate", typeof(DataTemplate), typeof(BarSeries), new PropertyMetadata(null)); + + public DataTemplateSelector BarItemTemplateSelector + { + get { return (DataTemplateSelector)GetValue(BarItemTemplateSelectorProperty); } + set { SetValue(BarItemTemplateSelectorProperty, value); } + } + public static readonly DependencyProperty BarItemTemplateSelectorProperty = + DependencyProperty.Register("BarItemTemplateSelector", typeof(DataTemplateSelector), typeof(BarSeries), new PropertyMetadata(null)); + + + + + public Brush BarBrush + { + get { return (Brush)GetValue(BarBrushProperty); } + set { SetValue(BarBrushProperty, value); } + } + public static readonly DependencyProperty BarBrushProperty = + DependencyProperty.Register("BarBrush", typeof(Brush), typeof(BarSeries), new PropertyMetadata(null, OnBarBrushPropertyChanged)); + private static void OnBarBrushPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((BarSeries)d).OnBarBrushChanged(); + } + + protected virtual void OnBarBrushChanged() + { + + if (this.PART_BarItemsControl != null) + { + foreach (var barItem in this.PART_BarItemsControl.GetChildren().OfType()) + { + var vs = DependencyPropertyHelper.GetValueSource(barItem, BarItem.FillProperty); + if (vs.IsCurrent) + { + barItem.SetCurrentValue(BarItem.FillProperty, this.BarBrush); + } + + } + } + } + + + public Brush BarBorderBrush + { + get { return (Brush)GetValue(BarBorderBrushProperty); } + set { SetValue(BarBorderBrushProperty, value); } + } + public static readonly DependencyProperty BarBorderBrushProperty = + DependencyProperty.Register("BarBorderBrush", typeof(Brush), typeof(BarSeries), new PropertyMetadata(null, OnBarBorderBrushPropertyChanged)); + + private static void OnBarBorderBrushPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((BarSeries)d).OnBarBorderBrushChanged(); + } + + protected virtual void OnBarBorderBrushChanged() + { + + + if (this.PART_BarItemsControl != null) + { + foreach (var barItem in this.PART_BarItemsControl.GetChildren().OfType()) + { + var vs = DependencyPropertyHelper.GetValueSource(barItem, BarItem.StrokeProperty); + if (vs.IsCurrent) + { + barItem.SetCurrentValue(BarItem.StrokeProperty, this.BarBorderBrush); + } + + } + } + } + + public double BarBorderThickness + { + get { return (double)GetValue(BarBorderThicknessProperty); } + set { SetValue(BarBorderThicknessProperty, value); } + } + public static readonly DependencyProperty BarBorderThicknessProperty = + DependencyProperty.Register("BarBorderThickness", typeof(double), typeof(BarSeries), new PropertyMetadata(double.NaN, OnBarBorderThicknessPropertyChanged)); + private static void OnBarBorderThicknessPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((BarSeries)d).OnBarBorderThicknessChanged(); + } + + protected virtual void OnBarBorderThicknessChanged() + { + if (this.PART_BarItemsControl != null) + { + foreach (var barItem in this.PART_BarItemsControl.GetChildren().OfType()) + { + var vs = DependencyPropertyHelper.GetValueSource(barItem, BarItem.StrokeThicknessProperty); + if (vs.IsCurrent) + { + barItem.SetCurrentValue(BarItem.StrokeThicknessProperty, this.BarBorderThickness); + } + + } + } + } + + public Style BarStyle + { + get { return (Style)GetValue(BarStyleProperty); } + set { SetValue(BarStyleProperty, value); } + } + public static readonly DependencyProperty BarStyleProperty = + DependencyProperty.Register("BarStyle", typeof(Style), typeof(BarSeries), new PropertyMetadata(null, OnBarStyleThicknessPropertyChanged)); + private static void OnBarStyleThicknessPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((BarSeries)d).OnBarStyleThicknessChanged(); + } + + protected virtual void OnBarStyleThicknessChanged() + { + if (this.PART_BarItemsControl != null) + { + foreach (var barItem in this.PART_BarItemsControl.GetChildren().OfType()) + { + var vs = DependencyPropertyHelper.GetValueSource(barItem, BarItem.StyleProperty); + if (vs.IsCurrent) + { + barItem.SetCurrentValue(BarItem.StyleProperty, this.BarStyle); + } + + } + } + } + + /// + /// user-set bar width + /// + public double BarWidth + { + get { return (double)GetValue(BarWidthProperty); } + set { SetValue(BarWidthProperty, value); } + } + public static readonly DependencyProperty BarWidthProperty = + DependencyProperty.Register("BarWidth", typeof(double), typeof(BarSeries), new PropertyMetadata(double.NaN, OnBarWidthPropertyChanged)); + + private static void OnBarWidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((BarSeries)d).OnBarWidthChanged(); + } + private void OnBarWidthChanged() + { + UpdateBarItemWidth(); + } + + //private double _barWidthInternal = double.NaN; + ///// + ///// calculated bar width + ///// + //internal double BarWidthInternal + //{ + // get { return this._barWidthInternal; } + // set + // { + // if (this._barWidthInternal != value) + // { + + // this._barWidthInternal = value; + // UpdateBarItemWidth(); + // } + + // } + //} + + //private double GetProperBarWidth() + //{ + // double w = this.BarWidth; + // if (w.IsNaN()) + // { + // w = this.BarWidthInternal; + // } + + // return w; + //} + + private void UpdateBarItemWidth() + { + if (this.PART_BarItemsControl == null) + { + return; + } + + + foreach (var barItem in this.PART_BarItemsControl.GetChildren().OfType()) + { + var vs = DependencyPropertyHelper.GetValueSource(barItem, BarItem.WidthProperty); + if (vs.IsCurrent) + { + barItem.SetCurrentValue(BarItem.WidthProperty, this.BarWidth); + } + + } + } + + + internal void SetBarWidth(double width) + { + var vs = DependencyPropertyHelper.GetValueSource(this, BarWidthProperty); + if (vs.IsCurrent) + { + this.SetCurrentValue(BarWidthProperty, width); + } + } + + internal void UpdateBarWidth(double minXGap, double xPixelPerUnit) + { + if (minXGap.IsNaN() || xPixelPerUnit.IsInvalid()) + { + return; + } + + double width = minXGap * xPixelPerUnit; + this.SetBarWidth(width); + } + + + internal void UpdateBarCoordinateAndHeight(object item, PointNS coordinate, double previousYCoordinate) + { + var barItem = (BarItem)this.PART_BarItemsControl?.TryGetChildForItem(item); + if (barItem != null) + { + var barHeight = coordinate.Y - previousYCoordinate; + + barItem.SetBarHeight(barHeight); + + barItem.Coordinate = coordinate; + } + + } + + internal void UpdateBarItemCoordinateAndHeight() + { + if (this.PART_BarItemsControl == null) + { + return; + } + + + var coordinates = this.Owner.GetCoordinates(); + + if (coordinates == null) + { + return; + } + + if (coordinates.Length != this.PART_BarItemsControl.ItemCount) + { + throw new NotImplementedException(); + } + + var previous = this.Owner.GetPreviousSeriesCoordinates(false); + if (previous != null && previous.Length != coordinates.Length) + { + throw new NotImplementedException(); + } + + for (int i = 0; i < coordinates.Length; i++) + { + var pt = coordinates[i]; + var item = this.PART_BarItemsControl.ItemsSource[i]; + + double y = previous == null ? 0 : previous[i].Y; + UpdateBarCoordinateAndHeight(item, pt, y); + } + + + } + + } +} diff --git a/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/BarSeries/BarSeriesStyle.xaml b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/BarSeries/BarSeriesStyle.xaml new file mode 100644 index 0000000..a87d20c --- /dev/null +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/BarSeries/BarSeriesStyle.xaml @@ -0,0 +1,54 @@ + + + + + + + + + + \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/LineSeries.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/LineSeries.cs new file mode 100644 index 0000000..d7b06e9 --- /dev/null +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/LineSeries.cs @@ -0,0 +1,69 @@ +using System; +using System.Diagnostics; +using System.Windows; + +namespace MvvmCharting.WpfFX.Series +{ + /// + /// Represents a concrete line series type, which is basically a path + /// whose Fill property is fixed to null. It is also the base class + /// for the three specific line types: , + /// and + /// + public class LineSeries: LineSeriesBase + { + static LineSeries() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(LineSeries), new FrameworkPropertyMetadata(typeof(LineSeries))); + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + if (this.PART_Shape != null) + { + this.PART_Shape.Fill = null; + } + + + + } + } + + /// + /// Represents a line series which use to + /// create its path geometry. + /// + public class PolyLineSeries : LineSeries + { + static PolyLineSeries() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(PolyLineSeries), new FrameworkPropertyMetadata(typeof(PolyLineSeries))); + } + } + + /// + /// Represents a line series which use to + /// create its path geometry. + /// + public class StepLineSeries : LineSeries + { + static StepLineSeries() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(StepLineSeries), new FrameworkPropertyMetadata(typeof(StepLineSeries))); + } + } + + /// + /// Represents a line series which use to + /// create its path geometry. + /// + public class SplineSeries : LineSeries + { + static SplineSeries() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(SplineSeries), new FrameworkPropertyMetadata(typeof(SplineSeries))); + } + } + +} \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/LineSeriesBase.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/LineSeriesBase.cs new file mode 100644 index 0000000..1ea1339 --- /dev/null +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/LineSeriesBase.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using MvvmCharting.Common; +using MvvmCharting.Drawing; +using MvvmCharting.Series; + +namespace MvvmCharting.WpfFX.Series +{ + + + public interface ILineSeriesOwner + { + ISeriesControlOwner SeriesControlOwner { get; } + IList ItemsSource { get; } + + double XPixelPerUnit { get; } + double YPixelPerUnit { get; } + + PointNS[] GetCoordinates(); + + PointNS[] GetPreviousSeriesCoordinates(bool isAreaSeries); + + } + + /// + /// Represents a line or area series which can be displayed. + /// Basically, it use a to draw the shape. + /// If the Fill property of the path is null, then it will + /// draw a line series(), otherwise, + /// it will draw a area series(). + /// To customize the path geometry, user can implements the + /// and pass the object to the property, + /// or set the PathData using mini-language in Xaml directly. + /// + [TemplatePart(Name = "PART_Shape", Type = typeof(Shape))] + public class LineSeriesBase : InteractiveControl + { + + private static readonly string sPART_Shape = "PART_Shape"; + + protected Path PART_Shape; + + /// + /// cache the created Geometry object + /// + private Geometry _pathData; + private Geometry PathData + { + get { return this._pathData;} + set + { + if (this._pathData != value) + { + this._pathData = value; + + if (this.PART_Shape != null) + { + this.PART_Shape.Data = value; + } + } + + } + + } + protected bool IsAreaMode { get; set; } + + internal ILineSeriesOwner Owner { get; set; } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + PART_Shape = (Path)this.GetTemplateChild(sPART_Shape); + + + } + + + public Brush Stroke + { + get { return (Brush)GetValue(StrokeProperty); } + set { SetValue(StrokeProperty, value); } + } + public static readonly DependencyProperty StrokeProperty = + Shape.StrokeProperty.AddOwner(typeof(LineSeriesBase)); + + public double StrokeThickness + { + get { return (double)GetValue(StrokeThicknessProperty); } + set { SetValue(StrokeThicknessProperty, value); } + } + + public static readonly DependencyProperty StrokeThicknessProperty = + Shape.StrokeThicknessProperty.AddOwner(typeof(LineSeriesBase), new PropertyMetadata(1.0)); + + + + public Style LineStyle + { + get { return (Style)GetValue(LineStyleProperty); } + set { SetValue(LineStyleProperty, value); } + } + public static readonly DependencyProperty LineStyleProperty = + DependencyProperty.Register("LineStyle", typeof(Style), typeof(LineSeriesBase), new PropertyMetadata(null)); + + + /// + /// This should be called when GeometryBuilder, Mode or coordinates changed + /// + internal void UpdateShape() + { + if (this.Owner?.SeriesControlOwner == null) + { + return; + } + + if (this.Owner.SeriesControlOwner.IsSeriesCollectionChanging) + { + return; + } + + if (this.GeometryBuilder == null || + this.PART_Shape == null || + this.Owner.XPixelPerUnit.IsInvalid() || + this.Owner.YPixelPerUnit.IsInvalid() || + !this.IsLoaded) + { + return; + } + + + var coordinates = this.Owner.GetCoordinates(); + if (coordinates == null || coordinates.Length < 2) + { + this.PathData = Geometry.Empty; + return; + } + + PointNS[] previous = this.Owner.GetPreviousSeriesCoordinates(this.IsAreaMode); + + this.PathData = (Geometry)this.GeometryBuilder.GetGeometry(coordinates, previous); + + } + + + + public ISeriesGeometryBuilder GeometryBuilder + { + get { return (ISeriesGeometryBuilder)GetValue(GeometryBuilderProperty); } + set { SetValue(GeometryBuilderProperty, value); } + } + public static readonly DependencyProperty GeometryBuilderProperty = + DependencyProperty.Register("GeometryBuilder", typeof(ISeriesGeometryBuilder), typeof(LineSeriesBase), new PropertyMetadata(new PolyLineGeometryBuilder(), OnGeometryProviderPropertyChanged)); + + private static void OnGeometryProviderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((LineSeriesBase)d).UpdateShape(); + } + } + + +} diff --git a/WPF/WpfFX/MvvmChartWpfFX/Series/SeriesStyle.xaml b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/LineSeriesStyle.xaml similarity index 52% rename from WPF/WpfFX/MvvmChartWpfFX/Series/SeriesStyle.xaml rename to WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/LineSeriesStyle.xaml index fe8619c..a990a21 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/Series/SeriesStyle.xaml +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/LineSeriesStyle.xaml @@ -1,48 +1,30 @@  + xmlns:local="clr-namespace:MvvmCharting.WpfFX.Series" + xmlns:system="clr-namespace:System;assembly=mscorlib" + xmlns:wpfFx="clr-namespace:MvvmCharting.WpfFX"> + 3.5 - - - - - 3.5 - - - - - \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/SeriesGeomeryBuilders/PolyLineGeometryBuilder.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/SeriesGeomeryBuilders/PolyLineGeometryBuilder.cs similarity index 98% rename from WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/SeriesGeomeryBuilders/PolyLineGeometryBuilder.cs rename to WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/SeriesGeomeryBuilders/PolyLineGeometryBuilder.cs index 7655bd4..ee872ca 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/SeriesGeomeryBuilders/PolyLineGeometryBuilder.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/SeriesGeomeryBuilders/PolyLineGeometryBuilder.cs @@ -5,7 +5,7 @@ using MvvmCharting.Drawing; using MvvmCharting.Series; -namespace MvvmCharting.WpfFX +namespace MvvmCharting.WpfFX.Series { public class PolyLineGeometryBuilder : ISeriesGeometryBuilder { diff --git a/WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/SeriesGeomeryBuilders/SplineGeometryBuilder.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/SeriesGeomeryBuilders/SplineGeometryBuilder.cs similarity index 99% rename from WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/SeriesGeomeryBuilders/SplineGeometryBuilder.cs rename to WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/SeriesGeomeryBuilders/SplineGeometryBuilder.cs index 35c58c8..6ffd52e 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/SeriesGeomeryBuilders/SplineGeometryBuilder.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/SeriesGeomeryBuilders/SplineGeometryBuilder.cs @@ -8,7 +8,7 @@ using MvvmCharting.Drawing; using MvvmCharting.Series; -namespace MvvmCharting.WpfFX +namespace MvvmCharting.WpfFX.Series { diff --git a/WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/SeriesGeomeryBuilders/StepLineGeometryBuilder.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/SeriesGeomeryBuilders/StepLineGeometryBuilder.cs similarity index 96% rename from WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/SeriesGeomeryBuilders/StepLineGeometryBuilder.cs rename to WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/SeriesGeomeryBuilders/StepLineGeometryBuilder.cs index 85cfdd6..9513061 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/SeriesGeomeryBuilders/StepLineGeometryBuilder.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/LineSeries/SeriesGeomeryBuilders/StepLineGeometryBuilder.cs @@ -2,7 +2,7 @@ using MvvmCharting.Drawing; using MvvmCharting.Series; -namespace MvvmCharting.WpfFX +namespace MvvmCharting.WpfFX.Series { public class StepLineGeometryBuilder : ISeriesGeometryBuilder { diff --git a/WPF/WpfFX/MvvmChartWpfFX/Series/Scatter/Scatter.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/Scatter.cs similarity index 74% rename from WPF/WpfFX/MvvmChartWpfFX/Series/Scatter/Scatter.cs rename to WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/Scatter.cs index c4dede7..96d5c53 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/Series/Scatter/Scatter.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/Scatter.cs @@ -2,10 +2,11 @@ using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; +using MvvmCharting.Common; using MvvmCharting.Drawing; using MvvmCharting.Series; -namespace MvvmCharting.WpfFX +namespace MvvmCharting.WpfFX.Series { /// /// This is used to display a point(dot) for an Item on the plotting area. @@ -14,7 +15,7 @@ namespace MvvmCharting.WpfFX /// the series. If the performance is not as required, just use , /// it's much light weighted. /// - public class Scatter : Control, IScatter + public class Scatter : InteractiveControl, IPlottable_2D { static Scatter() { @@ -58,26 +59,14 @@ public Brush Fill public static readonly DependencyProperty FillProperty = Shape.FillProperty.AddOwner(typeof(Scatter)); - - - public Stretch Stretch { get { return (Stretch)GetValue(StretchProperty); } set { SetValue(StretchProperty, value); } } - - // Using a DependencyProperty as the backing store for Stretch. This enables animation, styling, binding, etc... public static readonly DependencyProperty StretchProperty = Shape.StretchProperty.AddOwner(typeof(Scatter)); - public bool IsSelected - { - get { return (bool)GetValue(IsSelectedProperty); } - set { SetValue(IsSelectedProperty, value); } - } - public static readonly DependencyProperty IsSelectedProperty = - DependencyProperty.Register("IsSelected", typeof(bool), typeof(Scatter), new PropertyMetadata(false)); /// @@ -92,7 +81,7 @@ public Geometry Data public static readonly DependencyProperty DataProperty = Path.DataProperty.AddOwner(typeof(Scatter)); - + public PointNS Coordinate { @@ -100,7 +89,7 @@ public PointNS Coordinate set { SetValue(CoordinateProperty, value); } } public static readonly DependencyProperty CoordinateProperty = - DependencyProperty.Register("Coordinate", typeof(PointNS), typeof(Scatter), new PropertyMetadata(PointNSHelper.EmptyPoint, OnCoordinatePropertyChanged)); + DependencyProperty.Register("Coordinate", typeof(PointNS), typeof(Scatter), new PropertyMetadata(PointNS.Empty, OnCoordinatePropertyChanged)); private static void OnCoordinatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { @@ -109,7 +98,7 @@ private static void OnCoordinatePropertyChanged(DependencyObject d, DependencyPr private void OnCoordinateChanged(PointNS newValue) { UpdateAdjustedCoordinate(); - + } @@ -120,25 +109,17 @@ private void UpdateAdjustedCoordinate() return; } - double x, y; - if (this.NeedOffsetForSize) + PointNS offset = GetOffsetForSizeChangedOverride(); + if (offset.IsEmpty()) { - PointNS offset = GetOffsetForSizeChangedOverride(); - if (offset.IsEmpty()) - { - return; - } - - x = this.Coordinate.X + offset.X; - y = this.Coordinate.Y + offset.Y; - } - else - { - x = this.Coordinate.X; - y = this.Coordinate.Y; + return; } + var x = this.Coordinate.X + offset.X; + var y = this.Coordinate.Y + offset.Y; + + //if (!double.IsInfinity(x)) //{ // Canvas.SetLeft(this, x); @@ -161,12 +142,6 @@ private void UpdateAdjustedCoordinate() } } - /// - /// Only EllipseGeometry doesn't need to set NeedOffsetForSize to true, - /// because it's default center is (0, 0), while other shapes or UIElements - /// are centered at (ActualWidth / 2, ActualHeight / 2) - /// - public bool NeedOffsetForSize { get; set; } public virtual PointNS GetOffsetForSizeChangedOverride() { diff --git a/WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/ScatterGeometryBuilders/CustomGeometryBuilder.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/ScatterGeometryBuilders/CustomGeometryBuilder.cs similarity index 96% rename from WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/ScatterGeometryBuilders/CustomGeometryBuilder.cs rename to WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/ScatterGeometryBuilders/CustomGeometryBuilder.cs index b7feaa7..7cce979 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/ScatterGeometryBuilders/CustomGeometryBuilder.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/ScatterGeometryBuilders/CustomGeometryBuilder.cs @@ -1,7 +1,7 @@ using System.Windows.Media; using MvvmCharting.Series; -namespace MvvmCharting.WpfFX +namespace MvvmCharting.WpfFX.Series { /// /// Customizable GeometryBuilder which can accept a mini-language string, diff --git a/WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/ScatterGeometryBuilders/EllipseGeometryBuilder.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/ScatterGeometryBuilders/EllipseGeometryBuilder.cs similarity index 91% rename from WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/ScatterGeometryBuilders/EllipseGeometryBuilder.cs rename to WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/ScatterGeometryBuilders/EllipseGeometryBuilder.cs index d8a7eb3..8d8ce60 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/ScatterGeometryBuilders/EllipseGeometryBuilder.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/ScatterGeometryBuilders/EllipseGeometryBuilder.cs @@ -2,7 +2,7 @@ using System.Windows.Media; using MvvmCharting.Series; -namespace MvvmCharting.WpfFX +namespace MvvmCharting.WpfFX.Series { public class EllipseGeometryBuilder : IScatterGeometryBuilder { diff --git a/WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/ScatterGeometryBuilders/RectangleGeometryBuilder.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/ScatterGeometryBuilders/RectangleGeometryBuilder.cs similarity index 89% rename from WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/ScatterGeometryBuilders/RectangleGeometryBuilder.cs rename to WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/ScatterGeometryBuilders/RectangleGeometryBuilder.cs index 0b1d623..6fa7b3e 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/GeometryBuilders/ScatterGeometryBuilders/RectangleGeometryBuilder.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/ScatterGeometryBuilders/RectangleGeometryBuilder.cs @@ -2,7 +2,7 @@ using System.Windows.Media; using MvvmCharting.Series; -namespace MvvmCharting.WpfFX +namespace MvvmCharting.WpfFX.Series { public class RectangleGeometryBuilder : IScatterGeometryBuilder { diff --git a/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/ScatterSeries.cs b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/ScatterSeries.cs new file mode 100644 index 0000000..6b61a84 --- /dev/null +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/ScatterSeries.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using MvvmCharting.Common; +using MvvmCharting.Drawing; +using MvvmCharting.Series; + +namespace MvvmCharting.WpfFX.Series +{ + + public interface IScatterSeriesOwner + { + ISeriesControlOwner SeriesControlOwner { get; } + IList ItemsSource { get; } + + double XPixelPerUnit { get; } + double YPixelPerUnit { get; } + + PointNS[] GetCoordinates(); + PointNS GetPlotCoordinateForItem(object item, int itemIndex); + } + + /// + /// Represents a collection of scatters, and each scatter represents an item. + /// + [TemplatePart(Name = "PART_ScatterItemsControl", Type = typeof(SlimItemsControl))] + public class ScatterSeries : InteractiveControl + { + static ScatterSeries() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(ScatterSeries), new FrameworkPropertyMetadata(typeof(ScatterSeries))); + } + + private static readonly string sPART_ScatterItemsControl = "PART_ScatterItemsControl"; + private SlimItemsControl PART_ScatterItemsControl; + + + internal IScatterSeriesOwner Owner { get; set; } + + + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (this.PART_ScatterItemsControl != null) + { + this.PART_ScatterItemsControl.ElementGenerated -= ScatterItemsControlScatterGenerated; + + } + + this.PART_ScatterItemsControl = (SlimItemsControl)GetTemplateChild(sPART_ScatterItemsControl); + if (this.PART_ScatterItemsControl != null) + { + this.PART_ScatterItemsControl.ElementGenerated += ScatterItemsControlScatterGenerated; + + this.PART_ScatterItemsControl.SetBinding(SlimItemsControl.ItemTemplateSelectorProperty, new Binding(nameof(this.ScatterTemplateSelector)) { Source = this }); + this.PART_ScatterItemsControl.SetBinding(SlimItemsControl.ItemTemplateProperty, new Binding(nameof(this.ScatterTemplate)) { Source = this }); + + UpdateItemsSource(); + + } + + + + } + + protected virtual void ScatterItemsControlScatterGenerated(object sender, FrameworkElement root, int index) + { + var scatter = root as Scatter; + if (scatter == null) + { + MvvmChartException.ThrowDataTemplateRootElementException(nameof(this.ScatterTemplate), typeof(Scatter)); + } + + + + if (this.ScatterBrush != null && scatter.Fill == null) + { + scatter.SetCurrentValue(Scatter.FillProperty, this.ScatterBrush); + } + + var item = scatter.DataContext; + + if (!this.Owner.SeriesControlOwner.IsSeriesCollectionChanging) + { + if (!this.Owner.XPixelPerUnit.IsNaN() && !this.Owner.YPixelPerUnit.IsNaN()) + { + scatter.Coordinate = this.Owner.GetPlotCoordinateForItem(item, index); + } + } + } + + public DataTemplate ScatterTemplate + { + get { return (DataTemplate)GetValue(ScatterTemplateProperty); } + set { SetValue(ScatterTemplateProperty, value); } + } + public static readonly DependencyProperty ScatterTemplateProperty = + DependencyProperty.Register("ScatterTemplate", typeof(DataTemplate), typeof(ScatterSeries), new PropertyMetadata(null)); + + public DataTemplateSelector ScatterTemplateSelector + { + get { return (DataTemplateSelector)GetValue(ScatterTemplateSelectorProperty); } + set { SetValue(ScatterTemplateSelectorProperty, value); } + } + public static readonly DependencyProperty ScatterTemplateSelectorProperty = + DependencyProperty.Register("ScatterTemplateSelector", typeof(DataTemplateSelector), typeof(ScatterSeries), new PropertyMetadata(null)); + + public Brush ScatterBrush + { + get { return (Brush)GetValue(ScatterBrushProperty); } + set { SetValue(ScatterBrushProperty, value); } + } + public static readonly DependencyProperty ScatterBrushProperty = + DependencyProperty.Register("ScatterBrush", typeof(Brush), typeof(ScatterSeries), new PropertyMetadata(null, OnScatterBrushPropertyChanged)); + + private static void OnScatterBrushPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + //((ScatterSeries)d).OnScatterBrushChanged(); + } + + private void OnScatterBrushChanged() + { + + if (this.PART_ScatterItemsControl == null) + { + return; + } + + foreach (var scatter in this.PART_ScatterItemsControl.GetChildren().OfType()) + { + if (this.ScatterBrush == null || + DependencyPropertyHelper.GetValueSource(scatter, Scatter.FillProperty).IsCurrent) + { + scatter.SetCurrentValue(Scatter.FillProperty, this.ScatterBrush); + } + } + + } + internal void UpdateItemsSource() + { + if (this.PART_ScatterItemsControl != null && this.Owner != null) + { + this.PART_ScatterItemsControl.ItemsSource = this.Owner.ItemsSource; + } + } + + internal void UpdateScatterCoordinate(object item, PointNS coordinate) + { + var scatter = (Scatter)this.PART_ScatterItemsControl?.TryGetChildForItem(item); + if (scatter != null) + { + scatter.Coordinate = coordinate; + } + + } + + internal void UpdateCoordinate() + { + if (this.PART_ScatterItemsControl == null) + { + return; + } + + var coordinates = this.Owner.GetCoordinates(); + + if (coordinates == null) + { + + return; + } + + if (coordinates.Length != this.PART_ScatterItemsControl.ItemCount) + { + throw new NotImplementedException(); + } + + for (int i = 0; i < coordinates.Length; i++) + { + var item = this.PART_ScatterItemsControl.ItemsSource[i]; + UpdateScatterCoordinate(item, coordinates[i]); + } + + + } + + + + + + } +} diff --git a/WPF/WpfFX/MvvmChartWpfFX/Series/Scatter/ScatterStyle.xaml b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/ScatterSeriesStyle.xaml similarity index 73% rename from WPF/WpfFX/MvvmChartWpfFX/Series/Scatter/ScatterStyle.xaml rename to WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/ScatterSeriesStyle.xaml index bf73a06..19fa861 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/Series/Scatter/ScatterStyle.xaml +++ b/WPF/WpfFX/MvvmChartWpfFX/SeriesControl/SeriesParts/ScatterSeries/ScatterSeriesStyle.xaml @@ -1,11 +1,12 @@  + xmlns:local="clr-namespace:MvvmCharting.WpfFX.Series" + xmlns:wpfFx="clr-namespace:MvvmCharting.WpfFX"> - + + \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/Themes/Generic.xaml b/WPF/WpfFX/MvvmChartWpfFX/Themes/Generic.xaml index de0ce4f..a7368f9 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/Themes/Generic.xaml +++ b/WPF/WpfFX/MvvmChartWpfFX/Themes/Generic.xaml @@ -3,16 +3,17 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MvvmCharting.WpfFX" xmlns:axis1="clr-namespace:MvvmCharting.Axis;assembly=CommonLib" - xmlns:axis="clr-namespace:MvvmCharting.WpfFX.Axis"> + xmlns:axis="clr-namespace:MvvmCharting.WpfFX.Axis" + xmlns:series="clr-namespace:MvvmCharting.WpfFX.Series"> - - - + + + - - + + @@ -74,7 +75,6 @@ @@ -83,7 +83,7 @@ - + @@ -107,4 +107,6 @@ + + diff --git a/WPF/WpfFX/MvvmChartWpfFX/common/InteractiveControl.cs b/WPF/WpfFX/MvvmChartWpfFX/common/InteractiveControl.cs new file mode 100644 index 0000000..5ae26b4 --- /dev/null +++ b/WPF/WpfFX/MvvmChartWpfFX/common/InteractiveControl.cs @@ -0,0 +1,64 @@ +using System; +using System.Windows; +using System.Windows.Controls; + +namespace MvvmCharting.WpfFX +{ + /// + /// Represents a control that can be highlighted and selected + /// + public class InteractiveControl : Control + { + public event Action IsHighlightedChanged; + public event Action IsSelectedChanged; + + protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + + base.OnPropertyChanged(e); + if (e.Property == IsMouseOverProperty) + { + this.SetCurrentValue(IsHighlightedProperty, this.IsMouseOver); + //this.IsHighlighted = this.IsMouseOver; + } + } + + public bool IsHighlighted + { + get { return (bool)GetValue(IsHighlightedProperty); } + set { SetValue(IsHighlightedProperty, value); } + } + public static readonly DependencyProperty IsHighlightedProperty = + DependencyProperty.Register("IsHighlighted", typeof(bool), typeof(InteractiveControl), new PropertyMetadata(false, OnIsHighlightedPropertyChanged + )); + + private static void OnIsHighlightedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((InteractiveControl)d).OnIsHighlightedChanged(); + } + + protected virtual void OnIsHighlightedChanged() + { + this.IsHighlightedChanged?.Invoke(this, this.IsHighlighted); + } + + + public bool IsSelected + { + get { return (bool)GetValue(IsSelectedProperty); } + set { SetValue(IsSelectedProperty, value); } + } + public static readonly DependencyProperty IsSelectedProperty = + DependencyProperty.Register("IsSelected", typeof(bool), typeof(InteractiveControl), new PropertyMetadata(false, OnIsSelectedPropertyChanged)); + + private static void OnIsSelectedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((InteractiveControl)d).OnIsSelectedChanged(); + } + + protected virtual void OnIsSelectedChanged() + { + this.IsSelectedChanged?.Invoke(this, this.IsSelected); + } + } +} \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/common/SizeHelper.cs b/WPF/WpfFX/MvvmChartWpfFX/common/SizeHelper.cs index e309dc0..f3980d4 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/common/SizeHelper.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/common/SizeHelper.cs @@ -9,5 +9,15 @@ public static bool IsInvalid(this Size size) { return size.IsEmpty || (size.Width + size.Height).IsNaNOrZero(); } + + public static bool NearlyEqual(this Size size, Size other) + { + if (size.IsEmpty && other.IsEmpty) + { + return true; + } + + return size.Width.NearlyEqual(other.Width, 0.5) && size.Height.NearlyEqual(other.Height, 0.5); + } } } \ No newline at end of file diff --git a/WPF/WpfFX/MvvmChartWpfFX/SlimItemsControl/SlimItemsControl.cs b/WPF/WpfFX/MvvmChartWpfFX/common/SlimItemsControl/SlimItemsControl.cs similarity index 97% rename from WPF/WpfFX/MvvmChartWpfFX/SlimItemsControl/SlimItemsControl.cs rename to WPF/WpfFX/MvvmChartWpfFX/common/SlimItemsControl/SlimItemsControl.cs index 6b0dfe5..0fb4daa 100644 --- a/WPF/WpfFX/MvvmChartWpfFX/SlimItemsControl/SlimItemsControl.cs +++ b/WPF/WpfFX/MvvmChartWpfFX/common/SlimItemsControl/SlimItemsControl.cs @@ -99,13 +99,21 @@ public FrameworkElement TryGetChildForItem(object item) public IEnumerable GetChildren() { - if (this._itemChildMap.Count != this.PART_Root.Children.Count) + if (this.PART_Root == null) { - throw new NotImplementedException(); + Debug.Assert(this._itemChildMap.Count == 0); + return Enumerable.Empty(); } + Debug.Assert(this._itemChildMap.Count == this.PART_Root.Children.Count); + return this.PART_Root.Children.OfType(); } + + public int GetChildIndex(FrameworkElement child) + { + return this.PART_Root.Children.IndexOf(child); + } #endregion public IList ItemsSource @@ -127,7 +135,7 @@ private static void OnItemsSourcePropertyChanged(DependencyObject d, DependencyP private void OnItemsSourceChanged(IList oldValue, IList newValue) { #if DEBUG_SlimItemsControl - Debug.WriteLine($"{this.Name}({this.GetHashCode()})....OnItemsSourceChanged...."); + Debug.WriteLine($"{this.Name}({this.GetHashCode()})....OnItemsSourceChanged....newValue={newValue}"); #endif @@ -434,6 +442,7 @@ public void LoadAllItems() if (object.ReferenceEquals(this._handledItemsSource, this.ItemsSource)) { + return; } diff --git a/WPF/WpfFX/MvvmChartWpfFX/SlimItemsControl/SlimItemsControlStyle.xaml b/WPF/WpfFX/MvvmChartWpfFX/common/SlimItemsControl/SlimItemsControlStyle.xaml similarity index 100% rename from WPF/WpfFX/MvvmChartWpfFX/SlimItemsControl/SlimItemsControlStyle.xaml rename to WPF/WpfFX/MvvmChartWpfFX/common/SlimItemsControl/SlimItemsControlStyle.xaml