public override Axis Generate(AxisLabelerOptions options, double density)
        {
            var m = (((options.AxisLocation == AxisLocation.Top || options.AxisLocation == AxisLocation.Bottom) ? options.Screen.Width : options.Screen.Height) * density);
            m = Math.Max(m, 2);

            var v = (Numeric)options.Symbol;

            //loose labeling
            var vMin = v.Range.Min;
            var vMax = v.Range.Max;
            var range = heckbertNiceNum(vMax - vMin, false);
            var d = heckbertNiceNum(range / (m - 1), true);
            var rMin = Math.Floor(vMin / d) * d;
            var rMax = Math.Ceiling(vMax / d) * d;

            var labels = new List<AxisLabel>();
            var option = options.DefaultAxis();

            var currX = rMin;
            while (currX <= rMax + 0.5 * d)
            {
                labels.Add(new AxisLabel(currX, currX.ToString(CultureInfo.InvariantCulture)));
                currX += d;
            }

            option.VisibleRange = new Range(rMin, currX - d);

            option.Labels = labels;
            option.Score = 1;

            return option;
        }
 protected double legibilityScoreMax(Axis data, AxisLabelerOptions options)
 {
     return (legibility_format(data, options) +
             legibility_fontSize(data, options) +
             legibility_orientation(data, options) +
             1) / 4;
 }
 protected double legibility_fontSize(Axis data, AxisLabelerOptions options)
 {
     var fsmin = fontSizes.Min();
     return (data.FontSize > options.FontSize || data.FontSize < fsmin)
                ? double.NegativeInfinity
                : ((data.FontSize == options.FontSize)
                       ? 1
                       : 0.2 * ((data.FontSize - fsmin + 1) / (options.FontSize - fsmin)));
 }
        public override Axis Generate(AxisLabelerOptions options, double density)
        {
            var f = (Factor)options.Symbol;

            var labels = f.AllLevels.Select(level => new AxisLabel(level.LevelIndex, level.ToString())).ToList();

            var result = options.DefaultAxis();
            result.Labels = labels;

            return result;
        }
        public override Axis Generate(AxisLabelerOptions options, double density)
        {
            //int nbins = 9;      // in the actual Matplotlib implementation this was fixed at 9. here we let it vary like the other methods for a better comparison
            var m = (((options.AxisLocation == AxisLocation.Top || options.AxisLocation == AxisLocation.Bottom) ? options.Screen.Width : options.Screen.Height) * density);
            var nbins = (int)Math.Max(m, 2);

            var steps = new List<double> { 1, 2, 5, 10 };

            var vMin = options.DataRange.Min;
            var vMax = options.DataRange.Max;

            var value = matPlotLibScaleRange(vMin, vMax, nbins);
            var scale = value.Item1;
            var offset = value.Item2;

            vMin -= offset;
            vMax -= offset;

            var rawStep = (vMax - vMin) / nbins;
            var scaledRawStep = rawStep / scale;

            var bestMax = vMax;
            var bestMin = vMin;

            double scaledStep = 1;
            foreach (int step in steps)
            {
                if (step < scaledRawStep) continue;
                scaledStep = step * scale;
                bestMin = scaledStep * Math.Floor(vMin / scaledStep);
                bestMax = bestMin + scaledStep * nbins;
                if (bestMax >= vMax) break;
            }
            var extraBins = (int)Math.Floor((bestMax - vMax) / scaledStep);
            nbins -= extraBins;

            var labels = new List<AxisLabel>();
            var option = options.DefaultAxis();

            //compute actual labels
            for (var i = 0; i <= nbins; i++)
            {
                var labelVal = bestMin + i * scaledStep + offset;
                labels.Add(new AxisLabel(labelVal, labelVal.ToString(CultureInfo.InvariantCulture)));
            }
            option.Labels = labels;
            option.Score = 1;

            option.VisibleRange = new Range(bestMin + offset, bestMin + nbins * scaledStep + offset);

            return option;
        }
 public AxisLayout(AxisLocation axisLocation, Vector symbol, Range dataRange, Range visibleRange, Func<string, double, Axis, AxisLabelerOptions, Rect> computeLabelRect, Rect screen)
 {
     _options = new AxisLabelerOptions
     {
         AxisLocation = axisLocation,
         Symbol = symbol,
         DataRange = dataRange,
         VisibleRange = visibleRange,
         FontSize = AxisFontSize,
         ComputeLabelRect = computeLabelRect,
         Screen = screen
     };
 }
        static Axis Helper(AxisLabelerOptions options, double m, double mincoverage = 0.8)
        {
            var snice = double.NegativeInfinity;

            var intervals = (int)Math.Max(Math.Floor(m), 2) - 1;

            var v = (Numeric)options.Symbol;
            var min = v.Range.Min;
            var max = v.Range.Max;

            var Q = new List<double> { 1.0, 10.0, 5.0, 2.0, 2.5, 3.0, 4.0, 1.5, 7.0, 6.0, 8.0, 9.0 };

            var range = max - min;
            var dc = range / intervals;
            var dbase = Math.Pow(10, Math.Floor(Math.Log10(dc)));

            Axis best = null;

            foreach (var q in Q)
            {
                var tdelta = q * dbase;
                var tmin = Math.Floor(min / tdelta) * tdelta;
                var tmax = tmin + intervals * tdelta;

                var i = Q.IndexOf(q);
                var roundness = 1.0 - ((i + 1) - ((tmin <= 0 && tmax >= 0) ? 1.0 : 0.0)) / Q.Count();
                var coverage = (max - min) / (tmax - tmin);

                if (coverage <= mincoverage || tmin > min || tmax < max) continue;
                var score = roundness + coverage;

                if (score <= snice) continue;
                var stepSequence = Enumerable.Range(0, intervals + 1).Select(x => tmin + x * tdelta).ToList();
                //var newlabels = stepSequence.Select(value => Tuple.Create(value, value.ToString(CultureInfo.InvariantCulture))).ToList();
                var newlabels = stepSequence.Select(value => new AxisLabel(value, value.ToString(CultureInfo.InvariantCulture))).ToList();

                var candidate = options.DefaultAxis();
                candidate.Score = score;
                candidate.Labels = newlabels;
                best = candidate;

                snice = score;
            }

            return best;
        }
        public Axis Generate(AxisLabelerOptions options, double density, double mincoverage)
        {
            var m = (((options.AxisLocation == AxisLocation.Top || options.AxisLocation == AxisLocation.Bottom) ? options.Screen.Width : options.Screen.Height) * density);
            m = Math.Max(m, 2);

            Axis best = null;

            for (var i = 2; i < 12; i++)
            {
                var b = Helper(options, i, mincoverage);
                var granularity = 1 - Math.Abs(i - m) / m;
                if (b == null || (best != null && b.Score + granularity <= best.Score)) continue;
                best = b;
                best.Score += granularity;
            }

            if (best != null) best.VisibleRange = new Range(Math.Min(options.VisibleRange.Min, best.Labels.Min(t => t.Value)), Math.Max(options.VisibleRange.Max, best.Labels.Max(t => t.Value)));

            return best;
        }
        public override Axis Format(List<Axis> list, List<Format> formats, AxisLabelerOptions options, Func<Axis, double> ScoreAxis, double bestScore = double.NegativeInfinity)
        {
            var result = options.DefaultAxis();
            foreach (var data in list)
                foreach (var format in formats)
                {
                    var f = data.Clone();
                    f.FormatStyle = format;
                    f.Legibility = legibilityScoreMax(f, options);

                    if (ScoreAxis(f) < bestScore) continue;
                    var labels = f.FormatStyle.FormalLabels(f.Labels.Select(x => (object)x.Value).ToList());
                    f.Labels = f.Labels.Select(x => x.Value).Zip(labels.Item1, (value, label) => new AxisLabel(value, label)).ToList();
                    f.AxisTitleExtension = labels.Item2;
                    f.Legibility = legibilityScore(f, options);
                    f.Score = ScoreAxis(f);
                    if (f.Score < bestScore) continue;
                    bestScore = f.Score;
                    result = f;
                }
            return result;
        }
        protected double legibility_overlap(Axis data, AxisLabelerOptions options)
        {
            // compute overlap score
            var em = ems[data.FontSize];
            var rects = data.Labels.Select(s => options.ComputeLabelRect(s.Label, s.Value, data, options)).ToList();
            // takes adjacent pairs of rectangles
            var take = rects.Take(rects.Count - 1).ToList();
            var skip = rects.Skip(1).ToList();
            var zip = take.Zip(skip, (a, b) =>
                               {
                                   var dist = (options.AxisLocation == AxisLocation.Top || options.AxisLocation == AxisLocation.Bottom) ? b.Left - a.Right : a.Top - b.Bottom;
                                   return Math.Min(1, 2 - (1.5 * em) / Math.Max(0, dist));
                               }).ToList();
            var overlap = zip.Min();
#if false
            var overlap = rects.Take(rects.Count - 1).Zip(rects.Skip(1),
                                                            (a, b) =>
                                                            {
                                                                var dist = (options.AxisLocation == AxisLocation.Top || options.AxisLocation == AxisLocation.Bottom) ? b.Left - a.Right : a.Top - b.Bottom;
                                                                return Math.Min(1, 2 - (1.5 * em) / Math.Max(0, dist));
                                                            }).Min();
#endif
            return overlap;
        }
 public override Axis Generate(AxisLabelerOptions options, double density) { return Generate(options, density, 0.8); }
 public abstract Axis Generate(AxisLabelerOptions labeler, double m);
 public override Axis Generate(AxisLabelerOptions options, double m) { return options.DefaultAxis(); }
 protected double legibility_orientation(Axis data, AxisLabelerOptions options) { return data.LabelDirection == AxisDirection.Horizontal ? 1.0 : -0.5; }
        public override Axis Generate(AxisLabelerOptions options, double density)
        {
            var formatter = new QuantitativeFormatter(options.Typeface.FontFamily);
            var space = ((options.AxisLocation == AxisLocation.Top || options.AxisLocation == AxisLocation.Bottom) ? options.Screen.Width : options.Screen.Height);

            var dmax = options.DataRange.Max;
            var dmin = options.DataRange.Min;

            if (dmax == dmin) return null;

            Axis best = null;
            double bestScore = -2;

            var j = 1;
            while (j < int.MaxValue)
            {
                foreach (var q in Q)
                {
                    var sm = max_simplicity(q, Q, j);
                    if (w[0] * sm + w[1] + w[2] + w[3] < bestScore)
                    {
                        j = int.MaxValue - 1;
                        break;
                    }

                    var k = 2;
                    while (k < int.MaxValue)
                    {
                        var dm = max_density(k / space, density);

                        if (w[0] * sm + w[1] + w[2] * dm + w[3] < bestScore) break;

                        var delta = (dmax - dmin) / (k + 1) / (j * q);
                        var z = (int)Math.Ceiling(Math.Log10(delta));

                        while (z < int.MaxValue)
                        {
                            var step = j * q * Math.Pow(10, z);
                            var cm = max_coverage(dmin, dmax, step * (k - 1));

                            if (w[0] * sm + w[1] * cm + w[2] * dm + w[3] < bestScore) break;

                            for (var start = (int)(Math.Floor(dmax / step - (k - 1)) * j); start <= (int)(Math.Ceiling(dmin / step)) * j; start++)
                            {
                                var lmin = start * step / j;
                                var lmax = lmin + step * (k - 1);

                                var s = simplicity(q, Q, j, lmin, lmax, step);
                                var d = this.density(k / space, density);
                                var c = coverage(dmin, dmax, lmin, lmax);

                                if (w[0] * s + w[1] * c + w[2] * d + w[3] < bestScore) continue;

                                var option = options.DefaultAxis();

                                var stepSequence = Enumerable.Range(0, k).Select(x => lmin + x * step).ToList();
                                var newlabels = stepSequence.Select(value => new AxisLabel(value, value.ToString(CultureInfo.InvariantCulture))).ToList();

                                option.Labels = newlabels;
                                option.Granularity = d;
                                option.Coverage = c;
                                option.Simplicity = s;
                                option.Score = s + c + d;

                                //format and choose best
                                var subPossibilities = new List<Axis> { option };
                                var optionFormatted = formatter.Format(
                                                                       formatter.varyOrientation(formatter.varyFontSize(subPossibilities, options)),
                                                                       formats,
                                                                       options,
                                                                       a => w[0] * a.Simplicity + w[1] * a.Coverage + w[2] * a.Granularity + w[3] * a.Legibility,
                                                                       bestScore);

                                var score = w[0] * optionFormatted.Simplicity + w[1] * optionFormatted.Coverage +
                                            w[2] * optionFormatted.Granularity + w[3] * optionFormatted.Legibility;

                                if (score <= bestScore) continue;
                                bestScore = score;
                                optionFormatted.Score = score;
                                best = optionFormatted;
                            }

                            z = z + 1;
                        }

                        k = k + 1;
                    }
                }

                j = j + 1;
            }

            //if (best == null) Console.WriteLine(@"WARNING: Extended algorithm found 0 solutions");
            //else best.VisibleRange = new Range(Math.Min(options.VisibleRange.Min, best.Labels.Min(t => t.Value)), Math.Max(options.VisibleRange.Max, best.Labels.Max(t => t.Value)));
            if (best != null) best.VisibleRange = new Range(Math.Min(options.VisibleRange.Min, best.Labels.Min(t => t.Value)), Math.Max(options.VisibleRange.Max, best.Labels.Max(t => t.Value)));
            return best;
        }
        public DataAxis()
        {
            SnapsToDevicePixels = true;
            UseLayoutRounding = true;
            AxisTicks = new ObservableList<DataAxisTick>();
            SizeChanged += (s, e) => OnSizeChanged();
            _axisOptions = new AxisLabelerOptions
            {
                ComputeLabelRect = ComputeLabelRect,
                FontSize = TextBlock.GetFontSize(this),
                Typeface = new Typeface(TextBlock.GetFontFamily(this), TextBlock.GetFontStyle(this), TextBlock.GetFontWeight(this), TextBlock.GetFontStretch(this))
            };
            _axisLabeler = new ExtendedAxisLabeler();

            SetValue(VisibleRangePropertyKey, _visibleRange);
            _visibleRange.ObserveOnDispatcher().Subscribe(e =>
            {
                //if (_showDebugMessages) Debug.WriteLine(string.Format("{0:HH:mm:ss.fff} DataAxis: Axis \"{1}\": Updated _visbleRange to: {2}", DateTime.Now, AxisLabel, _visibleRange));
                VisibleRangeChanged();
                SetValue(VisibleRangePropertyKey, new Range(_visibleRange));
            });

            var presentationSource = PresentationSource.FromVisual(this);
            if (presentationSource == null || presentationSource.CompositionTarget == null) return;
            var matrix = presentationSource.CompositionTarget.TransformToDevice;
            _pixelsPerInch = Math.Max(matrix.M11, matrix.M22);
        }
 Rect ComputeLabelRect(string label, double position, Axis axis, AxisLabelerOptions options)
 {
     var axisPosition = options.AxisTransform.Transform(new Point(position, MajorTickLength));
     var text = new FormattedText(label, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, options.Typeface, options.FontSize, Brushes.Black);
     var left = axisPosition.X;
     var top = axisPosition.Y;
     switch (axis.AxisLocation)
     {
         case AxisLocation.Top:
             left -= text.Width / 2;
             top -= text.Height;
             break;
         case AxisLocation.Bottom:
             left -= text.Width / 2;
             break;
         case AxisLocation.Left:
             top -= text.Height / 2;
             left -= text.Width;
             break;
         case AxisLocation.Right:
             top -= text.Height / 2;
             break;
         default:
             throw new ApplicationException("DataAxis: Unknown AxisLocation value.");
     }
     return new Rect(new Point(left, top), new Size(text.Width, text.Height));
 }
 public List<Axis> varyFontSize(List<Axis> list, AxisLabelerOptions options)
 {
     var possibilities = new List<Axis>();
     // Reverse to produce the font sizes in decreasing order of goodness
     foreach (var size in fontSizes.Where(s => s <= options.FontSize).Reverse())
         foreach (var data in list)
         {
             var option = data.Clone();
             option.FontSize = size;
             possibilities.Add(option);
         }
     return possibilities;
 }
 protected double legibility_format(Axis data, AxisLabelerOptions options)
 {
     var format = data.FormatStyle.Score(data.Labels.Select(x => (object)x.Value).ToList());
     return format;
 }
 public abstract Axis Format(List<Axis> list, List<Format> formats, AxisLabelerOptions options, Func<Axis, double> ScoreAxis, double bestScore = double.NegativeInfinity);