External/OxyPlot/OxyPlot/Series/ContourSeries.cs
author moel.mich
Sat, 08 Jun 2013 16:53:22 +0000
changeset 391 5be8f2773237
permissions -rw-r--r--
Added the source code of OxyPlot as of commit d190d7748a73 (6.5.2013).
moel@391
     1
// --------------------------------------------------------------------------------------------------------------------
moel@391
     2
// <copyright file="ContourSeries.cs" company="OxyPlot">
moel@391
     3
//   The MIT License (MIT)
moel@391
     4
//
moel@391
     5
//   Copyright (c) 2012 Oystein Bjorke
moel@391
     6
//
moel@391
     7
//   Permission is hereby granted, free of charge, to any person obtaining a
moel@391
     8
//   copy of this software and associated documentation files (the
moel@391
     9
//   "Software"), to deal in the Software without restriction, including
moel@391
    10
//   without limitation the rights to use, copy, modify, merge, publish,
moel@391
    11
//   distribute, sublicense, and/or sell copies of the Software, and to
moel@391
    12
//   permit persons to whom the Software is furnished to do so, subject to
moel@391
    13
//   the following conditions:
moel@391
    14
//
moel@391
    15
//   The above copyright notice and this permission notice shall be included
moel@391
    16
//   in all copies or substantial portions of the Software.
moel@391
    17
//
moel@391
    18
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
moel@391
    19
//   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
moel@391
    20
//   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
moel@391
    21
//   IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
moel@391
    22
//   CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
moel@391
    23
//   TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
moel@391
    24
//   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
moel@391
    25
// </copyright>
moel@391
    26
// <summary>
moel@391
    27
//   Represents a series for contour plots.
moel@391
    28
// </summary>
moel@391
    29
// --------------------------------------------------------------------------------------------------------------------
moel@391
    30
namespace OxyPlot.Series
moel@391
    31
{
moel@391
    32
    using System;
moel@391
    33
    using System.Collections.Generic;
moel@391
    34
    using System.Linq;
moel@391
    35
moel@391
    36
    /// <summary>
moel@391
    37
    /// Represents a series that renders contours.
moel@391
    38
    /// </summary>
moel@391
    39
    /// <remarks>
moel@391
    40
    /// See http://en.wikipedia.org/wiki/Contour_line and http://www.mathworks.se/help/techdoc/ref/contour.html.
moel@391
    41
    /// </remarks>
moel@391
    42
    public class ContourSeries : XYAxisSeries
moel@391
    43
    {
moel@391
    44
        /// <summary>
moel@391
    45
        /// The contour collection.
moel@391
    46
        /// </summary>
moel@391
    47
        private List<Contour> contours;
moel@391
    48
moel@391
    49
        /// <summary>
moel@391
    50
        /// The temporary segment collection.
moel@391
    51
        /// </summary>
moel@391
    52
        private List<ContourSegment> segments;
moel@391
    53
moel@391
    54
        /// <summary>
moel@391
    55
        /// The default color.
moel@391
    56
        /// </summary>
moel@391
    57
        private OxyColor defaultColor;
moel@391
    58
moel@391
    59
        /// <summary>
moel@391
    60
        /// Initializes a new instance of the <see cref = "ContourSeries" /> class.
moel@391
    61
        /// </summary>
moel@391
    62
        public ContourSeries()
moel@391
    63
        {
moel@391
    64
            this.ContourLevelStep = double.NaN;
moel@391
    65
moel@391
    66
            this.LabelSpacing = double.NaN;
moel@391
    67
            this.LabelStep = 1;
moel@391
    68
            this.LabelBackground = OxyColor.FromAColor(220, OxyColors.White);
moel@391
    69
moel@391
    70
            this.Color = null;
moel@391
    71
            this.StrokeThickness = 1.0;
moel@391
    72
            this.LineStyle = LineStyle.Solid;
moel@391
    73
moel@391
    74
            this.TrackerFormatString = "{1}: {2:0.####}\n{3}: {4:0.####}\n{5}: {6:0.####}";
moel@391
    75
        }
moel@391
    76
moel@391
    77
        /// <summary>
moel@391
    78
        /// Gets or sets the color.
moel@391
    79
        /// </summary>
moel@391
    80
        /// <value>The color.</value>
moel@391
    81
        public OxyColor Color { get; set; }
moel@391
    82
moel@391
    83
        /// <summary>
moel@391
    84
        /// Gets the actual color.
moel@391
    85
        /// </summary>
moel@391
    86
        /// <value>The actual color.</value>
moel@391
    87
        public OxyColor ActualColor
moel@391
    88
        {
moel@391
    89
            get { return this.Color ?? this.defaultColor; }
moel@391
    90
        }
moel@391
    91
moel@391
    92
        /// <summary>
moel@391
    93
        /// Gets or sets the column coordinates.
moel@391
    94
        /// </summary>
moel@391
    95
        /// <value>The column coordinates.</value>
moel@391
    96
        public double[] ColumnCoordinates { get; set; }
moel@391
    97
moel@391
    98
        /// <summary>
moel@391
    99
        /// Gets or sets the contour level step size.
moel@391
   100
        /// This property is not used if the ContourLevels vector is set.
moel@391
   101
        /// </summary>
moel@391
   102
        /// <value>The contour level step size.</value>
moel@391
   103
        public double ContourLevelStep { get; set; }
moel@391
   104
moel@391
   105
        /// <summary>
moel@391
   106
        /// Gets or sets the contour levels.
moel@391
   107
        /// </summary>
moel@391
   108
        /// <value>The contour levels.</value>
moel@391
   109
        public double[] ContourLevels { get; set; }
moel@391
   110
moel@391
   111
        /// <summary>
moel@391
   112
        /// Gets or sets the contour colors.
moel@391
   113
        /// </summary>
moel@391
   114
        /// <value>The contour colors.</value>
moel@391
   115
        /// <remarks>
moel@391
   116
        /// These colors will override the Color of the series.
moel@391
   117
        /// If there are less colors than the number of contour levels, the colors will cycle.
moel@391
   118
        /// </remarks>
moel@391
   119
        public OxyColor[] ContourColors { get; set; }
moel@391
   120
moel@391
   121
        /// <summary>
moel@391
   122
        /// Gets or sets the data.
moel@391
   123
        /// </summary>
moel@391
   124
        /// <value>The data.</value>
moel@391
   125
        public double[,] Data { get; set; }
moel@391
   126
moel@391
   127
        /// <summary>
moel@391
   128
        /// Gets or sets the text background color.
moel@391
   129
        /// </summary>
moel@391
   130
        /// <value>The text background color.</value>
moel@391
   131
        public OxyColor LabelBackground { get; set; }
moel@391
   132
moel@391
   133
        /// <summary>
moel@391
   134
        /// Gets or sets the format string for contour values.
moel@391
   135
        /// </summary>
moel@391
   136
        /// <value>The format string.</value>
moel@391
   137
        public string LabelFormatString { get; set; }
moel@391
   138
moel@391
   139
        /// <summary>
moel@391
   140
        /// Gets or sets the label spacing.
moel@391
   141
        /// </summary>
moel@391
   142
        /// <value>The label spacing.</value>
moel@391
   143
        public double LabelSpacing { get; set; }
moel@391
   144
moel@391
   145
        /// <summary>
moel@391
   146
        /// Gets or sets the label step (number of contours per label).
moel@391
   147
        /// </summary>
moel@391
   148
        /// <value>The label step.</value>
moel@391
   149
        public int LabelStep { get; set; }
moel@391
   150
moel@391
   151
        /// <summary>
moel@391
   152
        /// Gets or sets the line style.
moel@391
   153
        /// </summary>
moel@391
   154
        /// <value>The line style.</value>
moel@391
   155
        public LineStyle LineStyle { get; set; }
moel@391
   156
moel@391
   157
        /// <summary>
moel@391
   158
        /// Gets or sets the row coordinates.
moel@391
   159
        /// </summary>
moel@391
   160
        /// <value>The row coordinates.</value>
moel@391
   161
        public double[] RowCoordinates { get; set; }
moel@391
   162
moel@391
   163
        /// <summary>
moel@391
   164
        /// Gets or sets the stroke thickness.
moel@391
   165
        /// </summary>
moel@391
   166
        /// <value>The stroke thickness.</value>
moel@391
   167
        public double StrokeThickness { get; set; }
moel@391
   168
moel@391
   169
        /// <summary>
moel@391
   170
        /// Calculates the contours.
moel@391
   171
        /// </summary>
moel@391
   172
        public void CalculateContours()
moel@391
   173
        {
moel@391
   174
            if (this.Data == null)
moel@391
   175
            {
moel@391
   176
                return;
moel@391
   177
            }
moel@391
   178
moel@391
   179
            double[] actualContourLevels = this.ContourLevels;
moel@391
   180
moel@391
   181
            this.segments = new List<ContourSegment>();
moel@391
   182
            Conrec.RendererDelegate renderer = (startX, startY, endX, endY, contourLevel) =>
moel@391
   183
                this.segments.Add(new ContourSegment(new DataPoint(startX, startY), new DataPoint(endX, endY), contourLevel));
moel@391
   184
moel@391
   185
            if (actualContourLevels == null)
moel@391
   186
            {
moel@391
   187
                double max = this.Data[0, 0];
moel@391
   188
                double min = this.Data[0, 0];
moel@391
   189
                for (int i = 0; i < this.Data.GetUpperBound(0); i++)
moel@391
   190
                {
moel@391
   191
                    for (int j = 0; j < this.Data.GetUpperBound(1); j++)
moel@391
   192
                    {
moel@391
   193
                        max = Math.Max(max, this.Data[i, j]);
moel@391
   194
                        min = Math.Min(min, this.Data[i, j]);
moel@391
   195
                    }
moel@391
   196
                }
moel@391
   197
moel@391
   198
                double actualStep = this.ContourLevelStep;
moel@391
   199
                if (double.IsNaN(actualStep))
moel@391
   200
                {
moel@391
   201
                    double range = max - min;
moel@391
   202
                    double step = range / 20;
moel@391
   203
                    actualStep = Math.Pow(10, Math.Floor(step.GetExponent()));
moel@391
   204
                }
moel@391
   205
moel@391
   206
                max = max.ToUpperMultiple(actualStep);
moel@391
   207
                min = min.ToLowerMultiple(actualStep);
moel@391
   208
                actualContourLevels = ArrayHelper.CreateVector(min, max, actualStep);
moel@391
   209
            }
moel@391
   210
moel@391
   211
            Conrec.Contour(this.Data, this.RowCoordinates, this.ColumnCoordinates, actualContourLevels, renderer);
moel@391
   212
moel@391
   213
            this.JoinContourSegments();
moel@391
   214
moel@391
   215
            if (this.ContourColors != null && this.ContourColors.Length > 0)
moel@391
   216
            {
moel@391
   217
                foreach (var c in this.contours)
moel@391
   218
                {
moel@391
   219
                    // get the index of the contour's level
moel@391
   220
                    var index = IndexOf(actualContourLevels, c.ContourLevel);
moel@391
   221
                    if (index >= 0)
moel@391
   222
                    {
moel@391
   223
                        // clamp the index to the range of the ContourColors array
moel@391
   224
                        index = index % this.ContourColors.Length;
moel@391
   225
                        c.Color = this.ContourColors[index];
moel@391
   226
                    }
moel@391
   227
                }
moel@391
   228
            }
moel@391
   229
        }
moel@391
   230
moel@391
   231
        /// <summary>
moel@391
   232
        /// Gets the point in the dataset that is nearest the specified point.
moel@391
   233
        /// </summary>
moel@391
   234
        /// <param name="point">
moel@391
   235
        /// The point.
moel@391
   236
        /// </param>
moel@391
   237
        /// <param name="interpolate">
moel@391
   238
        /// The interpolate.
moel@391
   239
        /// </param>
moel@391
   240
        /// <returns>
moel@391
   241
        /// A hit result object.
moel@391
   242
        /// </returns>
moel@391
   243
        public override TrackerHitResult GetNearestPoint(ScreenPoint point, bool interpolate)
moel@391
   244
        {
moel@391
   245
            TrackerHitResult result = null;
moel@391
   246
moel@391
   247
            var xaxisTitle = this.XAxis.Title ?? "X";
moel@391
   248
            var yaxisTitle = this.YAxis.Title ?? "Y";
moel@391
   249
            var zaxisTitle = "Z";
moel@391
   250
moel@391
   251
            foreach (var c in this.contours)
moel@391
   252
            {
moel@391
   253
                var r = interpolate ? this.GetNearestInterpolatedPointInternal(c.Points, point) : this.GetNearestPointInternal(c.Points, point);
moel@391
   254
                if (r != null)
moel@391
   255
                {
moel@391
   256
                    if (result == null || result.Position.DistanceToSquared(point) > r.Position.DistanceToSquared(point))
moel@391
   257
                    {
moel@391
   258
                        result = r;
moel@391
   259
                        result.Text = StringHelper.Format(
moel@391
   260
                            this.ActualCulture,
moel@391
   261
                            this.TrackerFormatString,
moel@391
   262
                            null,
moel@391
   263
                            this.Title,
moel@391
   264
                            xaxisTitle,
moel@391
   265
                            r.DataPoint.X,
moel@391
   266
                            yaxisTitle,
moel@391
   267
                            r.DataPoint.Y,
moel@391
   268
                            zaxisTitle,
moel@391
   269
                            c.ContourLevel);
moel@391
   270
                    }
moel@391
   271
                }
moel@391
   272
            }
moel@391
   273
moel@391
   274
            return result;
moel@391
   275
        }
moel@391
   276
moel@391
   277
        /// <summary>
moel@391
   278
        /// Renders the series on the specified rendering context.
moel@391
   279
        /// </summary>
moel@391
   280
        /// <param name="rc">
moel@391
   281
        /// The rendering context.
moel@391
   282
        /// </param>
moel@391
   283
        /// <param name="model">
moel@391
   284
        /// The model.
moel@391
   285
        /// </param>
moel@391
   286
        public override void Render(IRenderContext rc, PlotModel model)
moel@391
   287
        {
moel@391
   288
            if (this.contours == null)
moel@391
   289
            {
moel@391
   290
                this.CalculateContours();
moel@391
   291
            }
moel@391
   292
moel@391
   293
            if (this.contours.Count == 0)
moel@391
   294
            {
moel@391
   295
                return;
moel@391
   296
            }
moel@391
   297
moel@391
   298
            this.VerifyAxes();
moel@391
   299
moel@391
   300
            var clippingRect = this.GetClippingRect();
moel@391
   301
moel@391
   302
            var contourLabels = new List<ContourLabel>();
moel@391
   303
moel@391
   304
            foreach (var contour in this.contours)
moel@391
   305
            {
moel@391
   306
                if (this.StrokeThickness > 0 && this.LineStyle != LineStyle.None)
moel@391
   307
                {
moel@391
   308
                    var pts = new ScreenPoint[contour.Points.Count];
moel@391
   309
                    {
moel@391
   310
                        int i = 0;
moel@391
   311
                        foreach (var pt in contour.Points)
moel@391
   312
                        {
moel@391
   313
                            pts[i++] = this.Transform(pt.X, pt.Y);
moel@391
   314
                        }
moel@391
   315
                    }
moel@391
   316
moel@391
   317
                    rc.DrawClippedLine(
moel@391
   318
                        pts,
moel@391
   319
                        clippingRect,
moel@391
   320
                        4,
moel@391
   321
                        this.GetSelectableColor(contour.Color ?? this.ActualColor),
moel@391
   322
                        this.StrokeThickness,
moel@391
   323
                        this.LineStyle,
moel@391
   324
                        OxyPenLineJoin.Miter,
moel@391
   325
                        false);
moel@391
   326
moel@391
   327
                    // rc.DrawClippedPolygon(pts, clippingRect, 4, model.GetDefaultColor(), OxyColors.Black);
moel@391
   328
                    if (pts.Length > 10)
moel@391
   329
                    {
moel@391
   330
                        this.AddContourLabels(contour, pts, clippingRect, contourLabels);
moel@391
   331
                    }
moel@391
   332
                }
moel@391
   333
            }
moel@391
   334
moel@391
   335
            foreach (var cl in contourLabels)
moel@391
   336
            {
moel@391
   337
                this.RenderLabelBackground(rc, cl);
moel@391
   338
            }
moel@391
   339
moel@391
   340
            foreach (var cl in contourLabels)
moel@391
   341
            {
moel@391
   342
                this.RenderLabel(rc, cl);
moel@391
   343
            }
moel@391
   344
        }
moel@391
   345
moel@391
   346
        /// <summary>
moel@391
   347
        /// Sets default values from the plotmodel.
moel@391
   348
        /// </summary>
moel@391
   349
        /// <param name="model">
moel@391
   350
        /// The plot model.
moel@391
   351
        /// </param>
moel@391
   352
        protected internal override void SetDefaultValues(PlotModel model)
moel@391
   353
        {
moel@391
   354
            if (this.Color == null)
moel@391
   355
            {
moel@391
   356
                this.LineStyle = model.GetDefaultLineStyle();
moel@391
   357
                this.defaultColor = model.GetDefaultColor();
moel@391
   358
            }
moel@391
   359
        }
moel@391
   360
moel@391
   361
        /// <summary>
moel@391
   362
        /// Updates the max/min from the datapoints.
moel@391
   363
        /// </summary>
moel@391
   364
        protected internal override void UpdateMaxMin()
moel@391
   365
        {
moel@391
   366
            this.MinX = this.ColumnCoordinates.Min();
moel@391
   367
            this.MaxX = this.ColumnCoordinates.Max();
moel@391
   368
            this.MinY = this.RowCoordinates.Min();
moel@391
   369
            this.MaxY = this.RowCoordinates.Max();
moel@391
   370
        }
moel@391
   371
moel@391
   372
        /// <summary>
moel@391
   373
        /// Determines if two values are close.
moel@391
   374
        /// </summary>
moel@391
   375
        /// <param name="x1">
moel@391
   376
        /// The first value.
moel@391
   377
        /// </param>
moel@391
   378
        /// <param name="x2">
moel@391
   379
        /// The second value.
moel@391
   380
        /// </param>
moel@391
   381
        /// <param name="eps">
moel@391
   382
        /// The squared tolerance.
moel@391
   383
        /// </param>
moel@391
   384
        /// <returns>
moel@391
   385
        /// True if the values are close.
moel@391
   386
        /// </returns>
moel@391
   387
        private static bool AreClose(double x1, double x2, double eps = 1e-6)
moel@391
   388
        {
moel@391
   389
            double dx = x1 - x2;
moel@391
   390
            return dx * dx < eps;
moel@391
   391
        }
moel@391
   392
moel@391
   393
        /// <summary>
moel@391
   394
        /// Determines if two points are close.
moel@391
   395
        /// </summary>
moel@391
   396
        /// <param name="p0">
moel@391
   397
        /// The first point.
moel@391
   398
        /// </param>
moel@391
   399
        /// <param name="p1">
moel@391
   400
        /// The second point.
moel@391
   401
        /// </param>
moel@391
   402
        /// <param name="eps">
moel@391
   403
        /// The squared tolerance.
moel@391
   404
        /// </param>
moel@391
   405
        /// <returns>
moel@391
   406
        /// True if the points are close.
moel@391
   407
        /// </returns>
moel@391
   408
        private static bool AreClose(DataPoint p0, DataPoint p1, double eps = 1e-6)
moel@391
   409
        {
moel@391
   410
            double dx = p0.X - p1.X;
moel@391
   411
            double dy = p0.Y - p1.Y;
moel@391
   412
            return (dx * dx) + (dy * dy) < eps;
moel@391
   413
        }
moel@391
   414
moel@391
   415
        /// <summary>
moel@391
   416
        /// Gets the index of item that is closest to the specified value.
moel@391
   417
        /// </summary>
moel@391
   418
        /// <param name="values">A list of values.</param>
moel@391
   419
        /// <param name="value">A value.</param>
moel@391
   420
        /// <returns>An index.</returns>
moel@391
   421
        private static int IndexOf(IList<double> values, double value)
moel@391
   422
        {
moel@391
   423
            double min = double.MaxValue;
moel@391
   424
            int index = -1;
moel@391
   425
            for (int i = 0; i < values.Count; i++)
moel@391
   426
            {
moel@391
   427
                var d = Math.Abs(values[i] - value);
moel@391
   428
                if (d < min)
moel@391
   429
                {
moel@391
   430
                    min = d;
moel@391
   431
                    index = i;
moel@391
   432
                }
moel@391
   433
            }
moel@391
   434
moel@391
   435
            return index;
moel@391
   436
        }
moel@391
   437
moel@391
   438
        /// <summary>
moel@391
   439
        /// The add contour labels.
moel@391
   440
        /// </summary>
moel@391
   441
        /// <param name="contour">
moel@391
   442
        /// The contour.
moel@391
   443
        /// </param>
moel@391
   444
        /// <param name="pts">
moel@391
   445
        /// The pts.
moel@391
   446
        /// </param>
moel@391
   447
        /// <param name="clippingRect">
moel@391
   448
        /// The clipping rect.
moel@391
   449
        /// </param>
moel@391
   450
        /// <param name="contourLabels">
moel@391
   451
        /// The contour labels.
moel@391
   452
        /// </param>
moel@391
   453
        private void AddContourLabels(
moel@391
   454
            Contour contour, ScreenPoint[] pts, OxyRect clippingRect, List<ContourLabel> contourLabels)
moel@391
   455
        {
moel@391
   456
            // todo: support label spacing and label step
moel@391
   457
            if (pts.Length < 2)
moel@391
   458
            {
moel@391
   459
                return;
moel@391
   460
            }
moel@391
   461
moel@391
   462
            // Calculate position and angle of the label
moel@391
   463
            double i = (pts.Length - 1) * 0.5;
moel@391
   464
            var i0 = (int)i;
moel@391
   465
            int i1 = i0 + 1;
moel@391
   466
            double dx = pts[i1].X - pts[i0].X;
moel@391
   467
            double dy = pts[i1].Y - pts[i0].Y;
moel@391
   468
            double x = pts[i0].X + (dx * (i - i0));
moel@391
   469
            double y = pts[i0].Y + (dy * (i - i0));
moel@391
   470
            if (!clippingRect.Contains(x, y))
moel@391
   471
            {
moel@391
   472
                return;
moel@391
   473
            }
moel@391
   474
moel@391
   475
            var pos = new ScreenPoint(x, y);
moel@391
   476
            double angle = Math.Atan2(dy, dx) * 180 / Math.PI;
moel@391
   477
            if (angle > 90)
moel@391
   478
            {
moel@391
   479
                angle -= 180;
moel@391
   480
            }
moel@391
   481
moel@391
   482
            if (angle < -90)
moel@391
   483
            {
moel@391
   484
                angle += 180;
moel@391
   485
            }
moel@391
   486
moel@391
   487
            string text = contour.ContourLevel.ToString(this.LabelFormatString, this.ActualCulture);
moel@391
   488
            contourLabels.Add(new ContourLabel { Position = pos, Angle = angle, Text = text });
moel@391
   489
        }
moel@391
   490
moel@391
   491
        /// <summary>
moel@391
   492
        /// Finds the connected segment.
moel@391
   493
        /// </summary>
moel@391
   494
        /// <param name="point">
moel@391
   495
        /// The point.
moel@391
   496
        /// </param>
moel@391
   497
        /// <param name="contourLevel">
moel@391
   498
        /// The contour level.
moel@391
   499
        /// </param>
moel@391
   500
        /// <param name="eps">
moel@391
   501
        /// The eps.
moel@391
   502
        /// </param>
moel@391
   503
        /// <param name="reverse">
moel@391
   504
        /// reverse the segment if set to <c>true</c>.
moel@391
   505
        /// </param>
moel@391
   506
        /// <returns>
moel@391
   507
        /// The connected segment, or null if no segment was found.
moel@391
   508
        /// </returns>
moel@391
   509
        private ContourSegment FindConnectedSegment(DataPoint point, double contourLevel, double eps, out bool reverse)
moel@391
   510
        {
moel@391
   511
            reverse = false;
moel@391
   512
            foreach (var s in this.segments)
moel@391
   513
            {
moel@391
   514
                if (!AreClose(s.ContourLevel, contourLevel, eps))
moel@391
   515
                {
moel@391
   516
                    continue;
moel@391
   517
                }
moel@391
   518
moel@391
   519
                if (AreClose(point, s.StartPoint, eps))
moel@391
   520
                {
moel@391
   521
                    return s;
moel@391
   522
                }
moel@391
   523
moel@391
   524
                if (AreClose(point, s.EndPoint, eps))
moel@391
   525
                {
moel@391
   526
                    reverse = true;
moel@391
   527
                    return s;
moel@391
   528
                }
moel@391
   529
            }
moel@391
   530
moel@391
   531
            return null;
moel@391
   532
        }
moel@391
   533
moel@391
   534
        /// <summary>
moel@391
   535
        /// Joins the contour segments.
moel@391
   536
        /// </summary>
moel@391
   537
        /// <param name="eps">
moel@391
   538
        /// The tolerance for segment ends to connect (squared distance).
moel@391
   539
        /// </param>
moel@391
   540
        private void JoinContourSegments(double eps = 1e-10)
moel@391
   541
        {
moel@391
   542
            // This is a simple, slow, naïve method - should be improved:
moel@391
   543
            // http://stackoverflow.com/questions/1436091/joining-unordered-line-segments
moel@391
   544
            this.contours = new List<Contour>();
moel@391
   545
            var contourPoints = new List<IDataPoint>();
moel@391
   546
            int contourPointsCount = 0;
moel@391
   547
moel@391
   548
            ContourSegment firstSegment = null;
moel@391
   549
            int segmentCount = this.segments.Count;
moel@391
   550
            while (segmentCount > 0)
moel@391
   551
            {
moel@391
   552
                ContourSegment segment1 = null, segment2 = null;
moel@391
   553
moel@391
   554
                if (firstSegment != null)
moel@391
   555
                {
moel@391
   556
                    bool reverse;
moel@391
   557
moel@391
   558
                    // Find a segment that is connected to the head of the contour
moel@391
   559
                    segment1 = this.FindConnectedSegment(
moel@391
   560
                        (DataPoint)contourPoints[0], firstSegment.ContourLevel, eps, out reverse);
moel@391
   561
                    if (segment1 != null)
moel@391
   562
                    {
moel@391
   563
                        contourPoints.Insert(0, reverse ? segment1.StartPoint : segment1.EndPoint);
moel@391
   564
                        contourPointsCount++;
moel@391
   565
                        this.segments.Remove(segment1);
moel@391
   566
                        segmentCount--;
moel@391
   567
                    }
moel@391
   568
moel@391
   569
                    // Find a segment that is connected to the tail of the contour
moel@391
   570
                    segment2 = this.FindConnectedSegment(
moel@391
   571
                        (DataPoint)contourPoints[contourPointsCount - 1], firstSegment.ContourLevel, eps, out reverse);
moel@391
   572
                    if (segment2 != null)
moel@391
   573
                    {
moel@391
   574
                        contourPoints.Add(reverse ? segment2.StartPoint : segment2.EndPoint);
moel@391
   575
                        contourPointsCount++;
moel@391
   576
                        this.segments.Remove(segment2);
moel@391
   577
                        segmentCount--;
moel@391
   578
                    }
moel@391
   579
                }
moel@391
   580
moel@391
   581
                if ((segment1 == null && segment2 == null) || segmentCount == 0)
moel@391
   582
                {
moel@391
   583
                    if (contourPointsCount > 0 && firstSegment != null)
moel@391
   584
                    {
moel@391
   585
                        this.contours.Add(new Contour(contourPoints, firstSegment.ContourLevel));
moel@391
   586
                        contourPoints = new List<IDataPoint>();
moel@391
   587
                        contourPointsCount = 0;
moel@391
   588
                    }
moel@391
   589
moel@391
   590
                    if (segmentCount > 0)
moel@391
   591
                    {
moel@391
   592
                        firstSegment = this.segments.First();
moel@391
   593
                        contourPoints.Add(firstSegment.StartPoint);
moel@391
   594
                        contourPoints.Add(firstSegment.EndPoint);
moel@391
   595
                        contourPointsCount += 2;
moel@391
   596
                        this.segments.Remove(firstSegment);
moel@391
   597
                        segmentCount--;
moel@391
   598
                    }
moel@391
   599
                }
moel@391
   600
            }
moel@391
   601
        }
moel@391
   602
moel@391
   603
        /// <summary>
moel@391
   604
        /// Renders the contour label.
moel@391
   605
        /// </summary>
moel@391
   606
        /// <param name="rc">
moel@391
   607
        /// The render context.
moel@391
   608
        /// </param>
moel@391
   609
        /// <param name="cl">
moel@391
   610
        /// The contour label.
moel@391
   611
        /// </param>
moel@391
   612
        private void RenderLabel(IRenderContext rc, ContourLabel cl)
moel@391
   613
        {
moel@391
   614
            if (this.ActualFontSize > 0)
moel@391
   615
            {
moel@391
   616
                rc.DrawText(
moel@391
   617
                    cl.Position,
moel@391
   618
                    cl.Text,
moel@391
   619
                    this.ActualTextColor,
moel@391
   620
                    this.ActualFont,
moel@391
   621
                    this.ActualFontSize,
moel@391
   622
                    this.ActualFontWeight,
moel@391
   623
                    cl.Angle,
moel@391
   624
                    HorizontalAlignment.Center,
moel@391
   625
                    VerticalAlignment.Middle);
moel@391
   626
            }
moel@391
   627
        }
moel@391
   628
moel@391
   629
        /// <summary>
moel@391
   630
        /// Renders the contour label background.
moel@391
   631
        /// </summary>
moel@391
   632
        /// <param name="rc">
moel@391
   633
        /// The render context.
moel@391
   634
        /// </param>
moel@391
   635
        /// <param name="cl">
moel@391
   636
        /// The contour label.
moel@391
   637
        /// </param>
moel@391
   638
        private void RenderLabelBackground(IRenderContext rc, ContourLabel cl)
moel@391
   639
        {
moel@391
   640
            if (this.LabelBackground != null)
moel@391
   641
            {
moel@391
   642
                // Calculate background polygon
moel@391
   643
                var size = rc.MeasureText(cl.Text, this.ActualFont, this.ActualFontSize, this.ActualFontWeight);
moel@391
   644
                double a = cl.Angle / 180 * Math.PI;
moel@391
   645
                double dx = Math.Cos(a);
moel@391
   646
                double dy = Math.Sin(a);
moel@391
   647
moel@391
   648
                double ux = dx * 0.6;
moel@391
   649
                double uy = dy * 0.6;
moel@391
   650
                double vx = -dy * 0.5;
moel@391
   651
                double vy = dx * 0.5;
moel@391
   652
                double x = cl.Position.X;
moel@391
   653
                double y = cl.Position.Y;
moel@391
   654
moel@391
   655
                var bpts = new[]
moel@391
   656
                    {
moel@391
   657
                        new ScreenPoint(x - (size.Width * ux) - (size.Height * vx), y - (size.Width * uy) - (size.Height * vy)),
moel@391
   658
                        new ScreenPoint(x + (size.Width * ux) - (size.Height * vx), y + (size.Width * uy) - (size.Height * vy)),
moel@391
   659
                        new ScreenPoint(x + (size.Width * ux) + (size.Height * vx), y + (size.Width * uy) + (size.Height * vy)),
moel@391
   660
                        new ScreenPoint(x - (size.Width * ux) + (size.Height * vx), y - (size.Width * uy) + (size.Height * vy))
moel@391
   661
                    };
moel@391
   662
                rc.DrawPolygon(bpts, this.LabelBackground, null);
moel@391
   663
            }
moel@391
   664
        }
moel@391
   665
moel@391
   666
        /// <summary>
moel@391
   667
        /// Represents a contour.
moel@391
   668
        /// </summary>
moel@391
   669
        private class Contour
moel@391
   670
        {
moel@391
   671
            /// <summary>
moel@391
   672
            /// Gets or sets the contour level.
moel@391
   673
            /// </summary>
moel@391
   674
            /// <value>The contour level.</value>
moel@391
   675
            internal readonly double ContourLevel;
moel@391
   676
moel@391
   677
            /// <summary>
moel@391
   678
            /// Gets or sets the points.
moel@391
   679
            /// </summary>
moel@391
   680
            /// <value>The points.</value>
moel@391
   681
            internal readonly IList<IDataPoint> Points;
moel@391
   682
moel@391
   683
            /// <summary>
moel@391
   684
            /// Initializes a new instance of the <see cref="Contour"/> class.
moel@391
   685
            /// </summary>
moel@391
   686
            /// <param name="points">
moel@391
   687
            /// The points.
moel@391
   688
            /// </param>
moel@391
   689
            /// <param name="contourLevel">
moel@391
   690
            /// The contour level.
moel@391
   691
            /// </param>
moel@391
   692
            public Contour(IList<IDataPoint> points, double contourLevel)
moel@391
   693
            {
moel@391
   694
                this.Points = points;
moel@391
   695
                this.ContourLevel = contourLevel;
moel@391
   696
            }
moel@391
   697
moel@391
   698
            /// <summary>
moel@391
   699
            /// Gets or sets the color of the contour.
moel@391
   700
            /// </summary>
moel@391
   701
            public OxyColor Color { get; set; }
moel@391
   702
        }
moel@391
   703
moel@391
   704
        /// <summary>
moel@391
   705
        /// Represents a contour label.
moel@391
   706
        /// </summary>
moel@391
   707
        private class ContourLabel
moel@391
   708
        {
moel@391
   709
            /// <summary>
moel@391
   710
            /// Gets or sets the angle.
moel@391
   711
            /// </summary>
moel@391
   712
            /// <value>The angle.</value>
moel@391
   713
            public double Angle { get; set; }
moel@391
   714
moel@391
   715
            /// <summary>
moel@391
   716
            /// Gets or sets the position.
moel@391
   717
            /// </summary>
moel@391
   718
            /// <value>The position.</value>
moel@391
   719
            public ScreenPoint Position { get; set; }
moel@391
   720
moel@391
   721
            /// <summary>
moel@391
   722
            /// Gets or sets the text.
moel@391
   723
            /// </summary>
moel@391
   724
            /// <value>The text.</value>
moel@391
   725
            public string Text { get; set; }
moel@391
   726
moel@391
   727
        }
moel@391
   728
moel@391
   729
        /// <summary>
moel@391
   730
        /// Represents a contour segment.
moel@391
   731
        /// </summary>
moel@391
   732
        private class ContourSegment
moel@391
   733
        {
moel@391
   734
            /// <summary>
moel@391
   735
            /// The contour level.
moel@391
   736
            /// </summary>
moel@391
   737
            internal readonly double ContourLevel;
moel@391
   738
moel@391
   739
            /// <summary>
moel@391
   740
            /// The end point.
moel@391
   741
            /// </summary>
moel@391
   742
            internal readonly DataPoint EndPoint;
moel@391
   743
moel@391
   744
            /// <summary>
moel@391
   745
            /// The start point.
moel@391
   746
            /// </summary>
moel@391
   747
            internal readonly DataPoint StartPoint;
moel@391
   748
moel@391
   749
            /// <summary>
moel@391
   750
            /// Initializes a new instance of the <see cref="ContourSegment"/> class.
moel@391
   751
            /// </summary>
moel@391
   752
            /// <param name="startPoint">
moel@391
   753
            /// The start point.
moel@391
   754
            /// </param>
moel@391
   755
            /// <param name="endPoint">
moel@391
   756
            /// The end point.
moel@391
   757
            /// </param>
moel@391
   758
            /// <param name="contourLevel">
moel@391
   759
            /// The contour level.
moel@391
   760
            /// </param>
moel@391
   761
            public ContourSegment(DataPoint startPoint, DataPoint endPoint, double contourLevel)
moel@391
   762
            {
moel@391
   763
                this.ContourLevel = contourLevel;
moel@391
   764
                this.StartPoint = startPoint;
moel@391
   765
                this.EndPoint = endPoint;
moel@391
   766
            }
moel@391
   767
moel@391
   768
        }
moel@391
   769
    }
moel@391
   770
}