Under review

Checkable legend

Oystein Bjorke 10 years ago updated by anonymous 10 years ago 4
This discussion was imported from CodePlex

luckymichael wrote at 2012-08-20 20:03:

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

 

Thanks


objo wrote at 2012-08-21 12:59:

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.


pdinnissen wrote at 2012-12-07 18:17:

Hi luckymichael,

Have you had any luck performing this?

Thanks,

Pierre


wolf9s wrote at 2014-07-26 05:03:

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?

Slxe wrote at 2014-07-27 05:45:

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.

wolf9s wrote at 2014-07-28 04:52:

First, let me thank you for your response.
Yes , I need you more help, need sample code.

Slxe wrote at 2014-07-28 18:06:

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.

DJDAS wrote at 2014-08-07 12:28:

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>

Slxe wrote at 2014-08-07 15:31:

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!

DJDAS wrote at 2014-08-07 15:36:

GREAT! I couldn't find a way to display it better! :)
Many thanks!
Bye!

Slxe wrote at 2014-08-07 17:22:

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);
}
I actually built a custom user control for work that includes this and my drawing toolbar, with minimal interaction with OxyPlot libraries needed (although you can directly access the PlotModel and PlotController if needed). Figured I'd upload it in case anyone else finds it helpful: https://github.com/Slxe/OxyPlotChartControl
Let me know what you think please =) any feedback is always helpful!
Under review
cool, I will check this one out too!
Bah just noticed I can't seem to edit my old post >_< built* not build, and minimal... not with T_T damnit I need to proofread more lol
That issue should be reported to userecho. I fixed your post, Sir :-)