From Friday, April 19th (11:00 PM CDT) through Saturday, April 20th (2:00 PM CDT), 2024, ni.com will undergo system upgrades that may result in temporary service interruption.

We appreciate your patience as we improve our online experience.

Measurement Studio for .NET Languages

cancel
Showing results for 
Search instead for 
Did you mean: 

Aligning WPF elements with the plot area

Solved!
Go to solution

Is there a recommended way to align WPF elements outside the graph with the plot area? I like to align my UI with the plot area left edge. Another use case is aligning outside elements with a RangeCursor.

 

The way we do it now is to add a Canvas as a child of the graph. The canvas contains a hidden visual, which we position with Canvas.SetLeft using values calculated from the graph's margin and padding. Then we call PointToScreen(new Point(0,0)) on the visual. This works most of the time, but the position of the visual somehow lags behind the actual graph edge when the y-axis range changes (and a few other axis changes, which I haven't fully nailed down). By calling UpdateLayout on the visual when the range changes, I can make it work most of the time, but when I do that I find I spend an awful lot of CPU time in the UI thread doing that call.

0 Kudos
Message 1 of 3
(5,763 Views)
Solution
Accepted by topic author ShawnHoover

Currently, the graph does not directly expose the offset of the plot area within the border of the control. Your method of using a nested visual to calculate the offset seems like a reasonable approach with the current API. I created a quick mockup using this approach for some shapes referencing the plot area and a range cursor:


    XAML
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="2*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <ni:Graph x:Name="graph" Grid.Row="1" Grid.Column="1" LayoutUpdated="OnGraphLayoutUpdated">
            <ni:Graph.Axes>
                <ni:AxisDouble x:Name="xAxis" Orientation="Horizontal"/>
                <ni:AxisDouble x:Name="yAxis" Orientation="Vertical"/>
            </ni:Graph.Axes>
            <ni:Graph.Children>
                <Canvas x:Name="referenceVisual" />
                <ni:RangeCursor x:Name="rangeCursor" />
            </ni:Graph.Children>
        </ni:Graph>

        <Rectangle x:Name="graphLeftRectangle" Grid.Row="1" Fill="#90D0" />
        <Rectangle x:Name="graphTopRectangle" Grid.Column="1" Fill="#90D0" />
        <Rectangle x:Name="cursorRightRectangle" Grid.Row="1" Grid.Column="2" Fill="#900D" />
        <Rectangle x:Name="cursorBottomRectangle" Grid.Column="1" Grid.Row="2" Fill="#900D" />
    </Grid>

    Code
    public partial class MainWindow : Window {
        private static readonly Range<double> RelativeRange = new Range<double>( 0.0, 1.0 );

        public MainWindow( ) {
            InitializeComponent( );

            rangeCursor.HorizontalAxis = xAxis;
            rangeCursor.VerticalAxis = yAxis;
        }

        private void OnGraphLayoutUpdated( object sender, EventArgs e ) {
            UpdateAlignment( );
        }

        private void UpdateAlignment( ) {
            Size size = graph.RenderSize;
            if( size.Width <= 0.0 || size.Height <= 0.0 )
                return;

            // Get reference point to canvas in bottom-left corner of graph.
            var referenceTransform = referenceVisual.TransformToVisual( graph );
            Point graphBottomLeft = referenceTransform.Transform( new Point( ) );

            // Apply margin to size and bottom-left reference point.
            Thickness margin = graph.Margin;
            size.Width += margin.Left + margin.Right;
            size.Height += margin.Top + margin.Bottom;
            graphBottomLeft.X += margin.Left;
            graphBottomLeft.Y += margin.Top;

            // Calculate top-right reference point.
            Size plotAreaSize = graph.GetPlotAreaSize( );
            Point graphTopRight = new Point(
                graphBottomLeft.X + plotAreaSize.Width,
                graphBottomLeft.Y - plotAreaSize.Height );

            // Get reference points for range cursor within plot area.
            var horizontalRange = rangeCursor.GetRelativeHorizontalRange( ) ?? RelativeRange;
            var verticalRange = rangeCursor.GetRelativeVerticalRange( ) ?? RelativeRange;
            Point cursorBottomLeft = graph.RelativeToScreen( new Point( horizontalRange.Minimum, verticalRange.Minimum ) );
            Point cursorTopRight = graph.RelativeToScreen( new Point( horizontalRange.Maximum, verticalRange.Maximum ) );
            cursorBottomLeft.X += graphBottomLeft.X;
            cursorBottomLeft.Y += graphTopRight.Y;
            cursorTopRight.X += graphBottomLeft.X;
            cursorTopRight.Y += graphTopRight.Y;

            // Set margin to align graph and cursor visuals.
            graphTopRectangle.Margin = new Thickness { Left = graphBottomLeft.X, Right = size.Width - graphTopRight.X };
            graphLeftRectangle.Margin = new Thickness { Top = graphTopRight.Y, Bottom = size.Height - graphBottomLeft.Y };
            cursorBottomRectangle.Margin = new Thickness { Left = cursorBottomLeft.X, Right = size.Width - cursorTopRight.X };
            cursorRightRectangle.Margin = new Thickness { Top = cursorTopRight.Y, Bottom = size.Height - cursorBottomLeft.Y };
        }
    }

~ Paul H
Message 2 of 3
(5,725 Views)

This helped a lot. I think we had some other timing issues making it appear that we needed to explicitly update the layout of the child control in order to get accurate alignment. This hurt performance a bit since it caused rendering work to happen on the UI thread.

 

I fixed my issues and confirmed that using the graph LayoutUpdated event as you show as the trigger to adjust margins and canvas positions covers all the circumstances needed. We were doing it all over: size changed, data changed, axes changed, etc. This way is much cleaner.

 

I will also examine your techniques for computing margin. They may be simpler than what we have now.

0 Kudos
Message 3 of 3
(5,631 Views)