// --------------------------------------------------------------------------------------------------------------------
//
// 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];
}
}
}
}
}