/// <summary> /// rule coordinates (x:[0..1], y:axis) /// </summary> /// <param name="icrc">The context.</param> void IRequireTransforms.Transforms(IChartRenderContext icrc) { if (ValueAxis == null) { return; } var matx = MatrixSupport.TransformFor(icrc.SeriesArea, ValueAxis); _trace.Verbose($"transforms sy:{matx.M22:F3} matx:{matx} sa:{icrc.SeriesArea}"); if (ClipToDataRegion) { Path.Clip = new RectangleGeometry() { Rect = icrc.SeriesArea }; } var vx = ValueAxis.For(Value); var offset = matx.Transform(new Point(0, vx)); Rule.SetValue(Canvas.TopProperty, offset.Y); matx.OffsetY = 0; Rule.Transform = new MatrixTransform() { Matrix = matx }; }
/// <summary> /// Scale the NDC rectangle to the dimensions given. /// </summary> /// <param name="icrc">Context.</param> void IRequireTransforms.Transforms(IChartRenderContext icrc) { var matx = MatrixSupport.ProjectForQuadrant(4, icrc.SeriesArea); Rectangle.Transform = new MatrixTransform() { Matrix = matx }; }
/// <summary> /// Adjust transforms for the various components. /// Geometry: scaled to actual values in cartesian coordinates as indicated by axes. /// </summary> /// <param name="icrc"></param> void IRequireTransforms.Transforms(IChartRenderContext icrc) { if (CategoryAxis == null || ValueAxis == null) { return; } if (ItemState.Count == 0) { return; } var mp = MatrixSupport.DataArea(CategoryAxis, ValueAxis, icrc.Area, 1); // cancel x-offset var proj2 = mp.Item2; proj2.OffsetX = 0; // get the local marker matrix var marker = MatrixSupport.LocalFor(mp.Item1, MarkerWidth, icrc.Area, -MarkerOrigin.X, -MarkerOrigin.Y); // get the offset matrix var mato = MatrixSupport.Multiply(mp.Item1, proj2); foreach (var state in ItemState) { // assemble Mk * M * P transform for this path var iworld = (state as IItemStateMatrix).World; var model = MatrixSupport.Multiply(marker, iworld); var matx = MatrixSupport.Multiply(model, proj2); var mt = new MatrixTransform() { Matrix = matx }; if (state.Element.DataContext is GeometryWithOffsetShim <Geometry> gs) { gs.GeometryTransform = mt; var output = mato.Transform(new Point(state.XValue, 0)); gs.Offset = icrc.Area.Left + output.X; } else { state.Element.Data.Transform = mt; } if (ClipToDataRegion) { state.Element.Clip = new RectangleGeometry() { Rect = icrc.SeriesArea }; } } _trace.Verbose($"{Name} mat:{mp.Item1} clip:{icrc.SeriesArea}"); }
/// <summary> /// The geometry is a circle of radius 10 centered at (0,0). /// </summary> /// <param name="area">Projection area.</param> /// <returns>MP matrix for this component's geometry.</returns> Matrix IProvideCustomTransform.TransformFor(Rect area) { var range = 2.0 * RADIUS; var mmatx = new Matrix(1.0 / range, 0, 0, -1.0 / range, RADIUS / range, RADIUS / range); var pmatx = MatrixSupport.ProjectForQuadrant(4, area); // lock aspect ratio to smallest dimension so it's a circle var scale = Math.Min(pmatx.M11, pmatx.M22); pmatx.M11 = pmatx.M22 = scale; // TODO adjust offset so it's centered var matx = MatrixSupport.Multiply(mmatx, pmatx); _trace.Verbose($"{Name} mat:{matx} M:{mmatx} P:{pmatx}"); return(matx); }
void IRequireAfterAxesFinalized.AxesFinalized(IChartRenderContext icrc) { if (icrc.Type == RenderType.TransformsOnly) { return; } var mat = MatrixSupport.DataArea(CategoryAxis1, CategoryAxis2, icrc.SeriesArea, 4); var matmp = MatrixSupport.Multiply(mat.Item1, mat.Item2); _trace.Verbose($"{Name} matmp:{matmp} clip:{icrc.SeriesArea}"); var mt = new MatrixTransform() { Matrix = matmp }; StyleGenerator?.Reset(); foreach (var istate in ItemState) { if (istate.Element.DataContext is GeometryWith2OffsetShim <RectangleGeometry> gs) { gs.GeometryTransform = mt; } else { istate.Element.Data.Transform = mt; } if (StyleGenerator != null) { var style = StyleGenerator.For(new Category2StyleContext(CategoryAxis1, CategoryAxis2, this, new double[3] { istate.Value, istate.XValue, (istate as IProvideYValue).YValue })); if (style != null) { istate.Element.Style = style; } else { if (PathStyle != null) { BindTo(this, nameof(PathStyle), istate.Element, FrameworkElement.StyleProperty); } } } } StyleGenerator?.UpdateLegend(new Category2StyleContext(CategoryAxis1, CategoryAxis2, this, new double[0])); }
/// <summary> /// Adjust transforms for the various components. /// Geometry: scaled to actual values in cartesian coordinates as indicated by axes. /// </summary> /// <param name="icrc"></param> void IRequireTransforms.Transforms(IChartRenderContext icrc) { if (CategoryAxis1 == null || CategoryAxis2 == null) { return; } if (ItemState.Count == 0) { return; } // TODO quad depends on orientations of both axes this is assuming default orientations var mat = MatrixSupport.DataArea(CategoryAxis1, CategoryAxis2, icrc.SeriesArea, 4); var matmp = MatrixSupport.Multiply(mat.Item1, mat.Item2); _trace.Verbose($"{Name} matmp:{matmp} clip:{icrc.SeriesArea}"); var mt = new MatrixTransform() { Matrix = matmp }; foreach (var state in ItemState) { if (state.Element.DataContext is GeometryWith2OffsetShim <RectangleGeometry> gs) { gs.GeometryTransform = mt; var output = matmp.Transform(new Point(state.XValue, (state as IProvideYValue).YValue)); gs.Offset = output.X - icrc.SeriesArea.Left; gs.Offset2 = output.Y - icrc.SeriesArea.Top; } else { state.Element.Data.Transform = mt; } if (ClipToDataRegion) { var cg = new RectangleGeometry() { Rect = icrc.SeriesArea }; state.Element.Clip = cg; } } }
Matrix ProjectionForAxis(Rect area, double scale) { switch (Side) { case Side.Bottom: return(MatrixSupport.ProjectionFor(area.Left, area.Top + AxisMargin, scale, 1)); case Side.Top: return(MatrixSupport.ProjectionFor(area.Left, area.Bottom - AxisMargin, scale, 1)); case Side.Left: return(MatrixSupport.ProjectionFor(area.Right - AxisMargin, area.Top, 1, scale)); case Side.Right: return(MatrixSupport.ProjectionFor(area.Left + AxisMargin, area.Top, 1, scale)); default: throw new NotImplementedException($"Not Implemented: {Side}"); } }
void IRequireAfterAxesFinalized.AxesFinalized(IChartRenderContext icrc) { if (CategoryAxis == null || ValueAxis == null) { return; } if (ItemState.Count == 0) { return; } var world = MatrixSupport.ModelFor(CategoryAxis, ValueAxis); foreach (var state in ItemState) { if (state is IItemStateMatrix ism) { ism.World = MatrixSupport.Translate(world, state.XOffset, state.Value); } } }
/// <summary> /// Adjust transforms for the various components. /// Geometry: scaled to actual values in cartesian coordinates as indicated by axes. /// </summary> /// <param name="icrc"></param> void IRequireTransforms.Transforms(IChartRenderContext icrc) { if (CategoryAxis == null || ValueAxis == null) { return; } if (ItemState.Count == 0) { return; } var mp = MatrixSupport.DataArea(CategoryAxis, ValueAxis, icrc.Area, 1); // get the offset matrix var mato = MatrixSupport.Multiply(mp.Item1, mp.Item2); // TODO preserve aspect ratio of image; will require ImageOpened evh var dimx = MarkerWidth * mato.M11; _trace.Verbose($"dimx {dimx}"); foreach (var state in ItemState) { if (state.Element.DataContext is ImageSourceShim iss) { var output = mato.Transform(new Point(state.XValue + MarkerOffset, state.Value)); _trace.Verbose($"output {output.X:F1},{output.Y:F1} value {state.XValue},{state.Value} image {state.Element.ActualWidth:F1}x{state.Element.ActualHeight:F1}"); var hw = dimx * MarkerOrigin.X; var hh = dimx * MarkerOrigin.Y; // these have bindings so effect is immediate iss.OffsetX = output.X - hw; iss.OffsetY = output.Y - hh; iss.Width = dimx; iss.Height = dimx; } if (ClipToDataRegion) { state.Element.Clip = new RectangleGeometry() { Rect = icrc.SeriesArea }; } } _trace.Verbose($"{Name} mat:{mp.Item1} clip:{icrc.SeriesArea}"); }
/// <summary> /// Adjust transforms for the various components. /// Geometry: scaled to actual values in cartesian coordinates as indicated by axes. /// </summary> /// <param name="icrc"></param> void IRequireTransforms.Transforms(IChartRenderContext icrc) { if (CategoryAxis == null || ValueAxis == null) { return; } var matx = MatrixSupport.TransformFor(icrc.Area, CategoryAxis, ValueAxis); _trace.Verbose($"{Name} mat:{matx} clip:{icrc.SeriesArea}"); Geometry.Transform = new MatrixTransform() { Matrix = matx }; if (ClipToDataRegion) { Segments.Clip = new RectangleGeometry() { Rect = icrc.SeriesArea }; } }
Matrix ProjectionFor(Rect area, bool reverse = false) { switch (Side) { case Side.Bottom: return(MatrixSupport.ProjectionFor( reverse ? area.Right : area.Left, area.Top + AxisLineThickness + 2 * AxisMargin, reverse ? -1 : 1, 1 )); case Side.Top: return(MatrixSupport.ProjectionFor( reverse ? area.Right : area.Left, area.Top + AxisLineThickness + 2 * AxisMargin, reverse ? -1 : 1, -1 )); case Side.Left: return(MatrixSupport.ProjectionFor( area.Right, area.Top, -area.Width - AxisLineThickness - 2 * AxisMargin, reverse ? -1 : 1 )); case Side.Right: return(MatrixSupport.ProjectionFor( area.Left + AxisLineThickness + 2 * AxisMargin, area.Top, 1, reverse ? -1 : 1 )); default: throw new NotImplementedException($"Not Implemented: {Side}"); } }
/// <summary> /// Adjust transforms for the various components. /// Geometry: scaled to actual values in cartesian coordinates as indicated by axes. /// </summary> /// <param name="icrc"></param> void IRequireTransforms.Transforms(IChartRenderContext icrc) { if (CategoryAxis == null || ValueAxis == null) { return; } if (ItemState.Count == 0) { return; } var matx = MatrixSupport.TransformForOffsetX(icrc.Area, CategoryAxis, ValueAxis); _trace.Verbose($"{Name} mat:{matx} clip:{icrc.SeriesArea}"); var mt = new MatrixTransform() { Matrix = matx }; foreach (var state in ItemState) { if (state.Element.DataContext is GeometryWithOffsetShim <RectangleGeometry> gs) { gs.GeometryTransform = mt; var output = matx.Transform(new Point(state.XValue, 0)); gs.Offset = icrc.Area.Left + output.X; } else { state.Element.Data.Transform = mt; } if (ClipToDataRegion) { var cg = new RectangleGeometry() { Rect = icrc.SeriesArea }; state.Element.Clip = cg; } } }
/// <summary> /// rule coordinates (x:[0..1], y:axis) /// </summary> /// <param name="icrc">The context.</param> void IRequireTransforms.Transforms(IChartRenderContext icrc) { if (ValueAxis == null) { return; } var matx = MatrixSupport.TransformFor(icrc.SeriesArea, ValueAxis); _trace.Verbose($"transforms sy:{matx.M22:F3} matx:{matx} sa:{icrc.SeriesArea}"); if (ClipToDataRegion) { Value1Path.Clip = new RectangleGeometry() { Rect = icrc.SeriesArea }; Value2Path.Clip = new RectangleGeometry() { Rect = icrc.SeriesArea }; BandPath.Clip = new RectangleGeometry() { Rect = icrc.SeriesArea }; } #if true var vmin = ValueAxis.For(Value2); var vmax = ValueAxis.For(Value1); var mmin = DoMinMax ? Math.Min(vmin, vmax) : vmin; var mmax = DoMinMax ? Math.Max(vmin, vmax) : vmax; var offset1 = matx.Transform(new Point(0, vmin)); Value1Path.SetValue(Canvas.TopProperty, offset1.Y); var offset2 = matx.Transform(new Point(0, vmax)); Value2Path.SetValue(Canvas.TopProperty, offset2.Y); var matx2 = matx; matx2.OffsetY = 0; Value1Rule.Transform = new MatrixTransform() { Matrix = matx2 }; Value2Rule.Transform = new MatrixTransform() { Matrix = matx2 }; BandPath.SetValue(Canvas.TopProperty, offset1.Y); Band.Transform = new MatrixTransform() { Matrix = matx2 }; #else Value1Rule.Transform = new MatrixTransform() { Matrix = matx }; Value2Rule.Transform = new MatrixTransform() { Matrix = matx }; Band.Transform = new MatrixTransform() { Matrix = matx }; #endif }