// Teal Dulcet, CS546 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace graphs { using namespace std; enum style_type { style_ASCII, style_basic, style_light, style_heavy, style_double, style_arc, style_light_dashed, style_heavy_dashed }; enum style_type const style_types[] = {style_ASCII, style_basic, style_light, style_heavy, style_double, style_arc, style_light_dashed, style_heavy_dashed}; const char *const styles[][11] = { {"-", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+"}, // ASCII {"—", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+"}, // Basic {"─", "│", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"}, // Light {"━", "┃", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"}, // Heavy {"═", "║", "╔", "╦", "╗", "╠", "╬", "╣", "╚", "╩", "╝"}, // Double {"─", "│", "╭", "┬", "╮", "├", "┼", "┤", "╰", "┴", "╯"}, // Light Arc {"╌", "┊", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘"}, // Light Dashed {"╍", "┋", "┏", "┳", "┓", "┣", "╋", "┫", "┗", "┻", "┛"} // Heavy Dashed // {" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "} // No border }; enum color_type { color_default, color_black, color_red, color_green, color_yellow, color_blue, color_magenta, color_cyan, color_light_gray, color_dark_gray, color_light_red, color_light_green, color_light_yellow, color_light_blue, color_light_magenta, color_light_cyan, color_white }; enum color_type const color_types[] = {color_default, color_black, color_red, color_green, color_yellow, color_blue, color_magenta, color_cyan, color_light_gray, color_dark_gray, color_light_red, color_light_green, color_light_yellow, color_light_blue, color_light_magenta, color_light_cyan, color_white}; const char *const colors[] = {"\e[39m", "\e[30m", "\e[31m", "\e[32m", "\e[33m", "\e[34m", "\e[35m", "\e[36m", "\e[37m", "\e[90m", "\e[91m", "\e[92m", "\e[93m", "\e[94m", "\e[95m", "\e[96m", "\e[97m"}; const char *const dots[] = {"⠀", "⠁", "⠂", "⠃", "⠄", "⠅", "⠆", "⠇", "⠈", "⠉", "⠊", "⠋", "⠌", "⠍", "⠎", "⠏", "⠐", "⠑", "⠒", "⠓", "⠔", "⠕", "⠖", "⠗", "⠘", "⠙", "⠚", "⠛", "⠜", "⠝", "⠞", "⠟", "⠠", "⠡", "⠢", "⠣", "⠤", "⠥", "⠦", "⠧", "⠨", "⠩", "⠪", "⠫", "⠬", "⠭", "⠮", "⠯", "⠰", "⠱", "⠲", "⠳", "⠴", "⠵", "⠶", "⠷", "⠸", "⠹", "⠺", "⠻", "⠼", "⠽", "⠾", "⠿", "⡀", "⡁", "⡂", "⡃", "⡄", "⡅", "⡆", "⡇", "⡈", "⡉", "⡊", "⡋", "⡌", "⡍", "⡎", "⡏", "⡐", "⡑", "⡒", "⡓", "⡔", "⡕", "⡖", "⡗", "⡘", "⡙", "⡚", "⡛", "⡜", "⡝", "⡞", "⡟", "⡠", "⡡", "⡢", "⡣", "⡤", "⡥", "⡦", "⡧", "⡨", "⡩", "⡪", "⡫", "⡬", "⡭", "⡮", "⡯", "⡰", "⡱", "⡲", "⡳", "⡴", "⡵", "⡶", "⡷", "⡸", "⡹", "⡺", "⡻", "⡼", "⡽", "⡾", "⡿", "⢀", "⢁", "⢂", "⢃", "⢄", "⢅", "⢆", "⢇", "⢈", "⢉", "⢊", "⢋", "⢌", "⢍", "⢎", "⢏", "⢐", "⢑", "⢒", "⢓", "⢔", "⢕", "⢖", "⢗", "⢘", "⢙", "⢚", "⢛", "⢜", "⢝", "⢞", "⢟", "⢠", "⢡", "⢢", "⢣", "⢤", "⢥", "⢦", "⢧", "⢨", "⢩", "⢪", "⢫", "⢬", "⢭", "⢮", "⢯", "⢰", "⢱", "⢲", "⢳", "⢴", "⢵", "⢶", "⢷", "⢸", "⢹", "⢺", "⢻", "⢼", "⢽", "⢾", "⢿", "⣀", "⣁", "⣂", "⣃", "⣄", "⣅", "⣆", "⣇", "⣈", "⣉", "⣊", "⣋", "⣌", "⣍", "⣎", "⣏", "⣐", "⣑", "⣒", "⣓", "⣔", "⣕", "⣖", "⣗", "⣘", "⣙", "⣚", "⣛", "⣜", "⣝", "⣞", "⣟", "⣠", "⣡", "⣢", "⣣", "⣤", "⣥", "⣦", "⣧", "⣨", "⣩", "⣪", "⣫", "⣬", "⣭", "⣮", "⣯", "⣰", "⣱", "⣲", "⣳", "⣴", "⣵", "⣶", "⣷", "⣸", "⣹", "⣺", "⣻", "⣼", "⣽", "⣾", "⣿"}; const int dotvalues[][4] = {{0x1, 0x2, 0x4, 0x40}, {0x8, 0x10, 0x20, 0x80}}; const char *const blocks[] = {" ", "▖", "▗", "▄", "▘", "▌", "▚", "▙", "▝", "▞", "▐", "▟", "▀", "▛", "▜", "█"}; const int blockvalues[][2] = {{4, 1}, {8, 2}}; const char *const bars[] = {" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"}; enum type_type { type_braille, type_block, type_histogram // Set automatically by the histogram() function }; enum type_type const type_types[] = {type_braille, type_block /* , type_histogram */}; enum plot_type { plot_scatter, plot_line }; enum plot_type const plot_types[] = {plot_scatter, plot_line}; const short marks[][8][2] = {{{0, 0}}, {{0, 1}, {-1, 0}, {0, 0}, {1, 0}, {0, -1}}, {{-1, 1}, {0, 1}, {1, 1}, {-1, 0}, {1, 0}, {-1, -1}, {0, -1}, {1, -1}}}; enum mark_type { mark_dot, mark_plus, mark_square }; enum mark_type const mark_types[] = {mark_dot, mark_plus, mark_square}; enum graph_type { graph_dot, graph_shade_above, graph_shade_below }; enum graph_type const graph_types[] = {graph_dot, graph_shade_above, graph_shade_below}; const char *const fractions[] = {"¼", "½", "¾", "⅐", "⅑", "⅒", "⅓", "⅔", "⅕", "⅖", "⅗", "⅘", "⅙", "⅚", "⅛", "⅜", "⅝", "⅞"}; const long double fractionvalues[] = {1.0L / 4.0L, 1.0L / 2.0L, 3.0L / 4.0L, 1.0L / 7.0L, 1.0L / 9.0L, 1.0L / 10.0L, 1.0L / 3.0L, 2.0L / 3.0L, 1.0L / 5.0L, 2.0L / 5.0L, 3.0L / 5.0L, 4.0L / 5.0L, 1.0L / 6.0L, 5.0L / 6.0L, 1.0L / 8.0L, 3.0L / 8.0L, 5.0L / 8.0L, 7.0L / 8.0L}; const char *const constants[] = {"π", "e"}; const long double constantvalues[] = {M_PI, M_E}; enum units_type { units_number, units_scale_none, units_scale_SI, units_scale_IEC, units_scale_IEC_I, units_fracts, units_percent, units_date, units_time, units_monetary }; enum units_type const units_types[] = {units_number, units_scale_SI, units_scale_IEC, units_scale_IEC_I, units_fracts, units_percent, units_date, units_time, units_monetary}; const char *const suffix_power_char[] = {"", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"}; const long double max_bit = scalbn(1.0L, LDBL_MANT_DIG - 1); const long double MAX = max_bit + (max_bit - 1); struct options { bool border = false; bool axis = true; bool axislabel = true; bool axistick = true; bool axisunitslabel = true; units_type xunits = units_fracts; units_type yunits = units_fracts; type_type type = type_braille; mark_type mark = mark_dot; const char *title = nullptr; style_type style = style_light; color_type color = color_red; bool check = true; }; const options defaultoptions; options histogram_defaultoptions; template constexpr size_t size(const T &array) { return distance(begin(array), end(array)); } // Number of columns needed to represent the string // Adapted from: https://stackoverflow.com/a/31124065 int strcol(const char *const str) { size_t length = strlen(str); for (size_t i = 0; i < length; ++i) if (iscntrl(str[i])) { cerr << "\nError! Control character in string.\n"; cout << "Control character: " << (int)str[i] << '\n'; } length = mbstowcs(nullptr, str, 0); if (length == static_cast(-1)) { cerr << "\nError! mbstowcs failed. Invalid multibyte character.\n"; exit(1); } ++length; auto *wcstring = new wchar_t[length]; if (mbstowcs(wcstring, str, length) == static_cast(-1)) { if (wcstring) delete[] wcstring; cerr << "\nError! mbstowcs failed. Invalid multibyte character.\n"; exit(1); } int width = wcswidth(wcstring, length); if (width == -1) { cerr << "\nError! wcswidth failed. Nonprintable wide character.\n"; exit(1); } if (wcstring) delete[] wcstring; return width; } // Word wrap // Source: https://gist.github.com/tdulcet/819821ca69501822ad3f84a060c640a0 // Adapted from: https://stackoverflow.com/a/42016346 and https://stackoverflow.com/a/13094734 string wrap(const char *const str, const size_t line_length) { string words = str; string wrapped; size_t index = 0; size_t linelen = 0; while (words[index] != '\0') { if (words[index] == '\n') { linelen = 0; } else if (isspace(words[index])) { size_t tempindex = index + 1; size_t templinelen = linelen; while (!isspace(words[tempindex]) and words[tempindex] != '\0') { ++templinelen; ++tempindex; } char temp[templinelen + 1]; strncpy(temp, words.data() + (index - linelen), templinelen); temp[templinelen] = '\0'; size_t width = strcol(temp); if (width >= line_length) { words[index] = '\n'; linelen = 0; } } if (words[index] == '\t') linelen += 8 - (linelen % 8); else if (words[index] != '\n') ++linelen; ++index; } wrapped = words; return wrapped; } // Auto-scale number to unit // Adapted from: https://github.com/coreutils/coreutils/blob/master/src/numfmt.c void outputunit(long double number, const units_type scale, ostringstream &strm) { unsigned int x = 0; long double val = number; if (val >= -LDBL_MAX and val <= LDBL_MAX) { while (abs(val) >= 10) { ++x; val /= 10; } } if (scale == units_scale_none) { if (x > LDBL_DIG) return; strm << setprecision(LDBL_DIG) << number; return; } if (x > 33 - 1) return; double scale_base; switch (scale) { case units_scale_IEC: case units_scale_IEC_I: scale_base = 1024; break; case units_scale_none: case units_scale_SI: default: scale_base = 1000; break; } unsigned int power = 0; if (number >= -LDBL_MAX and number <= LDBL_MAX) { while (abs(number) >= scale_base) { ++power; number /= scale_base; } } long double anumber = abs(number); anumber += anumber < 10 ? 0.0005 : anumber < 100 ? 0.005 : anumber < 1000 ? 0.05 : 0.5; if (number != 0 and anumber < 1000 and power > 0) { strm << setprecision(LDBL_DIG) << number; string str = strm.str(); const unsigned int length = 5 + (number < 0 ? 1 : 0); if (str.length() > length) { const int prec = anumber < 10 ? 3 : anumber < 100 ? 2 : 1; strm.str(""); strm << setprecision(prec) << fixed << number; } } else { strm << setprecision(0) << fixed << number; } strm << (power < graphs::size(suffix_power_char) ? suffix_power_char[power] : "(error)"); if (scale == units_scale_IEC_I and power > 0) strm << "i"; } // Convert fractions and constants to Unicode characters void outputfraction(const long double number, ostringstream &strm) { bool output = false; const long double n = abs(number); if (n <= MAX) { long double intpart = 0; long double fractionpart = abs(modf(number, &intpart)); for (size_t i = 0; i < graphs::size(fractions) and !output; ++i) { if (abs(fractionpart - fractionvalues[i]) <= DBL_EPSILON * n) { if (intpart == 0 and number < 0) strm << "-"; else if (intpart != 0) strm << setprecision(LDBL_DIG) << intpart; strm << fractions[i]; output = true; } } if (n > DBL_EPSILON) { for (size_t i = 0; i < graphs::size(constants) and !output; ++i) { if (abs(fmod(number, constantvalues[i])) <= DBL_EPSILON * n) { intpart = number / constantvalues[i]; if (intpart == -1) strm << "-"; else if (intpart != 1) strm << setprecision(LDBL_DIG) << intpart; strm << constants[i]; output = true; } } } } if (!output) strm << number; } size_t outputlabel(const long double label, const units_type units, ostringstream &strm) { strm.imbue(locale("")); switch (units) { case units_number: strm << label; break; case units_scale_none: case units_scale_SI: case units_scale_IEC: case units_scale_IEC_I: outputunit(label, units, strm); break; case units_fracts: outputfraction(label, strm); break; case units_percent: strm << label * 100 << '%'; break; case units_date: { // const time_t t = chrono::system_clock::to_time_t(chrono::sys_seconds(chrono::duration_cast(chrono::duration(label)))); const time_t t = chrono::system_clock::to_time_t(chrono::system_clock::time_point(chrono::duration_cast(chrono::duration(label)))); const tm atm = *localtime(&t); strm << put_time(&atm, "%x"); break; } case units_time: { // const time_t t = chrono::system_clock::to_time_t(chrono::sys_seconds(chrono::duration_cast(chrono::duration(label)))); const time_t t = chrono::system_clock::to_time_t(chrono::system_clock::time_point(chrono::duration_cast(chrono::duration(label)))); const tm atm = *localtime(&t); strm << put_time(&atm, "%X"); break; } case units_monetary: strm << showbase << put_money(label); break; } size_t length = strcol(strm.str().c_str()); return length; } // Output graph int graph(const size_t height, const size_t width, const long double xmin, const long double xmax, const long double ymin, const long double ymax, const vector> &array, const options &aoptions) { if (!graphs::size(array)) return 1; const bool border = aoptions.border; const bool axis = aoptions.axis; const bool axislabel = aoptions.axislabel; const bool axistick = aoptions.axistick; const bool axisunitslabel = aoptions.axisunitslabel; const type_type type = aoptions.type; const char *const title = aoptions.title; const style_type style = aoptions.style; if (height == 0) return 1; if (width == 0) return 1; struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); const size_t ai = type == type_histogram ? 8 : type == type_block ? 2 : 4; const size_t aj = type == type_histogram ? 1 : 2; const size_t aheight = height / ai; const size_t awidth = width / aj; if (aoptions.check) { if (aheight > w.ws_row) { cerr << "The height of the graph (" << aheight << ") is greater then the height of the terminal (" << w.ws_row << ").\n"; return 1; } if (awidth > w.ws_col) { cerr << "The width of the graph (" << awidth << ") is greater then the width of the terminal (" << w.ws_col << ").\n"; return 1; } } if (xmin >= xmax) { cerr << "xmin must be less than xmax.\n"; return 1; } if (ymin >= ymax) { cerr << "ymin must be less than ymax.\n"; return 1; } const long double xstep = (xmax - xmin) / width; const long double ystep = (ymax - ymin) / height; const long double xaxis = xmin > 0 ? 0 : xmax < 0 ? width : width - (xmax / xstep); const long double yaxis = ymin > 0 ? height : ymax < 0 ? 0 : ymax / ystep; const int xdivisor = 4 * aj * ((((2 * width) / aj) / 160) + 2); const int ydivisor = 2 * ai * ((((4 * height) / ai) / 160) + 2); setlocale(LC_ALL, ""); if (title and title[0] != '\0') cout << wrap(title, awidth) << '\n'; if (border) { cout << styles[style][2]; for (size_t k = 0; k < awidth; ++k) cout << styles[style][0]; cout << styles[style][4] << '\n'; } for (size_t i = 0; i < height; i += ai) { const bool ayaxis = yaxis <= (height - ai) ? i <= yaxis and (i + ai) > yaxis : i < yaxis and (i + ai) >= yaxis; const bool yaxislabel = yaxis <= (height - ai) ? i <= (yaxis + ai) and (i + ai) > (yaxis + ai) : i < (yaxis - ai) and (i + ai) >= (yaxis - ai); ostringstream ylabelstrm; size_t ylabellength = 0; if (axis and axistick and axisunitslabel and yaxis >= 0 and yaxis <= height) { bool output = false; long double label = 0; int adivisor = i < yaxis ? -ydivisor : ydivisor; for (long double k = yaxis + adivisor; (i < yaxis ? k >= i : k < (i + ai)) and i >= ai and !output; k += adivisor) { if (i <= k and (i + ai) > k) { label = ymax - ((k > height ? height : k) * ystep); output = true; } } if (output) { ylabellength = outputlabel(label, aoptions.yunits, ylabelstrm); ylabellength *= aj; } } if (border) cout << styles[style][1]; for (size_t j = 0; j < width; j += aj) { const bool axaxis = xaxis >= aj ? j < xaxis and (j + aj) >= xaxis : j <= xaxis and (j + aj) > xaxis; const bool xaxislabel = xaxis >= aj ? j < (xaxis - aj) and (j + aj) >= (xaxis - aj) : j <= (xaxis + aj) and (j + aj) > (xaxis + aj); bool output = false; if (axis) { if (axaxis and ayaxis) { cout << styles[style][6]; output = true; } else if (axaxis) { if (i == 0) { cout << styles[style][4]; output = true; } else if (i >= (height - ai)) { cout << styles[style][10]; output = true; } else if (axistick) { int adivisor = i < yaxis ? -ydivisor : ydivisor; for (long double k = yaxis + adivisor; (i < yaxis ? k >= i : k < (i + ai)) and i >= ai and !output; k += adivisor) { if (i <= k and (i + ai) > k) { cout << styles[style][xaxis >= aj ? 7 : 5]; output = true; } } } if (!output) { cout << styles[style][1]; output = true; } } else if (ayaxis) { if (j == 0) { cout << styles[style][2]; output = true; } else if (j >= (width - aj)) { cout << styles[style][4]; output = true; } else if (axistick) { int adivisor = j < xaxis ? -xdivisor : xdivisor; for (long double k = xaxis + adivisor; (j < xaxis ? k >= j : k < (j + aj)) and j < (width - (aj * 2)) and !output; k += adivisor) { if (j <= k and (j + aj) > k) { cout << styles[style][yaxis <= (height - ai) ? 3 : 9]; output = true; } } } if (!output) { cout << styles[style][0]; output = true; } } else if (yaxislabel and xaxislabel and axistick and axisunitslabel and ymin <= 0 and ymax >= 0 and xmin <= 0 and xmax >= 0) { cout << "0"; output = true; } else if ((xaxis <= (width - aj) ? j >= (width - aj) : j == 0) and yaxislabel and axislabel) { cout << "x"; output = true; } else if (yaxislabel and axistick and axisunitslabel) { long double label = 0; int adivisor = j < xaxis ? -xdivisor : xdivisor; if (j < xaxis) j += aj; for (long double k = xaxis + adivisor; (j < xaxis ? k >= j : k < (j + aj)) and j < (width - aj) and !output; k += adivisor) { if (j <= k and (j + aj) > k) { label = ((k > width ? width : k) * xstep) + xmin; output = true; } } if (adivisor < 0) j -= aj; if (output) { output = false; ostringstream strm; size_t length = outputlabel(label, aoptions.xunits, strm); length *= aj; if ((j >= xaxis or (j + length) < (ymin <= 0 and ymax >= 0 and xmin <= 0 and xmax >= 0 ? xaxis - ai : xaxis)) and (j + length) < (width - aj) and (xaxis <= (width - aj) or j > aj)) { cout << strm.str(); if (length > aj) j += length - aj; if (adivisor < 0) output = true; else j += aj; } } } else if ((yaxis >= ai ? i == 0 : i >= (height - ai)) and xaxislabel and axislabel) { cout << "y"; output = true; } else if (ylabellength and (xaxis < aj ? xaxislabel : j < (xaxis - ylabellength) and (j + aj) >= (xaxis - ylabellength)) and (yaxis >= ai or i < (height - ai)) and axistick and axisunitslabel) { cout << ylabelstrm.str(); output = true; if (ylabellength > aj) j += ylabellength - aj; } } if (!output) { size_t dot = 0; unsigned short color = 0; for (size_t k = 0; k < aj and k < (width - j); ++k) { for (size_t l = 0; l < ai and l < (height - i); ++l) { const unsigned short value = array[j + k][i + l]; if (value) { if (type == type_histogram) { if (!dot) dot = (graphs::size(bars) - l) - 1; } else if (type == type_block) dot += blockvalues[k][l]; else dot += dotvalues[k][l]; } if (color) { if (value and color != value) color = 1; } else color = value; } } if (color) --color; if (color) cout << colors[color]; cout << (type == type_histogram ? bars[dot] : type == type_block ? blocks[dot] : dots[dot]); if (color) cout << colors[0]; } } if (border) cout << styles[style][1]; if (i < (height - ai) or border) cout << '\n'; } if (border) { cout << styles[style][8]; for (size_t k = 0; k < awidth; ++k) cout << styles[style][0]; cout << styles[style][10]; } cout << endl; return 0; } template int histogram(size_t height, size_t width, long double xmin, long double xmax, long double ymin, long double ymax, const T &aarray, /* const */ options &aoptions = histogram_defaultoptions) { if (!graphs::size(aarray)) return 1; const color_type color = aoptions.color; struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); if (height == 0) height = w.ws_row * 4; if (width == 0) width = w.ws_col * 2; if (aoptions.check) { const size_t aheight = height / 4; const size_t awidth = width / 2; if (aheight > w.ws_row) { cerr << "The height of the graph (" << aheight << ") is greater then the height of the terminal (" << w.ws_row << ").\n"; return 1; } if (awidth > w.ws_col) { cerr << "The width of the graph (" << awidth << ") is greater then the width of the terminal (" << w.ws_col << ").\n"; return 1; } } height *= 2; width /= 2; if (xmin == 0 and xmax == 0) { const auto &minmax = minmax_element(begin(aarray), end(aarray)); xmin = *minmax.first; xmax = *minmax.second; } if (xmin >= xmax) { cerr << "xmin must be less than xmax.\n"; return 1; } vector histogram(width, 0); const long double xstep = (xmax - xmin) / width; for (const auto &x : aarray) { if (x >= xmin and x < xmax) { const size_t index = (x - xmin) / xstep; ++histogram[index]; } } if (ymin == 0 and ymax == 0) { const auto &minmax = minmax_element(histogram.begin(), histogram.end()); ymin = *minmax.first; ymax = *minmax.second; } if (ymin >= ymax) { cerr << "ymin must be less than ymax.\n"; return 1; } const long double ystep = (ymax - ymin) / height; const long double yaxis = ymax / ystep; vector> aaarray(width, vector(height, 0)); const unsigned int acolor = color + 1; for (size_t x = 0; x < graphs::size(histogram); ++x) { const size_t ay = histogram[x]; for (size_t y = ay >= ymax ? 0 : yaxis - (ay / ystep); y < yaxis and y < height; ++y) aaarray[x][y] = acolor; } aoptions.type = type_histogram; return graph(height, width, xmin, xmax, ymin, ymax, aaarray, aoptions); } template int histogram(size_t height, size_t width, long double xmin, long double xmax, long double ymin, long double ymax, const size_t rows, T *aarray, /* const */ options &aoptions = histogram_defaultoptions) { if (rows == 0) return 1; vector aaarray(rows); copy(aarray, aarray + rows, aaarray.begin()); return histogram(height, width, xmin, xmax, ymin, ymax, aaarray, aoptions); } // Convert one or more arrays to graph and output template int plots(size_t height, size_t width, long double xmin, long double xmax, long double ymin, long double ymax, const T &arrays, const options &aoptions = defaultoptions) { if (!graphs::size(arrays)) return 1; if (!all_of(begin(arrays), end(arrays), [](const auto &array) { return all_of(begin(array), end(array), [](const auto &x) { return graphs::size(x) == 2; }); })) { cerr << "Error: The arrays must have two columns.\n"; return 1; } const color_type color = aoptions.color; struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); if (height == 0) height = w.ws_row * 4; if (width == 0) width = w.ws_col * 2; if (aoptions.check) { const size_t aheight = height / 4; const size_t awidth = width / 2; if (aheight > w.ws_row) { cerr << "The height of the graph (" << aheight << ") is greater then the height of the terminal (" << w.ws_row << ").\n"; return 1; } if (awidth > w.ws_col) { cerr << "The width of the graph (" << awidth << ") is greater then the width of the terminal (" << w.ws_col << ").\n"; return 1; } } if (aoptions.type == type_block) height /= 2; if (xmin == 0 and xmax == 0) { const auto compare = [](const auto &a, const auto &b) { return a[0] < b[0]; }; const auto &result = accumulate(begin(arrays), end(arrays), make_pair(arrays[0][0], arrays[0][0]), [compare](const auto ¤t, const auto &array) { const auto &minmax = minmax_element(begin(array), end(array), compare); return make_pair(min(current.first, *minmax.first, compare), max(current.second, *minmax.second, compare)); }); xmin = result.first[0]; xmax = result.second[0]; } if (ymin == 0 and ymax == 0) { const auto compare = [](const auto &a, const auto &b) { return a[1] < b[1]; }; const auto &result = accumulate(begin(arrays), end(arrays), make_pair(arrays[0][0], arrays[0][0]), [compare](const auto ¤t, const auto &array) { const auto &minmax = minmax_element(begin(array), end(array), compare); return make_pair(min(current.first, *minmax.first, compare), max(current.second, *minmax.second, compare)); }); ymin = result.first[1]; ymax = result.second[1]; } if (xmin >= xmax) { cerr << "xmin must be less than xmax.\n"; return 1; } if (ymin >= ymax) { cerr << "ymin must be less than ymax.\n"; return 1; } const long double xstep = (xmax - xmin) / width; const long double ystep = (ymax - ymin) / height; const long double xaxis = width - (xmax / xstep); const long double yaxis = ymax / ystep; vector> aarray(width, vector(height, 0)); for (size_t j = 0; j < graphs::size(arrays); ++j) { const auto &array = arrays[j]; const unsigned int acolor = graphs::size(arrays) == 1 ? color + 1 : (j % (graphs::size(colors) - 2)) + 3; for (size_t i = 0; i < graphs::size(array); ++i) { const auto &x = array[i][0], &y = array[i][1]; if (x >= xmin and x < xmax and y >= ymin and y < ymax) { const size_t ax = (x / xstep) + xaxis; const size_t ay = (yaxis - (y / ystep)) - 1; for (const auto &mark : marks[aoptions.mark]) { const size_t x = ax + mark[0]; const size_t y = ay + mark[1]; if (x < width and y < height) { if (aarray[x][y]) { if (aarray[x][y] != acolor) aarray[x][y] = 1; } else aarray[x][y] = acolor; } } } } } return graph(height, width, xmin, xmax, ymin, ymax, aarray, aoptions); } // Convert single array to graph and output template int plot(size_t height, size_t width, long double xmin, long double xmax, long double ymin, long double ymax, const T &aarray, const options &aoptions = defaultoptions) { const std::array aaarray = {aarray}; return plots(height, width, xmin, xmax, ymin, ymax, aaarray, aoptions); } // Convert single array to graph and output template int plot(size_t height, size_t width, long double xmin, long double xmax, long double ymin, long double ymax, const size_t rows, T **aarray, const options &aoptions = defaultoptions) { if (rows == 0) return 1; const size_t columns = 2; vector> aaarray(rows); for (size_t i = 0; i < rows; ++i) copy(aarray[i], aarray[i] + columns, aaarray[i].begin()); return plot(height, width, xmin, xmax, ymin, ymax, aaarray, aoptions); } // Convert one or more functions to graph and output template int functions(size_t height, size_t width, const long double xmin, const long double xmax, const long double ymin, const long double ymax, const size_t numfunctions, function functions[], const options &aoptions = defaultoptions) { const color_type color = aoptions.color; if (numfunctions == 0) return 1; struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); if (height == 0) height = w.ws_row * 4; if (width == 0) width = w.ws_col * 2; if (aoptions.check) { const size_t aheight = height / 4; const size_t awidth = width / 2; if (aheight > w.ws_row) { cerr << "The height of the graph (" << aheight << ") is greater then the height of the terminal (" << w.ws_row << ").\n"; return 1; } if (awidth > w.ws_col) { cerr << "The width of the graph (" << awidth << ") is greater then the width of the terminal (" << w.ws_col << ").\n"; return 1; } } if (aoptions.type == type_block) height /= 2; if (xmin >= xmax) { cerr << "xmin must be less than xmax.\n"; return 1; } if (ymin >= ymax) { cerr << "ymin must be less than ymax.\n"; return 1; } const size_t rows = width; const long double xstep = (xmax - xmin) / width; const long double ystep = (ymax - ymin) / height; const long double xaxis = width - (xmax / xstep); const long double yaxis = ymax / ystep; const size_t xres = 2; vector> array(width, vector(height, 0)); for (size_t j = 0; j < numfunctions; ++j) { unsigned short acolor = numfunctions == 1 ? color + 1 : (j % (graphs::size(colors) - 2)) + 3; for (size_t i = 0; i < rows * xres; ++i) { T x = ((i / (long double)xres) * xstep) + xmin; T y = (functions[j])(x); if (x >= xmin and x < xmax and y >= ymin and y < ymax) { const size_t ax = (x / xstep) + xaxis; const size_t ay = (yaxis - (y / ystep)) - 1; if (array[ax][ay]) { if (array[ax][ay] != acolor) array[ax][ay] = 1; } else array[ax][ay] = acolor; } } } return graph(height, width, xmin, xmax, ymin, ymax, array, aoptions); } // Convert single function to function array and output template int function(size_t height, size_t width, const long double xmin, const long double xmax, const long double ymin, const long double ymax, const function &afunction, const options &aoptions = defaultoptions) { std::function afunctions[] = {afunction}; return functions(height, width, xmin, xmax, ymin, ymax, 1, afunctions, aoptions); } // Convert single function to function array and output template int function(size_t height, size_t width, const long double xmin, const long double xmax, const long double ymin, const long double ymax, T afunction(T), const options &aoptions = defaultoptions) { std::function afunctions[] = {afunction}; return functions(height, width, xmin, xmax, ymin, ymax, 1, afunctions, aoptions); } }