/// <summary> /// Fast index of bar where max(bar[i].X) <= x /// </summary> /// <returns>The index of the bar closest to X, where max(bar[i].X) <= x.</returns> /// <param name="x">The x coordinate.</param> /// <param name="startingIndex">starting index</param> public int FindByX(double x, int startingIndex = -1) { if (startingIndex < 0) { startingIndex = this.winIndex; } return(OhlcvItem.FindIndex(this.data, x, startingIndex)); }
/// <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.XAxis == null || this.YAxis == null || interpolate || this.data.Count == 0) { return(null); } var nbars = this.data.Count; var xy = this.InverseTransform(point); var targetX = xy.X; // punt if beyond start & end of series if (targetX > (this.data[nbars - 1].X + this.minDx)) { return(null); } else if (targetX < (this.data[0].X - this.minDx)) { return(null); } var pidx = OhlcvItem.FindIndex(this.data, targetX, this.winIndex); var nidx = ((pidx + 1) < this.data.Count) ? pidx + 1 : pidx; Func <OhlcvItem, double> distance = bar => { var dx = bar.X - xy.X; return(dx * dx); }; // determine closest point var midx = distance(this.data[pidx]) <= distance(this.data[nidx]) ? pidx : nidx; var mbar = this.data[midx]; var hit = new DataPoint(mbar.X, mbar.Close); return(new TrackerHitResult { Series = this, DataPoint = hit, Position = this.Transform(hit), Item = mbar, Index = midx, Text = StringHelper.Format( this.ActualCulture, this.TrackerFormatString, mbar, this.XAxis.GetValue(mbar.X), this.YAxis.GetValue(mbar.High), this.YAxis.GetValue(mbar.Low), this.YAxis.GetValue(mbar.Open), this.YAxis.GetValue(mbar.Close), this.YAxis.GetValue(mbar.BuyVolume), this.YAxis.GetValue(mbar.SellVolume)) }); }
/// <summary> /// Append a bar to the series (must be in X order) /// </summary> /// <param name="bar">The Bar.</param> public void Append(OhlcvItem bar) { if (this.data == null) { this.data = new List <OhlcvItem>(); } if (this.data.Count > 0 && this.data[this.data.Count - 1].X > bar.X) { throw new ArgumentException("cannot append bar out of order, must be sequential in X"); } this.data.Add(bar); }
/// <summary> /// Append a bar to the series (must be in X order) /// </summary> /// <param name="bar">Bar object.</param> public void Append(OhlcvItem bar) { if (this.data == null) { this.data = new List<OhlcvItem>(); } if (this.data.Count > 0 && this.data[this.data.Count - 1].X > bar.X) { throw new ArgumentException("cannot append bar out of order, must be sequential in X"); } this.data.Add(bar); }
/// <summary> /// Renders the series on the specified rendering context. /// </summary> /// <param name="rc">The rendering context.</param> public override void Render(IRenderContext rc) { if (this.data == null || this.data.Count == 0) { return; } var items = this.data; var nitems = this.data.Count; this.VerifyAxes(); var clippingRect = this.GetClippingRect(); var datacandlewidth = (this.BarWidth > 0) ? this.BarWidth : this.minDx * 0.80; var halfDataCandleWidth = datacandlewidth * 0.5; // colors var fillUp = this.GetSelectableFillColor(this.PositiveColor); var fillDown = this.GetSelectableFillColor(this.NegativeColor); var barfillUp = this.PositiveHollow ? OxyColors.Transparent : fillUp; var barfillDown = this.NegativeHollow ? OxyColors.Transparent : fillDown; var lineUp = this.GetSelectableColor(this.PositiveColor.ChangeIntensity(this.StrokeIntensity)); var lineDown = this.GetSelectableColor(this.NegativeColor.ChangeIntensity(this.StrokeIntensity)); // determine render range var xmin = this.XAxis.ActualMinimum; var xmax = this.XAxis.ActualMaximum; this.winIndex = OhlcvItem.FindIndex(items, xmin, this.winIndex); for (int i = this.winIndex; i < nitems; i++) { var bar = items[i]; // if item beyond visible range, done if (bar.X > xmax) { break; } // check to see whether is valid if (!bar.IsValid()) { continue; } var p1 = this.Transform(bar.X - halfDataCandleWidth, 0); switch (this.VolumeStyle) { case VolumeStyle.Combined: { var p2 = this.Transform(bar.X + halfDataCandleWidth, Math.Abs(bar.BuyVolume - bar.SellVolume)); var fillcolor = (bar.BuyVolume > bar.SellVolume) ? barfillUp : barfillDown; var linecolor = (bar.BuyVolume > bar.SellVolume) ? lineUp : lineDown; var rect1 = new OxyRect(p1, p2); rc.DrawClippedRectangleAsPolygon(clippingRect, rect1, fillcolor, linecolor, this.StrokeThickness); } break; case VolumeStyle.PositiveNegative: { var p2Buy = this.Transform(bar.X + halfDataCandleWidth, bar.BuyVolume); var p2Bell = this.Transform(bar.X + halfDataCandleWidth, -bar.SellVolume); var rectBuy = new OxyRect(p1, p2Buy); var rectSell = new OxyRect(p1, p2Bell); rc.DrawClippedRectangleAsPolygon(clippingRect, rectBuy, fillUp, lineUp, this.StrokeThickness); rc.DrawClippedRectangleAsPolygon(clippingRect, rectSell, fillDown, lineDown, this.StrokeThickness); } break; case VolumeStyle.Stacked: { var p2Buy = this.Transform(bar.X + halfDataCandleWidth, bar.BuyVolume); var p2Sell = this.Transform(bar.X + halfDataCandleWidth, bar.SellVolume); var pBoth = this.Transform(bar.X - halfDataCandleWidth, bar.BuyVolume + bar.SellVolume); OxyRect rectBuy; OxyRect rectSell; if (bar.BuyVolume > bar.SellVolume) { rectSell = new OxyRect(p1, p2Sell); rectBuy = new OxyRect(p2Sell, pBoth); } else { rectBuy = new OxyRect(p1, p2Buy); rectSell = new OxyRect(p2Buy, pBoth); } rc.DrawClippedRectangleAsPolygon(clippingRect, rectBuy, fillUp, lineUp, this.StrokeThickness); rc.DrawClippedRectangleAsPolygon(clippingRect, rectSell, fillDown, lineDown, this.StrokeThickness); break; } case VolumeStyle.None: break; default: throw new ArgumentOutOfRangeException(); } } if (this.InterceptStrokeThickness > 0 && this.InterceptLineStyle != LineStyle.None) { // draw volume y=0 line var p1 = this.InverseTransform(clippingRect.BottomLeft); var p2 = this.InverseTransform(clippingRect.TopRight); var lineA = this.Transform(p1.X, 0); var lineB = this.Transform(p2.X, 0); rc.DrawClippedLine( clippingRect, new[] { lineA, lineB }, 0, this.InterceptColor, this.InterceptStrokeThickness, this.InterceptLineStyle.GetDashArray(), LineJoin.Miter, true); } }
/// <summary> /// Renders the series on the specified rendering context. /// </summary> /// <param name="rc">The rendering context.</param> // ReSharper disable once FunctionComplexityOverflow public override void Render(IRenderContext rc) { if (this.data == null || this.data.Count == 0) { return; } var items = this.data; var nitems = this.data.Count; this.VerifyAxes(); var clippingBar = this.GetClippingRect(this.BarAxis); var clippingSep = this.GetSeparationClippingRect(); var clippingVol = this.GetClippingRect(this.VolumeAxis); var datacandlewidth = (this.CandleWidth > 0) ? this.CandleWidth : this.minDx * 0.80; var candlewidth = this.XAxis.Transform(items[0].X + datacandlewidth) - this.XAxis.Transform(items[0].X) - this.StrokeThickness; // colors var fillUp = this.GetSelectableFillColor(this.PositiveColor); var fillDown = this.GetSelectableFillColor(this.NegativeColor); var barfillUp = this.PositiveHollow ? OxyColors.Transparent : fillUp; var barfillDown = this.NegativeHollow ? OxyColors.Transparent : fillDown; var lineUp = this.GetSelectableColor(this.PositiveColor.ChangeIntensity(this.StrokeIntensity)); var lineDown = this.GetSelectableColor(this.NegativeColor.ChangeIntensity(this.StrokeIntensity)); // determine render range var xmin = this.XAxis.ActualMinimum; var xmax = this.XAxis.ActualMaximum; this.winIndex = OhlcvItem.FindIndex(items, xmin, this.winIndex); for (int i = this.winIndex; i < nitems; i++) { var bar = items[i]; // if item beyond visible range, done if (bar.X > xmax) { break; } // check to see whether is valid if (!bar.IsValid()) { continue; } var fillColor = bar.Close > bar.Open ? barfillUp : barfillDown; var lineColor = bar.Close > bar.Open ? lineUp : lineDown; var high = this.Transform(bar.X, bar.High); var low = this.Transform(bar.X, bar.Low); var open = this.Transform(bar.X, bar.Open); var close = this.Transform(bar.X, bar.Close); var max = new ScreenPoint(open.X, Math.Max(open.Y, close.Y)); var min = new ScreenPoint(open.X, Math.Min(open.Y, close.Y)); // Bar part rc.DrawClippedLine( clippingBar, new[] { high, min }, 0, lineColor, this.StrokeThickness, null, LineJoin.Miter, true); // Lower extent rc.DrawClippedLine( clippingBar, new[] { max, low }, 0, lineColor, this.StrokeThickness, null, LineJoin.Miter, true); // Body var openLeft = open + new ScreenVector(-candlewidth * 0.5, 0); var rect = new OxyRect(openLeft.X, min.Y, candlewidth, max.Y - min.Y); rc.DrawClippedRectangleAsPolygon( clippingBar, rect, fillColor, lineColor, this.StrokeThickness); // Volume Part if (this.VolumeAxis == null || this.VolumeStyle == VolumeStyle.None) { continue; } var iY0 = this.VolumeAxis.Transform(0); switch (this.VolumeStyle) { case VolumeStyle.Combined: { var adj = this.VolumeAxis.Transform(Math.Abs(bar.BuyVolume - bar.SellVolume)); var fillcolor = (bar.BuyVolume > bar.SellVolume) ? barfillUp : barfillDown; var linecolor = (bar.BuyVolume > bar.SellVolume) ? lineUp : lineDown; var rect1 = new OxyRect(openLeft.X, adj, candlewidth, Math.Abs(adj - iY0)); rc.DrawClippedRectangleAsPolygon(clippingVol, rect1, fillcolor, linecolor, this.StrokeThickness); } break; case VolumeStyle.PositiveNegative: { var buyY = this.VolumeAxis.Transform(bar.BuyVolume); var sellY = this.VolumeAxis.Transform(-bar.SellVolume); var rect1 = new OxyRect(openLeft.X, buyY, candlewidth, Math.Abs(buyY - iY0)); rc.DrawClippedRectangleAsPolygon(clippingVol, rect1, fillUp, lineUp, this.StrokeThickness); var rect2 = new OxyRect(openLeft.X, iY0, candlewidth, Math.Abs(sellY - iY0)); rc.DrawClippedRectangleAsPolygon(clippingVol, rect2, fillDown, lineDown, this.StrokeThickness); } break; case VolumeStyle.Stacked: if (bar.BuyVolume > bar.SellVolume) { var buyY = this.VolumeAxis.Transform(bar.BuyVolume); var sellY = this.VolumeAxis.Transform(bar.SellVolume); var dyoffset = sellY - iY0; var rect2 = new OxyRect(openLeft.X, sellY, candlewidth, Math.Abs(sellY - iY0)); rc.DrawClippedRectangleAsPolygon(clippingVol, rect2, fillDown, lineDown, this.StrokeThickness); var rect1 = new OxyRect(openLeft.X, buyY + dyoffset, candlewidth, Math.Abs(buyY - iY0)); rc.DrawClippedRectangleAsPolygon(clippingVol, rect1, fillUp, lineUp, this.StrokeThickness); } else { var buyY = this.VolumeAxis.Transform(bar.BuyVolume); var sellY = this.VolumeAxis.Transform(bar.SellVolume); var dyoffset = buyY - iY0; var rect1 = new OxyRect(openLeft.X, buyY, candlewidth, Math.Abs(buyY - iY0)); rc.DrawClippedRectangleAsPolygon(clippingVol, rect1, fillUp, lineUp, this.StrokeThickness); var rect2 = new OxyRect(openLeft.X, sellY + dyoffset, candlewidth, Math.Abs(sellY - iY0)); rc.DrawClippedRectangleAsPolygon(clippingVol, rect2, fillDown, lineDown, this.StrokeThickness); } break; } } // draw volume & bar separation line if (this.VolumeStyle != VolumeStyle.None) { var ysep = (clippingSep.Bottom + clippingSep.Top) / 2.0; rc.DrawClippedLine( clippingSep, new[] { new ScreenPoint(clippingSep.Left, ysep), new ScreenPoint(clippingSep.Right, ysep) }, 0, this.SeparatorColor, this.SeparatorStrokeThickness, this.SeparatorLineStyle.GetDashArray(), LineJoin.Miter, true); } // draw volume y=0 line if (this.VolumeAxis != null && this.VolumeStyle == VolumeStyle.PositiveNegative) { var y0 = this.VolumeAxis.Transform(0); rc.DrawClippedLine( clippingVol, new[] { new ScreenPoint(clippingVol.Left, y0), new ScreenPoint(clippingVol.Right, y0) }, 0, OxyColors.Goldenrod, this.SeparatorStrokeThickness, this.SeparatorLineStyle.GetDashArray(), LineJoin.Miter, true); } }
/// <summary> /// Renders the series on the specified rendering context. /// </summary> /// <param name="rc">The rendering context.</param> public override void Render(IRenderContext rc) { if (this.data == null || this.data.Count == 0) { return; } var items = this.data; var nitems = this.data.Count; this.VerifyAxes(); var clipping = this.GetClippingRect(); var datacandlewidth = (this.BarWidth > 0) ? this.BarWidth : this.minDx * 0.80; var candlewidth = this.XAxis.Transform(items[0].X + datacandlewidth) - this.XAxis.Transform(items[0].X) - this.StrokeThickness; // colors var fillUp = this.GetSelectableFillColor(this.PositiveColor); var fillDown = this.GetSelectableFillColor(this.NegativeColor); var barfillUp = this.PositiveHollow ? OxyColors.Transparent : fillUp; var barfillDown = this.NegativeHollow ? OxyColors.Transparent : fillDown; var lineUp = this.GetSelectableColor(this.PositiveColor.ChangeIntensity(this.StrokeIntensity)); var lineDown = this.GetSelectableColor(this.NegativeColor.ChangeIntensity(this.StrokeIntensity)); // determine render range var xmin = this.XAxis.ActualMinimum; var xmax = this.XAxis.ActualMaximum; this.winIndex = OhlcvItem.FindIndex(items, xmin, this.winIndex); for (int i = this.winIndex; i < nitems; i++) { var bar = items[i]; // if item beyond visible range, done if (bar.X > xmax) { break; } // check to see whether is valid if (!bar.IsValid()) { continue; } var leftX = this.XAxis.Transform(bar.X) - (this.BarWidth / 2.0); var y0 = this.YAxis.Transform(0); switch (this.VolumeStyle) { case VolumeStyle.Combined: { var adj = this.YAxis.Transform(Math.Abs(bar.BuyVolume - bar.SellVolume)); var fillcolor = (bar.BuyVolume > bar.SellVolume) ? barfillUp : barfillDown; var linecolor = (bar.BuyVolume > bar.SellVolume) ? lineUp : lineDown; var rect1 = new OxyRect(leftX, adj, candlewidth, Math.Abs(adj - y0)); rc.DrawClippedRectangleAsPolygon(clipping, rect1, fillcolor, linecolor, this.StrokeThickness); } break; case VolumeStyle.PositiveNegative: { var buyY = this.YAxis.Transform(bar.BuyVolume); var sellY = this.YAxis.Transform(-bar.SellVolume); var rect1 = new OxyRect(leftX, buyY, candlewidth, Math.Abs(buyY - y0)); rc.DrawClippedRectangleAsPolygon(clipping, rect1, fillUp, lineUp, this.StrokeThickness); var rect2 = new OxyRect(leftX, y0, candlewidth, Math.Abs(sellY - y0)); rc.DrawClippedRectangleAsPolygon(clipping, rect2, fillDown, lineDown, this.StrokeThickness); } break; case VolumeStyle.Stacked: if (bar.BuyVolume > bar.SellVolume) { var buyY = this.YAxis.Transform(bar.BuyVolume); var sellY = this.YAxis.Transform(bar.SellVolume); var dyoffset = sellY - y0; var rect2 = new OxyRect(leftX, sellY, candlewidth, Math.Abs(sellY - y0)); rc.DrawClippedRectangleAsPolygon(clipping, rect2, fillDown, lineDown, this.StrokeThickness); var rect1 = new OxyRect(leftX, buyY + dyoffset, candlewidth, Math.Abs(buyY - y0)); rc.DrawClippedRectangleAsPolygon(clipping, rect1, fillUp, lineUp, this.StrokeThickness); } else { var buyY = this.YAxis.Transform(bar.BuyVolume); var sellY = this.YAxis.Transform(bar.SellVolume); var dyoffset = buyY - y0; var rect1 = new OxyRect(leftX, buyY, candlewidth, Math.Abs(buyY - y0)); rc.DrawClippedRectangleAsPolygon(clipping, rect1, fillUp, lineUp, this.StrokeThickness); var rect2 = new OxyRect(leftX, sellY + dyoffset, candlewidth, Math.Abs(sellY - y0)); rc.DrawClippedRectangleAsPolygon(clipping, rect2, fillDown, lineDown, this.StrokeThickness); } break; } } // draw volume y=0 line var intercept = this.YAxis.Transform(0); rc.DrawClippedLine( clipping, new[] { new ScreenPoint(clipping.Left, intercept), new ScreenPoint(clipping.Right, intercept) }, 0, this.InterceptColor, this.InterceptStrokeThickness, this.InterceptLineStyle.GetDashArray(), LineJoin.Miter, true); }