Initial Commit
This commit is contained in:
330
.gitignore
vendored
Normal file
330
.gitignore
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
**/Properties/launchSettings.json
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
42
AssemblyInfo.cs
Normal file
42
AssemblyInfo.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Resources;
|
||||
using System.Windows.Markup;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Research.DynamicDataDisplay;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Security;
|
||||
|
||||
[module: SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")]
|
||||
|
||||
[assembly: XmlnsDefinition(D3AssemblyConstants.DefaultXmlNamespace, "Microsoft.Research.DynamicDataDisplay")]
|
||||
[assembly: XmlnsDefinition(D3AssemblyConstants.DefaultXmlNamespace, "Microsoft.Research.DynamicDataDisplay.Navigation")]
|
||||
[assembly: XmlnsDefinition(D3AssemblyConstants.DefaultXmlNamespace, "Microsoft.Research.DynamicDataDisplay.Charts")]
|
||||
[assembly: XmlnsDefinition(D3AssemblyConstants.DefaultXmlNamespace, "Microsoft.Research.DynamicDataDisplay.Charts.Navigation")]
|
||||
[assembly: XmlnsDefinition(D3AssemblyConstants.DefaultXmlNamespace, "Microsoft.Research.DynamicDataDisplay.DataSources")]
|
||||
[assembly: XmlnsDefinition(D3AssemblyConstants.DefaultXmlNamespace, "Microsoft.Research.DynamicDataDisplay.Common.Palettes")]
|
||||
[assembly: XmlnsDefinition(D3AssemblyConstants.DefaultXmlNamespace, "Microsoft.Research.DynamicDataDisplay.Charts.Axes")]
|
||||
[assembly: XmlnsDefinition(D3AssemblyConstants.DefaultXmlNamespace, "Microsoft.Research.DynamicDataDisplay.Charts.Axes.Numeric")]
|
||||
[assembly: XmlnsDefinition(D3AssemblyConstants.DefaultXmlNamespace, "Microsoft.Research.DynamicDataDisplay.PointMarkers")]
|
||||
[assembly: XmlnsDefinition(D3AssemblyConstants.DefaultXmlNamespace, "Microsoft.Research.DynamicDataDisplay.Charts.Shapes")]
|
||||
[assembly: XmlnsDefinition(D3AssemblyConstants.DefaultXmlNamespace, "Microsoft.Research.DynamicDataDisplay.Charts.Markers")]
|
||||
[assembly: XmlnsDefinition(D3AssemblyConstants.DefaultXmlNamespace, "Microsoft.Research.DynamicDataDisplay.Converters")]
|
||||
[assembly: XmlnsDefinition(D3AssemblyConstants.DefaultXmlNamespace, "Microsoft.Research.DynamicDataDisplay.MarkupExtensions")]
|
||||
[assembly: XmlnsDefinition(D3AssemblyConstants.DefaultXmlNamespace, "Microsoft.Research.DynamicDataDisplay.Charts.Isolines")]
|
||||
[assembly: XmlnsDefinition(D3AssemblyConstants.DefaultXmlNamespace, "Microsoft.Research.DynamicDataDisplay.ViewportRestrictions")]
|
||||
|
||||
[assembly: XmlnsPrefix(D3AssemblyConstants.DefaultXmlNamespace, "d3")]
|
||||
|
||||
[assembly: CLSCompliant(true)]
|
||||
[assembly: InternalsVisibleTo("DynamicDataDisplay.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010039f88065585acdedaac491218a8836c4c54070b4b0f85bc909bd002856b509349f95fc845d1d4c664ea6b93045f2ada3b4fe70c6cd9b3fb615f94b8b5f67e4ea8ea5decb233e2e3c0ce84b78dc3ca0cd9fd2260792ece12224fca5813f03c7ad57b1faa07e3ca8fafb278fa23976fc7a35b8b4ae4efedacd1e193d89738ac2aa")]
|
||||
[assembly: InternalsVisibleTo("DynamicDataDisplay.Maps, PublicKey=002400000480000094000000060200000024000052534131000400000100010039f88065585acdedaac491218a8836c4c54070b4b0f85bc909bd002856b509349f95fc845d1d4c664ea6b93045f2ada3b4fe70c6cd9b3fb615f94b8b5f67e4ea8ea5decb233e2e3c0ce84b78dc3ca0cd9fd2260792ece12224fca5813f03c7ad57b1faa07e3ca8fafb278fa23976fc7a35b8b4ae4efedacd1e193d89738ac2aa")]
|
||||
[assembly: InternalsVisibleTo("DynamicDataDisplay.Markers, PublicKey=002400000480000094000000060200000024000052534131000400000100010039f88065585acdedaac491218a8836c4c54070b4b0f85bc909bd002856b509349f95fc845d1d4c664ea6b93045f2ada3b4fe70c6cd9b3fb615f94b8b5f67e4ea8ea5decb233e2e3c0ce84b78dc3ca0cd9fd2260792ece12224fca5813f03c7ad57b1faa07e3ca8fafb278fa23976fc7a35b8b4ae4efedacd1e193d89738ac2aa")]
|
||||
|
||||
[assembly: AllowPartiallyTrustedCallers]
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay
|
||||
{
|
||||
public static class D3AssemblyConstants
|
||||
{
|
||||
public const string DefaultXmlNamespace = "http://research.microsoft.com/DynamicDataDisplay/1.0";
|
||||
}
|
||||
}
|
||||
1
Changelog.txt
Normal file
1
Changelog.txt
Normal file
@@ -0,0 +1 @@
|
||||
Renamed ChartPlotter's HorizontalAxis to MainHorizontalAxis, VerticalAxis to MainVerticalAxis.
|
||||
503
ChartPlotter.cs
Normal file
503
ChartPlotter.cs
Normal file
@@ -0,0 +1,503 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Navigation;
|
||||
using Microsoft.Research.DynamicDataDisplay.Navigation;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay
|
||||
{
|
||||
/// <summary>Chart plotter is a plotter that renders axis and grid</summary>
|
||||
public class ChartPlotter : Plotter2D
|
||||
{
|
||||
private GeneralAxis horizontalAxis = new HorizontalAxis();
|
||||
private GeneralAxis verticalAxis = new VerticalAxis();
|
||||
private AxisGrid axisGrid = new AxisGrid();
|
||||
|
||||
private readonly Legend legend = new Legend();
|
||||
|
||||
private NewLegend newLegend = new NewLegend();
|
||||
public NewLegend NewLegend
|
||||
{
|
||||
get { return newLegend; }
|
||||
set { newLegend = value; }
|
||||
}
|
||||
|
||||
public ItemsPanelTemplate LegendPanelTemplate
|
||||
{
|
||||
get { return newLegend.ItemsPanel; }
|
||||
set { newLegend.ItemsPanel = value; }
|
||||
}
|
||||
|
||||
public Style LegendStyle
|
||||
{
|
||||
get { return newLegend.Style; }
|
||||
set { newLegend.Style = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChartPlotter"/> class.
|
||||
/// </summary>
|
||||
public ChartPlotter()
|
||||
: base()
|
||||
{
|
||||
horizontalAxis.TicksChanged += OnHorizontalAxisTicksChanged;
|
||||
verticalAxis.TicksChanged += OnVerticalAxisTicksChanged;
|
||||
|
||||
SetIsDefaultAxis(horizontalAxis as DependencyObject, true);
|
||||
SetIsDefaultAxis(verticalAxis as DependencyObject, true);
|
||||
|
||||
mouseNavigation = new MouseNavigation();
|
||||
keyboardNavigation = new KeyboardNavigation();
|
||||
defaultContextMenu = new DefaultContextMenu();
|
||||
horizontalAxisNavigation = new AxisNavigation { Placement = AxisPlacement.Bottom };
|
||||
verticalAxisNavigation = new AxisNavigation { Placement = AxisPlacement.Left };
|
||||
|
||||
Children.AddMany(
|
||||
horizontalAxis,
|
||||
verticalAxis,
|
||||
axisGrid,
|
||||
mouseNavigation,
|
||||
keyboardNavigation,
|
||||
defaultContextMenu,
|
||||
horizontalAxisNavigation,
|
||||
legend,
|
||||
verticalAxisNavigation,
|
||||
new LongOperationsIndicator(),
|
||||
newLegend
|
||||
);
|
||||
|
||||
#if DEBUG
|
||||
Children.Add(new DebugMenu());
|
||||
#endif
|
||||
|
||||
SetAllChildrenAsDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates generic plotter from this ChartPlotter.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public GenericChartPlotter<double, double> GetGenericPlotter()
|
||||
{
|
||||
return new GenericChartPlotter<double, double>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates generic plotter from this ChartPlotter.
|
||||
/// Horizontal and Vertical types of GenericPlotter should correspond to ChartPlotter's actual main axes types.
|
||||
/// </summary>
|
||||
/// <typeparam name="THorizontal">The type of horizontal values.</typeparam>
|
||||
/// <typeparam name="TVertical">The type of vertical values.</typeparam>
|
||||
/// <returns>GenericChartPlotter, associated to this ChartPlotter.</returns>
|
||||
public GenericChartPlotter<THorizontal, TVertical> GetGenericPlotter<THorizontal, TVertical>()
|
||||
{
|
||||
return new GenericChartPlotter<THorizontal, TVertical>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates generic plotter from this ChartPlotter.
|
||||
/// </summary>
|
||||
/// <typeparam name="THorizontal">The type of the horizontal axis.</typeparam>
|
||||
/// <typeparam name="TVertical">The type of the vertical axis.</typeparam>
|
||||
/// <param name="horizontalAxis">The horizontal axis to use as data conversion source.</param>
|
||||
/// <param name="verticalAxis">The vertical axis to use as data conversion source.</param>
|
||||
/// <returns>GenericChartPlotter, associated to this ChartPlotter</returns>
|
||||
public GenericChartPlotter<THorizontal, TVertical> GetGenericPlotter<THorizontal, TVertical>(AxisBase<THorizontal> horizontalAxis, AxisBase<TVertical> verticalAxis)
|
||||
{
|
||||
return new GenericChartPlotter<THorizontal, TVertical>(this, horizontalAxis, verticalAxis);
|
||||
}
|
||||
|
||||
protected ChartPlotter(PlotterLoadMode loadMode) : base(loadMode) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates empty plotter without any axes, navigation, etc.
|
||||
/// </summary>
|
||||
/// <returns>Empty plotter without any axes, navigation, etc.</returns>
|
||||
public static ChartPlotter CreateEmpty()
|
||||
{
|
||||
return new ChartPlotter(PlotterLoadMode.OnlyViewport);
|
||||
}
|
||||
|
||||
public void BeginLongOperation()
|
||||
{
|
||||
LongOperationsIndicator.BeginLongOperation(this);
|
||||
}
|
||||
|
||||
public void EndLongOperation()
|
||||
{
|
||||
LongOperationsIndicator.EndLongOperation(this);
|
||||
}
|
||||
|
||||
#region Default charts
|
||||
|
||||
private MouseNavigation mouseNavigation;
|
||||
/// <summary>
|
||||
/// Gets the default mouse navigation of ChartPlotter.
|
||||
/// </summary>
|
||||
/// <value>The mouse navigation.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public MouseNavigation MouseNavigation
|
||||
{
|
||||
get { return mouseNavigation; }
|
||||
}
|
||||
|
||||
private KeyboardNavigation keyboardNavigation;
|
||||
/// <summary>
|
||||
/// Gets the default keyboard navigation of ChartPlotter.
|
||||
/// </summary>
|
||||
/// <value>The keyboard navigation.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public KeyboardNavigation KeyboardNavigation
|
||||
{
|
||||
get { return keyboardNavigation; }
|
||||
}
|
||||
|
||||
private DefaultContextMenu defaultContextMenu;
|
||||
/// <summary>
|
||||
/// Gets the default context menu of ChartPlotter.
|
||||
/// </summary>
|
||||
/// <value>The default context menu.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public DefaultContextMenu DefaultContextMenu
|
||||
{
|
||||
get { return defaultContextMenu; }
|
||||
}
|
||||
|
||||
private AxisNavigation horizontalAxisNavigation;
|
||||
/// <summary>
|
||||
/// Gets the default horizontal axis navigation of ChartPlotter.
|
||||
/// </summary>
|
||||
/// <value>The horizontal axis navigation.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public AxisNavigation HorizontalAxisNavigation
|
||||
{
|
||||
get { return horizontalAxisNavigation; }
|
||||
}
|
||||
|
||||
private AxisNavigation verticalAxisNavigation;
|
||||
/// <summary>
|
||||
/// Gets the default vertical axis navigation of ChartPlotter.
|
||||
/// </summary>
|
||||
/// <value>The vertical axis navigation.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public AxisNavigation VerticalAxisNavigation
|
||||
{
|
||||
get { return verticalAxisNavigation; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default axis grid of ChartPlotter.
|
||||
/// </summary>
|
||||
/// <value>The axis grid.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public AxisGrid AxisGrid
|
||||
{
|
||||
get { return axisGrid; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void OnHorizontalAxisTicksChanged(object sender, EventArgs e)
|
||||
{
|
||||
GeneralAxis axis = (GeneralAxis)sender;
|
||||
UpdateHorizontalTicks(axis);
|
||||
}
|
||||
|
||||
private void UpdateHorizontalTicks(GeneralAxis axis)
|
||||
{
|
||||
axisGrid.BeginTicksUpdate();
|
||||
|
||||
if (axis != null)
|
||||
{
|
||||
axisGrid.HorizontalTicks = axis.ScreenTicks;
|
||||
axisGrid.MinorHorizontalTicks = axis.MinorScreenTicks;
|
||||
}
|
||||
else
|
||||
{
|
||||
axisGrid.HorizontalTicks = null;
|
||||
axisGrid.MinorHorizontalTicks = null;
|
||||
}
|
||||
|
||||
axisGrid.EndTicksUpdate();
|
||||
}
|
||||
|
||||
private void OnVerticalAxisTicksChanged(object sender, EventArgs e)
|
||||
{
|
||||
GeneralAxis axis = (GeneralAxis)sender;
|
||||
UpdateVerticalTicks(axis);
|
||||
}
|
||||
|
||||
private void UpdateVerticalTicks(GeneralAxis axis)
|
||||
{
|
||||
axisGrid.BeginTicksUpdate();
|
||||
|
||||
if (axis != null)
|
||||
{
|
||||
axisGrid.VerticalTicks = axis.ScreenTicks;
|
||||
axisGrid.MinorVerticalTicks = axis.MinorScreenTicks;
|
||||
}
|
||||
else
|
||||
{
|
||||
axisGrid.VerticalTicks = null;
|
||||
axisGrid.MinorVerticalTicks = null;
|
||||
}
|
||||
|
||||
axisGrid.EndTicksUpdate();
|
||||
}
|
||||
|
||||
bool keepOldAxis = false;
|
||||
bool updatingAxis = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the main vertical axis of ChartPlotter.
|
||||
/// Main vertical axis of ChartPlotter is axis which ticks are used to draw horizontal lines on AxisGrid.
|
||||
/// Value can be set to null to completely remove main vertical axis.
|
||||
/// </summary>
|
||||
/// <value>The main vertical axis.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public GeneralAxis MainVerticalAxis
|
||||
{
|
||||
get { return verticalAxis; }
|
||||
set
|
||||
{
|
||||
if (updatingAxis)
|
||||
return;
|
||||
|
||||
if (value == null && verticalAxis != null)
|
||||
{
|
||||
if (!keepOldAxis)
|
||||
{
|
||||
Children.Remove(verticalAxis);
|
||||
}
|
||||
verticalAxis.TicksChanged -= OnVerticalAxisTicksChanged;
|
||||
verticalAxis = null;
|
||||
|
||||
UpdateVerticalTicks(verticalAxis);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
VerifyAxisType(value.Placement, AxisType.Vertical);
|
||||
|
||||
if (value != verticalAxis)
|
||||
{
|
||||
ValidateVerticalAxis(value);
|
||||
|
||||
updatingAxis = true;
|
||||
|
||||
if (verticalAxis != null)
|
||||
{
|
||||
verticalAxis.TicksChanged -= OnVerticalAxisTicksChanged;
|
||||
SetIsDefaultAxis(verticalAxis, false);
|
||||
if (!keepOldAxis)
|
||||
{
|
||||
Children.Remove(verticalAxis);
|
||||
}
|
||||
}
|
||||
SetIsDefaultAxis(value, true);
|
||||
|
||||
verticalAxis = value;
|
||||
verticalAxis.TicksChanged += OnVerticalAxisTicksChanged;
|
||||
|
||||
if (!Children.Contains(value))
|
||||
{
|
||||
Children.Add(value);
|
||||
}
|
||||
|
||||
UpdateVerticalTicks(value);
|
||||
OnVerticalAxisChanged();
|
||||
|
||||
updatingAxis = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnVerticalAxisChanged() { }
|
||||
protected virtual void ValidateVerticalAxis(GeneralAxis axis) { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the main horizontal axis visibility.
|
||||
/// </summary>
|
||||
/// <value>The main horizontal axis visibility.</value>
|
||||
public Visibility MainHorizontalAxisVisibility
|
||||
{
|
||||
get { return MainHorizontalAxis != null ? ((UIElement)MainHorizontalAxis).Visibility : Visibility.Hidden; }
|
||||
set
|
||||
{
|
||||
if (MainHorizontalAxis != null)
|
||||
{
|
||||
((UIElement)MainHorizontalAxis).Visibility = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the main vertical axis visibility.
|
||||
/// </summary>
|
||||
/// <value>The main vertical axis visibility.</value>
|
||||
public Visibility MainVerticalAxisVisibility
|
||||
{
|
||||
get { return MainVerticalAxis != null ? ((UIElement)MainVerticalAxis).Visibility : Visibility.Hidden; }
|
||||
set
|
||||
{
|
||||
if (MainVerticalAxis != null)
|
||||
{
|
||||
((UIElement)MainVerticalAxis).Visibility = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the main horizontal axis of ChartPlotter.
|
||||
/// Main horizontal axis of ChartPlotter is axis which ticks are used to draw vertical lines on AxisGrid.
|
||||
/// Value can be set to null to completely remove main horizontal axis.
|
||||
/// </summary>
|
||||
/// <value>The main horizontal axis.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public GeneralAxis MainHorizontalAxis
|
||||
{
|
||||
get { return horizontalAxis; }
|
||||
set
|
||||
{
|
||||
if (updatingAxis)
|
||||
return;
|
||||
|
||||
if (value == null && horizontalAxis != null)
|
||||
{
|
||||
Children.Remove(horizontalAxis);
|
||||
horizontalAxis.TicksChanged -= OnHorizontalAxisTicksChanged;
|
||||
horizontalAxis = null;
|
||||
|
||||
UpdateHorizontalTicks(horizontalAxis);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
VerifyAxisType(value.Placement, AxisType.Horizontal);
|
||||
|
||||
if (value != horizontalAxis)
|
||||
{
|
||||
ValidateHorizontalAxis(value);
|
||||
|
||||
updatingAxis = true;
|
||||
|
||||
if (horizontalAxis != null)
|
||||
{
|
||||
horizontalAxis.TicksChanged -= OnHorizontalAxisTicksChanged;
|
||||
SetIsDefaultAxis(horizontalAxis, false);
|
||||
if (!keepOldAxis)
|
||||
{
|
||||
Children.Remove(horizontalAxis);
|
||||
}
|
||||
}
|
||||
SetIsDefaultAxis(value, true);
|
||||
|
||||
horizontalAxis = value;
|
||||
horizontalAxis.TicksChanged += OnHorizontalAxisTicksChanged;
|
||||
|
||||
if (!Children.Contains(value))
|
||||
{
|
||||
Children.Add(value);
|
||||
}
|
||||
|
||||
UpdateHorizontalTicks(value);
|
||||
OnHorizontalAxisChanged();
|
||||
|
||||
updatingAxis = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnHorizontalAxisChanged() { }
|
||||
protected virtual void ValidateHorizontalAxis(GeneralAxis axis) { }
|
||||
|
||||
private static void VerifyAxisType(AxisPlacement axisPlacement, AxisType axisType)
|
||||
{
|
||||
bool result = false;
|
||||
switch (axisPlacement)
|
||||
{
|
||||
case AxisPlacement.Left:
|
||||
case AxisPlacement.Right:
|
||||
result = axisType == AxisType.Vertical;
|
||||
break;
|
||||
case AxisPlacement.Top:
|
||||
case AxisPlacement.Bottom:
|
||||
result = axisType == AxisType.Horizontal;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!result)
|
||||
throw new ArgumentException(Strings.Exceptions.InvalidAxisPlacement);
|
||||
}
|
||||
|
||||
protected override void OnIsDefaultAxisChangedCore(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
GeneralAxis axis = d as GeneralAxis;
|
||||
if (axis != null)
|
||||
{
|
||||
bool value = (bool)e.NewValue;
|
||||
bool oldKeepOldAxis = keepOldAxis;
|
||||
|
||||
bool horizontal = axis.Placement == AxisPlacement.Bottom || axis.Placement == AxisPlacement.Top;
|
||||
keepOldAxis = true;
|
||||
|
||||
if (value && horizontal)
|
||||
{
|
||||
MainHorizontalAxis = axis;
|
||||
}
|
||||
else if (value && !horizontal)
|
||||
{
|
||||
MainVerticalAxis = axis;
|
||||
}
|
||||
else if (!value && horizontal)
|
||||
{
|
||||
MainHorizontalAxis = null;
|
||||
}
|
||||
else if (!value && !horizontal)
|
||||
{
|
||||
MainVerticalAxis = null;
|
||||
}
|
||||
|
||||
keepOldAxis = oldKeepOldAxis;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default legend of ChartPlotter.
|
||||
/// </summary>
|
||||
/// <value>The legend.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public Legend Legend
|
||||
{
|
||||
get { return legend; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the visibility of legend.
|
||||
/// </summary>
|
||||
/// <value>The legend visibility.</value>
|
||||
public Visibility LegendVisibility
|
||||
{
|
||||
get { return legend.Visibility; }
|
||||
set { legend.Visibility = value; }
|
||||
}
|
||||
|
||||
public bool NewLegendVisible
|
||||
{
|
||||
get { return newLegend.LegendVisible; }
|
||||
set { newLegend.LegendVisible = value; }
|
||||
}
|
||||
|
||||
private enum AxisType
|
||||
{
|
||||
Horizontal,
|
||||
Vertical
|
||||
}
|
||||
}
|
||||
}
|
||||
411
Charts/Axes/AxisBase.cs
Normal file
411
Charts/Axes/AxisBase.cs
Normal file
@@ -0,0 +1,411 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Data;
|
||||
using System.Diagnostics;
|
||||
using System.ComponentModel;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a base class for all axes in ChartPlotter.
|
||||
/// Contains a real UI representation of axis - AxisControl, and means to adjust number of ticks, algorythms of their generating and
|
||||
/// look of ticks' labels.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of each tick's value</typeparam>
|
||||
public abstract class AxisBase<T> : GeneralAxis, ITypedAxis<T>, IValueConversion<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AxisBase<T>"/> class.
|
||||
/// </summary>
|
||||
/// <param name="axisControl">The axis control.</param>
|
||||
/// <param name="convertFromDouble">The convert from double.</param>
|
||||
/// <param name="convertToDouble">The convert to double.</param>
|
||||
protected AxisBase(AxisControl<T> axisControl, Func<double, T> convertFromDouble, Func<T, double> convertToDouble)
|
||||
{
|
||||
if (axisControl == null)
|
||||
throw new ArgumentNullException("axisControl");
|
||||
if (convertFromDouble == null)
|
||||
throw new ArgumentNullException("convertFromDouble");
|
||||
if (convertToDouble == null)
|
||||
throw new ArgumentNullException("convertToDouble");
|
||||
|
||||
this.convertToDouble = convertToDouble;
|
||||
this.convertFromDouble = convertFromDouble;
|
||||
|
||||
this.axisControl = axisControl;
|
||||
axisControl.MakeDependent();
|
||||
axisControl.ConvertToDouble = convertToDouble;
|
||||
axisControl.ScreenTicksChanged += axisControl_ScreenTicksChanged;
|
||||
|
||||
Content = axisControl;
|
||||
axisControl.SetBinding(Control.BackgroundProperty, new Binding("Background") { Source = this });
|
||||
|
||||
Focusable = false;
|
||||
|
||||
Loaded += OnLoaded;
|
||||
}
|
||||
|
||||
public override void ForceUpdate()
|
||||
{
|
||||
axisControl.UpdateUI();
|
||||
}
|
||||
|
||||
private void axisControl_ScreenTicksChanged(object sender, EventArgs e)
|
||||
{
|
||||
RaiseTicksChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this axis is default axis.
|
||||
/// ChartPlotter's AxisGrid gets axis ticks to display from two default axes - horizontal and vertical.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is default axis; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsDefaultAxis
|
||||
{
|
||||
get { return Microsoft.Research.DynamicDataDisplay.Plotter.GetIsDefaultAxis(this); }
|
||||
set { Microsoft.Research.DynamicDataDisplay.Plotter.SetIsDefaultAxis(this, value); }
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
RaiseTicksChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the screen coordinates of axis ticks.
|
||||
/// </summary>
|
||||
/// <value>The screen ticks.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override double[] ScreenTicks
|
||||
{
|
||||
get { return axisControl.ScreenTicks; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the screen coordinates of minor ticks.
|
||||
/// </summary>
|
||||
/// <value>The minor screen ticks.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override MinorTickInfo<double>[] MinorScreenTicks
|
||||
{
|
||||
get { return axisControl.MinorScreenTicks; }
|
||||
}
|
||||
|
||||
private AxisControl<T> axisControl;
|
||||
/// <summary>
|
||||
/// Gets the axis control - actual UI representation of axis.
|
||||
/// </summary>
|
||||
/// <value>The axis control.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public AxisControl<T> AxisControl
|
||||
{
|
||||
get { return axisControl; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ticks provider, which is used to generate ticks in given range.
|
||||
/// </summary>
|
||||
/// <value>The ticks provider.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public ITicksProvider<T> TicksProvider
|
||||
{
|
||||
get { return axisControl.TicksProvider; }
|
||||
set { axisControl.TicksProvider = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the label provider, that is used to create UI look of axis ticks.
|
||||
///
|
||||
/// Should not be null.
|
||||
/// </summary>
|
||||
/// <value>The label provider.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
[NotNull]
|
||||
public LabelProviderBase<T> LabelProvider
|
||||
{
|
||||
get { return axisControl.LabelProvider; }
|
||||
set { axisControl.LabelProvider = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the major label provider, which creates labels for major ticks.
|
||||
/// If null, major labels will not be shown.
|
||||
/// </summary>
|
||||
/// <value>The major label provider.</value>
|
||||
public LabelProviderBase<T> MajorLabelProvider
|
||||
{
|
||||
get { return axisControl.MajorLabelProvider; }
|
||||
set { axisControl.MajorLabelProvider = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the label string format, used to create simple formats of each tick's label, such as
|
||||
/// changing tick label from "1.2" to "$1.2".
|
||||
/// Should be in format "*{0}*", where '*' is any number of any chars.
|
||||
///
|
||||
/// If value is null, format string will not be used.
|
||||
/// </summary>
|
||||
/// <value>The label string format.</value>
|
||||
public string LabelStringFormat
|
||||
{
|
||||
get { return LabelProvider.LabelStringFormat; }
|
||||
set { LabelProvider.LabelStringFormat = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show minor ticks.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if show minor ticks; otherwise, <c>false</c>.</value>
|
||||
public bool ShowMinorTicks
|
||||
{
|
||||
get { return axisControl.DrawMinorTicks; }
|
||||
set { axisControl.DrawMinorTicks = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show major labels.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if show major labels; otherwise, <c>false</c>.</value>
|
||||
public bool ShowMajorLabels
|
||||
{
|
||||
get { return axisControl.DrawMajorLabels; }
|
||||
set { axisControl.DrawMajorLabels = value; }
|
||||
}
|
||||
|
||||
protected override void OnPlotterAttached(Plotter2D plotter)
|
||||
{
|
||||
plotter.Viewport.PropertyChanged += OnViewportPropertyChanged;
|
||||
|
||||
Panel panel = GetPanelByPlacement(Placement);
|
||||
if (panel != null)
|
||||
{
|
||||
int index = GetInsertionIndexByPlacement(Placement, panel);
|
||||
panel.Children.Insert(index, this);
|
||||
}
|
||||
|
||||
using (axisControl.OpenUpdateRegion(true))
|
||||
{
|
||||
UpdateAxisControl(plotter);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAxisControl(Plotter2D plotter2d)
|
||||
{
|
||||
axisControl.Transform = plotter2d.Viewport.Transform;
|
||||
axisControl.Range = CreateRangeFromRect(plotter2d.Visible.ViewportToData(plotter2d.Viewport.Transform));
|
||||
}
|
||||
|
||||
private int GetInsertionIndexByPlacement(AxisPlacement placement, Panel panel)
|
||||
{
|
||||
int index = panel.Children.Count;
|
||||
|
||||
switch (placement)
|
||||
{
|
||||
case AxisPlacement.Left:
|
||||
index = 0;
|
||||
break;
|
||||
case AxisPlacement.Top:
|
||||
index = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
ExtendedPropertyChangedEventArgs visibleChangedEventArgs;
|
||||
int viewportPropertyChangedEnters = 0;
|
||||
DataRect prevDataRect = DataRect.Empty;
|
||||
private void OnViewportPropertyChanged(object sender, ExtendedPropertyChangedEventArgs e)
|
||||
{
|
||||
if (viewportPropertyChangedEnters > 4)
|
||||
{
|
||||
if (e.PropertyName == "Visible")
|
||||
{
|
||||
visibleChangedEventArgs = e;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
viewportPropertyChangedEnters++;
|
||||
|
||||
Viewport2D viewport = (Viewport2D)sender;
|
||||
|
||||
DataRect visible = viewport.Visible;
|
||||
|
||||
DataRect dataRect = visible.ViewportToData(viewport.Transform);
|
||||
bool forceUpdate = dataRect != prevDataRect;
|
||||
prevDataRect = dataRect;
|
||||
|
||||
Range<T> range = CreateRangeFromRect(dataRect);
|
||||
|
||||
using (axisControl.OpenUpdateRegion(false)) // todo was forceUpdate
|
||||
{
|
||||
axisControl.Range = range;
|
||||
axisControl.Transform = viewport.Transform;
|
||||
}
|
||||
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
viewportPropertyChangedEnters--;
|
||||
if (visibleChangedEventArgs != null)
|
||||
{
|
||||
OnViewportPropertyChanged(Plotter.Viewport, visibleChangedEventArgs);
|
||||
}
|
||||
visibleChangedEventArgs = null;
|
||||
}, DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
private Func<double, T> convertFromDouble;
|
||||
/// <summary>
|
||||
/// Gets or sets the delegate that is used to create each tick from double.
|
||||
/// Is used to create typed range to display for internal AxisControl.
|
||||
/// If changed, ConvertToDouble should be changed appropriately, too.
|
||||
/// Should not be null.
|
||||
/// </summary>
|
||||
/// <value>The convert from double.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
[NotNull]
|
||||
public Func<double, T> ConvertFromDouble
|
||||
{
|
||||
get { return convertFromDouble; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
if (convertFromDouble != value)
|
||||
{
|
||||
convertFromDouble = value;
|
||||
if (ParentPlotter != null)
|
||||
{
|
||||
UpdateAxisControl(ParentPlotter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Func<T, double> convertToDouble;
|
||||
/// <summary>
|
||||
/// Gets or sets the delegate that is used to convert each tick to double.
|
||||
/// Is used by internal AxisControl to convert tick to double to get tick's coordinates inside of viewport.
|
||||
/// If changed, ConvertFromDouble should be changed appropriately, too.
|
||||
/// Should not be null.
|
||||
/// </summary>
|
||||
/// <value>The convert to double.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
[NotNull]
|
||||
public Func<T, double> ConvertToDouble
|
||||
{
|
||||
get { return convertToDouble; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
if (convertToDouble != value)
|
||||
{
|
||||
convertToDouble = value;
|
||||
axisControl.ConvertToDouble = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets conversions of axis - functions used to convert values of axis type to and from double values of viewport.
|
||||
/// Sets both ConvertToDouble and ConvertFromDouble properties.
|
||||
/// </summary>
|
||||
/// <param name="min">The minimal viewport value.</param>
|
||||
/// <param name="minValue">The value of axis type, corresponding to minimal viewport value.</param>
|
||||
/// <param name="max">The maximal viewport value.</param>
|
||||
/// <param name="maxValue">The value of axis type, corresponding to maximal viewport value.</param>
|
||||
public virtual void SetConversion(double min, T minValue, double max, T maxValue)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Range<T> CreateRangeFromRect(DataRect visible)
|
||||
{
|
||||
T min, max;
|
||||
|
||||
Range<T> range;
|
||||
switch (Placement)
|
||||
{
|
||||
case AxisPlacement.Left:
|
||||
case AxisPlacement.Right:
|
||||
min = ConvertFromDouble(visible.YMin);
|
||||
max = ConvertFromDouble(visible.YMax);
|
||||
break;
|
||||
case AxisPlacement.Top:
|
||||
case AxisPlacement.Bottom:
|
||||
min = ConvertFromDouble(visible.XMin);
|
||||
max = ConvertFromDouble(visible.XMax);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
TrySort(ref min, ref max);
|
||||
range = new Range<T>(min, max);
|
||||
return range;
|
||||
}
|
||||
|
||||
private static void TrySort<TS>(ref TS min, ref TS max)
|
||||
{
|
||||
if (min is IComparable)
|
||||
{
|
||||
IComparable c1 = (IComparable)min;
|
||||
// if min > max
|
||||
if (c1.CompareTo(max) > 0)
|
||||
{
|
||||
TS temp = min;
|
||||
min = max;
|
||||
max = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPlacementChanged(AxisPlacement oldPlacement, AxisPlacement newPlacement)
|
||||
{
|
||||
axisControl.Placement = Placement;
|
||||
if (ParentPlotter != null)
|
||||
{
|
||||
Panel panel = GetPanelByPlacement(oldPlacement);
|
||||
panel.Children.Remove(this);
|
||||
|
||||
Panel newPanel = GetPanelByPlacement(newPlacement);
|
||||
int index = GetInsertionIndexByPlacement(newPlacement, newPanel);
|
||||
newPanel.Children.Insert(index, this);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPlotterDetaching(Plotter2D plotter)
|
||||
{
|
||||
if (plotter == null)
|
||||
return;
|
||||
|
||||
Panel panel = GetPanelByPlacement(Placement);
|
||||
if (panel != null)
|
||||
{
|
||||
panel.Children.Remove(this);
|
||||
}
|
||||
|
||||
plotter.Viewport.PropertyChanged -= OnViewportPropertyChanged;
|
||||
axisControl.Transform = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
1231
Charts/Axes/AxisControl.cs
Normal file
1231
Charts/Axes/AxisControl.cs
Normal file
File diff suppressed because it is too large
Load Diff
42
Charts/Axes/AxisControlBase.cs
Normal file
42
Charts/Axes/AxisControlBase.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public abstract class AxisControlBase : ContentControl
|
||||
{
|
||||
#region Properties
|
||||
|
||||
public HorizontalAlignment LabelsHorizontalAlignment
|
||||
{
|
||||
get { return (HorizontalAlignment)GetValue(LabelsHorizontalAlignmentProperty); }
|
||||
set { SetValue(LabelsHorizontalAlignmentProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LabelsHorizontalAlignmentProperty = DependencyProperty.Register(
|
||||
"LabelsHorizontalAlignment",
|
||||
typeof(HorizontalAlignment),
|
||||
typeof(AxisControlBase),
|
||||
new FrameworkPropertyMetadata(HorizontalAlignment.Center));
|
||||
|
||||
|
||||
public VerticalAlignment LabelsVerticalAlignment
|
||||
{
|
||||
get { return (VerticalAlignment)GetValue(LabelsVerticalAlignmentProperty); }
|
||||
set { SetValue(LabelsVerticalAlignmentProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LabelsVerticalAlignmentProperty = DependencyProperty.Register(
|
||||
"LabelsVerticalAlignment",
|
||||
typeof(VerticalAlignment),
|
||||
typeof(AxisControlBase),
|
||||
new FrameworkPropertyMetadata(VerticalAlignment.Center));
|
||||
|
||||
#endregion // end of Properties
|
||||
|
||||
}
|
||||
}
|
||||
76
Charts/Axes/AxisControlStyle.xaml
Normal file
76
Charts/Axes/AxisControlStyle.xaml
Normal file
@@ -0,0 +1,76 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Microsoft.Research.DynamicDataDisplay.Charts"
|
||||
>
|
||||
<Style TargetType="{x:Type Grid}" x:Key="gridStyle">
|
||||
<Setter Property="Grid.ClipToBounds" Value="False"/>
|
||||
</Style>
|
||||
|
||||
<RotateTransform Angle="-90" x:Key="additionalLabelsTransformLeft"/>
|
||||
|
||||
<ControlTemplate x:Key="axisControlTemplateBottom">
|
||||
<Grid Style="{StaticResource gridStyle}" Name="PART_ContentsGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Path Name="PART_TicksPath" Grid.Row="0" Stroke="Black"/>
|
||||
<local:StackCanvas Name="PART_CommonLabelsCanvas" Grid.Row="1" Placement="Bottom"/>
|
||||
<local:StackCanvas Name="PART_AdditionalLabelsCanvas" Grid.Row="2" Placement="Bottom"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="axisControlTemplateTop">
|
||||
<Grid Background="{TemplateBinding Background}" Style="{StaticResource gridStyle}" Name="PART_ContentsGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Path Name="PART_TicksPath" Grid.Row="2" Stroke="Black">
|
||||
<Path.LayoutTransform>
|
||||
<MatrixTransform Matrix="1,0,0,-1.1,0,0"/>
|
||||
</Path.LayoutTransform>
|
||||
</Path>
|
||||
<local:StackCanvas Name="PART_CommonLabelsCanvas" Grid.Row="1" Placement="Top"/>
|
||||
<local:StackCanvas Name="PART_AdditionalLabelsCanvas" Grid.Row="0" Placement="Top"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="axisControlTemplateLeft">
|
||||
<Grid Background="{TemplateBinding Background}" Style="{StaticResource gridStyle}" Name="PART_ContentsGrid">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Rectangle Grid.Column="2"/>
|
||||
<Path Name="PART_TicksPath" Grid.Column="2" Stroke="Black">
|
||||
<Path.LayoutTransform>
|
||||
<MatrixTransform Matrix="-1,0,0,1,0,0"/>
|
||||
</Path.LayoutTransform>
|
||||
</Path>
|
||||
<local:StackCanvas Name="PART_CommonLabelsCanvas" Grid.Column="1" Placement="Left" Margin="1,0,1,0"/>
|
||||
<local:StackCanvas Name="PART_AdditionalLabelsCanvas" Grid.Column="0" Placement="Left" Margin="1,0,1,0"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="axisControlTemplateRight">
|
||||
<Grid Background="{TemplateBinding Background}" Style="{StaticResource gridStyle}" Name="PART_ContentsGrid">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Path Name="PART_TicksPath" Grid.Column="0" Stroke="Black"/>
|
||||
<local:StackCanvas Name="PART_CommonLabelsCanvas" Grid.Column="1" Placement="Right" Margin="1,0,0,0"/>
|
||||
<local:StackCanvas Name="PART_AdditionalLabelsCanvas" Grid.Column="2" Placement="Right"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
</ResourceDictionary>
|
||||
287
Charts/Axes/AxisGrid.cs
Normal file
287
Charts/Axes/AxisGrid.cs
Normal file
@@ -0,0 +1,287 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts;
|
||||
using System.Windows.Controls;
|
||||
using System;
|
||||
using System.Windows.Shapes;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay
|
||||
{
|
||||
/// <summary>
|
||||
/// Draws grid over viewport. Number of
|
||||
/// grid lines depends on Plotter's MainHorizontalAxis and MainVerticalAxis ticks.
|
||||
/// </summary>
|
||||
public class AxisGrid : ContentControl, IPlotterElement
|
||||
{
|
||||
static AxisGrid()
|
||||
{
|
||||
Type thisType = typeof(AxisGrid);
|
||||
Panel.ZIndexProperty.OverrideMetadata(thisType, new FrameworkPropertyMetadata(-1));
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
|
||||
internal void BeginTicksUpdate()
|
||||
{
|
||||
}
|
||||
internal void EndTicksUpdate()
|
||||
{
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
protected internal MinorTickInfo<double>[] MinorHorizontalTicks { get; set; }
|
||||
|
||||
protected internal MinorTickInfo<double>[] MinorVerticalTicks { get; set; }
|
||||
|
||||
protected internal double[] HorizontalTicks { get; set; }
|
||||
|
||||
protected internal double[] VerticalTicks { get; set; }
|
||||
|
||||
|
||||
private bool drawVerticalTicks = true;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to draw vertical tick lines.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if draw vertical ticks; otherwise, <c>false</c>.</value>
|
||||
public bool DrawVerticalTicks
|
||||
{
|
||||
get { return drawVerticalTicks; }
|
||||
set
|
||||
{
|
||||
if (drawVerticalTicks != value)
|
||||
{
|
||||
drawVerticalTicks = value;
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool drawHorizontalTicks = true;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to draw horizontal tick lines.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if draw horizontal ticks; otherwise, <c>false</c>.</value>
|
||||
public bool DrawHorizontalTicks
|
||||
{
|
||||
get { return drawHorizontalTicks; }
|
||||
set
|
||||
{
|
||||
if (drawHorizontalTicks != value)
|
||||
{
|
||||
drawHorizontalTicks = value;
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool drawHorizontalMinorTicks = false;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to draw horizontal minor ticks.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if draw horizontal minor ticks; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool DrawHorizontalMinorTicks
|
||||
{
|
||||
get { return drawHorizontalMinorTicks; }
|
||||
set
|
||||
{
|
||||
if (drawHorizontalMinorTicks != value)
|
||||
{
|
||||
drawHorizontalMinorTicks = value;
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool drawVerticalMinorTicks = false;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to draw vertical minor ticks.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if draw vertical minor ticks; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool DrawVerticalMinorTicks
|
||||
{
|
||||
get { return drawVerticalMinorTicks; }
|
||||
set
|
||||
{
|
||||
if (drawVerticalMinorTicks != value)
|
||||
{
|
||||
drawVerticalMinorTicks = value;
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double gridBrushThickness = 1;
|
||||
|
||||
private Path path = new Path();
|
||||
private Canvas canvas = new Canvas();
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AxisGrid"/> class.
|
||||
/// </summary>
|
||||
public AxisGrid()
|
||||
{
|
||||
IsHitTestVisible = false;
|
||||
|
||||
canvas.ClipToBounds = true;
|
||||
|
||||
path.Stroke = Brushes.LightGray;
|
||||
path.StrokeThickness = gridBrushThickness;
|
||||
|
||||
Content = canvas;
|
||||
}
|
||||
|
||||
private readonly ResourcePool<LineGeometry> lineGeometryPool = new ResourcePool<LineGeometry>();
|
||||
private readonly ResourcePool<Line> linePool = new ResourcePool<Line>();
|
||||
|
||||
private void UpdateUIRepresentation()
|
||||
{
|
||||
foreach (UIElement item in canvas.Children)
|
||||
{
|
||||
Line line = item as Line;
|
||||
if (line != null)
|
||||
{
|
||||
linePool.Put(line);
|
||||
}
|
||||
}
|
||||
|
||||
canvas.Children.Clear();
|
||||
Size size = RenderSize;
|
||||
|
||||
DrawMinorHorizontalTicks();
|
||||
DrawMinorVerticalTicks();
|
||||
|
||||
GeometryGroup prevGroup = path.Data as GeometryGroup;
|
||||
if (prevGroup != null)
|
||||
{
|
||||
foreach (LineGeometry geometry in prevGroup.Children)
|
||||
{
|
||||
lineGeometryPool.Put(geometry);
|
||||
}
|
||||
}
|
||||
|
||||
GeometryGroup group = new GeometryGroup();
|
||||
if (HorizontalTicks != null && drawHorizontalTicks)
|
||||
{
|
||||
double minY = 0;
|
||||
double maxY = size.Height;
|
||||
|
||||
for (int i = 0; i < HorizontalTicks.Length; i++)
|
||||
{
|
||||
double screenX = HorizontalTicks[i];
|
||||
LineGeometry line = lineGeometryPool.GetOrCreate();
|
||||
line.StartPoint = new Point(screenX, minY);
|
||||
line.EndPoint = new Point(screenX, maxY);
|
||||
group.Children.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (VerticalTicks != null && drawVerticalTicks)
|
||||
{
|
||||
double minX = 0;
|
||||
double maxX = size.Width;
|
||||
|
||||
for (int i = 0; i < VerticalTicks.Length; i++)
|
||||
{
|
||||
double screenY = VerticalTicks[i];
|
||||
LineGeometry line = lineGeometryPool.GetOrCreate();
|
||||
line.StartPoint = new Point(minX, screenY);
|
||||
line.EndPoint = new Point(maxX, screenY);
|
||||
group.Children.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
canvas.Children.Add(path);
|
||||
path.Data = group;
|
||||
}
|
||||
|
||||
private void DrawMinorVerticalTicks()
|
||||
{
|
||||
Size size = RenderSize;
|
||||
if (MinorVerticalTicks != null && drawVerticalMinorTicks)
|
||||
{
|
||||
double minX = 0;
|
||||
double maxX = size.Width;
|
||||
|
||||
for (int i = 0; i < MinorVerticalTicks.Length; i++)
|
||||
{
|
||||
double screenY = MinorVerticalTicks[i].Tick;
|
||||
if (screenY < 0)
|
||||
continue;
|
||||
if (screenY > size.Height)
|
||||
continue;
|
||||
|
||||
Line line = linePool.GetOrCreate();
|
||||
|
||||
line.Y1 = screenY;
|
||||
line.Y2 = screenY;
|
||||
line.X1 = minX;
|
||||
line.X2 = maxX;
|
||||
line.Stroke = Brushes.LightGray;
|
||||
line.StrokeThickness = MinorVerticalTicks[i].Value * gridBrushThickness;
|
||||
|
||||
canvas.Children.Add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMinorHorizontalTicks()
|
||||
{
|
||||
Size size = RenderSize;
|
||||
if (MinorHorizontalTicks != null && drawHorizontalMinorTicks)
|
||||
{
|
||||
double minY = 0;
|
||||
double maxY = size.Height;
|
||||
|
||||
for (int i = 0; i < MinorHorizontalTicks.Length; i++)
|
||||
{
|
||||
double screenX = MinorHorizontalTicks[i].Tick;
|
||||
if (screenX < 0)
|
||||
continue;
|
||||
if (screenX > size.Width)
|
||||
continue;
|
||||
|
||||
Line line = linePool.GetOrCreate();
|
||||
line.X1 = screenX;
|
||||
line.X2 = screenX;
|
||||
line.Y1 = minY;
|
||||
line.Y2 = maxY;
|
||||
line.Stroke = Brushes.LightGray;
|
||||
line.StrokeThickness = MinorHorizontalTicks[i].Value * gridBrushThickness;
|
||||
|
||||
canvas.Children.Add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IPlotterElement Members
|
||||
|
||||
void IPlotterElement.OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
this.plotter = plotter;
|
||||
plotter.CentralGrid.Children.Add(this);
|
||||
}
|
||||
|
||||
void IPlotterElement.OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
plotter.CentralGrid.Children.Remove(this);
|
||||
this.plotter = null;
|
||||
}
|
||||
|
||||
private Plotter plotter;
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public Plotter Plotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
30
Charts/Axes/AxisPlacement.cs
Normal file
30
Charts/Axes/AxisPlacement.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the position of axis inside ChartPlotter.
|
||||
/// </summary>
|
||||
public enum AxisPlacement
|
||||
{
|
||||
/// <summary>
|
||||
/// Axis is placed to the left.
|
||||
/// </summary>
|
||||
Left,
|
||||
/// <summary>
|
||||
/// Axis is placed to the right.
|
||||
/// </summary>
|
||||
Right,
|
||||
/// <summary>
|
||||
/// Axis is placed to the top.
|
||||
/// </summary>
|
||||
Top,
|
||||
/// <summary>
|
||||
/// Axis is placed to the bottom.
|
||||
/// </summary>
|
||||
Bottom
|
||||
}
|
||||
}
|
||||
113
Charts/Axes/DateTime/DateTimeAxis.cs
Normal file
113
Charts/Axes/DateTime/DateTimeAxis.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.ViewportRestrictions;
|
||||
using System.Windows.Media;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an axis with ticks of <see cref="System.DateTime"/> type.
|
||||
/// </summary>
|
||||
public class DateTimeAxis : AxisBase<DateTime>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateTimeAxis"/> class.
|
||||
/// </summary>
|
||||
public DateTimeAxis()
|
||||
: base(new DateTimeAxisControl(), DoubleToDate,
|
||||
dt => dt.Ticks / 10000000000.0)
|
||||
{
|
||||
AxisControl.SetBinding(MajorLabelBackgroundBrushProperty, new Binding("MajorLabelBackgroundBrush") { Source = this });
|
||||
AxisControl.SetBinding(MajorLabelRectangleBorderPropertyProperty, new Binding("MajorLabelRectangleBorderProperty") { Source = this });
|
||||
}
|
||||
|
||||
#region VisualProperties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the major tick labels' background brush. This is a DependencyProperty.
|
||||
/// </summary>
|
||||
/// <value>The major label background brush.</value>
|
||||
public Brush MajorLabelBackgroundBrush
|
||||
{
|
||||
get { return (Brush)GetValue(MajorLabelBackgroundBrushProperty); }
|
||||
set { SetValue(MajorLabelBackgroundBrushProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty MajorLabelBackgroundBrushProperty = DependencyProperty.Register(
|
||||
"MajorLabelBackgroundBrush",
|
||||
typeof(Brush),
|
||||
typeof(DateTimeAxis),
|
||||
new FrameworkPropertyMetadata(Brushes.Beige));
|
||||
|
||||
|
||||
public Brush MajorLabelRectangleBorderProperty
|
||||
{
|
||||
get { return (Brush)GetValue(MajorLabelRectangleBorderPropertyProperty); }
|
||||
set { SetValue(MajorLabelRectangleBorderPropertyProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty MajorLabelRectangleBorderPropertyProperty = DependencyProperty.Register(
|
||||
"MajorLabelRectangleBorderProperty",
|
||||
typeof(Brush),
|
||||
typeof(DateTimeAxis),
|
||||
new FrameworkPropertyMetadata(Brushes.Peru));
|
||||
|
||||
#endregion // end of VisualProperties
|
||||
|
||||
private ViewportRestriction restriction = new DateTimeHorizontalAxisRestriction();
|
||||
protected ViewportRestriction Restriction
|
||||
{
|
||||
get { return restriction; }
|
||||
set { restriction = value; }
|
||||
}
|
||||
|
||||
protected override void OnPlotterAttached(Plotter2D plotter)
|
||||
{
|
||||
base.OnPlotterAttached(plotter);
|
||||
|
||||
plotter.Viewport.Restrictions.Add(restriction);
|
||||
}
|
||||
|
||||
protected override void OnPlotterDetaching(Plotter2D plotter)
|
||||
{
|
||||
plotter.Viewport.Restrictions.Remove(restriction);
|
||||
|
||||
base.OnPlotterDetaching(plotter);
|
||||
}
|
||||
|
||||
private static readonly long minTicks = DateTime.MinValue.Ticks;
|
||||
private static readonly long maxTicks = DateTime.MaxValue.Ticks;
|
||||
private static DateTime DoubleToDate(double d)
|
||||
{
|
||||
long ticks = (long)(d * 10000000000L);
|
||||
|
||||
// todo should we throw an exception if number of ticks is too big or small?
|
||||
if (ticks < minTicks)
|
||||
ticks = minTicks;
|
||||
else if (ticks > maxTicks)
|
||||
ticks = maxTicks;
|
||||
|
||||
return new DateTime(ticks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets conversions of axis - functions used to convert values of axis type to and from double values of viewport.
|
||||
/// Sets both ConvertToDouble and ConvertFromDouble properties.
|
||||
/// </summary>
|
||||
/// <param name="min">The minimal viewport value.</param>
|
||||
/// <param name="minValue">The value of axis type, corresponding to minimal viewport value.</param>
|
||||
/// <param name="max">The maximal viewport value.</param>
|
||||
/// <param name="maxValue">The value of axis type, corresponding to maximal viewport value.</param>
|
||||
public override void SetConversion(double min, DateTime minValue, double max, DateTime maxValue)
|
||||
{
|
||||
var conversion = new DateTimeToDoubleConversion(min, minValue, max, maxValue);
|
||||
|
||||
ConvertToDouble = conversion.ToDouble;
|
||||
ConvertFromDouble = conversion.FromDouble;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Charts/Axes/DateTime/DateTimeAxisControl.cs
Normal file
27
Charts/Axes/DateTime/DateTimeAxisControl.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// AxisControl for DateTime axes.
|
||||
/// </summary>
|
||||
public class DateTimeAxisControl : AxisControl<DateTime>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateTimeAxisControl"/> class.
|
||||
/// </summary>
|
||||
public DateTimeAxisControl()
|
||||
{
|
||||
LabelProvider = new DateTimeLabelProvider();
|
||||
TicksProvider = new DateTimeTicksProvider();
|
||||
MajorLabelProvider = new MajorDateTimeLabelProvider();
|
||||
|
||||
ConvertToDouble = dt => dt.Ticks;
|
||||
|
||||
Range = new Range<DateTime>(DateTime.Now, DateTime.Now.AddYears(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Charts/Axes/DateTime/DateTimeLabelProvider.cs
Normal file
48
Charts/Axes/DateTime/DateTimeLabelProvider.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a label provider for <see cref="System.DateTime"/> ticks.
|
||||
/// </summary>
|
||||
public class DateTimeLabelProvider : DateTimeLabelProviderBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateTimeLabelProvider"/> class.
|
||||
/// </summary>
|
||||
public DateTimeLabelProvider() { }
|
||||
|
||||
public override UIElement[] CreateLabels(ITicksInfo<DateTime> ticksInfo)
|
||||
{
|
||||
object info = ticksInfo.Info;
|
||||
var ticks = ticksInfo.Ticks;
|
||||
|
||||
if (info is DifferenceIn)
|
||||
{
|
||||
DifferenceIn diff = (DifferenceIn)info;
|
||||
DateFormat = GetDateFormat(diff);
|
||||
}
|
||||
|
||||
LabelTickInfo<DateTime> tickInfo = new LabelTickInfo<DateTime> { Info = info };
|
||||
|
||||
UIElement[] res = new UIElement[ticks.Length];
|
||||
for (int i = 0; i < ticks.Length; i++)
|
||||
{
|
||||
tickInfo.Tick = ticks[i];
|
||||
|
||||
string tickText = GetString(tickInfo);
|
||||
UIElement label = new TextBlock { Text = tickText, ToolTip = ticks[i] };
|
||||
ApplyCustomView(tickInfo, label);
|
||||
res[i] = label;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Charts/Axes/DateTime/DateTimeLabelProviderBase.cs
Normal file
59
Charts/Axes/DateTime/DateTimeLabelProviderBase.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public abstract class DateTimeLabelProviderBase : LabelProviderBase<DateTime>
|
||||
{
|
||||
private string dateFormat;
|
||||
protected string DateFormat
|
||||
{
|
||||
get { return dateFormat; }
|
||||
set { dateFormat = value; }
|
||||
}
|
||||
|
||||
protected override string GetStringCore(LabelTickInfo<DateTime> tickInfo)
|
||||
{
|
||||
return tickInfo.Tick.ToString(dateFormat);
|
||||
}
|
||||
|
||||
protected virtual string GetDateFormat(DifferenceIn diff)
|
||||
{
|
||||
string format = null;
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
format = "yyyy";
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
format = "MMM";
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
format = "%d";
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
format = "HH:mm";
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
format = "%m";
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
format = "ss";
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
format = "fff";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
}
|
||||
}
|
||||
307
Charts/Axes/DateTime/DateTimeTicksProvider.cs
Normal file
307
Charts/Axes/DateTime/DateTimeTicksProvider.cs
Normal file
@@ -0,0 +1,307 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a ticks provider for ticks of <see cref="T:System.DateTime"/> type.
|
||||
/// </summary>
|
||||
public class DateTimeTicksProvider : TimeTicksProviderBase<DateTime>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateTimeTicksProvider"/> class.
|
||||
/// </summary>
|
||||
public DateTimeTicksProvider() { }
|
||||
|
||||
static DateTimeTicksProvider()
|
||||
{
|
||||
Providers.Add(DifferenceIn.Year, new YearDateTimeProvider());
|
||||
Providers.Add(DifferenceIn.Month, new MonthDateTimeProvider());
|
||||
Providers.Add(DifferenceIn.Day, new DayDateTimeProvider());
|
||||
Providers.Add(DifferenceIn.Hour, new HourDateTimeProvider());
|
||||
Providers.Add(DifferenceIn.Minute, new MinuteDateTimeProvider());
|
||||
Providers.Add(DifferenceIn.Second, new SecondDateTimeProvider());
|
||||
Providers.Add(DifferenceIn.Millisecond, new MillisecondDateTimeProvider());
|
||||
|
||||
MinorProviders.Add(DifferenceIn.Year, new MinorDateTimeProvider(new YearDateTimeProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Month, new MinorDateTimeProvider(new MonthDateTimeProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Day, new MinorDateTimeProvider(new DayDateTimeProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Hour, new MinorDateTimeProvider(new HourDateTimeProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Minute, new MinorDateTimeProvider(new MinuteDateTimeProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Second, new MinorDateTimeProvider(new SecondDateTimeProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Millisecond, new MinorDateTimeProvider(new MillisecondDateTimeProvider()));
|
||||
}
|
||||
|
||||
protected sealed override TimeSpan GetDifference(DateTime start, DateTime end)
|
||||
{
|
||||
return end - start;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class DateTimeArrayExtensions
|
||||
{
|
||||
internal static int GetIndex(this DateTime[] array, DateTime value)
|
||||
{
|
||||
for (int i = 0; i < array.Length - 1; i++)
|
||||
{
|
||||
if (array[i] <= value && value < array[i + 1])
|
||||
return i;
|
||||
}
|
||||
|
||||
return array.Length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MinorDateTimeProvider : MinorTimeProviderBase<DateTime>
|
||||
{
|
||||
public MinorDateTimeProvider(ITicksProvider<DateTime> owner) : base(owner) { }
|
||||
|
||||
protected override bool IsInside(DateTime value, Range<DateTime> range)
|
||||
{
|
||||
return range.Min < value && value < range.Max;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class YearDateTimeProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Year;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 20, 10, 5, 4, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return dt.Year;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
int year = start.Year;
|
||||
int newYear = (year / step) * step;
|
||||
if (newYear == 0) newYear = 1;
|
||||
|
||||
return new DateTime(newYear, 1, 1);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return dt.Year == DateTime.MinValue.Year;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
if (dt.Year + step > DateTime.MaxValue.Year)
|
||||
return DateTime.MaxValue;
|
||||
|
||||
return dt.AddYears(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MonthDateTimeProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Month;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 12, 6, 4, 3, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return dt.Month + (dt.Year - start.Year) * 12;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return new DateTime(start.Year, 1, 1);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return dt.Month == DateTime.MinValue.Month;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddMonths(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DayDateTimeProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Day;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 30, 15, 10, 5, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (dt - start).Days;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date;
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return dt.Day == 1;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddDays(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HourDateTimeProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Hour;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 24, 12, 6, 4, 3, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (int)(dt - start).TotalHours;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date;
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddHours(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MinuteDateTimeProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Minute;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 60, 30, 20, 15, 10, 5, 4, 3, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (int)(dt - start).TotalMinutes;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date.AddHours(start.Hour);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddMinutes(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SecondDateTimeProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Second;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 60, 30, 20, 15, 10, 5, 4, 3, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (int)(dt - start).TotalSeconds;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date.AddHours(start.Hour).AddMinutes(start.Minute);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddSeconds(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MillisecondDateTimeProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Millisecond;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 100, 50, 40, 25, 20, 10, 5, 4, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (int)(dt - start).TotalMilliseconds;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date.AddHours(start.Hour).AddMinutes(start.Minute).AddSeconds(start.Second);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddMilliseconds(step);
|
||||
}
|
||||
}
|
||||
}
|
||||
134
Charts/Axes/DateTime/DateTimeTicksProviderBase.cs
Normal file
134
Charts/Axes/DateTime/DateTimeTicksProviderBase.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public abstract class DateTimeTicksProviderBase : ITicksProvider<DateTime>
|
||||
{
|
||||
public event EventHandler Changed;
|
||||
protected void RaiseChanged()
|
||||
{
|
||||
if (Changed != null)
|
||||
{
|
||||
Changed(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
protected static DateTime Shift(DateTime dateTime, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = dateTime;
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = res.AddYears(1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = res.AddMonths(1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = res.AddDays(1);
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = res.AddHours(1);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = res.AddMinutes(1);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = res.AddSeconds(1);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = res.AddMilliseconds(1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected static DateTime RoundDown(DateTime dateTime, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = dateTime;
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = new DateTime(dateTime.Year, 1, 1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = new DateTime(dateTime.Year, dateTime.Month, 1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = dateTime.Date;
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour).AddMinutes(dateTime.Minute);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour).AddMinutes(dateTime.Minute).AddSeconds(dateTime.Second);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour).AddMinutes(dateTime.Minute).AddSeconds(dateTime.Second).AddMilliseconds(dateTime.Millisecond);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
DebugVerify.Is(res <= dateTime);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected static DateTime RoundUp(DateTime dateTime, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = RoundDown(dateTime, diff);
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = res.AddYears(1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = res.AddMonths(1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = res.AddDays(1);
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = res.AddHours(1);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = res.AddMinutes(1);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = res.AddSeconds(1);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = res.AddMilliseconds(1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#region ITicksProvider<DateTime> Members
|
||||
|
||||
public abstract ITicksInfo<DateTime> GetTicks(Range<DateTime> range, int ticksCount);
|
||||
public abstract int DecreaseTickCount(int ticksCount);
|
||||
public abstract int IncreaseTickCount(int ticksCount);
|
||||
public abstract ITicksProvider<DateTime> MinorProvider { get; }
|
||||
public abstract ITicksProvider<DateTime> MajorProvider { get; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
39
Charts/Axes/DateTime/DateTimeToDoubleConversion.cs
Normal file
39
Charts/Axes/DateTime/DateTimeToDoubleConversion.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal sealed class DateTimeToDoubleConversion
|
||||
{
|
||||
public DateTimeToDoubleConversion(double min, DateTime minDate, double max, DateTime maxDate)
|
||||
{
|
||||
this.min = min;
|
||||
this.length = max - min;
|
||||
this.ticksMin = minDate.Ticks;
|
||||
this.ticksLength = maxDate.Ticks - ticksMin;
|
||||
}
|
||||
|
||||
private double min;
|
||||
private double length;
|
||||
private long ticksMin;
|
||||
private long ticksLength;
|
||||
|
||||
internal DateTime FromDouble(double d)
|
||||
{
|
||||
double ratio = (d - min) / length;
|
||||
long tick = (long)(ticksMin + ticksLength * ratio);
|
||||
|
||||
tick = MathHelper.Clamp(tick, DateTime.MinValue.Ticks, DateTime.MaxValue.Ticks);
|
||||
|
||||
return new DateTime(tick);
|
||||
}
|
||||
|
||||
internal double ToDouble(DateTime dt)
|
||||
{
|
||||
double ratio = (dt.Ticks - ticksMin) / (double)ticksLength;
|
||||
return min + ratio * length;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Charts/Axes/DateTime/DifferenceIn.cs
Normal file
22
Charts/Axes/DateTime/DifferenceIn.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public enum DifferenceIn
|
||||
{
|
||||
Biggest = Year,
|
||||
|
||||
Year = 6,
|
||||
Month = 5,
|
||||
Day = 4,
|
||||
Hour = 3,
|
||||
Minute = 2,
|
||||
Second = 1,
|
||||
Millisecond = 0,
|
||||
|
||||
Smallest = Millisecond
|
||||
}
|
||||
}
|
||||
28
Charts/Axes/DateTime/HorizontalDateTimeAxis.cs
Normal file
28
Charts/Axes/DateTime/HorizontalDateTimeAxis.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an axis with ticks of <see cref="System.DateTime"/> type, which can be placed only from bottom or top of <see cref="Plotter"/>.
|
||||
/// By default is placed from bottom.
|
||||
/// </summary>
|
||||
public class HorizontalDateTimeAxis : DateTimeAxis
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HorizontalDateTimeAxis"/> class.
|
||||
/// </summary>
|
||||
public HorizontalDateTimeAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Bottom;
|
||||
}
|
||||
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Left || newPlacement == AxisPlacement.Right)
|
||||
throw new ArgumentException(Strings.Exceptions.HorizontalAxisCannotBeVertical);
|
||||
}
|
||||
}
|
||||
}
|
||||
133
Charts/Axes/DateTime/MajorDateTimeLabelProvider.cs
Normal file
133
Charts/Axes/DateTime/MajorDateTimeLabelProvider.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a label provider for major ticks of <see cref="System.DateTime"/> type.
|
||||
/// </summary>
|
||||
public class MajorDateTimeLabelProvider : DateTimeLabelProviderBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MajorDateTimeLabelProvider"/> class.
|
||||
/// </summary>
|
||||
public MajorDateTimeLabelProvider() { }
|
||||
|
||||
public override UIElement[] CreateLabels(ITicksInfo<DateTime> ticksInfo)
|
||||
{
|
||||
object info = ticksInfo.Info;
|
||||
var ticks = ticksInfo.Ticks;
|
||||
UIElement[] res = new UIElement[ticks.Length - 1];
|
||||
int labelsNum = 3;
|
||||
|
||||
if (info is DifferenceIn)
|
||||
{
|
||||
DifferenceIn diff = (DifferenceIn)info;
|
||||
DateFormat = GetDateFormat(diff);
|
||||
}
|
||||
else if (info is MajorLabelsInfo)
|
||||
{
|
||||
MajorLabelsInfo mInfo = (MajorLabelsInfo)info;
|
||||
DifferenceIn diff = (DifferenceIn)mInfo.Info;
|
||||
DateFormat = GetDateFormat(diff);
|
||||
labelsNum = mInfo.MajorLabelsCount + 1;
|
||||
|
||||
//DebugVerify.Is(labelsNum < 100);
|
||||
}
|
||||
|
||||
DebugVerify.Is(ticks.Length < 10);
|
||||
|
||||
LabelTickInfo<DateTime> tickInfo = new LabelTickInfo<DateTime>();
|
||||
for (int i = 0; i < ticks.Length - 1; i++)
|
||||
{
|
||||
tickInfo.Info = info;
|
||||
tickInfo.Tick = ticks[i];
|
||||
|
||||
string tickText = GetString(tickInfo);
|
||||
|
||||
Grid grid = new Grid { };
|
||||
|
||||
// doing binding as described at http://sdolha.spaces.live.com/blog/cns!4121802308C5AB4E!3724.entry?wa=wsignin1.0&sa=835372863
|
||||
|
||||
grid.SetBinding(Grid.BackgroundProperty, new Binding { Path = new PropertyPath("(0)", DateTimeAxis.MajorLabelBackgroundBrushProperty), RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor) { AncestorType = typeof(AxisControlBase) } });
|
||||
Rectangle rect = new Rectangle
|
||||
{
|
||||
StrokeThickness = 2
|
||||
};
|
||||
rect.SetBinding(Rectangle.StrokeProperty, new Binding { Path = new PropertyPath("(0)", DateTimeAxis.MajorLabelRectangleBorderPropertyProperty), RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor) { AncestorType = typeof(AxisControlBase) } });
|
||||
|
||||
Grid.SetColumn(rect, 0);
|
||||
Grid.SetColumnSpan(rect, labelsNum);
|
||||
|
||||
for (int j = 0; j < labelsNum; j++)
|
||||
{
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition());
|
||||
}
|
||||
|
||||
grid.Children.Add(rect);
|
||||
|
||||
for (int j = 0; j < labelsNum; j++)
|
||||
{
|
||||
var tb = new TextBlock
|
||||
{
|
||||
Text = tickText,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Margin = new Thickness(0, 3, 0, 3)
|
||||
};
|
||||
Grid.SetColumn(tb, j);
|
||||
grid.Children.Add(tb);
|
||||
}
|
||||
|
||||
ApplyCustomView(tickInfo, grid);
|
||||
|
||||
res[i] = grid;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected override string GetDateFormat(DifferenceIn diff)
|
||||
{
|
||||
string format = null;
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
format = "yyyy";
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
format = "MMMM yyyy";
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
format = "%d MMMM yyyy";
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
format = "HH:mm %d MMMM yyyy";
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
format = "HH:mm %d MMMM yyyy";
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
format = "HH:mm:ss %d MMMM yyyy";
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
format = "fff";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
}
|
||||
}
|
||||
114
Charts/Axes/DateTime/MinorTimeProviderBase.cs
Normal file
114
Charts/Axes/DateTime/MinorTimeProviderBase.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal abstract class MinorTimeProviderBase<T> : ITicksProvider<T>
|
||||
{
|
||||
public event EventHandler Changed;
|
||||
protected void RaiseChanged()
|
||||
{
|
||||
if (Changed != null)
|
||||
{
|
||||
Changed(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ITicksProvider<T> provider;
|
||||
public MinorTimeProviderBase(ITicksProvider<T> provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
private T[] majorTicks = new T[] { };
|
||||
internal void SetTicks(T[] ticks)
|
||||
{
|
||||
this.majorTicks = ticks;
|
||||
}
|
||||
|
||||
private double ticksSize = 0.5;
|
||||
public ITicksInfo<T> GetTicks(Range<T> range, int ticksCount)
|
||||
{
|
||||
if (majorTicks.Length == 0)
|
||||
return new TicksInfo<T>();
|
||||
|
||||
ticksCount /= majorTicks.Length;
|
||||
if (ticksCount == 0)
|
||||
ticksCount = 2;
|
||||
|
||||
var ticks = majorTicks.GetPairs().Select(r => Clip(provider.GetTicks(r, ticksCount), r)).
|
||||
SelectMany(t => t.Ticks).ToArray();
|
||||
|
||||
var res = new TicksInfo<T>
|
||||
{
|
||||
Ticks = ticks,
|
||||
TickSizes = ArrayExtensions.CreateArray(ticks.Length, ticksSize)
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
private ITicksInfo<T> Clip(ITicksInfo<T> ticks, Range<T> range)
|
||||
{
|
||||
var newTicks = new List<T>(ticks.Ticks.Length);
|
||||
var newSizes = new List<double>(ticks.TickSizes.Length);
|
||||
|
||||
for (int i = 0; i < ticks.Ticks.Length; i++)
|
||||
{
|
||||
T tick = ticks.Ticks[i];
|
||||
if (IsInside(tick, range))
|
||||
{
|
||||
newTicks.Add(tick);
|
||||
newSizes.Add(ticks.TickSizes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return new TicksInfo<T>
|
||||
{
|
||||
Ticks = newTicks.ToArray(),
|
||||
TickSizes = newSizes.ToArray(),
|
||||
Info = ticks.Info
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract bool IsInside(T value, Range<T> range);
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
if (majorTicks.Length > 0)
|
||||
ticksCount /= majorTicks.Length;
|
||||
|
||||
int minorTicksCount = provider.DecreaseTickCount(ticksCount);
|
||||
|
||||
if (majorTicks.Length > 0)
|
||||
minorTicksCount *= majorTicks.Length;
|
||||
|
||||
return minorTicksCount;
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
if (majorTicks.Length > 0)
|
||||
ticksCount /= majorTicks.Length;
|
||||
|
||||
int minorTicksCount = provider.IncreaseTickCount(ticksCount);
|
||||
|
||||
if (majorTicks.Length > 0)
|
||||
minorTicksCount *= majorTicks.Length;
|
||||
|
||||
return minorTicksCount;
|
||||
}
|
||||
|
||||
public ITicksProvider<T> MinorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public ITicksProvider<T> MajorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class DefaultDateTimeTicksStrategy : IDateTimeTicksStrategy
|
||||
{
|
||||
public virtual DifferenceIn GetDifference(TimeSpan span)
|
||||
{
|
||||
span = span.Duration();
|
||||
|
||||
DifferenceIn diff;
|
||||
if (span.Days > 365)
|
||||
diff = DifferenceIn.Year;
|
||||
else if (span.Days > 30)
|
||||
diff = DifferenceIn.Month;
|
||||
else if (span.Days > 0)
|
||||
diff = DifferenceIn.Day;
|
||||
else if (span.Hours > 0)
|
||||
diff = DifferenceIn.Hour;
|
||||
else if (span.Minutes > 0)
|
||||
diff = DifferenceIn.Minute;
|
||||
else if (span.Seconds > 0)
|
||||
diff = DifferenceIn.Second;
|
||||
else
|
||||
diff = DifferenceIn.Millisecond;
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
public virtual bool TryGetLowerDiff(DifferenceIn diff, out DifferenceIn lowerDiff)
|
||||
{
|
||||
lowerDiff = diff;
|
||||
|
||||
int code = (int)diff;
|
||||
bool res = code > (int)DifferenceIn.Smallest;
|
||||
if (res)
|
||||
{
|
||||
lowerDiff = (DifferenceIn)(code - 1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public virtual bool TryGetBiggerDiff(DifferenceIn diff, out DifferenceIn biggerDiff)
|
||||
{
|
||||
biggerDiff = diff;
|
||||
|
||||
int code = (int)diff;
|
||||
bool res = code < (int)DifferenceIn.Biggest;
|
||||
if (res)
|
||||
{
|
||||
biggerDiff = (DifferenceIn)(code + 1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Charts/Axes/DateTime/Strategies/DelegateStrategy.cs
Normal file
30
Charts/Axes/DateTime/Strategies/DelegateStrategy.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.DateTime.Strategies
|
||||
{
|
||||
public class DelegateDateTimeStrategy : DefaultDateTimeTicksStrategy
|
||||
{
|
||||
private readonly Func<TimeSpan, DifferenceIn?> function;
|
||||
public DelegateDateTimeStrategy(Func<TimeSpan, DifferenceIn?> function)
|
||||
{
|
||||
if (function == null)
|
||||
throw new ArgumentNullException("function");
|
||||
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
public override DifferenceIn GetDifference(TimeSpan span)
|
||||
{
|
||||
DifferenceIn? customResult = function(span);
|
||||
|
||||
DifferenceIn result = customResult.HasValue ?
|
||||
customResult.Value :
|
||||
base.GetDifference(span);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Charts/Axes/DateTime/Strategies/ExtendedDaysStrategy.cs
Normal file
67
Charts/Axes/DateTime/Strategies/ExtendedDaysStrategy.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class ExtendedDaysStrategy : IDateTimeTicksStrategy
|
||||
{
|
||||
private static readonly DifferenceIn[] diffs = new DifferenceIn[] {
|
||||
DifferenceIn.Year,
|
||||
DifferenceIn.Day,
|
||||
DifferenceIn.Hour,
|
||||
DifferenceIn.Minute,
|
||||
DifferenceIn.Second,
|
||||
DifferenceIn.Millisecond
|
||||
};
|
||||
|
||||
public DifferenceIn GetDifference(TimeSpan span)
|
||||
{
|
||||
span = span.Duration();
|
||||
|
||||
DifferenceIn diff;
|
||||
if (span.Days > 365)
|
||||
diff = DifferenceIn.Year;
|
||||
else if (span.Days > 0)
|
||||
diff = DifferenceIn.Day;
|
||||
else if (span.Hours > 0)
|
||||
diff = DifferenceIn.Hour;
|
||||
else if (span.Minutes > 0)
|
||||
diff = DifferenceIn.Minute;
|
||||
else if (span.Seconds > 0)
|
||||
diff = DifferenceIn.Second;
|
||||
else
|
||||
diff = DifferenceIn.Millisecond;
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
public bool TryGetLowerDiff(DifferenceIn diff, out DifferenceIn lowerDiff)
|
||||
{
|
||||
lowerDiff = diff;
|
||||
|
||||
int index = Array.IndexOf(diffs, diff);
|
||||
if (index == -1)
|
||||
return false;
|
||||
|
||||
if (index == diffs.Length - 1)
|
||||
return false;
|
||||
|
||||
lowerDiff = diffs[index + 1];
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetBiggerDiff(DifferenceIn diff, out DifferenceIn biggerDiff)
|
||||
{
|
||||
biggerDiff = diff;
|
||||
|
||||
int index = Array.IndexOf(diffs, diff);
|
||||
if (index == -1 || index == 0)
|
||||
return false;
|
||||
|
||||
biggerDiff = diffs[index - 1];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Charts/Axes/DateTime/Strategies/IDateTimeTicksStrategy.cs
Normal file
14
Charts/Axes/DateTime/Strategies/IDateTimeTicksStrategy.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public interface IDateTimeTicksStrategy
|
||||
{
|
||||
DifferenceIn GetDifference(TimeSpan span);
|
||||
bool TryGetLowerDiff(DifferenceIn diff, out DifferenceIn lowerDiff);
|
||||
bool TryGetBiggerDiff(DifferenceIn diff, out DifferenceIn biggerDiff);
|
||||
}
|
||||
}
|
||||
269
Charts/Axes/DateTime/TimePeriodTicksProvider.cs
Normal file
269
Charts/Axes/DateTime/TimePeriodTicksProvider.cs
Normal file
@@ -0,0 +1,269 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal abstract class TimePeriodTicksProvider<T> : ITicksProvider<T>
|
||||
{
|
||||
public event EventHandler Changed;
|
||||
protected void RaiseChanged()
|
||||
{
|
||||
if (Changed != null)
|
||||
{
|
||||
Changed(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract T RoundUp(T time, DifferenceIn diff);
|
||||
protected abstract T RoundDown(T time, DifferenceIn diff);
|
||||
|
||||
private bool differenceInited = false;
|
||||
private DifferenceIn difference;
|
||||
protected DifferenceIn Difference
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!differenceInited)
|
||||
{
|
||||
difference = GetDifferenceCore();
|
||||
differenceInited = true;
|
||||
}
|
||||
return difference;
|
||||
}
|
||||
}
|
||||
protected abstract DifferenceIn GetDifferenceCore();
|
||||
|
||||
private int[] tickCounts = null;
|
||||
protected int[] TickCounts
|
||||
{
|
||||
get
|
||||
{
|
||||
if (tickCounts == null)
|
||||
tickCounts = GetTickCountsCore();
|
||||
return tickCounts;
|
||||
}
|
||||
}
|
||||
protected abstract int[] GetTickCountsCore();
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
if (ticksCount > TickCounts[0]) return TickCounts[0];
|
||||
|
||||
for (int i = 0; i < TickCounts.Length; i++)
|
||||
if (ticksCount > TickCounts[i])
|
||||
return TickCounts[i];
|
||||
|
||||
return TickCounts.Last();
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
if (ticksCount >= TickCounts[0]) return TickCounts[0];
|
||||
|
||||
for (int i = TickCounts.Length - 1; i >= 0; i--)
|
||||
if (ticksCount < TickCounts[i])
|
||||
return TickCounts[i];
|
||||
|
||||
return TickCounts.Last();
|
||||
}
|
||||
|
||||
protected abstract int GetSpecificValue(T start, T dt);
|
||||
protected abstract T GetStart(T start, int value, int step);
|
||||
protected abstract bool IsMinDate(T dt);
|
||||
protected abstract T AddStep(T dt, int step);
|
||||
|
||||
public ITicksInfo<T> GetTicks(Range<T> range, int ticksCount)
|
||||
{
|
||||
T start = range.Min;
|
||||
T end = range.Max;
|
||||
DifferenceIn diff = Difference;
|
||||
start = RoundDown(start, end);
|
||||
end = RoundUp(start, end);
|
||||
|
||||
RoundingInfo bounds = RoundingHelper.CreateRoundedRange(
|
||||
GetSpecificValue(start, start),
|
||||
GetSpecificValue(start, end));
|
||||
|
||||
int delta = (int)(bounds.Max - bounds.Min);
|
||||
if (delta == 0)
|
||||
return new TicksInfo<T> { Ticks = new T[] { start } };
|
||||
|
||||
int step = delta / ticksCount;
|
||||
|
||||
if (step == 0) step = 1;
|
||||
|
||||
T tick = GetStart(start, (int)bounds.Min, step);
|
||||
bool isMinDateTime = IsMinDate(tick) && step != 1;
|
||||
if (isMinDateTime)
|
||||
step--;
|
||||
|
||||
List<T> ticks = new List<T>();
|
||||
T finishTick = AddStep(range.Max, step);
|
||||
while (Continue(tick, finishTick))
|
||||
{
|
||||
ticks.Add(tick);
|
||||
tick = AddStep(tick, step);
|
||||
if (isMinDateTime)
|
||||
{
|
||||
isMinDateTime = false;
|
||||
step++;
|
||||
}
|
||||
}
|
||||
|
||||
ticks = Trim(ticks, range);
|
||||
|
||||
TicksInfo<T> res = new TicksInfo<T> { Ticks = ticks.ToArray(), Info = diff };
|
||||
return res;
|
||||
}
|
||||
|
||||
protected abstract bool Continue(T current, T end);
|
||||
|
||||
protected abstract T RoundUp(T start, T end);
|
||||
|
||||
protected abstract T RoundDown(T start, T end);
|
||||
|
||||
protected abstract List<T> Trim(List<T> ticks, Range<T> range);
|
||||
|
||||
public ITicksProvider<T> MinorProvider
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public ITicksProvider<T> MajorProvider
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class DatePeriodTicksProvider : TimePeriodTicksProvider<DateTime>
|
||||
{
|
||||
protected sealed override bool Continue(DateTime current, DateTime end)
|
||||
{
|
||||
return current < end;
|
||||
}
|
||||
|
||||
protected sealed override List<DateTime> Trim(List<DateTime> ticks, Range<DateTime> range)
|
||||
{
|
||||
int startIndex = 0;
|
||||
for (int i = 0; i < ticks.Count - 1; i++)
|
||||
{
|
||||
if (ticks[i] <= range.Min && range.Min <= ticks[i + 1])
|
||||
{
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int endIndex = ticks.Count - 1;
|
||||
for (int i = ticks.Count - 1; i >= 1; i--)
|
||||
{
|
||||
if (ticks[i] >= range.Max && range.Max > ticks[i - 1])
|
||||
{
|
||||
endIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<DateTime> res = new List<DateTime>(endIndex - startIndex + 1);
|
||||
for (int i = startIndex; i <= endIndex; i++)
|
||||
{
|
||||
res.Add(ticks[i]);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected sealed override DateTime RoundUp(DateTime start, DateTime end)
|
||||
{
|
||||
bool isPositive = (end - start).Ticks > 0;
|
||||
return isPositive ? SafelyRoundUp(end) : RoundDown(end, Difference);
|
||||
}
|
||||
|
||||
private DateTime SafelyRoundUp(DateTime dt)
|
||||
{
|
||||
if (AddStep(dt, 1) == DateTime.MaxValue)
|
||||
return DateTime.MaxValue;
|
||||
|
||||
return RoundUp(dt, Difference);
|
||||
}
|
||||
|
||||
protected sealed override DateTime RoundDown(DateTime start, DateTime end)
|
||||
{
|
||||
bool isPositive = (end - start).Ticks > 0;
|
||||
return isPositive ? RoundDown(start, Difference) : SafelyRoundUp(start);
|
||||
}
|
||||
|
||||
protected sealed override DateTime RoundDown(DateTime time, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = time;
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = new DateTime(time.Year, 1, 1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = new DateTime(time.Year, time.Month, 1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = time.Date;
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = time.Date.AddHours(time.Hour);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = time.Date.AddHours(time.Hour).AddMinutes(time.Minute);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = time.Date.AddHours(time.Hour).AddMinutes(time.Minute).AddSeconds(time.Second);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = time.Date.AddHours(time.Hour).AddMinutes(time.Minute).AddSeconds(time.Second).AddMilliseconds(time.Millisecond);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
DebugVerify.Is(res <= time);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected override DateTime RoundUp(DateTime dateTime, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = RoundDown(dateTime, diff);
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = res.AddYears(1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = res.AddMonths(1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = res.AddDays(1);
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = res.AddHours(1);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = res.AddMinutes(1);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = res.AddSeconds(1);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = res.AddMilliseconds(1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Charts/Axes/DateTime/VerticalDateTimeAxis.cs
Normal file
23
Charts/Axes/DateTime/VerticalDateTimeAxis.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.ViewportRestrictions;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class VerticalDateTimeAxis : DateTimeAxis
|
||||
{
|
||||
public VerticalDateTimeAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Left;
|
||||
Restriction = new DateTimeVerticalAxisRestriction();
|
||||
}
|
||||
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Bottom || newPlacement == AxisPlacement.Top)
|
||||
throw new ArgumentException(Strings.Exceptions.VerticalAxisCannotBeHorizontal);
|
||||
}
|
||||
}
|
||||
}
|
||||
495
Charts/Axes/DateTimeTicksProvider.cs
Normal file
495
Charts/Axes/DateTimeTicksProvider.cs
Normal file
@@ -0,0 +1,495 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.NewAxis
|
||||
{
|
||||
public class DateTimeTicksProvider : DateTimeTicksProviderBase
|
||||
{
|
||||
private static readonly Dictionary<DifferenceIn, ITicksProvider<DateTime>> providers =
|
||||
new Dictionary<DifferenceIn, ITicksProvider<DateTime>>();
|
||||
|
||||
static DateTimeTicksProvider()
|
||||
{
|
||||
providers.Add(DifferenceIn.Year, new YearProvider());
|
||||
providers.Add(DifferenceIn.Month, new MonthProvider());
|
||||
providers.Add(DifferenceIn.Day, new DayProvider());
|
||||
providers.Add(DifferenceIn.Hour, new HourProvider());
|
||||
providers.Add(DifferenceIn.Minute, new MinuteProvider());
|
||||
providers.Add(DifferenceIn.Second, new SecondProvider());
|
||||
}
|
||||
|
||||
private DifferenceIn diff;
|
||||
/// <summary>
|
||||
/// Gets the ticks.
|
||||
/// </summary>
|
||||
/// <param name="range">The range.</param>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns></returns>
|
||||
public override ITicksInfo<DateTime> GetTicks(Range<DateTime> range, int ticksCount)
|
||||
{
|
||||
Verify.Is(ticksCount > 0);
|
||||
|
||||
DateTime start = range.Min;
|
||||
DateTime end = range.Max;
|
||||
TimeSpan length = end - start;
|
||||
|
||||
diff = GetDifference(length);
|
||||
|
||||
TicksInfo<DateTime> res = new TicksInfo<DateTime> { Info = diff };
|
||||
if (providers.ContainsKey(diff))
|
||||
{
|
||||
ITicksInfo<DateTime> result = providers[diff].GetTicks(range, ticksCount);
|
||||
DateTime[] mayorTicks = result.Ticks;
|
||||
|
||||
res.Ticks = mayorTicks;
|
||||
|
||||
DifferenceIn lowerDiff = DifferenceIn.Year;
|
||||
// todo разобраться с minor ticks
|
||||
bool lowerDiffExists = TryGetLowerDiff(diff, out lowerDiff);
|
||||
if (lowerDiffExists && providers.ContainsKey(lowerDiff))
|
||||
{
|
||||
var minorTicks = result.Ticks.GetPairs().Select(r => ((IMinorTicksProvider<DateTime>)providers[lowerDiff]).CreateTicks(r)).
|
||||
SelectMany(m => m).ToArray();
|
||||
|
||||
res.MinorTicks = minorTicks;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
DateTime newStart = RoundDown(start, diff);
|
||||
DateTime newEnd = RoundUp(end, diff);
|
||||
|
||||
DebugVerify.Is(newStart <= start);
|
||||
|
||||
List<DateTime> resultTicks = new List<DateTime>();
|
||||
DateTime dt = newStart;
|
||||
do
|
||||
{
|
||||
resultTicks.Add(dt);
|
||||
dt = Shift(dt, diff);
|
||||
} while (dt <= newEnd);
|
||||
|
||||
while (resultTicks.Count > ticksCount)
|
||||
{
|
||||
var res2 = resultTicks;
|
||||
resultTicks = res2.Where((date, i) => i % 2 == 0).ToList();
|
||||
}
|
||||
|
||||
res.Ticks = resultTicks.ToArray();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries the get lower diff.
|
||||
/// </summary>
|
||||
/// <param name="diff">The diff.</param>
|
||||
/// <param name="lowerDiff">The lower diff.</param>
|
||||
/// <returns></returns>
|
||||
private static bool TryGetLowerDiff(DifferenceIn diff, out DifferenceIn lowerDiff)
|
||||
{
|
||||
lowerDiff = diff;
|
||||
|
||||
int code = (int)diff;
|
||||
bool res = code > 0;
|
||||
if (res)
|
||||
{
|
||||
lowerDiff = (DifferenceIn)(code - 1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decreases the tick count.
|
||||
/// </summary>
|
||||
/// <param name="tickCount">The tick count.</param>
|
||||
/// <returns></returns>
|
||||
public override int DecreaseTickCount(int tickCount)
|
||||
{
|
||||
if (providers.ContainsKey(diff))
|
||||
return providers[diff].DecreaseTickCount(tickCount);
|
||||
|
||||
int res = tickCount / 2;
|
||||
if (res < 2) res = 2;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases the tick count.
|
||||
/// </summary>
|
||||
/// <param name="tickCount">The tick count.</param>
|
||||
/// <returns></returns>
|
||||
public override int IncreaseTickCount(int tickCount)
|
||||
{
|
||||
DebugVerify.Is(tickCount < 2000);
|
||||
|
||||
if (providers.ContainsKey(diff))
|
||||
return providers[diff].IncreaseTickCount(tickCount);
|
||||
|
||||
return tickCount * 2;
|
||||
}
|
||||
}
|
||||
|
||||
public enum DifferenceIn
|
||||
{
|
||||
Year = 7,
|
||||
Month = 6,
|
||||
Day = 5,
|
||||
Hour = 4,
|
||||
Minute = 3,
|
||||
Second = 2,
|
||||
Millisecond = 1
|
||||
}
|
||||
|
||||
internal static class DateTimeArrayExt
|
||||
{
|
||||
[Obsolete("Works wrongly", true)]
|
||||
internal static DateTime[] Clip(this DateTime[] array, DateTime start, DateTime end)
|
||||
{
|
||||
if (start > end)
|
||||
{
|
||||
DateTime temp = start;
|
||||
start = end;
|
||||
end = temp;
|
||||
}
|
||||
|
||||
int startIndex = array.GetIndex(start);
|
||||
int endIndex = array.GetIndex(end) + 1;
|
||||
DateTime[] res = new DateTime[endIndex - startIndex];
|
||||
Array.Copy(array, startIndex, res, 0, res.Length);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
internal static int GetIndex(this DateTime[] array, DateTime value)
|
||||
{
|
||||
for (int i = 0; i < array.Length - 1; i++)
|
||||
{
|
||||
if (array[i] <= value && value < array[i + 1])
|
||||
return i;
|
||||
}
|
||||
|
||||
return array.Length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class DatePeriodTicksProvider : DateTimeTicksProviderBase, IMinorTicksProvider<DateTime>
|
||||
{
|
||||
protected DatePeriodTicksProvider()
|
||||
{
|
||||
tickCounts = GetTickCountsCore();
|
||||
difference = GetDifferenceCore();
|
||||
}
|
||||
|
||||
protected DifferenceIn difference;
|
||||
protected abstract DifferenceIn GetDifferenceCore();
|
||||
|
||||
protected abstract int[] GetTickCountsCore();
|
||||
protected int[] tickCounts = { };
|
||||
|
||||
public sealed override int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
if (ticksCount > tickCounts[0]) return tickCounts[0];
|
||||
|
||||
for (int i = 0; i < tickCounts.Length; i++)
|
||||
if (ticksCount > tickCounts[i])
|
||||
return tickCounts[i];
|
||||
|
||||
return tickCounts.Last();
|
||||
}
|
||||
|
||||
public sealed override int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
if (ticksCount >= tickCounts[0]) return tickCounts[0];
|
||||
|
||||
for (int i = tickCounts.Length - 1; i >= 0; i--)
|
||||
if (ticksCount < tickCounts[i])
|
||||
return tickCounts[i];
|
||||
|
||||
return tickCounts.Last();
|
||||
}
|
||||
|
||||
protected abstract int GetSpecificValue(DateTime start, DateTime dt);
|
||||
protected abstract DateTime GetStart(DateTime start, int value, int step);
|
||||
protected abstract bool IsMinDate(DateTime dt);
|
||||
protected abstract DateTime AddStep(DateTime dt, int step);
|
||||
|
||||
public sealed override ITicksInfo<DateTime> GetTicks(Range<DateTime> range, int ticksCount)
|
||||
{
|
||||
DateTime start = range.Min;
|
||||
DateTime end = range.Max;
|
||||
TimeSpan length = end - start;
|
||||
|
||||
bool isPositive = length.Ticks > 0;
|
||||
DifferenceIn diff = difference;
|
||||
|
||||
DateTime newStart = isPositive ? RoundDown(start, diff) : SafelyRoundUp(start);
|
||||
DateTime newEnd = isPositive ? SafelyRoundUp(end) : RoundDown(end, diff);
|
||||
|
||||
RoundingInfo bounds = RoundHelper.CreateRoundedRange(GetSpecificValue(newStart, newStart), GetSpecificValue(newStart, newEnd));
|
||||
|
||||
int delta = (int)(bounds.Max - bounds.Min);
|
||||
if (delta == 0)
|
||||
return new TicksInfo<DateTime> { Ticks = new DateTime[] { newStart } };
|
||||
|
||||
int step = delta / ticksCount;
|
||||
|
||||
if (step == 0) step = 1;
|
||||
|
||||
DateTime tick = GetStart(newStart, (int)bounds.Min, step);
|
||||
bool isMinDateTime = IsMinDate(tick) && step != 1;
|
||||
if (isMinDateTime)
|
||||
step--;
|
||||
|
||||
List<DateTime> ticks = new List<DateTime>();
|
||||
DateTime finishTick = AddStep(range.Max, step);
|
||||
while (tick < finishTick)
|
||||
{
|
||||
ticks.Add(tick);
|
||||
tick = AddStep(tick, step);
|
||||
if (isMinDateTime)
|
||||
{
|
||||
isMinDateTime = false;
|
||||
step++;
|
||||
}
|
||||
}
|
||||
|
||||
TicksInfo<DateTime> res = new TicksInfo<DateTime> { Ticks = ticks.ToArray(), Info = diff };
|
||||
return res;
|
||||
}
|
||||
|
||||
private DateTime SafelyRoundUp(DateTime dt)
|
||||
{
|
||||
if (AddStep(dt, 1) == DateTime.MaxValue)
|
||||
return DateTime.MaxValue;
|
||||
|
||||
return RoundUp(dt, difference);
|
||||
}
|
||||
|
||||
#region IMinorTicksProvider<DateTime> Members
|
||||
|
||||
public MinorTickInfo<DateTime>[] CreateTicks(Range<DateTime> range)
|
||||
{
|
||||
int tickCount = tickCounts[1];
|
||||
ITicksInfo<DateTime> ticks = GetTicks(range, tickCount);
|
||||
|
||||
MinorTickInfo<DateTime>[] res = ticks.Ticks.
|
||||
Select(dt => new MinorTickInfo<DateTime>(0.5, dt)).ToArray();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal class YearProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Year;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 20, 10, 5, 4, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return dt.Year;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
int year = start.Year;
|
||||
int newYear = (year / step) * step;
|
||||
if (newYear == 0) newYear = 1;
|
||||
|
||||
return new DateTime(newYear, 1, 1);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return dt.Year == DateTime.MinValue.Year;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
if (dt.Year + step > DateTime.MaxValue.Year)
|
||||
return DateTime.MaxValue;
|
||||
|
||||
return dt.AddYears(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MonthProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Month;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 12, 6, 4, 3, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return dt.Month + (dt.Year - start.Year) * 12;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return new DateTime(start.Year, 1, 1);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return dt.Month == DateTime.MinValue.Month;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddMonths(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal class DayProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Day;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 30, 15, 10, 5, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (dt - start).Days;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date;
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return dt.Day == 1;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddDays(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal class HourProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Hour;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 24, 12, 6, 4, 3, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (dt - start).Hours;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date;//.AddHours(start.Hour);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddHours(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MinuteProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Minute;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 60, 30, 20, 15, 10, 5, 4, 3, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (dt - start).Minutes;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date.AddHours(start.Hour);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddMinutes(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal class SecondProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Second;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 60, 30, 20, 15, 10, 5, 4, 3, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (dt - start).Seconds;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date.AddHours(start.Hour).AddMinutes(start.Minute);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddSeconds(step);
|
||||
}
|
||||
}
|
||||
}
|
||||
145
Charts/Axes/DateTimeTicksProviderBase.cs
Normal file
145
Charts/Axes/DateTimeTicksProviderBase.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.NewAxis
|
||||
{
|
||||
public abstract class DateTimeTicksProviderBase : ITicksProvider<DateTime>
|
||||
{
|
||||
public abstract ITicksInfo<DateTime> GetTicks(Range<DateTime> range, int ticksCount);
|
||||
|
||||
public abstract int DecreaseTickCount(int ticksCount);
|
||||
|
||||
public abstract int IncreaseTickCount(int ticksCount);
|
||||
|
||||
protected static DifferenceIn GetDifference(TimeSpan span)
|
||||
{
|
||||
// for negative time spans
|
||||
span = span.Duration();
|
||||
|
||||
DifferenceIn diff;
|
||||
if (span.Days > 365)
|
||||
diff = DifferenceIn.Year;
|
||||
else if (span.Days > 30)
|
||||
diff = DifferenceIn.Month;
|
||||
else if (span.Days > 0)
|
||||
diff = DifferenceIn.Day;
|
||||
else if (span.Hours > 0)
|
||||
diff = DifferenceIn.Hour;
|
||||
else if (span.Minutes > 0)
|
||||
diff = DifferenceIn.Minute;
|
||||
else if (span.Seconds > 0)
|
||||
diff = DifferenceIn.Second;
|
||||
else
|
||||
diff = DifferenceIn.Millisecond;
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
protected static DateTime Shift(DateTime dateTime, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = dateTime;
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = res.AddYears(1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = res.AddMonths(1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = res.AddDays(1);
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = res.AddHours(1);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = res.AddMinutes(1);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = res.AddSeconds(1);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = res.AddMilliseconds(1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected static DateTime RoundDown(DateTime dateTime, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = dateTime;
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = new DateTime(dateTime.Year, 1, 1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = new DateTime(dateTime.Year, dateTime.Month, 1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = dateTime.Date;
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour).AddMinutes(dateTime.Minute);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour).AddMinutes(dateTime.Minute).AddSeconds(dateTime.Second);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour).AddMinutes(dateTime.Minute).AddSeconds(dateTime.Second).AddMilliseconds(dateTime.Millisecond);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
DebugVerify.Is(res <= dateTime);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected static DateTime RoundUp(DateTime dateTime, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = RoundDown(dateTime, diff);
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = res.AddYears(1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = res.AddMonths(1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = res.AddDays(1);
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = res.AddHours(1);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = res.AddMinutes(1);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = res.AddSeconds(1);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = res.AddMilliseconds(1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
109
Charts/Axes/DefaultAxisConversions.cs
Normal file
109
Charts/Axes/DefaultAxisConversions.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains default axis value conversions.
|
||||
/// </summary>
|
||||
public static class DefaultAxisConversions
|
||||
{
|
||||
#region double
|
||||
|
||||
private static readonly Func<double, double> doubleToDouble = d => d;
|
||||
public static Func<double, double> DoubleToDouble
|
||||
{
|
||||
get { return DefaultAxisConversions.doubleToDouble; }
|
||||
}
|
||||
|
||||
private static readonly Func<double, double> doubleFromDouble = d => d;
|
||||
public static Func<double, double> DoubleFromDouble
|
||||
{
|
||||
get { return DefaultAxisConversions.doubleFromDouble; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DateTime
|
||||
|
||||
private static readonly long minDateTimeTicks = DateTime.MinValue.Ticks;
|
||||
private static readonly long maxDateTimeTicks = DateTime.MaxValue.Ticks;
|
||||
private static readonly Func<double, System.DateTime> dateTimeFromDouble = d =>
|
||||
{
|
||||
long ticks = (long)(d * 10000000000L);
|
||||
|
||||
// todo should we throw an exception if number of ticks is too big or small?
|
||||
if (ticks < minDateTimeTicks)
|
||||
ticks = minDateTimeTicks;
|
||||
else if (ticks > maxDateTimeTicks)
|
||||
ticks = maxDateTimeTicks;
|
||||
|
||||
return new DateTime(ticks);
|
||||
};
|
||||
public static Func<double, DateTime> DateTimeFromDouble
|
||||
{
|
||||
get { return dateTimeFromDouble; }
|
||||
}
|
||||
|
||||
private static readonly Func<DateTime, double> dateTimeToDouble = dt => dt.Ticks / 10000000000.0;
|
||||
public static Func<DateTime, double> DateTimeToDouble
|
||||
{
|
||||
get { return DefaultAxisConversions.dateTimeToDouble; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TimeSpan
|
||||
|
||||
private static readonly long minTimeSpanTicks = TimeSpan.MinValue.Ticks;
|
||||
private static readonly long maxTimeSpanTicks = TimeSpan.MaxValue.Ticks;
|
||||
|
||||
private static readonly Func<double, TimeSpan> timeSpanFromDouble = d =>
|
||||
{
|
||||
long ticks = (long)(d * 10000000000L);
|
||||
|
||||
// todo should we throw an exception if number of ticks is too big or small?
|
||||
if (ticks < minTimeSpanTicks)
|
||||
ticks = minTimeSpanTicks;
|
||||
else if (ticks > maxTimeSpanTicks)
|
||||
ticks = maxTimeSpanTicks;
|
||||
|
||||
return new TimeSpan(ticks);
|
||||
};
|
||||
|
||||
public static Func<double, TimeSpan> TimeSpanFromDouble
|
||||
{
|
||||
get { return DefaultAxisConversions.timeSpanFromDouble; }
|
||||
}
|
||||
|
||||
private static readonly Func<TimeSpan, double> timeSpanToDouble = timeSpan =>
|
||||
{
|
||||
return timeSpan.Ticks / 10000000000.0;
|
||||
};
|
||||
|
||||
public static Func<TimeSpan, double> TimeSpanToDouble
|
||||
{
|
||||
get { return DefaultAxisConversions.timeSpanToDouble; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region integer
|
||||
|
||||
private readonly static Func<double, int> intFromDouble = d => (int)d;
|
||||
public static Func<double, int> IntFromDouble
|
||||
{
|
||||
get { return DefaultAxisConversions.intFromDouble; }
|
||||
}
|
||||
|
||||
private readonly static Func<int, double> intToDouble = i => (double)i;
|
||||
public static Func<int, double> IntToDouble
|
||||
{
|
||||
get { return DefaultAxisConversions.intToDouble; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
99
Charts/Axes/DefaultNumericTicksProvider.cs
Normal file
99
Charts/Axes/DefaultNumericTicksProvider.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.NewAxis
|
||||
{
|
||||
public sealed class DefaultDoubleTicksProvider : ITicksProvider<double>
|
||||
{
|
||||
public double[] GetTicks(Range<double> range, int preferredTicksCount)
|
||||
{
|
||||
double start = range.Min;
|
||||
double finish = range.Max;
|
||||
|
||||
double delta = finish - start;
|
||||
|
||||
int log = (int)Math.Round(Math.Log10(delta));
|
||||
|
||||
double newStart = Round(start, log);
|
||||
double newFinish = Round(finish, log);
|
||||
if (newStart == newFinish)
|
||||
{
|
||||
log--;
|
||||
newStart = Round(start, log);
|
||||
newFinish = Round(finish, log);
|
||||
}
|
||||
|
||||
double step = (newFinish - newStart) / preferredTicksCount;
|
||||
|
||||
//double[] ticks = CreateTicks(newStart, newFinish, preferredTicksCount);
|
||||
double[] ticks = CreateTicks(start, finish, step);
|
||||
return ticks;
|
||||
}
|
||||
|
||||
protected static double[] CreateTicks(double start, double finish, double step)
|
||||
{
|
||||
double x = step * (Math.Floor(start / step) + 1);
|
||||
List<double> res = new List<double>();
|
||||
while (x <= finish)
|
||||
{
|
||||
res.Add(x);
|
||||
x += step;
|
||||
}
|
||||
return res.ToArray();
|
||||
}
|
||||
|
||||
//private static double[] CreateTicks(double start, double finish, int tickCount)
|
||||
//{
|
||||
// double[] ticks = new double[tickCount];
|
||||
// if (tickCount == 0)
|
||||
// return ticks;
|
||||
|
||||
// DebugVerify.Is(tickCount > 0);
|
||||
|
||||
// double delta = (finish - start) / (tickCount - 1);
|
||||
|
||||
// for (int i = 0; i < tickCount; i++)
|
||||
// {
|
||||
// ticks[i] = start + i * delta;
|
||||
// }
|
||||
|
||||
// return ticks;
|
||||
//}
|
||||
|
||||
private static double Round(double number, int rem)
|
||||
{
|
||||
if (rem <= 0)
|
||||
{
|
||||
return Math.Round(number, -rem);
|
||||
}
|
||||
else
|
||||
{
|
||||
double pow = Math.Pow(10, rem - 1);
|
||||
double val = pow * Math.Round(number / Math.Pow(10, rem - 1));
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<int> TickCount =
|
||||
new ReadOnlyCollection<int>(new int[] { 20, 10, 5, 4, 2, 1 });
|
||||
|
||||
public const int DefaultPreferredTicksCount = 10;
|
||||
|
||||
public int DecreaseTickCount(int tickCount)
|
||||
{
|
||||
return TickCount.FirstOrDefault(tick => tick < tickCount);
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int tickCount) {
|
||||
int newTickCount = TickCount.Reverse().FirstOrDefault(tick => tick > tickCount);
|
||||
if (newTickCount == 0)
|
||||
newTickCount = TickCount[0];
|
||||
|
||||
return newTickCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Charts/Axes/DefaultTicksProvider.cs
Normal file
17
Charts/Axes/DefaultTicksProvider.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal static class DefaultTicksProvider
|
||||
{
|
||||
internal static readonly int DefaultTicksCount = 10;
|
||||
|
||||
internal static ITicksInfo<T> GetTicks<T>(this ITicksProvider<T> provider, Range<T> range)
|
||||
{
|
||||
return provider.GetTicks(range, DefaultTicksCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
142
Charts/Axes/GeneralAxis.cs
Normal file
142
Charts/Axes/GeneralAxis.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Controls;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a base class for all DynamicDataDisplay's axes.
|
||||
/// Has several axis-specific and all WPF-specific properties.
|
||||
/// </summary>
|
||||
public abstract class GeneralAxis : ContentControl, IPlotterElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GeneralAxis"/> class.
|
||||
/// </summary>
|
||||
protected GeneralAxis() { }
|
||||
|
||||
#region Placement property
|
||||
|
||||
private AxisPlacement placement = AxisPlacement.Bottom;
|
||||
/// <summary>
|
||||
/// Gets or sets the placement of axis - place in ChartPlotter where it should be placed.
|
||||
/// </summary>
|
||||
/// <value>The placement.</value>
|
||||
public AxisPlacement Placement
|
||||
{
|
||||
get { return placement; }
|
||||
set
|
||||
{
|
||||
if (placement != value)
|
||||
{
|
||||
ValidatePlacement(value);
|
||||
AxisPlacement oldPlacement = placement;
|
||||
placement = value;
|
||||
OnPlacementChanged(oldPlacement, placement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnPlacementChanged(AxisPlacement oldPlacement, AxisPlacement newPlacement) { }
|
||||
|
||||
protected Panel GetPanelByPlacement(AxisPlacement placement)
|
||||
{
|
||||
Panel panel = null;
|
||||
switch (placement)
|
||||
{
|
||||
case AxisPlacement.Left:
|
||||
panel = ParentPlotter.LeftPanel;
|
||||
break;
|
||||
case AxisPlacement.Right:
|
||||
panel = ParentPlotter.RightPanel;
|
||||
break;
|
||||
case AxisPlacement.Top:
|
||||
panel = ParentPlotter.TopPanel;
|
||||
break;
|
||||
case AxisPlacement.Bottom:
|
||||
panel = ParentPlotter.BottomPanel;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return panel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the placement - e.g., vertical axis should not be placed from top or bottom, etc.
|
||||
/// If proposed placement is wrong, throws an ArgumentException.
|
||||
/// </summary>
|
||||
/// <param name="newPlacement">The new placement.</param>
|
||||
protected virtual void ValidatePlacement(AxisPlacement newPlacement) { }
|
||||
|
||||
#endregion
|
||||
|
||||
protected void RaiseTicksChanged()
|
||||
{
|
||||
TicksChanged.Raise(this);
|
||||
}
|
||||
|
||||
public abstract void ForceUpdate();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when ticks changes.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public event EventHandler TicksChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the screen coordinates of axis ticks.
|
||||
/// </summary>
|
||||
/// <value>The screen ticks.</value>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract double[] ScreenTicks { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the screen coordinates of minor ticks.
|
||||
/// </summary>
|
||||
/// <value>The minor screen ticks.</value>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract MinorTickInfo<double>[] MinorScreenTicks { get; }
|
||||
|
||||
#region IPlotterElement Members
|
||||
|
||||
private Plotter2D plotter;
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public Plotter2D ParentPlotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
void IPlotterElement.OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
this.plotter = (Plotter2D)plotter;
|
||||
OnPlotterAttached(this.plotter);
|
||||
}
|
||||
|
||||
protected virtual void OnPlotterAttached(Plotter2D plotter) { }
|
||||
|
||||
void IPlotterElement.OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
OnPlotterDetaching(this.plotter);
|
||||
this.plotter = null;
|
||||
}
|
||||
|
||||
protected virtual void OnPlotterDetaching(Plotter2D plotter) { }
|
||||
|
||||
public Plotter2D Plotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
Plotter IPlotterElement.Plotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
55
Charts/Axes/GenericLabelProvider.cs
Normal file
55
Charts/Axes/GenericLabelProvider.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents default implementation of label provider for specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Axis values type.</typeparam>
|
||||
public class GenericLabelProvider<T> : LabelProviderBase<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GenericLabelProvider<T>"/> class.
|
||||
/// </summary>
|
||||
public GenericLabelProvider() { }
|
||||
|
||||
#region ILabelProvider<T> Members
|
||||
|
||||
/// <summary>
|
||||
/// Creates the labels by given ticks info.
|
||||
/// </summary>
|
||||
/// <param name="ticksInfo">The ticks info.</param>
|
||||
/// <returns>
|
||||
/// Array of <see cref="UIElement"/>s, which are axis labels for specified axis ticks.
|
||||
/// </returns>
|
||||
public override UIElement[] CreateLabels(ITicksInfo<T> ticksInfo)
|
||||
{
|
||||
var ticks = ticksInfo.Ticks;
|
||||
var info = ticksInfo.Info;
|
||||
|
||||
LabelTickInfo<T> tickInfo = new LabelTickInfo<T>();
|
||||
UIElement[] res = new UIElement[ticks.Length];
|
||||
for (int i = 0; i < res.Length; i++)
|
||||
{
|
||||
tickInfo.Tick = ticks[i];
|
||||
tickInfo.Info = info;
|
||||
|
||||
string text = GetString(tickInfo);
|
||||
|
||||
res[i] = new TextBlock
|
||||
{
|
||||
Text = text,
|
||||
ToolTip = ticks[i].ToString()
|
||||
};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.GenericLocational
|
||||
{
|
||||
public class GenericLocationalLabelProvider<TItem, TAxis> : LabelProviderBase<TAxis>
|
||||
{
|
||||
private readonly IList<TItem> collection;
|
||||
private readonly Func<TItem, string> displayMemberMapping;
|
||||
|
||||
public GenericLocationalLabelProvider(IList<TItem> collection, Func<TItem, string> displayMemberMapping)
|
||||
{
|
||||
if (collection == null)
|
||||
throw new ArgumentNullException("collection");
|
||||
if (displayMemberMapping == null)
|
||||
throw new ArgumentNullException("displayMemberMapping");
|
||||
|
||||
this.collection = collection;
|
||||
this.displayMemberMapping = displayMemberMapping;
|
||||
}
|
||||
|
||||
int startIndex;
|
||||
public override UIElement[] CreateLabels(ITicksInfo<TAxis> ticksInfo)
|
||||
{
|
||||
var ticks = ticksInfo.Ticks;
|
||||
|
||||
if (ticks.Length == 0)
|
||||
return EmptyLabelsArray;
|
||||
|
||||
startIndex = (int)ticksInfo.Info;
|
||||
|
||||
UIElement[] result = new UIElement[ticks.Length];
|
||||
|
||||
LabelTickInfo<TAxis> labelInfo = new LabelTickInfo<TAxis> { Info = ticksInfo.Info };
|
||||
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
var tick = ticks[i];
|
||||
labelInfo.Tick = tick;
|
||||
labelInfo.Index = i;
|
||||
|
||||
string labelText = GetString(labelInfo);
|
||||
|
||||
TextBlock label = new TextBlock { Text = labelText };
|
||||
|
||||
ApplyCustomView(labelInfo, label);
|
||||
|
||||
result[i] = label;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override string GetStringCore(LabelTickInfo<TAxis> tickInfo)
|
||||
{
|
||||
return displayMemberMapping(collection[tickInfo.Index + startIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
146
Charts/Axes/GenericLocational/GenericLocationalTicksProvider.cs
Normal file
146
Charts/Axes/GenericLocational/GenericLocationalTicksProvider.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.DataSearch;
|
||||
using System.Windows;
|
||||
using System.Collections;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.GenericLocational
|
||||
{
|
||||
public class GenericLocationalTicksProvider<TCollection, TAxis> : ITicksProvider<TAxis> where TAxis : IComparable<TAxis>
|
||||
{
|
||||
private IList<TCollection> collection;
|
||||
public IList<TCollection> Collection
|
||||
{
|
||||
get { return collection; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
Changed.Raise(this);
|
||||
collection = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Func<TCollection, TAxis> axisMapping;
|
||||
public Func<TCollection, TAxis> AxisMapping
|
||||
{
|
||||
get { return axisMapping; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
Changed.Raise(this);
|
||||
axisMapping = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GenericLocationalTicksProvider<T>"/> class.
|
||||
/// </summary>
|
||||
public GenericLocationalTicksProvider() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GenericLocationalTicksProvider<T>"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collection">The collection of axis ticks and labels.</param>
|
||||
public GenericLocationalTicksProvider(IList<TCollection> collection)
|
||||
{
|
||||
Collection = collection;
|
||||
}
|
||||
|
||||
public GenericLocationalTicksProvider(IList<TCollection> collection, Func<TCollection, TAxis> coordinateMapping)
|
||||
{
|
||||
Collection = collection;
|
||||
AxisMapping = coordinateMapping;
|
||||
}
|
||||
|
||||
#region ITicksProvider<T> Members
|
||||
|
||||
SearchResult1d minResult = SearchResult1d.Empty;
|
||||
SearchResult1d maxResult = SearchResult1d.Empty;
|
||||
GenericSearcher1d<TCollection, TAxis> searcher;
|
||||
/// <summary>
|
||||
/// Generates ticks for given range and preferred ticks count.
|
||||
/// </summary>
|
||||
/// <param name="range">The range.</param>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns></returns>
|
||||
public ITicksInfo<TAxis> GetTicks(Range<TAxis> range, int ticksCount)
|
||||
{
|
||||
EnsureSearcher();
|
||||
|
||||
//minResult = searcher.SearchBetween(range.Min, minResult);
|
||||
//maxResult = searcher.SearchBetween(range.Max, maxResult);
|
||||
|
||||
minResult = searcher.SearchFirstLess(range.Min);
|
||||
maxResult = searcher.SearchGreater(range.Max);
|
||||
|
||||
if (!(minResult.IsEmpty && maxResult.IsEmpty))
|
||||
{
|
||||
int startIndex = !minResult.IsEmpty ? minResult.Index : 0;
|
||||
int endIndex = !maxResult.IsEmpty ? maxResult.Index : collection.Count - 1;
|
||||
|
||||
int count = endIndex - startIndex + 1;
|
||||
|
||||
TAxis[] ticks = new TAxis[count];
|
||||
for (int i = startIndex; i <= endIndex; i++)
|
||||
{
|
||||
ticks[i - startIndex] = axisMapping(collection[i]);
|
||||
}
|
||||
|
||||
TicksInfo<TAxis> result = new TicksInfo<TAxis>
|
||||
{
|
||||
Info = startIndex,
|
||||
TickSizes = ArrayExtensions.CreateArray(count, 1.0),
|
||||
Ticks = ticks
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TicksInfo<TAxis>.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureSearcher()
|
||||
{
|
||||
if (searcher == null)
|
||||
{
|
||||
if (collection == null || axisMapping == null)
|
||||
throw new InvalidOperationException(Strings.Exceptions.GenericLocationalProviderInvalidState);
|
||||
|
||||
searcher = new GenericSearcher1d<TCollection, TAxis>(collection, axisMapping);
|
||||
}
|
||||
}
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
return collection.Count;
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
return collection.Count;
|
||||
}
|
||||
|
||||
public ITicksProvider<TAxis> MinorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public ITicksProvider<TAxis> MajorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public event EventHandler Changed;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
153
Charts/Axes/ITicksProvider.cs
Normal file
153
Charts/Axes/ITicksProvider.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about one minor tick - its value (relative size) and its tick.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
[DebuggerDisplay("{Value} @ {Tick}")]
|
||||
public struct MinorTickInfo<T>
|
||||
{
|
||||
internal MinorTickInfo(double value, T tick)
|
||||
{
|
||||
this.value = value;
|
||||
this.tick = tick;
|
||||
}
|
||||
|
||||
private readonly double value;
|
||||
private readonly T tick;
|
||||
|
||||
public double Value { get { return value; } }
|
||||
public T Tick { get { return tick; } }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format("{0} @ {1}", value, tick);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains data for all generated ticks.
|
||||
/// Used by TicksLabelProvider.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of axis tick.</typeparam>
|
||||
public interface ITicksInfo<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the array of axis ticks.
|
||||
/// </summary>
|
||||
/// <value>The ticks.</value>
|
||||
T[] Ticks { get; }
|
||||
/// <summary>
|
||||
/// Gets the tick sizes.
|
||||
/// </summary>
|
||||
/// <value>The tick sizes.</value>
|
||||
double[] TickSizes { get; }
|
||||
/// <summary>
|
||||
/// Gets the additional information, added to ticks info and specifying range's features.
|
||||
/// </summary>
|
||||
/// <value>The info.</value>
|
||||
object Info { get; }
|
||||
}
|
||||
|
||||
internal class TicksInfo<T> : ITicksInfo<T>
|
||||
{
|
||||
private T[] ticks = { };
|
||||
/// <summary>
|
||||
/// Gets the array of axis ticks.
|
||||
/// </summary>
|
||||
/// <value>The ticks.</value>
|
||||
public T[] Ticks
|
||||
{
|
||||
get { return ticks; }
|
||||
internal set { ticks = value; }
|
||||
}
|
||||
|
||||
private double[] tickSizes = { };
|
||||
/// <summary>
|
||||
/// Gets the tick sizes.
|
||||
/// </summary>
|
||||
/// <value>The tick sizes.</value>
|
||||
public double[] TickSizes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (tickSizes.Length != ticks.Length)
|
||||
tickSizes = ArrayExtensions.CreateArray(ticks.Length, 1.0);
|
||||
|
||||
return tickSizes;
|
||||
}
|
||||
internal set { tickSizes = value; }
|
||||
}
|
||||
|
||||
private object info = null;
|
||||
/// <summary>
|
||||
/// Gets the additional information, added to ticks info and specifying range's features.
|
||||
/// </summary>
|
||||
/// <value>The info.</value>
|
||||
public object Info
|
||||
{
|
||||
get { return info; }
|
||||
internal set { info = value; }
|
||||
}
|
||||
|
||||
private static readonly TicksInfo<T> empty = new TicksInfo<T> { info = null, ticks = new T[0], tickSizes = new double[0] };
|
||||
internal static TicksInfo<T> Empty
|
||||
{
|
||||
get { return empty; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base interface for ticks generator.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public interface ITicksProvider<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates ticks for given range and preferred ticks count.
|
||||
/// </summary>
|
||||
/// <param name="range">The range.</param>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns></returns>
|
||||
ITicksInfo<T> GetTicks(Range<T> range, int ticksCount);
|
||||
/// <summary>
|
||||
/// Decreases the tick count.
|
||||
/// Returned value should be later passed as ticksCount parameter to GetTicks method.
|
||||
/// </summary>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns>Decreased ticks count.</returns>
|
||||
int DecreaseTickCount(int ticksCount);
|
||||
/// <summary>
|
||||
/// Increases the tick count.
|
||||
/// Returned value should be later passed as ticksCount parameter to GetTicks method.
|
||||
/// </summary>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns>Increased ticks count.</returns>
|
||||
int IncreaseTickCount(int ticksCount);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minor ticks provider, used to generate ticks between each two adjacent ticks.
|
||||
/// </summary>
|
||||
/// <value>The minor provider. If there is no minor provider available, returns null.</value>
|
||||
ITicksProvider<T> MinorProvider { get; }
|
||||
/// <summary>
|
||||
/// Gets the major provider, used to generate major ticks - for example, years for common ticks as months.
|
||||
/// </summary>
|
||||
/// <value>The major provider. If there is no major provider available, returns null.</value>
|
||||
ITicksProvider<T> MajorProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when properties of ticks provider changeds.
|
||||
/// Notifies axis to rebuild its view.
|
||||
/// </summary>
|
||||
event EventHandler Changed;
|
||||
}
|
||||
}
|
||||
40
Charts/Axes/ITypedAxis.cs
Normal file
40
Charts/Axes/ITypedAxis.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes axis as having ticks type.
|
||||
/// Provides access to some typed properties.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Axis tick's type.</typeparam>
|
||||
public interface ITypedAxis<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the ticks provider.
|
||||
/// </summary>
|
||||
/// <value>The ticks provider.</value>
|
||||
ITicksProvider<T> TicksProvider { get; }
|
||||
/// <summary>
|
||||
/// Gets the label provider.
|
||||
/// </summary>
|
||||
/// <value>The label provider.</value>
|
||||
LabelProviderBase<T> LabelProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the convertion of tick from double.
|
||||
/// Should not be null.
|
||||
/// </summary>
|
||||
/// <value>The convert from double.</value>
|
||||
Func<double, T> ConvertFromDouble { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the convertion of tick to double.
|
||||
/// Should not be null.
|
||||
/// </summary>
|
||||
/// <value>The convert to double.</value>
|
||||
Func<T, double> ConvertToDouble { get; set; }
|
||||
}
|
||||
}
|
||||
13
Charts/Axes/IValueConversion.cs
Normal file
13
Charts/Axes/IValueConversion.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public interface IValueConversion<T>
|
||||
{
|
||||
Func<T, double> ConvertToDouble { get; set; }
|
||||
Func<double, T> ConvertFromDouble { get; set; }
|
||||
}
|
||||
}
|
||||
107
Charts/Axes/Integer/CollectionLabelProvider.cs
Normal file
107
Charts/Axes/Integer/CollectionLabelProvider.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
public class CollectionLabelProvider<T> : LabelProviderBase<int>
|
||||
{
|
||||
private IList<T> collection;
|
||||
|
||||
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
|
||||
public IList<T> Collection
|
||||
{
|
||||
get { return collection; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
if (collection != value)
|
||||
{
|
||||
DetachCollection();
|
||||
|
||||
collection = value;
|
||||
|
||||
AttachCollection();
|
||||
|
||||
RaiseChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Collection changed
|
||||
|
||||
private void AttachCollection()
|
||||
{
|
||||
INotifyCollectionChanged observableCollection = collection as INotifyCollectionChanged;
|
||||
if (observableCollection != null)
|
||||
{
|
||||
observableCollection.CollectionChanged += OnCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
RaiseChanged();
|
||||
}
|
||||
|
||||
private void DetachCollection()
|
||||
{
|
||||
INotifyCollectionChanged observableCollection = collection as INotifyCollectionChanged;
|
||||
if (observableCollection != null)
|
||||
{
|
||||
observableCollection.CollectionChanged -= OnCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CollectionLabelProvider<T>"/> class with empty labels collection.
|
||||
/// </summary>
|
||||
public CollectionLabelProvider() { }
|
||||
|
||||
public CollectionLabelProvider(IList<T> collection)
|
||||
: this()
|
||||
{
|
||||
Collection = collection;
|
||||
}
|
||||
|
||||
public override UIElement[] CreateLabels(ITicksInfo<int> ticksInfo)
|
||||
{
|
||||
var ticks = ticksInfo.Ticks;
|
||||
|
||||
UIElement[] res = new UIElement[ticks.Length];
|
||||
|
||||
var tickInfo = new LabelTickInfo<int> { Info = ticksInfo.Info };
|
||||
|
||||
for (int i = 0; i < res.Length; i++)
|
||||
{
|
||||
int tick = ticks[i];
|
||||
tickInfo.Tick = tick;
|
||||
|
||||
if (0 <= tick && tick < collection.Count)
|
||||
{
|
||||
string text = collection[tick].ToString();
|
||||
res[i] = new TextBlock
|
||||
{
|
||||
Text = text,
|
||||
ToolTip = text
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
res[i] = null;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Charts/Axes/Integer/HorizontalIntegerAxis.cs
Normal file
21
Charts/Axes/Integer/HorizontalIntegerAxis.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
public class HorizontalIntegerAxis : IntegerAxis
|
||||
{
|
||||
public HorizontalIntegerAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Bottom;
|
||||
}
|
||||
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Left || newPlacement == AxisPlacement.Right)
|
||||
throw new ArgumentException(Strings.Exceptions.HorizontalAxisCannotBeVertical);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Charts/Axes/Integer/IntegerAxis.cs
Normal file
18
Charts/Axes/Integer/IntegerAxis.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
public class IntegerAxis : AxisBase<int>
|
||||
{
|
||||
public IntegerAxis()
|
||||
: base(new IntegerAxisControl(),
|
||||
d => (int)d,
|
||||
i => (double)i)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Charts/Axes/Integer/IntegerAxisControl.cs
Normal file
18
Charts/Axes/Integer/IntegerAxisControl.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
public class IntegerAxisControl : AxisControl<int>
|
||||
{
|
||||
public IntegerAxisControl()
|
||||
{
|
||||
LabelProvider = new GenericLabelProvider<int>();
|
||||
TicksProvider = new IntegerTicksProvider();
|
||||
ConvertToDouble = i => (double)i;
|
||||
Range = new Range<int>(0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
189
Charts/Axes/Integer/IntegerTicksProvider.cs
Normal file
189
Charts/Axes/Integer/IntegerTicksProvider.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a ticks provider for intefer values.
|
||||
/// </summary>
|
||||
public class IntegerTicksProvider : ITicksProvider<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IntegerTicksProvider"/> class.
|
||||
/// </summary>
|
||||
public IntegerTicksProvider() { }
|
||||
|
||||
private int minStep = 0;
|
||||
/// <summary>
|
||||
/// Gets or sets the minimal step between ticks.
|
||||
/// </summary>
|
||||
/// <value>The min step.</value>
|
||||
public int MinStep
|
||||
{
|
||||
get { return minStep; }
|
||||
set
|
||||
{
|
||||
Verify.IsTrue(value >= 0, "value");
|
||||
if (minStep != value)
|
||||
{
|
||||
minStep = value;
|
||||
RaiseChangedEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int maxStep = Int32.MaxValue;
|
||||
/// <summary>
|
||||
/// Gets or sets the maximal step between ticks.
|
||||
/// </summary>
|
||||
/// <value>The max step.</value>
|
||||
public int MaxStep
|
||||
{
|
||||
get { return maxStep; }
|
||||
set
|
||||
{
|
||||
if (maxStep != value)
|
||||
{
|
||||
if (value < 0)
|
||||
throw new ArgumentOutOfRangeException("value", Strings.Exceptions.ParameterShouldBePositive);
|
||||
|
||||
maxStep = value;
|
||||
RaiseChangedEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region ITicksProvider<int> Members
|
||||
|
||||
/// <summary>
|
||||
/// Generates ticks for given range and preferred ticks count.
|
||||
/// </summary>
|
||||
/// <param name="range">The range.</param>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns></returns>
|
||||
public ITicksInfo<int> GetTicks(Range<int> range, int ticksCount)
|
||||
{
|
||||
double start = range.Min;
|
||||
double finish = range.Max;
|
||||
|
||||
double delta = finish - start;
|
||||
|
||||
int log = (int)Math.Round(Math.Log10(delta));
|
||||
|
||||
double newStart = RoundingHelper.Round(start, log);
|
||||
double newFinish = RoundingHelper.Round(finish, log);
|
||||
if (newStart == newFinish)
|
||||
{
|
||||
log--;
|
||||
newStart = RoundingHelper.Round(start, log);
|
||||
newFinish = RoundingHelper.Round(finish, log);
|
||||
}
|
||||
|
||||
// calculating step between ticks
|
||||
double unroundedStep = (newFinish - newStart) / ticksCount;
|
||||
int stepLog = log;
|
||||
// trying to round step
|
||||
int step = (int)RoundingHelper.Round(unroundedStep, stepLog);
|
||||
if (step == 0)
|
||||
{
|
||||
stepLog--;
|
||||
step = (int)RoundingHelper.Round(unroundedStep, stepLog);
|
||||
if (step == 0)
|
||||
{
|
||||
// step will not be rounded if attempts to be rounded to zero.
|
||||
step = (int)unroundedStep;
|
||||
}
|
||||
}
|
||||
|
||||
if (step < minStep)
|
||||
step = minStep;
|
||||
if (step > maxStep)
|
||||
step = maxStep;
|
||||
|
||||
if (step <= 0)
|
||||
step = 1;
|
||||
|
||||
int[] ticks = CreateTicks(start, finish, step);
|
||||
|
||||
TicksInfo<int> res = new TicksInfo<int> { Info = log, Ticks = ticks };
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static int[] CreateTicks(double start, double finish, int step)
|
||||
{
|
||||
DebugVerify.Is(step != 0);
|
||||
|
||||
int x = (int)(step * Math.Floor(start / (double)step));
|
||||
List<int> res = new List<int>();
|
||||
|
||||
checked
|
||||
{
|
||||
double increasedFinish = finish + step * 1.05;
|
||||
while (x <= increasedFinish)
|
||||
{
|
||||
res.Add(x);
|
||||
x += step;
|
||||
}
|
||||
}
|
||||
return res.ToArray();
|
||||
}
|
||||
|
||||
private static int[] tickCounts = new int[] { 20, 10, 5, 4, 2, 1 };
|
||||
|
||||
/// <summary>
|
||||
/// Decreases the tick count.
|
||||
/// Returned value should be later passed as ticksCount parameter to GetTicks method.
|
||||
/// </summary>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns>Decreased ticks count.</returns>
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
return tickCounts.FirstOrDefault(tick => tick < ticksCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases the tick count.
|
||||
/// Returned value should be later passed as ticksCount parameter to GetTicks method.
|
||||
/// </summary>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns>Increased ticks count.</returns>
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
int newTickCount = tickCounts.Reverse().FirstOrDefault(tick => tick > ticksCount);
|
||||
if (newTickCount == 0)
|
||||
newTickCount = tickCounts[0];
|
||||
|
||||
return newTickCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minor ticks provider, used to generate ticks between each two adjacent ticks.
|
||||
/// </summary>
|
||||
/// <value>The minor provider.</value>
|
||||
public ITicksProvider<int> MinorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the major provider, used to generate major ticks - for example, years for common ticks as months.
|
||||
/// </summary>
|
||||
/// <value>The major provider.</value>
|
||||
public ITicksProvider<int> MajorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
protected void RaiseChangedEvent()
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
public event EventHandler Changed;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
21
Charts/Axes/Integer/VerticalIntegerAxis.cs
Normal file
21
Charts/Axes/Integer/VerticalIntegerAxis.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
public class VerticalIntegerAxis : IntegerAxis
|
||||
{
|
||||
public VerticalIntegerAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Left;
|
||||
}
|
||||
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Bottom || newPlacement == AxisPlacement.Top)
|
||||
throw new ArgumentException(Strings.Exceptions.VerticalAxisCannotBeHorizontal);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Charts/Axes/LabelProvider.cs
Normal file
43
Charts/Axes/LabelProvider.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
public abstract class LabelProvider<T> : LabelProviderBase<T>
|
||||
{
|
||||
public override UIElement[] CreateLabels(ITicksInfo<T> ticksInfo)
|
||||
{
|
||||
var ticks = ticksInfo.Ticks;
|
||||
|
||||
UIElement[] res = new UIElement[ticks.Length];
|
||||
LabelTickInfo<T> labelInfo = new LabelTickInfo<T> { Info = ticksInfo.Info };
|
||||
|
||||
for (int i = 0; i < res.Length; i++)
|
||||
{
|
||||
labelInfo.Tick = ticks[i];
|
||||
labelInfo.Index = i;
|
||||
|
||||
string labelText = GetString(labelInfo);
|
||||
|
||||
TextBlock label = (TextBlock)GetResourceFromPool();
|
||||
if (label == null)
|
||||
{
|
||||
label = new TextBlock();
|
||||
}
|
||||
|
||||
label.Text = labelText;
|
||||
label.ToolTip = ticks[i].ToString();
|
||||
|
||||
res[i] = label;
|
||||
|
||||
ApplyCustomView(labelInfo, label);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
212
Charts/Axes/LabelProviderBase.cs
Normal file
212
Charts/Axes/LabelProviderBase.cs
Normal file
@@ -0,0 +1,212 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains data for custom generation of tick's label.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of ticks</typeparam>
|
||||
public sealed class LabelTickInfo<T>
|
||||
{
|
||||
internal LabelTickInfo() { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tick.
|
||||
/// </summary>
|
||||
/// <value>The tick.</value>
|
||||
public T Tick { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets or sets additional info about ticks range.
|
||||
/// </summary>
|
||||
/// <value>The info.</value>
|
||||
public object Info { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the index of tick in ticks array.
|
||||
/// </summary>
|
||||
/// <value>The index.</value>
|
||||
public int Index { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all label providers.
|
||||
/// Contains a number of properties that can be used to adjust generated labels.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of ticks, which labels are generated for</typeparam>
|
||||
/// <remarks>
|
||||
/// Order of apllication of custom label string properties:
|
||||
/// If CustomFormatter is not null, it is called first.
|
||||
/// Then, if it was null or if it returned null string,
|
||||
/// virtual GetStringCore method is called. It can be overloaded in subclasses. GetStringCore should not return null.
|
||||
/// Then if LabelStringFormat is not null, it is applied.
|
||||
/// After label's UI was created, you can change it by setting CustomView delegate - it allows you to adjust
|
||||
/// UI properties of label. Note: not all labelProviders takes CustomView into account.
|
||||
/// </remarks>
|
||||
public abstract class LabelProviderBase<T>
|
||||
{
|
||||
|
||||
#region Private
|
||||
|
||||
private string labelStringFormat = null;
|
||||
private Func<LabelTickInfo<T>, string> customFormatter = null;
|
||||
private Action<LabelTickInfo<T>, UIElement> customView = null;
|
||||
|
||||
#endregion
|
||||
|
||||
private static readonly UIElement[] emptyLabelsArray = new UIElement[0];
|
||||
protected static UIElement[] EmptyLabelsArray
|
||||
{
|
||||
get { return emptyLabelsArray; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates labels by given ticks info.
|
||||
/// Is not intended to be called from your code.
|
||||
/// </summary>
|
||||
/// <param name="ticksInfo">The ticks info.</param>
|
||||
/// <returns>Array of <see cref="UIElement"/>s, which are axis labels for specified axis ticks.</returns>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract UIElement[] CreateLabels(ITicksInfo<T> ticksInfo);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the label string format.
|
||||
/// </summary>
|
||||
/// <value>The label string format.</value>
|
||||
public string LabelStringFormat
|
||||
{
|
||||
get { return labelStringFormat; }
|
||||
set
|
||||
{
|
||||
if (labelStringFormat != value)
|
||||
{
|
||||
labelStringFormat = value;
|
||||
RaiseChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom formatter - delegate that can be called to create custom string representation of tick.
|
||||
/// </summary>
|
||||
/// <value>The custom formatter.</value>
|
||||
public Func<LabelTickInfo<T>, string> CustomFormatter
|
||||
{
|
||||
get { return customFormatter; }
|
||||
set
|
||||
{
|
||||
if (customFormatter != value)
|
||||
{
|
||||
customFormatter = value;
|
||||
RaiseChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom view - delegate that is used to create a custom, non-default look of axis label.
|
||||
/// Can be used to adjust some UI properties of generated label.
|
||||
/// </summary>
|
||||
/// <value>The custom view.</value>
|
||||
public Action<LabelTickInfo<T>, UIElement> CustomView
|
||||
{
|
||||
get { return customView; }
|
||||
set
|
||||
{
|
||||
if (customView != value)
|
||||
{
|
||||
customView = value;
|
||||
RaiseChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the custom formatter.
|
||||
/// This is alternative to CustomFormatter property setter, the only difference is that Visual Studio shows
|
||||
/// more convenient tooltip for methods rather than for properties' setters.
|
||||
/// </summary>
|
||||
/// <param name="formatter">The formatter.</param>
|
||||
public void SetCustomFormatter(Func<LabelTickInfo<T>, string> formatter)
|
||||
{
|
||||
CustomFormatter = formatter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the custom view.
|
||||
/// This is alternative to CustomView property setter, the only difference is that Visual Studio shows
|
||||
/// more convenient tooltip for methods rather than for properties' setters.
|
||||
/// </summary>
|
||||
/// <param name="view">The view.</param>
|
||||
public void SetCustomView(Action<LabelTickInfo<T>, UIElement> view)
|
||||
{
|
||||
CustomView = view;
|
||||
}
|
||||
|
||||
protected virtual string GetString(LabelTickInfo<T> tickInfo)
|
||||
{
|
||||
string text = null;
|
||||
if (CustomFormatter != null)
|
||||
{
|
||||
text = CustomFormatter(tickInfo);
|
||||
}
|
||||
if (text == null)
|
||||
{
|
||||
text = GetStringCore(tickInfo);
|
||||
|
||||
if (text == null)
|
||||
throw new ArgumentNullException(Strings.Exceptions.TextOfTickShouldNotBeNull);
|
||||
}
|
||||
if (LabelStringFormat != null)
|
||||
{
|
||||
text = String.Format(LabelStringFormat, text);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
protected virtual string GetStringCore(LabelTickInfo<T> tickInfo)
|
||||
{
|
||||
return tickInfo.Tick.ToString();
|
||||
}
|
||||
|
||||
protected void ApplyCustomView(LabelTickInfo<T> info, UIElement label)
|
||||
{
|
||||
if (CustomView != null)
|
||||
{
|
||||
CustomView(info, label);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when label provider is changed.
|
||||
/// Notifies axis to update its view.
|
||||
/// </summary>
|
||||
public event EventHandler Changed;
|
||||
protected void RaiseChanged()
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
|
||||
private readonly ResourcePool<UIElement> pool = new ResourcePool<UIElement>();
|
||||
internal void ReleaseLabel(UIElement label)
|
||||
{
|
||||
if (ReleaseCore(label))
|
||||
{
|
||||
pool.Put(label);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool ReleaseCore(UIElement label) { return false; }
|
||||
|
||||
protected UIElement GetResourceFromPool()
|
||||
{
|
||||
return pool.Get();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Charts/Axes/LabelProviderProperties.cs
Normal file
27
Charts/Axes/LabelProviderProperties.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
internal class LabelProviderProperties : DependencyObject
|
||||
{
|
||||
public static bool GetExponentialIsCommonLabel(DependencyObject obj)
|
||||
{
|
||||
return (bool)obj.GetValue(ExponentialIsCommonLabelProperty);
|
||||
}
|
||||
|
||||
public static void SetExponentialIsCommonLabel(DependencyObject obj, bool value)
|
||||
{
|
||||
obj.SetValue(ExponentialIsCommonLabelProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ExponentialIsCommonLabelProperty = DependencyProperty.RegisterAttached(
|
||||
"ExponentialIsCommonLabel",
|
||||
typeof(bool),
|
||||
typeof(LabelProviderProperties),
|
||||
new FrameworkPropertyMetadata(true));
|
||||
}
|
||||
}
|
||||
92
Charts/Axes/Numeric/CustomBaseNumericLabelProvider.cs
Normal file
92
Charts/Axes/Numeric/CustomBaseNumericLabelProvider.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using Microsoft.Research.DynamicDataDisplay;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.Numeric
|
||||
{
|
||||
public class CustomBaseNumericLabelProvider : LabelProvider<double>
|
||||
{
|
||||
private double customBase = 2;
|
||||
/// <summary>
|
||||
/// Gets or sets the custom base.
|
||||
/// </summary>
|
||||
/// <value>The custom base.</value>
|
||||
public double CustomBase
|
||||
{
|
||||
get { return customBase; }
|
||||
set
|
||||
{
|
||||
if (Double.IsNaN(value))
|
||||
throw new ArgumentException(Strings.Exceptions.CustomBaseTicksProviderBaseIsNaN);
|
||||
if (value <= 0)
|
||||
throw new ArgumentOutOfRangeException(Strings.Exceptions.CustomBaseTicksProviderBaseIsTooSmall);
|
||||
|
||||
customBase = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomBaseNumericLabelProvider"/> class.
|
||||
/// </summary>
|
||||
public CustomBaseNumericLabelProvider() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomBaseNumericLabelProvider"/> class.
|
||||
/// </summary>
|
||||
public CustomBaseNumericLabelProvider(double customBase)
|
||||
: this()
|
||||
{
|
||||
CustomBase = customBase;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomBaseNumericLabelProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="customBase">The custom base.</param>
|
||||
/// <param name="customBaseString">The custom base string.</param>
|
||||
public CustomBaseNumericLabelProvider(double customBase, string customBaseString)
|
||||
: this(customBase)
|
||||
{
|
||||
CustomBaseString = customBaseString;
|
||||
}
|
||||
|
||||
private string customBaseString = null;
|
||||
/// <summary>
|
||||
/// Gets or sets the custom base string.
|
||||
/// </summary>
|
||||
/// <value>The custom base string.</value>
|
||||
public string CustomBaseString
|
||||
{
|
||||
get { return customBaseString; }
|
||||
set
|
||||
{
|
||||
if (customBaseString != value)
|
||||
{
|
||||
customBaseString = value;
|
||||
RaiseChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetStringCore(LabelTickInfo<double> tickInfo)
|
||||
{
|
||||
double value = tickInfo.Tick / customBase;
|
||||
|
||||
string customBaseStr = customBaseString ?? customBase.ToString();
|
||||
string result;
|
||||
if (value == 1)
|
||||
result = customBaseStr;
|
||||
else if (value == -1)
|
||||
{
|
||||
result = "-" + customBaseStr;
|
||||
}
|
||||
else
|
||||
result = value.ToString() + customBaseStr;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
202
Charts/Axes/Numeric/CustomBaseNumericTicksProvider.cs
Normal file
202
Charts/Axes/Numeric/CustomBaseNumericTicksProvider.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using System.Windows.Markup;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.Numeric
|
||||
{
|
||||
[ContentProperty("TicksProvider")]
|
||||
public class CustomBaseNumericTicksProvider : ITicksProvider<double>
|
||||
{
|
||||
private double customBase = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom base.
|
||||
/// </summary>
|
||||
/// <value>The custom base.</value>
|
||||
public double CustomBase
|
||||
{
|
||||
get { return customBase; }
|
||||
set
|
||||
{
|
||||
if (Double.IsNaN(value))
|
||||
throw new ArgumentException(Strings.Exceptions.CustomBaseTicksProviderBaseIsNaN);
|
||||
if (value <= 0)
|
||||
throw new ArgumentOutOfRangeException(Strings.Exceptions.CustomBaseTicksProviderBaseIsTooSmall);
|
||||
|
||||
customBase = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomBaseNumericTicksProvider"/> class.
|
||||
/// </summary>
|
||||
public CustomBaseNumericTicksProvider() : this(2.0) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomBaseNumericTicksProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="customBase">The custom base, e.g. Math.PI</param>
|
||||
public CustomBaseNumericTicksProvider(double customBase) : this(customBase, new NumericTicksProvider()) { }
|
||||
|
||||
private CustomBaseNumericTicksProvider(double customBase, ITicksProvider<double> ticksProvider)
|
||||
{
|
||||
if (ticksProvider == null)
|
||||
throw new ArgumentNullException("ticksProvider");
|
||||
|
||||
CustomBase = customBase;
|
||||
|
||||
TicksProvider = ticksProvider;
|
||||
}
|
||||
|
||||
private void ticksProvider_Changed(object sender, EventArgs e)
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
|
||||
private ITicksProvider<double> ticksProvider = null;
|
||||
public ITicksProvider<double> TicksProvider
|
||||
{
|
||||
get { return ticksProvider; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
if (ticksProvider != null)
|
||||
ticksProvider.Changed -= ticksProvider_Changed;
|
||||
ticksProvider = value;
|
||||
ticksProvider.Changed += ticksProvider_Changed;
|
||||
|
||||
if (minorTicksProvider != null)
|
||||
minorTicksProvider.Changed -= minorTicksProvider_Changed;
|
||||
minorTicksProvider = new MinorProviderWrapper(this);
|
||||
minorTicksProvider.Changed += minorTicksProvider_Changed;
|
||||
|
||||
Changed.Raise(this);
|
||||
}
|
||||
}
|
||||
|
||||
void minorTicksProvider_Changed(object sender, EventArgs e)
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
|
||||
private Range<double> TransformRange(Range<double> range)
|
||||
{
|
||||
double min = range.Min / customBase;
|
||||
double max = range.Max / customBase;
|
||||
|
||||
return new Range<double>(min, max);
|
||||
}
|
||||
|
||||
#region ITicksProvider<double> Members
|
||||
|
||||
private double[] tickMarks;
|
||||
public ITicksInfo<double> GetTicks(Range<double> range, int ticksCount)
|
||||
{
|
||||
var ticks = ticksProvider.GetTicks(TransformRange(range), ticksCount);
|
||||
|
||||
TransformTicks(ticks);
|
||||
|
||||
tickMarks = ticks.Ticks;
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
private void TransformTicks(ITicksInfo<double> ticks)
|
||||
{
|
||||
for (int i = 0; i < ticks.Ticks.Length; i++)
|
||||
{
|
||||
ticks.Ticks[i] *= customBase;
|
||||
}
|
||||
}
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
return ticksProvider.DecreaseTickCount(ticksCount);
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
return ticksProvider.IncreaseTickCount(ticksCount);
|
||||
}
|
||||
|
||||
private ITicksProvider<double> minorTicksProvider;
|
||||
public ITicksProvider<double> MinorProvider
|
||||
{
|
||||
get { return minorTicksProvider; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the major provider, used to generate major ticks - for example, years for common ticks as months.
|
||||
/// </summary>
|
||||
/// <value>The major provider.</value>
|
||||
public ITicksProvider<double> MajorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public event EventHandler Changed;
|
||||
|
||||
#endregion
|
||||
|
||||
private sealed class MinorProviderWrapper : ITicksProvider<double>
|
||||
{
|
||||
private readonly CustomBaseNumericTicksProvider owner;
|
||||
|
||||
public MinorProviderWrapper(CustomBaseNumericTicksProvider owner)
|
||||
{
|
||||
this.owner = owner;
|
||||
|
||||
MinorTicksProvider.Changed += MinorTicksProvider_Changed;
|
||||
}
|
||||
|
||||
private void MinorTicksProvider_Changed(object sender, EventArgs e)
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
|
||||
private ITicksProvider<double> MinorTicksProvider
|
||||
{
|
||||
get { return owner.ticksProvider.MinorProvider; }
|
||||
}
|
||||
|
||||
#region ITicksProvider<double> Members
|
||||
|
||||
public ITicksInfo<double> GetTicks(Range<double> range, int ticksCount)
|
||||
{
|
||||
var minorProvider = MinorTicksProvider;
|
||||
var ticks = minorProvider.GetTicks(range, ticksCount);
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
return MinorTicksProvider.DecreaseTickCount(ticksCount);
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
return MinorTicksProvider.IncreaseTickCount(ticksCount);
|
||||
}
|
||||
|
||||
public ITicksProvider<double> MinorProvider
|
||||
{
|
||||
get { return MinorTicksProvider.MinorProvider; }
|
||||
}
|
||||
|
||||
public ITicksProvider<double> MajorProvider
|
||||
{
|
||||
get { return owner; }
|
||||
}
|
||||
|
||||
public event EventHandler Changed;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
92
Charts/Axes/Numeric/ExponentialLabelProvider.cs
Normal file
92
Charts/Axes/Numeric/ExponentialLabelProvider.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Globalization;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an axis label provider for double ticks, generating labels with numbers in exponential form when it is appropriate.
|
||||
/// </summary>
|
||||
public sealed class ExponentialLabelProvider : NumericLabelProviderBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExponentialLabelProvider"/> class.
|
||||
/// </summary>
|
||||
public ExponentialLabelProvider() { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates labels by given ticks info.
|
||||
/// Is not intended to be called from your code.
|
||||
/// </summary>
|
||||
/// <param name="ticksInfo">The ticks info.</param>
|
||||
/// <returns>
|
||||
/// Array of <see cref="UIElement"/>s, which are axis labels for specified axis ticks.
|
||||
/// </returns>
|
||||
public override UIElement[] CreateLabels(ITicksInfo<double> ticksInfo)
|
||||
{
|
||||
var ticks = ticksInfo.Ticks;
|
||||
|
||||
Init(ticks);
|
||||
|
||||
UIElement[] res = new UIElement[ticks.Length];
|
||||
|
||||
LabelTickInfo<double> tickInfo = new LabelTickInfo<double> { Info = ticksInfo.Info };
|
||||
|
||||
for (int i = 0; i < res.Length; i++)
|
||||
{
|
||||
var tick = ticks[i];
|
||||
tickInfo.Tick = tick;
|
||||
tickInfo.Index = i;
|
||||
|
||||
string labelText = GetString(tickInfo);
|
||||
|
||||
TextBlock label;
|
||||
if (labelText.Contains('E'))
|
||||
{
|
||||
string[] substrs = labelText.Split('E');
|
||||
string mantissa = substrs[0];
|
||||
string exponenta = substrs[1];
|
||||
exponenta = exponenta.TrimStart('+');
|
||||
Span span = new Span();
|
||||
span.Inlines.Add(String.Format(CultureInfo.CurrentCulture, "{0}·10", mantissa));
|
||||
Span exponentaSpan = new Span(new Run(exponenta));
|
||||
exponentaSpan.BaselineAlignment = BaselineAlignment.Superscript;
|
||||
exponentaSpan.FontSize = 8;
|
||||
span.Inlines.Add(exponentaSpan);
|
||||
|
||||
label = new TextBlock(span);
|
||||
LabelProviderProperties.SetExponentialIsCommonLabel(label, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
label = (TextBlock)GetResourceFromPool();
|
||||
if (label == null)
|
||||
{
|
||||
label = new TextBlock();
|
||||
}
|
||||
|
||||
label.Text = labelText;
|
||||
}
|
||||
res[i] = label;
|
||||
label.ToolTip = tick.ToString(CultureInfo.CurrentCulture);
|
||||
|
||||
ApplyCustomView(tickInfo, label);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected override bool ReleaseCore(UIElement label)
|
||||
{
|
||||
bool isNotExponential = LabelProviderProperties.GetExponentialIsCommonLabel(label);
|
||||
return isNotExponential && CustomView == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Charts/Axes/Numeric/HorizontalAxis.cs
Normal file
34
Charts/Axes/Numeric/HorizontalAxis.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a horizontal axis with values of <see cref="Double"/> type.
|
||||
/// Can be placed only from bottom or top side of plotter.
|
||||
/// By default is placed from the bottom side.
|
||||
/// </summary>
|
||||
public class HorizontalAxis : NumericAxis
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HorizontalAxis"/> class, placed on bottom of <see cref="ChartPlotter"/>.
|
||||
/// </summary>
|
||||
public HorizontalAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Bottom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the placement - e.g., vertical axis should not be placed from top or bottom, etc.
|
||||
/// If proposed placement is wrong, throws an ArgumentException.
|
||||
/// </summary>
|
||||
/// <param name="newPlacement">The new placement.</param>
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Left || newPlacement == AxisPlacement.Right)
|
||||
throw new ArgumentException(Strings.Exceptions.HorizontalAxisCannotBeVertical);
|
||||
}
|
||||
}
|
||||
}
|
||||
131
Charts/Axes/Numeric/LogarithmNumericTicksProvider.cs
Normal file
131
Charts/Axes/Numeric/LogarithmNumericTicksProvider.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Markup;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.Numeric
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a ticks provider for logarithmically transfomed axis - returns ticks which are a power of specified logarithm base.
|
||||
/// </summary>
|
||||
public class LogarithmNumericTicksProvider : ITicksProvider<double>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LogarithmNumericTicksProvider"/> class.
|
||||
/// </summary>
|
||||
public LogarithmNumericTicksProvider()
|
||||
{
|
||||
minorProvider = new MinorNumericTicksProvider(this);
|
||||
minorProvider.Changed += ticksProvider_Changed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LogarithmNumericTicksProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logarithmBase">The logarithm base.</param>
|
||||
public LogarithmNumericTicksProvider(double logarithmBase)
|
||||
: this()
|
||||
{
|
||||
LogarithmBase = logarithmBase;
|
||||
}
|
||||
|
||||
private void ticksProvider_Changed(object sender, EventArgs e)
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
|
||||
private double logarithmBase = 10;
|
||||
public double LogarithmBase
|
||||
{
|
||||
get { return logarithmBase; }
|
||||
set
|
||||
{
|
||||
if (value <= 0)
|
||||
throw new ArgumentOutOfRangeException(Strings.Exceptions.LogarithmBaseShouldBePositive);
|
||||
|
||||
logarithmBase = value;
|
||||
}
|
||||
}
|
||||
|
||||
private double LogByBase(double d)
|
||||
{
|
||||
return Math.Log10(d) / Math.Log10(logarithmBase);
|
||||
}
|
||||
|
||||
#region ITicksProvider<double> Members
|
||||
|
||||
private double[] ticks;
|
||||
public ITicksInfo<double> GetTicks(Range<double> range, int ticksCount)
|
||||
{
|
||||
double min = LogByBase(range.Min);
|
||||
double max = LogByBase(range.Max);
|
||||
|
||||
double minDown = Math.Floor(min);
|
||||
double maxUp = Math.Ceiling(max);
|
||||
|
||||
double logLength = LogByBase(range.GetLength());
|
||||
|
||||
ticks = CreateTicks(range);
|
||||
|
||||
int log = RoundingHelper.GetDifferenceLog(range.Min, range.Max);
|
||||
TicksInfo<double> result = new TicksInfo<double> { Ticks = ticks, TickSizes = ArrayExtensions.CreateArray(ticks.Length, 1.0), Info = log };
|
||||
return result;
|
||||
}
|
||||
|
||||
private double[] CreateTicks(Range<double> range)
|
||||
{
|
||||
double min = LogByBase(range.Min);
|
||||
double max = LogByBase(range.Max);
|
||||
|
||||
double minDown = Math.Floor(min);
|
||||
double maxUp = Math.Ceiling(max);
|
||||
|
||||
int intStart = (int)Math.Floor(minDown);
|
||||
int count = (int)(maxUp - minDown + 1);
|
||||
|
||||
var ticks = new double[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ticks[i] = intStart + i;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ticks.Length; i++)
|
||||
{
|
||||
ticks[i] = Math.Pow(logarithmBase, ticks[i]);
|
||||
}
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
return ticksCount;
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
return ticksCount;
|
||||
}
|
||||
|
||||
private MinorNumericTicksProvider minorProvider;
|
||||
public ITicksProvider<double> MinorProvider
|
||||
{
|
||||
get
|
||||
{
|
||||
minorProvider.SetRanges(ArrayExtensions.GetPairs(ticks));
|
||||
return minorProvider;
|
||||
}
|
||||
}
|
||||
|
||||
public ITicksProvider<double> MajorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public event EventHandler Changed;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
89
Charts/Axes/Numeric/MinorNumericTicksProvider.cs
Normal file
89
Charts/Axes/Numeric/MinorNumericTicksProvider.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public sealed class MinorNumericTicksProvider : ITicksProvider<double>
|
||||
{
|
||||
private readonly ITicksProvider<double> parent;
|
||||
private Range<double>[] ranges;
|
||||
internal void SetRanges(IEnumerable<Range<double>> ranges)
|
||||
{
|
||||
this.ranges = ranges.ToArray();
|
||||
}
|
||||
|
||||
private double[] coeffs;
|
||||
public double[] Coeffs
|
||||
{
|
||||
get { return coeffs; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
coeffs = value;
|
||||
Changed.Raise(this);
|
||||
}
|
||||
}
|
||||
|
||||
internal MinorNumericTicksProvider(ITicksProvider<double> parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
Coeffs = new double[] { 0.3, 0.3, 0.3, 0.3, 0.6, 0.3, 0.3, 0.3, 0.3 };
|
||||
}
|
||||
|
||||
#region ITicksProvider<double> Members
|
||||
|
||||
public event EventHandler Changed;
|
||||
|
||||
public ITicksInfo<double> GetTicks(Range<double> range, int ticksCount)
|
||||
{
|
||||
if (Coeffs.Length == 0)
|
||||
return new TicksInfo<double>();
|
||||
|
||||
var minorTicks = ranges.Select(r => CreateTicks(r)).SelectMany(m => m);
|
||||
var res = new TicksInfo<double>();
|
||||
res.TickSizes = minorTicks.Select(m => m.Value).ToArray();
|
||||
res.Ticks = minorTicks.Select(m => m.Tick).ToArray();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public MinorTickInfo<double>[] CreateTicks(Range<double> range)
|
||||
{
|
||||
double step = (range.Max - range.Min) / (Coeffs.Length + 1);
|
||||
|
||||
MinorTickInfo<double>[] res = new MinorTickInfo<double>[Coeffs.Length];
|
||||
for (int i = 0; i < Coeffs.Length; i++)
|
||||
{
|
||||
res[i] = new MinorTickInfo<double>(Coeffs[i], range.Min + step * (i + 1));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
return ticksCount;
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
return ticksCount;
|
||||
}
|
||||
|
||||
public ITicksProvider<double> MinorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public ITicksProvider<double> MajorProvider
|
||||
{
|
||||
get { return parent; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
41
Charts/Axes/Numeric/NumericAxis.cs
Normal file
41
Charts/Axes/Numeric/NumericAxis.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes.Numeric;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a numeric axis with values of <see cref="System.Double"/> type.
|
||||
/// </summary>
|
||||
public class NumericAxis : AxisBase<double>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NumericAxis"/> class.
|
||||
/// </summary>
|
||||
public NumericAxis()
|
||||
: base(new NumericAxisControl(),
|
||||
d => d,
|
||||
d => d)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets conversions of axis - functions used to convert values of axis type to and from double values of viewport.
|
||||
/// Sets both ConvertToDouble and ConvertFromDouble properties.
|
||||
/// </summary>
|
||||
/// <param name="min">The minimal viewport value.</param>
|
||||
/// <param name="minValue">The value of axis type, corresponding to minimal viewport value.</param>
|
||||
/// <param name="max">The maximal viewport value.</param>
|
||||
/// <param name="maxValue">The value of axis type, corresponding to maximal viewport value.</param>
|
||||
public override void SetConversion(double min, double minValue, double max, double maxValue)
|
||||
{
|
||||
var conversion = new NumericConversion(min, minValue, max, maxValue);
|
||||
|
||||
this.ConvertFromDouble = conversion.FromDouble;
|
||||
this.ConvertToDouble = conversion.ToDouble;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Charts/Axes/Numeric/NumericAxisControl.cs
Normal file
18
Charts/Axes/Numeric/NumericAxisControl.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class NumericAxisControl : AxisControl<double>
|
||||
{
|
||||
public NumericAxisControl()
|
||||
{
|
||||
LabelProvider = new ExponentialLabelProvider();
|
||||
TicksProvider = new NumericTicksProvider();
|
||||
ConvertToDouble = d => d;
|
||||
Range = new Range<double>(0, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Charts/Axes/Numeric/NumericConversion.cs
Normal file
38
Charts/Axes/Numeric/NumericConversion.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.Numeric
|
||||
{
|
||||
internal sealed class NumericConversion
|
||||
{
|
||||
private readonly double min;
|
||||
private readonly double length;
|
||||
private readonly double minValue;
|
||||
private readonly double valueLength;
|
||||
|
||||
public NumericConversion(double min, double minValue, double max, double maxValue)
|
||||
{
|
||||
this.min = min;
|
||||
this.length = max - min;
|
||||
|
||||
this.minValue = minValue;
|
||||
this.valueLength = maxValue - minValue;
|
||||
}
|
||||
|
||||
public double FromDouble(double value)
|
||||
{
|
||||
double ratio = (value - min) / length;
|
||||
|
||||
return minValue + ratio * valueLength;
|
||||
}
|
||||
|
||||
public double ToDouble(double value)
|
||||
{
|
||||
double ratio = (value - minValue) / valueLength;
|
||||
|
||||
return min + length * ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Charts/Axes/Numeric/NumericLabelProviderBase.cs
Normal file
54
Charts/Axes/Numeric/NumericLabelProviderBase.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public abstract class NumericLabelProviderBase : LabelProviderBase<double>
|
||||
{
|
||||
bool shouldRound = true;
|
||||
private int rounding;
|
||||
protected void Init(double[] ticks)
|
||||
{
|
||||
if (ticks.Length == 0)
|
||||
return;
|
||||
|
||||
double start = ticks[0];
|
||||
double finish = ticks[ticks.Length - 1];
|
||||
|
||||
if (start == finish)
|
||||
{
|
||||
shouldRound = false;
|
||||
return;
|
||||
}
|
||||
|
||||
double delta = finish - start;
|
||||
|
||||
rounding = (int)Math.Round(Math.Log10(delta));
|
||||
|
||||
double newStart = RoundingHelper.Round(start, rounding);
|
||||
double newFinish = RoundingHelper.Round(finish, rounding);
|
||||
if (newStart == newFinish)
|
||||
rounding--;
|
||||
}
|
||||
|
||||
protected override string GetStringCore(LabelTickInfo<double> tickInfo)
|
||||
{
|
||||
string res;
|
||||
if (!shouldRound)
|
||||
{
|
||||
res = tickInfo.Tick.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
int round = Math.Min(15, Math.Max(-15, rounding - 3)); // was rounding - 2
|
||||
res = RoundingHelper.Round(tickInfo.Tick, round).ToString();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
167
Charts/Axes/Numeric/NumericTicksProvider.cs
Normal file
167
Charts/Axes/Numeric/NumericTicksProvider.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a ticks provider for <see cref="System.Double"/> values.
|
||||
/// </summary>
|
||||
public sealed class NumericTicksProvider : ITicksProvider<double>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NumericTicksProvider"/> class.
|
||||
/// </summary>
|
||||
public NumericTicksProvider()
|
||||
{
|
||||
minorProvider = new MinorNumericTicksProvider(this);
|
||||
minorProvider.Changed += minorProvider_Changed;
|
||||
minorProvider.Coeffs = new double[] { 0.3, 0.3, 0.3, 0.3, 0.6, 0.3, 0.3, 0.3, 0.3 };
|
||||
}
|
||||
|
||||
private void minorProvider_Changed(object sender, EventArgs e)
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
|
||||
public event EventHandler Changed;
|
||||
private void RaiseChangedEvent()
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
|
||||
private double minStep = 0.0;
|
||||
/// <summary>
|
||||
/// Gets or sets the minimal step between ticks.
|
||||
/// </summary>
|
||||
/// <value>The min step.</value>
|
||||
public double MinStep
|
||||
{
|
||||
get { return minStep; }
|
||||
set
|
||||
{
|
||||
Verify.IsTrue(value >= 0.0, "value");
|
||||
if (minStep != value)
|
||||
{
|
||||
minStep = value;
|
||||
RaiseChangedEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double[] ticks;
|
||||
public ITicksInfo<double> GetTicks(Range<double> range, int ticksCount)
|
||||
{
|
||||
double start = range.Min;
|
||||
double finish = range.Max;
|
||||
|
||||
double delta = finish - start;
|
||||
|
||||
int log = (int)Math.Round(Math.Log10(delta));
|
||||
|
||||
double newStart = RoundingHelper.Round(start, log);
|
||||
double newFinish = RoundingHelper.Round(finish, log);
|
||||
if (newStart == newFinish)
|
||||
{
|
||||
log--;
|
||||
newStart = RoundingHelper.Round(start, log);
|
||||
newFinish = RoundingHelper.Round(finish, log);
|
||||
}
|
||||
|
||||
// calculating step between ticks
|
||||
double unroundedStep = (newFinish - newStart) / ticksCount;
|
||||
int stepLog = log;
|
||||
// trying to round step
|
||||
double step = RoundingHelper.Round(unroundedStep, stepLog);
|
||||
if (step == 0)
|
||||
{
|
||||
stepLog--;
|
||||
step = RoundingHelper.Round(unroundedStep, stepLog);
|
||||
if (step == 0)
|
||||
{
|
||||
// step will not be rounded if attempts to be rounded to zero.
|
||||
step = unroundedStep;
|
||||
}
|
||||
}
|
||||
|
||||
if (step < minStep)
|
||||
step = minStep;
|
||||
|
||||
if (step != 0.0)
|
||||
{
|
||||
ticks = CreateTicks(start, finish, step);
|
||||
}
|
||||
else
|
||||
{
|
||||
ticks = new double[] { };
|
||||
}
|
||||
|
||||
TicksInfo<double> res = new TicksInfo<double> { Info = log, Ticks = ticks };
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static double[] CreateTicks(double start, double finish, double step)
|
||||
{
|
||||
DebugVerify.Is(step != 0.0);
|
||||
|
||||
double x = step * Math.Floor(start / step);
|
||||
|
||||
if (x == x + step)
|
||||
{
|
||||
return new double[0];
|
||||
}
|
||||
|
||||
List<double> res = new List<double>();
|
||||
|
||||
double increasedFinish = finish + step * 1.05;
|
||||
while (x <= increasedFinish)
|
||||
{
|
||||
res.Add(x);
|
||||
DebugVerify.Is(res.Count < 2000);
|
||||
x += step;
|
||||
}
|
||||
return res.ToArray();
|
||||
}
|
||||
|
||||
private static int[] tickCounts = new int[] { 20, 10, 5, 4, 2, 1 };
|
||||
|
||||
public const int DefaultPreferredTicksCount = 10;
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
return tickCounts.FirstOrDefault(tick => tick < ticksCount);
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
int newTickCount = tickCounts.Reverse().FirstOrDefault(tick => tick > ticksCount);
|
||||
if (newTickCount == 0)
|
||||
newTickCount = tickCounts[0];
|
||||
|
||||
return newTickCount;
|
||||
}
|
||||
|
||||
private readonly MinorNumericTicksProvider minorProvider;
|
||||
public ITicksProvider<double> MinorProvider
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ticks != null)
|
||||
{
|
||||
minorProvider.SetRanges(ticks.GetPairs());
|
||||
}
|
||||
|
||||
return minorProvider;
|
||||
}
|
||||
}
|
||||
|
||||
public ITicksProvider<double> MajorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Charts/Axes/Numeric/ToStringLabelProvider.cs
Normal file
53
Charts/Axes/Numeric/ToStringLabelProvider.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a simple label provider for double ticks, which simply returns result of .ToString() method, called for rounded ticks.
|
||||
/// </summary>
|
||||
public class ToStringLabelProvider : NumericLabelProviderBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ToStringLabelProvider"/> class.
|
||||
/// </summary>
|
||||
public ToStringLabelProvider() { }
|
||||
|
||||
public override UIElement[] CreateLabels(ITicksInfo<double> ticksInfo)
|
||||
{
|
||||
var ticks = ticksInfo.Ticks;
|
||||
|
||||
Init(ticks);
|
||||
|
||||
UIElement[] res = new UIElement[ticks.Length];
|
||||
LabelTickInfo<double> tickInfo = new LabelTickInfo<double> { Info = ticksInfo.Info };
|
||||
for (int i = 0; i < res.Length; i++)
|
||||
{
|
||||
tickInfo.Tick = ticks[i];
|
||||
tickInfo.Index = i;
|
||||
|
||||
string labelText = GetString(tickInfo);
|
||||
|
||||
TextBlock label = (TextBlock)GetResourceFromPool();
|
||||
if (label == null)
|
||||
{
|
||||
label = new TextBlock();
|
||||
}
|
||||
|
||||
label.Text = labelText;
|
||||
label.ToolTip = ticks[i].ToString();
|
||||
|
||||
res[i] = label;
|
||||
|
||||
ApplyCustomView(tickInfo, label);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Charts/Axes/Numeric/UnroundingLabelProvider.cs
Normal file
11
Charts/Axes/Numeric/UnroundingLabelProvider.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.Numeric
|
||||
{
|
||||
public class UnroundingLabelProvider : LabelProvider<double>
|
||||
{
|
||||
}
|
||||
}
|
||||
35
Charts/Axes/Numeric/VerticalAxis.cs
Normal file
35
Charts/Axes/Numeric/VerticalAxis.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a vertical axis with values of System.Double type.
|
||||
/// Can be placed only from left or right side of plotter.
|
||||
/// By default is placed from the left side.
|
||||
/// </summary>
|
||||
public class VerticalAxis : NumericAxis
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VerticalAxis"/> class.
|
||||
/// </summary>
|
||||
public VerticalAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the placement - e.g., vertical axis should not be placed from top or bottom, etc.
|
||||
/// If proposed placement if wrong, throws an ArgumentException.
|
||||
/// </summary>
|
||||
/// <param name="newPlacement">The new placement.</param>
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Bottom || newPlacement == AxisPlacement.Top)
|
||||
throw new ArgumentException(Strings.Exceptions.VerticalAxisCannotBeHorizontal);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Charts/Axes/Numeric/VerticalNumericAxis.cs
Normal file
21
Charts/Axes/Numeric/VerticalNumericAxis.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
public class VerticalNumericAxis : NumericAxis
|
||||
{
|
||||
public VerticalNumericAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Left;
|
||||
}
|
||||
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Bottom || newPlacement == AxisPlacement.Top)
|
||||
throw new ArgumentException(Strings.Exceptions.VerticalAxisCannotBeHorizontal);
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Charts/Axes/RoundingHelper.cs
Normal file
67
Charts/Axes/RoundingHelper.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal static class RoundingHelper
|
||||
{
|
||||
internal static int GetDifferenceLog(double min, double max)
|
||||
{
|
||||
return (int)Math.Round(Math.Log10(Math.Abs(max - min)));
|
||||
}
|
||||
|
||||
internal static double Round(double number, int rem)
|
||||
{
|
||||
if (rem <= 0)
|
||||
{
|
||||
rem = MathHelper.Clamp(-rem, 0, 15);
|
||||
return Math.Round(number, rem);
|
||||
}
|
||||
else
|
||||
{
|
||||
double pow = Math.Pow(10, rem - 1);
|
||||
double val = pow * Math.Round(number / Math.Pow(10, rem - 1));
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
internal static double Round(double value, Range<double> range)
|
||||
{
|
||||
int log = GetDifferenceLog(range.Min, range.Max);
|
||||
|
||||
return Round(value, log);
|
||||
}
|
||||
|
||||
internal static RoundingInfo CreateRoundedRange(double min, double max)
|
||||
{
|
||||
double delta = max - min;
|
||||
|
||||
if (delta == 0)
|
||||
return new RoundingInfo { Min = min, Max = max, Log = 0 };
|
||||
|
||||
int log = (int)Math.Round(Math.Log10(Math.Abs(delta))) + 1;
|
||||
|
||||
double newMin = Round(min, log);
|
||||
double newMax = Round(max, log);
|
||||
if (newMin == newMax)
|
||||
{
|
||||
log--;
|
||||
newMin = Round(min, log);
|
||||
newMax = Round(max, log);
|
||||
}
|
||||
|
||||
return new RoundingInfo { Min = newMin, Max = newMax, Log = log };
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{Min} - {Max}, Log = {Log}")]
|
||||
internal sealed class RoundingInfo
|
||||
{
|
||||
public double Min { get; set; }
|
||||
public double Max { get; set; }
|
||||
public int Log { get; set; }
|
||||
}
|
||||
}
|
||||
237
Charts/Axes/StackCanvas.cs
Normal file
237
Charts/Axes/StackCanvas.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class StackCanvas : Panel
|
||||
{
|
||||
public StackCanvas()
|
||||
{
|
||||
//ClipToBounds = true;
|
||||
}
|
||||
|
||||
#region EndCoordinate attached property
|
||||
|
||||
[AttachedPropertyBrowsableForChildren]
|
||||
public static double GetEndCoordinate(DependencyObject obj)
|
||||
{
|
||||
return (double)obj.GetValue(EndCoordinateProperty);
|
||||
}
|
||||
|
||||
public static void SetEndCoordinate(DependencyObject obj, double value)
|
||||
{
|
||||
obj.SetValue(EndCoordinateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty EndCoordinateProperty = DependencyProperty.RegisterAttached(
|
||||
"EndCoordinate",
|
||||
typeof(double),
|
||||
typeof(StackCanvas),
|
||||
new PropertyMetadata(Double.NaN, OnCoordinateChanged));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Coordinate attached property
|
||||
|
||||
[AttachedPropertyBrowsableForChildren]
|
||||
public static double GetCoordinate(DependencyObject obj)
|
||||
{
|
||||
return (double)obj.GetValue(CoordinateProperty);
|
||||
}
|
||||
|
||||
public static void SetCoordinate(DependencyObject obj, double value)
|
||||
{
|
||||
obj.SetValue(CoordinateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty CoordinateProperty = DependencyProperty.RegisterAttached(
|
||||
"Coordinate",
|
||||
typeof(double),
|
||||
typeof(StackCanvas),
|
||||
new PropertyMetadata(0.0, OnCoordinateChanged));
|
||||
|
||||
private static void OnCoordinateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
UIElement reference = d as UIElement;
|
||||
if (reference != null)
|
||||
{
|
||||
StackCanvas parent = VisualTreeHelper.GetParent(reference) as StackCanvas;
|
||||
if (parent != null)
|
||||
{
|
||||
parent.InvalidateArrange();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AxisPlacement property
|
||||
|
||||
public AxisPlacement Placement
|
||||
{
|
||||
get { return (AxisPlacement)GetValue(PlacementProperty); }
|
||||
set { SetValue(PlacementProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PlacementProperty =
|
||||
DependencyProperty.Register(
|
||||
"Placement",
|
||||
typeof(AxisPlacement),
|
||||
typeof(StackCanvas),
|
||||
new FrameworkPropertyMetadata(
|
||||
AxisPlacement.Bottom,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange));
|
||||
|
||||
#endregion
|
||||
|
||||
private bool IsHorizontal
|
||||
{
|
||||
get { return Placement == AxisPlacement.Top || Placement == AxisPlacement.Bottom; }
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size constraint)
|
||||
{
|
||||
Size availableSize = constraint;
|
||||
Size size = new Size();
|
||||
|
||||
bool isHorizontal = IsHorizontal;
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
availableSize.Width = Double.PositiveInfinity;
|
||||
size.Width = constraint.Width;
|
||||
}
|
||||
else
|
||||
{
|
||||
availableSize.Height = Double.PositiveInfinity;
|
||||
size.Height = constraint.Height;
|
||||
}
|
||||
|
||||
// measuring all children and determinimg self width and height
|
||||
foreach (UIElement element in base.Children)
|
||||
{
|
||||
if (element != null)
|
||||
{
|
||||
Size childSize = GetChildSize(element, availableSize);
|
||||
element.Measure(childSize);
|
||||
Size desiredSize = element.DesiredSize;
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
size.Height = Math.Max(size.Height, desiredSize.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
size.Width = Math.Max(size.Width, desiredSize.Width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Double.IsPositiveInfinity(size.Width)) size.Width = 0;
|
||||
if (Double.IsPositiveInfinity(size.Height)) size.Height = 0;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private Size GetChildSize(UIElement element, Size availableSize)
|
||||
{
|
||||
var coordinate = GetCoordinate(element);
|
||||
var endCoordinate = GetEndCoordinate(element);
|
||||
|
||||
if (coordinate.IsNotNaN() && endCoordinate.IsNotNaN())
|
||||
{
|
||||
if (Placement.IsBottomOrTop())
|
||||
{
|
||||
availableSize.Width = endCoordinate - coordinate;
|
||||
}
|
||||
else
|
||||
{
|
||||
availableSize.Height = endCoordinate - coordinate;
|
||||
}
|
||||
}
|
||||
|
||||
return availableSize;
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
bool isHorizontal = IsHorizontal;
|
||||
|
||||
foreach (FrameworkElement element in base.Children)
|
||||
{
|
||||
if (element == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Size elementSize = element.DesiredSize;
|
||||
double x = 0.0;
|
||||
double y = 0.0;
|
||||
|
||||
switch (Placement)
|
||||
{
|
||||
case AxisPlacement.Left:
|
||||
x = finalSize.Width - elementSize.Width;
|
||||
break;
|
||||
case AxisPlacement.Right:
|
||||
x = 0;
|
||||
break;
|
||||
case AxisPlacement.Top:
|
||||
y = finalSize.Height - elementSize.Height;
|
||||
break;
|
||||
case AxisPlacement.Bottom:
|
||||
y = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
double coordinate = GetCoordinate(element);
|
||||
|
||||
if (!Double.IsNaN(GetEndCoordinate(element)))
|
||||
{
|
||||
double endCoordinate = GetEndCoordinate(element);
|
||||
double size = endCoordinate - coordinate;
|
||||
if (size < 0)
|
||||
{
|
||||
size = -size;
|
||||
coordinate -= size;
|
||||
}
|
||||
if (isHorizontal)
|
||||
elementSize.Width = size;
|
||||
else
|
||||
elementSize.Height = size;
|
||||
}
|
||||
|
||||
|
||||
// shift for common tick labels, not for major ones.
|
||||
if (isHorizontal)
|
||||
{
|
||||
x = coordinate;
|
||||
if (element.HorizontalAlignment == HorizontalAlignment.Center)
|
||||
x = coordinate - elementSize.Width / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (element.VerticalAlignment == VerticalAlignment.Center)
|
||||
y = coordinate - elementSize.Height / 2;
|
||||
else if (element.VerticalAlignment == VerticalAlignment.Bottom)
|
||||
y = coordinate - elementSize.Height;
|
||||
else if (element.VerticalAlignment == VerticalAlignment.Top)
|
||||
y = coordinate;
|
||||
}
|
||||
|
||||
Rect bounds = new Rect(new Point(x, y), elementSize);
|
||||
element.Arrange(bounds);
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Charts/Axes/TimeSpan/HorizontalTimeSpanAxis.cs
Normal file
27
Charts/Axes/TimeSpan/HorizontalTimeSpanAxis.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a horizontal axis with values of <see cref="TimeSpan"/> type.
|
||||
/// </summary>
|
||||
public class HorizontalTimeSpanAxis : TimeSpanAxis
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HorizontalTimeSpanAxis"/> class, placed on the bottom of <see cref="ChartPlotter"/>.
|
||||
/// </summary>
|
||||
public HorizontalTimeSpanAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Bottom;
|
||||
}
|
||||
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Left || newPlacement == AxisPlacement.Right)
|
||||
throw new ArgumentException(Strings.Exceptions.HorizontalAxisCannotBeVertical);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Charts/Axes/TimeSpan/MinorTimeSpanProvider.cs
Normal file
17
Charts/Axes/TimeSpan/MinorTimeSpanProvider.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal sealed class MinorTimeSpanTicksProvider : MinorTimeProviderBase<TimeSpan>
|
||||
{
|
||||
public MinorTimeSpanTicksProvider(ITicksProvider<TimeSpan> owner) : base(owner) { }
|
||||
|
||||
protected override bool IsInside(TimeSpan value, Range<TimeSpan> range)
|
||||
{
|
||||
return range.Min < value && value < range.Max;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Charts/Axes/TimeSpan/TimeSpanAxis.cs
Normal file
57
Charts/Axes/TimeSpan/TimeSpanAxis.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents axis with values of type TimeSpan.
|
||||
/// </summary>
|
||||
public class TimeSpanAxis : AxisBase<TimeSpan>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TimeSpanAxis"/> class with default values conversion.
|
||||
/// </summary>
|
||||
public TimeSpanAxis()
|
||||
: base(new TimeSpanAxisControl(),
|
||||
DoubleToTimeSpan, TimeSpanToDouble)
|
||||
{ }
|
||||
|
||||
private static readonly long minTicks = TimeSpan.MinValue.Ticks;
|
||||
private static readonly long maxTicks = TimeSpan.MaxValue.Ticks;
|
||||
private static TimeSpan DoubleToTimeSpan(double value)
|
||||
{
|
||||
long ticks = (long)(value * 10000000000L);
|
||||
|
||||
// todo should we throw an exception if number of ticks is too big or small?
|
||||
if (ticks < minTicks)
|
||||
ticks = minTicks;
|
||||
else if (ticks > maxTicks)
|
||||
ticks = maxTicks;
|
||||
|
||||
return new TimeSpan(ticks);
|
||||
}
|
||||
|
||||
private static double TimeSpanToDouble(TimeSpan time)
|
||||
{
|
||||
return time.Ticks / 10000000000.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets conversions of axis - functions used to convert values of axis type to and from double values of viewport.
|
||||
/// Sets both ConvertToDouble and ConvertFromDouble properties.
|
||||
/// </summary>
|
||||
/// <param name="min">The minimal viewport value.</param>
|
||||
/// <param name="minValue">The value of axis type, corresponding to minimal viewport value.</param>
|
||||
/// <param name="max">The maximal viewport value.</param>
|
||||
/// <param name="maxValue">The value of axis type, corresponding to maximal viewport value.</param>
|
||||
public override void SetConversion(double min, TimeSpan minValue, double max, TimeSpan maxValue)
|
||||
{
|
||||
var conversion = new TimeSpanToDoubleConversion(min, minValue, max, maxValue);
|
||||
|
||||
ConvertToDouble = conversion.ToDouble;
|
||||
ConvertFromDouble = conversion.FromDouble;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Charts/Axes/TimeSpan/TimeSpanAxisControl.cs
Normal file
20
Charts/Axes/TimeSpan/TimeSpanAxisControl.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class TimeSpanAxisControl : AxisControl<TimeSpan>
|
||||
{
|
||||
public TimeSpanAxisControl()
|
||||
{
|
||||
LabelProvider = new TimeSpanLabelProvider();
|
||||
TicksProvider = new TimeSpanTicksProvider();
|
||||
|
||||
ConvertToDouble = time => time.Ticks;
|
||||
|
||||
Range = new Range<TimeSpan>(new TimeSpan(), new TimeSpan(1, 0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Charts/Axes/TimeSpan/TimeSpanLabelProvider.cs
Normal file
33
Charts/Axes/TimeSpan/TimeSpanLabelProvider.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class TimeSpanLabelProvider : LabelProviderBase<TimeSpan>
|
||||
{
|
||||
public override UIElement[] CreateLabels(ITicksInfo<TimeSpan> ticksInfo)
|
||||
{
|
||||
object info = ticksInfo.Info;
|
||||
var ticks = ticksInfo.Ticks;
|
||||
|
||||
LabelTickInfo<TimeSpan> tickInfo = new LabelTickInfo<TimeSpan>();
|
||||
|
||||
UIElement[] res = new UIElement[ticks.Length];
|
||||
for (int i = 0; i < ticks.Length; i++)
|
||||
{
|
||||
tickInfo.Tick = ticks[i];
|
||||
tickInfo.Info = info;
|
||||
|
||||
string tickText = GetString(tickInfo);
|
||||
UIElement label = new TextBlock { Text = tickText, ToolTip = ticks[i] };
|
||||
res[i] = label;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Charts/Axes/TimeSpan/TimeSpanTicksProvider.cs
Normal file
34
Charts/Axes/TimeSpan/TimeSpanTicksProvider.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class TimeSpanTicksProvider : TimeTicksProviderBase<TimeSpan>
|
||||
{
|
||||
static TimeSpanTicksProvider()
|
||||
{
|
||||
Providers.Add(DifferenceIn.Year, new DayTimeSpanProvider());
|
||||
Providers.Add(DifferenceIn.Month, new DayTimeSpanProvider());
|
||||
Providers.Add(DifferenceIn.Day, new DayTimeSpanProvider());
|
||||
Providers.Add(DifferenceIn.Hour, new HourTimeSpanProvider());
|
||||
Providers.Add(DifferenceIn.Minute, new MinuteTimeSpanProvider());
|
||||
Providers.Add(DifferenceIn.Second, new SecondTimeSpanProvider());
|
||||
Providers.Add(DifferenceIn.Millisecond, new MillisecondTimeSpanProvider());
|
||||
|
||||
MinorProviders.Add(DifferenceIn.Year, new MinorTimeSpanTicksProvider(new DayTimeSpanProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Month, new MinorTimeSpanTicksProvider(new DayTimeSpanProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Day, new MinorTimeSpanTicksProvider(new DayTimeSpanProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Hour, new MinorTimeSpanTicksProvider(new HourTimeSpanProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Minute, new MinorTimeSpanTicksProvider(new MinuteTimeSpanProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Second, new MinorTimeSpanTicksProvider(new SecondTimeSpanProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Millisecond, new MinorTimeSpanTicksProvider(new MillisecondTimeSpanProvider()));
|
||||
}
|
||||
|
||||
protected override TimeSpan GetDifference(TimeSpan start, TimeSpan end)
|
||||
{
|
||||
return end - start;
|
||||
}
|
||||
}
|
||||
}
|
||||
319
Charts/Axes/TimeSpan/TimeSpanTicksProviderBase.cs
Normal file
319
Charts/Axes/TimeSpan/TimeSpanTicksProviderBase.cs
Normal file
@@ -0,0 +1,319 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal abstract class TimeSpanTicksProviderBase : TimePeriodTicksProvider<TimeSpan>
|
||||
{
|
||||
protected sealed override bool Continue(TimeSpan current, TimeSpan end)
|
||||
{
|
||||
return current < end;
|
||||
}
|
||||
|
||||
protected sealed override TimeSpan RoundDown(TimeSpan start, TimeSpan end)
|
||||
{
|
||||
return RoundDown(start, Difference);
|
||||
}
|
||||
|
||||
protected sealed override TimeSpan RoundUp(TimeSpan start, TimeSpan end)
|
||||
{
|
||||
return RoundUp(end, Difference);
|
||||
}
|
||||
|
||||
protected static TimeSpan Shift(TimeSpan span, DifferenceIn diff)
|
||||
{
|
||||
TimeSpan res = span;
|
||||
|
||||
TimeSpan shift = new TimeSpan();
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
case DifferenceIn.Month:
|
||||
case DifferenceIn.Day:
|
||||
shift = TimeSpan.FromDays(1);
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
shift = TimeSpan.FromHours(1);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
shift = TimeSpan.FromMinutes(1);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
shift = TimeSpan.FromSeconds(1);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
shift = TimeSpan.FromMilliseconds(1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
res = res.Add(shift);
|
||||
return res;
|
||||
}
|
||||
|
||||
protected sealed override TimeSpan RoundDown(TimeSpan timeSpan, DifferenceIn diff)
|
||||
{
|
||||
TimeSpan res = timeSpan;
|
||||
|
||||
if (timeSpan.Ticks < 0)
|
||||
{
|
||||
res = RoundUp(timeSpan.Duration(), diff).Negate();
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
case DifferenceIn.Month:
|
||||
case DifferenceIn.Day:
|
||||
res = TimeSpan.FromDays(timeSpan.Days);
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = TimeSpan.FromDays(timeSpan.Days).
|
||||
Add(TimeSpan.FromHours(timeSpan.Hours));
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = TimeSpan.FromDays(timeSpan.Days).
|
||||
Add(TimeSpan.FromHours(timeSpan.Hours)).
|
||||
Add(TimeSpan.FromMinutes(timeSpan.Minutes));
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = TimeSpan.FromDays(timeSpan.Days).
|
||||
Add(TimeSpan.FromHours(timeSpan.Hours)).
|
||||
Add(TimeSpan.FromMinutes(timeSpan.Minutes)).
|
||||
Add(TimeSpan.FromSeconds(timeSpan.Seconds));
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = timeSpan;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected sealed override TimeSpan RoundUp(TimeSpan dateTime, DifferenceIn diff)
|
||||
{
|
||||
TimeSpan res = RoundDown(dateTime, diff);
|
||||
res = Shift(res, diff);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected override List<TimeSpan> Trim(List<TimeSpan> ticks, Range<TimeSpan> range)
|
||||
{
|
||||
int startIndex = 0;
|
||||
for (int i = 0; i < ticks.Count - 1; i++)
|
||||
{
|
||||
if (ticks[i] <= range.Min && range.Min <= ticks[i + 1])
|
||||
{
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int endIndex = ticks.Count - 1;
|
||||
for (int i = ticks.Count - 1; i >= 1; i--)
|
||||
{
|
||||
if (ticks[i] >= range.Max && range.Max > ticks[i - 1])
|
||||
{
|
||||
endIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<TimeSpan> res = new List<TimeSpan>(endIndex - startIndex + 1);
|
||||
for (int i = startIndex; i <= endIndex; i++)
|
||||
{
|
||||
res.Add(ticks[i]);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected sealed override bool IsMinDate(TimeSpan dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DayTimeSpanProvider : TimeSpanTicksProviderBase
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Day;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 20, 10, 5, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(TimeSpan start, TimeSpan dt)
|
||||
{
|
||||
return (dt - start).Days;
|
||||
}
|
||||
|
||||
protected override TimeSpan GetStart(TimeSpan start, int value, int step)
|
||||
{
|
||||
double days = start.TotalDays;
|
||||
double newDays = ((int)(days / step)) * step;
|
||||
if (newDays > days) {
|
||||
newDays -= step;
|
||||
}
|
||||
return TimeSpan.FromDays(newDays);
|
||||
//return TimeSpan.FromDays(start.Days);
|
||||
}
|
||||
|
||||
protected override TimeSpan AddStep(TimeSpan dt, int step)
|
||||
{
|
||||
return dt.Add(TimeSpan.FromDays(step));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HourTimeSpanProvider : TimeSpanTicksProviderBase
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Hour;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 24, 12, 6, 4, 3, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(TimeSpan start, TimeSpan dt)
|
||||
{
|
||||
return (int)(dt - start).TotalHours;
|
||||
}
|
||||
|
||||
protected override TimeSpan GetStart(TimeSpan start, int value, int step)
|
||||
{
|
||||
double hours = start.TotalHours;
|
||||
double newHours = ((int)(hours / step)) * step;
|
||||
if (newHours > hours)
|
||||
{
|
||||
newHours -= step;
|
||||
}
|
||||
return TimeSpan.FromHours(newHours);
|
||||
//return TimeSpan.FromDays(start.Days);
|
||||
}
|
||||
|
||||
protected override TimeSpan AddStep(TimeSpan dt, int step)
|
||||
{
|
||||
return dt.Add(TimeSpan.FromHours(step));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MinuteTimeSpanProvider : TimeSpanTicksProviderBase
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Minute;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 60, 30, 20, 15, 10, 5, 4, 3, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(TimeSpan start, TimeSpan dt)
|
||||
{
|
||||
return (int)(dt - start).TotalMinutes;
|
||||
}
|
||||
|
||||
protected override TimeSpan GetStart(TimeSpan start, int value, int step)
|
||||
{
|
||||
double minutes = start.TotalMinutes;
|
||||
double newMinutes = ((int)(minutes / step)) * step;
|
||||
if (newMinutes > minutes)
|
||||
{
|
||||
newMinutes -= step;
|
||||
}
|
||||
|
||||
return TimeSpan.FromMinutes(newMinutes);
|
||||
}
|
||||
|
||||
protected override TimeSpan AddStep(TimeSpan dt, int step)
|
||||
{
|
||||
return dt.Add(TimeSpan.FromMinutes(step));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SecondTimeSpanProvider : TimeSpanTicksProviderBase
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Second;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 60, 30, 20, 15, 10, 5, 4, 3, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(TimeSpan start, TimeSpan dt)
|
||||
{
|
||||
return (int)(dt - start).TotalSeconds;
|
||||
}
|
||||
|
||||
protected override TimeSpan GetStart(TimeSpan start, int value, int step)
|
||||
{
|
||||
double seconds = start.TotalSeconds;
|
||||
double newSeconds = ((int)(seconds / step)) * step;
|
||||
if (newSeconds > seconds) {
|
||||
newSeconds -= step;
|
||||
}
|
||||
|
||||
return TimeSpan.FromSeconds(newSeconds);
|
||||
//return new TimeSpan(start.Days, start.Hours, start.Minutes, 0);
|
||||
}
|
||||
|
||||
protected override TimeSpan AddStep(TimeSpan dt, int step)
|
||||
{
|
||||
return dt.Add(TimeSpan.FromSeconds(step));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MillisecondTimeSpanProvider : TimeSpanTicksProviderBase
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Millisecond;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 100, 50, 40, 25, 20, 10, 5, 4, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(TimeSpan start, TimeSpan dt)
|
||||
{
|
||||
return (int)(dt - start).TotalMilliseconds;
|
||||
}
|
||||
|
||||
protected override TimeSpan GetStart(TimeSpan start, int value, int step)
|
||||
{
|
||||
double millis = start.TotalMilliseconds;
|
||||
double newMillis = ((int)(millis / step)) * step;
|
||||
if (newMillis > millis) {
|
||||
newMillis -= step;
|
||||
}
|
||||
|
||||
return TimeSpan.FromMilliseconds(newMillis);
|
||||
//return start;
|
||||
}
|
||||
|
||||
protected override TimeSpan AddStep(TimeSpan dt, int step)
|
||||
{
|
||||
return dt.Add(TimeSpan.FromMilliseconds(step));
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Charts/Axes/TimeSpan/TimeSpanToDoubleConversion.cs
Normal file
44
Charts/Axes/TimeSpan/TimeSpanToDoubleConversion.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal sealed class TimeSpanToDoubleConversion
|
||||
{
|
||||
public TimeSpanToDoubleConversion(TimeSpan minSpan, TimeSpan maxSpan)
|
||||
: this(0, minSpan, 1, maxSpan)
|
||||
{ }
|
||||
|
||||
public TimeSpanToDoubleConversion(double min, TimeSpan minSpan, double max, TimeSpan maxSpan)
|
||||
{
|
||||
this.min = min;
|
||||
this.length = max - min;
|
||||
this.ticksMin = minSpan.Ticks;
|
||||
this.ticksLength = maxSpan.Ticks - ticksMin;
|
||||
}
|
||||
|
||||
private double min;
|
||||
private double length;
|
||||
private long ticksMin;
|
||||
private long ticksLength;
|
||||
|
||||
internal TimeSpan FromDouble(double d)
|
||||
{
|
||||
double ratio = (d - min) / length;
|
||||
long ticks = (long)(ticksMin + ticksLength * ratio);
|
||||
|
||||
ticks = MathHelper.Clamp(ticks, TimeSpan.MinValue.Ticks, TimeSpan.MaxValue.Ticks);
|
||||
|
||||
return new TimeSpan(ticks);
|
||||
}
|
||||
|
||||
internal double ToDouble(TimeSpan span)
|
||||
{
|
||||
double ratio = (span.Ticks - ticksMin) / (double)ticksLength;
|
||||
return min + ratio * length;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
159
Charts/Axes/TimeSpan/TimeTicksProviderBase.cs
Normal file
159
Charts/Axes/TimeSpan/TimeTicksProviderBase.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public abstract class TimeTicksProviderBase<T> : ITicksProvider<T>
|
||||
{
|
||||
public event EventHandler Changed;
|
||||
protected void RaiseChanged()
|
||||
{
|
||||
if (Changed != null)
|
||||
{
|
||||
Changed(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Dictionary<DifferenceIn, ITicksProvider<T>> providers =
|
||||
new Dictionary<DifferenceIn, ITicksProvider<T>>();
|
||||
|
||||
protected static Dictionary<DifferenceIn, ITicksProvider<T>> Providers
|
||||
{
|
||||
get { return TimeTicksProviderBase<T>.providers; }
|
||||
}
|
||||
|
||||
private static readonly Dictionary<DifferenceIn, ITicksProvider<T>> minorProviders =
|
||||
new Dictionary<DifferenceIn, ITicksProvider<T>>();
|
||||
|
||||
protected static Dictionary<DifferenceIn, ITicksProvider<T>> MinorProviders
|
||||
{
|
||||
get { return TimeTicksProviderBase<T>.minorProviders; }
|
||||
}
|
||||
|
||||
protected abstract TimeSpan GetDifference(T start, T end);
|
||||
|
||||
#region ITicksProvider<T> Members
|
||||
|
||||
private IDateTimeTicksStrategy strategy = new DefaultDateTimeTicksStrategy();
|
||||
public IDateTimeTicksStrategy Strategy
|
||||
{
|
||||
get { return strategy; }
|
||||
set
|
||||
{
|
||||
if (strategy != value)
|
||||
{
|
||||
strategy = value;
|
||||
RaiseChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ITicksInfo<T> result;
|
||||
private DifferenceIn diff;
|
||||
|
||||
public ITicksInfo<T> GetTicks(Range<T> range, int ticksCount)
|
||||
{
|
||||
Verify.IsTrue(ticksCount > 0);
|
||||
|
||||
T start = range.Min;
|
||||
T end = range.Max;
|
||||
TimeSpan length = GetDifference(start, end);
|
||||
|
||||
diff = strategy.GetDifference(length);
|
||||
|
||||
TicksInfo<T> result = new TicksInfo<T> { Info = diff };
|
||||
if (providers.ContainsKey(diff))
|
||||
{
|
||||
ITicksInfo<T> innerResult = providers[diff].GetTicks(range, ticksCount);
|
||||
T[] ticks = ModifyTicksGuard(innerResult.Ticks, diff);
|
||||
|
||||
result.Ticks = ticks;
|
||||
this.result = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(Strings.Exceptions.UnsupportedRangeInAxis);
|
||||
}
|
||||
|
||||
private T[] ModifyTicksGuard(T[] ticks, object info)
|
||||
{
|
||||
var result = ModifyTicks(ticks, info);
|
||||
if (result == null)
|
||||
throw new ArgumentNullException("ticks");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected virtual T[] ModifyTicks(T[] ticks, object info)
|
||||
{
|
||||
return ticks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decreases the tick count.
|
||||
/// </summary>
|
||||
/// <param name="tickCount">The tick count.</param>
|
||||
/// <returns></returns>
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
if (providers.ContainsKey(diff))
|
||||
return providers[diff].DecreaseTickCount(ticksCount);
|
||||
|
||||
int res = ticksCount / 2;
|
||||
if (res < 2) res = 2;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases the tick count.
|
||||
/// </summary>
|
||||
/// <param name="ticksCount">The tick count.</param>
|
||||
/// <returns></returns>
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
DebugVerify.Is(ticksCount < 2000);
|
||||
|
||||
if (providers.ContainsKey(diff))
|
||||
return providers[diff].IncreaseTickCount(ticksCount);
|
||||
|
||||
return ticksCount * 2;
|
||||
}
|
||||
|
||||
public ITicksProvider<T> MinorProvider
|
||||
{
|
||||
get
|
||||
{
|
||||
DifferenceIn smallerDiff = DifferenceIn.Smallest;
|
||||
if (strategy.TryGetLowerDiff(diff, out smallerDiff) && minorProviders.ContainsKey(smallerDiff))
|
||||
{
|
||||
var minorProvider = (MinorTimeProviderBase<T>)minorProviders[smallerDiff];
|
||||
minorProvider.SetTicks(result.Ticks);
|
||||
return minorProvider;
|
||||
}
|
||||
|
||||
return null;
|
||||
// todo What to do if this already is the smallest provider?
|
||||
}
|
||||
}
|
||||
|
||||
public ITicksProvider<T> MajorProvider
|
||||
{
|
||||
get
|
||||
{
|
||||
DifferenceIn biggerDiff = DifferenceIn.Smallest;
|
||||
if (strategy.TryGetBiggerDiff(diff, out biggerDiff))
|
||||
{
|
||||
return providers[biggerDiff];
|
||||
}
|
||||
|
||||
return null;
|
||||
// todo What to do if this already is the biggest provider?
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
32
Charts/Axes/TimeSpan/VerticalTimeSpanAxis.cs
Normal file
32
Charts/Axes/TimeSpan/VerticalTimeSpanAxis.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a vertical axis with values of <see cref="TimeSpan"/> type.
|
||||
/// </summary>
|
||||
public class VerticalTimeSpanAxis : TimeSpanAxis
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VerticalTimeSpanAxis"/> class, placed (by default) on the left side of <see cref="ChartPlotter"/>.
|
||||
/// </summary>
|
||||
public VerticalTimeSpanAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the placement - e.g., vertical axis should not be placed from top or bottom, etc.
|
||||
/// If proposed placement is wrong, throws an ArgumentException.
|
||||
/// </summary>
|
||||
/// <param name="newPlacement">The new placement.</param>
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Bottom || newPlacement == AxisPlacement.Top)
|
||||
throw new ArgumentException(Strings.Exceptions.VerticalAxisCannotBeHorizontal);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Charts/BackgroundRenderer.cs
Normal file
53
Charts/BackgroundRenderer.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public static class BackgroundRenderer
|
||||
{
|
||||
public static readonly RoutedEvent RenderingFinished = EventManager.RegisterRoutedEvent(
|
||||
"RenderingFinished",
|
||||
RoutingStrategy.Bubble,
|
||||
typeof(RoutedEventHandler),
|
||||
typeof(BackgroundRenderer));
|
||||
|
||||
public static void RaiseRenderingFinished(FrameworkElement eventSource)
|
||||
{
|
||||
eventSource.RaiseEvent(new RoutedEventArgs(RenderingFinished));
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent UpdateRequested = EventManager.RegisterRoutedEvent(
|
||||
"UpdateRequested",
|
||||
RoutingStrategy.Bubble,
|
||||
typeof(RoutedEventHandler),
|
||||
typeof(BackgroundRenderer));
|
||||
|
||||
public static void RaiseUpdateRequested(FrameworkElement eventSource)
|
||||
{
|
||||
eventSource.RaiseEvent(new RoutedEventArgs(UpdateRequested));
|
||||
}
|
||||
|
||||
#region UsesBackgroundRendering
|
||||
|
||||
public static bool GetUsesBackgroundRendering(DependencyObject obj)
|
||||
{
|
||||
return (bool)obj.GetValue(UsesBackgroundRenderingProperty);
|
||||
}
|
||||
|
||||
public static void SetUsesBackgroundRendering(DependencyObject obj, bool value)
|
||||
{
|
||||
obj.SetValue(UsesBackgroundRenderingProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty UsesBackgroundRenderingProperty = DependencyProperty.RegisterAttached(
|
||||
"UsesBackgroundRendering",
|
||||
typeof(bool),
|
||||
typeof(BackgroundRenderer),
|
||||
new FrameworkPropertyMetadata(false));
|
||||
|
||||
#endregion // end of UsesBackgroundRendering
|
||||
}
|
||||
}
|
||||
341
Charts/BitmapBasedGraph.cs
Normal file
341
Charts/BitmapBasedGraph.cs
Normal file
@@ -0,0 +1,341 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
//using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using Microsoft.Research.DynamicDataDisplay.DataSources;
|
||||
using Microsoft.Research.DynamicDataDisplay.PointMarkers;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Shapes;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay
|
||||
{
|
||||
public abstract class BitmapBasedGraph : ViewportElement2D
|
||||
{
|
||||
static BitmapBasedGraph()
|
||||
{
|
||||
Type thisType = typeof(BitmapBasedGraph);
|
||||
BackgroundRenderer.UsesBackgroundRenderingProperty.OverrideMetadata(thisType, new FrameworkPropertyMetadata(true));
|
||||
}
|
||||
|
||||
private int nextRequestId = 0;
|
||||
|
||||
/// <summary>Latest complete request</summary>
|
||||
private RenderRequest completedRequest = null;
|
||||
|
||||
/// <summary>Currently running request</summary>
|
||||
private RenderRequest activeRequest = null;
|
||||
|
||||
/// <summary>Result of latest complete request</summary>
|
||||
private BitmapSource completedBitmap = null;
|
||||
|
||||
/// <summary>Pending render request</summary>
|
||||
private RenderRequest pendingRequest = null;
|
||||
|
||||
/// <summary>Single apartment thread used for background rendering</summary>
|
||||
/// <remarks>STA is required for creating WPF components in this thread</remarks>
|
||||
private Thread renderThread = null;
|
||||
|
||||
private AutoResetEvent renderRequested = new AutoResetEvent(false);
|
||||
|
||||
private ManualResetEvent shutdownRequested = new ManualResetEvent(false);
|
||||
|
||||
/// <summary>True means that current bitmap is invalidated and is to be re-rendered.</summary>
|
||||
private bool bitmapInvalidated = true;
|
||||
|
||||
/// <summary>Shows tooltips.</summary>
|
||||
private PopupTip popup;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MarkerPointsGraph"/> class.
|
||||
/// </summary>
|
||||
public BitmapBasedGraph()
|
||||
{
|
||||
ManualTranslate = true;
|
||||
}
|
||||
|
||||
protected virtual UIElement GetTooltipForPoint(Point point, DataRect visible, Rect output)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected PopupTip GetPopupTipWindow()
|
||||
{
|
||||
if (popup != null)
|
||||
return popup;
|
||||
|
||||
foreach (var item in Plotter.Children)
|
||||
{
|
||||
if (item is ViewportUIContainer)
|
||||
{
|
||||
ViewportUIContainer container = (ViewportUIContainer)item;
|
||||
if (container.Content is PopupTip)
|
||||
return popup = (PopupTip)container.Content;
|
||||
}
|
||||
}
|
||||
|
||||
popup = new PopupTip();
|
||||
popup.Placement = PlacementMode.Relative;
|
||||
popup.PlacementTarget = this;
|
||||
Plotter.Children.Add(popup);
|
||||
return popup;
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(System.Windows.Input.MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseMove(e);
|
||||
|
||||
var popup = GetPopupTipWindow();
|
||||
if (popup.IsOpen)
|
||||
popup.Hide();
|
||||
|
||||
if (bitmapInvalidated) return;
|
||||
|
||||
Point p = e.GetPosition(this);
|
||||
Point dp = p.ScreenToData(Plotter2D.Transform);
|
||||
|
||||
var tooltip = GetTooltipForPoint(p, completedRequest.Visible, completedRequest.Output);
|
||||
if (tooltip == null) return;
|
||||
|
||||
popup.VerticalOffset = p.Y + 20;
|
||||
popup.HorizontalOffset = p.X;
|
||||
|
||||
popup.ShowDelayed(new TimeSpan(0, 0, 1));
|
||||
|
||||
Grid grid = new Grid();
|
||||
|
||||
Rectangle rect = new Rectangle
|
||||
{
|
||||
Stroke = Brushes.Black,
|
||||
Fill = SystemColors.InfoBrush
|
||||
};
|
||||
|
||||
StackPanel sp = new StackPanel();
|
||||
sp.Orientation = Orientation.Vertical;
|
||||
sp.Children.Add(tooltip);
|
||||
sp.Margin = new Thickness(4, 2, 4, 2);
|
||||
|
||||
var tb = new TextBlock();
|
||||
tb.Text = String.Format("Location: {0:F2}, {1:F2}", dp.X, dp.Y);
|
||||
tb.Foreground = SystemColors.GrayTextBrush;
|
||||
sp.Children.Add(tb);
|
||||
|
||||
grid.Children.Add(rect);
|
||||
grid.Children.Add(sp);
|
||||
grid.Measure(SizeHelper.CreateInfiniteSize());
|
||||
popup.Child = grid;
|
||||
}
|
||||
|
||||
protected override void OnMouseLeave(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseLeave(e);
|
||||
GetPopupTipWindow().Hide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a render task and invalidates visual.
|
||||
/// </summary>
|
||||
public void UpdateVisualization()
|
||||
{
|
||||
if (Viewport == null) return;
|
||||
|
||||
Rect output = new Rect(this.RenderSize);
|
||||
CreateRenderTask(Viewport.Visible, output);
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
protected override void OnVisibleChanged(DataRect newRect, DataRect oldRect)
|
||||
{
|
||||
base.OnVisibleChanged(newRect, oldRect);
|
||||
CreateRenderTask(newRect, Viewport.Output);
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
|
||||
{
|
||||
base.OnRenderSizeChanged(sizeInfo);
|
||||
CreateRenderTask(Viewport.Visible, new Rect(sizeInfo.NewSize));
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
protected abstract BitmapSource RenderFrame(DataRect visible, Rect output);
|
||||
|
||||
private void RenderThreadFunc()
|
||||
{
|
||||
WaitHandle[] events = new WaitHandle[] { renderRequested, shutdownRequested };
|
||||
while (true)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
activeRequest = null;
|
||||
if (pendingRequest != null)
|
||||
{
|
||||
activeRequest = pendingRequest;
|
||||
pendingRequest = null;
|
||||
}
|
||||
}
|
||||
if (activeRequest == null)
|
||||
{
|
||||
WaitHandle.WaitAny(events);
|
||||
if (shutdownRequested.WaitOne(0))
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
BitmapSource result = (BitmapSource)RenderFrame(activeRequest.Visible, activeRequest.Output);
|
||||
if (result != null)
|
||||
Dispatcher.BeginInvoke(
|
||||
new RenderCompletionHandler(OnRenderCompleted),
|
||||
new RenderResult(activeRequest, result));
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
Trace.WriteLine(String.Format("RenderRequest {0} failed: {1}", activeRequest.RequestID, exc.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateRenderTask(DataRect visible, Rect output)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
bitmapInvalidated = true;
|
||||
|
||||
if (activeRequest != null)
|
||||
activeRequest.Cancel();
|
||||
pendingRequest = new RenderRequest(nextRequestId++, visible, output);
|
||||
renderRequested.Set();
|
||||
}
|
||||
if (renderThread == null)
|
||||
{
|
||||
renderThread = new Thread(RenderThreadFunc);
|
||||
renderThread.IsBackground = true;
|
||||
renderThread.SetApartmentState(ApartmentState.STA);
|
||||
renderThread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private delegate void RenderCompletionHandler(RenderResult result);
|
||||
|
||||
protected virtual void OnRenderCompleted(RenderResult result)
|
||||
{
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
completedRequest = result.Request;
|
||||
completedBitmap = result.CreateBitmap();
|
||||
bitmapInvalidated = false;
|
||||
|
||||
InvalidateVisual();
|
||||
BackgroundRenderer.RaiseRenderingFinished(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnRenderCore(DrawingContext dc, RenderState state)
|
||||
{
|
||||
if (completedRequest != null && completedBitmap != null)
|
||||
dc.DrawImage(completedBitmap, completedRequest.Visible.ViewportToScreen(Viewport.Transform));
|
||||
}
|
||||
}
|
||||
|
||||
public class RenderRequest
|
||||
{
|
||||
private int requestId;
|
||||
private DataRect visible;
|
||||
private Rect output;
|
||||
private int cancelling;
|
||||
|
||||
public RenderRequest(int requestId, DataRect visible, Rect output)
|
||||
{
|
||||
this.requestId = requestId;
|
||||
this.visible = visible;
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
public int RequestID
|
||||
{
|
||||
get { return requestId; }
|
||||
}
|
||||
|
||||
public DataRect Visible
|
||||
{
|
||||
get { return visible; }
|
||||
}
|
||||
|
||||
public Rect Output
|
||||
{
|
||||
get { return output; }
|
||||
}
|
||||
|
||||
public bool IsCancellingRequested
|
||||
{
|
||||
get { return cancelling > 0; }
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
Interlocked.Increment(ref cancelling);
|
||||
}
|
||||
}
|
||||
|
||||
public class RenderResult
|
||||
{
|
||||
private RenderRequest request;
|
||||
private int pixelWidth, pixelHeight, stride;
|
||||
private double dpiX, dpiY;
|
||||
private BitmapPalette palette;
|
||||
private PixelFormat format;
|
||||
private Array pixels;
|
||||
|
||||
/// <summary>Constructs successul rendering result</summary>
|
||||
/// <param name="request">Source request</param>
|
||||
/// <param name="result">Rendered bitmap</param>
|
||||
public RenderResult(RenderRequest request, BitmapSource result)
|
||||
{
|
||||
this.request = request;
|
||||
pixelWidth = result.PixelWidth;
|
||||
pixelHeight = result.PixelHeight;
|
||||
stride = result.PixelWidth * result.Format.BitsPerPixel / 8;
|
||||
dpiX = result.DpiX;
|
||||
dpiY = result.DpiY;
|
||||
palette = result.Palette;
|
||||
format = result.Format;
|
||||
pixels = new byte[pixelHeight * stride];
|
||||
result.CopyPixels(pixels, stride, 0);
|
||||
}
|
||||
|
||||
public RenderRequest Request
|
||||
{
|
||||
get
|
||||
{
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSuccess
|
||||
{
|
||||
get
|
||||
{
|
||||
return pixels != null;
|
||||
}
|
||||
}
|
||||
|
||||
public BitmapSource CreateBitmap()
|
||||
{
|
||||
return BitmapFrame.Create(pixelWidth, pixelHeight, dpiX, dpiY,
|
||||
format, palette, pixels, stride);
|
||||
}
|
||||
}
|
||||
}
|
||||
89
Charts/ContentGraph.cs
Normal file
89
Charts/ContentGraph.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public abstract class ContentGraph : ContentControl, IPlotterElement
|
||||
{
|
||||
static ContentGraph()
|
||||
{
|
||||
EventManager.RegisterClassHandler(typeof(ContentGraph), Plotter.PlotterChangedEvent, new PlotterChangedEventHandler(OnPlotterChanged));
|
||||
}
|
||||
|
||||
private static void OnPlotterChanged(object sender, PlotterChangedEventArgs e)
|
||||
{
|
||||
ContentGraph owner = (ContentGraph)sender;
|
||||
owner.OnPlotterChanged(e);
|
||||
}
|
||||
|
||||
private void OnPlotterChanged(PlotterChangedEventArgs e)
|
||||
{
|
||||
if (plotter == null && e.CurrentPlotter != null)
|
||||
{
|
||||
plotter = (Plotter2D)e.CurrentPlotter;
|
||||
plotter.Viewport.PropertyChanged += Viewport_PropertyChanged;
|
||||
OnPlotterAttached();
|
||||
}
|
||||
if (plotter != null && e.PreviousPlotter != null)
|
||||
{
|
||||
OnPlotterDetaching();
|
||||
plotter.Viewport.PropertyChanged -= Viewport_PropertyChanged;
|
||||
plotter = null;
|
||||
}
|
||||
}
|
||||
|
||||
#region IPlotterElement Members
|
||||
|
||||
private void Viewport_PropertyChanged(object sender, ExtendedPropertyChangedEventArgs e)
|
||||
{
|
||||
OnViewportPropertyChanged(e);
|
||||
}
|
||||
|
||||
protected virtual void OnViewportPropertyChanged(ExtendedPropertyChangedEventArgs e) { }
|
||||
|
||||
protected virtual Panel HostPanel
|
||||
{
|
||||
get { return plotter.CentralGrid; }
|
||||
}
|
||||
|
||||
void IPlotterElement.OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
this.plotter = (Plotter2D)plotter;
|
||||
HostPanel.Children.Add(this);
|
||||
this.plotter.Viewport.PropertyChanged += Viewport_PropertyChanged;
|
||||
|
||||
OnPlotterAttached();
|
||||
}
|
||||
|
||||
protected virtual void OnPlotterAttached() { }
|
||||
|
||||
void IPlotterElement.OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
OnPlotterDetaching();
|
||||
|
||||
this.plotter.Viewport.PropertyChanged -= Viewport_PropertyChanged;
|
||||
HostPanel.Children.Remove(this);
|
||||
this.plotter = null;
|
||||
}
|
||||
|
||||
protected virtual void OnPlotterDetaching() { }
|
||||
|
||||
private Plotter2D plotter;
|
||||
protected Plotter2D Plotter2D
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
Plotter IPlotterElement.Plotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
361
Charts/DataFollowChart.cs
Normal file
361
Charts/DataFollowChart.cs
Normal file
@@ -0,0 +1,361 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.DataSearch;
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Markup;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a marker with position.X bound to mouse cursor's position and position.Y is determined by interpolation of <see cref="MarkerPointsGraph"/>'s points.
|
||||
/// </summary>
|
||||
[ContentProperty("MarkerTemplate")]
|
||||
public class DataFollowChart : ViewportHostPanel, INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataFollowChart"/> class.
|
||||
/// </summary>
|
||||
public DataFollowChart()
|
||||
{
|
||||
Marker = CreateDefaultMarker();
|
||||
SetX(marker, 0);
|
||||
SetY(marker, 0);
|
||||
Children.Add(marker);
|
||||
}
|
||||
|
||||
private static Ellipse CreateDefaultMarker()
|
||||
{
|
||||
return new Ellipse
|
||||
{
|
||||
Width = 10,
|
||||
Height = 10,
|
||||
Stroke = Brushes.Green,
|
||||
StrokeThickness = 1,
|
||||
Fill = Brushes.LightGreen,
|
||||
Visibility = Visibility.Hidden
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataFollowChart"/> class, bound to specified <see cref="PointsGraphBase"/>.
|
||||
/// </summary>
|
||||
/// <param name="pointSource">The point source.</param>
|
||||
public DataFollowChart(PointsGraphBase pointSource)
|
||||
: this()
|
||||
{
|
||||
PointSource = pointSource;
|
||||
}
|
||||
|
||||
#region MarkerTemplate property
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the template, used to create a marker. This is a dependency property.
|
||||
/// </summary>
|
||||
/// <value>The marker template.</value>
|
||||
public DataTemplate MarkerTemplate
|
||||
{
|
||||
get { return (DataTemplate)GetValue(MarkerTemplateProperty); }
|
||||
set { SetValue(MarkerTemplateProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="MarkerTemplate"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty MarkerTemplateProperty = DependencyProperty.Register(
|
||||
"MarkerTemplate",
|
||||
typeof(DataTemplate),
|
||||
typeof(DataFollowChart),
|
||||
new FrameworkPropertyMetadata(null, OnMarkerTemplateChanged));
|
||||
|
||||
private static void OnMarkerTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
DataFollowChart chart = (DataFollowChart)d;
|
||||
DataTemplate template = (DataTemplate)e.NewValue;
|
||||
|
||||
FrameworkElement marker;
|
||||
if (template != null)
|
||||
{
|
||||
marker = (FrameworkElement)template.LoadContent();
|
||||
}
|
||||
else
|
||||
{
|
||||
marker = CreateDefaultMarker();
|
||||
}
|
||||
|
||||
chart.Children.Remove(chart.marker);
|
||||
chart.Marker = marker;
|
||||
chart.Children.Add(marker);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Point sources
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the source of points.
|
||||
/// Can be null.
|
||||
/// </summary>
|
||||
/// <value>The point source.</value>
|
||||
public PointsGraphBase PointSource
|
||||
{
|
||||
get { return (PointsGraphBase)GetValue(PointSourceProperty); }
|
||||
set { SetValue(PointSourceProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="PointSource"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PointSourceProperty = DependencyProperty.Register(
|
||||
"PointSource",
|
||||
typeof(PointsGraphBase),
|
||||
typeof(DataFollowChart),
|
||||
new FrameworkPropertyMetadata(null, OnPointSourceChanged));
|
||||
|
||||
private static void OnPointSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
DataFollowChart chart = (DataFollowChart)d;
|
||||
|
||||
PointsGraphBase previous = e.OldValue as PointsGraphBase;
|
||||
if (previous != null)
|
||||
{
|
||||
previous.VisiblePointsChanged -= chart.Source_VisiblePointsChanged;
|
||||
}
|
||||
|
||||
PointsGraphBase current = e.NewValue as PointsGraphBase;
|
||||
if (current != null)
|
||||
{
|
||||
current.ProvideVisiblePoints = true;
|
||||
current.VisiblePointsChanged += chart.Source_VisiblePointsChanged;
|
||||
if (current.VisiblePoints != null)
|
||||
{
|
||||
chart.searcher = new SortedXSearcher1d(current.VisiblePoints);
|
||||
}
|
||||
}
|
||||
|
||||
chart.UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
private SearchResult1d searchResult = SearchResult1d.Empty;
|
||||
private SortedXSearcher1d searcher;
|
||||
private FrameworkElement marker;
|
||||
|
||||
[NotNull]
|
||||
public FrameworkElement Marker
|
||||
{
|
||||
get { return marker; }
|
||||
protected set
|
||||
{
|
||||
marker = value;
|
||||
marker.DataContext = followDataContext;
|
||||
PropertyChanged.Raise(this, "Marker");
|
||||
}
|
||||
}
|
||||
|
||||
private FollowDataContext followDataContext = new FollowDataContext();
|
||||
public FollowDataContext FollowDataContext
|
||||
{
|
||||
get { return followDataContext; }
|
||||
}
|
||||
|
||||
private void UpdateUIRepresentation()
|
||||
{
|
||||
if (Plotter == null)
|
||||
return;
|
||||
|
||||
PointsGraphBase source = this.PointSource;
|
||||
|
||||
if (source == null || (source != null && source.VisiblePoints == null))
|
||||
{
|
||||
SetValue(MarkerPositionPropertyKey, new Point(Double.NaN, Double.NaN));
|
||||
marker.Visibility = Visibility.Hidden;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Point mousePos = Mouse.GetPosition(Plotter.CentralGrid);
|
||||
|
||||
var transform = Plotter.Transform;
|
||||
Point viewportPos = mousePos.ScreenToViewport(transform);
|
||||
|
||||
double x = viewportPos.X;
|
||||
searchResult = searcher.SearchXBetween(x, searchResult);
|
||||
SetValue(ClosestPointIndexPropertyKey, searchResult.Index);
|
||||
if (!searchResult.IsEmpty)
|
||||
{
|
||||
marker.Visibility = Visibility.Visible;
|
||||
|
||||
IList<Point> points = source.VisiblePoints;
|
||||
Point ptBefore = points[searchResult.Index];
|
||||
Point ptAfter = points[searchResult.Index + 1];
|
||||
|
||||
double ratio = (x - ptBefore.X) / (ptAfter.X - ptBefore.X);
|
||||
double y = ptBefore.Y + (ptAfter.Y - ptBefore.Y) * ratio;
|
||||
|
||||
Point temp = new Point(x, y);
|
||||
SetX(marker, temp.X);
|
||||
SetY(marker, temp.Y);
|
||||
|
||||
Point markerPosition = temp;
|
||||
followDataContext.Position = markerPosition;
|
||||
SetValue(MarkerPositionPropertyKey, markerPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetValue(MarkerPositionPropertyKey, new Point(Double.NaN, Double.NaN));
|
||||
marker.Visibility = Visibility.Hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region ClosestPointIndex property
|
||||
|
||||
private static readonly DependencyPropertyKey ClosestPointIndexPropertyKey = DependencyProperty.RegisterReadOnly(
|
||||
"ClosestPointIndex",
|
||||
typeof(int),
|
||||
typeof(DataFollowChart),
|
||||
new PropertyMetadata(-1)
|
||||
);
|
||||
|
||||
public static readonly DependencyProperty ClosestPointIndexProperty = ClosestPointIndexPropertyKey.DependencyProperty;
|
||||
|
||||
public int ClosestPointIndex
|
||||
{
|
||||
get { return (int)GetValue(ClosestPointIndexProperty); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MarkerPositionProperty
|
||||
|
||||
private static readonly DependencyPropertyKey MarkerPositionPropertyKey = DependencyProperty.RegisterReadOnly(
|
||||
"MarkerPosition",
|
||||
typeof(Point),
|
||||
typeof(DataFollowChart),
|
||||
new PropertyMetadata(new Point(), OnMarkerPositionChanged));
|
||||
|
||||
private static void OnMarkerPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
DataFollowChart chart = (DataFollowChart)d;
|
||||
chart.MarkerPositionChanged.Raise(chart);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="MarkerPosition"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty MarkerPositionProperty = MarkerPositionPropertyKey.DependencyProperty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the marker position.
|
||||
/// </summary>
|
||||
/// <value>The marker position.</value>
|
||||
public Point MarkerPosition
|
||||
{
|
||||
get { return (Point)GetValue(MarkerPositionProperty); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when marker position changes.
|
||||
/// </summary>
|
||||
public event EventHandler MarkerPositionChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
public override void OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
base.OnPlotterAttached(plotter);
|
||||
plotter.MainGrid.MouseMove += MainGrid_MouseMove;
|
||||
}
|
||||
|
||||
private void MainGrid_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
public override void OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
plotter.MainGrid.MouseMove -= MainGrid_MouseMove;
|
||||
base.OnPlotterDetaching(plotter);
|
||||
}
|
||||
|
||||
protected override void Viewport_PropertyChanged(object sender, ExtendedPropertyChangedEventArgs e)
|
||||
{
|
||||
base.Viewport_PropertyChanged(sender, e);
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
private void Source_VisiblePointsChanged(object sender, EventArgs e)
|
||||
{
|
||||
PointsGraphBase source = (PointsGraphBase)sender;
|
||||
if (source.VisiblePoints != null)
|
||||
{
|
||||
searcher = new SortedXSearcher1d(source.VisiblePoints);
|
||||
}
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region INotifyPropertyChanged Members
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a property value changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a special data context, which encapsulates marker's position and custom data.
|
||||
/// Used in <see cref="DataFollowChart"/>.
|
||||
/// </summary>
|
||||
public class FollowDataContext : INotifyPropertyChanged
|
||||
{
|
||||
private Point position;
|
||||
/// <summary>
|
||||
/// Gets or sets the position of marker.
|
||||
/// </summary>
|
||||
/// <value>The position.</value>
|
||||
public Point Position
|
||||
{
|
||||
get { return position; }
|
||||
set
|
||||
{
|
||||
position = value;
|
||||
PropertyChanged.Raise(this, "Position");
|
||||
}
|
||||
}
|
||||
|
||||
private object data;
|
||||
/// <summary>
|
||||
/// Gets or sets the additional custom data.
|
||||
/// </summary>
|
||||
/// <value>The data.</value>
|
||||
public object Data
|
||||
{
|
||||
get { return data; }
|
||||
set
|
||||
{
|
||||
data = value;
|
||||
PropertyChanged.Raise(this, "Data");
|
||||
}
|
||||
}
|
||||
|
||||
#region INotifyPropertyChanged Members
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a property value changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
45
Charts/DataSource2dContext.cs
Normal file
45
Charts/DataSource2dContext.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
// todo probably remove
|
||||
public sealed class DataSource2dContext : DependencyObject
|
||||
{
|
||||
public static DataRect GetVisibleRect(DependencyObject obj)
|
||||
{
|
||||
return (DataRect)obj.GetValue(VisibleRectProperty);
|
||||
}
|
||||
|
||||
public static void SetVisibleRect(DependencyObject obj, DataRect value)
|
||||
{
|
||||
obj.SetValue(VisibleRectProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty VisibleRectProperty = DependencyProperty.RegisterAttached(
|
||||
"VisibleRect",
|
||||
typeof(DataRect),
|
||||
typeof(DataSource2dContext),
|
||||
new FrameworkPropertyMetadata(new DataRect()));
|
||||
|
||||
public static Rect GetScreenRect(DependencyObject obj)
|
||||
{
|
||||
return (Rect)obj.GetValue(ScreenRectProperty);
|
||||
}
|
||||
|
||||
public static void SetScreenRect(DependencyObject obj, Rect value)
|
||||
{
|
||||
obj.SetValue(ScreenRectProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ScreenRectProperty = DependencyProperty.RegisterAttached(
|
||||
"ScreenRect",
|
||||
typeof(Rect),
|
||||
typeof(DataSource2dContext),
|
||||
new FrameworkPropertyMetadata(new Rect()));
|
||||
}
|
||||
}
|
||||
|
||||
61
Charts/DebugMenu.cs
Normal file
61
Charts/DebugMenu.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a menu that appears in Debug version of DynamicDataDisplay.
|
||||
/// </summary>
|
||||
public class DebugMenu : IPlotterElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DebugMenu"/> class.
|
||||
/// </summary>
|
||||
public DebugMenu()
|
||||
{
|
||||
Panel.SetZIndex(menu, 1);
|
||||
}
|
||||
|
||||
private Plotter plotter;
|
||||
private readonly Menu menu = new Menu
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Margin = new Thickness(3)
|
||||
};
|
||||
public Menu Menu
|
||||
{
|
||||
get { return menu; }
|
||||
}
|
||||
|
||||
public MenuItem TryFindMenuItem(string itemName)
|
||||
{
|
||||
return menu.Items.OfType<MenuItem>().Where(item => item.Header.Equals(itemName)).FirstOrDefault();
|
||||
}
|
||||
|
||||
#region IPlotterElement Members
|
||||
|
||||
public void OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
this.plotter = plotter;
|
||||
plotter.CentralGrid.Children.Add(menu);
|
||||
}
|
||||
|
||||
public void OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
plotter.CentralGrid.Children.Remove(menu);
|
||||
this.plotter = null;
|
||||
}
|
||||
|
||||
public Plotter Plotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
136
Charts/FakePointList.cs
Normal file
136
Charts/FakePointList.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay
|
||||
{
|
||||
[DebuggerDisplay("Count = {Count}")]
|
||||
public sealed class FakePointList : IList<Point> {
|
||||
private int first;
|
||||
private int last;
|
||||
private int count;
|
||||
private Point startPoint;
|
||||
private bool hasPoints;
|
||||
|
||||
private double leftBound;
|
||||
private double rightBound;
|
||||
private readonly List<Point> points;
|
||||
|
||||
internal FakePointList(List<Point> points, double left, double right) {
|
||||
this.points = points;
|
||||
this.leftBound = left;
|
||||
this.rightBound = right;
|
||||
|
||||
Calc();
|
||||
}
|
||||
|
||||
internal void SetXBorders(double left, double right) {
|
||||
this.leftBound = left;
|
||||
this.rightBound = right;
|
||||
Calc();
|
||||
}
|
||||
|
||||
private void Calc() {
|
||||
Debug.Assert(leftBound <= rightBound);
|
||||
|
||||
first = points.FindIndex(p => p.X > leftBound);
|
||||
if (first > 0)
|
||||
first--;
|
||||
|
||||
last = points.FindLastIndex(p => p.X < rightBound);
|
||||
|
||||
if (last < points.Count - 1)
|
||||
last++;
|
||||
count = last - first;
|
||||
hasPoints = first >= 0 && last > 0;
|
||||
|
||||
if (hasPoints) {
|
||||
startPoint = points[first];
|
||||
}
|
||||
}
|
||||
|
||||
public Point StartPoint {
|
||||
get { return startPoint; }
|
||||
}
|
||||
|
||||
public bool HasPoints {
|
||||
get { return hasPoints; }
|
||||
}
|
||||
|
||||
#region IList<Point> Members
|
||||
|
||||
public int IndexOf(Point item) {
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void Insert(int index, Point item) {
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void RemoveAt(int index) {
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public Point this[int index] {
|
||||
get {
|
||||
return points[first + 1 + index];
|
||||
}
|
||||
set {
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection<Point> Members
|
||||
|
||||
public void Add(Point item) {
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public bool Contains(Point item) {
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void CopyTo(Point[] array, int arrayIndex) {
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public int Count {
|
||||
get { return count; }
|
||||
}
|
||||
|
||||
public bool IsReadOnly {
|
||||
get { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public bool Remove(Point item) {
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable<Point> Members
|
||||
|
||||
public IEnumerator<Point> GetEnumerator() {
|
||||
for (int i = first + 1; i <= last; i++) {
|
||||
yield return points[i];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
49
Charts/FilterCollection.cs
Normal file
49
Charts/FilterCollection.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Filters;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
using System.Collections.Specialized;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a collection of point filters of <see cref="LineGraph"/>.
|
||||
/// </summary>
|
||||
public sealed class FilterCollection : D3Collection<IPointsFilter>
|
||||
{
|
||||
protected override void OnItemAdding(IPointsFilter item)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException("item");
|
||||
}
|
||||
|
||||
protected override void OnItemAdded(IPointsFilter item)
|
||||
{
|
||||
item.Changed += OnItemChanged;
|
||||
}
|
||||
|
||||
private void OnItemChanged(object sender, EventArgs e)
|
||||
{
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
protected override void OnItemRemoving(IPointsFilter item)
|
||||
{
|
||||
item.Changed -= OnItemChanged;
|
||||
}
|
||||
|
||||
internal List<Point> Filter(List<Point> points, Rect screenRect)
|
||||
{
|
||||
foreach (var filter in Items)
|
||||
{
|
||||
filter.SetScreenRect(screenRect);
|
||||
points = filter.Filter(points);
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Charts/Filters/EmptyFilter.cs
Normal file
16
Charts/Filters/EmptyFilter.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Filters
|
||||
{
|
||||
public sealed class EmptyFilter : PointsFilterBase
|
||||
{
|
||||
public override List<Point> Filter(List<Point> points)
|
||||
{
|
||||
return points;
|
||||
}
|
||||
}
|
||||
}
|
||||
136
Charts/Filters/FrequencyFilter.cs
Normal file
136
Charts/Filters/FrequencyFilter.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Filters;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Filters
|
||||
{
|
||||
public sealed class FrequencyFilter : PointsFilterBase
|
||||
{
|
||||
|
||||
/// <summary>Visible region in screen coordinates</summary>
|
||||
private Rect screenRect;
|
||||
|
||||
#region IPointFilter Members
|
||||
|
||||
public override void SetScreenRect(Rect screenRect)
|
||||
{
|
||||
this.screenRect = screenRect;
|
||||
}
|
||||
|
||||
// todo probably use LINQ here.
|
||||
public override List<Point> Filter(List<Point> points)
|
||||
{
|
||||
if (points.Count == 0) return points;
|
||||
|
||||
List<Point> resultPoints = points;
|
||||
List<Point> currentChain = new List<Point>();
|
||||
|
||||
if (points.Count > 2 * screenRect.Width)
|
||||
{
|
||||
resultPoints = new List<Point>();
|
||||
|
||||
double currentX = Math.Floor(points[0].X);
|
||||
foreach (Point p in points)
|
||||
{
|
||||
if (Math.Floor(p.X) == currentX)
|
||||
{
|
||||
currentChain.Add(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Analyse current chain
|
||||
if (currentChain.Count <= 2)
|
||||
{
|
||||
resultPoints.AddRange(currentChain);
|
||||
}
|
||||
else
|
||||
{
|
||||
Point first = MinByX(currentChain);
|
||||
Point last = MaxByX(currentChain);
|
||||
Point min = MinByY(currentChain);
|
||||
Point max = MaxByY(currentChain);
|
||||
resultPoints.Add(first);
|
||||
|
||||
Point smaller = min.X < max.X ? min : max;
|
||||
Point greater = min.X > max.X ? min : max;
|
||||
if (smaller != resultPoints.GetLast())
|
||||
{
|
||||
resultPoints.Add(smaller);
|
||||
}
|
||||
if (greater != resultPoints.GetLast())
|
||||
{
|
||||
resultPoints.Add(greater);
|
||||
}
|
||||
if (last != resultPoints.GetLast())
|
||||
{
|
||||
resultPoints.Add(last);
|
||||
}
|
||||
}
|
||||
currentChain.Clear();
|
||||
currentChain.Add(p);
|
||||
currentX = Math.Floor(p.X);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resultPoints.AddRange(currentChain);
|
||||
|
||||
return resultPoints;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static Point MinByX(IList<Point> points)
|
||||
{
|
||||
Point minPoint = points[0];
|
||||
foreach (Point p in points)
|
||||
{
|
||||
if (p.X < minPoint.X)
|
||||
{
|
||||
minPoint = p;
|
||||
}
|
||||
}
|
||||
return minPoint;
|
||||
}
|
||||
|
||||
private static Point MaxByX(IList<Point> points)
|
||||
{
|
||||
Point maxPoint = points[0];
|
||||
foreach (Point p in points)
|
||||
{
|
||||
if (p.X > maxPoint.X)
|
||||
{
|
||||
maxPoint = p;
|
||||
}
|
||||
}
|
||||
return maxPoint;
|
||||
}
|
||||
|
||||
private static Point MinByY(IList<Point> points)
|
||||
{
|
||||
Point minPoint = points[0];
|
||||
foreach (Point p in points)
|
||||
{
|
||||
if (p.Y < minPoint.Y)
|
||||
{
|
||||
minPoint = p;
|
||||
}
|
||||
}
|
||||
return minPoint;
|
||||
}
|
||||
|
||||
private static Point MaxByY(IList<Point> points)
|
||||
{
|
||||
Point maxPoint = points[0];
|
||||
foreach (Point p in points)
|
||||
{
|
||||
if (p.Y > maxPoint.Y)
|
||||
{
|
||||
maxPoint = p;
|
||||
}
|
||||
}
|
||||
return maxPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
90
Charts/Filters/FrequencyFilter2.cs
Normal file
90
Charts/Filters/FrequencyFilter2.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Filters;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Filters
|
||||
{
|
||||
public class FrequencyFilter2 : PointsFilterBase
|
||||
{
|
||||
private Rect screenRect;
|
||||
public override void SetScreenRect(Rect screenRect)
|
||||
{
|
||||
this.screenRect = screenRect;
|
||||
}
|
||||
|
||||
public override List<Point> Filter(List<Point> points)
|
||||
{
|
||||
List<Point> result = new List<Point>();
|
||||
|
||||
using (var enumerator = points.GetEnumerator())
|
||||
{
|
||||
double currentX = Double.NegativeInfinity;
|
||||
|
||||
double minX = 0, maxX = 0, minY = 0, maxY = 0;
|
||||
|
||||
Point left = new Point(), right = new Point(), top = new Point(), bottom = new Point();
|
||||
|
||||
bool isFirstPoint = true;
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
Point currPoint = enumerator.Current;
|
||||
double x = currPoint.X;
|
||||
double y = currPoint.Y;
|
||||
double xInt = Math.Floor(x);
|
||||
if (xInt == currentX)
|
||||
{
|
||||
if (x > maxX)
|
||||
{
|
||||
maxX = x;
|
||||
right = currPoint;
|
||||
}
|
||||
|
||||
if (y > maxY)
|
||||
{
|
||||
maxY = y;
|
||||
top = currPoint;
|
||||
}
|
||||
else if (y < minY)
|
||||
{
|
||||
minY = y;
|
||||
bottom = currPoint;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isFirstPoint)
|
||||
{
|
||||
result.Add(left);
|
||||
|
||||
Point leftY = top.X < bottom.X ? top : bottom;
|
||||
Point rightY = top.X > bottom.X ? top : bottom;
|
||||
|
||||
if (top != bottom)
|
||||
{
|
||||
result.Add(leftY);
|
||||
result.Add(rightY);
|
||||
}
|
||||
else if (top != left)
|
||||
result.Add(top);
|
||||
|
||||
if (right != rightY)
|
||||
result.Add(right);
|
||||
}
|
||||
|
||||
currentX = xInt;
|
||||
left = right = top = bottom = currPoint;
|
||||
minX = maxX = x;
|
||||
minY = maxY = y;
|
||||
}
|
||||
|
||||
isFirstPoint = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Charts/Filters/IPointsFilter.cs
Normal file
23
Charts/Filters/IPointsFilter.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Filters
|
||||
{
|
||||
/// <summary>Provides algorithm for filtering point lists in screen coordinates</summary>
|
||||
public interface IPointsFilter
|
||||
{
|
||||
|
||||
/// <summary>Performs filtering</summary>
|
||||
/// <param name="points">List of source points</param>
|
||||
/// <returns>List of filtered points</returns>
|
||||
List<Point> Filter(List<Point> points);
|
||||
|
||||
/// <summary>Sets visible rectangle in screen coordinates</summary>
|
||||
/// <param name="rect">Screen rectangle</param>
|
||||
/// <remarks>Should be invoked before first call to <see cref="Filter"/></remarks>
|
||||
void SetScreenRect(Rect screenRect);
|
||||
|
||||
event EventHandler Changed;
|
||||
}
|
||||
}
|
||||
74
Charts/Filters/InclinationFilter.cs
Normal file
74
Charts/Filters/InclinationFilter.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Filters;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Filters
|
||||
{
|
||||
[Obsolete("Works incorrectly", true)]
|
||||
public sealed class InclinationFilter : PointsFilterBase
|
||||
{
|
||||
private double criticalAngle = 179;
|
||||
public double CriticalAngle
|
||||
{
|
||||
get { return criticalAngle; }
|
||||
set
|
||||
{
|
||||
if (criticalAngle != value)
|
||||
{
|
||||
criticalAngle = value;
|
||||
RaiseChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IPointFilter Members
|
||||
|
||||
public override List<Point> Filter(List<Point> points)
|
||||
{
|
||||
if (points.Count == 0)
|
||||
return points;
|
||||
|
||||
List<Point> res = new List<Point> { points[0] };
|
||||
|
||||
int i = 1;
|
||||
while (i < points.Count)
|
||||
{
|
||||
bool added = false;
|
||||
int j = i;
|
||||
while (!added && (j < points.Count - 1))
|
||||
{
|
||||
Point x1 = res[res.Count - 1];
|
||||
Point x2 = points[j];
|
||||
Point x3 = points[j + 1];
|
||||
|
||||
double a = (x1 - x2).Length;
|
||||
double b = (x2 - x3).Length;
|
||||
double c = (x1 - x3).Length;
|
||||
|
||||
double angle13 = Math.Acos((a * a + b * b - c * c) / (2 * a * b));
|
||||
double degrees = 180 / Math.PI * angle13;
|
||||
if (degrees < criticalAngle)
|
||||
{
|
||||
res.Add(x2);
|
||||
added = true;
|
||||
i = j + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
j++;
|
||||
}
|
||||
}
|
||||
// reached the end of resultPoints
|
||||
if (!added)
|
||||
{
|
||||
res.Add(points.GetLast());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
26
Charts/Filters/PointsFilterBase.cs
Normal file
26
Charts/Filters/PointsFilterBase.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Filters;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Filters
|
||||
{
|
||||
public abstract class PointsFilterBase : IPointsFilter
|
||||
{
|
||||
#region IPointsFilter Members
|
||||
|
||||
public abstract List<Point> Filter(List<Point> points);
|
||||
|
||||
public virtual void SetScreenRect(Rect screenRect) { }
|
||||
|
||||
protected void RaiseChanged()
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
public event EventHandler Changed;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
14
Charts/IOneDimensionalChart.cs
Normal file
14
Charts/IOneDimensionalChart.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.DataSources;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay
|
||||
{
|
||||
public interface IOneDimensionalChart
|
||||
{
|
||||
IPointDataSource DataSource { get; set; }
|
||||
event EventHandler DataChanged;
|
||||
}
|
||||
}
|
||||
87
Charts/Isolines/AdditionalLinesRenderer.cs
Normal file
87
Charts/Isolines/AdditionalLinesRenderer.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Media;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
|
||||
{
|
||||
public class AdditionalLinesRenderer : IsolineRenderer
|
||||
{
|
||||
protected override void CreateUIRepresentation()
|
||||
{
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
protected override void OnPlotterAttached()
|
||||
{
|
||||
base.OnPlotterAttached();
|
||||
|
||||
FrameworkElement parent = (FrameworkElement)Parent;
|
||||
var renderer = (FrameworkElement)parent.FindName("PART_IsolineRenderer");
|
||||
|
||||
Binding contentBoundsBinding = new Binding { Path = new PropertyPath("(0)", Viewport2D.ContentBoundsProperty), Source = renderer };
|
||||
SetBinding(Viewport2D.ContentBoundsProperty, contentBoundsBinding);
|
||||
SetBinding(ViewportPanel.ViewportBoundsProperty, contentBoundsBinding);
|
||||
|
||||
Plotter2D.Viewport.EndPanning += Viewport_EndPanning;
|
||||
Plotter2D.Viewport.PropertyChanged += Viewport_PropertyChanged;
|
||||
}
|
||||
|
||||
void Viewport_PropertyChanged(object sender, ExtendedPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == "Visible")
|
||||
{
|
||||
if (Plotter2D.Viewport.PanningState == Viewport2DPanningState.NotPanning)
|
||||
InvalidateVisual();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPlotterDetaching()
|
||||
{
|
||||
Plotter2D.Viewport.EndPanning -= Viewport_EndPanning;
|
||||
Plotter2D.Viewport.PropertyChanged -= Viewport_PropertyChanged;
|
||||
|
||||
base.OnPlotterDetaching();
|
||||
}
|
||||
|
||||
private void Viewport_EndPanning(object sender, EventArgs e)
|
||||
{
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext drawingContext)
|
||||
{
|
||||
if (Plotter2D == null) return;
|
||||
if (DataSource == null) return;
|
||||
|
||||
var collection = (IsolineCollection)Parent.GetValue(IsolineCollectionProperty);
|
||||
if (collection == null) return;
|
||||
|
||||
var bounds = ViewportPanel.GetViewportBounds(this);
|
||||
if (bounds.IsEmpty) return;
|
||||
|
||||
var dc = drawingContext;
|
||||
var strokeThickness = StrokeThickness;
|
||||
|
||||
var transform = Plotter2D.Transform.WithRects(bounds, new Rect(RenderSize));
|
||||
|
||||
//dc.DrawRectangle(null, new Pen(Brushes.Green, 2), new Rect(RenderSize));
|
||||
|
||||
var additionalLevels = GetAdditionalLevels(collection);
|
||||
IsolineBuilder.DataSource = DataSource;
|
||||
var additionalIsolineCollections = additionalLevels.Select(level =>
|
||||
{
|
||||
return IsolineBuilder.BuildIsoline(level);
|
||||
});
|
||||
|
||||
foreach (var additionalCollection in additionalIsolineCollections)
|
||||
{
|
||||
RenderIsolineCollection(dc, strokeThickness, additionalCollection, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
399
Charts/Isolines/CellInfo.cs
Normal file
399
Charts/Isolines/CellInfo.cs
Normal file
@@ -0,0 +1,399 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
|
||||
{
|
||||
/// <summary>
|
||||
/// Isoline's grid cell
|
||||
/// </summary>
|
||||
internal interface ICell
|
||||
{
|
||||
Vector LeftTop { get; }
|
||||
Vector LeftBottom { get; }
|
||||
Vector RightTop { get; }
|
||||
Vector RightBottom { get; }
|
||||
}
|
||||
|
||||
internal sealed class IrregularCell : ICell
|
||||
{
|
||||
public IrregularCell(Vector leftBottom, Vector rightBottom, Vector rightTop, Vector leftTop)
|
||||
{
|
||||
this.leftBottom = leftBottom;
|
||||
this.rightBottom = rightBottom;
|
||||
this.rightTop = rightTop;
|
||||
this.leftTop = leftTop;
|
||||
}
|
||||
|
||||
public IrregularCell(Point lb, Point rb, Point rt, Point lt)
|
||||
{
|
||||
leftTop = lt.ToVector();
|
||||
leftBottom = lb.ToVector();
|
||||
rightTop = rt.ToVector();
|
||||
rightBottom = rb.ToVector();
|
||||
}
|
||||
|
||||
#region ICell Members
|
||||
|
||||
private readonly Vector leftTop;
|
||||
public Vector LeftTop
|
||||
{
|
||||
get { return leftTop; }
|
||||
}
|
||||
|
||||
private readonly Vector leftBottom;
|
||||
public Vector LeftBottom
|
||||
{
|
||||
get { return leftBottom; }
|
||||
}
|
||||
|
||||
private readonly Vector rightTop;
|
||||
public Vector RightTop
|
||||
{
|
||||
get { return rightTop; }
|
||||
}
|
||||
|
||||
private readonly Vector rightBottom;
|
||||
public Vector RightBottom
|
||||
{
|
||||
get { return rightBottom; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sides
|
||||
public Vector LeftSide
|
||||
{
|
||||
get { return (leftBottom + leftTop) / 2; }
|
||||
}
|
||||
|
||||
public Vector RightSide
|
||||
{
|
||||
get { return (rightBottom + rightTop) / 2; }
|
||||
}
|
||||
public Vector TopSide
|
||||
{
|
||||
get { return (leftTop + rightTop) / 2; }
|
||||
}
|
||||
public Vector BottomSide
|
||||
{
|
||||
get { return (leftBottom + rightBottom) / 2; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
public Point Center
|
||||
{
|
||||
get { return ((LeftSide + RightSide) / 2).ToPoint(); }
|
||||
}
|
||||
|
||||
public IrregularCell GetSubRect(SubCell sub)
|
||||
{
|
||||
switch (sub)
|
||||
{
|
||||
case SubCell.LeftBottom:
|
||||
return new IrregularCell(LeftBottom, BottomSide, Center.ToVector(), LeftSide);
|
||||
case SubCell.LeftTop:
|
||||
return new IrregularCell(LeftSide, Center.ToVector(), TopSide, LeftTop);
|
||||
case SubCell.RightBottom:
|
||||
return new IrregularCell(BottomSide, RightBottom, RightSide, Center.ToVector());
|
||||
case SubCell.RightTop:
|
||||
default:
|
||||
return new IrregularCell(Center.ToVector(), RightSide, RightTop, TopSide);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal enum SubCell
|
||||
{
|
||||
LeftBottom = 0,
|
||||
LeftTop = 1,
|
||||
RightBottom = 2,
|
||||
RightTop = 3
|
||||
}
|
||||
|
||||
internal class ValuesInCell
|
||||
{
|
||||
double min = Double.MaxValue, max = Double.MinValue;
|
||||
|
||||
/// <summary>Initializes values in four corners of cell</summary>
|
||||
/// <param name="leftBottom"></param>
|
||||
/// <param name="rightBottom"></param>
|
||||
/// <param name="rightTop"></param>
|
||||
/// <param name="leftTop"></param>
|
||||
/// <remarks>Some or all values can be NaN. That means that value is not specified (misssing)</remarks>
|
||||
public ValuesInCell(double leftBottom, double rightBottom, double rightTop, double leftTop)
|
||||
{
|
||||
this.leftTop = leftTop;
|
||||
this.leftBottom = leftBottom;
|
||||
this.rightTop = rightTop;
|
||||
this.rightBottom = rightBottom;
|
||||
|
||||
// Find max and min values (with respect to possible NaN values)
|
||||
if (!Double.IsNaN(leftTop))
|
||||
{
|
||||
if (min > leftTop)
|
||||
min = leftTop;
|
||||
if (max < leftTop)
|
||||
max = leftTop;
|
||||
}
|
||||
|
||||
if (!Double.IsNaN(leftBottom))
|
||||
{
|
||||
if (min > leftBottom)
|
||||
min = leftBottom;
|
||||
if (max < leftBottom)
|
||||
max = leftBottom;
|
||||
}
|
||||
|
||||
if (!Double.IsNaN(rightTop))
|
||||
{
|
||||
if (min > rightTop)
|
||||
min = rightTop;
|
||||
if (max < rightTop)
|
||||
max = rightTop;
|
||||
}
|
||||
|
||||
if (!Double.IsNaN(rightBottom))
|
||||
{
|
||||
if (min > rightBottom)
|
||||
min = rightBottom;
|
||||
if (max < rightBottom)
|
||||
max = rightBottom;
|
||||
}
|
||||
|
||||
left = (leftTop + leftBottom) / 2;
|
||||
bottom = (leftBottom + rightBottom) / 2;
|
||||
right = (rightTop + rightBottom) / 2;
|
||||
top = (rightTop + leftTop) / 2;
|
||||
}
|
||||
|
||||
public ValuesInCell(double leftBottom, double rightBottom, double rightTop, double leftTop, double missingValue)
|
||||
{
|
||||
DebugVerify.IsNotNaN(leftBottom);
|
||||
DebugVerify.IsNotNaN(rightBottom);
|
||||
DebugVerify.IsNotNaN(rightTop);
|
||||
DebugVerify.IsNotNaN(leftTop);
|
||||
|
||||
// Copy values and find min and max with respect to possible missing values
|
||||
if (leftTop != missingValue)
|
||||
{
|
||||
this.leftTop = leftTop;
|
||||
if (min > leftTop)
|
||||
min = leftTop;
|
||||
if (max < leftTop)
|
||||
max = leftTop;
|
||||
}
|
||||
else
|
||||
this.leftTop = Double.NaN;
|
||||
|
||||
if (leftBottom != missingValue)
|
||||
{
|
||||
this.leftBottom = leftBottom;
|
||||
if (min > leftBottom)
|
||||
min = leftBottom;
|
||||
if (max < leftBottom)
|
||||
max = leftBottom;
|
||||
}
|
||||
else
|
||||
this.leftBottom = Double.NaN;
|
||||
|
||||
if (rightTop != missingValue)
|
||||
{
|
||||
this.rightTop = rightTop;
|
||||
if (min > rightTop)
|
||||
min = rightTop;
|
||||
if (max < rightTop)
|
||||
max = rightTop;
|
||||
}
|
||||
else
|
||||
this.rightTop = Double.NaN;
|
||||
|
||||
if (rightBottom != missingValue)
|
||||
{
|
||||
this.rightBottom = rightBottom;
|
||||
if (min > rightBottom)
|
||||
min = rightBottom;
|
||||
if (max < rightBottom)
|
||||
max = rightBottom;
|
||||
}
|
||||
else
|
||||
this.rightBottom = Double.NaN;
|
||||
|
||||
left = (this.leftTop + this.leftBottom) / 2;
|
||||
bottom = (this.leftBottom + this.rightBottom) / 2;
|
||||
right = (this.rightTop + this.rightBottom) / 2;
|
||||
top = (this.rightTop + this.leftTop) / 2;
|
||||
|
||||
|
||||
/*
|
||||
if (leftTop != missingValue && )
|
||||
{
|
||||
if (leftBottom != missingValue)
|
||||
left = (leftTop + leftBottom) / 2;
|
||||
else
|
||||
left = Double.NaN;
|
||||
|
||||
if (rightTop != missingValue)
|
||||
top = (leftTop + rightTop) / 2;
|
||||
else
|
||||
top = Double.NaN;
|
||||
}
|
||||
|
||||
if (rightBottom != missingValue)
|
||||
{
|
||||
if (leftBottom != missingValue)
|
||||
bottom = (leftBottom + rightBottom) / 2;
|
||||
else
|
||||
bottom = Double.NaN;
|
||||
|
||||
if (rightTop != missingValue)
|
||||
right = (rightTop + rightBottom) / 2;
|
||||
else
|
||||
right = Double.NaN;
|
||||
}*/
|
||||
}
|
||||
|
||||
|
||||
/*internal bool ValueBelongTo(double value)
|
||||
{
|
||||
IEnumerable<double> values = new double[] { leftTop, leftBottom, rightTop, rightBottom };
|
||||
|
||||
return !(values.All(v => v > value) || values.All(v => v < value));
|
||||
}*/
|
||||
|
||||
internal bool ValueBelongTo(double value)
|
||||
{
|
||||
return (min <= value && value <= max);
|
||||
}
|
||||
|
||||
#region Edges
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
private readonly double leftTop;
|
||||
public double LeftTop { get { return leftTop; } }
|
||||
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
private readonly double leftBottom;
|
||||
public double LeftBottom { get { return leftBottom; } }
|
||||
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
private readonly double rightTop;
|
||||
public double RightTop
|
||||
{
|
||||
get { return rightTop; }
|
||||
}
|
||||
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
private readonly double rightBottom;
|
||||
public double RightBottom
|
||||
{
|
||||
get { return rightBottom; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Sides & center
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
private readonly double left;
|
||||
public double Left
|
||||
{
|
||||
get { return left; }
|
||||
}
|
||||
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
private readonly double right;
|
||||
public double Right
|
||||
{
|
||||
get { return right; }
|
||||
}
|
||||
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
private readonly double top;
|
||||
public double Top
|
||||
{
|
||||
get { return top; }
|
||||
}
|
||||
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
private readonly double bottom;
|
||||
public double Bottom
|
||||
{
|
||||
get { return bottom; }
|
||||
}
|
||||
|
||||
public double Center
|
||||
{
|
||||
get { return (Left + Right) * 0.5; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SubCells
|
||||
public ValuesInCell LeftTopCell
|
||||
{
|
||||
get { return new ValuesInCell(Left, Center, Top, LeftTop); }
|
||||
}
|
||||
|
||||
public ValuesInCell RightTopCell
|
||||
{
|
||||
get { return new ValuesInCell(Center, Right, RightTop, Top); }
|
||||
}
|
||||
|
||||
public ValuesInCell RightBottomCell
|
||||
{
|
||||
get { return new ValuesInCell(Bottom, RightBottom, Right, Center); }
|
||||
}
|
||||
|
||||
public ValuesInCell LeftBottomCell
|
||||
{
|
||||
get { return new ValuesInCell(LeftBottom, Bottom, Center, Left); }
|
||||
}
|
||||
|
||||
public ValuesInCell GetSubCell(SubCell subCell)
|
||||
{
|
||||
switch (subCell)
|
||||
{
|
||||
case SubCell.LeftBottom:
|
||||
return LeftBottomCell;
|
||||
case SubCell.LeftTop:
|
||||
return LeftTopCell;
|
||||
case SubCell.RightBottom:
|
||||
return RightBottomCell;
|
||||
case SubCell.RightTop:
|
||||
default:
|
||||
return RightTopCell;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns bitmask of comparison of values at cell corners with reference value.
|
||||
/// Corresponding bit is set to one if value at cell corner is greater than reference value.
|
||||
/// a------b
|
||||
/// | Cell |
|
||||
/// d------c
|
||||
/// </summary>
|
||||
/// <param name="a">Value at corner (see figure)</param>
|
||||
/// <param name="b">Value at corner (see figure)</param>
|
||||
/// <param name="c">Value at corner (see figure)</param>
|
||||
/// <param name="d">Value at corner (see figure)</param>
|
||||
/// <param name="value">Reference value</param>
|
||||
/// <returns>Bitmask</returns>
|
||||
public CellBitmask GetCellValue(double value)
|
||||
{
|
||||
CellBitmask n = CellBitmask.None;
|
||||
if (!Double.IsNaN(leftTop) && leftTop > value)
|
||||
n |= CellBitmask.LeftTop;
|
||||
if (!Double.IsNaN(leftBottom) && leftBottom > value)
|
||||
n |= CellBitmask.LeftBottom;
|
||||
if (!Double.IsNaN(rightBottom) && rightBottom > value)
|
||||
n |= CellBitmask.RightBottom;
|
||||
if (!Double.IsNaN(rightTop) && rightTop > value)
|
||||
n |= CellBitmask.RightTop;
|
||||
|
||||
return n;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Charts/Isolines/Enums.cs
Normal file
67
Charts/Isolines/Enums.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
|
||||
{
|
||||
/// <summary>
|
||||
/// Edge identifier - indicates which side of cell isoline crosses.
|
||||
/// </summary>
|
||||
internal enum Edge
|
||||
{
|
||||
// todo check if everything is ok with None.
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// Isoline crosses left boundary of cell (bit 0)
|
||||
/// </summary>
|
||||
Left = 1,
|
||||
/// <summary>
|
||||
/// Isoline crosses top boundary of cell (bit 1)
|
||||
/// </summary>
|
||||
Top = 2,
|
||||
/// <summary>
|
||||
/// Isoline crosses right boundary of cell (bit 2)
|
||||
/// </summary>
|
||||
Right = 4,
|
||||
/// <summary>
|
||||
/// Isoline crosses bottom boundary of cell (bit 3)
|
||||
/// </summary>
|
||||
Bottom = 8
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum CellBitmask
|
||||
{
|
||||
None = 0,
|
||||
LeftTop = 1,
|
||||
LeftBottom = 8,
|
||||
RightBottom = 4,
|
||||
RightTop = 2
|
||||
}
|
||||
|
||||
internal static class IsolineExtensions
|
||||
{
|
||||
internal static bool IsDiagonal(this CellBitmask bitmask)
|
||||
{
|
||||
return bitmask == (CellBitmask.RightBottom | CellBitmask.LeftTop) ||
|
||||
bitmask == (CellBitmask.LeftBottom | CellBitmask.RightTop);
|
||||
}
|
||||
|
||||
internal static bool IsAppropriate(this SubCell sub, Edge edge)
|
||||
{
|
||||
switch (sub)
|
||||
{
|
||||
case SubCell.LeftBottom:
|
||||
return edge == Edge.Left || edge == Edge.Bottom;
|
||||
case SubCell.LeftTop:
|
||||
return edge == Edge.Left || edge == Edge.Top;
|
||||
case SubCell.RightBottom:
|
||||
return edge == Edge.Right || edge == Edge.Bottom;
|
||||
case SubCell.RightTop:
|
||||
default:
|
||||
return edge == Edge.Right || edge == Edge.Top;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Charts/Isolines/FastIsolineDisplay.xaml
Normal file
19
Charts/Isolines/FastIsolineDisplay.xaml
Normal file
@@ -0,0 +1,19 @@
|
||||
<isolines:IsolineGraphBase x:Class="Microsoft.Research.DynamicDataDisplay.Charts.Isolines.FastIsolineDisplay"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:isolines="clr-namespace:Microsoft.Research.DynamicDataDisplay.Charts.Isolines"
|
||||
xmlns:local="clr-namespace:Microsoft.Research.DynamicDataDisplay.Charts"
|
||||
xmlns:common="clr-namespace:Microsoft.Research.DynamicDataDisplay"
|
||||
IsHitTestVisible="False"
|
||||
>
|
||||
<isolines:IsolineGraphBase.Template>
|
||||
<ControlTemplate TargetType="{x:Type isolines:IsolineGraphBase}">
|
||||
<Canvas IsHitTestVisible="False">
|
||||
<local:ViewportHostPanel Name="PART_ViewportPanel">
|
||||
<isolines:FastIsolineRenderer Name="PART_IsolineRenderer"/>
|
||||
<isolines:AdditionalLinesRenderer Name="PART_AdditionalLinesRenderer"/>
|
||||
</local:ViewportHostPanel>
|
||||
</Canvas>
|
||||
</ControlTemplate>
|
||||
</isolines:IsolineGraphBase.Template>
|
||||
</isolines:IsolineGraphBase>
|
||||
59
Charts/Isolines/FastIsolineDisplay.xaml.cs
Normal file
59
Charts/Isolines/FastIsolineDisplay.xaml.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Shapes;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
|
||||
{
|
||||
public partial class FastIsolineDisplay : IsolineGraphBase
|
||||
{
|
||||
public FastIsolineDisplay()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override Panel HostPanel
|
||||
{
|
||||
get
|
||||
{
|
||||
return Plotter2D.CentralGrid;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
var isolineRenderer = (FastIsolineRenderer)Template.FindName("PART_IsolineRenderer", this);
|
||||
//Binding contentBoundsBinding = new Binding { Path = new PropertyPath("(0)", Viewport2D.ContentBoundsProperty), Source = isolineRenderer };
|
||||
//SetBinding(Viewport2D.ContentBoundsProperty, contentBoundsBinding);
|
||||
|
||||
if (isolineRenderer != null)
|
||||
{
|
||||
isolineRenderer.AddHandler(Viewport2D.ContentBoundsChangedEvent, new RoutedEventHandler(OnRendererContentBoundsChanged));
|
||||
UpdateContentBounds(isolineRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRendererContentBoundsChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
UpdateContentBounds((DependencyObject)sender);
|
||||
}
|
||||
|
||||
private void UpdateContentBounds(DependencyObject source)
|
||||
{
|
||||
var contentBounds = Viewport2D.GetContentBounds(source);
|
||||
Viewport2D.SetContentBounds(this, contentBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
200
Charts/Isolines/FastIsolineRenderer.cs
Normal file
200
Charts/Isolines/FastIsolineRenderer.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Media;
|
||||
using System.Windows;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Shapes;
|
||||
using System.Windows.Threading;
|
||||
using System.Globalization;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.NewLine;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
|
||||
{
|
||||
public class FastIsolineRenderer : IsolineRenderer
|
||||
{
|
||||
private List<IsolineCollection> additionalLines = new List<IsolineCollection>();
|
||||
private const int subDivisionNum = 10;
|
||||
|
||||
protected override void CreateUIRepresentation()
|
||||
{
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
protected override void OnPlotterAttached()
|
||||
{
|
||||
base.OnPlotterAttached();
|
||||
|
||||
FrameworkElement parent = (FrameworkElement)Parent;
|
||||
Binding collectionBinding = new Binding("IsolineCollection") { Source = this };
|
||||
parent.SetBinding(IsolineCollectionProperty, collectionBinding);
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext drawingContext)
|
||||
{
|
||||
if (Plotter2D == null) return;
|
||||
if (Collection == null) return;
|
||||
if (DataSource == null) return;
|
||||
if (Collection.Lines.Count == 0)
|
||||
{
|
||||
IsolineBuilder.DataSource = DataSource;
|
||||
IsolineBuilder.MissingValue = MissingValue;
|
||||
Collection = IsolineBuilder.BuildIsoline();
|
||||
}
|
||||
|
||||
IsolineCollection = Collection;
|
||||
|
||||
var dc = drawingContext;
|
||||
var strokeThickness = StrokeThickness;
|
||||
var collection = Collection;
|
||||
|
||||
var bounds = DataRect.Empty;
|
||||
// determining content bounds
|
||||
foreach (LevelLine line in collection)
|
||||
{
|
||||
foreach (Point point in line.AllPoints)
|
||||
{
|
||||
bounds.Union(point);
|
||||
}
|
||||
}
|
||||
|
||||
Viewport2D.SetContentBounds(this, bounds);
|
||||
ViewportPanel.SetViewportBounds(this, bounds);
|
||||
|
||||
if (bounds.IsEmpty) return;
|
||||
|
||||
// custom transform with output set to renderSize of this control
|
||||
var transform = Plotter2D.Transform.WithRects(bounds, new Rect(RenderSize));
|
||||
|
||||
// actual drawing of isolines
|
||||
RenderIsolineCollection(dc, strokeThickness, collection, transform);
|
||||
|
||||
//var additionalLevels = GetAdditionalIsolines(collection);
|
||||
|
||||
//var additionalIsolineCollections = additionalLevels.Select(level => IsolineBuilder.BuildIsoline(level));
|
||||
|
||||
//foreach (var additionalCollection in additionalIsolineCollections)
|
||||
//{
|
||||
// RenderIsolineCollection(dc, strokeThickness, additionalCollection, transform);
|
||||
//}
|
||||
|
||||
RenderLabels(dc, collection);
|
||||
|
||||
// foreach (var additionalCollection in additionalIsolineCollections)
|
||||
// {
|
||||
// RenderLabels(dc, additionalCollection);
|
||||
// }
|
||||
}
|
||||
|
||||
private IEnumerable<double> GetAdditionalIsolines(IsolineCollection collection)
|
||||
{
|
||||
var dataSource = DataSource;
|
||||
var visibleMinMax = dataSource.GetMinMax(Plotter2D.Visible);
|
||||
var visibleMinMaxRatio = (collection.Max - collection.Min) / visibleMinMax.GetLength();
|
||||
|
||||
var log = Math.Log10(visibleMinMaxRatio);
|
||||
if (log > 0.9)
|
||||
{
|
||||
var upperLog = Math.Ceiling(log);
|
||||
var divisionsNum = Math.Pow(10, upperLog);
|
||||
var delta = (collection.Max - collection.Min) / divisionsNum;
|
||||
|
||||
var start = Math.Ceiling(visibleMinMax.Min / delta) * delta;
|
||||
|
||||
var x = start;
|
||||
while (x < visibleMinMax.Max)
|
||||
{
|
||||
yield return x;
|
||||
x += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderLabels(DrawingContext dc, IsolineCollection collection)
|
||||
{
|
||||
if (Plotter2D == null) return;
|
||||
if (collection == null) return;
|
||||
if (!DrawLabels) return;
|
||||
|
||||
var viewportBounds = ViewportPanel.GetViewportBounds(this);
|
||||
if (viewportBounds.IsEmpty)
|
||||
return;
|
||||
|
||||
var strokeThickness = StrokeThickness;
|
||||
var visible = Plotter2D.Visible;
|
||||
var output = Plotter2D.Viewport.Output;
|
||||
|
||||
var transform = Plotter2D.Transform.WithRects(viewportBounds, new Rect(RenderSize));
|
||||
var labelStringFormat = LabelStringFormat;
|
||||
|
||||
// drawing constants
|
||||
var labelRectangleFill = Brushes.White;
|
||||
|
||||
var biggerViewport = viewportBounds.ZoomOutFromCenter(1.1);
|
||||
|
||||
// getting and filtering annotations to draw only visible ones
|
||||
Annotater.WayBeforeText = Math.Sqrt(visible.Width * visible.Width + visible.Height * visible.Height) / 100 * WayBeforeTextMultiplier;
|
||||
var annotations = Annotater.Annotate(collection, visible)
|
||||
.Where(annotation =>
|
||||
{
|
||||
Point viewportPosition = annotation.Position.DataToViewport(transform);
|
||||
return biggerViewport.Contains(viewportPosition);
|
||||
});
|
||||
|
||||
// drawing annotations
|
||||
foreach (var annotation in annotations)
|
||||
{
|
||||
FormattedText text = CreateFormattedText(annotation.Value.ToString(LabelStringFormat));
|
||||
Point position = annotation.Position.DataToScreen(transform);
|
||||
|
||||
var labelTransform = CreateTransform(annotation, text, position);
|
||||
|
||||
// creating rectange stroke
|
||||
double colorRatio = (annotation.Value - collection.Min) / (collection.Max - collection.Min);
|
||||
colorRatio = MathHelper.Clamp(colorRatio);
|
||||
Color rectangleStrokeColor = Palette.GetColor(colorRatio);
|
||||
SolidColorBrush rectangleStroke = new SolidColorBrush(rectangleStrokeColor);
|
||||
Pen labelRectangleStrokePen = new Pen(rectangleStroke, 2);
|
||||
|
||||
dc.PushTransform(labelTransform);
|
||||
{
|
||||
var bounds = RectExtensions.FromCenterSize(position, new Size(text.Width, text.Height));
|
||||
bounds = bounds.ZoomOutFromCenter(1.3);
|
||||
dc.DrawRoundedRectangle(labelRectangleFill, labelRectangleStrokePen, bounds, 8, 8);
|
||||
|
||||
DrawTextInPosition(dc, text, position);
|
||||
}
|
||||
dc.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawTextInPosition(DrawingContext dc, FormattedText text, Point position)
|
||||
{
|
||||
var textPosition = position;
|
||||
textPosition.Offset(-text.Width / 2, -text.Height / 2);
|
||||
dc.DrawText(text, textPosition);
|
||||
}
|
||||
|
||||
private static Transform CreateTransform(IsolineTextLabel isolineLabel, FormattedText text, Point position)
|
||||
{
|
||||
double angle = isolineLabel.Rotation;
|
||||
if (angle < 0)
|
||||
angle += 360;
|
||||
if (90 < angle && angle < 270)
|
||||
angle -= 180;
|
||||
|
||||
RotateTransform transform = new RotateTransform(angle, position.X, position.Y);
|
||||
return transform;
|
||||
}
|
||||
|
||||
private static FormattedText CreateFormattedText(string text)
|
||||
{
|
||||
FormattedText result = new FormattedText(text,
|
||||
CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface("Arial"), 12, Brushes.Black,1.0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
708
Charts/Isolines/IsolineBuilder.cs
Normal file
708
Charts/Isolines/IsolineBuilder.cs
Normal file
@@ -0,0 +1,708 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using Microsoft.Research.DynamicDataDisplay.DataSources;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates geometric object for isolines of the input 2d scalar field.
|
||||
/// </summary>
|
||||
public sealed class IsolineBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// The density of isolines means the number of levels to draw.
|
||||
/// </summary>
|
||||
private int density = 12;
|
||||
|
||||
private bool[,] processed;
|
||||
|
||||
/// <summary>Number to be treated as missing value. NaN if no missing value is specified</summary>
|
||||
private double missingValue = Double.NaN;
|
||||
|
||||
static IsolineBuilder()
|
||||
{
|
||||
SetCellDictionaries();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IsolineBuilder"/> class.
|
||||
/// </summary>
|
||||
public IsolineBuilder() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IsolineBuilder"/> class for specified 2d scalar data source.
|
||||
/// </summary>
|
||||
/// <param name="dataSource">The data source with 2d scalar data.</param>
|
||||
public IsolineBuilder(IDataSource2D<double> dataSource)
|
||||
{
|
||||
DataSource = dataSource;
|
||||
}
|
||||
|
||||
public double MissingValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return missingValue;
|
||||
}
|
||||
set
|
||||
{
|
||||
missingValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
#region Private methods
|
||||
|
||||
private static Dictionary<int, Dictionary<int, Edge>> dictChooser = new Dictionary<int, Dictionary<int, Edge>>();
|
||||
private static void SetCellDictionaries()
|
||||
{
|
||||
var bottomDict = new Dictionary<int, Edge>();
|
||||
bottomDict.Add((int)CellBitmask.RightBottom, Edge.Right);
|
||||
bottomDict.Add(Edge.Left,
|
||||
CellBitmask.LeftTop,
|
||||
CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.RightTop,
|
||||
CellBitmask.LeftTop | CellBitmask.RightBottom | CellBitmask.RightTop,
|
||||
CellBitmask.LeftBottom);
|
||||
bottomDict.Add(Edge.Right,
|
||||
CellBitmask.RightTop,
|
||||
CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.LeftTop,
|
||||
CellBitmask.LeftBottom | CellBitmask.LeftTop | CellBitmask.RightTop);
|
||||
bottomDict.Add(Edge.Top,
|
||||
CellBitmask.RightBottom | CellBitmask.RightTop,
|
||||
CellBitmask.LeftBottom | CellBitmask.LeftTop);
|
||||
|
||||
var leftDict = new Dictionary<int, Edge>();
|
||||
leftDict.Add(Edge.Top,
|
||||
CellBitmask.LeftTop,
|
||||
CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.RightTop);
|
||||
leftDict.Add(Edge.Right,
|
||||
CellBitmask.LeftTop | CellBitmask.RightTop,
|
||||
CellBitmask.LeftBottom | CellBitmask.RightBottom);
|
||||
leftDict.Add(Edge.Bottom,
|
||||
CellBitmask.RightBottom | CellBitmask.RightTop | CellBitmask.LeftTop,
|
||||
CellBitmask.LeftBottom);
|
||||
|
||||
var topDict = new Dictionary<int, Edge>();
|
||||
topDict.Add(Edge.Right,
|
||||
CellBitmask.RightTop,
|
||||
CellBitmask.LeftTop | CellBitmask.LeftBottom | CellBitmask.RightBottom);
|
||||
topDict.Add(Edge.Right,
|
||||
CellBitmask.RightBottom,
|
||||
CellBitmask.LeftTop | CellBitmask.LeftBottom | CellBitmask.RightTop);
|
||||
topDict.Add(Edge.Left,
|
||||
CellBitmask.RightBottom | CellBitmask.RightTop | CellBitmask.LeftTop,
|
||||
CellBitmask.LeftBottom,
|
||||
CellBitmask.LeftTop,
|
||||
CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.RightTop);
|
||||
topDict.Add(Edge.Bottom,
|
||||
CellBitmask.RightBottom | CellBitmask.RightTop,
|
||||
CellBitmask.LeftTop | CellBitmask.LeftBottom);
|
||||
|
||||
var rightDict = new Dictionary<int, Edge>();
|
||||
rightDict.Add(Edge.Top,
|
||||
CellBitmask.RightTop,
|
||||
CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.LeftTop);
|
||||
rightDict.Add(Edge.Left,
|
||||
CellBitmask.LeftTop | CellBitmask.RightTop,
|
||||
CellBitmask.LeftBottom | CellBitmask.RightBottom);
|
||||
rightDict.Add(Edge.Bottom,
|
||||
CellBitmask.RightBottom,
|
||||
CellBitmask.LeftTop | CellBitmask.LeftBottom | CellBitmask.RightTop);
|
||||
|
||||
dictChooser.Add((int)Edge.Left, leftDict);
|
||||
dictChooser.Add((int)Edge.Right, rightDict);
|
||||
dictChooser.Add((int)Edge.Bottom, bottomDict);
|
||||
dictChooser.Add((int)Edge.Top, topDict);
|
||||
}
|
||||
|
||||
private Edge GetOutEdge(Edge inEdge, ValuesInCell cv, IrregularCell rect, double value)
|
||||
{
|
||||
// value smaller than all values in corners or
|
||||
// value greater than all values in corners
|
||||
if (!cv.ValueBelongTo(value))
|
||||
{
|
||||
throw new IsolineGenerationException(Strings.Exceptions.IsolinesValueIsOutOfCell);
|
||||
}
|
||||
|
||||
CellBitmask cellVal = cv.GetCellValue(value);
|
||||
var dict = dictChooser[(int)inEdge];
|
||||
if (dict.ContainsKey((int)cellVal))
|
||||
{
|
||||
Edge result = dict[(int)cellVal];
|
||||
switch (result)
|
||||
{
|
||||
case Edge.Left:
|
||||
if (cv.LeftTop.IsNaN() || cv.LeftBottom.IsNaN())
|
||||
result = Edge.None;
|
||||
break;
|
||||
case Edge.Right:
|
||||
if (cv.RightTop.IsNaN() || cv.RightBottom.IsNaN())
|
||||
result = Edge.None;
|
||||
break;
|
||||
case Edge.Top:
|
||||
if (cv.RightTop.IsNaN() || cv.LeftTop.IsNaN())
|
||||
result = Edge.None;
|
||||
break;
|
||||
case Edge.Bottom:
|
||||
if (cv.LeftBottom.IsNaN() || cv.RightBottom.IsNaN())
|
||||
result = Edge.None;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else if (cellVal.IsDiagonal())
|
||||
{
|
||||
return GetOutForOpposite(inEdge, cellVal, value, cv, rect);
|
||||
}
|
||||
|
||||
const double near_zero = 0.0001;
|
||||
const double near_one = 1 - near_zero;
|
||||
|
||||
double lt = cv.LeftTop;
|
||||
double rt = cv.RightTop;
|
||||
double rb = cv.RightBottom;
|
||||
double lb = cv.LeftBottom;
|
||||
|
||||
switch (inEdge)
|
||||
{
|
||||
case Edge.Left:
|
||||
if (value == lt)
|
||||
value = near_one * lt + near_zero * lb;
|
||||
else if (value == lb)
|
||||
value = near_one * lb + near_zero * lt;
|
||||
else
|
||||
return Edge.None;
|
||||
// Now this is possible because of missing value
|
||||
//throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
|
||||
break;
|
||||
case Edge.Top:
|
||||
if (value == rt)
|
||||
value = near_one * rt + near_zero * lt;
|
||||
else if (value == lt)
|
||||
value = near_one * lt + near_zero * rt;
|
||||
else
|
||||
return Edge.None;
|
||||
// Now this is possibe because of missing value
|
||||
//throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
|
||||
break;
|
||||
case Edge.Right:
|
||||
if (value == rb)
|
||||
value = near_one * rb + near_zero * rt;
|
||||
else if (value == rt)
|
||||
value = near_one * rt + near_zero * rb;
|
||||
else
|
||||
return Edge.None;
|
||||
// Now this is possibe because of missing value
|
||||
//throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
|
||||
break;
|
||||
case Edge.Bottom:
|
||||
if (value == rb)
|
||||
value = near_one * rb + near_zero * lb;
|
||||
else if (value == lb)
|
||||
value = near_one * lb + near_zero * rb;
|
||||
else
|
||||
return Edge.None;
|
||||
// Now this is possibe because of missing value
|
||||
//throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
|
||||
break;
|
||||
}
|
||||
|
||||
// Recursion?
|
||||
//return GetOutEdge(inEdge, cv, rect, value);
|
||||
|
||||
return Edge.None;
|
||||
}
|
||||
|
||||
private Edge GetOutForOpposite(Edge inEdge, CellBitmask cellVal, double value, ValuesInCell cellValues, IrregularCell rect)
|
||||
{
|
||||
Edge outEdge;
|
||||
|
||||
SubCell subCell = GetSubCell(inEdge, value, cellValues);
|
||||
|
||||
int iters = 1000; // max number of iterations
|
||||
do
|
||||
{
|
||||
ValuesInCell subValues = cellValues.GetSubCell(subCell);
|
||||
IrregularCell subRect = rect.GetSubRect(subCell);
|
||||
outEdge = GetOutEdge(inEdge, subValues, subRect, value);
|
||||
if (outEdge == Edge.None)
|
||||
return Edge.None;
|
||||
bool isAppropriate = subCell.IsAppropriate(outEdge);
|
||||
if (isAppropriate)
|
||||
{
|
||||
ValuesInCell sValues = subValues.GetSubCell(subCell);
|
||||
|
||||
Point point = GetPointXY(outEdge, value, subValues, subRect);
|
||||
segments.AddPoint(point);
|
||||
return outEdge;
|
||||
}
|
||||
else
|
||||
{
|
||||
subCell = GetAdjacentEdge(subCell, outEdge);
|
||||
}
|
||||
|
||||
byte e = (byte)outEdge;
|
||||
inEdge = (Edge)((e > 2) ? (e >> 2) : (e << 2));
|
||||
iters--;
|
||||
} while (iters >= 0);
|
||||
|
||||
throw new IsolineGenerationException(Strings.Exceptions.IsolinesDataIsUndetailized);
|
||||
}
|
||||
|
||||
private static SubCell GetAdjacentEdge(SubCell sub, Edge edge)
|
||||
{
|
||||
SubCell res = SubCell.LeftBottom;
|
||||
|
||||
switch (sub)
|
||||
{
|
||||
case SubCell.LeftBottom:
|
||||
res = edge == Edge.Top ? SubCell.LeftTop : SubCell.RightBottom;
|
||||
break;
|
||||
case SubCell.LeftTop:
|
||||
res = edge == Edge.Bottom ? SubCell.LeftBottom : SubCell.RightTop;
|
||||
break;
|
||||
case SubCell.RightBottom:
|
||||
res = edge == Edge.Top ? SubCell.RightTop : SubCell.LeftBottom;
|
||||
break;
|
||||
case SubCell.RightTop:
|
||||
default:
|
||||
res = edge == Edge.Bottom ? SubCell.RightBottom : SubCell.LeftTop;
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static SubCell GetSubCell(Edge inEdge, double value, ValuesInCell vc)
|
||||
{
|
||||
double lb = vc.LeftBottom;
|
||||
double rb = vc.RightBottom;
|
||||
double rt = vc.RightTop;
|
||||
double lt = vc.LeftTop;
|
||||
|
||||
SubCell res = SubCell.LeftBottom;
|
||||
switch (inEdge)
|
||||
{
|
||||
case Edge.Left:
|
||||
res = (Math.Abs(value - lb) < Math.Abs(value - lt)) ? SubCell.LeftBottom : SubCell.LeftTop;
|
||||
break;
|
||||
case Edge.Top:
|
||||
res = (Math.Abs(value - lt) < Math.Abs(value - rt)) ? SubCell.LeftTop : SubCell.RightTop;
|
||||
break;
|
||||
case Edge.Right:
|
||||
res = (Math.Abs(value - rb) < Math.Abs(value - rt)) ? SubCell.RightBottom : SubCell.RightTop;
|
||||
break;
|
||||
case Edge.Bottom:
|
||||
default:
|
||||
res = (Math.Abs(value - lb) < Math.Abs(value - rb)) ? SubCell.LeftBottom : SubCell.RightBottom;
|
||||
break;
|
||||
}
|
||||
|
||||
ValuesInCell subValues = vc.GetSubCell(res);
|
||||
bool valueInside = subValues.ValueBelongTo(value);
|
||||
if (!valueInside)
|
||||
{
|
||||
throw new IsolineGenerationException(Strings.Exceptions.IsolinesDataIsUndetailized);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static Point GetPoint(double value, double a1, double a2, Vector v1, Vector v2)
|
||||
{
|
||||
double ratio = (value - a1) / (a2 - a1);
|
||||
|
||||
Verify.IsTrue(0 <= ratio && ratio <= 1);
|
||||
|
||||
Vector r = (1 - ratio) * v1 + ratio * v2;
|
||||
return new Point(r.X, r.Y);
|
||||
}
|
||||
|
||||
private Point GetPointXY(Edge edge, double value, ValuesInCell vc, IrregularCell rect)
|
||||
{
|
||||
double lt = vc.LeftTop;
|
||||
double lb = vc.LeftBottom;
|
||||
double rb = vc.RightBottom;
|
||||
double rt = vc.RightTop;
|
||||
|
||||
switch (edge)
|
||||
{
|
||||
case Edge.Left:
|
||||
return GetPoint(value, lb, lt, rect.LeftBottom, rect.LeftTop);
|
||||
case Edge.Top:
|
||||
return GetPoint(value, lt, rt, rect.LeftTop, rect.RightTop);
|
||||
case Edge.Right:
|
||||
return GetPoint(value, rb, rt, rect.RightBottom, rect.RightTop);
|
||||
case Edge.Bottom:
|
||||
return GetPoint(value, lb, rb, rect.LeftBottom, rect.RightBottom);
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private bool BelongsToEdge(double value, double edgeValue1, double edgeValue2, bool onBoundary)
|
||||
{
|
||||
if (!Double.IsNaN(missingValue) && (edgeValue1 == missingValue || edgeValue2 == missingValue))
|
||||
return false;
|
||||
|
||||
if (onBoundary)
|
||||
{
|
||||
return (edgeValue1 <= value && value < edgeValue2) ||
|
||||
(edgeValue2 <= value && value < edgeValue1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (edgeValue1 < value && value < edgeValue2) ||
|
||||
(edgeValue2 < value && value < edgeValue1);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPassed(Edge edge, int i, int j, byte[,] edges)
|
||||
{
|
||||
switch (edge)
|
||||
{
|
||||
case Edge.Left:
|
||||
return (i == 0) || (edges[i, j] & (byte)edge) != 0;
|
||||
case Edge.Bottom:
|
||||
return (j == 0) || (edges[i, j] & (byte)edge) != 0;
|
||||
case Edge.Top:
|
||||
return (j == edges.GetLength(1) - 2) || (edges[i, j + 1] & (byte)Edge.Bottom) != 0;
|
||||
case Edge.Right:
|
||||
return (i == edges.GetLength(0) - 2) || (edges[i + 1, j] & (byte)Edge.Left) != 0;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private void MakeEdgePassed(Edge edge, int i, int j)
|
||||
{
|
||||
switch (edge)
|
||||
{
|
||||
case Edge.Left:
|
||||
case Edge.Bottom:
|
||||
edges[i, j] |= (byte)edge;
|
||||
break;
|
||||
case Edge.Top:
|
||||
edges[i, j + 1] |= (byte)Edge.Bottom;
|
||||
break;
|
||||
case Edge.Right:
|
||||
edges[i + 1, j] |= (byte)Edge.Left;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private Edge TrackLine(Edge inEdge, double value, ref int x, ref int y, out double newX, out double newY)
|
||||
{
|
||||
// Getting output edge
|
||||
ValuesInCell vc = (missingValue.IsNaN()) ?
|
||||
(new ValuesInCell(values[x, y],
|
||||
values[x + 1, y],
|
||||
values[x + 1, y + 1],
|
||||
values[x, y + 1])) :
|
||||
(new ValuesInCell(values[x, y],
|
||||
values[x + 1, y],
|
||||
values[x + 1, y + 1],
|
||||
values[x, y + 1],
|
||||
missingValue));
|
||||
|
||||
IrregularCell rect = new IrregularCell(
|
||||
grid[x, y],
|
||||
grid[x + 1, y],
|
||||
grid[x + 1, y + 1],
|
||||
grid[x, y + 1]);
|
||||
|
||||
Edge outEdge = GetOutEdge(inEdge, vc, rect, value);
|
||||
if (outEdge == Edge.None)
|
||||
{
|
||||
newX = newY = -1; // Impossible cell indices
|
||||
return Edge.None;
|
||||
}
|
||||
|
||||
// Drawing new segment
|
||||
Point point = GetPointXY(outEdge, value, vc, rect);
|
||||
newX = point.X;
|
||||
newY = point.Y;
|
||||
segments.AddPoint(point);
|
||||
processed[x, y] = true;
|
||||
|
||||
// Whether out-edge already was passed?
|
||||
if (IsPassed(outEdge, x, y, edges)) // line is closed
|
||||
{
|
||||
//MakeEdgePassed(outEdge, x, y); // boundaries should be marked as passed too
|
||||
return Edge.None;
|
||||
}
|
||||
|
||||
// Make this edge passed
|
||||
MakeEdgePassed(outEdge, x, y);
|
||||
|
||||
// Getting next cell's indices
|
||||
switch (outEdge)
|
||||
{
|
||||
case Edge.Left:
|
||||
x--;
|
||||
return Edge.Right;
|
||||
case Edge.Top:
|
||||
y++;
|
||||
return Edge.Bottom;
|
||||
case Edge.Right:
|
||||
x++;
|
||||
return Edge.Left;
|
||||
case Edge.Bottom:
|
||||
y--;
|
||||
return Edge.Top;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private void TrackLineNonRecursive(Edge inEdge, double value, int x, int y)
|
||||
{
|
||||
int s = x, t = y;
|
||||
|
||||
ValuesInCell vc = (missingValue.IsNaN()) ?
|
||||
(new ValuesInCell(values[x, y],
|
||||
values[x + 1, y],
|
||||
values[x + 1, y + 1],
|
||||
values[x, y + 1])) :
|
||||
(new ValuesInCell(values[x, y],
|
||||
values[x + 1, y],
|
||||
values[x + 1, y + 1],
|
||||
values[x, y + 1],
|
||||
missingValue));
|
||||
|
||||
IrregularCell rect = new IrregularCell(
|
||||
grid[x, y],
|
||||
grid[x + 1, y],
|
||||
grid[x + 1, y + 1],
|
||||
grid[x, y + 1]);
|
||||
|
||||
Point point = GetPointXY(inEdge, value, vc, rect);
|
||||
|
||||
segments.StartLine(point, (value - minMax.Min) / (minMax.Max - minMax.Min), value);
|
||||
|
||||
MakeEdgePassed(inEdge, x, y);
|
||||
|
||||
//processed[x, y] = true;
|
||||
|
||||
double x2, y2;
|
||||
do
|
||||
{
|
||||
inEdge = TrackLine(inEdge, value, ref s, ref t, out x2, out y2);
|
||||
} while (inEdge != Edge.None);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private bool HasIsoline(int x, int y)
|
||||
{
|
||||
return (edges[x,y] != 0 &&
|
||||
((x < edges.GetLength(0) - 1 && edges[x+1,y] != 0) ||
|
||||
(y < edges.GetLength(1) - 1 && edges[x,y+1] != 0)));
|
||||
}
|
||||
|
||||
/// <summary>Finds isoline for specified reference value</summary>
|
||||
/// <param name="value">Reference value</param>
|
||||
private void PrepareCells(double value)
|
||||
{
|
||||
double currentRatio = (value - minMax.Min) / (minMax.Max - minMax.Min);
|
||||
|
||||
if (currentRatio < 0 || currentRatio > 1)
|
||||
return; // No contour lines for such value
|
||||
|
||||
int xSize = dataSource.Width;
|
||||
int ySize = dataSource.Height;
|
||||
int x, y;
|
||||
for (x = 0; x < xSize; x++)
|
||||
for (y = 0; y < ySize; y++)
|
||||
edges[x, y] = 0;
|
||||
|
||||
processed = new bool[xSize, ySize];
|
||||
|
||||
// Looking in boundaries.
|
||||
// left
|
||||
for (y = 1; y < ySize; y++)
|
||||
{
|
||||
if (BelongsToEdge(value, values[0, y - 1], values[0, y], true) &&
|
||||
(edges[0, y - 1] & (byte)Edge.Left) == 0)
|
||||
{
|
||||
TrackLineNonRecursive(Edge.Left, value, 0, y - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// bottom
|
||||
for (x = 0; x < xSize - 1; x++)
|
||||
{
|
||||
if (BelongsToEdge(value, values[x, 0], values[x + 1, 0], true)
|
||||
&& (edges[x, 0] & (byte)Edge.Bottom) == 0)
|
||||
{
|
||||
TrackLineNonRecursive(Edge.Bottom, value, x, 0);
|
||||
};
|
||||
}
|
||||
|
||||
// right
|
||||
x = xSize - 1;
|
||||
for (y = 1; y < ySize; y++)
|
||||
{
|
||||
// Is this correct?
|
||||
//if (BelongsToEdge(value, values[0, y - 1], values[0, y], true) &&
|
||||
// (edges[0, y - 1] & (byte)Edge.Left) == 0)
|
||||
//{
|
||||
// TrackLineNonRecursive(Edge.Left, value, 0, y - 1);
|
||||
//};
|
||||
|
||||
if (BelongsToEdge(value, values[x, y - 1], values[x, y], true) &&
|
||||
(edges[x, y - 1] & (byte)Edge.Left) == 0)
|
||||
{
|
||||
TrackLineNonRecursive(Edge.Right, value, x - 1, y - 1);
|
||||
};
|
||||
}
|
||||
|
||||
// horizontals
|
||||
for (x = 1; x < xSize - 1; x++)
|
||||
for (y = 1; y < ySize - 1; y++)
|
||||
{
|
||||
if ((edges[x, y] & (byte)Edge.Bottom) == 0 &&
|
||||
BelongsToEdge(value, values[x, y], values[x + 1, y], false) &&
|
||||
!processed[x,y-1])
|
||||
{
|
||||
TrackLineNonRecursive(Edge.Top, value, x, y-1);
|
||||
}
|
||||
if ((edges[x, y] & (byte)Edge.Bottom) == 0 &&
|
||||
BelongsToEdge(value, values[x, y], values[x + 1, y], false) &&
|
||||
!processed[x,y])
|
||||
{
|
||||
TrackLineNonRecursive(Edge.Bottom, value, x, y);
|
||||
}
|
||||
if ((edges[x, y] & (byte)Edge.Left) == 0 &&
|
||||
BelongsToEdge(value, values[x, y], values[x, y - 1], false) &&
|
||||
!processed[x-1,y-1])
|
||||
{
|
||||
TrackLineNonRecursive(Edge.Right, value, x - 1, y - 1);
|
||||
}
|
||||
if ((edges[x, y] & (byte)Edge.Left) == 0 &&
|
||||
BelongsToEdge(value, values[x, y], values[x, y - 1], false) &&
|
||||
!processed[x,y-1])
|
||||
{
|
||||
TrackLineNonRecursive(Edge.Left, value, x, y - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds isoline data for 2d scalar field contained in data source.
|
||||
/// </summary>
|
||||
/// <returns>Collection of data describing built isolines.</returns>
|
||||
public IsolineCollection BuildIsoline()
|
||||
{
|
||||
VerifyDataSource();
|
||||
|
||||
segments = new IsolineCollection();
|
||||
|
||||
minMax = (Double.IsNaN(missingValue) ? dataSource.GetMinMax() : dataSource.GetMinMax(missingValue));
|
||||
|
||||
segments.Min = minMax.Min;
|
||||
segments.Max = minMax.Max;
|
||||
|
||||
if (!minMax.IsEmpty)
|
||||
{
|
||||
values = dataSource.Data;
|
||||
double[] levels = GetLevelsForIsolines();
|
||||
|
||||
foreach (double level in levels)
|
||||
{
|
||||
PrepareCells(level);
|
||||
}
|
||||
|
||||
if (segments.Lines.Count > 0 && segments.Lines[segments.Lines.Count - 1].OtherPoints.Count == 0)
|
||||
segments.Lines.RemoveAt(segments.Lines.Count - 1);
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds isoline data for the specified level in 2d scalar field.
|
||||
/// </summary>
|
||||
/// <param name="level">The level.</param>
|
||||
/// <returns></returns>
|
||||
public IsolineCollection BuildIsoline(double level)
|
||||
{
|
||||
VerifyDataSource();
|
||||
|
||||
segments = new IsolineCollection();
|
||||
|
||||
minMax = (Double.IsNaN(missingValue) ? dataSource.GetMinMax() : dataSource.GetMinMax(missingValue));
|
||||
if (!minMax.IsEmpty)
|
||||
{
|
||||
values = dataSource.Data;
|
||||
|
||||
|
||||
PrepareCells(level);
|
||||
|
||||
if (segments.Lines.Count > 0 && segments.Lines[segments.Lines.Count - 1].OtherPoints.Count == 0)
|
||||
segments.Lines.RemoveAt(segments.Lines.Count - 1);
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
private void VerifyDataSource()
|
||||
{
|
||||
if (dataSource == null)
|
||||
throw new InvalidOperationException(Strings.Exceptions.IsolinesDataSourceShouldBeSet);
|
||||
}
|
||||
|
||||
IsolineCollection segments;
|
||||
|
||||
private double[,] values;
|
||||
private byte[,] edges;
|
||||
private Point[,] grid;
|
||||
|
||||
private Range<double> minMax;
|
||||
private IDataSource2D<double> dataSource;
|
||||
/// <summary>
|
||||
/// Gets or sets the data source - 2d scalar field.
|
||||
/// </summary>
|
||||
/// <value>The data source.</value>
|
||||
public IDataSource2D<double> DataSource
|
||||
{
|
||||
get { return dataSource; }
|
||||
set
|
||||
{
|
||||
if (dataSource != value)
|
||||
{
|
||||
value.VerifyNotNull("value");
|
||||
|
||||
dataSource = value;
|
||||
grid = dataSource.Grid;
|
||||
edges = new byte[dataSource.Width, dataSource.Height];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const double shiftPercent = 0.05;
|
||||
private double[] GetLevelsForIsolines()
|
||||
{
|
||||
double[] levels;
|
||||
double min = minMax.Min;
|
||||
double max = minMax.Max;
|
||||
|
||||
double step = (max - min) / (density - 1);
|
||||
double delta = (max - min);
|
||||
|
||||
levels = new double[density];
|
||||
levels[0] = min + delta * shiftPercent;
|
||||
levels[levels.Length - 1] = max - delta * shiftPercent;
|
||||
|
||||
for (int i = 1; i < levels.Length - 1; i++)
|
||||
levels[i] = min + i * step;
|
||||
|
||||
return levels;
|
||||
}
|
||||
}
|
||||
}
|
||||
159
Charts/Isolines/IsolineCollection.cs
Normal file
159
Charts/Isolines/IsolineCollection.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Collections;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
|
||||
{
|
||||
/// <summary>
|
||||
/// LevelLine contains all data for one isoline line - its start point, other points and value in field.
|
||||
/// </summary>
|
||||
public sealed class LevelLine
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the value of line in limits of [0..1].
|
||||
/// </summary>
|
||||
/// <value>The value01.</value>
|
||||
public double Value01 { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the real value of line - without scaling to [0..1] segment.
|
||||
/// </summary>
|
||||
/// <value>The real value.</value>
|
||||
public double RealValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the start point of line.
|
||||
/// </summary>
|
||||
/// <value>The start point.</value>
|
||||
public Point StartPoint { get; set; }
|
||||
|
||||
private readonly List<Point> otherPoints = new List<Point>();
|
||||
/// <summary>
|
||||
/// Gets other points of line, except first point.
|
||||
/// </summary>
|
||||
/// <value>The other points.</value>
|
||||
public List<Point> OtherPoints
|
||||
{
|
||||
get { return otherPoints; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all points of line, including start point.
|
||||
/// </summary>
|
||||
/// <value>All points.</value>
|
||||
public IEnumerable<Point> AllPoints
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return StartPoint;
|
||||
for (int i = 0; i < otherPoints.Count; i++)
|
||||
{
|
||||
yield return otherPoints[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the segments of lines.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Range<Point>> GetSegments()
|
||||
{
|
||||
if (otherPoints.Count < 1)
|
||||
yield break;
|
||||
|
||||
yield return new Range<Point>(StartPoint, otherPoints[0]);
|
||||
for (int i = 1; i < otherPoints.Count; i++)
|
||||
{
|
||||
yield return new Range<Point>(otherPoints[i - 1], otherPoints[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IsolineTextLabel contains information about one label in isoline - its text, position and rotation.
|
||||
/// </summary>
|
||||
public sealed class IsolineTextLabel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the rotation of isoline text label.
|
||||
/// </summary>
|
||||
/// <value>The rotation.</value>
|
||||
public double Rotation { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the text of isoline label.
|
||||
/// </summary>
|
||||
/// <value>The text.</value>
|
||||
public double Value { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the position of isoline text label.
|
||||
/// </summary>
|
||||
/// <value>The position.</value>
|
||||
public Point Position { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collection which contains all data generated by <seealso cref="IsolineBuilder"/>.
|
||||
/// </summary>
|
||||
public sealed class IsolineCollection : IEnumerable<LevelLine>
|
||||
{
|
||||
private double min;
|
||||
public double Min
|
||||
{
|
||||
get { return min; }
|
||||
set { min = value; }
|
||||
}
|
||||
|
||||
private double max;
|
||||
public double Max
|
||||
{
|
||||
get { return max; }
|
||||
set { max = value; }
|
||||
}
|
||||
|
||||
private readonly List<LevelLine> lines = new List<LevelLine>();
|
||||
/// <summary>
|
||||
/// Gets the list of isoline lines.
|
||||
/// </summary>
|
||||
/// <value>The lines.</value>
|
||||
public List<LevelLine> Lines
|
||||
{
|
||||
get { return lines; }
|
||||
}
|
||||
|
||||
internal void StartLine(Point p, double value01, double realValue)
|
||||
{
|
||||
LevelLine segment = new LevelLine { StartPoint = p, Value01 = value01, RealValue = realValue };
|
||||
if (lines.Count == 0 || lines[lines.Count - 1].OtherPoints.Count > 0)
|
||||
lines.Add(segment);
|
||||
else
|
||||
lines[lines.Count - 1] = segment;
|
||||
|
||||
}
|
||||
|
||||
internal void AddPoint(Point p)
|
||||
{
|
||||
lines[lines.Count - 1].OtherPoints.Add(p);
|
||||
}
|
||||
|
||||
#region IEnumerable<LevelLine> Members
|
||||
|
||||
public IEnumerator<LevelLine> GetEnumerator()
|
||||
{
|
||||
return lines.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
20
Charts/Isolines/IsolineGenerationException.cs
Normal file
20
Charts/Isolines/IsolineGenerationException.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception that is thrown when error occurs while building isolines.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class IsolineGenerationException : Exception
|
||||
{
|
||||
internal IsolineGenerationException() { }
|
||||
internal IsolineGenerationException(string message) : base(message) { }
|
||||
internal IsolineGenerationException(string message, Exception inner) : base(message, inner) { }
|
||||
internal IsolineGenerationException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
||||
}
|
||||
}
|
||||
258
Charts/Isolines/IsolineGraph.cs
Normal file
258
Charts/Isolines/IsolineGraph.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Isolines;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Draws isolines on given two-dimensional scalar data.
|
||||
/// </summary>
|
||||
public sealed class IsolineGraph : IsolineRenderer
|
||||
{
|
||||
private static Brush labelBackground = new SolidColorBrush(Color.FromArgb(130, 255, 255, 255));
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IsolineGraph"/> class.
|
||||
/// </summary>
|
||||
public IsolineGraph()
|
||||
{
|
||||
Content = content;
|
||||
Viewport2D.SetIsContentBoundsHost(this, true);
|
||||
}
|
||||
|
||||
protected override void OnPlotterAttached()
|
||||
{
|
||||
CreateUIRepresentation();
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
private readonly Canvas content = new Canvas();
|
||||
|
||||
protected override void UpdateDataSource()
|
||||
{
|
||||
base.UpdateDataSource();
|
||||
|
||||
CreateUIRepresentation();
|
||||
rebuildText = true;
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
protected override void OnLineThicknessChanged()
|
||||
{
|
||||
foreach (var path in linePaths)
|
||||
{
|
||||
path.StrokeThickness = StrokeThickness;
|
||||
}
|
||||
}
|
||||
|
||||
private List<FrameworkElement> textBlocks = new List<FrameworkElement>();
|
||||
private List<Path> linePaths = new List<Path>();
|
||||
protected override void CreateUIRepresentation()
|
||||
{
|
||||
if (Plotter2D == null)
|
||||
return;
|
||||
|
||||
content.Children.Clear();
|
||||
linePaths.Clear();
|
||||
|
||||
if (Collection != null)
|
||||
{
|
||||
DataRect bounds = DataRect.Empty;
|
||||
|
||||
foreach (var line in Collection.Lines)
|
||||
{
|
||||
foreach (var point in line.AllPoints)
|
||||
{
|
||||
bounds.Union(point);
|
||||
}
|
||||
|
||||
Path path = new Path
|
||||
{
|
||||
Stroke = new SolidColorBrush(Palette.GetColor(line.Value01)),
|
||||
StrokeThickness = StrokeThickness,
|
||||
Data = CreateGeometry(line),
|
||||
Tag = line
|
||||
};
|
||||
content.Children.Add(path);
|
||||
linePaths.Add(path);
|
||||
}
|
||||
|
||||
Viewport2D.SetContentBounds(this, bounds);
|
||||
|
||||
if (DrawLabels)
|
||||
{
|
||||
var transform = Plotter2D.Viewport.Transform;
|
||||
double wayBeforeText = new Rect(new Size(2000, 2000)).ScreenToData(transform).Width;
|
||||
Annotater.WayBeforeText = wayBeforeText;
|
||||
var textLabels = Annotater.Annotate(Collection, Plotter2D.Viewport.Visible);
|
||||
|
||||
foreach (var textLabel in textLabels)
|
||||
{
|
||||
var text = CreateTextLabel(textLabel);
|
||||
content.Children.Add(text);
|
||||
textBlocks.Add(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private FrameworkElement CreateTextLabel(IsolineTextLabel textLabel)
|
||||
{
|
||||
var transform = Plotter2D.Viewport.Transform;
|
||||
Point screenPos = textLabel.Position.DataToScreen(transform);
|
||||
|
||||
double angle = textLabel.Rotation;
|
||||
if (angle < 0)
|
||||
angle += 360;
|
||||
if (135 < angle && angle < 225)
|
||||
angle -= 180;
|
||||
|
||||
string tooltip = textLabel.Value.ToString("F"); //String.Format("{0} at ({1}, {2})", textLabel.Text, textLabel.Position.X, textLabel.Position.Y);
|
||||
Grid grid = new Grid
|
||||
{
|
||||
RenderTransform = new RotateTransform(angle),
|
||||
Tag = textLabel,
|
||||
RenderTransformOrigin = new Point(0.5, 0.5),
|
||||
ToolTip = tooltip
|
||||
};
|
||||
|
||||
TextBlock res = new TextBlock
|
||||
{
|
||||
Text = textLabel.Value.ToString("F"),
|
||||
Margin = new Thickness(3,1,3,1)
|
||||
};
|
||||
|
||||
//res.Measure(SizeHelper.CreateInfiniteSize());
|
||||
|
||||
Rectangle rect = new Rectangle
|
||||
{
|
||||
Stroke = Brushes.Gray,
|
||||
Fill = labelBackground,
|
||||
RadiusX = 8,
|
||||
RadiusY = 8
|
||||
};
|
||||
|
||||
grid.Children.Add(rect);
|
||||
grid.Children.Add(res);
|
||||
|
||||
grid.Measure(SizeHelper.CreateInfiniteSize());
|
||||
|
||||
Size textSize = grid.DesiredSize;
|
||||
Point position = new Point(screenPos.X - textSize.Width / 2, screenPos.Y - textSize.Height / 2);
|
||||
|
||||
Canvas.SetLeft(grid, position.X);
|
||||
Canvas.SetTop(grid, position.Y);
|
||||
return grid;
|
||||
}
|
||||
|
||||
private Geometry CreateGeometry(LevelLine lineData)
|
||||
{
|
||||
var transform = Plotter2D.Viewport.Transform;
|
||||
|
||||
StreamGeometry geometry = new StreamGeometry();
|
||||
using (var context = geometry.Open())
|
||||
{
|
||||
context.BeginFigure(lineData.StartPoint.DataToScreen(transform), false, false);
|
||||
context.PolyLineTo(lineData.OtherPoints.DataToScreenAsList(transform), true, true);
|
||||
}
|
||||
geometry.Freeze();
|
||||
return geometry;
|
||||
}
|
||||
|
||||
private bool rebuildText = true;
|
||||
protected override void OnViewportPropertyChanged(ExtendedPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == "Visible" || e.PropertyName == "Output")
|
||||
{
|
||||
bool isVisibleChanged = e.PropertyName == "Visible";
|
||||
DataRect prevRect = isVisibleChanged ? (DataRect)e.OldValue : new DataRect((Rect)e.OldValue);
|
||||
DataRect currRect = isVisibleChanged ? (DataRect)e.NewValue : new DataRect((Rect)e.NewValue);
|
||||
|
||||
// completely rebuild text only if width and height have changed many
|
||||
const double smallChangePercent = 0.05;
|
||||
bool widthChangedLittle = Math.Abs(currRect.Width - prevRect.Width) / currRect.Width < smallChangePercent;
|
||||
bool heightChangedLittle = Math.Abs(currRect.Height - prevRect.Height) / currRect.Height < smallChangePercent;
|
||||
|
||||
rebuildText = !(widthChangedLittle && heightChangedLittle);
|
||||
}
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
private void UpdateUIRepresentation()
|
||||
{
|
||||
if (Plotter2D == null) return;
|
||||
|
||||
foreach (var path in linePaths)
|
||||
{
|
||||
LevelLine line = (LevelLine)path.Tag;
|
||||
path.Data = CreateGeometry(line);
|
||||
}
|
||||
|
||||
var transform = Plotter2D.Viewport.Transform;
|
||||
Rect output = Plotter2D.Viewport.Output;
|
||||
DataRect visible = Plotter2D.Viewport.Visible;
|
||||
|
||||
if (rebuildText && DrawLabels)
|
||||
{
|
||||
rebuildText = false;
|
||||
foreach (var text in textBlocks)
|
||||
{
|
||||
if (text.Visibility == Visibility.Visible)
|
||||
content.Children.Remove(text);
|
||||
}
|
||||
textBlocks.Clear();
|
||||
|
||||
double wayBeforeText = new Rect(new Size(100, 100)).ScreenToData(transform).Width;
|
||||
Annotater.WayBeforeText = wayBeforeText;
|
||||
var textLabels = Annotater.Annotate(Collection, Plotter2D.Viewport.Visible);
|
||||
foreach (var textLabel in textLabels)
|
||||
{
|
||||
var text = CreateTextLabel(textLabel);
|
||||
textBlocks.Add(text);
|
||||
if (visible.Contains(textLabel.Position))
|
||||
{
|
||||
content.Children.Add(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Visibility = Visibility.Hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var text in textBlocks)
|
||||
{
|
||||
IsolineTextLabel label = (IsolineTextLabel)text.Tag;
|
||||
Point screenPos = label.Position.DataToScreen(transform);
|
||||
Size textSize = text.DesiredSize;
|
||||
|
||||
Point position = new Point(screenPos.X - textSize.Width / 2, screenPos.Y - textSize.Height / 2);
|
||||
|
||||
if (output.Contains(position))
|
||||
{
|
||||
Canvas.SetLeft(text, position.X);
|
||||
Canvas.SetTop(text, position.Y);
|
||||
if (text.Visibility == Visibility.Hidden)
|
||||
{
|
||||
text.Visibility = Visibility.Visible;
|
||||
content.Children.Add(text);
|
||||
}
|
||||
}
|
||||
else if (text.Visibility == Visibility.Visible)
|
||||
{
|
||||
text.Visibility = Visibility.Hidden;
|
||||
content.Children.Remove(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user