/// Copyright (c) 2021 Iiro Iivanainen, Harri Linna, Jere Pakkanen, Riikka Vilavaara
///
/// Permission is hereby granted, free of charge, to any person obtaining
/// a copy of this software and associated documentation files (the
/// "Software"), to deal in the Software without restriction, including
/// without limitation the rights to use, copy, modify, merge, publish,
/// distribute, sublicense, and/or sell copies of the Software, and to
/// permit persons to whom the Software is furnished to do so, subject to
/// the following conditions:
///
/// The above copyright notice and this permission notice shall be included
/// in all copies or substantial portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
/// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
/// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
/// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
/// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
/// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
/// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
///
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace GroundhogApp.UserControls
{
///
/// Interaction logic for MarkedSlider.xaml
///
public partial class MarkedSlider : UserControl
{
List Marks = new List();
Button SelectedMark;
///
/// Marked slider takes an array where index represents the slice and
/// the content represents the amount of marks in the slice.
///
public MarkedSlider()
{
InitializeComponent();
}
///
/// Sets empty space columns and mark columns and places a button with
/// the tag representing the index.
///
private void CreateColumns()
{
MarkGrid.ColumnDefinitions.Clear(); //Clear the grid columns
MarkGrid.Children.Clear();
Marks.Clear();
int sizeInBetween = 0;
for (int i = 0; i < MarkAmounts.Length; i++)
{
if (MarkAmounts[i] > 0) // There's a button to add
{
if (sizeInBetween > 0) // Add the proportionate white space
{
ColumnDefinition cd1 = new ColumnDefinition(); // Column for white space
cd1.Width = new GridLength(sizeInBetween, GridUnitType.Star);
MarkGrid.ColumnDefinitions.Add(cd1);
sizeInBetween = 0;
}
ColumnDefinition cd2 = new ColumnDefinition(); // Column for the button
cd2.Width = new GridLength(1, GridUnitType.Star);
MarkGrid.ColumnDefinitions.Add(cd2);
Button button = new Button();
button.Tag = i;
Marks.Add(button);
MarkGrid.Children.Add(button);
Grid.SetColumn(button, (MarkGrid.ColumnDefinitions.Count - 1)); //Set button to latest column
}
else sizeInBetween++;
}
if (sizeInBetween > 0)
{
ColumnDefinition cd1 = new ColumnDefinition(); // Column for white space
cd1.Width = new GridLength(sizeInBetween, GridUnitType.Star);
MarkGrid.ColumnDefinitions.Add(cd1);
}
}
///
/// Formats marks and adds event handlers.
///
private void FormatMarks()
{
for (int i = 0; i < Marks.Count; i++)
{
Button button = Marks[i];
button.BorderBrush = Brushes.Transparent;
button.BorderThickness = new Thickness(1, 1, 1, 1);
button.Height = 18;
button.MinWidth = 14;
int buttonsIndex = (int)Marks[i].Tag;
button.ToolTip
= $"Slice at {buttonsIndex + 1} has {MarkAmounts[buttonsIndex]} points of data associated with it.";
button.Click += (sender, e) => {
Button b = sender as Button;
if (SliderEnabled)
Slider.Value = (int)b.Tag + 1; // Need to add 1
};
button.Content = MarkAmounts[buttonsIndex];
button.FontSize = 9;
button.Foreground = Brushes.White;
button.FontWeight = FontWeights.Normal;
}
}
///
/// Colors based marks on quarters.
/// TODO: Set color preference
///
private void ColorMarksByQuantiles()
{
List positiveAmounts = new List();
foreach (int n in MarkAmounts)
{
if (n > 0) positiveAmounts.Add(n);
}
positiveAmounts.Sort();
int[] quartiles = new int[3];
if (positiveAmounts.Count > 0)
{
quartiles[0] = positiveAmounts[(int)(positiveAmounts.Count * 0.25)]; // Lower quartiles
quartiles[1] = positiveAmounts[(int)(positiveAmounts.Count * 0.50)]; // Middle quartiles
quartiles[2] = positiveAmounts[(int)(positiveAmounts.Count * 0.75)]; // Upper quartiles
}
Color markColor = DarkestMarkColor.Color;
for (int i = 0; i < Marks.Count; i++)
{
int ii = (int)Marks[i].Tag; // Index
int amount = MarkAmounts[ii];
if (amount == 0) continue;
if (amount < quartiles[0] || amount == 1)
{
Marks[i].Background = new SolidColorBrush(ChangeAlpha(markColor, 0.25f));
continue;
}
if (amount < quartiles[1])
{
Marks[i].Background = new SolidColorBrush(ChangeAlpha(markColor, 0.5f));
continue;
}
if (amount < quartiles[2])
{
Marks[i].Background = new SolidColorBrush(ChangeAlpha(markColor, 0.75f));
continue;
}
Marks[i].Background = new SolidColorBrush(ChangeAlpha(markColor, 1f));
continue;
}
}
///
/// Changes the alpha value of the color to make it appear brighter.
///
/// Base color.
/// Coefficient for changing the alpha value.
///
Color ChangeAlpha(Color color, float coef)
{
return Color.FromArgb((byte)(color.A * coef), color.R, color.G, color.B);
}
///
/// Simple highlighting of the selected mark.
///
///
///
void HighlightMark(object sender, RoutedEventArgs e)
{
// Slider values begin from 1 whereas indexing begins from 0
int current = (int)(sender as Slider).Value-1;
if (SelectedMark is not null)
{
SelectedMark.BorderBrush = Brushes.Transparent;
SelectedMark = null;
}
foreach (var mark in Marks) //TODO: This is called each time the slider value changes.
//If this causes lagginess it could be better to search
//the marks from a dictionary
{
if ((int)mark.Tag == current)
{
SelectedMark = mark;
SelectedMark.BorderBrush = Brushes.Gray;
SelectedMark.BringIntoView();
}
}
}
#region Dependancy properties
///
/// Underlying value that the slider is changing.
///
public int Depth
{
get { return (int)GetValue(DepthProperty); }
set { SetValue(DepthProperty, value); }
}
public static readonly DependencyProperty DepthProperty =
DependencyProperty.Register("Depth", typeof(int), typeof(MarkedSlider), new PropertyMetadata(1));
///
/// Dependancy property that sets the marks.
///
public int[] MarkAmounts
{
get { return (int[])GetValue(MarkAmountsProperty); }
set { SetValue(MarkAmountsProperty, value); }
}
public static readonly DependencyProperty MarkAmountsProperty =
DependencyProperty.Register("MarkAmounts", typeof(int[]), typeof(MarkedSlider), new PropertyMetadata(new int[0], MarkAmountsChanged));
///
/// Whether or not slider can be moved by mouse
///
public bool SliderEnabled
{
get { return (bool)GetValue(SliderEnabledProperty); }
set { SetValue(SliderEnabledProperty, value); }
}
public static readonly DependencyProperty SliderEnabledProperty =
DependencyProperty.Register("SliderEnabled", typeof(bool), typeof(MarkedSlider), new PropertyMetadata(false, SliderEnabledChanged));
public SolidColorBrush DarkestMarkColor
{
get { return (SolidColorBrush)GetValue(DarkestMarkColorProperty); }
set { SetValue(DarkestMarkColorProperty, value); }
}
public static readonly DependencyProperty DarkestMarkColorProperty =
DependencyProperty.Register("DarkestMarkColor", typeof(Brush), typeof(MarkedSlider), new PropertyMetadata(new SolidColorBrush(Colors.Gold), RecolorMarks));
#endregion
///
/// Called when the values change.
///
///
///
static void RecolorMarks(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
MarkedSlider ms = (MarkedSlider)dependencyObject;
ms.ColorMarksByQuantiles();
}
///
/// Called when the values change.
///
///
///
static void SliderEnabledChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
MarkedSlider ms = (MarkedSlider)dependencyObject;
ms.Slider.IsEnabled = ms.SliderEnabled;
}
///
/// Called when the values change.
///
///
///
static void MarkAmountsChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
MarkedSlider ms = (MarkedSlider)dependencyObject;
ms.SetMarkedSlider();
}
///
/// Resets and sets MarkedSLider values.
///
void SetMarkedSlider()
{
CreateColumns(); // Clears and sets new column definitions and marks.
FormatMarks();
ColorMarksByQuantiles(); // Colors marks by quarters.
Slider.Minimum = 1;
Slider.Maximum = MarkAmounts.Length;
//Slider.Value = 1;
Slider.IsEnabled = SliderEnabled;
}
}
}