/// <summary> /// Draw the the PlotSurface and contents (axes, drawables, and legend) using the /// Drawing Context supplied and the bounding rectangle for the PlotSurface to cover /// </summary> /// <param name="ctx">The Drawing Context with which to draw.</param> /// <param name="bounds">The rectangle within which to draw</param> public void Draw(Context ctx, Rectangle bounds) { Point titleOrigin = Point.Zero; ctx.Save(); // determine font sizes and tick scale factor. double scale = DetermineScaleFactor(bounds.Width, bounds.Height); // if there is nothing to plot, draw title and return. if (drawables.Count == 0) { // draw title //TODO: Title should be centred here - not its origin Point origin = Point.Zero; titleOrigin.X = bounds.Width / 2; titleOrigin.Y = bounds.Height / 2; DrawTitle(ctx, titleOrigin, scale); ctx.Restore(); return; } // determine the [non physical] axes to draw based on the axis properties set. Axis XAxis1 = null; Axis XAxis2 = null; Axis YAxis1 = null; Axis YAxis2 = null; DetermineAxesToDraw(out XAxis1, out XAxis2, out YAxis1, out YAxis2); // apply scale factor to axes as desired. if (XAxis1.AutoScaleTicks) { XAxis1.TickScale = scale; } if (XAxis1.AutoScaleText) { XAxis1.FontScale = scale; } if (YAxis1.AutoScaleTicks) { YAxis1.TickScale = scale; } if (YAxis1.AutoScaleText) { YAxis1.FontScale = scale; } if (XAxis2.AutoScaleTicks) { XAxis2.TickScale = scale; } if (XAxis2.AutoScaleText) { XAxis2.FontScale = scale; } if (YAxis2.AutoScaleTicks) { YAxis2.TickScale = scale; } if (YAxis2.AutoScaleText) { YAxis2.FontScale = scale; } // determine the default physical positioning of those axes. PhysicalAxis pXAxis1 = null; PhysicalAxis pYAxis1 = null; PhysicalAxis pXAxis2 = null; PhysicalAxis pYAxis2 = null; DeterminePhysicalAxesToDraw( bounds, XAxis1, XAxis2, YAxis1, YAxis2, out pXAxis1, out pXAxis2, out pYAxis1, out pYAxis2); double oldXAxis2Height = pXAxis2.PhysicalMin.Y; // Apply axes constraints for (int i = 0; i < axesConstraints.Count; ++i) { ((AxesConstraint)axesConstraints[i]).ApplyConstraint( pXAxis1, pYAxis1, pXAxis2, pYAxis2); } // draw legend if have one. // Note: this will update axes if necessary. Point legendPosition = new Point(0, 0); if (legend != null) { legend.UpdateAxesPositions( pXAxis1, pYAxis1, pXAxis2, pYAxis2, drawables, scale, Padding, bounds, out legendPosition); } double newXAxis2Height = pXAxis2.PhysicalMin.Y; double titleExtraOffset = oldXAxis2Height - newXAxis2Height; // now we are ready to define the clipping region plotAreaBoundingBoxCache = new Rectangle( Math.Min(pXAxis1.PhysicalMin.X, pXAxis1.PhysicalMax.X), Math.Min(pYAxis1.PhysicalMax.Y, pYAxis1.PhysicalMin.Y), Math.Abs(pXAxis1.PhysicalMax.X - pXAxis1.PhysicalMin.X + 1), Math.Abs(pYAxis1.PhysicalMin.Y - pYAxis1.PhysicalMax.Y + 1) ); bbXAxis1Cache = pXAxis1.GetBoundingBox(); bbXAxis2Cache = pXAxis2.GetBoundingBox(); bbYAxis1Cache = pYAxis1.GetBoundingBox(); bbYAxis2Cache = pYAxis2.GetBoundingBox(); Rectangle plotBounds = (Rectangle)plotAreaBoundingBoxCache; // set the clipping region.. (necessary for zoom) // Note: although clipping is enforced by the clip region, it is probably more efficient // for each Drawable to check against the plotBounds and not draw if points are outside. // This hasn't yet been implemented ctx.Save(); ctx.Rectangle(plotBounds); ctx.Clip(); // Fill in the plot background. if (plotBackImage != null) { // Ensure plotBounds has integer size for correct tiling/drawing plotBounds.Width = Math.Truncate(plotBounds.Width); plotBounds.Height = Math.Truncate(plotBounds.Height); ctx.DrawImage(Utils.TiledImage(plotBackImage, plotBounds.Size), plotBounds); } else if (plotBackGradient != null) { // Scale plotBackGradient to plotBounds double startX = plotBounds.X + (plotBackGradient.StartPoint.X * plotBounds.Width); double startY = plotBounds.Y + (plotBackGradient.StartPoint.Y * plotBounds.Height); double endX = plotBounds.X + (plotBackGradient.EndPoint.X * plotBounds.Width); double endY = plotBounds.Y + (plotBackGradient.EndPoint.Y * plotBounds.Height); LinearGradient g = new LinearGradient(startX, startY, endX, endY); g.AddColorStop(0, plotBackGradient.StartColor); g.AddColorStop(1, plotBackGradient.EndColor); ctx.Rectangle(plotBounds); ctx.Pattern = g; ctx.Fill(); } else { ctx.Rectangle(plotBounds); ctx.SetColor(plotBackColor); ctx.Fill(); } // draw title at centre of Physical X-axis and at top of plot titleOrigin.X = (pXAxis2.PhysicalMax.X + pXAxis2.PhysicalMin.X) / 2.0; titleOrigin.Y = bounds.Top + Padding - titleExtraOffset; Size s = DrawTitle(ctx, titleOrigin, scale); bbTitleCache = new Rectangle(titleOrigin.X - s.Width / 2, titleOrigin.Y, s.Width, s.Height); // draw drawables.. bool legendDrawn = false; for (int i_o = 0; i_o < ordering.Count; ++i_o) { int i = (int)ordering.GetByIndex(i_o); double zOrder = (double)ordering.GetKey(i_o); if (zOrder > legendZOrder) { // draw legend. if (!legendDrawn && legend != null) { legend.Draw(ctx, legendPosition, drawables, scale); legendDrawn = true; } } IDrawable drawable = (IDrawable)drawables[i]; XAxisPosition xap = (XAxisPosition)xAxisPositions[i]; YAxisPosition yap = (YAxisPosition)yAxisPositions[i]; PhysicalAxis drawXAxis; PhysicalAxis drawYAxis; if (xap == XAxisPosition.Bottom) { drawXAxis = pXAxis1; } else { drawXAxis = pXAxis2; } if (yap == YAxisPosition.Left) { drawYAxis = pYAxis1; } else { drawYAxis = pYAxis2; } drawable.Draw(ctx, drawXAxis, drawYAxis); } if (!legendDrawn && legend != null) { legend.Draw(ctx, legendPosition, drawables, scale); } ctx.Restore(); // end of clipping region // cache the physical axes we used on this draw; pXAxis1Cache = pXAxis1; pYAxis1Cache = pYAxis1; pXAxis2Cache = pXAxis2; pYAxis2Cache = pYAxis2; // now draw axes. Rectangle axisBounds; pXAxis1.Draw(ctx, out axisBounds); pXAxis2.Draw(ctx, out axisBounds); pYAxis1.Draw(ctx, out axisBounds); pYAxis2.Draw(ctx, out axisBounds); #if DEBUG_BOUNDING_BOXES ctx.SetColor(Colors.Orange); ctx.Rectangle((Rectangle)bbXAxis1Cache); ctx.Rectangle((Rectangle)bbXAxis2Cache); ctx.Rectangle((Rectangle)bbYAxis1Cache); ctx.Rectangle((Rectangle)bbYAxis2Cache); ctx.Stroke(); ctx.SetColor(Colors.Red); ctx.Rectangle((Rectangle)plotAreaBoundingBoxCache); ctx.Rectangle((Rectangle)bbTitleCache); ctx.Stroke(); #endif ctx.Restore(); }