Is there any way I can click on a MSChart and drag it left/right in order to scroll the chart to the right/left.
The idea is to drag the chart instead of dragging the chart's scroll bar.
The reason I'm not simply using the scroll bar is because I was asked to add this feature (bosses and their demands..).
Btw, I'm using C# and winforms.
Hope I was clear enough.
Thanks!!
Yes this is possible.
One issue is that you can't use the normal zooming/scrolling mechanism as this itself works by dragging over the chart.
Instead pass it by and code two mouseevents and use the Axis.PixelPositionToValue function to access the data values.
First we store the data value of the clicked location:
double mDown = double.NaN;
private void chart1_MouseDown(object sender, MouseEventArgs e)
{
Axis ax = chart1.ChartAreas[0].AxisX;
mDown = ax.PixelPositionToValue(e.Location.X);
}
Then we calculate the old range how far the Axis.Minimum has moved and adapt Minimum and Maximum:
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
if (!e.Button.HasFlag(MouseButtons.Left)) return;
Axis ax = chart1.ChartAreas[0].AxisX;
double range = ax.Maximum - ax.Minimum;
double xv = ax.PixelPositionToValue(e.Location.X);
ax.Minimum -= xv - mDown;
ax.Maximum = ax.Minimum + range;
}
You will want to add a few checks to prevent dragging too far left or right..
Note that the code assumes that you have set Minimum and Maximum!
Also note that you will want to take control over the format of the x-axis labels so they don't make the chart jump around by having lot of fractional digits.
Or, depending on your data, you may want to set the step to a multiple of some value, suitable for your interval or in my case simply to ints:
ax.Minimum -= (int)(xv - mDown) ;
If you need to have the scrollbars visible you can still use the above code; but you need to replace this
ax.Minimum -= (int)(xv - mDown);
ax.Maximum = ax.Minimum + range;
by this:
double oldPos = ax.ScaleView.Position;
ax.ScaleView.Position -= (xv - mDown);
However this does interfer with the normal scolling. You can insert checks to make sure you are not hitting the scrollbar like this:
RectangleF ipar = InnerPlotPositionClientRectangle(chart1, chart1.ChartAreas[0]);
if (ipar.Contains(e.Location))
{
..
..
..but it still acts up here. So either or seems to work best.
The InnerPlotPositionClientRectangle is defined here.
Related
I've started using LightningChart in my real time monitoring application. In my app there are many y axis which use segmented layout (one y axis per segment):
mainChart.ViewXY.AxisLayout.YAxesLayout = YAxesLayout.Segmented;
My goal is that when you mouse click a segment, it gets larger compared to other segments (kinda like zoom effect) and the other segments gets smaller. When you click it again it goes back to normal.
I know I can change the size of the segments with:
mainChart.ViewXY.AxisLayout.Segments[segmentNumber].Height = someValue;
That takes care of the zooming effect.
Now the problem is that how can I solve which segment was actually clicked? I figured out that you get mouse position via MouseClick -event (e.MousePos) but that seem to give only the screen coordinates so i'm not sure that it helps.
I'm using the LightningChart version 8.4.2
You are correct that getting mouse position via MouseClick event is the key here. The screen coordinates you get via e.GetPosition (not e.MousePos) can be converted to chart axis values with CoordToValue() -method. Then you just compare the y-coordinate to each y-axis minimum/maximum value to find out what segment was clicked. Here is an example:
_chart.MouseClick += _chart_MouseClick;
private void _chart_MouseClick(object sender, MouseButtonEventArgs e)
{
var mousePos = e.GetPosition(_chart).Y;
double axisPos = 0;
bool isWithinYRange = false;
foreach (AxisY ay in _chart.ViewXY.YAxes)
{
ay.CoordToValue((float)mousePos, out axisPos, true);
if (axisPos >= ay.Minimum && axisPos <= ay.Maximum)
{
// Segment clicked, get the index via ay.SegmentIndex;
isWithinYRange = true;
}
}
if (!isWithinYRange)
{
// Not in any segment
}
}
After finding out the segment index, you can modify its height as you described:
_chart.ViewXY.AxisLayout.Segments[0].Height = 1.5;
Note Height means segment height compared to other segments.
Hope this is helpful.
How do you create an Annotation on-the-run and how do you enable end-user placement with Annotation.BeginPlacement()? I've tried to do this in multiple ways, but cannot get it working. It should render itself in real-time after the BeginPlacement() has been called.
Documentations on this subject is little to none - and mostly none - so I'm not able to find any help for this problem.
What I've tried so far, is to create an annotation and place it with AnchorX/Y, set all Allow- flags to true and called BeginPlacement() while mouse is moving, but cannot see the annotation while placing it nor will it go in it's place accordingly. For example, LineAnnotation starts in right position, but doesn't end where I left it. When I move it so it starts from my ChartAreas {0,0}, it will hit the end-point.
What I want to know, is when and how to use these tools available? What I am trying to do, is to let the user draw annotations on a chart and use as tools when analyzing the charts.
You need to calculate the right positions. Remember that the MouseMove will not give you positions (percentages) or values(data) but pixels. You can transform them using the various axis functions. Officially they only work in a xxxPaint event, but during mouse events they also work fine.
Update: There two ways to do the anchoring:
Either by using the 'Positions', i.e. the percentages or the 'Values', i.e. the data values.
Here is an example of the 1st kind:
LineAnnotation laNew = null;
private void chart1_MouseDown(object sender, MouseEventArgs e)
{
if (cbx_drawAnnotation.Checked)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
laNew = new LineAnnotation();
chart1.Annotations.Add(laNew);
double vx = ax.ValueToPosition(ax.PixelPositionToValue(e.X));
double vy = ay.ValueToPosition(ay.PixelPositionToValue(e.Y));
laNew.X = vx;
laNew.Y = vy;
}
}
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left) && cbx_drawAnnotation.Checked)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
double vx = ax.ValueToPosition(ax.PixelPositionToValue(e.X))- laNew.X;
double vy = ay.ValueToPosition(ay.PixelPositionToValue(e.Y)) - laNew.Y;
laNew.Width = Math.Min(100, vx);
laNew.Height = Math.Min(100, vy);
laNew.LineColor = rb_green.Checked ? Color.Green : Color.Red;
laNew.AllowMoving = true; // optional
}
}
This works fine unles you need to rescale the axis in some way, like changing the axis minimum and/or maximum values.
In the case you need to anchor to data values.
First we need to relate the Annotation to the Axes and also set IsSizeAlwaysRelative to false. Then we can calculate the anchor and size values:
private void chart1_MouseDown(object sender, MouseEventArgs e)
{
if (cbx_drawAnnotation.Checked)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
laNew = new LineAnnotation();
chart1.Annotations.Add(laNew);
laNew.IsSizeAlwaysRelative = false;
laNew.AxisX = ax;
laNew.AxisY = ay;
laNew.AnchorX = ax.PixelPositionToValue(e.X);
laNew.AnchorY = ay.PixelPositionToValue(e.Y);
laNew.LineColor = rb_green.Checked ? Color.Green : Color.Red;
laNew.AllowMoving = true;
}
}
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left) && cbx_drawAnnotation.Checked)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
laNew.Width = ax.PixelPositionToValue(e.X) - laNew.AnchorX; // values
laNew.Height = ay.PixelPositionToValue(e.Y) - laNew.AnchorY;
}
}
Note how I now can scale the maximum and also still resize the the chart and the annotations stay with the data points..:
Update: To restrict the line to the ChartArea add this to the definition in the MouseDown event:
laNew.ClipToChartArea = chart1.ChartAreas[0].Name;
To prevent an exception from leaving the Chart, add this to the condition in the MouseMove..:
.. && chart1.ClientRectangle.Contains(e.Location)
I am using method SetView to zoom for two points in map but when they are too close I am too zoomed. So I hope I can check something like this:
myMap.SetView(bounds);
if (myMap.ZoomLevel > Constants.DefaultZoomLevel)
myMap.ZoomLevel = Constants.DefaultZoomLevel;
But method SetView isn't immediately set ZoomLevel property. What can I do to fix it? How can I set some zoom level border? Thanks
Edit:
I found that in 8.0 sdk there is ZoomLevelChanged event? This could be useful for me. So is there possible how to get it worked in 7.1?
I tried several solutions and these are my results (I needed working for WP7.1, with just targeting for WP8 there could be easier solution):
Use of TargetZoomLevel
Like this:
myMap.ZoomLevel = Math.Min(myMap.TargetZoomLevel, maxZoomLevel);
Doesn't solve my problem because SetView set ZoomLevel after this code execute. Map was just blinking between ZoomLevel from SetView and maxZoomLevel (I have timer for checking positions so that is why it was blinking and isn't good for me).
Use of MapZoom event
Like this:
private void map1_MapZoom(object sender, Microsoft.Phone.Controls.Maps.MapZoomEventArgs e)
{
if (((Map)sender).ZoomLevel > maxZoomLevel)
{
e.Handled = true;
}
}
Problem: MapZoom event handler is not fired when SetView it's used.
Check distance and set ZoomLevel if distance is little:
public static double GetDistanceBetweenPoints(LatLon firstPoint, LatLon secondPoint)
{
var sCoord = new GeoCoordinate(firstPoint.Lat, firstPoint.Lon);
var eCoord = new GeoCoordinate(secondPoint.Lat, secondPoint.Lon);
return sCoord.GetDistanceTo(eCoord);
}
usage:
var distance = LocationHelper.GetDistanceBetweenPoints(carPosition, userPosition);
if (distance < MinMetersDistance)
myMap.ZoomLevel = maxZoomLevel;
Problem: Again blinking. Sometimes I set ZoomLevel by this code. Other time it was set by SetView.
My real solution:
I just check distance between two points like in previous dot. But this time if the distance was little I just create new points from the old ones and set them in new positions (latitude and longitude) which was in min distance (one on the left side I moved more to left and second on the right side I moved more to right). Then I used SetView on new points.
I'm using the C# built-in Winforms Chart control (System.Windows.Forms.DataVisualization.Charting.Chart) with its built-in ability to let the user select a range. What I'd like to do is to read back what range the user has selected. Surely there must be some easy way to do this, but I haven't been able to find it.
The cursor is enabled like so:
var ca = chart1.ChartAreas["ChartArea1"].CursorX;
ca.CursorX.IsUserEnabled = true;
ca.CursorX.IsUserSelectionEnabled = true;
I am aware that I can make the chart zoom when the user selects a range by enabling ca.AxisX.ScaleView.Zoomable, but I don't want the picture to change: instead I am using the chart as a way to display information and let the user select a range of X values for which I then do some extra processing.
I tried hooking to chart1.SelectionRangeChanged and that indeed fires every time the range is changed - I just can't seem to get the selection range from the CursorEventArg I get back. It has "NewSelectionStart" and "NewSelectionEnd" fields, but those are NaN, disappointingly. I tried looking at the various properties of the chart and the axes, but didn't find anything that sounded promising.
Further investigation reveals the ChartArea.CursorX.SelectionStart property which sounds like exactly what I need... except that it's NaN too. I don't know whether this is normal or I'm hitting some sort of bug?
So, how can I figure out what range the user selected?
OK, well, I figured it out. Here's the scoop:
There's a SelectionRangeChang*ing* event, and when that one runs the ChartArea.CursorX.SelectionStart and ChartArea.CursorX.SelectionEnd fields have correct values in them. But the user hasn't released the mouse button yet, so you should just store them.
When the user releases the mouse button, the SelectionRangeChang*ed* event fires. Somehow it's designed in such a way that SelectionStart and SelectionEnd are reset to NaN (just like the NewSelectionStart and NewSelectionEnd fields in the event parameters). What you have to do is to use the values that you squirreled away from the other event handler now that you know the time is right to use them.
So there you have it! Hopefully this answer will save someone else from wasting time.
In addition to redtuna to set cursors in a c# chart:
It worked for me to use "SelectionRangeChanging" instead of "SelectionRangeChanged" to not get the NaN issue:
When initializing the Form
this.chart1.SelectionRangeChanging += chart1_SelectionRangeChanging;
and
chart1.ChartAreas[0].CursorX.IsUserEnabled = false; // red cursor at SelectionEnd
chart1.ChartAreas[0].CursorX.IsUserSelectionEnabled = true;
chart1.ChartAreas[0].AxisX.ScaleView.Zoomable = false; // zoom into SelectedRange
chart1.ChartAreas[0].AxisX.ScrollBar.IsPositionedInside = true;
chart1.ChartAreas[0].CursorX.Interval = 0.01; // set "resolution" of CursorX
What is executed if the range is chosen / the cursors are set
private void chart1_SelectionRangeChanging(object sender, CursorEventArgs e)
{
double x1 = x1 = e.NewSelectionStart; // or: chart1.ChartAreas[0].CursorX.SelectionStart;
double x2 = e.NewSelectionEnd; // or: x2 = chart1.ChartAreas[0].CursorX.SelectionEnd;
double diffx1x2 = x2 - x1;
}
To zoom in & out (x-axis) I just added a button that takes the cursor values. That way zooming by mouseClick (ScaleView.Zoomable = false;) does not interfere my cursor positioning :)
private void button_ZoomIn(object sender, EventArgs e)
{
double x1 = chart1.ChartAreas[0].CursorX.SelectionStart; // x1 = X1
double x2 = chart1.ChartAreas[0].CursorX.SelectionEnd; // x2 = X2
if (x2 > x1)
{
// hard setting: chart1.ChartAreas[0].AxisX.Minimum = x1;
// hard setting: chart1.ChartAreas[0].AxisX.Maximum = x2;
chart1.ChartAreas[0].AxisX.ScaleView.Zoom(x1,x2); // dynamic approach with scrollbar
}
else
{
chart1.ChartAreas[0].AxisX.ScaleView.Zoom(x2,x1);
}
}
Zoom out
private void button_ZoomOut(object sender, EventArgs e)
{
chart1.ChartAreas[0].AxisX.ScaleView.ZoomReset(0);
}
Zooming can also be implemented by mouseWheel: how to enable zooming in Microsoft chart control by using Mouse wheel
And if you also want right-click action in the chart: How to get a right click mouse event? Changing EventArgs to MouseEventArgs causes an error in Form1Designer?
I have added the scroll bar to the x-axis of my mschart control using this link Adding a scroll bar to MS Chart control C# and it worked as expected. But now my requirement is, I need zooming for both the axis. But since I removed Zoom reset button for x-axis, I have used the following to reset it by forcing.
private void chart1_AxisScrollBarClicked(object sender, ScrollBarEventArgs e)
{
// Handle zoom reset button
if(e.ButtonType == ScrollBarButtonType.ZoomReset)
{
// Event is handled, no more processing required
e.IsHandled = true;
// Reset zoom on X and Y axis
chart1.ChartAreas[0].AxisX.ScaleView.ZoomReset();
chart1.ChartAreas[0].AxisY.ScaleView.ZoomReset();
}
}
But it is not working properly. Please help me in fixing this in c#..
Try using ZoomReset(0).
private void zeroZoom_Click(object sender, EventArgs e)
{
chart1.ChartAreas[0].AxisX.ScaleView.ZoomReset(0);
chart1.ChartAreas[0].AxisY.ScaleView.ZoomReset(0);
}
The first thing coming in mind, is that your problem is related to multiple zooming.
As you had noticed, by default the zoom-reset button (exactly like the ZoomReset method) doesn't reset the zoom completely, but restore the previous view-status, i.e. if you have zoomed more than one time, it returns just to the previous zoomed view.
To completely reset the zoom, you can use this code:
while (chart1.ChartAreas[0].AxisX.ScaleView.IsZoomed)
chart1.ChartAreas[0].AxisX.ScaleView.ZoomReset();
while (chart1.ChartAreas[0].AxisY.ScaleView.IsZoomed)
chart1.ChartAreas[0].AxisY.ScaleView.ZoomReset();
Conversely, if you like the default zoom-reset behaviour, you should have two buttons for the two axis because it's possible to have different number of view-statex for the different axis.
Another possibility, is that you are zooming a secondary axis, like AxisX2 or AxisY2 (not sure, but I think that is depending on the chart type), so you should reset those (or, to be safe, just reset all axis...).
I tried with the below code today and it seems like working fine. Here the for loop handles the X axis with scroll and the next if block handles the ordinary X-axis. Could you please have a glance at it and let me know your views about it?
private void chart1_AxisScrollBarClicked(object sender, ScrollBarEventArgs e)
{
Boolean blnIsXaxisReset = false;
try
{
// Handle zoom reset button
if(e.ButtonType == ScrollBarButtonType.ZoomReset)
{
// Event is handled, no more processing required
e.IsHandled = true;
// Reset zoom on Y axis
while (chart1.ChartAreas[0].AxisY.ScaleView.IsZoomed)
chart1.ChartAreas[0].AxisY.ScaleView.ZoomReset();
//Handles Zoom reset on X axis with scroll bar
foreach (Series series in chart1.Series)
{
if (series.YAxisType == AxisType.Secondary)
{
chart1.ChartAreas[0].AxisX.ScaleView.Zoom(-10, 10);
blnIsXaxisReset = true;
break;
}
}
//Handles Zoom reset on ordinary X axis
if (blnIsXaxisReset == false)
{
while (chart1.ChartAreas[0].AxisX.ScaleView.IsZoomed)
chart1.ChartAreas[0].AxisX.ScaleView.ZoomReset();
}
}
}
catch (Exception ex)
{
BuildException buildException = new BuildException();
buildException.SystemException = ex;
buildException.CustomMessage = "Error in zooming the Chart";
ExceptionHandler.HandleException(buildException);
}
}
Thanks for your effort!!