scrolling issue with huge points of stackedbar graph - c#

I have chart with multiple chart areas and every area contain a bunch of series that need to be updated with scrolling, all chart areas working well except the one below , it takes alot of resources from the memory i think and not scrolling smoothly.
I want to achieve this chart area effectively with smooth scrolling
i achieved same results using stacked bar graph, i added the points from data table, here is how the data table looks like
And as i know displaying only the portion that is needed is certainly the option the takes least resources and time,
so here is the code i use to add my points from data table
private void GraphDemo_Load(object sender, EventArgs e)
{
System.Drawing.Rectangle workingRectangle =
System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
this.chart.Size = new System.Drawing.Size(workingRectangle.Width - 50, 1000);
scroller = new VScrollBar();
scroller.Dock = DockStyle.Left;
croller.Maximum = 6000 - windowSize; //windowSize = 180;
scroller.LargeChange = 180;
scroller.Scroll += scroller_Scroll;
}
void scroller_Scroll(object sender, ScrollEventArgs e)
{
for (int s = 0; s < chart.Series.Count; s++)
{
LoadCore.chart.Series[s].Points.Clear();
}
for (float ii = 0; ii < dt.Rows.Count; ii++)
{
row = dt.Rows[(int)ii];
ts11 = Convert.ToDouble(row.ItemArray[0]) + 1; //top.value column + 1
ts12 = Convert.ToDouble(row.ItemArray[1]); //base.value column
for (double t = LoadCore.ts11; t <= LoadCore.ts12; t++)
{
//k is the index no. of each series column
for (int k = 2; k <= dt.Columns.Count - 1; k++)
{
if (scroller.Value < t)
{
if (scroller.Value + windowSize > t) //windowSize = 180;
{
//tempindex1 is the index of specific row value at specific base.value columnn
chart.Series[Convert.ToString(dt.Columns[k].ColumnName)].Points.AddXY(t, dt.Rows[tempIndex1][k]);
chart.Series[Convert.ToString(dt.Columns[k].ColumnName)]["PointWidth"] = "1";
}
}
}
}
}
}
With the above code i still can not scroll smoothly and i think it's because of the huge number of points needed to be added with the portion of 180 (window size), ...
so my question is there an alternative effective way to achieve the first image with smooth scrolling?.
should i use any other types of graph instead of stacked bar graph ? what is it?

Related

Multiple chart areas in one column

I have a chart with multiple chart areas. When I press a button a new chart area is being created etc.
My problem is that after adding some chart areas I get the following result :
I want to have each chart area in only one column, one after the other like this :
is this possible ?
EDIT: Adding chart areas dynamically
on the left it is the chart with 3 chart areas added and the right is the chart with 4 areas.
Use property ChartArea.AlignWithChartArea
Through the use of the AlignWithChartArea, AlignmentOrientation and AlignmentStyle properties, it is possible to align or synchronize two or more chart areas horizontally, vertically or both.
First, set the AlignWithChartArea property to the name of a ChartArea object. This chart area will then be aligned, based on the AlignmentStyle setting, which defines the alignment to use, and the AlignmentOrientation setting, which defines the elements of the chart area that should be used to set the alignment.
So to put ChartArea2 below ChartArea1:
ChartArea2.AlignWithChartArea1;
ChartArea2.AlignmentStyle = AreaAlignmentStyles.Position;
ChartArea2.AlignmentOrientation = AreaAlignmentOrientation.Vertical;
Here is how you can achive what you need, also referring for your question here: https://stackoverflow.com/questions/67124754/dynamically-add-chart-areas-in-vertical-alignment
Use the Position property and please read comments inside the code:
private void button1_Click(object sender, EventArgs e)
{
List<ChartArea> Areas = new List<ChartArea>();
int numberOfAreas = 5;
chart1.Legends[0].Enabled = false;
for (int k = 1; k <= numberOfAreas; k++) // YOU WANT 5 and not 4 AREAS - changed k< to k<=
{
var S1 = new Series();
chart1.Series.Add(S1);
S1.Name = k.ToString();
for (int j = 0; j < 100; j += 10) S1.Points.AddXY(j, j / 10);
S1.Color = Color.Transparent;
Areas.Add(new ChartArea(k.ToString()));
chart1.ChartAreas.Add(Areas[k - 1]); // IT IS k-1 and not k - we start counting from 0
S1.ChartArea = k.ToString();
chart1.ChartAreas[k - 1].AlignWithChartArea = chart1.ChartAreas[k - 1].Name;
chart1.ChartAreas[k - 1].AlignmentStyle = AreaAlignmentStyles.Position;
chart1.ChartAreas[k - 1].AlignmentOrientation = AreaAlignmentOrientations.Vertical;
}
// NOW THE IMPORTANT PART
float currentHeight = 0;
foreach (var itm in Areas)
{
itm.Position.Height = 100 / numberOfAreas; // Note: the valus are in percenteges and not absolute pixels
itm.Position.Y = currentHeight;
itm.Position.X = 5;
itm.Position.Width = 95;
currentHeight += 100 / numberOfAreas;
}
}
OUTPUT:

Scrolling/Moving MS chart area to highlighted datapoint when in zoom state

In windows form, I am displaying data in MS Chart from Datagridview.
When selecting an row in the datagridview, I am highlighting the corresponding datapoint in the chart with different color.
When chart is in zoom state , if a datapoint is highlighted newly and if it is not in the visible state, I have to scroll/move the chart to highlighted datapoint.
chart.ChartAreas.Add("LineGraphHistory");
chart.ChartAreas["LineGraphHistory"].AxisX.Title = "X Axis";
chart.ChartAreas["LineGraphHistory"].AxisX.MajorGrid.LineColor = System.Drawing.Color.Black;
chart.ChartAreas["LineGraphHistory"].AxisX.MajorGrid.LineDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Dash;
chart.ChartAreas["LineGraphHistory"].AxisY.Title = "Y Axis";
chart.ChartAreas["LineGraphHistory"].AxisY.MajorGrid.LineColor = Color.Black;
chart.ChartAreas["LineGraphHistory"].AxisY.MajorGrid.LineDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Dash;
chart.ChartAreas["LineGraphHistory"].BackColor = Color.White;
chart.ChartAreas["LineGraphHistory"].CursorX.IsUserEnabled = true;
chart.ChartAreas["LineGraphHistory"].CursorX.IsUserSelectionEnabled = true;
chart.ChartAreas["LineGraphHistory"].CursorX.Interval = 0;
chart.ChartAreas["LineGraphHistory"].AxisX.ScaleView.Zoomable = true;
chart.ChartAreas["LineGraphHistory"].AxisX.ScrollBar.Enabled = true;
chart.Legends.Add("Legend");
chart.Legends["Legend"].BorderColor = Color.Tomato;
chart.DataSource = CSVDataTable;
chart.ChartAreas["LineGraphHistory"].AxisX.IntervalType = DateTimeIntervalType.Seconds;
chart.ChartAreas["LineGraphHistory"].AxisX.LabelStyle.Format ="dd-MM-yyyy\n hh:mm:ss"; ;
chart.Series[s].XValueType =ChartValueType.DateTime ;
chart.DataBind();
chart.Update();
private void cDataGrid_SelectionChanged(object sender, EventArgs e)
{
int nCount = csvDataGrid.SelectedRows.Count;
if (nCount > 0)
{
for (int i = 0; i < nCount; i++)
{
int index = csvDataGrid.SelectedRows[i].Index;
if (index >= csvDataGrid.Rows.Count-1)
return;
for (int k = 0; k < chart.Series.Count; k++)
{
DataPointCollection pr = chart.Series[k].Points;
pr[index].MarkerColor = Color.DarkGoldenrod;
pr[index].MarkerStyle = MarkerStyle.Star10;
pr[index].MarkerSize = 20;
// chart.
}
chart.Update();
}
}
}
How to achieve this?
As Taw suggested I tried to set scaleview position.
I have 10 datapoints. The range of x value of datapoints are 20 to 200. Each x value has equal difference of 20. The view size is 100. In zoom mode, when I scrolling to maximum the x range is 101 to 200 in the view , the last point is displayed as 5th point in the view. Whereas if I use your code to set scaleview position to highlight last datapoint , the x range becomes 180 to 240 and highlighted last datpoint is visible as first range.
Why paintviewmin and paintviewmax values are changing?
The images are
You need to calculate the offset from the DataPoint dp.XValue, maybe like this:
Axis ax = chart.ChartAreas[0].AxisX;
var size = ax.ScaleView.ViewMaximum - ax.ScaleView.ViewMinimum;
ax.ScaleView.Position = dp.XValue - size / 2.0;
Example:
Update: When smaller data sets are displayed the automatically added margins mess up the simple calculation above. To avoid this you can add:
chart.ChartAreas[0].AxisX.IsMarginVisible = false;

Dynamically Generated Grid of Images

I'm writing a Winform application that processes a grid of products printed on a sheet. To more effectively communicate with my end user I've been tasked with creating a visual representation of the grid (as opposed to just giving them "Row x Col y in text" ) to depict which items on the sheet could not be processed.
My first thought was to use a grid of images (green check mark/red X) aligned in a grid to match the sheet that the app will actually process. The problem with that approach is that eventually we'll have different jobs that use different sheet alignments. One might be a 3x10 grid, another might be a 1x8 etc.
Is there something I can use to define an area of my form as reserved for images, and also a way to insert copies of my image file that have been resized to fit in that area?
Something like:
container.add(
new Image("myFileLocation", imgHeight, imgWidth),
(container.height / numRows),
(container.width/numCols)
);
?
Sorry if this is a stupid question. I'm comfortable in c# but have approximately zero experience designing GUIs for these things.
#ASh that's exactly what I wanted, thanks.
In case it helps anyone else out there here are some references that I found useful to learn how to use TableLayoutPanels:
Generate rows/columns at runtime.
TableLayoutPanel rows & columns at runtime
Add image file to picturebox
Set image source to picturebox which is added dynamically to Table Layout Panel
Resize image inside picturebox (stretch to fill)
Fit Image into PictureBox
Center images inside cells
https://learn.microsoft.com/en-us/dotnet/framework/winforms/controls/how-to-align-and-stretch-a-control-in-a-tablelayoutpanel-control
My code:
int rows = 7; //Will come from database
int cols = 3; //Will come from database
int colWidth;
int rowHeight;
PictureBox pbox;
Random rnd = new Random();
colWidth = 100 / cols;
if (100 % cols != 0)
colWidth--;
rowHeight = 100 / rows;
if (100 % rows != 0)
rowHeight--;
tabLP.Controls.Clear();
tabLP.ColumnStyles.Clear();
tabLP.RowStyles.Clear();
tabLP.ColumnCount = cols;
for (int i = 0; i < rows; i++)
{
tabLP.RowStyles.Add(new RowStyle(SizeType.Percent, rowHeight));
for (int j = 0; j < cols; j++)
{
tabLP.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, colWidth));
if (rnd.NextDouble() > 0.5 )
{
pbox = new PictureBox() { Image = Properties.Resources.red_X};
}
else
{
pbox = new PictureBox() { Image = Properties.Resources.checkbox_green };
}
pbox.Dock = DockStyle.Fill;
pbox.SizeMode = PictureBoxSizeMode.StretchImage;
tabLP.Controls.Add(pbox, j, i);
}
}

How can I instantiate large number of buttons in Windows Forms?

I'm developing a theatre reservation software. I'm using Windows Forms, the seats is represented by a 2-dimensioned array. And I draw the buttons as following:
public void DrawSeats()
{
// pnl_seats is a Panel
pnl_seats.Controls.Clear();
// Here I store all Buttons instance, to later add all buttons in one call (AddRange) to the Panel
var btns = new List<Control>();
// Suspend layout to avoid undesired Redraw/Refresh
this.SuspendLayout();
for (int y = 0; y < _seatZone.VerticalSize; y++)
{
for (int x = 0; x < _seatZone.HorizontalSize; x++)
{
// Check if this seat exists
if (IsException(x, y))
continue;
// Construct the button with desired properties. SeatSize is a common value for every button
var btn = new Button
{
Width = SeatSize,
Height = SeatSize,
Left = (x * SeatSize),
Top = (y * SeatSize),
Text = y + "" + x,
Tag = y + ";" + x, // When the button clicks, the purpose of this is to remember which seat this button is.
Font = new Font(new FontFamily("Microsoft Sans Serif"), 6.5f)
};
// Check if it is already reserved
if (ExistsReservation(x, y))
btn.Enabled = false;
else
btn.Click += btn_seat_Click; // Add click event
btns.Add(btn);
}
}
// As said before, add all buttons in one call
pnl_seats.Controls.AddRange(btns.ToArray());
// Resume the layout
this.ResumeLayout();
}
But already with a seat zone of 20 by 20 (400 buttons), it spent almost 1 minute to draw it, and in debug I checked that the lack of performance, is the instantiation of the buttons.
There is a way to make it faster? Perhaps disable all events during the instatiation or another lightweight Control that has the Click event too?
UPDATE:
lbl was a test, the correct is btn, sorry.
UPDATE 2:
Here is the IsException and ExistsReservations methods:
private bool IsException(int x, int y)
{
for (var k = 0; k < _seatsExceptions.GetLength(0); k++)
if (_seatsExceptions[k, 0] == x && _seatsExceptions[k, 1] == y)
return true;
return false;
}
private bool ExistsReservation(int x, int y)
{
for (var k = 0; k < _seatsReservations.GetLength(0); k++)
if (_seatsReservations[k, 0] == x && _seatsReservations[k, 1] == y)
return true;
return false;
}
Suppose that you change your arrays for reservations and exclusions to
public List<string> _seatsExceptions = new List<string>();
public List<string> _seatsReservations = new List<string>();
you add your exclusions and reservations in the list with something like
_seatsExceptions.Add("1;10");
_seatsExceptions.Add("4;19");
_seatsReservations.Add("2;5");
_seatsReservations.Add("5;5");
your checks for exclusions and reservations could be changed to
bool IsException(int x, int y)
{
string key = x.ToString() + ";" + y.ToString();
return _seatsExceptions.Contains(key);
}
bool ExistsReservation(int x, int y)
{
string key = x.ToString() + ";" + y.ToString();
return _seatsReservations.Contains(key);
}
of course I don't know if you are able to make this change or not in your program. However consider to change the search on your array sooner or later.
EDIT I have made some tests, and while a virtual grid of 20x20 buttons works acceptably well (31 millisecs 0.775ms on average), a bigger one slows down noticeably. At 200x50 the timing jumps to 10 seconds (1,0675 on average). So perhaps a different approach is needed. A bound DataGridView could be a simpler solution and will be relatively easy to handle.
I also won't use such a myriad of controls to implement such a thing. Instead you should maybe create your own UserControl, which will paint all the seats as images and reacts on a click event.
To make it a little easier for you i created such a simple UserControl, that will draw all the seats and reacts on a mouse click for changing of the state. Here it is:
public enum SeatState
{
Empty,
Selected,
Full
}
public partial class Seats : UserControl
{
private int _Columns;
private int _Rows;
private List<List<SeatState>> _SeatStates;
public Seats()
{
InitializeComponent();
this.DoubleBuffered = true;
_SeatStates = new List<List<SeatState>>();
_Rows = 40;
_Columns = 40;
ReDimSeatStates();
MouseUp += OnMouseUp;
Paint += OnPaint;
Resize += OnResize;
}
public int Columns
{
get { return _Columns; }
set
{
_Columns = Math.Min(1, value);
ReDimSeatStates();
}
}
public int Rows
{
get { return _Rows; }
set
{
_Rows = Math.Min(1, value);
ReDimSeatStates();
}
}
private Image GetPictureForSeat(int row, int column)
{
var seatState = _SeatStates[row][column];
switch (seatState)
{
case SeatState.Empty:
return Properties.Resources.emptySeat;
case SeatState.Selected:
return Properties.Resources.choosenSeat;
default:
case SeatState.Full:
return Properties.Resources.fullSeat;
}
}
private void OnMouseUp(object sender, MouseEventArgs e)
{
var heightPerSeat = Height / (float)Rows;
var widthPerSeat = Width / (float)Columns;
var row = (int)(e.X / widthPerSeat);
var column = (int)(e.Y / heightPerSeat);
var seatState = _SeatStates[row][column];
switch (seatState)
{
case SeatState.Empty:
_SeatStates[row][column] = SeatState.Selected;
break;
case SeatState.Selected:
_SeatStates[row][column] = SeatState.Empty;
break;
}
Invalidate();
}
private void OnPaint(object sender, PaintEventArgs e)
{
var heightPerSeat = Height / (float)Rows;
var widthPerSeat = Width / (float)Columns;
e.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
for (int row = 0; row < Rows; row++)
{
for (int column = 0; column < Columns; column++)
{
var seatImage = GetPictureForSeat(row, column);
e.Graphics.DrawImage(seatImage, row * widthPerSeat, column * heightPerSeat, widthPerSeat, heightPerSeat);
}
}
}
private void OnResize(object sender, System.EventArgs e)
{
Invalidate();
}
private void ReDimSeatStates()
{
while (_SeatStates.Count < Rows)
_SeatStates.Add(new List<SeatState>());
if (_SeatStates.First().Count < Columns)
foreach (var columnList in _SeatStates)
while (columnList.Count < Columns)
columnList.Add(SeatState.Empty);
while (_SeatStates.Count > Rows)
_SeatStates.RemoveAt(_SeatStates.Count - 1);
if (_SeatStates.First().Count > Columns)
foreach (var columnList in _SeatStates)
while (columnList.Count > Columns)
columnList.RemoveAt(columnList.Count - 1);
}
}
This will currently draw forty rows and columns (so there are 800 seats) and you can click on each seat to change its state.
Here are the images i used:
EmtpySeat:
ChoosenSeat:
FullSeat:
If you anchor this control and resize it or you click on a seat to change its state there can be some minor lacking for the repainting if you further increase the number of rows or columns, but that is still somewhere far below one second. If this still hurts you, you have to improve the paint method and maybe check the ClipRectangle property of the paint event and only paint the really needed parts, but that's another story.
Rather than using actual button controls, just draw the image of the seats then when the user clicks on a seat translate the mouse X,Y coordinates to determine which seat was clicked. This will be more efficient. Of course, the drawback is that you have to write the method to translate x,y coordinates to a seat, but that really isn't that difficult.
EDIT; it has been pointed out to me this will not work in Windows Forms!
Well, you are Sequentially working through it.
if one iteration costs 1 sec, the full process will take 400*1 in time.
Perhaps you should try and make a collection of your objects, and process it 'parallel'.
try the .Net framework (4 and above) 'parallel foreach' method:
http://msdn.microsoft.com/en-s/library/system.threading.tasks.parallel.foreach(v=vs.110).aspx
edit: so, if you have a list buttonnames, you can say
buttonNames.ForEach(x=>CreateButton(x));
while your CreateButton() method is as following:
private Button CreateButton(string nameOfButton) { Button b = new
Button(); b.Text = nameOfButton; //do whatever you want...
return b; }

how to increase the tabs width to the mouse position

I'm developing a browser relying on webbrowser winform component.
I used xtratabcontrol in order to surf with tabs (DevExpress component) which has close button in its tabs.
I'm trying to achieve what internet explorer(its last version) does when closing a tab ; the other tabs are extended(their width increases) to reach the mouse poisiton (to enable closing tab after tab without moving the mouse)
this is the code which I added ( in close button click event)
Point cursor = MousePosition;
int x = cursor.X;
int count = browserTabControl.TabPages.Count -1;// I don't want to include the last tab (which opens another tabs)
int width = x / count;
for (int i = 0; i < browserTabControl.TabPages.Count -1 ; i++)
browserTabControl.TabPages[i].TabPageWidth = width;
I also tried to get the tabs whole width before removing the last tab, then to divide it on the new tabs count , and to set the result to each tab :
int current_width = (browserTabControl.TabPages.Count - 1) * browserTabControl.TabPages[0].TabPageWidth;
//..............some code removing the last tab (actually the tab before the last tab)
//after removing the last tab
int count = browserTabControl.TabPages.Count - 1;
int width = current_width / count;
for (int i = 0; i < browserTabControl.TabPages.Count - 1; i++)
browserTabControl.TabPages[i].TabPageWidth = width;
the first code result
the second code result :
Probably the problem is when I divide int/int I lose the rest of the division, I can get a double result , but the TabPageWidth is int.
Try the following solution:
// Initialization
foreach(XtraTabPage page in xtraTabControl.TabPages) {
if(page == addNewTabPage) continue;
page.TabPageWidth = 100; // turn off headers auto-size
}
}
void xtraTabControl_CloseButtonClick(object sender, EventArgs e) {
ClosePageButtonEventArgs ea = e as ClosePageButtonEventArgs;
if(ea.Page != addNewTabPage) {
xtraTabControl.BeginUpdate();
((XtraTabPage)ea.Page).Dispose();
int totalWidth = 0;
var visiblePages =((IXtraTab)xtraTabControl).ViewInfo.HeaderInfo.VisiblePages;
int totalHeadersGrow = 0;
for(int i = 0; i < visiblePages.Count; i++) {
var pageInfo = visiblePages[i];
if(pageInfo.Page == addNewTabPage) continue;
totalWidth += pageInfo.Bounds.Width;
totalHeadersGrow += (pageInfo.Bounds.Width - pageInfo.Page.TabPageWidth);
}
int count = xtraTabControl.TabPages.Count - 1;
int width = totalWidth / count - totalHeadersGrow / (count + 1);
foreach(XtraTabPage page in xtraTabControl.TabPages) {
if(page == addNewTabPage) continue;
page.TabPageWidth = width;
}
xtraTabControl.EndUpdate();
}
}
P.S. You can contact DevExpress support directly (and I believe that this is the best way when running into issues when using their product) to get official answer in this regard.
You can use HeaderAutoFill feature for this. which will automatically fill the tabs to the client area; so there is no need for the user to move the mouse for closing multiple
this.xtraTabControl1.HeaderAutoFill = DevExpress.Utils.DefaultBoolean.True;

Categories