-
Notifications
You must be signed in to change notification settings - Fork 2
/
ExpressionsForm.cs
1467 lines (1280 loc) · 66.1 KB
/
ExpressionsForm.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using GmicDrosteAnimate;
using GmicFilterAnimatorApp;
using MathNet.Numerics;
using MathNet.Symbolics;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
namespace GmicAnimate
{
[SupportedOSPlatform("windows")]
public partial class ExpressionsForm : Form
{
private MainForm mainForm;
private string customExpressionStringFromMainWindow;
private string masterParamExpressionStringFromMainWindow;
private int masterParamIndexFromMainWindow;
private Color disabledForeColor = Color.Gray;
private Color disabledBackgroundColor = Color.LightGray;
private Color disabledMasterBackgroundColor = Color.DarkSeaGreen;
// Get parameter names from FilterParameters
private string[] paramNames = FilterParameters.GetParameterNamesList();
//Parameter count
private int filterParameterCount = FilterParameters.GetActiveParameterCount();
// For storing any data used by compare series graph
private List<PointF> compareSeriesData;
private string compareSeriesExpression;
public ExpressionsForm(MainForm mainform, string incomingExpressionParamString, int incomingMasterParamIndex, string incomingMasterParamExpression)
{
InitializeComponent();
InitalizatManualComponents();
InitializeDataGridView(); // Setup your DataGridView here
//AttachHandlersToAllDataGridViewEvents(dataGridViewExpressions);
compareSeriesData = new List<PointF>();
compareSeriesExpression = "";
// Create mouse scroll handler to properly scroll increment on master increment numeric updown
nudMasterParamIndexClone.MouseWheel += new MouseEventHandler(this.ScrollHandlerFunction);
string[] expressions = null;
// Count number of expressions in the incoming string
int expressionCount = incomingExpressionParamString.Count(c => c == ',') + 1;
if (!string.IsNullOrEmpty(incomingExpressionParamString) && expressionCount == filterParameterCount)
{
customExpressionStringFromMainWindow = incomingExpressionParamString;
}
else
// Set defaults from app info class
{
incomingExpressionParamString = FilterParameters.GetParameterValuesAsString("DefaultExponent");
customExpressionStringFromMainWindow = incomingExpressionParamString;
}
if (incomingMasterParamExpression != null)
{
masterParamExpressionStringFromMainWindow = incomingMasterParamExpression;
}
else
{
incomingMasterParamExpression = FilterParameters.GetParameterValuesAsList("DefaultExponent")[incomingMasterParamIndex].ToString();
masterParamExpressionStringFromMainWindow = incomingMasterParamExpression;
}
expressions = incomingExpressionParamString.Split(',');
UpdateAnimatedParamHighlighting();
UpdateExpressionsDataGridView(expressions: expressions, incomingMasterParamIndex);
// Set expression string. If it's empty the function will handle it by auto filling from the data table
SetCurrenExpressionParamString(expressions);
//this.masterParamIndexFromMainWindow = masterParamIndexFromMainWindow;
this.mainForm = mainform; // This is needed to send the updated expression string back to the main form
// Update graph
if (checkBoxAutoUpdateGraph.Checked)
{
PlotGraph();
}
}
// Override mouse scroll increment for numeric updown control of master increment so it doesn't change by 3
private void ScrollHandlerFunction(object sender, MouseEventArgs e)
{
NumericUpDown control = (NumericUpDown)sender;
((HandledMouseEventArgs)e).Handled = true;
decimal value = control.Value + ((e.Delta > 0) ? control.Increment : -control.Increment);
control.Value = Math.Max(control.Minimum, Math.Min(value, control.Maximum));
}
// Getter setter to trigger chart refresh
public bool TriggerGraphRefreshSetter
{
get { return checkBoxAutoUpdateGraph.Checked; }
set
{
if (checkBoxAutoUpdateGraph.Checked)
{
PlotGraph();
}
UpdateAnimatedParamHighlighting();
}
}
public string NormalizersChangeSetterExpressionsForm
{
set
{
if (value == "NormalizeStartEnd")
{
radioNormalizeStartEndClone.Checked = true;
//radioNormalizeStartEndClone_CheckedChanged(null, null);
}
else if (value == "NormalizeMaxRanges")
{
radioNormalizeMaxRangesClone.Checked = true;
//radioNormalizeMaxRangesClone_CheckedChanged(null, null);
}
else if (value == "NormalizeExtendedRanges")
{
radioNormalizeExtendedRangesClone.Checked = true;
//radioNormalizeExtendedRangesClone_CheckedChanged(null, null);
}
else if (value == "NoNormalize")
{
radioNoNormalizeClone.Checked = true;
//radioNoNormalizeClone_CheckedChanged(null, null);
}
}
}
public bool AbsoluteModeCheckBoxChangeSetterExpressionsForm
{
set
{
checkBoxAbsoluteMode.Checked = value;
}
}
public decimal MasterParamIndexNUDChangeSetterExpressionsForm
{
set
{
nudMasterParamIndexClone.Value = value;
}
}
public decimal nudGraphConstantFrameCountGetterSetter
{
get
{
if (nudGraphConstantFrameCount.Enabled)
{
return nudGraphConstantFrameCount.Value;
}
else
{
return 0;
}
}
set
{
nudGraphConstantFrameCount.Value = value;
}
}
private void InitializeDataGridView()
{
dataGridViewExpressions.Columns.Clear();
dataGridViewExpressions.AutoGenerateColumns = false;
// Hide the row headers by setting their visibility to false
dataGridViewExpressions.RowHeadersVisible = false;
// Increase row height based on DPI
int defaultRowHeight = dataGridViewExpressions.RowTemplate.Height;
dataGridViewExpressions.RowTemplate.Height = CompensateDPI(defaultRowHeight);
//Add a checkbox column
DataGridViewCheckBoxColumn chkBoxColumn = new DataGridViewCheckBoxColumn();
chkBoxColumn.HeaderText = "";
chkBoxColumn.Width = CompensateDPI(25);
chkBoxColumn.Name = "CheckBox";
chkBoxColumn.TrueValue = true;
chkBoxColumn.FalseValue = false;
dataGridViewExpressions.Columns.Add(chkBoxColumn);
// Add dummy column to be first column so I can hide it
//DataGridViewTextBoxColumn dummyColumn = new DataGridViewTextBoxColumn();
//dummyColumn.HeaderText = "";
//dummyColumn.Name = "Dummy";
//dummyColumn.Width = 0;
//dummyColumn.ReadOnly = true;
//dataGridViewExpressions.Columns.Add(dummyColumn);
// Add column for parameter names
DataGridViewTextBoxColumn paramNameColumn = new DataGridViewTextBoxColumn();
paramNameColumn.HeaderText = "Parameter Name";
paramNameColumn.Name = "ParameterName";
paramNameColumn.Width = CompensateDPI(140);
paramNameColumn.ReadOnly = true;
dataGridViewExpressions.Columns.Add(paramNameColumn);
// Add column for expressions
DataGridViewTextBoxColumn expressionColumn = new DataGridViewTextBoxColumn();
expressionColumn.HeaderText = "Exponent / Expression";
expressionColumn.Name = "Expression";
int borderWidth = dataGridViewExpressions.BorderStyle == BorderStyle.None ? 0 : CompensateDPI(2); // Calculate necessary expression column width to fill the remaining space.
expressionColumn.Width = dataGridViewExpressions.Width - paramNameColumn.Width - chkBoxColumn.Width - borderWidth;
expressionColumn.ReadOnly = false; // Set to false to allow user to enter expressions
dataGridViewExpressions.Columns.Add(expressionColumn);
dataGridViewExpressions.AllowUserToAddRows = false;
dataGridViewExpressions.AllowUserToDeleteRows = false;
dataGridViewExpressions.EditMode = DataGridViewEditMode.EditOnEnter;
// Don't allow sorting - It messes up the master parameter index highlighting
foreach (DataGridViewColumn column in dataGridViewExpressions.Columns)
{
column.SortMode = DataGridViewColumnSortMode.NotSortable;
}
}
public void UpdateEntireWindowWithNewFilter()
{
// Get parameter names and other info from FilterParameters
paramNames = FilterParameters.GetParameterNamesList();
filterParameterCount = FilterParameters.GetActiveParameterCount();
// Update max value of NUD master index
nudMasterParamIndexClone.Maximum = filterParameterCount;
// Reset NUD master index
nudMasterParamIndexClone.Value = masterParamIndexFromMainWindow + 1; // Need to add 1 because the variable is the parameter index, not the NUD value
customExpressionStringFromMainWindow = FilterParameters.GetParameterValuesAsString("DefaultExponent");
masterParamExpressionStringFromMainWindow = FilterParameters.GetParameterValuesAsList("DefaultExponent")[masterParamIndexFromMainWindow].ToString();
// Use expressions list from FilterParameters and convert to list of strings, if not null
string[] expressionsList = string.IsNullOrEmpty(customExpressionStringFromMainWindow) ? null : customExpressionStringFromMainWindow.Split(',');
// Update the data grid view
//Disable cell value change events
dataGridViewExpressions.CellValueChanged -= dataGridViewExpressions_CellValueChanged;
UpdateExpressionsDataGridView(expressionsList, masterParamIndexFromMainWindow);
dataGridViewExpressions.CellValueChanged += dataGridViewExpressions_CellValueChanged;
}
private void UpdateExpressionsDataGridView(string[] expressions, int masterParamIndex)
{
// Get current expression values in the grid
string[] currentExpressionValuesBeforeUpdate = ValuesFromDataTable(dataGridView: dataGridViewExpressions, columnName: "Expression");
// Clear existing rows
dataGridViewExpressions.Rows.Clear();
// Get default exponent values if needed. Assuming exponents are for initialization or defaults.
double[] defaultExponents = FilterParameters.GetParameterValuesAsList("DefaultExponent");
// Get animated parameter indexes to highlight
List<int> animatedParamIndexes = GetAnimatedParamIndexes();
for (int i = 0; i < paramNames.Length; i++)
{
int idx = dataGridViewExpressions.Rows.Add();
var row = dataGridViewExpressions.Rows[idx];
// Set checkbox value. Assuming unchecked by default. Adjust logic as needed.
// row.Cells["CheckBox"].Value = false;
// Set parameter name
row.Cells["ParameterName"].Value = paramNames[i];
string paramType = FilterParameters.GetParameterType(i);
// Set expression from the expressions array or default to an empty string if out of range
if (expressions != null && i < expressions.Length)
{
row.Cells["Expression"].Value = !string.IsNullOrEmpty(expressions[i]) ? expressions[i] : "";
}
// If already values in the grid use those
else if (currentExpressionValuesBeforeUpdate[i] != null)
{
row.Cells["Expression"].Value = currentExpressionValuesBeforeUpdate[i];
}
else
{
// Set default exponent value if available, but if parameter type is binary set it blank
row.Cells["Expression"].Value = (paramType == "Continuous" || paramType == "Step") ? defaultExponents[i].ToString() : "";
}
// Special highlighting rules for master parameter and animated parameters. Master parameter takes precedence
if (i == masterParamIndex)
{
row.DefaultCellStyle.BackColor = Color.LightGreen; // Choose a highlighting color that suits your UI design
}
else if (animatedParamIndexes.Contains(i))
{
row.DefaultCellStyle.BackColor = Color.LemonChiffon; // Choose a highlighting color that suits your UI design
}
// Set font of set-expressions column cells to Consolas - but only for applicable types
if (paramType == "Continuous" || paramType == "Step")
{
row.Cells["Expression"].Style.Font = new Font("Consolas", 10);
}
}
// Disable checkboxes for binary parameters, gray out the entire row
for (int i = 0; i < paramNames.Length; i++)
{
SetRowDisabledIfNecessary(rowIndex: i);
}
UpdateAnimatedParamHighlighting();
}
// Function to set row as disabled if necesary
private void SetRowDisabledIfNecessary(int rowIndex)
{
if (FilterParameters.GetNonExponentableParamIndexes().Contains(rowIndex))
{
dataGridViewExpressions.Rows[rowIndex].DefaultCellStyle.BackColor = disabledBackgroundColor;
dataGridViewExpressions.Rows[rowIndex].DefaultCellStyle.ForeColor = disabledForeColor;
dataGridViewExpressions.Rows[rowIndex].DefaultCellStyle.Font = new Font("Arial", 7);
dataGridViewExpressions.Rows[rowIndex].Height = CompensateDPI(12); // Smaller height for less important data
//dataGridViewExpressions.Rows[i].Cells["CheckBox"].ReadOnly = true;
dataGridViewExpressions.Rows[rowIndex].Cells["Expression"].ReadOnly = true;
}
}
public void UpdateMasterExponentIndex(int masterParamIndex)
{
// Set globals to latest
masterParamIndexFromMainWindow = masterParamIndex;
UpdateMasterExponentHighlighting(masterParamIndex);
// If checkbox to auto update graph is checked, update the graph
if (checkBoxAutoUpdateGraph.Checked)
{
PlotGraph();
}
}
private void ClearDataGridViewStyles()
{
foreach (DataGridViewRow row in dataGridViewExpressions.Rows)
{
row.DefaultCellStyle.BackColor = Color.White; // Clear row background color
foreach (DataGridViewCell cell in row.Cells)
{
//cell. = Color.White; // Clear specific style properties
}
}
}
// Update master exponent highlighting
private void UpdateMasterExponentHighlighting(int masterParamIndex)
{
if (dataGridViewExpressions.IsCurrentCellInEditMode)
{
dataGridViewExpressions.EndEdit();
}
// Get current expression values in the grid even if null
//string[] currentExpressionValuesBeforeUpdate = ValuesFromDataTable(dataGridView: dataGridViewExpressions, columnName: "Expression");
List<int> disabledRows = FilterParameters.GetNonExponentableParamIndexes();
ClearDataGridViewStyles();
for (int i = 0; i < dataGridViewExpressions.Rows.Count; i++)
{
SetRowDisabledIfNecessary(i);
// If the row is the master parameter row
if (i == masterParamIndex)
{
// If it's disabled then set it as darker green
if (disabledRows.Contains(i))
{
dataGridViewExpressions.Rows[i].DefaultCellStyle.BackColor = disabledMasterBackgroundColor;
dataGridViewExpressions.Rows[i].DefaultCellStyle.SelectionBackColor = disabledMasterBackgroundColor;
}
// If it's not disabled, set it as light green
else
{
dataGridViewExpressions.Rows[i].DefaultCellStyle.BackColor = Color.LightGreen;
dataGridViewExpressions.Rows[i].DefaultCellStyle.SelectionBackColor = Color.LightGreen;
}
}
// If the cell is not the master parameter row and it's not disabled
else if (!disabledRows.Contains(i))
{
dataGridViewExpressions.Rows[i].DefaultCellStyle.BackColor = Color.White;
dataGridViewExpressions.Rows[i].DefaultCellStyle.SelectionBackColor = Color.White;
}
// If the cell is not the master parameter row and it is disabled
else
{
dataGridViewExpressions.Rows[i].DefaultCellStyle.BackColor = disabledBackgroundColor;
dataGridViewExpressions.Rows[i].DefaultCellStyle.SelectionBackColor = disabledBackgroundColor;
}
}
UpdateAnimatedParamHighlighting();
}
// Function to set current start param string from array
public void SetCurrenExpressionParamString(string[] currentParamString)
{
if (!string.IsNullOrEmpty(txtCurrentExpressionParamString.Text))
{
txtCurrentExpressionParamString.Text = string.Join(",", currentParamString);
}
// If the current parameter string is empty, check if the data table has full set of values, and if so take from there
else
{
if (dataGridViewExpressions.Rows.Count == filterParameterCount)
{
string[] expressionParamValuesFromGrid = ValuesFromDataTable(dataGridView: dataGridViewExpressions, columnName: "Expression");
// If a value is for a binary parameter, set it to 1
for (int i = 0; i < expressionParamValuesFromGrid.Length; i++)
{
if (FilterParameters.GetNonExponentableParamIndexes().Contains(i))
{
expressionParamValuesFromGrid[i] = "1";
}
}
txtCurrentExpressionParamString.Text = string.Join(",", expressionParamValuesFromGrid);
}
}
}
private string[] ValuesFromDataTable(DataGridView dataGridView, string columnName)
{
string[] values = new string[dataGridView.Rows.Count];
for (int i = 0; i < dataGridView.Rows.Count; i++)
{
string cellExpression = dataGridView.Rows[i].Cells[columnName].Value.ToString();
if (!string.IsNullOrEmpty(cellExpression))
{
values[i] = cellExpression;
}
// Early return null if any of the values are null
//else
//{
// return null;
//}
}
return values;
}
private void dataGridViewExpressions_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
// Ensure the value changes are only processed for valid rows and specific columns
if (e.RowIndex >= 0 && (e.ColumnIndex == dataGridViewExpressions.Columns["Expression"].Index))
{
// Don't do anything if the start or end values are empty
if (!string.IsNullOrEmpty((string)dataGridViewExpressions.Rows[e.RowIndex].Cells["Expression"].Value))
{
UpdateParameterStringsWithNewTableData();
}
if (checkBoxAutoUpdateGraph.Checked)
{
PlotGraph();
}
if (checkBoxAutoUpdateMainWindow.Checked)
{
// Doing these instead of sending button event because I don't want to change the exponent mode
if (mainForm != null)
{
// Send the full string to the custom array text box
mainForm.CustomExpressionArrayTextBoxChangeSetter = txtCurrentExpressionParamString.Text;
// Send only the master parameter expression to the master parameter text box
mainForm.CustomMasterExpressionTextBoxChangeSetter = dataGridViewExpressions.Rows[masterParamIndexFromMainWindow].Cells["Expression"].Value.ToString();
}
}
}
// Update highlighting again
UpdateMasterExponentHighlighting(masterParamIndexFromMainWindow);
}
// When exiting a cell update the highlighting
private void dataGridViewExpressions_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
UpdateMasterExponentHighlighting(masterParamIndexFromMainWindow);
}
private void dataGridViewExpressions_CellClick(object sender, DataGridViewCellEventArgs e)
{
// Update highlighting when clicking on a cell
UpdateMasterExponentHighlighting(masterParamIndexFromMainWindow);
}
private void UpdateParameterStringsWithNewTableData()
{
string[] expressions = new string[dataGridViewExpressions.Rows.Count];
// Ensure the grids have all the values
if (expressions.Length == filterParameterCount)
{
for (int i = 0; i < dataGridViewExpressions.Rows.Count; i++)
{
if (!string.IsNullOrEmpty((string)dataGridViewExpressions.Rows[i].Cells["Expression"].Value))
{
expressions[i] = (string)dataGridViewExpressions.Rows[i].Cells["Expression"].Value;
}
//
else
{
// If it's a binary parameter, set it to 1
if (FilterParameters.GetNonExponentableParamIndexes().Contains(i))
{
expressions[i] = "1";
}
else
{
expressions[i] = "";
}
}
}
}
// Set the text boxes to the new comma-separated strings of the start and end parameters
SetCurrenExpressionParamString(expressions);
}
private void btnSendExpressionsStringToMainWindow_Click(object sender, EventArgs e)
{
if (mainForm != null)
{
// Send the full string to the custom array text box
mainForm.CustomExpressionArrayTextBoxChangeSetter = txtCurrentExpressionParamString.Text;
// Send only the master parameter expression to the master parameter text box
mainForm.CustomMasterExpressionTextBoxChangeSetter = dataGridViewExpressions.Rows[masterParamIndexFromMainWindow].Cells["Expression"].Value.ToString();
// Set the mode to custom exponents in main window
mainForm.ExponentModeRadioSetterMainForm = "CustomExponents";
}
}
public static readonly Dictionary<string, double> MathConstants = new Dictionary<string, double>
{
//{"pi", Math.PI},
//{"e", Math.E}
// Add more constants as needed
};
private void btnTestChart_Click(object sender, EventArgs e)
{
try
{
//double t = 0.0;
var variables = MathConstants.ToDictionary(kvp => kvp.Key, kvp => (FloatingPoint)kvp.Value);
// This will add 't' or update its value if 't' is somehow already in the dictionary
string formula = "t";
// Parse the formula as a symbolic expression
var expression = SymbolicExpression.Parse(formula);
System.Windows.Forms.DataVisualization.Charting.Series series = chartCurve.Series["ValueSeries"];
series.Points.Clear();
for (double t = 0; t <= 10; t += 0.1)
{
variables["t"] = t;
// Evaluate the expression symbolically with these substitutions
var substitutedExpression = expression.Evaluate(variables);
double y = (double)substitutedExpression.RealValue;
series.Points.AddXY(t, y);
}
}
catch (Exception ex)
{
MessageBox.Show($"Error plotting the function: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void btnChartValues_Click(object sender, EventArgs e)
{
PlotGraph(silent: false);
}
// Function to plot graph from button or otherwise
public void PlotGraph(bool silent = true)
{
labelErrorWhileGraphing.Visible = false;
labelReplacingXWithT.Visible = false;
//If the parameter to be graphed is binary, don't graph
if (FilterParameters.GetNonExponentableParamIndexes().Contains(masterParamIndexFromMainWindow))
{
//Show empty graph
System.Windows.Forms.DataVisualization.Charting.Series series = chartCurve.Series["ValueSeries"];
series.Points.Clear();
labelNoGraphToggleParam.Visible = true;
return;
}
else
{
labelNoGraphToggleParam.Visible = false;
}
// If can't get data from main form, return (don't graph)
if (mainForm == null)
{
return;
}
// If cell value is null or doesn't exist, return (don't graph)
if ((masterParamIndexFromMainWindow > dataGridViewExpressions.Rows.Count) || (dataGridViewExpressions.Rows[masterParamIndexFromMainWindow].Cells["Expression"].Value == null))
{
return;
}
// Get the expression to evaluate from the master parameter text box
string expressionToEvaluate = dataGridViewExpressions.Rows[masterParamIndexFromMainWindow].Cells["Expression"].Value.ToString();
//Remove spaces and make it lowercase
expressionToEvaluate = expressionToEvaluate.Replace(" ", "");
expressionToEvaluate = expressionToEvaluate.ToLower();
// Determine if it's an expression or just an exponent, and if it's an exponent, convert it to an expression
if (!ContainsStandaloneLetter(expressionToEvaluate, "t"))
{
// If it's a number then add t^
if (double.TryParse(expressionToEvaluate, out double exponent))
{
expressionToEvaluate = $"t^{exponent}";
}
}
// Get frame count from numeric up down control if constant frame count option is checked. Otherwise use 0 to let the main form decide
int frameCount = 0;
if (checkBoxKeepFramesConstant.Checked)
{
frameCount = (int)nudGraphConstantFrameCount.Value;
}
// Do some validation on the expression. If not t or x, ensure it's only a number value
// Additional variables allowed for absolute mode. Can allow x and t
if (checkBoxAbsoluteMode.Checked)
{
// If it contains neither t nor x while in absolute mode
if (!ContainsStandaloneLetter(expressionToEvaluate, "t") && !ContainsStandaloneLetter(expressionToEvaluate, "x"))
{
if (!double.TryParse(expressionToEvaluate, out double exponent))
{
if (!silent)
{
MessageBox.Show(
"The expression string must include the variable 't' or 'x' when using absolute mode. " +
"Where t is normalized for time between 0 and 1, and x is the frame number.",
"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
labelErrorWhileGraphing.Visible = true;
return;
}
}
}
// If outside absolute mode and doesn't use t
else if (!ContainsStandaloneLetter(expressionToEvaluate, "t"))
{
// If it's just a number then move on because it's an exponent - Otherwise display message about needing t
if (!double.TryParse(expressionToEvaluate, out double exponent))
{
if (!silent)
{
MessageBox.Show(
"The expression string must either be only a number, or an expression that use at least the variable 't'. " +
"Where t is normalized for time between 0 and 1." +
"\n\nExpressions can also contain the variable 'x' (which equals to the frame number), but only in absolute mode can an expression use only 'x' without 't'." +
"\n\nConstants like pi are allowed too.",
"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
// If it contains an x, show a message that it's being replaced with t. Otherwise display error message. Be sure not to replace x part of something like exp()
if (ContainsStandaloneLetter(expressionToEvaluate, "x"))
{
labelReplacingXWithT.Visible = true;
// Replace x with t
expressionToEvaluate = ReplaceStandaloneLetter(input: expressionToEvaluate, letterToReplace: "x", replacementString: "t");
}
else
{
labelErrorWhileGraphing.Visible = true;
return;
}
}
else
{
// Here do nothing because it's just an exponent
}
}
// Get data to graph the primary series
double[] primaryValuesToGraph = null;
List<Dictionary<string, object>> primaryErrorsList = new List<Dictionary<string, object>>();
List<Dictionary<string, object>> compareErrorsList = new List<Dictionary<string, object>>();
try
{
// Get the interpolated values
(primaryValuesToGraph, primaryErrorsList) = GetInterpolatedDataFromMainForm(expressionToEvaluate, masterParamIndexFromMainWindow, frameCount, silent: silent);
}
catch (Exception ex)
{
if (!silent)
{
MessageBox.Show($"Error plotting the function: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
labelErrorWhileGraphing.Visible = true;
return;
}
// Get data to graph the compare series using saved expression if there is one and the option to keep updated with normalization is checked
double[] comparedValuesToGraph = null;
if (!string.IsNullOrEmpty(compareSeriesExpression) && checkBoxCompareUpdateNormalization.Checked)
{
try
{
(comparedValuesToGraph, compareErrorsList) = GetInterpolatedDataFromMainForm(compareSeriesExpression, masterParamIndexFromMainWindow, frameCount, silent: silent);
}
catch (Exception ex)
{
if (!silent)
{
MessageBox.Show($"Error plotting the comparison function: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
labelErrorWhileGraphing.Visible = true;
return;
}
}
// If there are any errors, display the number of them and the first error
if (primaryErrorsList.Count > 0)
{
if (!silent)
{
string errorToDisplay = ErrorMessageConstructor(errorList: primaryErrorsList, totalFrames: primaryValuesToGraph.Count());
MessageBox.Show($"{errorToDisplay}",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
labelErrorWhileGraphing.Visible = true;
//return;
}
// Plot
try
{
// Clear any series to start fresh
System.Windows.Forms.DataVisualization.Charting.Series series = chartCurve.Series["ValueSeries"];
series.Points.Clear();
System.Windows.Forms.DataVisualization.Charting.Series compareSeries = chartCurve.Series["CompareSeries"];
compareSeries.Points.Clear();
List<int> framesWithErrorsIndexedFrom0 = GetFrameIndexesWithErrors(primaryErrorsList);
// Plot main series
for (int i = 0; i < primaryValuesToGraph.Length; i++)
{
var point = new System.Windows.Forms.DataVisualization.Charting.DataPoint(i, primaryValuesToGraph[i]);
// Check if index is in the list of frame indexes with errors
if (framesWithErrorsIndexedFrom0.Contains(i))
{
point.Color = Color.Red;
point.MarkerStyle = System.Windows.Forms.DataVisualization.Charting.MarkerStyle.Cross;
point.MarkerSize = 10; // Adjust marker size as needed
}
series.Points.Add(point);
}
// If any values are negative, create zero line strip
if (primaryValuesToGraph.Min() < 0)
{
// Create a new StripLine (a thick line) on y=0 line if any value go below zero
StripLine zeroLineStrip = new StripLine();
zeroLineStrip.IntervalOffset = 0; // Position at Y = 0
zeroLineStrip.StripWidth = 0; // Set the width of the strip line to 0 for it to appear as a line
zeroLineStrip.BackColor = Color.Black; // Choose the color to make it visible, matching or contrasting the axis color
zeroLineStrip.BorderWidth = 5; // Set the thickness of the zero line
zeroLineStrip.BorderColor = Color.Black; // Set the color of the border
// Add the StripLine to the Y-axis
chartCurve.ChartAreas[0].AxisY.StripLines.Add(zeroLineStrip);
}
else
{
// Remove the zero line strip if it exists
if (chartCurve.ChartAreas[0].AxisY.StripLines.Count > 0)
{
chartCurve.ChartAreas[0].AxisY.StripLines.Clear();
}
}
// If the compare series points exist, plot them
if (compareSeriesData.Count > 0 && !checkBoxCompareUpdateNormalization.Checked)
{
foreach (var point in compareSeriesData)
{
compareSeries.Points.AddXY(point.X, point.Y);
}
}
else if (comparedValuesToGraph != null)
{
// Plot compare series
for (int i = 0; i < comparedValuesToGraph.Length; i++)
{
compareSeries.Points.AddXY(i, comparedValuesToGraph[i]);
}
}
}
catch (Exception ex)
{
MessageBox.Show($"Error plotting the function: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
labelErrorWhileGraphing.Visible = true;
return;
}
}
private List<int> GetFrameIndexesWithErrors(List<Dictionary<string, object>> errorList)
{
List<int> frameIndexesFrom0 = new List<int>();
foreach (var error in errorList)
{
frameIndexesFrom0.Add((int)error["frame"] - 1);
}
return frameIndexesFrom0;
}
private string ErrorMessageConstructor(List<Dictionary<string, object>> errorList, int totalFrames)
{
// Check if all the errors are the same by comparing the "message" key in each dictionary
bool allErrorsSame = errorList.All(x => x["message"].Equals(errorList[0]["message"]));
string stringToDisplayLast = "";
// Get list of unique errors based on the "message" key. Also store info about which frames, and the first instance of an expression that caused each error
var uniqueErrors = errorList
.GroupBy(x => x["message"])
.Select(g => new
{
Message = g.Key,
Count = g.Count(),
Frames = g.Select(e => e["frame"]).ToList(),
Expression = g.First()["expression"]
})
.ToList();
string errorToDisplay = "";
if (allErrorsSame)
{
errorToDisplay = $"{errorList.Count} errors of the same kind occurred while graphing the expression." +
$"\n\nError:\n{errorList[0]["message"]}" +
$"\nExpression example from frame {uniqueErrors[0].Frames[0]}:" +
$"\n{uniqueErrors[0].Expression}";
// If the number of frames is the same as number of errors
if (totalFrames == errorList.Count)
{
errorToDisplay += $"\n\nThe error ocurred on all {totalFrames} frames.";
}
else
{
// Add this to display last because the list of frames might be very long, don't want it pushing the error message off the screen
stringToDisplayLast += $"\n\nThe error occurred on the following frames:\n{string.Join(", ", uniqueErrors[0].Frames)}";
}
}
else
{
errorToDisplay = $"{errorList.Count} errors occurred while graphing the expression." +
$"\nOf those, there were {uniqueErrors.Count} different types of errors." +
$"\n--------------------------";
if (uniqueErrors.Count > 10)
{
errorToDisplay += $"\n\nFirst 10 errors:\n\n";
for (int i = 0; i < 10; i++)
{
var error = uniqueErrors[i];
errorToDisplay += $"\n{error.Message}" +
$"\n - (Occurred on frames: {string.Join(", ", error.Frames)})" +
$"\n - Expression example from Frame {error.Frames[0]}:\n{error.Expression}" +
$"\n-----------------------------------------------";
}
}
else
{
errorToDisplay += $"\n\nErrors:\n";
foreach (var error in uniqueErrors)
{
errorToDisplay += $"\n{error.Message}" +
$"\n - (Occurred on frames: {string.Join(", ", error.Frames)})" +
$"\n - Expression example from Frame {error.Frames[0]}:\n{error.Expression}" +
$"\n-----------------------------------------------";
}
}
}
// Handle specific error messages if they're among the list
if (uniqueErrors.Any(item => item.Message.ToString().Contains("failed to find symbol", StringComparison.OrdinalIgnoreCase)))
{
// Self explanatory error message
}
if (uniqueErrors.Any(item => item.Message.ToString().Contains("the given key was not present in the dictionary", StringComparison.OrdinalIgnoreCase)))
{
errorToDisplay += "\n\nAbout Error: \"The given key was not present in the dictionary\"\n" +
"This might mean that a function or operation you tried to use is not supported or using the wrong name. Refer to the 'supported functions' button.";
}
if (uniqueErrors.Any(item => item.Message.ToString().Contains("value not convertible to a real number", StringComparison.OrdinalIgnoreCase)))
{
errorToDisplay += "\n\nAbout Error: \"Value not convertible to a real number\"\n" +
"This might mean one of the frames calculated value is not a real number, such as a division by zero or imaginary number result.";
}
if (uniqueErrors.Any(item => item.Message.ToString().Contains("expecting: end of input or infix operator", StringComparison.OrdinalIgnoreCase)))
{
errorToDisplay += "\n\nAbout Error: \"Expecting: end of input or infix operator\"\n" +
"This error might mean you forgot to put an operator symbol between values. For example if you put \"2(cos(t))\" instead of \"2*(cos(t))\", leaving out the * multiplication sign after the 2." +
"It could also mean you used an unknown operator such as %.";
}
errorToDisplay += stringToDisplayLast;
return errorToDisplay;
}
public class DictionaryComparer : IEqualityComparer<Dictionary<string, object>>
{
public bool Equals(Dictionary<string, object> x, Dictionary<string, object> y)
{
if (x == null || y == null)
{
return false;
}
return x["message"].Equals(y["message"]);
}
public int GetHashCode(Dictionary<string, object> obj)
{
return obj["message"].GetHashCode();
}
}
public static string ReplaceStandaloneLetter(string input, string letterToReplace, string replacementString)
{
// Escape special characters in the letterToReplace to safely include it in the regex pattern
string escapedLetterToReplace = Regex.Escape(letterToReplace);
// Regex pattern to match the letterToReplace not surrounded by alphanumeric characters
string pattern = $@"\b{escapedLetterToReplace}\b";
// Replace standalone letterToReplace with replacementString
string result = Regex.Replace(input, pattern, replacementString);
return result;
}
static bool ContainsStandaloneLetter(string input, string letterToCheck)
{
// Escape special characters in the letterToCheck to safely include it in the regex pattern
string escapedLetterToCheck = Regex.Escape(letterToCheck);
// Regex pattern to match the letterToCheck not surrounded by alphanumeric characters
string pattern = $@"\b{escapedLetterToCheck}\b";
// Check if the pattern exists in the input
bool contains = Regex.IsMatch(input, pattern);
return contains;
}
// Get interpolated data into graphable form
private (double[], List<Dictionary<string, object>>) GetInterpolatedDataFromMainForm(string expressionToEvaluate, int masterParamIndex, int frameCount, bool silent = true)
{
List<string> interpolatedValuesPerFrameArray = new List<string>();
List<Dictionary<string, object>> errorsInfoList = new List<Dictionary<string, object>>();