Change the color of selected points in a scatter series

Oystein Bjorke 10 лет назад 0
This discussion was imported from CodePlex

Gimly wrote at 2013-09-20 14:01:

I need to display a point in the scatter series with a different color when it is selected.

I can't find any example and any information on how to do it, is it possible?

objo wrote at 2013-09-20 14:53:

Sorry, the scatter series is not currently supporting selection of individual points. You can probably find a work-around by temporary setting special (e.g. high or low) values to the ScatterPoint.Value property.
I think the nicest solution would be to create a custom scatter series with an IsSelected property in the items.

Gimly wrote at 2013-09-20 15:36:

By creating a custom scatter series, you mean in my code? It would mean creating a class inheriting from ScatterSeries that would use a IsSelected property on items?

Or maybe I should do it in a fork? Would it be interesting to add this functionality in the core of OxyPlot?

If so, should it be a new ScatterSeries inheriting from ScatterSeries (like "SelectableScatterSeries") or should I add this functionality directly into the ScatterSeries?

objo wrote at 2013-09-23 21:43:

Yes, let us try to implement the (multi)select functionality into a SelectableScatterSeries first.

I see at least three possible ways to implement the selection
a. create a SelectableScatterPoint class with an IsSelected property
b. keep the selection in a dictionary in the series class
c. add IsSelected to the ScatterPoint class
I guess a) and c) is better for data binding.

My only concern about this is that it may trigger requests for selecting single items of Bar/ColumnSeries, PieSeries, segments of LineSeries etc. etc.... :)

Gimly wrote at 2013-09-24 09:52:

I would go with the c) option, but then does it really make sense to create a "SelectableScatterSeries"? Wouldn't it be more logical to add a "SelectionType" enumerable to the ScatterSeries with these options:
  • Series => the whole series is selected, default value, like now
  • SinglePoint => only one point at a time can be selected, when we select another, it is deselected
  • MultiplePoints => multiple points in the series can be selected
Like that we add the functionality directly to the scatter series without modifying anything.

By the way, ScatterSeries already has a "SelectedIndex", coming from SelectablePlotElement. Wouldn't it make sense to use this? Unfortunately it won't help with the MultiplePoints, but it could be used for the SinglePoint and Series, as it already states that it should use "-1" for all items.

I see that a lot of series type (e.g. line, stairs or candle stick) use this option through the GetSelectableColor method. Is there a reason for which not all series use this option?

Edit: Actually after further research, I see that the GetSelectableColor is always called without the "index", which passes "-1" as a default value, making always the whole series selected. Was this a first implementation of selection that was never finished? Actually, I see no examples of selection, has the selection implementation been finished?

objo wrote at 2013-09-24 13:29:

Yes, this implementation is not finished.

Yes, we should add a selection type property to the SelectablePlotElement, but I suggest to call it SelectionMode
http://msdn.microsoft.com/en-us/library/system.windows.controls.selectionmode.aspx

Note that the SelectablePlotElement is used by both annotations and series.

I suggest to rename GetSelectableColor to GetActualItemColor and replace SelectedIndex by something like
        /// <summary>
        /// Occurs when the selected items is changed.
        /// </summary>
        public event EventHandler SelectedItemsChanged;

        /// <summary>
        /// Gets or sets the selection mode.
        /// </summary>
        /// <value>The selection mode.</value>
        public SelectionMode SelectionMode { get; set; }

        /// <summary>
        /// The selected items.
        /// </summary>
        private Dictionary<int, bool> selectedItems = new Dictionary<int, bool>();

        /// <summary>
        /// Gets the indices of the selected items in this element.
        /// </summary>
        /// <returns>A collection of item indices.</returns>
        public IEnumerable<int> GetSelectedItems()
        {
            return this.selectedItems.Keys;
        }

        /// <summary>
        /// Clears the selected items.
        /// </summary>
        public void ClearSelectedItems()
        {
            this.selectedItems.Clear();
            this.OnSelectedItemsChanged();
        }

        /// <summary>
        /// Determines whether the specified item is selected.
        /// </summary>
        /// <param name="index">The index of the item.</param>
        /// <returns><c>true</c> if the item is selected; otherwise, <c>false</c>.</returns>
        public bool IsItemSelected(int index)
        {
            return this.selectedItems.ContainsKey(index);
        }

        /// <summary>
        /// Selects the specified item.
        /// </summary>
        /// <param name="index">The index.</param>
        public void SelectItem(int index)
        {
            this.selectedItems[index] = true;
            this.OnSelectedItemsChanged();
        }

        /// <summary>
        /// Unselects the specified item.
        /// </summary>
        /// <param name="index">The index.</param>
        public void UnselectItem(int index)
        {
            if (this.selectedItems.ContainsKey(index))
            {
                this.selectedItems.Remove(index);
                this.OnSelectedItemsChanged();
            }
        }

        /// <summary>
        /// Raises the <see cref="E:SelectedItemsChanged" /> event.
        /// </summary>
        /// <param name="args">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void OnSelectedItemsChanged(EventArgs args = null)
        {
            var e = this.SelectedItemsChanged;
            if (e != null)
            {
                e(this, args);
            }
        }
where
    public enum SelectionMode { All, Single, Multiple }
Adding selected/unselected items to the SelectedItemsChanged event args is probably also a good idea.

I keep getting a 404 from codeplex, so cannot push any of these changes.

Agree, we can implement this without creating a sub class.

Gimly wrote at 2013-09-24 14:13:

Seems very good to me!

The only tricky part is that the GetSelectableColor is used in the Render section of most series, but without passing the index since it's calling methods (like DrawClippedLines) that take a series of points. The way the render of most series is implemented now, it's impossible to pass different colors for each point.

Maybe it should be the RenderContext's role to check if the point is selected and select the correct color if the element is selected? But this would mean that the IsSelected should be also on ScreenPoints and I don't know if that makes a lot of sense.

PS: CodePlex gives me also a 404 for both my Fork and OxyPlot, any info on that?

objo wrote at 2013-09-24 19:13:

I don't think we should add more to the ScreenPoints - it should be a lightweight structure.
I see the selection should contain both an item index and an index to what is being selected (e.g. high/low/open/close in a HighLowSeries, or segment/point in a LineSeries).

I think selection can be handled in the Series, and we should avoid adding more complexity to the RenderContext (I want to keep it as simple as possible, to make it easy to port to new platforms). We can add an extension method that does DrawClippedLines on selected segments only.

404: I submitted a notice on the codeplex support form, and there is an issue: https://codeplex.codeplex.com/workitem/26971

Gimly wrote at 2013-09-26 14:08:

So, I went a bit further in my investigation for doing this.

First, about the modification of the SelectablePlotElement:
  • What should we do with the IsSelected property? Is it still valid? For example, for selecting a point, we could imagine that first the series should be selected, and then individual points in the series could be selected as well. Or, it could be valid only if the SelectionMode is "All".
  • Should we keep the Selectable property, or should we add a SelectionMode option to "None".
About the index of what is being selected, are you sure that selection of individual elements (like high/low/open/close for a HighLowSeries or segment in a LineSeries) makes sense? For the LineSeries, I would have guessed that only selection of "Points" (PointMarker) would make sense. Or maybe a selection of a range (like if you would like to change the color of the selected range in the "Select range" example.

In general and about implementing it on the series.
  • The GetSelectableColor methods on the SelectablePlotElement are really awkward and don't really make sense since all selectable element should have the same selection color.
  • Since most points / segments are drawn as a batch through the extension methods, we need to find a way to pass to those extension methods information about which points / segments are actually selected, and what is the "selection" color.
I would say the simplest would be to modify the extension methods to add an enumerable of "selected index" and a selection color (or 2 for example for the marker where there is the stroke and fill color.

What do you think?

Edit:
Another idea I just had, we could do the same as was done with the MarkerSize for the DrawMarkers extension method, make the extension method accept an IList<OxyColor> instead of simply markerFill and markerStroke.

objo wrote at 2013-09-26 14:57:

I think all selected points should be drawn in the same call, this will be better for performance.
Also, it is good that all selected points are drawn on top of the others.

Idea - we could create a new Selection type with a method IsItemSelected(int index, Enum feature)

where index is related to the items and feature describes whether it is a marker, line segment, high, low etc.

Should the Selection be stored on the PlotModel or on each element of the plot?

Gimly wrote at 2013-09-26 15:18:

Aaah, I think I just understood something that I had missed :)

The selected points / segments / whatever should be drawn on top of the "standard" series point / segment / whatever so that we can select even things that are not displayed (like a Marker in a Series that doesn't display a marker).

I was trying to change the color of existing markers whether they are selected or not, but I see now that this doesn't really make sense as we would only see selection if the element is displayed and we might want to display selected items differently...

The difficulty with the Selection type would be that the selection can be very different depending on the series type and even a simple series could have multiple types of selection.

If we look at the LineSeries, here are a few types of selection that we could want:
  • Selection of a range (from x=1 to x=20), on both axes
  • Selection of the whole series (if we have multiple series for example)
  • Selection of individual point(s) in the series
  • Selection of individual segment(s) of the series (would make sense only if the series is not smooth)
And the user could want to display also differently each type. For example, the segment selection, he could want to display or not point markers. You could want to display also selection differently from series to series or want to select multiple Series at the same time.

Storing the Selection on the PlotModel would allow more easily to make only one series selectable at the same time, or having multiple. But the big question... what should the Selection type be? :D

Gimly wrote at 2013-09-26 16:34:

OK, I implemented an example that makes it possible to select the ScatterSeries correctly. The Scatter is the simplest of the series to make the selection since it's only a matter of selecting individual makers. I've simply used the "SelectionColor" that is available in the Plot and used it for both fill and stroke of the selection marker.

The code is visible in my fork.
https://hg.codeplex.com/forks/gimly/oxyplot

I don't want to make a pull request just yet as I think this solution might not be optimal to make the selection work easily with other type of series. It's working for my needs with the scatter, but I think we should find something more global to make it work with any series.


Gimly wrote at 2013-09-27 10:41:

Thanks, fixed.

What do you think? Should I do a pull request or you prefer to find a better solution first?

objo wrote at 2013-09-27 10:45:

It looked good - I pushed it into the default fork with some changes (added a "Selection" field - prepared for selection of features of items + I don't think the rendering needs to know about the SelectionMode)

Gimly wrote at 2013-09-27 12:35:

Nice, thanks!

Found a small bug though, in the SelectablePlotElement.SelectItem method, if the SelectionMode is Single, you should clear the selection before selecting the new one.

The way it works now, Single or Multiple doesn't make any difference.

I would have fixed it myself, but I messed-up my fork and had to delete it.

objo wrote at 2013-09-27 15:33:

I have submitted the change.

Сервис поддержки клиентов работает на платформе UserEcho