// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) 2014 OxyPlot contributors // // // Implements a polar heat map series. // // -------------------------------------------------------------------------------------------------------------------- namespace OxyPlot.Series { using System; using System.Collections.Generic; using System.Linq; using OxyPlot.Axes; /// /// Implements a polar heat map series. /// public class PolarHeatMapSeries : XYAxisSeries { /// /// The image /// private OxyImage image; /// /// The pixels /// private OxyColor[,] pixels; /// /// Initializes a new instance of the class. /// public PolarHeatMapSeries() { this.Interpolate = true; } /// /// Gets or sets the size of the image - if set to 0, the image will be generated at every update. /// /// The size of the image. public int ImageSize { get; set; } /// /// Gets or sets the x-coordinate of the left column mid point. /// public double Angle0 { get; set; } /// /// Gets or sets the x-coordinate of the right column mid point. /// public double Angle1 { get; set; } /// /// Gets or sets the y-coordinate of the top row mid point. /// public double Magnitude0 { get; set; } /// /// Gets or sets the y-coordinate of the bottom row mid point. /// public double Magnitude1 { get; set; } /// /// Gets or sets the data array. /// /// Note that the indices of the data array refer to [x,y]. public double[,] Data { get; set; } /// /// Gets or sets a value indicating whether to interpolate when rendering. /// /// This property is not supported on all platforms. public bool Interpolate { get; set; } /// /// Gets or sets the minimum value of the dataset. /// public double MinValue { get; protected set; } /// /// Gets or sets the maximum value of the dataset. /// public double MaxValue { get; protected set; } /// /// Gets or sets the color axis. /// /// The color axis. public IColorAxis ColorAxis { get; protected set; } /// /// Gets or sets the color axis key. /// /// The color axis key. public string ColorAxisKey { get; set; } /// /// Renders the series on the specified render context. /// /// The rendering context. public override void Render(IRenderContext rc) { if (this.Data == null) { this.image = null; return; } if (this.ImageSize > 0) { this.RenderFixed(rc, this.PlotModel); } else { this.RenderDynamic(rc, this.PlotModel); } } /// /// Renders by an image sized from the available plot area. /// /// The rc. /// The model. public void RenderDynamic(IRenderContext rc, PlotModel model) { int m = this.Data.GetLength(0); int n = this.Data.GetLength(1); // get the available plot area var dest = model.PlotArea; int width = (int)dest.Width; int height = (int)dest.Height; if (width == 0 || height == 0) { return; } if (this.pixels == null || this.pixels.GetLength(0) != height || this.pixels.GetLength(1) != width) { this.pixels = new OxyColor[width, height]; } var p = this.pixels; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // transform from screen to magnitude/angle var sp = new ScreenPoint(dest.Left + x, dest.Top + y); var xy = this.InverseTransform(sp); double angle; double magnitude; if (this.PlotModel.PlotType != PlotType.Polar) { angle = Math.Atan2(xy.Y, xy.X) / Math.PI * 180; magnitude = Math.Sqrt((xy.X * xy.X) + (xy.Y * xy.Y)); } else { angle = xy.Y / Math.PI * 180; magnitude = xy.X; while (angle < 0) { angle += 360; } while (angle > 360) { angle -= 360; } } // transform to indices in the Data array var ii = (angle - this.Angle0) / (this.Angle1 - this.Angle0) * m; var jj = (magnitude - this.Magnitude0) / (this.Magnitude1 - this.Magnitude0) * n; if (ii >= 0 && ii < m && jj >= 0 && jj < n) { // get the (interpolated) value var value = this.GetValue(ii, jj); // use the color axis to get the color p[x, y] = OxyColor.FromAColor(160, this.ColorAxis.GetColor(value)); } else { // outside the range of the Data array p[x, y] = OxyColors.Transparent; } } } // Create the PNG image this.image = OxyImage.Create(p, ImageFormat.Png); // Render the image rc.DrawImage(this.image, dest.Left, dest.Top, dest.Width, dest.Height, 1, false); } /// /// Refreshes the image next time the series is rendered. /// public void Refresh() { this.image = null; } /// /// Renders by scaling a fixed image. /// /// The render context. /// The model. public void RenderFixed(IRenderContext rc, PlotModel model) { if (image == null) { int m = this.Data.GetLength(0); int n = this.Data.GetLength(1); int width = this.ImageSize; int height = this.ImageSize; if (this.pixels == null || this.pixels.GetLength(0) != height || this.pixels.GetLength(1) != width) { this.pixels = new OxyColor[width, height]; } var p = this.pixels; for (int yi = 0; yi < height; yi++) { for (int xi = 0; xi < width; xi++) { double x = (xi - width * 0.5) / (width * 0.5) * this.Magnitude1; double y = -(yi - height * 0.5) / (height * 0.5) * this.Magnitude1; double angle = Math.Atan2(y, x) / Math.PI * 180; double magnitude = Math.Sqrt(x * x + y * y); while (angle < 0) { angle += 360; } while (angle > 360) { angle -= 360; } // transform to indices in the Data array var ii = (angle - this.Angle0) / (this.Angle1 - this.Angle0) * m; var jj = (magnitude - this.Magnitude0) / (this.Magnitude1 - this.Magnitude0) * n; if (ii >= 0 && ii < m && jj >= 0 && jj < n) { // get the (interpolated) value var value = this.GetValue(ii, jj); // use the color axis to get the color p[xi, yi] = OxyColor.FromAColor(160, this.ColorAxis.GetColor(value)); } else { // outside the range of the Data array p[xi, yi] = OxyColors.Transparent; } } } // Create the PNG image this.image = OxyImage.Create(p, ImageFormat.Png); } OxyRect dest; if (this.PlotModel.PlotType != PlotType.Polar) { var topleft = this.Transform(-this.Magnitude1, this.Magnitude1); var bottomright = this.Transform(this.Magnitude1, -this.Magnitude1); dest = new OxyRect(topleft.X, topleft.Y, bottomright.X - topleft.X, bottomright.Y - topleft.Y); } else { var top = this.Transform(this.Magnitude1, 90); var bottom = this.Transform(this.Magnitude1, 270); var left = this.Transform(this.Magnitude1, 180); var right = this.Transform(this.Magnitude1, 0); dest = new OxyRect(left.X, top.Y, right.X - left.X, bottom.Y - top.Y); } // Render the image rc.DrawImage(this.image, dest.Left, dest.Top, dest.Width, dest.Height, 1, false); } /// /// Gets the value at the specified data indices. /// /// The first index in the Data array. /// The second index in the Data array. /// The value. protected virtual double GetValue(double ii, double jj) { if (!this.Interpolate) { var i = (int)Math.Floor(ii); var j = (int)Math.Floor(jj); return this.Data[i, j]; } ii -= 0.5; jj -= 0.5; // bi-linear interpolation http://en.wikipedia.org/wiki/Bilinear_interpolation var r = (int)Math.Floor(ii); var c = (int)Math.Floor(jj); int r0 = r > 0 ? r : 0; int r1 = r + 1 < this.Data.GetLength(0) ? r + 1 : r; int c0 = c > 0 ? c : 0; int c1 = c + 1 < this.Data.GetLength(1) ? c + 1 : c; double v00 = this.Data[r0, c0]; double v01 = this.Data[r0, c1]; double v10 = this.Data[r1, c0]; double v11 = this.Data[r1, c1]; double di = ii - r; double dj = jj - c; double v0 = (v00 * (1 - dj)) + (v01 * dj); double v1 = (v10 * (1 - dj)) + (v11 * dj); return (v0 * (1 - di)) + (v1 * di); } /// /// Gets the point on the series that is nearest the specified point. /// /// The point. /// Interpolate the series if this flag is set to true. /// A TrackerHitResult for the current hit. public override TrackerHitResult GetNearestPoint(ScreenPoint point, bool interpolate) { return null; } /// /// Ensures that the axes of the series is defined. /// protected override void EnsureAxes() { base.EnsureAxes(); this.ColorAxis = this.ColorAxisKey != null ? this.PlotModel.GetAxis(this.ColorAxisKey) as IColorAxis : this.PlotModel.DefaultColorAxis as IColorAxis; } /// /// Updates the maximum and minimum values of the series. /// protected override void UpdateMaxMin() { base.UpdateMaxMin(); this.MinValue = this.GetData().Min(); this.MaxValue = this.GetData().Max(); //this.XAxis.Include(this.MinX); //this.XAxis.Include(this.MaxX); //this.YAxis.Include(this.MinY); //this.YAxis.Include(this.MaxY); var colorAxis = this.ColorAxis as Axis; if (colorAxis != null) { colorAxis.Include(this.MinValue); colorAxis.Include(this.MaxValue); } } /// /// Gets the data as a sequence (LINQ-friendly). /// /// The sequence of data. protected IEnumerable GetData() { int m = this.Data.GetLength(0); int n = this.Data.GetLength(1); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { yield return this.Data[i, j]; } } } } }