This is where refactoring is definitely needed, any ideas? / Hebrew
There are pet projects, and we have a project with developments that can be useful, for example, to students of technical specialties and simply to everyone who is interested in understanding the possibilities of visualization in C# + WPF, for example, or with the system of redundant coding.
The students and I made an application for analyzing the characteristics of LDPC codes first in Java (the Java code is also in the repository), then I rewrote it in the form of a C# + WPF project to add the possibility of configuring statistical experiments through a visual interface, and most importantly to be able to visualize the results of experiments in the form of graphs (ordinary, in X, Y axes). Just for work, I made a library for drawing ordinary mathematical graphs from arrays of values with the possibility of scaling the viewing area with the mouse.
I think that such a library can be useful for students of any technical fields, despite the fact that all its rather compact source code is localized in the project and is available both for study and for any changes and improvements.
Below is a link to the Git repository with the source code and an overview of the implemented functionality with screenshots.
Projects in C# and Java are available in the repository on Git. The Java project turned out to be completely abandoned and I don’t know what its condition is, but the pictures with graphs of the statistical characteristics of LDPC codes for the diploma were generated by students with the help of this Java project + some visualization library for constructing graphs in Python. In general, the implementation code of the statistical experiment in Java has already undergone one refactoring, I helped students determine the correct (in the sense of working) configuration of classes at the entity level to build the decoding process, but this is a very difficult topic. I myself, after a year, will probably need about a week to remember and figure out how the decoding process works there, because I need to load back into my brain the theory based on the Belief propagation algorithms, also known as sum-product message passing. Finally, I will tell you about the organization of a statistical experiment, it is not particularly difficult and I hope it can be useful to someone.
So, if you compile and run a C# project (probably in Visual Studio, I had 2022), then you can see a very unfashionable window interface against the background of command line capabilities:
With the statistical experiment configuration options listed in the Configs group. You can ignore the Misc group, this group was created to experiment with the possibilities of the selected method of visualization-saving-editing of configuration parameters. In general, it should be noted that the purpose of this project (code) was and remains the verification of various ideas and ways of implementing virtually arbitrary functionality, therefore, artifacts of such experiments are present everywhere at various stages of the development of these experiments. So if you want, for example, to experiment or just explore the possibilities, such as rendering-saving-editing configuration parameters, you can download this project.
If I have correctly committed the project to Git immediately after launch, a valid experiment configuration should load as in the picture, and the experiment can be started with the BPStart button. After that, you need to wait a few seconds while the program performs calculations for the experiment. This is, of course, a very serious flaw for a program with a visual interface. I never got around to making a progress bar, there is no indication of progress! It remains only to wait and hope that the result will appear, but it always seemed to appear, something like this:
Which can be scaled by simply selecting with the mouse the rectangle you want to take a closer look at:
Redrawn graph within the selected area:
Contents
Why do you need a home-made graphing library?
Somehow I needed to visualize a data array in the form of a graph with axes and division labels and preferably with a grid and with the ability to scale to individual segments of the graph. After such a list of tasks, it does not seem very simple, but I have done it many times in my work, so for me it is just a couple of dozen hours (it is difficult to estimate when it is done in fragments during the year) of a routine with a guaranteed result.
To begin with, I usually tried to download and use existing solutions – libraries, packages, but all of them turned out to be so sophisticated that I thought that it was easier and more promising (and more interesting) for me to make my own library than to figure out how to configure the display everyone I need the above attributes of a traditional to banality graph with all captions and grid and scaling.
The main problem with the existing graph drawing libraries is that they are made completely universal, they allow you to display dozens of types of graphs, histograms, charts, … so they require the setting of hundreds of parameters with the purpose and relationships of which, as I thought, was not possible figure it out in reasonable time. Another not insignificant argument for the development of my own is, of course, the possibility of my own development, because in addition to the fact that these foreign libraries seemed too complicated to me, the fact that I was simply interested in drawing myself and getting full control over the code that performs drawing played a big role. As a result, I got a library of 5-7 source code files of approximately 100 lines each. There are also artifacts from abandoned code experiments. It is necessary to see what code really refers to the implementation of graphs, but there is very little code in any case. Seems like a good result for a rendering library to me.
How it works from a C# code perspective
The custom window with the graph is created in the following function:
public static void makeGraph(double[] Yarr, double[] Xarr, double[] Y2arr)
{
Yarr = Array.ConvertAll(Yarr, y => Math.Log10(y));
var gpb = CustomControls.graphXYControl.getDblXYGraph(Yarr, Xarr, "arr1");
Y2arr = Array.ConvertAll(Y2arr, y => y != 0 ? Math.Log10(y):double.NegativeInfinity);
gpb.AddYarray(Y2arr, "Y2arr");
graphView graph = new graphView(gpb);
//graph.setXlimits(1, 3);//какой то артефакт
graph.Owner = wnd;
graph.Show();
}
For arrays of Y and X coordinate values (X-coordinates are also arbitrary, not a linearly increasing sequence!) an object of type GraphXYDblProc is created to display an array of points with arbitrary coordinates along the X axis with the type of values double. This object of type GraphXYDblProc is used through the GraphProcBase base class interface. The fact is that initially for work I needed graphs only for int (integer) values with a constant step along the X-axis. To add support for graphs with arbitrary x-axis values and floating-point coordinate values, I had to separate the base class, in which only the functions of drawing graphs and their attributes remained, and the creation of visualization objects depending on the type of output data was divided into child levels classes, thus I had:
-
GraphProc class that encapsulates the visualization of an array of integer values with a given constant step along the X axis;
-
More complex Class GraphXYDblProc which encapsulates the visualization of an array of points given by two arrays of double values along the X axis and along the Y axis;
-
GraphProcBase class that encapsulates the functionality related to the attributes and behavior of the graph (grid, signatures, scaling) regardless of the type of data on which the graph is constructed.
Next, this object of type GraphProcBase (any child) is passed to object of type graphXYControl which is the inheritor of the standard prototype of the visual component UserControl and which provides standard interaction with the form, with the mouse.
You will find the code that provides scaling in the file: \ldpcExam\csharp\dipLdpc\CustomControls\graphXYControl.xaml.cs
in functions:
private void drawingSurface_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
private void drawingSurface_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
as well as the code that generates graphics design attributes: axes (DrawAxis..), grid, captions, legends (DrawLegends).
Another tab in the main program window
The project has the rudiments of logging functionality. If you go to the Output tab in the main window, you will see a TextBox to which all logs are redirected.
Logging functions are made static and public, so as not to multiply and drag references to the logger throughout the project.
Also on this tab there are four Test buttons, which respectively run the corresponding tests.
-
“Simple test” is a graph test in integer array display mode, simply showing a graph with test (known, easily visually controlled) data.
-
“Codeword test” generates and checks a code word, logs the result.
-
“Gauss test” is a test of a random number generator with a normal distribution and another graph test in the display mode of an array with integers, artifacts are specially inserted into the graphs for visual control. It looks something like this:
artifact in close consideration:
-
“Wav test” is not relevant to the project at all, it checks the AAC encoded audio stream for validity, if that tells you anything :), ignore it.
About the organization of a statistical experiment
The scheme is basically standard:
-
The WordGen wg code word generator generates a random code.
-
The noise generator generates noise, replaces the output bits of the code with random probabilities with which they seem to be received by the receiver, it seems somewhere inside these calls:
...
chn.AddCodeWord(wg.getCodeWord());
double[] LLR = chn.convertToLLR();
...
-
The code decoder performs the decoding procedure – it tries to return the source code created by the generator, it seems (although it is correct, it is written decode.. 🙂 somewhere inside these calls:
bpAlg.StartWithLLR(LLR);
int[] resultCodeWord = bpAlg.decode(shiftBlocks);
-
The global algorithm compares the result obtained after the decoder with the output value from the generator and collects statistics depending on the noise level, code length, … I don’t remember all the options.
In general, nothing complicated.
Conclusion
The Internet and Khabr, in particular, are full of stories about how to correctly build the architecture of the program, what are the correct design patterns, the principles of OOP, KISS, DRY, SOLID, … and all this is demonstrated on finger-sucking examples that raise doubts not only about the necessity of these principles , and the need for programming itself.
I think we have a good example of a library program that could use a decent refactoring. Any ideas?