Hello Telerik Support,
after taking my first steps with RangeSelector and ChartView controls I found an issue with a simple bar chart. The length of some bars in the chart of the RangeSelector doesn't match with the bars in the ChartView. The relation is wrong.I provided a screenshot and marked the bars. I could provide a sample application, but I can't attach a zip file, so here's the code of the main form:
using
System;
using
System.Collections.Generic;
using
System.ComponentModel;
using
System.Data;
using
System.Drawing;
using
System.Linq;
using
System.Text;
using
System.Windows.Forms;
using
Telerik.WinControls;
using
Telerik.WinControls.UI;
namespace
TelerikChartView
{
public
partial
class
MainForm : Telerik.WinControls.UI.RadForm
{
private
List<Kapazitaetspunkt> _BackListPunkte;
private
BindingList<Kapazitaetspunkt> Punkte;
public
MainForm()
{
InitializeComponent();
}
private
void
MainForm_Load(
object
sender, EventArgs e)
{
_BackListPunkte =
new
List<Kapazitaetspunkt>();
Punkte =
new
BindingList<Kapazitaetspunkt>(_BackListPunkte);
//Punkte = new BindingList<Kapazitaetspunkt>();
rngTimeSelector.AssociatedControl = cvChart;
rngTimeSelector.RangeSelectorElement.ScrollSelectorElement.Visibility = ElementVisibility.Collapsed;
BarSeries bars =
new
BarSeries();
bars.DataSource = Punkte;
bars.ValueMember = nameof(Kapazitaetspunkt.Percentage);
bars.CategoryMember = nameof(Kapazitaetspunkt.Date);
cvChart.Series.Add(bars);
}
private
void
GenerateTestData(
int
addMonths = 0)
{
Punkte.Clear();
const
int
count = 25;
const
int
step = 4;
var von = DateTime.Today.AddMonths(addMonths);
var bis = von.AddDays(count);
double
percentage = 0;
//_BackListPunkte.Add(new Kapazitaetspunkt(von.AddDays(-1), null));
Punkte.Add(
new
Kapazitaetspunkt(von.AddDays(-1),
null
));
while
(von < bis)
{
//_BackListPunkte.Add(new Kapazitaetspunkt(von, percentage));
Punkte.Add(
new
Kapazitaetspunkt(von, percentage));
percentage += step;
von = von.AddDays(1);
}
//_BackListPunkte.Add(new Kapazitaetspunkt(von.AddDays(1), null));
Punkte.Add(
new
Kapazitaetspunkt(von.AddDays(1),
null
));
}
private
void
btnGenerateData_Click(
object
sender, EventArgs e)
{
GenerateTestData();
rngTimeSelector.RangeSelectorElement.InitializeElements();
rngTimeSelector.RangeSelectorElement.ResetLayout(
true
);
}
private
void
btnGenerateData2_Click(
object
sender, EventArgs e)
{
GenerateTestData(2);
rngTimeSelector.RangeSelectorElement.InitializeElements();
rngTimeSelector.RangeSelectorElement.ResetLayout(
true
);
}
private
void
rngTimeSelector_ScaleInitializing(
object
sender, ScaleInitializingEventArgs e)
{
e.Cancel =
true
;
}
}
public
class
Kapazitaetspunkt : INotifyPropertyChanged
{
private
DateTime _Date;
public
DateTime Date
{
get
=> _Date;
set
{
if
(value != Date)
{
_Date = value;
OnPropertyChanged();
}
}
}
public
double
? _Percentage;
public
double
? Percentage
{
get
=> _Percentage;
set
{
if
(value != Percentage)
{
_Percentage = value;
OnPropertyChanged();
}
}
}
public
Kapazitaetspunkt(DateTime date,
double
? percentage)
{
Date = date;
Percentage = percentage;
}
public
event
PropertyChangedEventHandler PropertyChanged;
protected
virtual
void
OnPropertyChanged([CallerMemberName]
string
propertyName =
null
)
{
PropertyChanged?.Invoke(
this
,
new
PropertyChangedEventArgs(propertyName));
}
}
}
Regards,
Stephan
To reproduce: please refer to the attached sample project and gif file. Workaround: CustomLassoSelectionController lassoSelectionController = new CustomLassoSelectionController(); this.radChartView1.Controllers.Add(lassoSelectionController); public class CustomLassoSelectionController : LassoSelectionController { private IList<DataPoint> selectedPoints = null; public CustomLassoSelectionController() { this.selectedPoints = new List<DataPoint>(); } private Point ClipLocation(Point point) { CartesianArea area = this.Area.View.GetArea<CartesianArea>(); if (area != null) { RectangleF clipRect = GetCartesianClipRect(area); if (point.X < clipRect.X) { point = new Point((int)clipRect.X, point.Y); } if (point.X > clipRect.Width + clipRect.X) { point = new Point((int)clipRect.Width + (int)clipRect.X, point.Y); } if (point.Y < clipRect.Y) { point = new Point(point.X, (int)clipRect.Y); } if (point.Y > clipRect.Height + clipRect.Y) { point = new Point(point.X, (int)clipRect.Height + (int)clipRect.Y); } } return point; } internal RectangleF GetCartesianClipRect(CartesianArea area) { float x1, x2, y1, y2; x1 = 0; y1 = 0; x2 = (float)area.View.Viewport.Right; y2 = (float)area.View.Viewport.Bottom; foreach (var axis in area.View.Axes) { if (axis.AxisType == AxisType.First) { if (axis.Model.VerticalLocation == AxisVerticalLocation.Bottom) { y2 = Math.Min(y2, (float)axis.Model.LayoutSlot.Y); } else { y1 = Math.Max(y1, (float)axis.Model.LayoutSlot.Bottom); } x1 = Math.Min(x1, (float)axis.Model.LayoutSlot.X); x2 = Math.Min(x2, (float)axis.Model.LayoutSlot.Right); } else { if (axis.Model.HorizontalLocation == AxisHorizontalLocation.Left) { x1 = Math.Max(x1, (float)axis.Model.LayoutSlot.Right); } else { x2 = Math.Min(x2, (float)axis.Model.LayoutSlot.X); } y1 = Math.Max(y1, (float)axis.Model.LayoutSlot.Y); y2 = Math.Min(y2, (float)axis.Model.LayoutSlot.Bottom); } } RectangleF result = new RectangleF((float)area.View.Viewport.X + x1, (float)area.View.Viewport.Y + y1, x2 - x1 + 1, y2 - y1 + 1); return result; } protected override ActionResult OnMouseUp(MouseEventArgs e) { if (e.Button != MouseButtons.Left || this.MouseDownLocation == Point.Empty || this.MouseMoveLocation == Point.Empty) { return base.OnMouseUp(e); } if (MouseDownLocation != MouseMoveLocation) { this.MouseMoveLocation = ClipLocation(e.Location); CartesianArea area = this.Area.View.GetArea<CartesianArea>(); if (area != null) { this.selectedPoints.Clear(); RectangleF areaRect = RectangleF.Empty; IChartView chartView = this.Area.View; areaRect = GetCartesianClipRect(area); Point topLeft = new Point(Math.Min(MouseDownLocation.X, e.X), Math.Min(MouseDownLocation.Y, e.Y)); Point lowerRight = new Point(Math.Max(MouseDownLocation.X, e.X), Math.Max(MouseDownLocation.Y, e.Y)); RectangleF lassoRect = new RectangleF(new PointF(topLeft.X - (float)chartView.PlotOriginX - area.View.Margin.Left, topLeft.Y - (float)chartView.PlotOriginY - area.View.Margin.Top), new SizeF(lowerRight.X - topLeft.X, lowerRight.Y - topLeft.Y)); foreach (var series in area.View.Series) { foreach (var dataPoint in series.DataPoints) { if (lassoRect.Contains(new PointF((float)dataPoint.LayoutSlot.Location.X, (float)dataPoint.LayoutSlot.Location.Y))) { dataPoint.IsSelected = true; this.selectedPoints.Add(dataPoint); } else { dataPoint.IsSelected = false; } } } ChartDataPointsEventArgs changedArgs = new ChartDataPointsEventArgs(this.selectedPoints); this.OnLassoSelectedPointsChanged(changedArgs); } MouseDownLocation = MouseMoveLocation = Point.Empty; } this.Result.ShouldInvalidate = true; return this.Result; } }
To reproduce: WaterfallSeries series = new WaterfallSeries(); series.ShowLabels = true; series.DataPoints.Add(new WaterfallDataPoint(50000, false, false, "Beginning\nBalance")); series.DataPoints.Add(new WaterfallDataPoint(17000, false, false, "Jan")); series.DataPoints.Add(new WaterfallDataPoint(14000, false, false, "Feb")); series.DataPoints.Add(new WaterfallDataPoint(-12000, false, false, "Mar")); series.DataPoints.Add(new WaterfallDataPoint(69000, true, false, "Q1")); series.DataPoints.Add(new WaterfallDataPoint(-22000, false, false, "Apr")); series.DataPoints.Add(new WaterfallDataPoint(-18000, false, false, "May")); series.DataPoints.Add(new WaterfallDataPoint(500, false, false, "Jun")); series.DataPoints.Add(new WaterfallDataPoint(-30000, true, false, "Q2")); series.DataPoints.Add(new WaterfallDataPoint(39000, false, true, "Ending\nBalance")); this.radChartView1.Series.Add(series); CartesianGridLineAnnotation annotation1 = new CartesianGridLineAnnotation(); annotation1.Label = "Annotation"; annotation1.ForeColor = Color.Lime; annotation1.BackColor = Color.Black; this.radChartView1.Annotations.Add(annotation1); Workaround: public RadForm1() { InitializeComponent(); this.radChartView1.CreateRenderer += radChartView1_CreateRenderer; WaterfallSeries series = new WaterfallSeries(); series.ShowLabels = true; series.DataPoints.Add(new WaterfallDataPoint(50000, false, false, "Beginning\nBalance")); series.DataPoints.Add(new WaterfallDataPoint(17000, false, false, "Jan")); series.DataPoints.Add(new WaterfallDataPoint(14000, false, false, "Feb")); series.DataPoints.Add(new WaterfallDataPoint(-12000, false, false, "Mar")); series.DataPoints.Add(new WaterfallDataPoint(69000, true, false, "Q1")); series.DataPoints.Add(new WaterfallDataPoint(-22000, false, false, "Apr")); series.DataPoints.Add(new WaterfallDataPoint(-18000, false, false, "May")); series.DataPoints.Add(new WaterfallDataPoint(500, false, false, "Jun")); series.DataPoints.Add(new WaterfallDataPoint(-30000, true, false, "Q2")); series.DataPoints.Add(new WaterfallDataPoint(39000, false, true, "Ending\nBalance")); this.radChartView1.Series.Add(series); CartesianGridLineAnnotation annotation1 = new CartesianGridLineAnnotation(); annotation1.Label = "Annotation"; annotation1.ForeColor = Color.Lime; annotation1.BackColor = Color.Black; annotation1.PositonOffset = new SizeF(0, -20); annotation1.Axis = this.radChartView1.Axes[1] as CartesianAxis; annotation1.Value = 70000; annotation1.BorderColor = Color.Red; annotation1.BorderDashStyle = DashStyle.Solid; annotation1.BorderWidth = 1; this.radChartView1.Annotations.Add(annotation1); } private void radChartView1_CreateRenderer(object sender, ChartViewCreateRendererEventArgs e) { e.Renderer = new CustomCartesianRenderer(e.Area as CartesianArea); } public class CustomCartesianRenderer : CartesianRenderer { public CustomCartesianRenderer(CartesianArea area) : base(area) { } protected override void Initialize() { base.Initialize(); for (int i = 0; i <= this.DrawParts.Count - 1; i++) { CartesianGridLineAnnotationDrawPart annotationPart = this.DrawParts[i] as CartesianGridLineAnnotationDrawPart; if (annotationPart != null) { this.DrawParts[i] = new CustomCartesianGridLineAnnotationDrawPart((CartesianGridLineAnnotation)annotationPart.Element, this); } } } } public class CustomCartesianGridLineAnnotationDrawPart : CartesianGridLineAnnotationDrawPart { public CustomCartesianGridLineAnnotationDrawPart(CartesianGridLineAnnotation element, CartesianRenderer renderer) : base(element, renderer) { } public override void Draw() { FieldInfo fi = typeof(CartesianGridLineAnnotation).GetField("model", BindingFlags.NonPublic | BindingFlags.Instance); ChartAnnotationModel model = fi.GetValue(this.Element) as ChartAnnotationModel; RectangleF rect = ChartRenderer.ToRectangleF(model.LayoutSlot); rect.Offset(this.ViewportOffsetX, this.ViewportOffsetY); Graphics graphics = this.Renderer.Surface as Graphics; RadGdiGraphics radGraphics = new RadGdiGraphics(graphics); Rectangle clipRect = ChartRenderer.ToRectangle(this.Element.View.GetArea<CartesianArea>().AreaModel.PlotArea.LayoutSlot); clipRect.Offset((int)this.ViewportOffsetX, (int)this.ViewportOffsetY); graphics.SetClip(clipRect); GraphicsPath path = new GraphicsPath(); path.AddLine(rect.Location, new PointF(rect.Right, rect.Bottom)); BorderPrimitiveImpl border = new BorderPrimitiveImpl(this.Element, null); border.PaintBorder(radGraphics, null, path, rect); rect.Size = graphics.MeasureString(this.Element.Label, this.Element.Font); rect.Offset(this.Element.PositonOffset.Width + 1, this.Element.PositonOffset.Height + 1); TextParams tp = new TextParams(); tp.font = this.Element.Font; tp.foreColor = this.Element.ForeColor; tp.paintingRectangle = rect; tp.text = this.Element.Label; FillPrimitiveImpl fill = new FillPrimitiveImpl(this.Element, null); fill.PaintFill(radGraphics, null, rect); TextPrimitiveHtmlImpl text = new TextPrimitiveHtmlImpl(); text.PaintPrimitive(radGraphics, 0f, new SizeF(1f, 1f), tp); } }
To reproduce: private void RadChartView1_LabelFormatting(object sender, ChartViewLabelFormattingEventArgs e) { var dataPoint = e.LabelElement.DataPoint as CategoricalDataPoint; if (dataPoint != null) { e.LabelElement.IsVisible = false; } } Workaround: private void RadChartView1_LabelFormatting(object sender, ChartViewLabelFormattingEventArgs e) { var dataPoint = e.LabelElement.DataPoint as CategoricalDataPoint; if (dataPoint != null) { e.LabelElement.Text = ""; } }
How to reproduce: explicitly set the border color of the series Workaround: public partial class Form1 : Form { private RangeSelectorViewElement chartElement; public Form1() { InitializeComponent(); LineSeries lineSeries = new LineSeries(); lineSeries.Name = "Line"; lineSeries.BorderColor = Color.Green; lineSeries.DataPoints.Add(new CategoricalDataPoint(20, "Jan")); lineSeries.DataPoints.Add(new CategoricalDataPoint(22, "Apr")); lineSeries.DataPoints.Add(new CategoricalDataPoint(12, "Jul")); lineSeries.DataPoints.Add(new CategoricalDataPoint(19, "Oct")); this.radChartView1.Series.Add(lineSeries); this.chartElement = this.radRangeSelector1.RangeSelectorElement.AssociatedElement as RangeSelectorViewElement; this.chartElement.SeriesInitialized += ChartElement_SeriesInitialized; } private void ChartElement_SeriesInitialized(object sender, SeriesInitializedEventArgs e) { if (e.Series.Name == "Line") { e.Series.BorderColor = this.radChartView1.Series.Where(s => s.Name == "Line").First().BorderColor; } } }
Use LogarithmicAxis and set the Minimum to 0.1 Workaround is availble in the atched project.
How to reproduce: Dim table As New DataTable() table.Columns.Add("Name", GetType(String)) table.Columns.Add("X", GetType(Integer)) For i As Integer = 0 To 99 table.Rows.Add($"User {i}", i) Next Dim series As New BarSeries() series.DataSource = table series.CategoryMember = "Name" series.ValueMember = "X" RadChartView1.Series.Add(series) CType(RadChartView1.Area, CartesianArea).Orientation = Orientation.Horizontal Dim panZoomController As New ChartPanZoomController() panZoomController.PanZoomMode = ChartPanZoomMode.Vertical RadChartView1.Controllers.Add(panZoomController) RadChartView1.Zoom(1, 8) Workaround: create a custom ChartPanZoomController Dim panZoomController As New MyChartPanZoomController() panZoomController.PanZoomMode = ChartPanZoomMode.Vertical RadChartView1.Controllers.Add(panZoomController) Public Class MyChartPanZoomController Inherits ChartPanZoomController Protected Overrides Function OnMouseMove(e As MouseEventArgs) As ActionResult Dim cartesianArea As CartesianArea = TryCast(Me.Area, CartesianArea) If cartesianArea Is Nothing Then Return MyBase.OnMouseMove(e) End If Dim panPoint As Point? = Me.GetType().BaseType.GetField("panPoint", BindingFlags.Instance Or BindingFlags.NonPublic).GetValue(Me) If e.Button = MouseButtons.Left AndAlso panPoint.HasValue Then Dim offsetX As Double = Me.GetType().BaseType.GetField("offsetX", BindingFlags.Instance Or BindingFlags.NonPublic).GetValue(Me) Dim currentOffsetX As Double = offsetX Dim defaultAxis As Axis = If(cartesianArea.Orientation = Orientation.Horizontal, Me.Area.GetType().GetMethod("GetDefaultFirstAxis", BindingFlags.Instance Or BindingFlags.NonPublic).Invoke(Me.Area, New Object() {}), Me.Area.GetType().GetMethod("GetDefaultSecondAxis", BindingFlags.Instance Or BindingFlags.NonPublic).Invoke(Me.Area, New Object() {})) If Me.PanZoomMode = ChartPanZoomMode.Horizontal OrElse Me.PanZoomMode = ChartPanZoomMode.Both Then currentOffsetX += (panPoint.Value.X - e.Location.X) * -1 If currentOffsetX > 0 Then currentOffsetX = 0 End If If currentOffsetX < defaultAxis.Model.LayoutSlot.Width * (DirectCast(cartesianArea.View, IChartView).ZoomWidth - 1) Then currentOffsetX = -defaultAxis.Model.LayoutSlot.Width * (DirectCast(cartesianArea.View, IChartView).ZoomWidth - 1) End If End If Dim offsetY As Double = Me.GetType().BaseType.GetField("offsetY", BindingFlags.Instance Or BindingFlags.NonPublic).GetValue(Me) Dim currentOffsetY As Double = offsetY If Me.PanZoomMode = ChartPanZoomMode.Vertical OrElse Me.PanZoomMode = ChartPanZoomMode.Both Then currentOffsetY += (panPoint.Value.Y - e.Location.Y) * -1 If currentOffsetY > 0 Then currentOffsetY = 0 End If If currentOffsetY < -defaultAxis.Model.LayoutSlot.Height * (DirectCast(cartesianArea.View, IChartView).ZoomHeight - 1) Then currentOffsetY = -defaultAxis.Model.LayoutSlot.Height * (DirectCast(cartesianArea.View, IChartView).ZoomHeight - 1) End If End If cartesianArea.View.Pan(currentOffsetX, currentOffsetY) End If Return Controller.Empty End Function End Class
It would be great to be able to generate Box Plot graphics, both in the RadCharView control and in the Report.
The new controller should allow lasso selection of data points without zooming the view port.
When combining scalebreaks and series stacking, the rendered chart does not match the data. See attached solution: The bar of the category "1/2016" has a length of 105 (as indicated by the tooltips) -- but the corresponding axis labeling shows a value of 37. The scalebreak is off too (probably matching the wrong axis).
Note: when you apply a palette the series doesn't have hover and selection indication.
Steps to reproduce: 1. Add chart to a form. 2. Add two bar series with CombineMode set to Stack100 3. Add two data points to the series with values 0.0 and identical category 4. Run the project an you will see an exception.
ChartLegendElement is not able to wrap the LegendItems
Add RightToLeft support in DrillDownNavigator.
The Line Series cannot be clicked/selected and cannot be used in DrillDown scenarios.
Add Scatter area series support for RadChartView.
Currently only axes labels can be rotated. There should be a way to rotate series labels as well. Resolution: Set the LabelRotationAngle property of the series to preferred angle.
Introduce mechanism that will allow the vertical axis to appear at a certain position regardless of the size of its labels. For reference see Stock Series \ Indicators example.