Checkable legend

Aug 20, 2012 at 7:03 PM

is it possible to add checkbox in front of the legend items to change the IsVisible property?

 

Thanks

Coordinator
Aug 21, 2012 at 11:59 AM

Sorry, this is currently not possible. (this will require checkbox and binding functionality in the IRenderingContext)

I think this should be solved by the client application - if you use WPF/SL it should be possible to overlay the legend box over the plot control, and bind the checkboxes to the PlotModel.

Dec 7, 2012 at 6:17 PM

Hi luckymichael,

Have you had any luck performing this?

Thanks,

Pierre

Jul 26, 2014 at 4:03 AM
objo wrote:
Sorry, this is currently not possible. (this will require checkbox and binding functionality in the IRenderingContext) I think this should be solved by the client application - if you use WPF/SL it should be possible to overlay the legend box over the plot control, and bind the checkboxes to the PlotModel.
First, I use WPF:
How solved the problem in the client application ?
How overlay ?
How bind checkboxes to the PlotModel?
Jul 27, 2014 at 4:45 AM
Edited Jul 27, 2014 at 4:49 AM
The legend in OxyPlot is pretty basic. If you want to do something like this you'll have to dynamically add checkboxes (and CheckChanged events) to a flow layout, I do it in an in-house app by using the same tag on the checkbox as the tag on the series. Then with an OnCheckChanged event I just pull out the series by the tag and set its visibility to the state of the checkbox. I can show some code when I get back into work on Monday if you need some more help.
Jul 28, 2014 at 3:52 AM
Edited Jul 28, 2014 at 6:24 AM
First, let me thank you for your response.
Yes , I need you more help, need sample code.
Jul 28, 2014 at 5:06 PM
Well the most basic thing you can do (assuming you want to display Series and Axes) is create a SplitContainer, with each Panel inside being a FlowLayoutPanel. Then as you add Series and Axes to the PlotModel, also add a CheckBox to the corresponding FlowLayoutPanel. You can use FlowLayoutPanel.SetFlowBreak(Control, true) to force each CheckBox to be on it's own line in the Panel. Make sure that each CheckBox has the same Tag as the Series or Axes it corresponds to, then just hook up a CheckChanged event that sets the Series.IsVisible or Axes.IsAxisVisible to whatever the CheckBox.Checked state is.
Aug 7, 2014 at 11:28 AM
Edited Aug 7, 2014 at 2:36 PM
Hi,
I found a solution that matched quite well my needs some time ago, following there is a simplified extract of my XAML code; essentially there is a graph and a stack panel with the checkboxes used as legend, all the "magic" is done by XAML binding, unfortunately I was not able to customize the bindings to the checkbox name to avoid repeating the code in the style of each series so I had to copy/paste the Style Triggers (I have about 25 series for one graph and 10 for another graph in the same page).
The properties bound to get data are List<DataPoint> for the Line and StairStep Series, while I created a new struct "AreaDataPoint" which is basically an extension of the DataPoint one with a "Y2" property to have the second values for the Y axys so in that case I have a List<AreaDataPoint> for the AreaSeries source items.
In the example you can also find a trick I used to hide all the AreaSeries with just one checkbox giving them different style colors, hope it will be useful :)
Disclaimer: I copied/pasted pieces of my code renaming all the styles, properties and so on because of NDA agreements so I'm not sure this code compiles, please use it as a sample idea not as a working solution (i.e. plot and axes styles are not included because they're simply dimensions and colors).
<Style x:Key="cbSeriesCheckboxes" TargetType="{x:Type CheckBox}" >
    <Setter Property="MinHeight" Value="16" />
    <Setter Property="IsEnabled" Value="True" />
</Style>

<Style x:Key="AreaSeriesSampleStyle" TargetType="{x:Type oxy:AreaSeries}">
    <Setter Property="LineStyle" Value="Solid" />
    <Setter Property="StrokeThickness" Value="1" />
    <Setter Property="DataFieldY2" Value="Y2" />
    <Setter Property="DataFieldX2" Value="X" />
    <Setter Property="Visibility" Value="Collapsed" />
    <Setter Property="Title" Value="{Binding ElementName=cbAreaSeriesSample, Path=Content}" />
    <Style.Triggers>
        <MultiDataTrigger>
            <MultiDataTrigger.Conditions>
                <Condition Binding="{Binding ElementName=cbAreaSeriesSample, Path=IsChecked}" Value="True"/>
                <Condition Binding="{Binding ElementName=cbAreaSeriesSample, Path=IsVisible}" Value="True"/>
            </MultiDataTrigger.Conditions>
            <Setter Property="Visibility" Value="Visible" />
        </MultiDataTrigger>
    </Style.Triggers>
    <Style.Triggers>
        <Trigger Property="Tag" Value="0">
            <Setter Property="Fill" Value="#4490EE90" />
            <Setter Property="Color" Value="LightGreen" />
        </Trigger>
        <Trigger Property="Tag" Value="1">
            <Setter Property="Fill" Value="#44FFD700" />
            <Setter Property="Color" Value="Gold" />
        </Trigger>
        <Trigger Property="Tag" Value="2">
            <Setter Property="Fill" Value="#44FFA500" />
            <Setter Property="Color" Value="Orange" />
        </Trigger>
        <Trigger Property="Tag" Value="3">
            <Setter Property="Fill" Value="#445F9EA0" />
            <Setter Property="Color" Value="CadetBlue" />
        </Trigger>
    </Style.Triggers>
</Style>
<Style x:Key="cbAreaSeriesSample" TargetType="{x:Type CheckBox}" BasedOn="{StaticResource cbSeriesCheckboxes}">
    <Setter Property="Foreground" Value="Green" />
    <Setter Property="Background" Value="WhiteSmoke" />
</Style>

<Style x:Key="StepSeriesSampleStyle" TargetType="{x:Type my:StairStepSeries}">
    <Setter Property="Color" Value="Blue" />
    <Setter Property="Title" Value="{Binding ElementName=cbStepSeriesSample, Path=Content}" />
    <Style.Triggers>
        <MultiDataTrigger>
            <MultiDataTrigger.Conditions>
                <Condition Binding="{Binding ElementName=cbStepSeriesSample, Path=IsChecked}" Value="True"/>
                <Condition Binding="{Binding ElementName=cbStepSeriesSample, Path=IsVisible}" Value="True"/>
            </MultiDataTrigger.Conditions>
            <Setter Property="Visibility" Value="Visible" />
        </MultiDataTrigger>
    </Style.Triggers>
</Style>
<Style x:Key="cbStepSeriesSample" TargetType="{x:Type CheckBox}" BasedOn="{StaticResource cbSeriesCheckboxes}">
    <Setter Property="Foreground" Value="Blue" />
    <Setter Property="Background" Value="WhiteSmoke" />
</Style>

<Style x:Key="LineSeriesSampleStyle" TargetType="{x:Type oxy:LineSeries}" >
    <Setter Property="LineStyle" Value="Solid" />
    <Setter Property="StrokeThickness" Value="3" />
    <Setter Property="Visibility" Value="Collapsed" />
    <Setter Property="CanTrackerInterpolatePoints" Value="False" />
    <Setter Property="Color" Value="Brown" />
    <Setter Property="Title" Value="{Binding ElementName=cbLineSeriesSample, Path=Content}" />
    <Style.Triggers>
        <MultiDataTrigger>
            <MultiDataTrigger.Conditions>
                <Condition Binding="{Binding ElementName=cbLineSeriesSample, Path=IsChecked}" Value="True"/>
                <Condition Binding="{Binding ElementName=cbLineSeriesSample, Path=IsVisible}" Value="True"/>
            </MultiDataTrigger.Conditions>
            <Setter Property="Visibility" Value="Visible" />
        </MultiDataTrigger>
    </Style.Triggers>
</Style>
<Style x:Key="cbLineSeriesSample" TargetType="{x:Type CheckBox}" BasedOn="{StaticResource cbSeriesCheckboxes}">
    <Setter Property="Foreground" Value="Brown" />
    <Setter Property="Background" Value="WhiteSmoke" />
</Style>




<StackPanel x:Name="Graphs" Orientation="Vertical">
    <oxy:Plot x:Name="TopPlot" Style="{StaticResource Plot}" MinHeight="380" Height="400" >
        <oxy:Plot.Axes>
            <oxy:DateTimeAxis Style="{StaticResource DateTimeAxis}"/>
            <oxy:LinearAxis Style="{StaticResource LinearAxis}"/> 
        </oxy:Plot.Axes>
        <oxy:AreaSeries Style="{StaticResource AreaSeriesSampleStyle}" ItemsSource="{Binding AreaSeriesData0}" Tag="0"/>
        <oxy:AreaSeries Style="{StaticResource AreaSeriesSampleStyle}" ItemsSource="{Binding AreaSeriesData1}" Tag="1"/>
        <oxy:AreaSeries Style="{StaticResource AreaSeriesSampleStyle}" ItemsSource="{Binding AreaSeriesData2}" Tag="2"/>
        <oxy:AreaSeries Style="{StaticResource AreaSeriesSampleStyle}" ItemsSource="{Binding AreaSeriesData3}" Tag="3"/>
        <my:StairStepSeries Style="{StaticResource StepSeriesSampleStyle}" ItemsSource="{Binding StepSeriesSampleData}"/>
        <oxy:LineSeries Style="{StaticResource LineSeriesSampleStyle}" ItemsSource="{Binding LineSeriesSampleData}"/>
    </oxy:Plot>
</StackPanel>

<StackPanel>
    <CheckBox x:Name="cbAreaSeriesSample" Style="{StaticResource cbAreaSeriesSample}" Content="Area Series" />
    <CheckBox x:Name="cbStepSeriesSample" Style="{StaticResource cbStepSeriesSample}" Content="StairStep Series" IsChecked="True"/>
    <CheckBox x:Name="cbLineSeriesSample" Style="{StaticResource cbLineSeriesSample}" Content="Line Series" IsChecked="False"  />
</StackPanel>
Aug 7, 2014 at 2:31 PM
Just as a tip: surround your code in triple backticks ("``` C#" and "```") to make it all display as code instead of how it's showing up =P. Thanks for the code example!
Aug 7, 2014 at 2:36 PM
GREAT! I couldn't find a way to display it better! :)
Many thanks!
Bye!
Aug 7, 2014 at 4:22 PM
Edited Aug 7, 2014 at 4:25 PM
I'll add an example of how I'm handling it for WinForms! Although keep in mind this is for LineSeries and LinearAxis only, it could probably easily be extended to handle more. Also I've been thinking of writing it to detect and add only newly added series and axis, but haven't had the time at work yet. An example of the result can be found on this post
/// <summary>
///     Refreshes the custom legend based on the axes and series in the PlotModel.
/// </summary>
private void UpdateCustomLegend()
{
    uiSeriesLegendLayout.Controls.Clear();
    uiAxesLegendLayout.Controls.Clear();

    foreach (LineSeries s in PlotModel.Series.OfType<LineSeries>().OrderBy(s => s.Title))
    {
        var seriesColorButton = new Button
        {
            AutoSize = true,
            BackColor = s.Color.ToColor(),
            FlatStyle = FlatStyle.Flat,
            Size = new Size(30, 15),
            Tag = s.Tag,
            UseVisualStyleBackColor = false
        };
        seriesColorButton.Click += (sender, args) =>
        {
            using (var d = new ColorDialog())
            {
                d.Color = s.Color.ToColor();
                if (d.ShowDialog() != DialogResult.OK)
                    return;

                s.Color = d.Color.ToOxyColor();
                ((Button) sender).BackColor = d.Color;
                PlotModel.InvalidatePlot(false);
            }
        };

        var seriesVisibilityCheckBox = new CheckBox
        {
            AutoSize = true,
            Checked = s.IsVisible,
            Tag = s.Tag,
            Text = s.Title
        };
        seriesVisibilityCheckBox.CheckedChanged += (sender, args) =>
        {
            s.IsVisible = ((CheckBox) sender).Checked;
            PlotModel.InvalidatePlot(false);
        };

        uiSeriesLegendLayout.Controls.Add(seriesColorButton);
        uiSeriesLegendLayout.Controls.Add(seriesVisibilityCheckBox);
        uiSeriesLegendLayout.SetFlowBreak(seriesVisibilityCheckBox, true);
    }

    var seriesSelectAllCheckBox = new CheckBox
    {
        AutoSize = true,
        Checked = true,
        Text = "Select All"
    };
    seriesSelectAllCheckBox.CheckedChanged += (sender, args) =>
    {
        foreach (CheckBox cb in uiSeriesLegendLayout.Controls.OfType<CheckBox>())
            cb.Checked = ((CheckBox) sender).Checked;
    };

    uiSeriesLegendLayout.Controls.Add(seriesSelectAllCheckBox);

    var hideLinkedSeriesCheckBox = new CheckBox
    {
        AutoSize = true,
        Checked = false,
        Text = "Hide Linked Series"
    };

    foreach (LinearAxis a in PlotModel.Axes.OfType<LinearAxis>())
    {
        var axisVisibilityCheckBox = new CheckBox
        {
            AutoSize = true,
            Checked = a.IsAxisVisible,
            Tag = a.Tag,
            Text = a.Title
        };
        axisVisibilityCheckBox.CheckedChanged += (sender, args) =>
        {
            a.IsAxisVisible = ((CheckBox) sender).Checked;
            PlotModel.InvalidatePlot(false);

            if (!hideLinkedSeriesCheckBox.Checked)
                return;

            foreach (
                LineSeries s in
                    PlotModel.Series.OfType<LineSeries>()
                             .Where(s => s.YAxisKey == a.Key || s.XAxisKey == a.Key))
            {
                s.IsVisible = a.IsAxisVisible;
                uiSeriesLegendLayout.Controls.OfType<CheckBox>()
                                    .Single(cb => cb.Tag == s.Tag)
                                    .Checked = s.IsVisible;
            }
        };

        uiAxesLegendLayout.Controls.Add(axisVisibilityCheckBox);
        uiAxesLegendLayout.SetFlowBreak(axisVisibilityCheckBox, true);
    }

    var axisSelectAllCheckBox = new CheckBox
    {
        AutoSize = true,
        Checked = true,
        Text = "Select All"
    };
    axisSelectAllCheckBox.CheckedChanged += (sender, args) =>
    {
        foreach (
            CheckBox cb in
                uiAxesLegendLayout.Controls.OfType<CheckBox>()
                                  .Where(cb => cb != hideLinkedSeriesCheckBox))
            cb.Checked = ((CheckBox) sender).Checked;
    };

    uiAxesLegendLayout.Controls.Add(axisSelectAllCheckBox);
    uiAxesLegendLayout.Controls.Add(hideLinkedSeriesCheckBox);
}