/// 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 CoreLibrary; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace GroundhogApp { /// /// Interaction logic for MapControl.xaml /// public partial class MapControl : UserControl { public event MouseButtonEventHandler MapMouseDown; public event MouseButtonEventHandler DataPointMouseDown; public double Xpos { get; set; } public double Ypos { get; set; } public enum Axis { X, Y, Z } public Axis HorizAxis { get; set; } public Axis VerticAxis { get; set; } /// /// Main constructor for the user control MapControl. /// public MapControl() { InitializeComponent(); Scale = 100; } #region Source image for the slice public static readonly DependencyProperty SliceSourceProperty = DependencyProperty.Register("SliceSource", typeof(BitmapImage), typeof(MapControl), new UIPropertyMetadata(null, ImageChangedCallback)); /// /// What happens when SliceSource changes. /// /// Root Element /// New value argument private static void ImageChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var input = (MapControl)d; input.sliceImg.Source = e.NewValue as BitmapImage; input.UpdateAxes(); input.ResizeCanvas(); } /// /// Source image for the slice. /// public BitmapImage SliceSource { get { return (BitmapImage)GetValue(SliceSourceProperty); } set { SetValue(SliceSourceProperty, value); } } #endregion #region Showing Color-coded axes public static readonly DependencyProperty ShowAxesProperty = DependencyProperty.Register("ShowAxes", typeof(bool), typeof(MapControl), new FrameworkPropertyMetadata(ShowAxesPropertyChangedCallback)); /// /// What happens when ShowAxes changes. /// /// Root element /// New value argument public static void ShowAxesPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var input = (MapControl)d; input.UpdateAxes(); } /// /// Wether color-coded axes are shown or not. /// public bool ShowAxes { get { return (bool)GetValue(ShowAxesProperty); } set { SetValue(ShowAxesProperty, value); } } #endregion #region Horizontal Axis Color public static readonly DependencyProperty HorizontalAxisColorProperty = DependencyProperty.Register("HorizontalAxisColor", typeof(Brush), typeof(MapControl), new UIPropertyMetadata(null, HorizontalColorChangedCallback)); /// /// What happens when HorizontalColor changes. /// /// Root element /// New value argument public static void HorizontalColorChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var input = (MapControl)d; input.HorizontalAxis.Stroke = e.NewValue as Brush; } /// /// Color for the horizontal axis. /// public Brush HorizontalAxisColor { get { return (Brush)GetValue(HorizontalAxisColorProperty); } set { SetValue(HorizontalAxisColorProperty, value); } } #endregion #region Vertical Axis Color public static readonly DependencyProperty VerticalAxisColorProperty = DependencyProperty.Register("VerticalAxisColor", typeof(Brush), typeof(MapControl), new UIPropertyMetadata(null, VerticalColorChangedCallback)); /// /// What happens when VerticalColor changes. /// /// Root element /// New value argument public static void VerticalColorChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var input = (MapControl)d; input.VerticalAxis.Stroke = e.NewValue as Brush; } /// /// Color for the vertical axis. /// public Brush VerticalAxisColor { get { return (Brush)GetValue(VerticalAxisColorProperty); } set { SetValue(VerticalAxisColorProperty, value); } } #endregion #region Attached Data Color public static readonly DependencyProperty AttachedDataColorProperty = DependencyProperty.Register("AttachedDataColor", typeof(Brush), typeof(MapControl), new UIPropertyMetadata(null, AttachedDataColorChangedCallback)); /// /// What happens when AttachedDataColor changes. /// /// Root Element /// New value argument public static void AttachedDataColorChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var input = (MapControl)d; if (input.ShowAttachedData) { input.RemoveAttDataPoints(); input.DrawAttDataPoints(); } } /// /// Color for AttachedData points. /// public Brush AttachedDataColor { get { return (Brush)GetValue(AttachedDataColorProperty); } set { SetValue(AttachedDataColorProperty, value); } } public static readonly DependencyProperty AttachedDataHighlightColorProperty = DependencyProperty.Register("AttachedDataHighlightColor", typeof(Brush), typeof(MapControl), new UIPropertyMetadata(null, AttachedDataHighlightColorChangedCallback)); /// /// What happens when AttachedDataHighlightColor changes /// /// /// public static void AttachedDataHighlightColorChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var input = (MapControl)d; if (input.ShowAttachedData) { input.RemoveAttDataPoints(); input.DrawAttDataPoints(); } } /// /// Color for highlighted AttachedData. /// public Brush AttachedDataHighlightColor { get { return (Brush)GetValue(AttachedDataHighlightColorProperty); } set { SetValue(AttachedDataHighlightColorProperty, value); } } #endregion #region Showing Data points on map public static readonly DependencyProperty ShowAttachedDataProperty = DependencyProperty.Register("ShowAttachedData", typeof(bool), typeof(MapControl), new FrameworkPropertyMetadata(ShowAttachedDataPropertyChangedCallback)); /// /// What happens when ShowAttachedData changes. /// /// Root Element /// New value argument public static void ShowAttachedDataPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var input = (MapControl)d; if (input.ShowAttachedData) { input.DrawAttDataPoints(); return; } input.RemoveAttDataPoints(); } /// /// Is Attached Data shown on Map or not. /// public bool ShowAttachedData { get { return (bool)GetValue(ShowAttachedDataProperty); } set { SetValue(ShowAttachedDataProperty, value); } } #endregion #region Color for the slice's borders public static readonly DependencyProperty SliceColorProperty = DependencyProperty.Register("SliceColor", typeof(Brush), typeof(MapControl), new UIPropertyMetadata(null, SliceColorChangedCallback)); /// /// What happens when SliceColor is changed. /// /// Root Element /// New value argument public static void SliceColorChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var input = (MapControl)d; input.SliceAxis.Stroke = e.NewValue as Brush; input.ColoredRectangle.Fill = e.NewValue as Brush; } /// /// Color of the axis that the slice represents. /// public Brush SliceColor { get { return (Brush)GetValue(SliceColorProperty); } set { SetValue(SliceColorProperty, value); } } #endregion #region Slice Name public static readonly DependencyProperty SliceNameProperty = DependencyProperty.Register("SliceName", typeof(string), typeof(MapControl), new UIPropertyMetadata(null, SliceNameChangedCallback)); /// /// What happens when SliceName changes. /// /// Root element /// New value argument public static void SliceNameChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var input = (MapControl)d; input.TextName.Text = e.NewValue as string; } /// /// Name of the slice being viewed. /// public string SliceName { get { return (string)GetValue(SliceNameProperty); } set { SetValue(SliceNameProperty, value); } } #endregion #region Horizontal axis location public static readonly DependencyProperty HorizontalDepthProperty = DependencyProperty.Register("HorizontalDepth", typeof(int), typeof(MapControl), new FrameworkPropertyMetadata(HorizontalDepthChangedCallback)); /// /// What happens when HorizontalDepth changes. /// /// Root element /// New value argument public static void HorizontalDepthChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var input = (MapControl)d; input.UpdateAxes(); } /// /// Location of Horizontal axis. /// public int HorizontalDepth { get { return (int)GetValue(HorizontalDepthProperty); } set { SetValue(HorizontalDepthProperty, value); } } #endregion #region Vertixal axis location public static readonly DependencyProperty VerticalDepthProperty = DependencyProperty.Register("VerticalDepth", typeof(int), typeof(MapControl), new FrameworkPropertyMetadata(VerticalDepthChangedCallback)); /// /// What happens when VerticalDepth changes. /// /// Root element /// New value argument public static void VerticalDepthChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var input = (MapControl)d; input.UpdateAxes(); } /// /// Location of Vertical axis. /// public int VerticalDepth { get { return (int)GetValue(VerticalDepthProperty); } set { SetValue(VerticalDepthProperty, value); } } #endregion #region Edit-mode on / off public static readonly DependencyProperty CanEditConnectorsProperty = DependencyProperty.Register("CanEditConnectors", typeof(bool), typeof(MapControl), new FrameworkPropertyMetadata(CanEditConnectorsPropertyChanged)); /// /// Adding event handler for mouse clicks if option is on. /// /// Root Element /// New value argument public static void CanEditConnectorsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var input = (MapControl)d; if (input.CanEditConnectors) { input.Canv.MouseDown += new MouseButtonEventHandler(input.Canv_MouseDown); return; } input.Canv.MouseDown -= new MouseButtonEventHandler(input.Canv_MouseDown); } /// /// Can add connectors for the Map shown or not. /// public bool CanEditConnectors { get { return (bool)GetValue(CanEditConnectorsProperty); } set { SetValue(CanEditConnectorsProperty, value); } } #endregion #region Map scaling public static readonly DependencyProperty ScaleProperty = DependencyProperty.Register("Scale", typeof(double), typeof(MapControl), new FrameworkPropertyMetadata(ScalePropertyChangedCallback)); /// /// What happens when Scale changes. /// /// Root Element /// New value argument public static void ScalePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var input = (MapControl)d; input.ResizeCanvas(); input.RemoveAttDataPoints(); input.UpdateAxes(); if (input.ShowAttachedData) input.DrawAttDataPoints(); } /// /// Map scale /// public double Scale { get { return (double)GetValue(ScaleProperty); } set { SetValue(ScaleProperty, value); } } /// /// Resizes Canvas and Map according to Scale. /// public void ResizeCanvas() { if (SliceSource == null) return; SliceBorder.Width = (Scale / 100.0) * SliceSource.Width; SliceBorder.Height = (Scale / 100.0) * SliceSource.Height; } #endregion #region Attached Data list public static readonly DependencyProperty DataListProperty = DependencyProperty.Register("DataList", typeof(List), typeof(MapControl), new UIPropertyMetadata(null, DataListChangedCallback)); /// /// What happens when DataList changes. /// /// Root Element /// New value argument public static void DataListChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var input = (MapControl)d; if (!input.ShowAttachedData) return; input.RemoveAttDataPoints(); input.DrawAttDataPoints(); } /// /// List of Attached data. /// public List DataList { get { return (List)GetValue(DataListProperty); } set { SetValue(DataListProperty, value); } } #endregion /// /// Invoking DataPointMouseDown event. /// /// Event sender. /// Event arguments. private void DataPoint_MouseDown(object sender, MouseButtonEventArgs e) { if (CanEditConnectors) return; DataPointMouseDown?.Invoke(sender, e); } /// /// Highlighting Attached Data Point with mouseover. /// /// Event sender. /// Event arguments. Not used. private void DataPoint_MouseEnter(object sender, MouseEventArgs e) { Rectangle rec = sender as Rectangle; rec.Stroke = AttachedDataHighlightColor; } /// /// Removing highlight from Attached Data Point when mouse leaves. /// /// Event sender. /// Event arguments. Not used. private void DataPoint_MouseLeave(object sender, MouseEventArgs e) { Rectangle rec = sender as Rectangle; rec.Stroke = AttachedDataColor; } /// /// Invoking a MouseDown event on Canvas. /// /// Event sender. /// Event arguments. private void Canv_MouseDown(object sender, MouseButtonEventArgs e) { Point p = Mouse.GetPosition(Canv); Xpos = (int)(p.X / (Scale / 100.0)) + 1; Ypos = (int)(p.Y / (Scale / 100.0)) + 1; MapMouseDown?.Invoke(sender, e); } /// /// Event handler for text changing on ZoomBox element. /// /// Event sender. Not used. /// Event arguments. Not used. private void ZoomBox_TextChanged(object sender, RoutedEventArgs e) { if (!this.IsInitialized) return; if (uint.TryParse(ZoomBox.Text.Trim(), out uint value) && value > 0) { ZoomBox.Style = (Style)FindResource("DefaultTextBox"); ZoomBox.ToolTip = null; ZoomSlider.Value = Math.Log(((double)value/100.0)) / Math.Log(10); Scale = value; } else { ZoomBox.Style = (Style)FindResource("ErrorTextBox"); ZoomBox.ToolTip = "Integrals above 0"; } } /// /// Event handler for ZoomSlider value changing. /// /// Event sender. Not used. /// Event arguments. Not used. private void ZoomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { double val = (double)Math.Pow(10,(ZoomSlider.Value)); ZoomBox.Text = ((int)(val * 100)).ToString(); } /// /// Updating color-coded axes /// private void UpdateAxes() { if (ShowAxes) { HorizontalAxis.Visibility = Visibility.Visible; VerticalAxis.Visibility = Visibility.Visible; SliceAxis.Visibility = Visibility.Visible; } else { HorizontalAxis.Visibility = Visibility.Hidden; VerticalAxis.Visibility = Visibility.Hidden; SliceAxis.Visibility = Visibility.Hidden; } HorizontalAxis.Y1 = (Scale / 100.0) * (HorizontalDepth - 1); HorizontalAxis.Y2 = (Scale / 100.0) * (HorizontalDepth - 1); VerticalAxis.X1 = (Scale / 100.0) * (VerticalDepth - 1); VerticalAxis.X2 = (Scale / 100.0) * (VerticalDepth - 1); if (SliceSource != null) { SliceAxis.Width = (Scale / 100.0) * SliceSource.Width; SliceAxis.Height = (Scale / 100.0) * SliceSource.Height; HorizontalAxis.X2 = (Scale / 100.0) * SliceSource.Width; VerticalAxis.Y2 = (Scale / 100.0) * SliceSource.Height; } } #region Draw / undraw attached data points /// /// Removing attached data points from Map. /// private void RemoveAttDataPoints() { var children = Canv.Children.OfType().ToList(); foreach (var child in children) { if ("AttData".Equals(child.Tag)) Canv.Children.Remove(child); } } /// /// Drawing attached data points on Map from DataList. /// private void DrawAttDataPoints() { if (DataList == null) return; foreach (AttachedData att in DataList) { float x = 0; float y = 0; if (att.AttachmentPoints.Count == 0) return; Vector3 vec = att.AttachmentPoints[0]; switch (HorizAxis) { case Axis.X: y = (float)(Scale / 100.0) * vec.X; break; case Axis.Y: y = (float)(Scale / 100.0) * vec.Y; break; case Axis.Z: y = (float)(Scale / 100.0) * vec.Z; break; default: break; } switch (VerticAxis) { case Axis.X: x = (float)(Scale / 100.0) * vec.X; break; case Axis.Y: x = (float)(Scale / 100.0) * vec.Y; break; case Axis.Z: x = (float)(Scale / 100.0) * vec.Z; break; default: break; } DrawPoint(x, y, att.DataName); } } /// /// Drawing a single Attached Data Point to specified location. /// /// Horizontal position /// Vertical position /// Data Point's unique ID private void DrawPoint(float x, float y, string id) { Rectangle point = new(); point.StrokeThickness = 2; point.Stroke = AttachedDataColor; point.Width = 5; point.Height = 5; point.Tag = "AttData"; point.Fill = Brushes.Black; point.MouseEnter += DataPoint_MouseEnter; point.MouseLeave += DataPoint_MouseLeave; point.Uid = id; point.MouseDown += DataPoint_MouseDown; Canv.Children.Add(point); Panel.SetZIndex(point, 1); Canvas.SetTop(point, x - (point.Width / 2.0)); Canvas.SetLeft(point, y - (point.Height / 2.0)); } #endregion } }