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);