Drawing a chart in WPF C# design questions - c#

I had a project a month ago where I drew a stock chart in an application using Windows Forms. I did this by creating a bitmap that would stretch to the dimensions of the window. This would allow my chart to resize with the window.
I am now expanding the project using WPF. I have been trying to work on my design for the project, but I cant seem to get any idea on the best way to do the same chart. I have looked at canvases, grids, and a few other controls. I thought I was on the right track with the canvas, but when I would resize the window, my drawing would stay in the same spot. I guess the point of my post tonight is to get some ideas to help me brainstorm a design for my project.
All advice and questions are appreciated.
Thanks,
Joseph

(Realizing this addresses at best a subset of this fairly old question, since it's only one chart type...)
Just fwiw, creating a bar graph in a Grid as Ed suggests is pretty straightforward. Here's a quick and dirty version:
Add a Grid to your Window's XAML. Just for testing, here's one that fills the Window entirely.
<Grid>
<Grid
Name="myGrid"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Width="auto"
Height="auto"
Margin="10,10,10,10"
/>
</Grid>
Now insert these two utility functions somewhere in your project. These provide simple, single-color columns and unstyled, but centered, label text for the x-axis.
I think the only nasty kludge is the maxHeight in the _placeSingleColorColumn call.
Worth mentioning: I don't have labels for the y-axis in this quick & dirty version.
private void _placeSingleColorColumn(Grid grid, Color color, int height, int colNum, int maxHeight)
{
Brush brush = new SolidColorBrush(color);
Rectangle rect = new Rectangle();
rect.Fill = brush;
Grid.SetColumn(rect, colNum);
Grid.SetRow(rect, maxHeight - height);
Grid.SetRowSpan(rect, height);
grid.Children.Add(rect);
}
private void _createLabels(Grid grid, string[] labels)
{
RowDefinition rowDefnLabels = new RowDefinition();
grid.RowDefinitions.Add(rowDefnLabels);
for (int i = 0; i < labels.Length; i++)
{
TextBlock block = new TextBlock();
block.Text = labels[i];
block.HorizontalAlignment = System.Windows.HorizontalAlignment.Center;
Grid.SetColumn(block, i);
Grid.SetRow(block, grid.RowDefinitions.Count);
grid.Children.Add(block);
}
}
That's really it. Here's some insanely quickly and dirty example code to create a 10 by 10 grid with some sample data.
public void createGrid10x10()
{
Random random = new Random();
for (int i=0; i<10; i++)
{
ColumnDefinition colDef = new ColumnDefinition();
myGrid.ColumnDefinitions.Add(colDef);
RowDefinition rowDef = new RowDefinition();
myGrid.RowDefinitions.Add(rowDef);
Color color = i % 2 == 0 ? Colors.Red : Colors.Blue;
_placeSingleColorColumn(this.myGrid, color, random.Next(1,11), i, 10);
}
string[] aLabels = "Dogs,Cats,Birds,Snakes,Rabbits,Hamsters,Horses,Rats,Bats,Unicorns".Split(',');
_createLabels(this.myGrid, aLabels);
}
Add one line to your MainWindow constructor, and you're done, afaict.
public MainWindow()
{
InitializeComponent();
this.createGrid10x10();
}
Now you've got a bar graph that'll resize and stay proportional as the window is resized, etc.
Adding more labels (bar values on top, y-axis labels, etc) should be pretty straightforward if you understand the above. Just throw in another column and/or row, create your TextBlocks, and place them in the right locations.

Try using a DockPanel and set LastChildFill to true. Then make your control the last child of the DockPanel.

Funny: I am just making the same thing!
I already developed a chart control, plenty of features. Now I need to renew and extend it with some other function. However, my deal is manage even 100k of points on a single chart, but keeping a good performance on a normal pc. By resizing the window will scale the chart, but not the text eventually placed on it. Also consider that I need a real-time rendering of the data incoming at least 0.5 sec.
All that has been resolved using the old-style bitmap creation, then placing it as a normal image on any wpf control. There are several limitation because there's no "living" objects as in the wpf you have, but the rendering performance is really huge compared to the wpf primitives.
So, unless you have charts with max 100 points to manage, I strongly recommend the hybrid approach. Anyway, it's a very hard task!
Cheers

I am assuming you want to draw your own chart rather than using WPF charts.
Canvas is not usually a good thing to use in WPF because it fixes objects at a specific location and size, exactly as you've seen (though I suppose you could use a Canvas with a ScaleTransform). Grid will take the size of its container, so putting a Grid into a window will make the Grid resize with the window (unless you specify a fixed Width and Height for the Grid). StackPanel will stack things and will attempt to take the minimum size of its content, so that's probably not what you want to use here.
Creating a chart layout inside a panel like a Grid isn't completely simple. If you are doing a bar chart, you could create a Column in the Grid for each bar, assign a percentage width such as star; and the columns would get larger as your Grid expanded with the window. You can use a similar trick by making each Bar a Grid, setting two columns in the Grid, and setting a third level of Grid inside the lowest column, then using percentages for the column heights (e.g., 90star and 10star for 90%, 10% heights). The bars would then grow taller as the window grows taller. You could reserve a Grid row below the bars for labels, and center them under the bars.
Line charts are trickier. You would probably want to create a GeometryDrawing of line segments, and then use a ScaleTransform bound to the window size to make it shrink and grow.
There are a lot of possibilities with WPF, but you'll need to do a bit of leaning and studying first. A book such as Adam Nathan's "Windows Presentation Foundation Unleashed" would quickly give you a lot of knowledge of WPF layout and how to proceed.
Edit: You could also use an empty panel and use its DrawingContext to draw lines, rectangles, text, ellipses, etc. at points you calculated from the current window size.

Related

Dynamical rectangle plot c# wpf

I want to create a plot that dynamically displays active elements as rectangles. I have achieved a first version that is actually ok using OxyPlot.Annotations.RectangleAnnotation which I add to myPlotModel.Annotations, you can see it in the image hereafter:
Example of wanted display
The thing is that after a while, the amount of drawn rectangles make the update not smooth as I update the shown timewindow (which is set to 15 seconds). I have already set a maximum of drawn elements that suffice to cover the displayed window (i.e. the rectangles get removed as they are too far in the past), but the rendering is still jerky. I draw the rectangles by allocating them to an equal fraction of the Y-axis, that is the third one from the top gets:
rowNumber= 3.0
minimumY = maximalY - maximalY / totalElements * rowNumber
maximumY = maximalY - maximalY / totalElements * (rowNumber + 1.0)
And the Y-axis is hidden.
My question:
Is there a smarter way of creating such a display that would be less computationally heavy, and therefore allow a smoother update? I do not have to stick to OxyPlot, it is simply the easiest way that I found to obtain what I wanted.
Thanks for your answers!
Technically, the answer to your question is "Yes".
There are a number of ways to do this.
You could have a vertical itemscontrol that had an itemscontrol in it's template. That could have a canvas as it's itemspresenter and you could bind canvas.top and canvas.left to properties in it's content. Template each into a rectangle and bind height and width.
And of course do something about the scale on the bottom and the column of activity labels or whatever you want to call them there.
Unless you're using an absolutely ancient machine, that'd just fly.
It's quite a lot of work but it would probably be quicker to write that than to search through a load of alternative packages and decide which was optimal.

How to determine the proper maximum value for scrollbar c# .NET (how does windows do it?)

Before I go into my question, let me explain my setup:
First: I have a PictureBox that holds a Bitmap which is generated at runtime. This Bitmap can be different widths but always the same height.
Second: PictureBoxes do not support scrolling, therefore, I have the PictureBox docked in a panel. Initially, I had used the panel's autoscroll feature, but abandoned that after I discovered through this article that PictureBoxes have a size limit. I also learned that it's better to instead have small PictureBoxes and only draw what needs to be seen instead of the whole image.
Third: So I added a HScrollBar, which is fine and dandy, but I can't seem to figure out the math behind how big to make the scroller. I tried setting the maximum of the scrollbar to the length of the bitmap, but as you can see the size of the scroller is much smaller in mine than the one Windows puts in if I use the autoscroll feature.
My question is, what is the math behind the scroller size and how do I emulate that in my custom scrollbar?
Let me know if my question is unclear and I will try my best to make it more understandable. And thanks in advance for your help!
I figured out what was the problem. Perhaps I should have tried a little longer. :)
The answer lies in the LargeChange property. I let the Maximum at the total width of the bitmap and then set the LargeChange to the width of what I wanted to show. (i.e. the width of the PictureBox)
The size of the "scroller" is determined by the ratio of the value of LargeChange to the value of Maximum. For example, if the width to show (LargeChange) is 100 and the total width (Maximum) is 300 then the "scroller" size will be 1/3 of the scrollbar length. (100/300).
I got same problem too and tried to figure it out, I have a panel which contain another panel inside it called panelChild, and the default scrollbar is small, I need lager scrollbar, so I use HScrollBar to do that (display over-top of default scrollbar), I post my solution here, may be it helpful to someone
public Form() {
InitializeComponent();
hScrollBar.Maximum = panelChild.Width;
hScrollBar.LargeChange = panel.Width; // panel which contain panelChild, and this hScrollBar will same as panel scrollbar
hScrollBar.Scroll += HScrollBar_Scroll;
}
private void HScrollBar_Scroll(object sender, ScrollEventArgs e)
{
int diference = e.OldValue - e.NewValue;
foreach (Control c in panel.Controls)
{
c.Location = new Point(c.Location.X + diference, c.Location.Y);
}
}

ChartFX export chart cuts the borders

I have a problem with exporting a chart. I use the ChartFX chart with
chart.ExportImageSize = new Size(600, 450);
and if the size of the image is larger than this (1127, 537), it cuts the right and the bottom border in the exported image.
For exporting, I use simple
chart.Export(FileFormat.Bitmap);
No custom controls are used in exporting the chart, and the chart looks normal in the application (borders all around, and I use simple black border).
Few interesting things I realized trying to solve this.
First I have no border
chart.Border = new SimpleBorder(SimpleBorderType.None, cOffice2007BackColor);
Than, I add the new border object just to export the chart with the border.
chart.Border = new SimpleBorder(SimpleBorderType.Color, Color.Black);
chart.Export(FileFormat.Bitmap);
Than I revert the border. And, it exports the chart with the new border, but it doesn't resize the border. If it's larger than ExportImageSize, I see only left and top border, and if it's smaller, I get a part of the chart that goes outside the borders.
So, I set the the border to begin with, and only change the color for the exporting.
One more realization, explicitly setting the ExportImageSize can lead to some interesting side-effects. Even dough your plot looks really good, it sometimes cuts the legend, if it is to large

Can I know width of TextBlock when create it?

I create TextBox on the Canvas like this:
TextBlock t = new TextBlock();
t.Foreground = new SolidColorBrush(...);
t.FontSize = 10;
t.HorizontalAlignment = HorizontalAlignment.Left;
t.VerticalAlignment = VerticalAlignment.Top;
t.Text = ...;
Canvas.SetLeft(t, "f(width)");
Canvas.SetTop(t, ...);
canvas.Children.Add(t);
I want to know width of this TextBlock, because Left coordinate depends on this.
Can I do it? ActualWidth is 0.
Thanks.
Before you add it, call Measure on it, then use the DesiredSize.
Edit: This is OK to do because Canvas does not affect the size of the element once placed. If you added it to, say, a Grid with a fixed-size row, this wouldn't give you the real Height once added since the adding to the Grid would change it.
As Mario Vernari points out, if you have real complex positioning needs it's pretty easy to override ArrangeOverride (and sometimes MeasureOverride) and make a custom panel. Canvas is actually written this way, as is StackPanel, etc. They are all just specific-measuring and arranging panels, and you can make your own, too.

Draw a Grid over a Picturebox in c#

I am trying to make a tool in c# which allows the user to put a grid on the screen on a picturebox. At the moment i don't know how to do this, so when a button is clicked, the picturebox comes up with a grid. It needs to be a grid which is spaced out enough that users can find out locations of objects on the picture in the picturebox. Help with what code i can use to do this would be very helpful as i was going to use ControlPaint.DrawGrid but not sure of the values i need to put in it to get my desired effect?
Thanks
Form the Documentation od controlpaint.Drawgrid,
I suppose you need to decide on the cell size in x- amd y-direction and pass this as a size parameter to Drawgrid:
public static void DrawGrid(
Graphics graphics,
Rectangle area,
Size pixelsBetweenDots,
Color backColor
)
for example, a 100*200 pixels square grid would be generated by
setting graphcis to the context you want to draw upon,
Setting area to the top left right and bottom parameters of your image
setting size.x to 100 and size.y to 200
setting color to any color you like.
Update
Something like this should do.
Rectangle myRect = new System.drawings.Rectangle();
myRect.Location := new System.Drawing.Point(0,0);
myRect.Height = 50;
myRect.Width = 50;
Drawgrid(FromImage(yourImage), mygrid , yourImage.Size, System.Drawing.Color.Black);
Disclaimer: i don't develope in c#, so above code is not tested for anything. I just picked stuff from the documentation (msdn).

Categories