WPF折线相对点值和拉伸绘制的曲线图
我试图创建一个包含代表图线相同的网格单元内的一系列折线的一个非常简单的图形组件。我的策略是查看集合中的所有点,确定最小值和最大值,然后相应地计算0到1之间的数字,然后使用Stretch =“Fill”来拉伸每条折线以填充网格单元格。我想要的效果是,在0点,.5将在小区的中心是垂直的,但在现实中折线被垂直拉伸这取决于最小和最大Y值是填补了整个小区。例如。如果.5是我的最大值,.7是我在多段线中的最小值,那么.5将在单元格的顶部清除,.7在底部清除,而不是中间的.5,.7 7/10到底端。WPF折线相对点值和拉伸绘制的曲线图
这里有一个简单的例子,用0和1之间的两条折线和计算点你会发现红色折线是直接在蓝色的顶部,即使红Y值更大。红色折线看起来应该和蓝色一样,但是在单元格中稍微偏低。然而,它被拉伸以填充整个单元格,因此它直接位于蓝色的顶部。
<Window x:Class="Test.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="100" Width="300">
<Grid>
<Polyline
Stretch="Fill"
Stroke="Blue"
Points="0,0 0.2,0 0.2,0.363636363636364 0.4,0.363636363636364 0.4,0.636363636363636 0.6,0.636363636363636 0.6,0.0909090909090909 0.8,0.0909090909090909 0.8,0 1,0" />
<Polyline
Stretch="Fill"
Stroke="Red"
Points="0,0.363636363636364 0.2,0.363636363636364 0.2,0.727272727272727 0.4,0.727272727272727 0.4,1 0.6,1 0.6,0.454545454545455 0.8,0.454545454545455 0.8,0.363636363636364 1,0.363636363636364" />
</Grid>
我使用0至1的值的原因是因为我想要的栅格单元的宽度和高度被容易地改变,例如通过滑块或其他来调整图形的高度,或者拖动窗口以调整宽度。所以我试图用这个拉伸策略来实现,而不是计算像素值w/out拉伸。
有关如何实现此目的的任何建议?
谢谢。
我有类似的问题,因为我找不到一个简单的方法来缩放多个形状。结束使用DrawingGroup
与多个GeometryDrawing
里面。所以他们一起扩展。这是你用这种方法的图表。看起来笨重,但应该工作得很快。另外,您将最有可能从代码填充线段:
<Window x:Class="Polyline.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="100" Width="300">
<Grid>
<Image>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="Transparent">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,1,1">
<RectangleGeometry.Transform>
<ScaleTransform ScaleX="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=Grid}}"
ScaleY="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Grid}}"/>
</RectangleGeometry.Transform>
</RectangleGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing>
<GeometryDrawing.Pen>
<Pen Brush="Blue" Thickness="1"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry>
<PathGeometry.Transform>
<ScaleTransform ScaleX="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=Grid}}"
ScaleY="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Grid}}"/>
</PathGeometry.Transform>
<PathGeometry.Figures>
<PathFigure StartPoint="0,0">
<PathFigure.Segments>
<LineSegment Point="0.2,0"/>
<LineSegment Point="0.2,0.363636363636364"/>
<LineSegment Point="0.4,0.363636363636364"/>
<LineSegment Point="0.4,0.636363636363636"/>
<LineSegment Point="0.6,0.636363636363636"/>
<LineSegment Point="0.6,0.0909090909090909"/>
<LineSegment Point="0.8,0.0909090909090909"/>
<LineSegment Point="0.8,0"/>
<LineSegment Point="1,0"/>
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing>
<GeometryDrawing.Pen>
<Pen Brush="Red" Thickness="1"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry>
<PathGeometry.Transform>
<ScaleTransform ScaleX="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=Grid}}"
ScaleY="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Grid}}"/>
</PathGeometry.Transform>
<PathGeometry.Figures>
<PathFigure StartPoint="0,0.363636363636364">
<PathFigure.Segments>
<LineSegment Point="0.2,0.363636363636364"/>
<LineSegment Point="0.2,0.727272727272727"/>
<LineSegment Point="0.4,0.727272727272727"/>
<LineSegment Point="0.4,1"/>
<LineSegment Point="0.6,1"/>
<LineSegment Point="0.6,0.454545454545455"/>
<LineSegment Point="0.8,0.454545454545455"/>
<LineSegment Point="0.8,0.363636363636364"/>
<LineSegment Point="1,0.363636363636364"/>
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Grid>
</Window>
您可以删除第一RectangleGeometry
如果你不需要图形总是扩展0和1之间
我就遇到了这个问题,而回。当时,我发现提出的解决方案,但雷普卡不满,因为这是比较复杂,效率不高,因为我所希望的。
我通过编码一组简单的Viewbox控件形状类的工作完全一样的问题解决了内置的Path
,Line
,Polyline
,并且Polygon
类除非他们可以很容易地得到拉伸工作,你想要的方式。
我的班是ViewboxPath
,ViewboxLine
,ViewboxPolyline
和ViewboxPolygon
,他们使用这样的:
<edf:ViewboxPolyline
Viewbox="0 0 1 1" <!-- Actually the default, can be omitted -->
Stretch="Fill" <!-- Also default, can be omitted -->
Stroke="Blue"
Points="0,0 0.2,0 0.2,0.3 0.4,0.3" />
<edf:ViewboxPolygon
Viewbox="0 0 10 10"
Stroke="Blue"
Points="5,0 10,5 5,10 0,5" />
<edf:ViewboxPath
Viewbox="0 0 10 10"
Stroke="Blue"
Data="M10,5 L4,4 L5,10" />
正如你看到的,我Viewbox控件形状类用于就像正常的形状(Polyline
,Polygon
,Path
和Line
)除额外Viewbox
参数,他们默认为Stretch="Fill"
的事实。Viewbox参数在用于指定形状的坐标系中指定应使用Fill
,Uniform
或UniformToFill
设置延伸的几何体的面积,而不是使用Geometry.GetBounds
。
这样就可以非常精确地控制拉伸,并且可以很容易地将单独的形状与另一个准确对齐。
下面是实际的代码为我Viewbox控件形状类,包括抽象基类ViewboxShape
包含常用功能:
public abstract class ViewboxShape : Shape
{
Matrix _transform;
Pen _strokePen;
Geometry _definingGeometry;
Geometry _renderGeometry;
static ViewboxShape()
{
StretchProperty.OverrideMetadata(typeof(ViewboxShape), new FrameworkPropertyMetadata
{
AffectsRender = true,
DefaultValue = Stretch.Fill,
});
}
// The built-in shapes compute stretching using the actual bounds of the geometry.
// ViewBoxShape and its subclasses use this Viewbox instead and ignore the actual bounds of the geometry.
public Rect Viewbox { get { return (Rect)GetValue(ViewboxProperty); } set { SetValue(ViewboxProperty, value); } }
public static readonly DependencyProperty ViewboxProperty = DependencyProperty.Register("Viewbox", typeof(Rect), typeof(ViewboxShape), new UIPropertyMetadata
{
DefaultValue = new Rect(0,0,1,1),
});
// If defined, replaces all the Stroke* properties with a single Pen
public Pen Pen { get { return (Pen)GetValue(PenProperty); } set { SetValue(PenProperty, value); } }
public static readonly DependencyProperty PenProperty = DependencyProperty.Register("Pen", typeof(Pen), typeof(ViewboxShape));
// Subclasses override this to define geometry if caching is desired, or just override DefiningGeometry
protected virtual Geometry ComputeDefiningGeometry()
{
return null;
}
// Subclasses can use this PropertyChangedCallback for properties that affect the defining geometry
protected static void OnGeometryChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var shape = sender as ViewboxShape;
if(shape!=null)
{
shape._definingGeometry = null;
shape._renderGeometry = null;
}
}
// Compute viewport from box & constraint
private Size ApplyStretch(Stretch stretch, Rect box, Size constraint)
{
double uniformScale;
switch(stretch)
{
default:
return new Size(box.Width, box.Height);
case Stretch.Fill:
return constraint;
case Stretch.Uniform:
uniformScale = Math.Min(constraint.Width/box.Width, constraint.Height/box.Height);
break;
case Stretch.UniformToFill:
uniformScale = Math.Max(constraint.Width/box.Width, constraint.Height/box.Height);
break;
}
return new Size(uniformScale * box.Width, uniformScale * box.Height);
}
protected override Size MeasureOverride(Size constraint)
{
// Clear pen cache if settings have changed
if(_strokePen!=null)
if(Pen!=null)
_strokePen = null;
else
if(_strokePen.Thickness != StrokeThickness ||
_strokePen.Brush != Stroke ||
_strokePen.StartLineCap != StrokeStartLineCap ||
_strokePen.EndLineCap != StrokeEndLineCap ||
_strokePen.DashCap != StrokeDashCap ||
_strokePen.LineJoin != StrokeLineJoin ||
_strokePen.MiterLimit != StrokeMiterLimit ||
_strokePen.DashStyle.Dashes != StrokeDashArray ||
_strokePen.DashStyle.Offset != StrokeDashOffset)
_strokePen = null;
_definingGeometry = null;
_renderGeometry = null;
return ApplyStretch(Stretch, Viewbox, constraint);
}
protected override Size ArrangeOverride(Size availableSize)
{
Stretch stretch = Stretch;
Size viewport;
Matrix transform;
// Compute new viewport and transform
if(stretch==Stretch.None)
{
viewport = availableSize;
transform = Matrix.Identity;
}
else
{
Rect box = Viewbox;
viewport = ApplyStretch(stretch, box, availableSize);
double scaleX = viewport.Width/box.Width;
double scaleY = viewport.Height/box.Height;
transform = new Matrix(scaleX, 0, 0, scaleY, -box.Left * scaleX, -box.Top * scaleY);
}
if(_transform!=transform)
{
_transform = transform;
_renderGeometry = null;
InvalidateArrange();
}
return viewport;
}
protected Pen PenOrStroke
{
get
{
if(Pen!=null)
return Pen;
if(_strokePen==null)
_strokePen = new Pen
{
Thickness = StrokeThickness,
Brush = Stroke,
StartLineCap = StrokeStartLineCap,
EndLineCap = StrokeEndLineCap,
DashCap = StrokeDashCap,
LineJoin = StrokeLineJoin,
MiterLimit = StrokeMiterLimit,
DashStyle =
StrokeDashArray.Count==0 && StrokeDashOffset==0 ? DashStyles.Solid :
new DashStyle(StrokeDashArray, StrokeDashOffset),
};
return _strokePen;
}
}
protected Matrix Transform
{
get
{
return _transform;
}
}
protected override Geometry DefiningGeometry
{
get
{
if(_definingGeometry==null)
_definingGeometry = ComputeDefiningGeometry();
return _definingGeometry;
}
}
protected Geometry RenderGeometry
{
get
{
if(_renderGeometry==null)
{
Geometry defining = DefiningGeometry;
if(_transform==Matrix.Identity || defining==Geometry.Empty)
_renderGeometry = defining;
else
{
Geometry geo = defining.CloneCurrentValue();
if(object.ReferenceEquals(geo, defining)) geo = defining.Clone();
geo.Transform = new MatrixTransform(
geo.Transform==null ? _transform : geo.Transform.Value * _transform);
_renderGeometry = geo;
}
}
return _renderGeometry;
}
}
protected override void OnRender(DrawingContext drawingContext)
{
drawingContext.DrawGeometry(Fill, PenOrStroke, RenderGeometry);
}
}
[ContentProperty("Data")]
public class ViewboxPath : ViewboxShape
{
public Geometry Data { get { return (Geometry)GetValue(DataProperty); } set { SetValue(DataProperty, value); } }
public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(Geometry), typeof(ViewboxPath), new UIPropertyMetadata
{
DefaultValue = Geometry.Empty,
PropertyChangedCallback = OnGeometryChanged,
});
protected override Geometry DefiningGeometry
{
get { return Data ?? Geometry.Empty; }
}
}
public class ViewboxLine : ViewboxShape
{
public double X1 { get { return (double)GetValue(X1Property); } set { SetValue(X1Property, value); } }
public double X2 { get { return (double)GetValue(X2Property); } set { SetValue(X2Property, value); } }
public double Y1 { get { return (double)GetValue(Y1Property); } set { SetValue(Y1Property, value); } }
public double Y2 { get { return (double)GetValue(Y2Property); } set { SetValue(Y2Property, value); } }
public static readonly DependencyProperty X1Property = DependencyProperty.Register("X1", typeof(double), typeof(ViewboxLine), new FrameworkPropertyMetadata { PropertyChangedCallback = OnGeometryChanged, AffectsRender = true });
public static readonly DependencyProperty X2Property = DependencyProperty.Register("X2", typeof(double), typeof(ViewboxLine), new FrameworkPropertyMetadata { PropertyChangedCallback = OnGeometryChanged, AffectsRender = true });
public static readonly DependencyProperty Y1Property = DependencyProperty.Register("Y1", typeof(double), typeof(ViewboxLine), new FrameworkPropertyMetadata { PropertyChangedCallback = OnGeometryChanged, AffectsRender = true });
public static readonly DependencyProperty Y2Property = DependencyProperty.Register("Y2", typeof(double), typeof(ViewboxLine), new FrameworkPropertyMetadata { PropertyChangedCallback = OnGeometryChanged, AffectsRender = true });
protected override Geometry ComputeDefiningGeometry()
{
return new LineGeometry(new Point(X1, Y1), new Point(X2, Y2));
}
}
[ContentProperty("Points")]
public class ViewboxPolyline : ViewboxShape
{
public ViewboxPolyline()
{
Points = new PointCollection();
}
public PointCollection Points { get { return (PointCollection)GetValue(PointsProperty); } set { SetValue(PointsProperty, value); } }
public static readonly DependencyProperty PointsProperty = DependencyProperty.Register("Points", typeof(PointCollection), typeof(ViewboxPolyline), new FrameworkPropertyMetadata
{
PropertyChangedCallback = OnGeometryChanged,
AffectsRender = true,
});
public FillRule FillRule { get { return (FillRule)GetValue(FillRuleProperty); } set { SetValue(FillRuleProperty, value); } }
public static readonly DependencyProperty FillRuleProperty = DependencyProperty.Register("FillRule", typeof(FillRule), typeof(ViewboxPolyline), new FrameworkPropertyMetadata
{
DefaultValue = FillRule.EvenOdd,
PropertyChangedCallback = OnGeometryChanged,
AffectsRender = true,
});
public bool CloseFigure { get { return (bool)GetValue(CloseFigureProperty); } set { SetValue(CloseFigureProperty, value); } }
public static readonly DependencyProperty CloseFigureProperty = DependencyProperty.Register("CloseFigure", typeof(bool), typeof(ViewboxPolyline), new FrameworkPropertyMetadata
{
DefaultValue = false,
PropertyChangedCallback = OnGeometryChanged,
AffectsRender = true,
});
protected override Geometry ComputeDefiningGeometry()
{
PointCollection points = Points;
if(points.Count<2) return Geometry.Empty;
var geometry = new StreamGeometry { FillRule = FillRule };
using(var context = geometry.Open())
{
context.BeginFigure(Points[0], true, CloseFigure);
context.PolyLineTo(Points.Skip(1).ToList(), true, true);
}
return geometry;
}
}
public class ViewboxPolygon : ViewboxPolyline
{
static ViewboxPolygon()
{
CloseFigureProperty.OverrideMetadata(typeof(ViewboxPolygon), new FrameworkPropertyMetadata
{
DefaultValue = true,
});
}
}
享受!
干杯,骰子解决方案!但有一个问题:ViewBoxShape代码的很大一部分涉及Pen属性。你为什么选择覆盖笔处理的大部分内容? Expression Blend似乎并不喜欢那样,抱怨“Pen”属性已经被Pen注册过了“。 – Jens 2010-07-16 10:04:20
您在我的房产注册码中发现了一个错误:我应该将Pen属性注册到ViewboxShape类。固定。至于代码量:Pen属性本身只增加了6行代码。笔相关代码的其余部分是支持在基本Shape类上定义的属性,例如“Stroke”,“StrokeThickness”等。正确有效地支持这些属性需要代码构造并缓存笔和代码,以检测对这些属性的更改并更新缓存的笔。 – 2010-07-17 04:39:03
完美地工作,谢谢!我实际上倾向于现在使用WPF Toolkit图表功能,但这对于未来的GeometryDrawing很有用。谢谢。 – Derek 2009-11-12 22:56:56
或者你可以看看我的Viewbox形状类(见下面的代码) – 2009-11-14 23:53:14