//The MIT License(MIT) //Copyright(c) 2016 Alberto Rodriguez & LiveCharts Contributors //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following conditions: //The above copyright notice and this permission notice shall be included in all //copies or substantial portions of the Software. //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE //SOFTWARE. using System; using System.Collections.Generic; using System.Linq; using LiveCharts.Definitions.Charts; using LiveCharts.Definitions.Series; using LiveCharts.Dtos; using LiveCharts.Helpers; namespace LiveCharts.Charts { /// /// Chart Model /// public class CartesianChartCore : ChartCore { #region Constructors /// /// Initializes Chart model /// /// The view. /// The updater. public CartesianChartCore(IChartView view, ChartUpdater updater) : base(view, updater) { updater.Chart = this; } #endregion #region Publics /// /// Prepares Chart Axes /// public override void PrepareAxes() { base.PrepareAxes(); if (View.ActualSeries.Any(x => !(x.Model is ICartesianSeries))) throw new LiveChartsException( "There is a invalid series in the series collection, " + "verify that all the series implement ICartesianSeries."); var cartesianSeries = View.ActualSeries.Select(x => x.Model).Cast().ToArray(); for (var index = 0; index < AxisX.Count; index++) { var xi = AxisX[index]; xi.CalculateSeparator(this, AxisOrientation.X); // ReSharper disable once AccessToModifiedClosure SetAxisLimits(xi, cartesianSeries.Where(x => x.View.ScalesXAt == index).ToArray(), AxisOrientation.X); if (Math.Abs(xi.BotLimit - xi.TopLimit) < xi.S * .01) { if (Math.Abs(xi.PreviousBot - xi.PreviousTop) < xi.S * .01) { if (double.IsNaN(xi.MinValue)) xi.BotLimit -= xi.S; else xi.BotLimit = xi.MinValue; if (double.IsNaN(xi.MaxValue)) xi.TopLimit += xi.S; else xi.TopLimit = xi.MaxValue; if (Math.Abs(xi.BotLimit - xi.TopLimit) < xi.S * .01 && !View.IsInDesignMode) throw new LiveChartsException("One axis has an invalid range, it is or it is " + "tends to zero, please ensure your axis has a valid " + "range"); } else { xi.BotLimit = xi.PreviousBot; xi.TopLimit = xi.PreviousTop; } } xi.PreviousBot = xi.BotLimit; xi.PreviousTop = xi.TopLimit; } for (var index = 0; index < AxisY.Count; index++) { var yi = AxisY[index]; yi.CalculateSeparator(this, AxisOrientation.Y); // ReSharper disable once AccessToModifiedClosure SetAxisLimits(yi, cartesianSeries.Where(x => x.View.ScalesYAt == index).ToArray(), AxisOrientation.Y); if (Math.Abs(yi.BotLimit - yi.TopLimit) < yi.S * .01) { if (Math.Abs(yi.PreviousBot - yi.PreviousTop) < yi.S * .01) { if (double.IsNaN(yi.MinValue)) yi.BotLimit -= yi.S; else yi.BotLimit = yi.MinValue; if (double.IsNaN(yi.MaxValue)) yi.TopLimit += yi.S; else yi.TopLimit = yi.MaxValue; if (Math.Abs(yi.BotLimit - yi.TopLimit) < yi.S * .01) throw new LiveChartsException("One axis has an invalid range, it is or it " + "tends to zero, please ensure your axis has a valid " + "range"); } else { yi.BotLimit = yi.PreviousBot; yi.TopLimit = yi.PreviousTop; } } yi.PreviousBot = yi.BotLimit; yi.PreviousTop = yi.TopLimit; } PrepareSeries(); CalculateComponentsAndMargin(); DrawOrUpdateSections(); AreComponentsLoaded = true; } /// /// Runs the specialized chart components. /// public override void RunSpecializedChartComponents() { foreach (var visualElement in ((ICartesianChart) View).VisualElements) { visualElement.AddOrMove(this); } } /// /// Draws the or update sections. /// public void DrawOrUpdateSections() { for (var index = 0; index < AxisX.Count; index++) { var xi = AxisX[index]; foreach (var section in xi.Sections) { section.AxisIndex = index; section.Source = AxisOrientation.X; section.View.DrawOrMove(AxisOrientation.X, index); } } for (var index = 0; index < AxisY.Count; index++) { var yi = AxisY[index]; foreach (var section in yi.Sections) { section.AxisIndex = index; section.Source = AxisOrientation.Y; section.View.DrawOrMove(AxisOrientation.Y, index); } } } #endregion #region Privates private static void SetAxisLimits(AxisCore ax, IList series, AxisOrientation orientation) { var first = new CoreLimit(); var firstR = 0d; if (series.Count > 0) { first = orientation == AxisOrientation.X ? new CoreLimit(series[0].GetMinX(ax), series[0].GetMaxX(ax)) : new CoreLimit(series[0].GetMinY(ax), series[0].GetMaxY(ax)); var view = series[0].View as IAreaPoint; firstR = view != null ? view.GetPointDiameter() : 0; } // [ max, min, pointRadius ] var boundries = new[] { first.Max, first.Min, firstR }; for (var index = 1; index < series.Count; index++) { var cartesianSeries = series[index]; var limit = orientation == AxisOrientation.X ? new CoreLimit(cartesianSeries.GetMinX(ax), cartesianSeries.GetMaxX(ax)) : new CoreLimit(cartesianSeries.GetMinY(ax), cartesianSeries.GetMaxY(ax)); var view = cartesianSeries.View as IAreaPoint; var radius = view != null ? view.GetPointDiameter() : 0; if (limit.Max > boundries[0]) boundries[0] = limit.Max; if (limit.Min < boundries[1]) boundries[1] = limit.Min; if (radius > boundries[2]) boundries[2] = radius; } ax.TopSeriesLimit = boundries[0]; ax.BotSeriesLimit = boundries[1]; ax.TopLimit = double.IsNaN(ax.MaxValue) ? boundries[0] : ax.MaxValue; ax.BotLimit = double.IsNaN(ax.MinValue) ? boundries[1] : ax.MinValue; ax.MaxPointRadius = boundries[2]; } private void PrepareSeries() { PrepareUnitWidth(); PrepareWeight(); PrepareStackedColumns(); PrepareStackedRows(); PrepareStackedAreas(); PrepareVerticalStackedAreas(); } private void PrepareWeight() { if (!View.ActualSeries.Any(x => x is IScatterSeriesView || x is IHeatSeriesView)) return; var vs = View.ActualSeries .Select(x => x.ActualValues.GetTracker(x).WLimit) .DefaultIfEmpty(new CoreLimit()).ToArray(); WLimit = new CoreLimit(vs.Select(x => x.Min).DefaultIfEmpty(0).Min(), vs.Select(x => x.Max).DefaultIfEmpty(0).Max()); } private void PrepareUnitWidth() { foreach (var series in View.ActualSeries) { if (series is IStackedColumnSeriesView || series is IColumnSeriesView || series is IFinancialSeriesView || series is IHeatSeriesView) { AxisX[series.ScalesXAt].EvaluatesUnitWidth = true; } if (series is IStackedRowSeriesView || series is IRowSeriesView || series is IHeatSeriesView) { AxisY[series.ScalesYAt].EvaluatesUnitWidth = true; } } } private void PrepareStackedColumns() { if (!View.ActualSeries.Any(x => x is IStackedColumnSeriesView)) return; var isPercentage = View.ActualSeries.Any(x => x is IStackModelableSeriesView && x is IStackedColumnSeriesView && ((IStackModelableSeriesView) x).StackMode == StackMode.Percentage); foreach (var group in View.ActualSeries.OfType().GroupBy(x => x.ScalesYAt)) { StackPoints(group, AxisOrientation.Y, group.Key, isPercentage ? StackMode.Percentage : StackMode.Values); } } private void PrepareStackedRows() { if (!View.ActualSeries.Any(x => x is IStackedRowSeriesView)) return; var isPercentage = View.ActualSeries.Any(x => x is IStackModelableSeriesView && x is IStackedRowSeriesView && ((IStackModelableSeriesView) x).StackMode == StackMode.Percentage); foreach (var group in View.ActualSeries.OfType().GroupBy(x => x.ScalesXAt)) { StackPoints(group, AxisOrientation.X, group.Key, isPercentage ? StackMode.Percentage : StackMode.Values); } } private void PrepareStackedAreas() { if (!View.ActualSeries.Any(x => x is IStackedAreaSeriesView)) return; var isPercentage = View.ActualSeries.Any(x => x is IStackModelableSeriesView && x is IStackedAreaSeriesView && ((IStackModelableSeriesView) x).StackMode == StackMode.Percentage); foreach (var group in View.ActualSeries.OfType().GroupBy(x => x.ScalesYAt)) { StackPoints(group, AxisOrientation.Y, group.Key, isPercentage ? StackMode.Percentage : StackMode.Values); } } private void PrepareVerticalStackedAreas() { if (!View.ActualSeries.Any(x => x is IVerticalStackedAreaSeriesView)) return; var isPercentage = View.ActualSeries.Any(x => x is IStackModelableSeriesView && x is IVerticalStackedAreaSeriesView && ((IStackModelableSeriesView) x).StackMode == StackMode.Percentage); foreach (var group in View.ActualSeries.OfType().GroupBy(x => x.ScalesXAt)) { StackPoints(group, AxisOrientation.X, group.Key, isPercentage ? StackMode.Percentage : StackMode.Values); } } #endregion } }