CPF/CPF/Charts/Chart.cs
新无止竞 5f52372fb4 修正CPF
2024-06-12 20:47:35 +08:00

954 lines
41 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using CPF.Controls;
using CPF.Drawing;
using CPF.Input;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace CPF.Charts
{
/// <summary>
/// 提供折线图,曲线图,柱状图
/// </summary>
[Description("提供折线图,曲线图,柱状图")]
public class Chart : Control
{
public Chart()
{
Data = new Collection<IChartData>();
XAxis = new Collection<string>();
}
protected override void InitializeComponent()
{
Children.Add(new WrapPanel
{
Name = "seriesPanel",
PresenterFor = this,
Orientation = Orientation.Horizontal,
MaxWidth = "100%",
MarginTop = -30,
});
Children.Add(new StackPanel
{
Name = "tipPanel",
PresenterFor = this,
Orientation = Orientation.Vertical,
Visibility = Visibility.Collapsed,
IsHitTestVisible = false,
Background = "#ffffff99",
BorderFill = "#00000055",
BorderStroke = new Stroke(1),
Children =
{
new TextBlock
{
Name="tipName",
PresenterFor=this,
MarginLeft=3,
},
new StackPanel
{
Name = "tipListPanel",
PresenterFor = this,
Orientation= Orientation.Vertical,
Margin="5",
}
}
});
}
Panel seriesPanel;
Panel tipPanel;
TextBlock tipName;
Panel tipListPanel;
protected override void OnInitialized()
{
base.OnInitialized();
seriesPanel = FindPresenterByName<Panel>("seriesPanel");
tipPanel = FindPresenterByName<Panel>("tipPanel");
tipName = FindPresenterByName<TextBlock>("tipName");
tipListPanel = FindPresenterByName<Panel>("tipListPanel");
}
private void Data_CollectionChanged(object sender, CollectionChangedEventArgs<IChartData> e)
{
switch (e.Action)
{
case CollectionChangedAction.Add:
e.NewItem.SetOwnerChart(this);
e.NewItem.PropertyChanged += NewItem_PropertyChanged;
break;
case CollectionChangedAction.Remove:
e.OldItem.PropertyChanged -= NewItem_PropertyChanged;
break;
case CollectionChangedAction.Replace:
e.NewItem.SetOwnerChart(this);
e.NewItem.PropertyChanged += NewItem_PropertyChanged;
e.OldItem.PropertyChanged -= NewItem_PropertyChanged;
break;
}
InvalidateData();
}
private void NewItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
InvalidateData();
}
/// <summary>
/// 数据
/// </summary>
public IList<IChartData> Data
{
get { return GetValue<IList<IChartData>>(); }
set { SetValue(value); }
}
/// <summary>
/// 水平缩放值 大于等于1
/// </summary>
[Description("水平缩放值 大于等于1"), PropertyMetadata(1f)]
public float HorizontalScaling
{
get { return GetValue<float>(); }
set
{
SetValue(value);
}
}
[PropertyChanged(nameof(HorizontalScaling))]
[PropertyChanged(nameof(ScrollValue))]
[PropertyChanged(nameof(XAxis))]
[PropertyChanged(nameof(MaxValue))]
[PropertyChanged(nameof(MinValue))]
void OnUpdateData(object newValue, object oldValue, PropertyMetadataAttribute attribute)
{
InvalidateData();
}
[PropertyChanged(nameof(Data))]
void OnDataChanged(object newValue, object oldValue, PropertyMetadataAttribute attribute)
{
var nv = newValue as Collection<IChartData>;
if (nv != null)
{
foreach (var item in nv)
{
Data_CollectionChanged(nv, new CollectionChangedEventArgs<IChartData>(item, 0, null, CollectionChangedAction.Add));
}
nv.CollectionChanged += Data_CollectionChanged;
}
else
{
var data = new Collection<IChartData>();
Data = data;
var list = newValue as IEnumerable<IChartData>;
if (list != null)
{
foreach (var item in list)
{
data.Add(item);
}
}
}
}
/// <summary>
/// 滚动值
/// </summary>
[Browsable(false)]
public float ScrollValue
{
get { return GetValue<float>(); }
set
{
SetValue(value);
}
}
protected override bool OnSetValue(string propertyName, ref object value)
{
if (propertyName == nameof(ScrollValue))
{
var scrollValue = (float)value;
if (scrollValue > 1)
{
scrollValue = 1;
}
else if (scrollValue < 0)
{
scrollValue = 0;
}
value = scrollValue;
}
else if (propertyName == nameof(HorizontalScaling))
{
var horizontalScaling = (float)value;
if (horizontalScaling < 1)
{
horizontalScaling = 1;
}
var xA = XAxis;
if (xA.Count > 0)
{
if (xA.Count / 2 < horizontalScaling)
{
horizontalScaling = Math.Max(1, xA.Count / 2);
}
}
value = horizontalScaling;
}
else if (propertyName == nameof(XAxis))
{
Collection<string> vs = value as Collection<string>;
if (vs == null)
{
var xa = XAxis as Collection<string>;
xa.Clear();
var list = value as IList<string>;
if (list != null)
{
xa.AddRange(list);
}
}
else
{
vs.CollectionChanged += Vs_CollectionChanged;
}
}
return base.OnSetValue(propertyName, ref value);
}
private void Vs_CollectionChanged(object sender, CollectionChangedEventArgs<string> e)
{
InvalidateData();
}
bool refreshData;
/// <summary>
/// 刷新数据,下次更新的时候更新
/// </summary>
public void InvalidateData()
{
if (!refreshData)
{
refreshData = true;
BeginInvoke(() =>
{
UpdateData();
});
}
}
///// <summary>
///// 图表区域内部间距
///// </summary>
//[Description("图表区域内部间距"), UIPropertyMetadata(typeof(Thickness), "50, 30, 10, 30",UIPropertyOptions.AffectsRender)]
//public Thickness Padding
//{
// get { return GetValue<Thickness>(); }
// set
// {
// SetValue(value);
// }
//}
/// <summary>
/// X轴颜色
/// </summary>
[Description("X轴颜色"), UIPropertyMetadata(typeof(ViewFill), "Black", UIPropertyOptions.AffectsRender)]
public ViewFill XAxisFill
{
get { return GetValue<ViewFill>(); }
set { SetValue(value); }
}
/// <summary>
/// Y轴颜色
/// </summary>
[Description("Y轴颜色"), UIPropertyMetadata(typeof(ViewFill), "Black", UIPropertyOptions.AffectsRender)]
public ViewFill YAxisFill
{
get { return GetValue<ViewFill>(); }
set { SetValue(value); }
}
/// <summary>
/// 图表区域填充
/// </summary>
[Description("图表区域填充"), UIPropertyMetadata(null, UIPropertyOptions.AffectsRender)]
public ViewFill ChartFill
{
get { return GetValue<ViewFill>(); }
set { SetValue(value); }
}
/// <summary>
/// 是否可以缩放滚动
/// </summary>
[Description("是否可以缩放滚动"), UIPropertyMetadata(false, UIPropertyOptions.AffectsRender)]
public bool CanScroll
{
get { return GetValue<bool>(); }
set { SetValue(value); }
}
/// <summary>
/// X轴文字
/// </summary>
[Description("X轴文字")]
public IList<string> XAxis
{
get { return GetValue<IList<string>>(); }
set { SetValue(value); }
}
double maxValue = 0;
double minValue = 0;
/// <summary>
/// Y轴最大值
/// </summary>
[Description("Y轴最大值")]
public double? MaxValue
{
get { return GetValue<double?>(); }
set { SetValue(value); }
}
/// <summary>
/// Y轴最小值
/// </summary>
[Description("Y轴最小值")]
public double? MinValue
{
get { return GetValue<double?>(); }
set { SetValue(value); }
}
/// <summary>
/// Y轴刻度分割数量大于等于1
/// </summary>
[Description("Y轴刻度分割数量大于等于1"), UIPropertyMetadata(1u, UIPropertyOptions.AffectsRender)]
public uint YAxisScaleCount
{
get { return GetValue<uint>(); }
set { SetValue(value); }
}
/// <summary>
/// 鼠标移入选中的线条填充
/// </summary>
[Description("鼠标移入选中的线条填充"), UIPropertyMetadata(typeof(ViewFill), "0, 0, 0, 50", UIPropertyOptions.AffectsRender)]
public ViewFill HoverSelectLineFill
{
get { return GetValue<ViewFill>(); }
set { SetValue(value); }
}
/// <summary>
/// 鼠标移入选中的坐标轴提示背景填充
/// </summary>
[Description("鼠标移入选中的坐标轴提示背景填充"), UIPropertyMetadata(typeof(ViewFill), "0, 0, 0, 100", UIPropertyOptions.AffectsRender)]
public ViewFill HoverSelectTipBackFill
{
get { return GetValue<ViewFill>(); }
set { SetValue(value); }
}
/// <summary>
/// 鼠标移入选中的坐标轴提示文字填充
/// </summary>
[Description("鼠标移入选中的坐标轴提示文字填充"), UIPropertyMetadata(typeof(ViewFill), "White", UIPropertyOptions.AffectsRender)]
public ViewFill HoverSelectTipFill
{
get { return GetValue<ViewFill>(); }
set { SetValue(value); }
}
/// <summary>
/// 网格显示模式
/// </summary>
[Description("网格显示模式"), UIPropertyMetadata(GridShowMode.None, UIPropertyOptions.AffectsRender)]
public GridShowMode GridShowMode
{
get { return GetValue<GridShowMode>(); }
set { SetValue(value); }
}
/// <summary>
/// 网格填充
/// </summary>
[Description("网格填充"), UIPropertyMetadata(typeof(ViewFill), "Silver", UIPropertyOptions.AffectsRender)]
public ViewFill GridFill
{
get { return GetValue<ViewFill>(); }
set { SetValue(value); }
}
/// <summary>
/// 显示滚动缩放值的线条填充
/// </summary>
[Description("显示滚动缩放值的线条填充"), UIPropertyMetadata(typeof(ViewFill), "Red", UIPropertyOptions.AffectsRender)]
public ViewFill ScrollLineFill
{
get { return GetValue<ViewFill>(); }
set { SetValue(value); }
}
/// <summary>
/// 鼠标移入图表的时候显示信息
/// </summary>
[Description("鼠标移入图表的时候显示信息"), PropertyMetadata(true)]
public bool MouseHoverShowTip
{
get { return GetValue<bool>(); }
set { SetValue(value); }
}
/// <summary>
/// 是否有数据
/// </summary>
[Description("是否有数据")]
public bool HasData
{
get { return GetValue<bool>(); }
private set { SetValue(value); }
}
/// <summary>
/// 图例模板
/// </summary>
public UIElementTemplate<SerieItem> SerieTemplate
{
get { return GetValue<UIElementTemplate<SerieItem>>(); }
set { SetValue(value); }
}
protected override void OnOverrideMetadata(OverrideMetadata overridePropertys)
{
base.OnOverrideMetadata(overridePropertys);
overridePropertys.Override(nameof(SerieTemplate), new PropertyMetadataAttribute((UIElementTemplate<SerieItem>)typeof(SerieItem)));
overridePropertys.Override(nameof(Padding), new UIPropertyMetadataAttribute(new Thickness(30), UIPropertyOptions.AffectsMeasure));
}
Point mouseHover = new Point();
//Direction direction = Direction.LeftToRight;
/// <summary>
/// 添加数据之后要调用该方法来更新界面
/// </summary>
public void UpdateData()
{
var data = Data;
HasData = data.Count > 0;
hoverIndex = -1;
var xAxis = XAxis;
if (xAxis == null || xAxis.Count == 0)
{
maxValue = 0;
minValue = 0;
return;
}
int startIndex, length;
GetRange(HorizontalScaling, ScrollValue, xAxis.Count, out startIndex, out length);
double max = 0;
double min = 0;
foreach (IChartData item in data)
{
max = Math.Max(item.GetMaxValue(startIndex, length), max);
min = Math.Min(item.GetMinValue(startIndex, length), min);
}
if (MaxValue.HasValue)
{
max = MaxValue.Value;
}
if (MinValue.HasValue)
{
min = MinValue.Value;
}
maxValue = max;
minValue = min;
refreshData = false;
Invalidate();
var template = SerieTemplate;
if (seriesPanel && template != null)
{
seriesPanel.Children.Clear();
foreach (var item in data)
{
var c = template.CreateElement();
c.DataContext = item;
c.Name = item.Name;
seriesPanel.Children.Add(c);
}
}
}
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
var g = dc;
var fillBrush = ChartFill;
var padding = Padding;
var ownerSize = ActualSize;
var bounds = new Rect(new Point(), ownerSize);
if (fillBrush != null)
{
using (var fb = fillBrush.CreateBrush(bounds, Root.RenderScaling))
{
g.FillRectangle(fb, new Rect(padding.Left, padding.Top, ownerSize.Width - padding.Horizontal, ownerSize.Height - padding.Vertical));
}
}
var xAxis = XAxis;
var _size = new Size(Math.Max(0, ownerSize.Width - padding.Horizontal), Math.Max(0, ownerSize.Height - padding.Vertical));
if (xAxis == null || xAxis.Count == 0)
{
return;
}
var data = Data;
int startIndex, length;
var horizontalScaling = HorizontalScaling;
var scrollValue = ScrollValue;
var yAxisColor = YAxisFill;
var gridColor = GridFill;
var gridShowMode = GridShowMode;
var scrollLineColor = ScrollLineFill;
var xAxisColor = XAxisFill;
var mouseHoverShowTip = MouseHoverShowTip;
var hoverSelectTipColor = HoverSelectTipFill;
var hoverSelectTipBackColor = HoverSelectTipBackFill;
var hoverSelectLineColor = HoverSelectLineFill;
var yAxisScaleCount = YAxisScaleCount;
GetRange(horizontalScaling, scrollValue, xAxis.Count, out startIndex, out length);
Font Font = new Font(FontFamily, FontSize, FontStyle);
var mustOffset = Data.Any(a => a is ChartBarData);
using (Brush foreColorBrush = Foreground == null ? new SolidColorBrush(Color.Transparent) : Foreground.CreateBrush(bounds, Root.RenderScaling))
{
using (var gpen = gridColor.CreateBrush(bounds, Root.RenderScaling))
using (var yApen = yAxisColor.CreateBrush(bounds, Root.RenderScaling))
{
var size = new Size();
var max = maxValue;
if (minValue == maxValue)
{
max = minValue + 1;
}
g.DrawLine(new Stroke(1), yApen, new Point(padding.Left, padding.Top), new Point(padding.Left, ownerSize.Height - padding.Bottom));
var v = (max - minValue) / yAxisScaleCount;
var offset1 = v / (max - minValue) * (ownerSize.Height - padding.Vertical);
for (int i = 0; i < yAxisScaleCount + 1; i++)
{
g.DrawLine(new Stroke(1), yApen, new Point(padding.Left - 3, (int)(padding.Top + offset1 * i)), new Point(padding.Left, (int)(padding.Top + offset1 * i)));
if (gridShowMode == GridShowMode.Horizontal || gridShowMode == GridShowMode.All)
{
g.DrawLine(new Stroke(1), gpen, new Point(ownerSize.Width - padding.Right, (int)(padding.Top + offset1 * i)), new Point(padding.Left, (int)(padding.Top + offset1 * i)));
}
if (i != 0 && i != yAxisScaleCount)
{
var vv = max - v * i;
var s = g.DrawingFactory.MeasureString(vv.ToString("0.##"), Font);
g.DrawString(new Point(padding.Left - s.Width - 3, (float)(padding.Top + offset1 * i - s.Height / 2)), foreColorBrush, vv.ToString("0.##"), Font);
}
}
size = g.DrawingFactory.MeasureString(max.ToString(), Font);
g.DrawString(new Point(padding.Left - size.Width - 3, padding.Top - size.Height / 2), foreColorBrush, max.ToString(), Font);
size = g.DrawingFactory.MeasureString(minValue.ToString(), Font);
g.DrawString(new Point(padding.Left - size.Width - 3, ownerSize.Height - padding.Bottom - size.Height / 2), foreColorBrush, minValue.ToString(), Font);
var offsetWidth = 1.0 * _size.Width / (length - (mustOffset ? 0 : 1));
var paddingLeft = mustOffset ? (padding.Left + (int)(offsetWidth / 2)) : (padding.Left);
var width = mustOffset ? (ownerSize.Width - padding.Horizontal - offsetWidth) : (ownerSize.Width - padding.Horizontal);
foreach (IChartData item in data)
{
if (startIndex > item.DataCount - 1)
{
continue;
}
var len = length;
//if (startIndex + length > item.DataCount)
//{
// len = item.DataCount - startIndex;
//}
item.PaintBackground(dc, startIndex, length,
new Rect(padding.Left + (mustOffset ? (int)(offsetWidth / 2) : 0),
padding.Top,
Math.Max(0, (int)((len - 1) * offsetWidth)),
_size.Height), maxValue, minValue, bounds, Root.RenderScaling);
}
using (var pen = xAxisColor.CreateBrush(bounds, Root.RenderScaling))
{
g.DrawLine(new Stroke(CanScroll ? 5 : 1), pen, new Point(padding.Left, ownerSize.Height - padding.Bottom), new Point(ownerSize.Width - padding.Right, ownerSize.Height - padding.Bottom));
}
if (CanScroll)
{
using (var p = scrollLineColor.CreateBrush(bounds, Root.RenderScaling))
{
var offset = (ownerSize.Width - padding.Horizontal - (ownerSize.Width - padding.Horizontal) / horizontalScaling) * scrollValue;
g.DrawLine(new Stroke(3), p, new Point(padding.Left + (int)offset, ownerSize.Height - padding.Bottom), new Point(padding.Left + (int)(offset + (ownerSize.Width - padding.Horizontal) / horizontalScaling), ownerSize.Height - padding.Bottom));
}
}
//GetRange(horizontalScaling, scrollValue, xAxis.Length, out int startIndex, out int length);
if (length > 1)
{
//using (StringFormat stringFormat = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center })
{
size = g.DrawingFactory.MeasureString(xAxis[startIndex], Font);
//g.DrawString(xAxis[startIndex], Font, foreColorBrush, new RectangleF(paddingLeft - (size.Width + size.Width / 5) / 2,ownerSize.Height - padding.Bottom + Font.Height / 4, size.Width + size.Width / 5, size.Height + size.Height / 5), stringFormat);
var w1 = size.Width;
size = g.DrawingFactory.MeasureString(xAxis[startIndex + length - 1], Font);
//g.DrawString(xAxis[startIndex + length - 1], Font, foreColorBrush, new RectangleF(paddingLeft + (int)width - (size.Width + size.Width / 5) / 2,ownerSize.Height - padding.Bottom + Font.Height / 4, size.Width + size.Width / 5, size.Height + size.Height / 5), stringFormat);
if (gridShowMode == GridShowMode.Vertical || gridShowMode == GridShowMode.All)
{
g.DrawLine(new Stroke(1), gpen, new Point(ownerSize.Width - padding.Right, ownerSize.Height - padding.Bottom), new Point(ownerSize.Width - padding.Right, padding.Top));
}
if (length > 1)
{
var count = (width + size.Width) / ((w1 + size.Width) / 2 + Font.LineHeight);
var offset = 1.0 * (width) / (length - 1);
var of = length / count;
if (of < 1)
{
of = 1;
}
double index = startIndex;
var w = 0f;
double preOffset = paddingLeft - w1 / 2;
while (index < startIndex + length && (((int)index - startIndex) * offset + paddingLeft + w <= ownerSize.Width))
{
var c = xAxis[(int)index];
var cs = g.DrawingFactory.MeasureString(c, Font);
var x = (int)(((int)index - startIndex) * offset + paddingLeft);
if (x > preOffset + w)
{
if (mustOffset)
{
preOffset = x + (int)offsetWidth / 2;
g.DrawLine(new Stroke(1), yApen, new Point((float)preOffset, ownerSize.Height - padding.Bottom), new Point((float)preOffset, ownerSize.Height - padding.Bottom + 4));
}
else
{
preOffset = x;
g.DrawLine(new Stroke(1), yApen, new Point(x, ownerSize.Height - padding.Bottom), new Point(x, ownerSize.Height - padding.Bottom + 4));
}
if (gridShowMode == GridShowMode.Vertical || gridShowMode == GridShowMode.All)
{
g.DrawLine(new Stroke(1), gpen, new Point(x, ownerSize.Height - padding.Bottom), new Point(x, padding.Top));
}
g.DrawString(new Point(x - cs.Width / 2, ownerSize.Height - padding.Bottom + Font.LineHeight / 4), foreColorBrush, c, Font, TextAlignment.Center, cs.Width + cs.Width / 5);
}
w = cs.Width;
index += of;
}
}
}
}
foreach (IChartData item in data)
{
if (startIndex > item.DataCount - 1)
{
continue;
}
var len = length;
//if (startIndex + length > item.DataCount)
//{
// len = item.DataCount - startIndex;
//}
//var w = 1.0 * _size.Width / (length - 1);
item.Paint(dc, startIndex, length, new Rect(padding.Left + (mustOffset ? (int)(offsetWidth / 2) : 0),
padding.Top,
Math.Max(0, (int)((len - 1) * offsetWidth)),
_size.Height), maxValue, minValue, bounds, Root.RenderScaling);
}
if (mouseHover.X > padding.Left && mouseHover.Y > padding.Top && mouseHover.Y < ownerSize.Height - padding.Bottom && mouseHover.X < ownerSize.Width - padding.Right && mouseHoverShowTip)
{
var yValue = (maxValue - (maxValue - minValue) / (ownerSize.Height - padding.Vertical) * (mouseHover.Y - padding.Top)).ToString("0.00");
size = g.DrawingFactory.MeasureString(yValue, Font);
using (Brush sb = hoverSelectTipBackColor.CreateBrush(bounds, Root.RenderScaling), b = hoverSelectTipColor.CreateBrush(bounds, Root.RenderScaling))
{
g.FillRectangle(sb, new Rect(padding.Left - size.Width, mouseHover.Y - size.Height / 2, size.Width, size.Height));
g.DrawString(new Point(padding.Left - size.Width, mouseHover.Y - size.Height / 2), b, yValue, Font);
using (var pen = hoverSelectLineColor.CreateBrush(bounds, Root.RenderScaling))
{
Stroke stroke = new Stroke(1);
if (xAxis != null && xAxis.Count > 0)
{
//GetRange(horizontalScaling, scrollValue, xAxis.Length, out int startIndex, out int length);
if (length > 1)
{
var offset = 1.0 * width / (length - 1);
var index = (int)((mouseHover.X - paddingLeft + offset / 2) / offset);
if (mustOffset)
{
stroke.Width = (int)offsetWidth;
g.DrawLine(stroke, pen, new Point((float)(index * offset + paddingLeft), padding.Top), new Point((float)(index * offset + paddingLeft), ownerSize.Height - padding.Bottom));
}
else
{
g.DrawLine(stroke, pen, new Point((float)(index * offset + paddingLeft), padding.Top), new Point((float)(index * offset + paddingLeft), ownerSize.Height - padding.Bottom));
}
if (xAxis.Count > index + startIndex)
{
size = g.DrawingFactory.MeasureString(xAxis[index + startIndex], Font);
g.FillRectangle(sb, new Rect((int)(index * offset + paddingLeft) - size.Width / 2, ownerSize.Height - padding.Bottom + 4, size.Width, size.Height));
g.DrawString(new Point((float)(index * offset + paddingLeft) - size.Width / 2, ownerSize.Height - padding.Bottom + 4), b, xAxis[index + startIndex], Font);
//var str = xAxis[index + startIndex];
//size = g.DrawingFactory.MeasureString(str, Font);
//var rect = new Rect(mouseHover.X + 15, mouseHover.Y + 15, size.Width, size.Height);
//List<KeyValuePair<Point, ChartData>> textPoints = new List<KeyValuePair<Point, ChartData>>();
//foreach (ChartData item in data)
//{
// if (index + startIndex < item.DataCount)
// {
// var s = item.GetTipString(index + startIndex);
// size = g.DrawingFactory.MeasureString(s, Font);
// textPoints.Add(new KeyValuePair<Point, ChartData>(new Point(20, (int)rect.Height), item));
// rect.Size = new Size(Math.Max(rect.Width, 20 + size.Width), rect.Height + size.Height);
// }
//}
//if (rect.X + rect.Width > ownerSize.Width)
//{
// rect.X = mouseHover.X - rect.Width - 10;
//}
//if (rect.Y + rect.Height > ownerSize.Height)
//{
// rect.Y = mouseHover.Y - rect.Height - 10;
//}
//g.FillRectangle(sb, rect);
//g.DrawString(new Point(rect.X, rect.Y), b, str, Font);
//foreach (var item in textPoints)
//{
// using (Brush brush = item.Value.Fill.CreateBrush(bounds, Root.RenderScaling))
// {
// g.FillEllipse(brush, new Point(rect.X + 5 + 4, rect.Y + item.Key.Y + (Font.DefaultLineHeight - 8) / 2 + 4), 4, 4);
// }
// g.DrawString(new Point(item.Key.X + rect.X, item.Key.Y + rect.Y), b, item.Value.GetTipString(index + startIndex), Font);
//}
}
}
}
stroke.Width = 1;
stroke.DashStyle = DashStyles.Dash;
stroke.DashPattern = new float[] { 4, 4 };
g.DrawLine(stroke, pen, new Point(padding.Left, mouseHover.Y), new Point(ownerSize.Width - padding.Right, mouseHover.Y));
}
}
}
//Size textSize = new Size();
//List<KeyValuePair<float, ChartData>> texts = new List<KeyValuePair<float, ChartData>>();
//foreach (ChartData item in data)
//{
// var s = item.Name;
// size = g.DrawingFactory.MeasureString(s, Font);
// texts.Add(new KeyValuePair<float, ChartData>(20 + size.Width, item));
// textSize = new Size(20 + textSize.Width + size.Width, Math.Max(textSize.Height, size.Height));
//}
//var left = (ownerSize.Width - textSize.Width) / 2;
//foreach (var item in texts)
//{
// using (Brush brush = item.Value.Fill.CreateBrush(bounds, Root.RenderScaling))
// {
// g.FillEllipse(brush, new Point(left + 4, (textSize.Height - 8) / 2 + 8), 4, 4);
// }
// g.DrawString(new Point(left + 10, 5), foreColorBrush, item.Value.Name, Font);
// left += item.Key;
//}
}
}
}
/// <summary>
/// 通过坐标获取该位置的数据索引
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
public int HitTestDataIndex(Point point, out int startIndex, out int length)
{
var ownerSize = ActualSize;
var padding = Padding;
var _size = new Size(ownerSize.Width - padding.Horizontal, ownerSize.Height - padding.Vertical);
var mustOffset = Data.Any(a => a is ChartBarData);
GetRange(HorizontalScaling, ScrollValue, XAxis.Count, out startIndex, out length);
if (length == 1)
{
return startIndex;
}
var offsetWidth = 1.0 * _size.Width / (length - (mustOffset ? 0 : 1));
var paddingLeft = mustOffset ? (padding.Left + (int)(offsetWidth / 2)) : (padding.Left);
var width = mustOffset ? (ownerSize.Width - padding.Horizontal - offsetWidth) : (ownerSize.Width - padding.Horizontal);
var offset = 1.0 * width / (length - 1);
var index = (int)((mouseHover.X - paddingLeft + offset / 2) / offset);
return index + startIndex;
}
public static void GetRange(float horizontalScaling, float scrollValue, int count, out int startIndex, out int length)
{
var range = 1 / horizontalScaling;//索引范围宽度
var scroll = 1 - range;//可以滚动的范围
var scrollStart = scroll * scrollValue;
startIndex = (int)Math.Round(scrollStart * count, MidpointRounding.AwayFromZero);
length = (int)Math.Round(range * count, MidpointRounding.AwayFromZero);
if (startIndex > count - 1)
{
startIndex = count - 1;
length = 1;
}
if (startIndex + length > count)
{
length = count - startIndex;
}
}
protected override void OnMouseWheel(Input.MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
if (!CanScroll)
{
return;
}
if (e.Delta.Y > 0)
{
HorizontalScaling += 1;
}
else
{
HorizontalScaling -= 1;
}
}
Point oldPoint;
protected override void OnMouseDown(Input.MouseButtonEventArgs e)
{
base.OnMouseDown(e);
oldPoint = e.Location;
this.CaptureMouse();
}
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
this.ReleaseMouseCapture();
}
int hoverIndex = -1;
protected override void OnMouseMove(MouseEventArgs e)
{
mouseHover = e.Location;
base.OnMouseMove(e);
var ownerSize = ActualSize;
if (IsMouseCaptured && HorizontalScaling != 1 && CanScroll)
{
var w = (ownerSize.Width - Padding.Horizontal) * HorizontalScaling - (ownerSize.Width - Padding.Horizontal);
var offset = e.Location.X - oldPoint.X;
ScrollValue -= (offset / w);
oldPoint = e.Location;
}
var padding = Padding;
var mouseHoverShowTip = MouseHoverShowTip;
var xAxis = XAxis;
var index = HitTestDataIndex(e.Location, out var startIndex, out var length);
if (mouseHover.X > padding.Left && mouseHover.Y > padding.Top && mouseHover.Y < ownerSize.Height - padding.Bottom && mouseHover.X < ownerSize.Width - padding.Right && mouseHoverShowTip && index < xAxis.Count)
{
if (tipPanel)
{
var data = Data;
tipPanel.Visibility = Visibility.Visible;
var str = xAxis[index];
if (tipName)
{
tipName.Text = str;
}
Font Font = new Font(FontFamily, FontSize, FontStyle);
var size = DrawingFactory.Default.MeasureString(xAxis[index], Font);
var rect = new Rect(mouseHover.X + 15, mouseHover.Y + 15, size.Width, size.Height);
//foreach (IChartData item in data)
//{
// if (index < item.DataCount)
// {
// var s = item.Name + ":" + item.GetValue(index);
// size = DrawingFactory.Default.MeasureString(s, Font);
// rect.Size = new Size(Math.Max(rect.Width, 20 + size.Width), rect.Height + size.Height);
// }
//}
if (hoverIndex != index)
{
tipListPanel.Children.Clear();
var template = SerieTemplate;
if (template != null)
{
foreach (var item in data)
{
if (index < item.DataCount)
{
//var s = item.GetTipString(index);
var c = template.CreateElement();
c.DataContext = item;
c.Name = item.Name;
c.Value = item.GetValue(index);
tipListPanel.Children.Add(c);
}
}
}
}
//tipPanel.Measure(ownerSize);
rect.Size = tipPanel.ActualSize;
if (rect.X + rect.Width > ownerSize.Width)
{
rect.X = mouseHover.X - rect.Width - 10;
}
if (rect.Y + rect.Height > ownerSize.Height)
{
rect.Y = mouseHover.Y - rect.Height - 10;
}
tipPanel.MarginLeft = rect.X - padding.Left;
tipPanel.MarginTop = rect.Y - padding.Top;
}
}
else
{
if (tipPanel)
{
tipPanel.Visibility = Visibility.Collapsed;
}
}
hoverIndex = index;
Invalidate();
}
protected override void OnMouseLeave(Input.MouseEventArgs e)
{
mouseHover = new Point();
base.OnMouseLeave(e);
Invalidate();
if (tipPanel)
{
tipPanel.Visibility = Visibility.Collapsed;
}
}
}
public enum GridShowMode
{
None,
All,
Horizontal,
Vertical
}
}