Add Render event and workaround

Oystein Bjorke 10 years ago 0
This discussion was imported from CodePlex

GeertvanHorrik wrote at 2014-03-26 16:22:

Hi,

Today I was trying to resize the plot to a specific size. The reason is that we need to align the actual plot without axes to a specific size. Unfortunately the sizes cannot be specified (only for the whole control).

I tried to get an event when the plot is being drawn (because I need the PlotArea to have valid values), but there are no events on the PlotModel class.

Below is a workaround which works great:
public static class OxyPlotExtensions
{
    private class RenderAnnotation : Annotation
    {
        public event EventHandler<EventArgs> Rendered;

        public override void Render(IRenderContext rc, PlotModel model)
        {
            base.Render(rc, model);

            Rendered.SafeInvoke(this);
        }
    }

    public static void AddRenderHandler(this PlotModel plotModel, EventHandler<EventArgs> handler)
    {
        Argument.IsNotNull(() => plotModel);
        Argument.IsNotNull(() => handler);

        var renderAnnotation = EnsureRenderAnnotation(plotModel);
        renderAnnotation.Rendered += handler;
    }

    public static void RemoveRenderHandler(this PlotModel plotModel, EventHandler<EventArgs> handler)
    {
        Argument.IsNotNull(() => plotModel);
        Argument.IsNotNull(() => handler);

        var renderAnnotation = EnsureRenderAnnotation(plotModel);
        renderAnnotation.Rendered -= handler;
    }

    private static RenderAnnotation EnsureRenderAnnotation(PlotModel plotModel)
    {
        var annotation = plotModel.Annotations.FirstOrDefault(x => x is RenderAnnotation);
        if (annotation == null)
        {
            annotation = new RenderAnnotation();
            plotModel.Annotations.Add(annotation);
        }

        return (RenderAnnotation)annotation;
    }
}
Maybe you can add the Render event to the PlotModel so everyone can do something when the plot is being rendered?

tibel wrote at 2014-03-26 19:20:

Rendering does belong to the control not to the model.

GeertvanHorrik wrote at 2014-03-26 19:42:

I fully agree. But then why is there a PlotArea on the model? And although the render itself is not part of the model, it would be very nice if I could at least be notified when the model is being rendered by the control.

objo wrote at 2014-03-26 20:45:

Did you try setting the Width, Height, AutoAdjustPlotMargins and PlotMargins of the Plot control? If you do this, I think the size of the plot should be fixed.
It is easy to add Rendering/Rendered events to the PlotModel, but I would like to avoid it if it is not really needed.
The PlotModel was meant to be the abstraction of the plot view - so it contains both the "plot area" and all the transforms that are related to the size of the view. See also https://oxyplot.codeplex.com/workitem/10133 which is related to this.

GeertvanHorrik wrote at 2014-03-26 20:51:

What I did was to use the Padding of the PlotModel to make sure that the plotarea itself always has a fixed size. Below is the code that allows a developer to make the width of the PlotArea (thus the plot itself can be larger) exactly the size of RealPlotWidth.
private void UpdatePlotArea(int dispatchCounter = 0)
{
    if (!plot.IsVisible)
    {
        return;
    }

    var plotWidth = plot.ActualWidth;
    if (plotWidth == 0d)
    {
        return;
    }

    var plotModel = plot.Model;
    if (plotModel == null)
    {
        return;
    }

    if (plotModel.PlotArea.Width == 0d)
    {
        if (dispatchCounter < 5)
        {
            Dispatcher.BeginInvoke(() => UpdatePlotArea(dispatchCounter + 1));
        }
        return;
    }

    // Magic constants we need to fix the left and right margins of Oxyplot
    const int RightPadding = 5;
    const int DefaultOxyplotPadding = 8;

    var axisWidth = plotModel.PlotArea.Left - plotModel.PlotAndAxisArea.Left + DefaultOxyplotPadding;

    var leftPadding = plot.ActualWidth - RealPlotWidth - axisWidth - DefaultOxyplotPadding;
    if (leftPadding < 50)
    {
        // Exit, issue with redrawing
        return;
    }

    var padding = new OxyThickness(leftPadding, 0, RightPadding + DefaultOxyplotPadding, 0);
    if (AreEqualOxyThickness(padding, plotModel.Padding))
    {
        return;
    }

    plotModel.Padding = padding;
    plotModel.InvalidatePlot(false);
}

private static bool AreEqualOxyThickness(OxyThickness oxyThickness1, OxyThickness oxyThickness2)
{
    if (oxyThickness1.Left != oxyThickness2.Left)
    {
        return false;
    }

    if (oxyThickness1.Top != oxyThickness2.Top)
    {
        return false;
    }

    if (oxyThickness1.Right != oxyThickness2.Right)
    {
        return false;
    }

    if (oxyThickness1.Bottom != oxyThickness2.Bottom)
    {
        return false;
    }

    return true;
}

bvsms wrote at 2014-03-27 01:14:

objo wrote:
Did you try setting the Width, Height, AutoAdjustPlotMargins and PlotMargins of the Plot control? If you do this, I think the size of the plot should be fixed.
It is easy to add Rendering/Rendered events to the PlotModel, but I would like to avoid it if it is not really needed.
The PlotModel was meant to be the abstraction of the plot view - so it contains both the "plot area" and all the transforms that are related to the size of the view. See also https://oxyplot.codeplex.com/workitem/10133 which is related to this.
Is is possible to fix the width of the x-axis?
For example a plot will display a line series with data that can vary fro 0 to 1000000.
Let's assume we fix the plot width. If the data is within the range 0-10 then the x-axis width will have a certain value. However if we load data in the range between 10000 -100000 then the x-axis width will shrink to accommodate the extra digits displayed on the Y-axis.
It gets even worse if we enable the Y-axis title. (the x-axis shrinks even more.)

In other words rather than fixing the plot width, we need a way of fixing the x-axis width and allow the plot width to move dynamically. Ideally it would be great to anchor the origin of the x-axis as well.

If this is not possible, then having a "rendered" event would allow us to do all the calculations manually to ensure the x-axis has the correct width and location.