2009-11-10 7 views
4

J'essaie de créer un composant graphique assez simple qui consiste en une série de polylignes dans la même cellule de la grille qui représentent les lignes de graphique. Ma stratégie est de regarder tous les points de mon ensemble, de déterminer les min et max, puis de calculer un nombre entre 0 et 1 en conséquence et d'utiliser Stretch = "Fill" pour étirer chaque polyligne pour remplir la cellule de la grille. Mon effet désiré serait qu'un point à 0, 0,5 serait verticalement au centre de la cellule, mais en réalité la polyligne est étirée verticalement pour remplir la cellule entière en fonction de la valeur Y min et max. Par exemple. si .5 est mon max et .7 est mon min dans la polyligne, alors .5 sera clair en haut de la cellule et .7 clair en bas, plutôt que .5 au centre et .7 7/10 à le fond.WPF Polyligne les valeurs de points relatives et étirer pour dessiner un graphique

Voici un exemple simple avec deux polylignes et des points calculés entre 0 et 1. Vous remarquerez que la polyligne rouge est directement au-dessus de la polyligne bleue, même si les valeurs Y rouges sont plus grandes. Le polyligne rouge devrait ressembler au bleu, mais être orienté légèrement plus bas dans la cellule. Cependant, il est étiré pour remplir toute la cellule de sorte qu'il se trouve directement sur le dessus du bleu.

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

La raison pour laquelle j'utilise 0 à 1 valeurs est parce que je veux la largeur et la hauteur de la cellule de grille pour être facilement modifiables, par exemple, via un curseur ou quelque chose pour ajuster la hauteur du graphique, ou en faisant glisser la fenêtre plus large pour ajuster la largeur. J'ai donc essayé d'utiliser cette stratégie d'étirement pour y parvenir au lieu de calculer des valeurs de pixels sans étirement.

Des conseils sur la façon d'y parvenir?

Merci.

Répondre

3

J'ai eu un problème similaire parce que je ne pouvais pas trouver un moyen facile de mettre à l'échelle plusieurs formes. Terminé en utilisant DrawingGroup avec plusieurs GeometryDrawing à l'intérieur. Donc, ils évoluent ensemble. Voici vos graphiques avec cette approche. On dirait volumineux mais devrait travailler vite. De plus, vous aurez plus de chances peuplez segments de ligne de code:

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

Vous pouvez supprimer d'abord RectangleGeometry si vous n'avez pas besoin de graphiques échelle toujours entre 0 et 1.

+0

Fonctionne parfaitement, merci! Je suis actuellement en train d'utiliser les fonctionnalités de création de graphiques WPF Toolkit, mais c'est bon à savoir avec GeometryDrawing pour le futur. Merci. – Derek

+0

Ou vous pouvez consulter mes classes de forme Viewbox (voir le code ci-dessous) –

3

Je suis tombé sur ce problème un certain temps . À l'époque, j'ai trouvé la solution repka proposée, mais elle était insatisfaite parce que c'était relativement complexe et pas aussi efficace que je l'aurais souhaité.

Je résolu le problème en codant un ensemble de classes simples de forme ViewBox qui fonctionnent exactement comme la construction dans Path, Line, Polyline et Polygon les classes, sauf qu'ils le rendent facile à obtenir l'étirement de travailler comme vous le voulez .

Mes cours sont ViewboxPath, ViewboxLine, ViewboxPolyline et ViewboxPolygon, et ils sont utilisés comme ceci:

<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" /> 

Comme vous pouvez le voir, mes les classes ViewBox de forme sont utilisés comme des formes normales (Polyline, Polygon , Path et Line) à l'exception du paramètre supplémentaire Viewbox et du fait qu'ils sont par défaut Stretch="Fill".Le paramètre Viewbox spécifie, dans le système de coordonnées utilisé pour spécifier la forme, la zone de la géométrie qui doit être étendue en utilisant les paramètres Fill, Uniform ou UniformToFill, au lieu d'utiliser Geometry.GetBounds.

Cela permet un contrôle très précis de l'étirement et facilite l'alignement des formes séparées les unes par rapport aux autres.

Voici le code réel pour mes cours ViewBox de forme, y compris la classe de base abstraite ViewboxShape qui contient des fonctionnalités communes:

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

Enjoy!

+0

Cheers, bice solution! Une question cependant: Une grande partie du code ViewboxShape concerne la propriété Pen. Pourquoi avez-vous choisi d'écraser une grande partie de la gestion du stylo? Expression Blend ne semble pas aimer cela, se plaignant "Pen" propriété a déjà été enregistrée par Pen ". – Jens

+0

Vous avez trouvé un bogue dans mon code d'enregistrement de propriété: j'aurais dû enregistrer la propriété Pen dans la classe ViewboxShape. Fixé. En ce qui concerne la quantité de code: La propriété Pen elle-même ajoute seulement 6 lignes de code. Le reste du code lié au stylo doit prendre en charge des propriétés telles que "Stroke", "StrokeThickness", etc. qui sont définies dans la classe Shape de base. La prise en charge correcte et efficace de ces propriétés nécessite du code pour construire et mettre en cache un crayon et du code pour détecter les modifications apportées à ces propriétés et mettre à jour le crayon mis en cache. –

Questions connexes