void DoTickLabels(IChartRenderContext icrc) { var tc = new TickCalculator(Minimum, Maximum); _trace.Verbose($"grid range:{tc.Range} tintv:{tc.TickInterval}"); // TODO may want to include the LabelStyle's padding if defined var padding = AxisLineThickness + 2 * AxisMargin; var tbr = new Recycler <FrameworkElement, ItemState>(TickLabels.Select(tl => tl.tb), (ist) => { var fe = CreateElement(ist); fe.Width = icrc.Area.Width - padding; if (fe is TextBlock tbb) { tbb.Padding = Side == Side.Right ? new Thickness(padding, 0, 0, 0) : new Thickness(0, 0, padding, 0); } return(fe); }); var itemstate = new List <ItemState>(); // materialize the ticks var lx = tc.GetTicks().ToArray(); var sc = new ValueAxisSelectorContext(this, icrc.Area, lx, tc.TickInterval); for (int ix = 0; ix < lx.Length; ix++) { //_trace.Verbose($"grid vx:{tick}"); sc.SetTick(ix); var createit = true; if (LabelSelector != null) { // ask the label selector var ox = LabelSelector.Convert(sc, typeof(bool), null, System.Globalization.CultureInfo.CurrentUICulture.Name); if (ox is bool bx) { createit = bx; } else { createit = ox != null; } } if (!createit) { continue; } var current = tbr.Next(null); var tick = lx[ix]; if (!current.Item1) { // restore binding if we are using a LabelFormatter if (LabelFormatter != null && LabelStyle != null) { BindTo(this, nameof(LabelStyle), current.Item2, FrameworkElement.StyleProperty); } } // default text var text = tick.Value.ToString(String.IsNullOrEmpty(LabelFormatString) ? "G" : LabelFormatString); if (LabelFormatter != null) { // call for Style, String override var format = LabelFormatter.Convert(sc, typeof(Tuple <Style, String>), null, System.Globalization.CultureInfo.CurrentUICulture.Name); if (format is Tuple <Style, String> ovx) { if (ovx.Item1 != null) { current.Item2.Style = ovx.Item1; } if (ovx.Item2 != null) { text = ovx.Item2; } } } var shim = new TextShim() { Text = text }; current.Item2.DataContext = shim; BindTo(shim, nameof(Visibility), current.Item2, UIElement.VisibilityProperty); var state = new ItemState() { tb = current.Item2, tick = tick }; state.SetLocation(icrc.Area.Left, tick.Value); sc.Generated(tick); itemstate.Add(state); } // VT and internal bookkeeping TickLabels = itemstate; Layer.Remove(tbr.Unused); Layer.Add(tbr.Created); foreach (var xx in TickLabels) { // force it to measure; needed for Transforms xx.tb.Measure(icrc.Dimensions); } }
/// <summary> /// Grid coordinates: /// x: "normalized" [0..1] and scaled to the area-width /// y: "axis" scale /// </summary> /// <param name="icrc"></param> void IRequireRender.Render(IChartRenderContext icrc) { if (ValueAxis == null) { return; } if (double.IsNaN(ValueAxis.Maximum) || double.IsNaN(ValueAxis.Minimum)) { return; } // grid lines var tc = new TickCalculator(ValueAxis.Minimum, ValueAxis.Maximum); var recycler = new Recycler <Path, ItemState>(GridLines.Where(tl => tl.Element != null).Select(tl => tl.Element), (ist) => { return(CreateElement(ist)); }); var mrecycler = new Recycler <Path, ItemState>(MinorGridLines.Where(tl => tl.Element != null).Select(tl => tl.Element), (ist) => { return(CreateSubElement(ist)); }); var itemstate = new List <ItemState>(); var mitemstate = new List <ItemState>(); //_trace.Verbose($"grid range:{tc.Range} tintv:{tc.TickInterval}"); var tixarray = tc.GetTicks().ToArray(); var sc = new ValueAxisSelectorContext(ValueAxis, icrc.SeriesArea, tixarray, tc.TickInterval); for (int ix = 0; ix < tixarray.Length; ix++) { var tick = tixarray[ix]; //_trace.Verbose($"grid vx:{tick}"); var state = new ItemState() { Tick = tick }; var current = recycler.Next(state); if (!current.Item1) { // restore binding if (PathStyle != null) { BindTo(this, nameof(PathStyle), current.Item2, FrameworkElement.StyleProperty); } } state.Element = current.Item2; ApplyBinding(this, nameof(Visibility), state.Element, UIElement.VisibilityProperty); if (PathFormatter != null) { sc.SetTick(ix); // call for Style, String override var format = PathFormatter.Convert(sc, typeof(Tuple <Style, String>), null, System.Globalization.CultureInfo.CurrentUICulture.Name); if (format is Tuple <Style, String> ovx) { if (ovx.Item1 != null) { current.Item2.Style = ovx.Item1; } } } state.SetLocation(icrc.Area.Left, tick.Value); sc.Generated(tick); itemstate.Add(state); if (MinorGridLineCount != 0) { // lay out minor divisions var mintv = tc.TickInterval / (double)(MinorGridLineCount + 1); var diry = Math.Sign(tick.Index); if (diry == 0) { // special case: zero for (int mx = 1; mx <= MinorGridLineCount; mx++) { var mtick = new SubtickState(tick.Index, mx, tick.Value + (double)mx * mintv); var mstate = CreateSubtick(icrc, mrecycler, mtick); if (mstate != null) { mitemstate.Add(mstate); } var mtick2 = new SubtickState(tick.Index, -mx, tick.Value + (double)(-mx) * mintv); var mstate2 = CreateSubtick(icrc, mrecycler, mtick2); if (mstate2 != null) { mitemstate.Add(mstate2); } } } else { for (int mx = 1; mx <= MinorGridLineCount; mx++) { var mtick = new SubtickState(tick.Index, diry * mx, tick.Value + (double)(diry * mx) * mintv); var mstate = CreateSubtick(icrc, mrecycler, mtick); if (mstate != null) { mitemstate.Add(mstate); } } } } } // VT and internal bookkeeping MinorGridLines = mitemstate; GridLines = itemstate; Layer.Remove(mrecycler.Unused); Layer.Remove(recycler.Unused); Layer.Add(recycler.Created); Layer.Add(mrecycler.Created); Dirty = false; }