Last active
February 27, 2016 21:33
-
-
Save VisualMelon/275c8a37e7d971efe24c to your computer and use it in GitHub Desktop.
Changes to HeatMapSeries
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// -------------------------------------------------------------------------------------------------------------------- | |
// <copyright file="HeatMapSeries.cs" company="OxyPlot"> | |
// Copyright (c) 2014 OxyPlot contributors | |
// </copyright> | |
// <summary> | |
// Specifies how the heat map coordinates are defined. | |
// </summary> | |
// -------------------------------------------------------------------------------------------------------------------- | |
namespace OxyPlot.Series | |
{ | |
using System; | |
using OxyPlot.Axes; | |
/// <summary> | |
/// Specifies how the heat map coordinates are defined. | |
/// </summary> | |
public enum HeatMapCoordinateDefinition | |
{ | |
/// <summary> | |
/// The coordinates defines the center of the cells | |
/// </summary> | |
Center, | |
/// <summary> | |
/// The coordinates defines the edge of the cells | |
/// </summary> | |
Edge | |
} | |
/// <summary> | |
/// Represents a heat map. | |
/// </summary> | |
public class HeatMapSeries : XYAxisSeries | |
{ | |
/// <summary> | |
/// The default tracker format string | |
/// </summary> | |
public new const string DefaultTrackerFormatString = "{0}\n{1}: {2}\n{3}: {4}\n{5}: {6}"; | |
/// <summary> | |
/// The default color-axis title | |
/// </summary> | |
private const string DefaultColorAxisTitle = "Value"; | |
/// <summary> | |
/// The hash code of the data when the image was updated. | |
/// </summary> | |
private int dataHash; | |
/// <summary> | |
/// The hash code of the color axis when the image was updated. | |
/// </summary> | |
private int colorAxisHash; | |
/// <summary> | |
/// The image | |
/// </summary> | |
private OxyImage image; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="HeatMapSeries" /> class. | |
/// </summary> | |
public HeatMapSeries() | |
{ | |
this.TrackerFormatString = DefaultTrackerFormatString; | |
this.Interpolate = true; | |
this.LabelFormatString = "0.00"; | |
this.LabelFontSize = 0; | |
} | |
/// <summary> | |
/// Gets or sets the x-coordinate of the elements at index [0,*] in the data set. | |
/// </summary> | |
/// <value> | |
/// If <see cref="CoordinateDefinition" /> equals <see cref="HeatMapCoordinateDefinition.Center"/>, the value defines the mid point of the element at index [0,*] in the data set. | |
/// If <see cref="CoordinateDefinition" /> equals <see cref="HeatMapCoordinateDefinition.Edge"/>, the value defines the coordinate of the left edge of the element at index [0,*] in the data set. | |
/// </value> | |
public double X0 { get; set; } | |
/// <summary> | |
/// Gets or sets the x-coordinate of the mid point for the elements at index [m-1,*] in the data set. | |
/// </summary> | |
/// <value> | |
/// If <see cref="CoordinateDefinition" /> equals <see cref="HeatMapCoordinateDefinition.Center"/>, the value defines the mid point of the element at index [m-1,*] in the data set. | |
/// If <see cref="CoordinateDefinition" /> equals <see cref="HeatMapCoordinateDefinition.Edge"/>, the value defines the coordinate of the right edge of the element at index [m-1,*] in the data set. | |
/// </value> | |
public double X1 { get; set; } | |
/// <summary> | |
/// Gets or sets the y-coordinate of the mid point for the elements at index [*,0] in the data set. | |
/// </summary> | |
/// <value> | |
/// If <see cref="CoordinateDefinition" /> equals <see cref="HeatMapCoordinateDefinition.Center"/>, the value defines the mid point of the element at index [*,0] in the data set. | |
/// If <see cref="CoordinateDefinition" /> equals <see cref="HeatMapCoordinateDefinition.Edge"/>, the value defines the coordinate of the bottom edge of the element at index [*,0] in the data set. | |
/// </value> | |
public double Y0 { get; set; } | |
/// <summary> | |
/// Gets or sets the y-coordinate of the mid point for the elements at index [*,n-1] in the data set. | |
/// </summary> | |
/// <value> | |
/// If <see cref="CoordinateDefinition" /> equals <see cref="HeatMapCoordinateDefinition.Center"/>, the value defines the mid point of the element at index [*,n-1] in the data set. | |
/// If <see cref="CoordinateDefinition" /> equals <see cref="HeatMapCoordinateDefinition.Edge"/>, the value defines the coordinate of the top edge of the element at index [*,n-1] in the data set. | |
/// </value> | |
public double Y1 { get; set; } | |
/// <summary> | |
/// Gets or sets the data array. | |
/// </summary> | |
/// <remarks>Note that the indices of the data array refer to [x,y]. | |
/// The first dimension is along the x-axis. | |
/// The second dimension is along the y-axis. | |
/// Remember to call the <see cref="Invalidate" /> method if the contents of the <see cref="Data" /> array is changed.</remarks> | |
public double[,] Data { get; set; } | |
/// <summary> | |
/// Gets or sets a value indicating whether to interpolate when rendering. The default value is <c>true</c>. | |
/// </summary> | |
/// <remarks>This property is not supported on all platforms.</remarks> | |
public bool Interpolate { get; set; } | |
/// <summary> | |
/// Gets or sets a value indicating whether to use discrete rectangles for rendering uninterpolated (ignored if Interpolate is set to true). The default value is <c>false</c>. | |
/// </summary> | |
public bool RenderDiscreteRectangles { get; set; } | |
/// <summary> | |
/// Gets or sets a value indicating whether to treat the x dimension as logarithmic. The default value is <c>false</c>. | |
/// </summary> | |
public bool LogX { get; set; } | |
/// <summary> | |
/// Gets or sets a value indicating whether to treat the y dimension as logarithmic. The default value is <c>false</c>. | |
/// </summary> | |
public bool LogY { get; set; } | |
/// <summary> | |
/// Gets the minimum value of the dataset. | |
/// </summary> | |
public double MinValue { get; private set; } | |
/// <summary> | |
/// Gets the maximum value of the dataset. | |
/// </summary> | |
public double MaxValue { get; private set; } | |
/// <summary> | |
/// Gets or sets the color axis. | |
/// </summary> | |
/// <value>The color axis.</value> | |
public IColorAxis ColorAxis { get; protected set; } | |
/// <summary> | |
/// Gets or sets the color axis key. | |
/// </summary> | |
/// <value>The color axis key.</value> | |
public string ColorAxisKey { get; set; } | |
/// <summary> | |
/// Gets or sets the coordinate definition. The default value is <see cref="HeatMapCoordinateDefinition.Center" />. | |
/// </summary> | |
/// <value>The coordinate definition.</value> | |
public HeatMapCoordinateDefinition CoordinateDefinition { get; set; } | |
/// <summary> | |
/// Gets or sets the format string for the cell labels. The default value is <c>0.00</c>. | |
/// </summary> | |
/// <value>The format string.</value> | |
/// <remarks>The label format string is only used when <see cref="LabelFontSize" /> is greater than 0.</remarks> | |
public string LabelFormatString { get; set; } | |
/// <summary> | |
/// Gets or sets the font size of the labels. The default value is <c>0</c> (labels not visible). | |
/// </summary> | |
/// <value>The font size relative to the cell height.</value> | |
public double LabelFontSize { get; set; } | |
/// <summary> | |
/// Invalidates the image that renders the heat map. The image will be regenerated the next time the <see cref="HeatMapSeries" /> is rendered. | |
/// </summary> | |
/// <remarks>Call <see cref="PlotModel.InvalidatePlot" /> to refresh the view.</remarks> | |
public void Invalidate() | |
{ | |
this.image = null; | |
} | |
/// <summary> | |
/// Renders the series on the specified render context. | |
/// </summary> | |
/// <param name="rc">The rendering context.</param> | |
public override void Render(IRenderContext rc) | |
{ | |
if (this.Data == null) | |
{ | |
this.image = null; | |
return; | |
} | |
if (this.ColorAxis == null) | |
{ | |
throw new InvalidOperationException("Color axis not specified."); | |
} | |
double left = this.X0; | |
double right = this.X1; | |
double bottom = this.Y0; | |
double top = this.Y1; | |
int m = this.Data.GetLength(0); | |
int n = this.Data.GetLength(1); | |
double dx = (this.X1 - this.X0) / (m - 1); | |
double dy = (this.Y1 - this.Y0) / (n - 1); | |
if (this.CoordinateDefinition == HeatMapCoordinateDefinition.Center) | |
{ | |
if (LogX) | |
{ | |
double gx = Math.Log(this.X1 / this.X0) / (m - 1); | |
left *= Math.Exp(gx / -2); | |
right *= Math.Exp(gx / 2); | |
} | |
else | |
{ | |
left -= dx / 2; | |
right += dx / 2; | |
} | |
if (LogY) | |
{ | |
double gy = Math.Log(this.Y1 / this.Y0) / (n - 1); | |
bottom *= Math.Exp(gy / -2); | |
top *= Math.Exp(gy / 2); | |
} | |
else | |
{ | |
bottom -= dy / 2; | |
top += dy / 2; | |
} | |
} | |
var s00 = this.Transform(left, bottom); | |
var s11 = this.Transform(right, top); | |
var rect = new OxyRect(s00, s11); | |
bool needImage = Interpolate || !RenderDiscreteRectangles; | |
var currentDataHash = this.Data.GetHashCode(); | |
var currentColorAxisHash = this.ColorAxis.GetElementHashCode(); | |
if ((needImage && this.image == null) || currentDataHash != this.dataHash || currentColorAxisHash != this.colorAxisHash) | |
{ | |
if (needImage) | |
this.UpdateImage(); | |
this.dataHash = currentDataHash; | |
this.colorAxisHash = currentColorAxisHash; | |
} | |
var clip = this.GetClippingRect(); | |
if (needImage) | |
{ | |
if (this.image != null) | |
{ | |
rc.DrawClippedImage(clip, this.image, rect.Left, rect.Top, rect.Width, rect.Height, 1, this.Interpolate); | |
} | |
} | |
else | |
{ | |
s00 = this.Orientate(s00); // disorientate | |
s11 = this.Orientate(s11); // disorientate | |
double sdx = (s11.X - s00.X) / m; | |
double sdy = (s11.Y - s00.Y) / n; | |
// draw lots of rectangles | |
for (int i = 0; i < m; i++) | |
{ | |
for (int j = 0; j < n; j++) | |
{ | |
OxyColor rectcolor = this.ColorAxis.GetColor(this.Data[i, j]); | |
var pointa = this.Orientate(new ScreenPoint(s00.X + i * sdx, s00.Y + j * sdy)); // reorientate | |
var pointb = this.Orientate(new ScreenPoint(s00.X + (i + 1) * sdx, s00.Y + (j + 1) * sdy)); // reorientate | |
OxyRect rectrect = new OxyRect(pointa, pointb); | |
rc.DrawClippedRectangle(clip, rectrect, rectcolor, OxyColors.Undefined, 0); | |
} | |
} | |
} | |
if (this.LabelFontSize > 0) | |
{ | |
this.RenderLabels(rc, rect); | |
} | |
} | |
// fudge Tranform/InverseTranform testing (for Transpose #535) | |
protected new OxyRect GetClippingRect() | |
{ | |
double minX = Math.Min(this.XAxis.ScreenMin.X, this.XAxis.ScreenMax.X); | |
double minY = Math.Min(this.YAxis.ScreenMin.Y, this.YAxis.ScreenMax.Y); | |
double maxX = Math.Max(this.XAxis.ScreenMin.X, this.XAxis.ScreenMax.X); | |
double maxY = Math.Max(this.YAxis.ScreenMin.Y, this.YAxis.ScreenMax.Y); | |
if (XAxis.IsVertical()) | |
return new OxyRect(minY, minX, maxY - minY, maxX - minX); | |
else | |
return new OxyRect(minX, minY, maxX - minX, maxY - minY); | |
} | |
private ScreenPoint Orientate(ScreenPoint point) | |
{ | |
if (XAxis.IsVertical()) | |
{ | |
point = new ScreenPoint(point.Y, point.X); | |
} | |
return point; | |
} | |
public new ScreenPoint Transform(double x, double y) | |
{ | |
return Orientate(base.Transform(x, y)); | |
} | |
public new ScreenPoint Transform(DataPoint p) | |
{ | |
return Orientate(base.Transform(p)); | |
} | |
public new DataPoint InverseTransform(ScreenPoint point) | |
{ | |
return base.InverseTransform(Orientate(point)); | |
} | |
// | |
/// <summary> | |
/// Gets the point on the series that is nearest the specified point. | |
/// </summary> | |
/// <param name="point">The point.</param> | |
/// <param name="interpolate">Interpolate the series if this flag is set to <c>true</c>.</param> | |
/// <returns>A TrackerHitResult for the current hit.</returns> | |
public override TrackerHitResult GetNearestPoint(ScreenPoint point, bool interpolate) | |
{ | |
if (!this.Interpolate) | |
{ | |
// It makes no sense to interpolate the tracker when the plot is not interpolated. | |
interpolate = false; | |
} | |
var p = this.InverseTransform(point); | |
if (!this.IsPointInRange(p)) | |
{ | |
return null; | |
} | |
// these are screen space coords | |
double i; | |
double j; | |
if (LogX) | |
{ | |
double gx = Math.Log(this.X1 / this.X0) / (this.Data.GetLength(0) - 1); | |
i = Math.Log(p.X / this.X0) / gx; | |
} | |
else | |
{ | |
double dx = (this.X1 - this.X0) / (this.Data.GetLength(0) - 1); | |
i = (p.X - this.X0) / dx; | |
} | |
if (LogY) | |
{ | |
double gy = Math.Log(this.Y1 / this.Y0) / (this.Data.GetLength(1) - 1); | |
j = Math.Log(p.Y / this.Y0) / gy; | |
} | |
else | |
{ | |
double dy = (this.Y1 - this.Y0) / (this.Data.GetLength(1) - 1); | |
j = (p.Y - this.Y0) / dy; | |
} | |
if (!interpolate) | |
{ | |
i = Math.Round(i); | |
j = Math.Round(j); | |
double px; | |
double py; | |
if (LogX) | |
{ | |
double gx = Math.Log(this.X1 / this.X0) / (this.Data.GetLength(0) - 1); | |
px = this.X0 * Math.Exp((double)i * gx); | |
} | |
else | |
{ | |
double dx = (this.X1 - this.X0) / (this.Data.GetLength(0) - 1); | |
px = (i * dx) + this.X0; | |
} | |
if (LogY) | |
{ | |
double gy = Math.Log(this.Y1 / this.Y0) / (this.Data.GetLength(1) - 1); | |
py = this.Y0 * Math.Exp((double)j * gy); | |
} | |
else | |
{ | |
double dy = (this.Y1 - this.Y0) / (this.Data.GetLength(1) - 1); | |
py = (j * dy) + this.Y0; | |
} | |
p = new DataPoint(px, py); | |
point = this.Transform(p); | |
} | |
var value = GetValue(this.Data, i, j); | |
var colorAxis = this.ColorAxis as Axis; | |
var colorAxisTitle = (colorAxis != null ? colorAxis.Title : null) ?? DefaultColorAxisTitle; | |
return new TrackerHitResult | |
{ | |
Series = this, | |
DataPoint = p, | |
Position = point, | |
Item = null, | |
Index = -1, | |
Text = StringHelper.Format( | |
this.ActualCulture, | |
this.TrackerFormatString, | |
null, | |
this.Title, | |
this.XAxis.Title ?? DefaultXAxisTitle, | |
this.XAxis.GetValue(p.X), | |
this.YAxis.Title ?? DefaultYAxisTitle, | |
this.YAxis.GetValue(p.Y), | |
colorAxisTitle, | |
value) | |
}; | |
} | |
/// <summary> | |
/// Ensures that the axes of the series is defined. | |
/// </summary> | |
protected internal override void EnsureAxes() | |
{ | |
base.EnsureAxes(); | |
this.ColorAxis = | |
this.PlotModel.GetAxisOrDefault(this.ColorAxisKey, (Axis)this.PlotModel.DefaultColorAxis) as IColorAxis; | |
} | |
/// <summary> | |
/// Updates the maximum and minimum values of the series for the x and y dimensions only. | |
/// </summary> | |
protected internal void UpdateMaxMinXY() | |
{ | |
int m = this.Data.GetLength(0); | |
int n = this.Data.GetLength(1); | |
this.MinX = Math.Min(this.X0, this.X1); | |
this.MaxX = Math.Max(this.X0, this.X1); | |
this.MinY = Math.Min(this.Y0, this.Y1); | |
this.MaxY = Math.Max(this.Y0, this.Y1); | |
if (this.CoordinateDefinition == HeatMapCoordinateDefinition.Center) | |
{ | |
if (LogX) | |
{ | |
double gx = Math.Log(this.MaxX / this.MinX) / (m - 1); | |
this.MinX *= Math.Exp(gx / -2); | |
this.MaxX *= Math.Exp(gx / 2); | |
} | |
else | |
{ | |
double dx = (this.MaxX - this.MinX) / (m - 1); | |
this.MinX -= dx / 2; | |
this.MaxX += dx / 2; | |
} | |
if (LogY) | |
{ | |
double gy = Math.Log(this.MaxY / this.MinY) / (n - 1); | |
this.MinY *= Math.Exp(gy / -2); | |
this.MaxY *= Math.Exp(gy / 2); | |
} | |
else | |
{ | |
double dy = (this.MaxY - this.MinY) / (n - 1); | |
this.MinY -= dy / 2; | |
this.MaxY += dy / 2; | |
} | |
} | |
} | |
/// <summary> | |
/// Updates the maximum and minimum values of the series. | |
/// </summary> | |
protected internal override void UpdateMaxMin() | |
{ | |
base.UpdateMaxMin(); | |
UpdateMaxMinXY(); | |
this.MinValue = this.Data.Min2D(true); | |
this.MaxValue = this.Data.Max2D(); | |
} | |
/// <summary> | |
/// Updates the axes to include the max and min of this series. | |
/// </summary> | |
protected internal override void UpdateAxisMaxMin() | |
{ | |
base.UpdateAxisMaxMin(); | |
var colorAxis = this.ColorAxis as Axis; | |
if (colorAxis != null) | |
{ | |
colorAxis.Include(this.MinValue); | |
colorAxis.Include(this.MaxValue); | |
} | |
} | |
/// <summary> | |
/// Renders the labels. | |
/// </summary> | |
/// <param name="rc">The <see cref="IRenderContext" /></param> | |
/// <param name="rect">The bounding rectangle for the data.</param> | |
protected virtual void RenderLabels(IRenderContext rc, OxyRect rect) | |
{ | |
var clip = this.GetClippingRect(); | |
int m = this.Data.GetLength(0); | |
int n = this.Data.GetLength(1); | |
//double dx = (this.X1 - this.X0) / (m - 1); | |
//double dy = (this.Y1 - this.Y0) / (n - 1); | |
double fontSize = (rect.Height / n) * this.LabelFontSize; | |
double left = this.X0; | |
double right = this.X1; | |
double bottom = this.Y0; | |
double top = this.Y1; | |
var s00 = this.Orientate(this.Transform(left, bottom)); // disorientate | |
var s11 = this.Orientate(this.Transform(right, top)); // disorientate | |
//var labelrect = new OxyRect(s00, s11); | |
//double sdx = labelrect.Width / (m - 1); | |
//double sdy = labelrect.Height / (n - 1); | |
//var labelrect = new OxyRect(s00, s11); | |
double sdx = (s11.X - s00.X) / (m - 1); | |
double sdy = (s11.Y - s00.Y) / (n - 1); | |
for (int i = 0; i < m; i++) | |
{ | |
for (int j = 0; j < n; j++) | |
{ | |
//var p = new DataPoint((i * dx) + this.X0, (j * dy) + this.Y0); | |
//var point = this.Transform(p); | |
var point = this.Orientate(new ScreenPoint(s00.X + i * sdx, s00.Y + j * sdy)); // reorientate | |
var v = GetValue(this.Data, i, j); | |
var color = this.ColorAxis.GetColor(v); | |
var hsv = color.ToHsv(); | |
var textColor = hsv[2] > 0.6 ? OxyColors.Black : OxyColors.White; | |
var label = this.GetLabel(v, i, j); | |
rc.DrawClippedText( | |
clip, | |
point, | |
label, | |
textColor, | |
this.ActualFont, | |
fontSize, | |
500, | |
0, | |
HorizontalAlignment.Center, | |
VerticalAlignment.Middle); | |
} | |
} | |
} | |
/// <summary> | |
/// Gets the label for the specified cell. | |
/// </summary> | |
/// <param name="v">The value of the cell.</param> | |
/// <param name="i">The first index.</param> | |
/// <param name="j">The second index.</param> | |
/// <returns>The label string.</returns> | |
protected virtual string GetLabel(double v, int i, int j) | |
{ | |
return v.ToString(this.LabelFormatString, this.ActualCulture); | |
} | |
/// <summary> | |
/// Gets the interpolated value at the specified position in the data array (by bilinear interpolation). | |
/// Where interpolation is impossible, return NaN, rather than a calculated nonsense value. | |
/// </summary> | |
/// <param name="data">The data.</param> | |
/// <param name="i">The first index.</param> | |
/// <param name="j">The second index.</param> | |
/// <returns>The interpolated value.</returns> | |
private static double GetValue(double[,] data, double i, double j) | |
{ | |
//// Note data[0, 0] is displayed in quadrant 1, not exactly at the origin, and that implies the invoker can produce negative coordinates. | |
i = Math.Max(i, 0); | |
j = Math.Max(j, 0); | |
int i0 = (int)i; | |
int i1 = i0 + 1 < data.GetLength(0) ? i0 + 1 : i0; | |
int j0 = (int)j; | |
int j1 = j0 + 1 < data.GetLength(1) ? j0 + 1 : j0; | |
i = Math.Min(i, i1); | |
j = Math.Min(j, j1); | |
if ((i == i0) && (j == j0)) | |
{ | |
return data[i0, j0]; | |
} | |
else if ((i != i0) && (j == j0)) | |
{ | |
//// interpolate only by i | |
if (double.IsNaN(data[i0, j0]) || double.IsNaN(data[i1, j0])) | |
{ | |
return double.NaN; | |
} | |
double ifraction = i - i0; | |
if (i0 != i1) | |
{ | |
return (data[i0, j0] * (1 - ifraction)) + (data[i1, j0] * ifraction); | |
} | |
else | |
{ | |
return data[i0, j0]; | |
} | |
} | |
else if ((i == i0) && (j != j0)) | |
{ | |
//// interpolate only by j | |
if (double.IsNaN(data[i0, j0]) || double.IsNaN(data[i0, j1])) | |
{ | |
return double.NaN; | |
} | |
double jfraction = j - j0; | |
if (j0 != j1) | |
{ | |
return (data[i0, j0] * (1 - jfraction)) + (data[i0, j1] * jfraction); | |
} | |
else | |
{ | |
return data[i0, j0]; | |
} | |
} | |
else | |
{ | |
if (double.IsNaN(data[i0, j0]) || double.IsNaN(data[i1, j0]) || double.IsNaN(data[i0, j1]) || double.IsNaN(data[i1, j1])) | |
{ | |
return double.NaN; | |
} | |
double ifraction = i - i0; | |
double jfraction = j - j0; | |
double v0; | |
double v1; | |
if (i0 != i1) | |
{ | |
v0 = (data[i0, j0] * (1 - ifraction)) + (data[i1, j0] * ifraction); | |
v1 = (data[i0, j1] * (1 - ifraction)) + (data[i1, j1] * ifraction); | |
} | |
else | |
{ | |
v0 = data[i0, j0]; | |
v1 = data[i0, j1]; | |
} | |
if (j0 != j1) | |
{ | |
return (v0 * (1 - jfraction)) + (v1 * jfraction); | |
} | |
else | |
{ | |
return v0; | |
} | |
} | |
} | |
/// <summary> | |
/// Tests if a <see cref="DataPoint" /> is inside the heat map | |
/// </summary> | |
/// <param name="p">The <see cref="DataPoint" /> to test.</param> | |
/// <returns><c>True</c> if the point is inside the heat map.</returns> | |
private bool IsPointInRange(DataPoint p) | |
{ | |
UpdateMaxMinXY(); | |
return p.X >= this.MinX && p.X <= this.MaxX && p.Y >= this.MinY && p.Y <= this.MaxY; | |
/* | |
double left = this.X0; | |
double right = this.X1; | |
double bottom = this.Y0; | |
double top = this.Y1; | |
if (this.CoordinateDefinition == HeatMapCoordinateDefinition.Center) | |
{ | |
double dx = (this.X1 - this.X0) / (this.Data.GetLength(0) - 1); | |
double dy = (this.Y1 - this.Y0) / (this.Data.GetLength(1) - 1); | |
left -= dx / 2; | |
right += dx / 2; | |
bottom -= dy / 2; | |
top += dy / 2; | |
} | |
return p.X >= left && p.X <= right && p.Y >= bottom && p.Y <= top; | |
*/ | |
} | |
/// <summary> | |
/// Updates the image. | |
/// </summary> | |
private void UpdateImage() | |
{ | |
// determine if the provided data should be reversed in x-direction | |
var reverseX = this.XAxis.Transform(this.X0) > this.XAxis.Transform(this.X1); | |
// determine if the provided data should be reversed in y-direction | |
var reverseY = this.YAxis.Transform(this.Y0) > this.YAxis.Transform(this.Y1); | |
var swapXY = this.XAxis.IsVertical(); | |
int m = this.Data.GetLength(0); | |
int n = this.Data.GetLength(1); | |
var buffer = swapXY ? new OxyColor[n, m] : new OxyColor[m, n]; | |
for (int i = 0; i < m; i++) | |
{ | |
var ii = reverseX ? m - 1 - i : i; | |
for (int j = 0; j < n; j++) | |
{ | |
var jj = reverseY ? n - 1 - j : j; | |
if (swapXY) | |
buffer[j, i] = this.ColorAxis.GetColor(this.Data[ii, jj]); | |
else | |
buffer[i, j] = this.ColorAxis.GetColor(this.Data[ii, jj]); | |
} | |
} | |
this.image = OxyImage.Create(buffer, ImageFormat.Png); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
These changes (to one file) attempt to address the following three issues: | |
https://github.com/oxyplot/oxyplot/issues/535 | |
https://github.com/oxyplot/oxyplot/issues/801 | |
https://github.com/oxyplot/oxyplot/issues/802 | |
They include adding 3 new properties to the HeatMapSeries, and changes to all rendering and tracking functions. | |
They make no effort to resolve https://github.com/oxyplot/oxyplot/issues/164, because I'm not sure it makes sense | |
The changes that influence #535 may well be applicable to other serieses, I am not certain. | |
These changes can be broken down into individual commits, #801 depending on #802, #535 being mostly independant (though it modifies code introduced by #801 and #802) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment