Skip to content

Commit 99fd9b9

Browse files
committed
Add dot graph feature
1 parent 96df6fe commit 99fd9b9

10 files changed

+342
-25
lines changed

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ If you want your chart to be vertical, you can use the `orientation` argument:
3434
$ ./ascii-data-visualizer file.csv --orientation vertical
3535
```
3636

37+
#### Graphs
38+
To make a dot graph:
39+
```bash
40+
$ ./ascii-data-visualizer file.csv --type graph
41+
```
42+
3743
### File Format
3844
Format follows `label,value` format:
3945
```csv
@@ -43,3 +49,14 @@ C,20
4349
C++,23
4450
Python,51
4551
```
52+
53+
#### Graphs
54+
Graph formats follow `x,y` values:
55+
```csv
56+
x: Time (years)
57+
y: Radioactive Decay (percent)
58+
59+
30,50
60+
60,25
61+
90,12.5
62+
```

ascii-data-visualizer.cpp

+3-23
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,10 @@
11
#include <iostream>
22
#include <string>
3-
#include <fstream>
4-
#include <regex>
5-
#include <filesystem>
6-
#include <cmath>
7-
#include <unordered_map>
8-
#include "chart_manager.h"
9-
#include "file_reader.h"
10-
#include "arg_handler.h"
3+
#include "visualizer.h"
114

125
int main(int argc, char **argv)
136
{
14-
std::string fileName = getFileNameFromArgs(argc, argv);
15-
auto cmdArgs = getCommandLineArgs(argc, argv);
16-
17-
std::string orientation = getArgValue(cmdArgs, "orientation", "horizontal");
18-
int length = std::stoi(getArgValue(cmdArgs, "length", "10"));
19-
std::string chartString = "";
20-
if (orientation == "vertical"){
21-
int height = std::stoi(getArgValue(cmdArgs, "height", "10"));
22-
VerticalBarChart myChart(fileName, length, height);
23-
chartString = myChart.drawChart();
24-
} else{
25-
HorizontalBarChart myChart(fileName, length);
26-
chartString = myChart.drawChart();
27-
}
28-
std::cout << chartString;
7+
std::string visualizerAscii = getVisualizerAscii(argc, argv);
8+
std::cout << visualizerAscii << std::endl;
299
return 0;
3010
}

include/data_utilities.h

+12
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,19 @@
33
#include <iostream>
44
#include <string>
55
#include <regex>
6+
#include <vector>
67

78
const std::regex lastDecimalPattern{"^([\\d.]+?(?:[1-9]?$|(?=0+?$)))"};
89

910
std::string doubleToStringPrecision(double value);
11+
std::string strToLower(std::string str);
12+
std::vector<double> sortDoubleVector(std::vector<double> _vector);
13+
std::pair<std::vector<double>, std::vector<double>> extractVectorsFromPair(
14+
std::vector<std::pair<double, double>> target
15+
);
16+
std::pair<double, double> findMinMaxDoubleInVector(std::vector<double> target);
17+
template<typename vecType>
18+
bool vectorContains(const std::vector<vecType> target, const vecType value)
19+
{
20+
return std::find(target.begin(), target.end(), value) != target.end();
21+
}

include/file_reader.h

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#include <iostream>
44
#include <string>
55
#include <regex>
6-
#include <unordered_map>
76

87
const std::regex csvFilePattern{"^(.+?),([\\d.]+)(?=,|$)"};
98
const std::regex csvDelimiterPattern{"^(\\w+?):\\s*(.+?)$"};

include/graph_manager.h

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#pragma once
2+
3+
#include <iostream>
4+
#include <string>
5+
#include <vector>
6+
#include <utility>
7+
8+
struct GraphPlotInfo{
9+
std::string xLabel;
10+
std::string yLabel;
11+
std::vector<std::pair<double, double>> values;
12+
};
13+
14+
15+
class DotGraph{
16+
public:
17+
int width, height;
18+
std::string xLabel;
19+
std::string yLabel;
20+
std::vector<std::pair<double, double>> values;
21+
std::string drawGraph();
22+
DotGraph(std::string filename, int width = 20, int height = 10);
23+
};

include/visualizer.h

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#pragma once
2+
3+
#include <iostream>
4+
#include <string>
5+
6+
enum class VisualizerTypes{
7+
HorizontalBarChart,
8+
VerticalBarChart,
9+
DotGraph,
10+
};
11+
12+
std::string getVisualizerAscii(int argc, char** argv);

src/data_utilities.cpp

+37
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include <iostream>
22
#include <string>
33
#include <regex>
4+
#include <algorithm>
5+
#include <utility>
46
#include "data_utilities.h"
57

68
std::string doubleToStringPrecision(double value)
@@ -15,3 +17,38 @@ std::string doubleToStringPrecision(double value)
1517
}
1618
return valueString;
1719
}
20+
21+
std::string strToLower(std::string str)
22+
{
23+
std::string lowerString = "";
24+
for (char c : str)
25+
lowerString += (char)std::tolower(c);
26+
return lowerString;
27+
}
28+
29+
std::pair<double, double> findMinMaxDoubleInVector(std::vector<double> target)
30+
{
31+
double minValue = *std::min_element(target.begin(), target.end());
32+
double maxValue = *std::max_element(target.begin(), target.end());
33+
auto result = std::make_pair(minValue, maxValue);
34+
return result;
35+
}
36+
37+
std::vector<double> sortDoubleVector(std::vector<double> _vector)
38+
{
39+
std::sort(_vector.begin(), _vector.end());
40+
return _vector;
41+
}
42+
43+
std::pair<std::vector<double>, std::vector<double>> extractVectorsFromPair(
44+
std::vector<std::pair<double, double>> target)
45+
{
46+
std::vector<double> firstValues;
47+
std::vector<double> secondValues;
48+
for (auto i : target){
49+
firstValues.push_back(i.first);
50+
secondValues.push_back(i.second);
51+
}
52+
auto myPair = std::make_pair(firstValues, secondValues);
53+
return myPair;
54+
}

src/file_reader.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#include <filesystem>
44
#include <fstream>
55
#include <regex>
6-
#include <unordered_map>
76
#include "file_reader.h"
87
#include "chart_manager.h"
98

src/graph_manager.cpp

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#include <iostream>
2+
#include <string>
3+
#include <vector>
4+
#include <regex>
5+
#include <utility>
6+
#include <algorithm>
7+
#include <unordered_map>
8+
#include <numeric>
9+
#include "graph_manager.h"
10+
#include "file_reader.h"
11+
#include "data_utilities.h"
12+
13+
const std::regex graphAxisRePattern{"^(\\d+(?:\\.\\d+)?),(\\d+(?:\\.\\d+)?)", std::regex_constants::icase};
14+
15+
GraphPlotInfo getGraphInfoFromFile(std::string filename);
16+
std::vector<std::string> getDotGraphLineStrings(int width, int height, GraphPlotInfo);
17+
std::string getDotGraphXAxisTicks(int width, std::vector<double> xValues);
18+
19+
DotGraph::DotGraph(std::string filename, int width, int height)
20+
{
21+
if (width < 20) width = 20;
22+
if (height < 10) height = 10;
23+
GraphPlotInfo info = getGraphInfoFromFile(filename);
24+
this->xLabel = info.xLabel;
25+
this->yLabel = info.yLabel;
26+
for (auto i : info.values)
27+
this->values.push_back(i);
28+
this->width = width;
29+
this->height = height;
30+
}
31+
32+
std::string DotGraph::drawGraph()
33+
{
34+
std::string graph = "";
35+
graph = yLabel + "\n";
36+
GraphPlotInfo plotInfo{
37+
.xLabel = xLabel, .yLabel = yLabel, .values = values
38+
};
39+
std::vector<std::string> graphLines = getDotGraphLineStrings(
40+
width, height, plotInfo
41+
);
42+
43+
for (auto i : graphLines){
44+
graph += i + "\n";
45+
}
46+
int xLabelPaddingCount = width / 2 - xLabel.length() / 2;
47+
graph += "\n" + std::string(xLabelPaddingCount, ' ') + xLabel;
48+
return graph;
49+
}
50+
51+
GraphPlotInfo getGraphInfoFromFile(std::string filename)
52+
{
53+
GraphPlotInfo graphInfo{};
54+
std::vector<std::string> fileLines = getFileLines(filename);
55+
std::smatch reMatch;
56+
for (auto line : fileLines)
57+
{
58+
if (std::regex_search(line, reMatch, graphAxisRePattern)){
59+
double xValue = std::stod(reMatch[1].str());
60+
double yValue = std::stod(reMatch[2].str());
61+
std::pair<double, double> valuePair = std::make_pair(xValue, yValue);
62+
graphInfo.values.push_back(valuePair);
63+
}
64+
else if (std::regex_search(line, reMatch, csvDelimiterPattern)){
65+
bool isXLabel = strToLower(reMatch[1].str()) == "x";
66+
bool isYLabel = strToLower(reMatch[1].str()) == "y";
67+
if (isXLabel)
68+
graphInfo.xLabel = reMatch[2].str();
69+
else if (isYLabel)
70+
graphInfo.yLabel = reMatch[2].str();
71+
}
72+
}
73+
return graphInfo;
74+
}
75+
76+
std::vector<std::string> getDotGraphLineStrings(
77+
int width, int height, GraphPlotInfo graphInfo)
78+
{
79+
std::vector<std::string> dotGraphLines{};
80+
auto xyValues = extractVectorsFromPair(graphInfo.values);
81+
82+
double lowestXValue = findMinMaxDoubleInVector(xyValues.first).first;
83+
double greatestXValue = findMinMaxDoubleInVector(xyValues.first).second;
84+
double lowestYValue = findMinMaxDoubleInVector(xyValues.second).first;
85+
double greatestYValue = findMinMaxDoubleInVector(xyValues.second).second;
86+
// Length of greatest `y` axis value when converted to a string.
87+
int maxYValueStrLength = std::to_string((int)greatestYValue).size();
88+
89+
// Computes the (x, y) coordinates from a given value.
90+
auto getValueCoordinates = [=](std::pair<double, double> value){
91+
int xCoordinate = value.first / greatestXValue * width;
92+
int yCoordinate = value.second / greatestYValue * height;
93+
return std::make_pair(xCoordinate, yCoordinate);
94+
};
95+
96+
// Determining where each data point will appear on our graph.
97+
std::vector<std::pair<int, int>> plotAppearances{};
98+
for (auto i : graphInfo.values){
99+
auto valuePair = std::make_pair(i.first, i.second);
100+
auto coordinates = getValueCoordinates(valuePair);
101+
plotAppearances.push_back(coordinates);
102+
}
103+
104+
// Create an unordered map to store line numbers as keys and their tick labels as values.
105+
std::unordered_map<int, int> yAxisTicks{};
106+
for (int lineNumber = 0; lineNumber <= height; ++lineNumber){
107+
int valueAtLine = (greatestYValue - lowestYValue) / height * lineNumber;
108+
bool tickExists = false;
109+
for (auto it : yAxisTicks)
110+
if (it.second == valueAtLine){
111+
tickExists = 1;
112+
break;
113+
}
114+
if (!tickExists)
115+
yAxisTicks.insert({lineNumber, valueAtLine});
116+
}
117+
118+
// Initialize the beginning of a string to the dot graph.
119+
auto initLineString = [&](int line){
120+
std::string result = "";
121+
if (!yAxisTicks.count(line))
122+
return result + std::string(maxYValueStrLength, ' ') + " |";
123+
int value = yAxisTicks.at(line);
124+
int valueStrLength = std::to_string(value).size();
125+
std::string padding(maxYValueStrLength - valueStrLength, ' ');
126+
result += std::to_string(value) + padding + " |";
127+
return result;
128+
};
129+
130+
for (int lineNumber = height; lineNumber > 0; --lineNumber)
131+
{
132+
std::string currentLineString = initLineString(lineNumber);
133+
for (int columnNumber = 0; columnNumber <= width; ++columnNumber)
134+
{
135+
auto coordinates = std::make_pair(columnNumber, lineNumber);
136+
if (std::find(plotAppearances.begin(), plotAppearances.end(), coordinates) != plotAppearances.end())
137+
currentLineString += "*";
138+
else
139+
currentLineString += " ";
140+
}
141+
dotGraphLines.push_back(currentLineString);
142+
}
143+
dotGraphLines.push_back(std::string(maxYValueStrLength + 2, ' ') + std::string(width, '-'));
144+
std::string xTicksString(maxYValueStrLength + 2, ' ');
145+
xTicksString += getDotGraphXAxisTicks(width, xyValues.first);
146+
dotGraphLines.push_back(xTicksString);
147+
return dotGraphLines;
148+
}
149+
150+
std::string getDotGraphXAxisTicks(int width, std::vector<double> xValues)
151+
{
152+
std::string result = "";
153+
std::sort(xValues.begin(), xValues.end());
154+
std::pair<double, double> minMaxValue = findMinMaxDoubleInVector(xValues);
155+
int minSpacesPerTick = width / xValues.size() + static_cast<int>(width / xValues.size() == 0);
156+
157+
auto getColumnValue = [minMaxValue, width](int column){
158+
double range = minMaxValue.second - minMaxValue.first;
159+
int value = range / width * column;
160+
return value;
161+
};
162+
163+
std::vector<int> usedTickValues{};
164+
for (int columnIndex = 0; columnIndex < width; ++columnIndex)
165+
{
166+
int tickValue = getColumnValue(columnIndex);
167+
168+
// Check if there is enough padding to display `x` axis tick labels.
169+
bool isPadded = false;
170+
if (result.find_last_not_of(' ') != result.size() - 1 || columnIndex == 0)
171+
isPadded = result.find_last_of(' ') - result.find_last_not_of(' ') >= minSpacesPerTick || columnIndex == 0;
172+
173+
// Add tick label if unused and padding has enough spaces.
174+
if (!vectorContains(usedTickValues, tickValue) && isPadded){
175+
usedTickValues.push_back(tickValue);
176+
result += std::to_string(tickValue);
177+
}
178+
else {
179+
result += " ";
180+
}
181+
if (result.size() >= width)
182+
break;
183+
}
184+
return result;
185+
}

0 commit comments

Comments
 (0)