I'm trying to implement a utility for showing throughput over time in a system, and am using Oxyplot to visualise the data.
Currently, zoom and pan are working as expected, but I would like some visual indication to the user which clearly shows whether the graph can be zoomed or panned.
After ditching the idea of using a scroll bar (being neither able to accurately get the position of the visible section of the graph, nor correctly position the thumb of the scroll bar releative to the chart), I have settled on using icons to show whether there is any data on the chart which is hidden to the left or rightmost side.
I would like these icons to work as buttons which allow the user to page left and right on the graph, however as with all things OxyPlot related, the implementation is far more complex than it first seems.
I'm using the WPF implementation, which uses a ViewModel representing the overall data set, with each series item represented by its own model.
This effectively renders almost every tutorial useless as the WPF implementation is significantly different to the basic OxyPlot package.
Currently, the code behind in the view handles the click on the page left/right buttons. I cannot put this in my ViewModel as it must interract directly with the PlotControl object.
private void btnPageRight_Click(object sender, RoutedEventArgs e) {
CategoryAxis axis = (CategoryAxis)PlotControl.ActualModel.Axes[0];
double xAxisMin = axis.ActualMinimum;
double xAxisMax = axis.ActualMaximum;
double visibleSpan = xAxisMax - xAxisMin;
double newMinOffset = xAxisMax + visibleSpan;
PlotControl.Axes[0].Minimum = newMinOffset;
PlotControl.Axes[0].Maximum = newMinOffset + visibleSpan;
PlotControl.ActualModel.InvalidatePlot(true);
}
As it stands, the above code throws no errors, but it does not work either.
If anybody can advise a possible way to make OxyPlot scroll to a given position using just code behind, I would be grateful.
As a last resort, I have pondered trying to simulate a mouse drag event to make this finicky beast behave.
I find the need to work around the problem in that way quite offensive, but desparation leads to odd solutions...
In case anybody else runs into this issue, the following snippet will scroll the graph in pages based on the number of columns visible on the graph at the time.
The snippet takes the number of visible columns as the viewport, and will move the visible area by the viewport size.
Although this applies to the WPF implementation, the only way I could find to make this work was to run this method from the code behind in the View containing the OxyPlot chart.
This should work correctly regardless of the zoom amount at the time.
The CategoryAxis reference must be obtained from the ActualModel as the WPF.Axis does not provide the ActualMinumum and ActualMaximum needed to calculate the viewable area.
The visibleSpan in this case represents the number of columns, with panStep denoting the amount to pan by in pixels.
private void ScrollInPages() {
//To zoom on the X axis.
CategoryAxis axis = (CategoryAxis)PlotControl.ActualModel.Axes[0];
double visibleSpan = axis.ActualMaximum - axis.ActualMinimum;
double panStep = 0;
//Scrolling the chart - comment out as appropriate
//Scroll right one page
panStep = axis.Transform(0 - (axis.Offset + visibleSpan));
//Scroll left one page
panStep = axis.Transform(axis.Offset + visibleSpan);
axis.Pan(panStep);
PlotControl.InvalidateFlag++;
}
Related
I have a fragment for more options in my app that I want mostly hidden except for one small rectangle that says "Pull up for more options". This rectangle is parked at the bottom of the screen. When the user pulls it up, it then pops up from the bottom but NOT taking over the full screen, only about 1/3rd of the bottom. Just enough to show some of the options (checkboxes).
I am doing this and have it working by setting the TranslationY setting of the options fragment layout, which is in a FrameLayout container by the way, so that it is at the bottom of the screen and just shows my "Pull up for more options" text.
Then, when they pull up on that, I have some motion events to bring it up to where I want.
Here is the issue, I can get it working just fine on one display using hard coded TranslationY settings. For example, on a Galaxy S2 which has a density of 1.5 (HDPI 240) and a screen of 480x800, these are my hard coded values that work. I had to find them just by playing around with the numbers.
int trackOptionsHome = 650; //Parked at bottom of screen.
int trackOptionsExtended = 450; //Extended out where I want it to.
Again, with those hard coded values on the S2, it works fine and the way I want. However, if I now try a different device that is STILL HDPI (1.5/240) except the screen size is 480x640 (3.5in), it does not display properly which is to be expected. So then I implemented something like this:
float trackOptionsHome = ((dMetrics.HeightPixels / dMetrics.Density) + 120);
float trackOptionsExtended = ((dMetrics.HeightPixels / dMetrics.Density) - 100);
This was to try to take into account for different display density and sizes. I was then trying to do math at the end of each to position my fragment where I want it. However, I am getting inconsistent results and the numbers are still arbitrary. I have to find them by playing around.
This raises two questions:
1. How do I make sure I get the results I want on DIFFERENT display densities, which is not even the issue I am having at the moment since I have the same density.
2. How do I scale properly for the different size screens, which appears to be my immediate problem.
For math purposes at the moment (I can adjust as needed after I get an answer) let's say I want 50px of the fragment showing from the bottom when it is in the home position and 300px showing up from the bottom when extended.
What is the correct way to do this?
Thanks!
Mike
I have a line chart, which, after enough data points have been plotted to it, the data will exceed what is visible on screen (so that the chart is only showing the most recent data). When this occurs, I would like a scroll bar to be filled for the X axis, allowing the user to use the scroll bar to view such previous data.
How do I go about doing this? I don't want the user to be able to drag or zoom on the chart itself, just to solely use the scroll bar to navigate along the chart.
I've looked at this article: https://msdn.microsoft.com/en-us/library/dd456730.aspx but it doesn't help & the scrollbars do not appear.
Without seeing the relevant parts of your code it is hard to pin down your problems.
Here is one strange statement:
after enough data points have been plotted to it, the data will exceed
what is visible on screen (so that the chart is only showing the most recent data).
Now this can only happen after you have set the AxisX.Maximum because by default the chart control will squeeze the area more and more while you add points.
But when you have set a maximum of what can be shown, no scrollbar can work or even been shown. Sounds logical, right?
So either don't set it in the first place or clear it when the number of points exceeds what you want to show. To clear it use NaN :
chart1.ChartAreas[0].AxisX.Maximum = Double.NaN;
Or, of course, set it to the last point you want to be shown!
After looking at what you mustn't do let's see what you need to do to show the scrollbar:
First you enable it:
chart1.ChartAreas[0].AxisX.ScrollBar.Enabled = true;
Next you tell it to show only the scrolling handle and not the zoom-reset button:
chart1.ChartAreas[0].AxisX.ScrollBar.ButtonStyle = ScrollBarButtonStyles.SmallScroll;
See MSDN on ScrollBarButtonStyles for the various things the scrollbar can show/do!
And to make sure the user can't zoom set this:
chart1.ChartAreas[0].AxisX.ScaleView.Zoomable = false;
And finally set the current range to show:
chart1.ChartAreas[0].AxisX.ScaleView.Size = 111; // show 111 DataPoints
Now the scrollbar should show.
It is a good idea to study the AxisScaleView class as it has a couple of helpful properties..
Depending on the data type of your X-Values you may also need to set the ScaleView.MinSizeType to whatever suits your data:
chart1.ChartAreas[0].AxisX.ScaleView.MinSizeType = DateTimeIntervalType.Number;
I've been working on a proper slider for my C# WPF project.
I wanted to create a slider, with a background that indicates different parts of the process, by adding a different color to each section on the slider. Furthermore I wanted to add small indicators (like the default ticks, but custom shape and irregular position) to the background.
I achived this by creating a drawing brush and adding correspondingly colored rectangles. This seemed to work fine, but a small distortion was still present, so I investigated further and realized the following:
With slider.ActualWidth I get the width of the whole widget. So in order to create a background covering the actual "slider" part, I'll have to be aware of the distance from the widget to the actual slider. (See image)
I measured the distance in a very small window, in fullscreen and stretched on two screens. It seems this distance is always 5 pixels. I tried google and looked through the info WPF provides on its pages, but either I read over it, or there is no information on this.
Can I be sure this distance is always 5 pixels ? In there any place such information is kept ? Is there maybe another way, to determine the size of the slider itself?
Assuming you haven't tinkered with the Slider template you can just walk down the visual tree and check the ActualWidth of the track:
Border b = VisualTreeHelper.GetChild(slider, 0) as Border;
Grid g = VisualTreeHelper.GetChild(b, 0) as Grid;
Border track = VisualTreeHelper.GetChild(g, 2) as Border;
Console.WriteLine("Track ActualWidth: " + track.ActualWidth);
My program is basically about analyzing videos.
A major part is to plot a diagram showing (f.e.) brightness per frame on y-axis and every frame number on x-axis. Because the program is written in C# and uses WPF, D³ was the way to go for plotting.
Now the user might see a peak signal in the diagram and wants to look on that single frame to understand why it's so bright (it might be just natural, or an encoding-artifact).
There comes my question: The most intuitive way for the user to click on the diagram where the peak is, which jumps the video preview (other GUI element) right to that frame. So I need the x-coordinate (=frame number) of the user click on the diagram.
It is possible to manually analyze the mouse-input event, but that would take much work (because the x-axis is different for each video and the entire diagram can be resized, so absolute coordinates are a no go).
But maybe something similar is already implemented by D³. I searched the documentary, but didn't find anything useful. The only piece of information was using a "DraggablePoint", but that's where the trail goes cold.
Does someone of you know how to get the x-coordinate without much work?
It sure is possible! The way that I have done it in the past is to add a CursorCoordinateGraph object to my plotters children, and it automatically tracks the mouse position on the graph in relation to the data. You can turn off the visual features of the CursorCoordinateGraph and use it for tracking only. Here's what it would look like:
CursorCoordinateGraph mouseTrack;
plotter.Children.Add(mouseTrack);
mouseTrack.ShowHorizontalLine = false;
mouseTrack.ShowVerticalLine = false;
And your mouse click event would look like this:
private void plotter_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point mousePos = mouseTrack.Position;
var transform = plotter.Viewport.Transform;
Point mousePosInData = mousePos.ScreenToData(transform);
double xValue = mousePosInData.X;
}
You can then use xValue and manipulate it however you would like to achieve your desired effect.
Original Question
I'm using Microsoft Chart Control to display some data points as a Line. I have a Legend with custom items used to display calculated information about the line (mean average and others).
Now I've enabled IsUserSelectionEnabled which allows the user to "zoom into" a range of values and I want the legend items to be calculated on only the data points that are currently in view.
I can use the AxisViewChanged event to be notified of the view change, but what I can't figure out is how to enumerate only those DataPoint currently in view.
Update
The zoom isn't going to work for my purpose. What I discovered is that the NewPosition and NewSize properties of the AxisViewChanged event do in fact contain the precise area selected by the user, but the resulting zoom contains points outside of that area. I need more precision than that. What I need are two cursors, but the control only gives you one.
So my question now is: How do I customize this thing to add another cursor? I'm not asking just yet and if I do I'll start a new question.
Though I do still need to figure out how to translate client coords into data coords...
Updated again
I found the coord translation functions right on the Axis. Seems obvious in retrospect.
ChartArea.Axis.PixelPositionToValue (for whichever axis you need)
ChartArea.Axis.ValueToPixelPosition