From 435fd94ca0ac5f4e80ee28268eb2b0082cf90b74 Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Mon, 30 Jun 2025 16:02:10 +0800 Subject: [PATCH 01/18] Add graphing functions --- include/colors.hpp | 11 ++ include/conPlot/conPlot.hpp | 140 ++++++++++++++++++++ include/conPlot/conPlotTypes.hpp | 44 +++++++ include/rounding.hpp | 4 +- include/steppable/number.hpp | 58 ++++----- include/symbols.hpp | 159 ++++++++++++++++++++++- include/types/concepts.hpp | 7 + include/types/rounding.hpp | 35 +++++ src/colors.cpp | 22 ++-- src/matrix/ref/ref.cpp | 25 ++-- src/steppable/number.cpp | 10 +- src/symbols.cpp | 212 +++++++++++++++++++++++++++---- 12 files changed, 637 insertions(+), 90 deletions(-) create mode 100644 include/conPlot/conPlot.hpp create mode 100644 include/conPlot/conPlotTypes.hpp create mode 100644 include/types/rounding.hpp diff --git a/include/colors.hpp b/include/colors.hpp index 53203c8f7..de51068b9 100644 --- a/include/colors.hpp +++ b/include/colors.hpp @@ -37,6 +37,7 @@ #pragma once +#include #include /** @@ -61,6 +62,8 @@ namespace steppable::__internals::utils { + using ColorFunc = std::function; + /** * @brief Check if the output stream is a terminal. * @@ -87,6 +90,14 @@ namespace steppable::__internals::utils */ namespace colors { + /** + * @brief Does nothing. + * + * @param[in] stream The output stream to modify. + * @return The modified output stream. + */ + std::ostream& keepOriginal(std::ostream& stream); + /** * @brief Set the text color to black. * diff --git a/include/conPlot/conPlot.hpp b/include/conPlot/conPlot.hpp new file mode 100644 index 000000000..1620090e4 --- /dev/null +++ b/include/conPlot/conPlot.hpp @@ -0,0 +1,140 @@ +#include "colors.hpp" +#include "conPlotTypes.hpp" +#include "steppable/number.hpp" +#include "symbols.hpp" // Adjust the path as needed +#include "types/rounding.hpp" + +#include +#include +#include +#include +#include +#include + +namespace steppable::graphing +{ + void conPlotLine(const Number& xGridSize, + const Number& yGridSize, + const Number& yMax, + const GraphOptions* graphOptions, + const LineOptions* lineOptions, + prettyPrint::ConsoleOutput* canvas, + std::map& fnValues) + { + // Plot function + for (long long i = 0; i < graphOptions->width; ++i) + { + Number x = graphOptions->xMin + Number(i) * xGridSize; + Number y = fnValues[x]; + auto gridY = std::stoll(((yMax - y) / yGridSize).present()); + + // Plot point + canvas->write(lineOptions->lineDot, { .x = i, .y = 3 + gridY }, false, lineOptions->lineColor); + } + } + + void conPlot(const std::vector& f, + const GraphOptions& graphOptions, + const std::vector& lineOptions) + { + // Create buffer + prettyPrint::ConsoleOutput canvas( + graphOptions.height + 10, + graphOptions.width + 12 + + __internals::stringUtils::getUnicodeDisplayWidth(graphOptions.xAxisTitle)); // Extra space for labels + std::map> fnValues; + + canvas.write( + graphOptions.title, { .x = 0, .y = 1 }, false, formats::bold, prettyPrint::HorizontalAlignment::CENTER); + + // Ticks spacing + Number yMin; + for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx) + { + yMin = f[fnIdx](graphOptions.xMin); + fnValues[fnIdx][graphOptions.xMin] = yMin; + } + Number yMax = yMin; + // 1 grid = 1 character on screen + Number xGridSize = (graphOptions.xMax - graphOptions.xMin) / graphOptions.width; + + // Calculate range of values + for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx) + { + for (long long i = 0; i <= graphOptions.width; ++i) + { + Number x = graphOptions.xMin + Number(i) * xGridSize; + Number y = f[fnIdx](x); + fnValues[fnIdx][x] = y; + + yMin = std::min(y, yMin); + yMax = std::max(y, yMax); + + // Write base frame + canvas.write(BoxDrawing::HORIZONTAL, { .x = i, .y = 3 + graphOptions.height }); + } + + if ((yMax - yMin).abs() < 1e-12) + { + yMin -= 1.0; + yMax += 1.0; + } + } + + // Axis positions + Number yGridSize = (yMax - yMin) / graphOptions.height; + Number yTickValue = yMax; + + // Write side frame + for (long long j = 0; j < graphOptions.height; ++j) + { + canvas.write(BoxDrawing::VERTICAL, { .x = graphOptions.width, .y = 3 + j }); + yTickValue -= yGridSize; + + // Write y ticks + if (j % graphOptions.yTickSpacing == 0) + { + yTickValue.setPrec(2); + canvas.write(yTickValue.present(), { .x = graphOptions.width + 2, .y = 3 + j }); + for (long long i = 0; i < graphOptions.width; ++i) + canvas.write(BoxDrawing::DOTTED_HORIZONTAL, { .x = i, .y = 3 + j }); + canvas.write(BoxDrawing::VERTICAL_LEFT, { .x = graphOptions.width, .y = 3 + j }); + } + } + canvas.write(BoxDrawing::BOTTOM_RIGHT_CORNER, { .x = graphOptions.width, .y = 3 + graphOptions.height }); + + // Draw grid + for (long long i = 0; i < graphOptions.width - 2; ++i) + { + Number x = graphOptions.xMin + Number(i) * xGridSize; + // Write grid label + if (i % graphOptions.xTickSpacing == 0) + { + // Write vertical gridlines + for (long long j = 0; j < graphOptions.height; ++j) + canvas.write(BoxDrawing::DOTTED_VERTICAL, { .x = i, .y = 3 + j }); + canvas.write(BoxDrawing::HORIZONTAL_UP, { .x = i, .y = 3 + graphOptions.height }); + x.setPrec(2); + canvas.write(x.present(), { .x = i, .y = 3 + graphOptions.height + 1 }); + } + } + + // Axis Titles + canvas.write( + std::string( + (graphOptions.width - __internals::stringUtils::getUnicodeDisplayWidth(graphOptions.xAxisTitle)) / 2, + ' ') + + graphOptions.xAxisTitle, + { .x = 0, .y = 5 + graphOptions.height }, + false, + formats::bold); + canvas.write(graphOptions.yAxisTitle, + { .x = graphOptions.width + 10, .y = graphOptions.height / 2 + 1 }, + false, + formats::bold); + + for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx) + conPlotLine(xGridSize, yGridSize, yMax, &graphOptions, &lineOptions[fnIdx], &canvas, fnValues[fnIdx]); + std::cout << canvas.asString() << "\n"; + } +} // namespace steppable::graphing \ No newline at end of file diff --git a/include/conPlot/conPlotTypes.hpp b/include/conPlot/conPlotTypes.hpp new file mode 100644 index 000000000..10e8ec6fa --- /dev/null +++ b/include/conPlot/conPlotTypes.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "colors.hpp" +#include "steppable/number.hpp" +#include "symbols.hpp" + +#include + +namespace steppable::graphing +{ + using namespace steppable::__internals::utils; + using namespace steppable::__internals::symbols; + + using GraphFn = std::function; + + namespace GraphDot + { + constexpr std::string ROUND_DOT = "\u25CF"; + constexpr std::string BLOCK = "\u2588"; + constexpr std::string LIGHT_BLOCK_1 = "\u2591"; + constexpr std::string LIGHT_BLOCK_2 = "\u2592"; + constexpr std::string LIGHT_BLOCK_3 = "\u2593"; + } // namespace GraphDot + + struct GraphOptions + { + Number xMin = -1; + Number xMax = 1; + long long width = 30; + long long height = 20; + long long xTickSpacing = 10; + long long yTickSpacing = 5; + std::string title = "Graph"; + std::string xAxisTitle = "X Axis Title"; + std::string yAxisTitle = "Y Axis Title"; + }; + + struct LineOptions + { + std::string lineDot = GraphDot::BLOCK; + ColorFunc lineColor = colors::green; + std::string title = "Line"; + }; +} // namespace steppable::graphing \ No newline at end of file diff --git a/include/rounding.hpp b/include/rounding.hpp index 21c7bcfde..0f9864ce7 100644 --- a/include/rounding.hpp +++ b/include/rounding.hpp @@ -21,7 +21,9 @@ **************************************************************************************************/ #pragma once -#include + +#include "types/rounding.hpp" + #include namespace steppable::__internals::numUtils diff --git a/include/steppable/number.hpp b/include/steppable/number.hpp index f2756e251..4f7f97c59 100644 --- a/include/steppable/number.hpp +++ b/include/steppable/number.hpp @@ -30,10 +30,12 @@ #pragma once +#include "rounding.hpp" #include "testing.hpp" +#include "types/rounding.hpp" #include "util.hpp" -#include +#include #include #include @@ -43,35 +45,6 @@ */ namespace steppable { - /** - * @enum RoundingMode - * @brief Specifies how Steppable should round the number in operations. - */ - enum class RoundingMode : std::uint8_t - { - /// @brief Use the higher precision whenever possible. - USE_MAXIMUM_PREC = 0xFF, - - /// @brief Use the lower precision whenever possible. - USE_MINIMUM_PREC = 0x01, - - /// @brief Use the current precision. - USE_CURRENT_PREC = 0x02, - - /// @brief Use the other number's precision. - USE_OTHER_PREC = 0x03, - - /// @brief Do not append any decimal places. - DISCARD_ALL_DECIMALS = 0x00 - }; - - enum class Rounding : std::uint8_t - { - ROUND_DOWN = 0x00, ///< Rounds the number down. - ROUND_UP = 0x01, ///< Rounds the number up. - ROUND_OFF = 0x02, ///< Rounds the number off. - }; - /** * @class Number * @brief Represents a number with arbitrary precision. It basically stores the value as a string. @@ -111,11 +84,6 @@ namespace steppable } public: - /// @brief The default constructor. Initializes the number with a value of 0. - Number(); - - Number(const Number& rhs); - /** * @brief Initializes a number with a specified value. * @note By default, the value is 0. @@ -138,6 +106,7 @@ namespace steppable { this->mode = mode; prec = newPrec; + value = __internals::numUtils::roundOff(value, prec); } /** @@ -306,6 +275,17 @@ namespace steppable * @return The number as a string. */ [[nodiscard]] std::string present() const; + + /** + * @brief Gets the absolute value of the number. + * @return The absolute value of the current number. + */ + [[nodiscard]] Number abs() const + { + if (*this > 0) + return *this; + return -(*this); + } }; /** @@ -319,3 +299,11 @@ namespace steppable inline Number operator""_n(unsigned long long value) { return Number(value); } } // namespace literals } // namespace steppable + +template<> +struct std::hash +{ + size_t operator()(const steppable::Number& n) const { return hash()(n.present()); } +}; + +std::ostream& operator<<(std::ostream& os, const steppable::Number& number); diff --git a/include/symbols.hpp b/include/symbols.hpp index bff6d8b73..d71a3a0f4 100644 --- a/include/symbols.hpp +++ b/include/symbols.hpp @@ -32,10 +32,14 @@ #pragma once +#include "colors.hpp" + #include #include +#include #include #include +#include #include /** @@ -44,6 +48,8 @@ */ namespace steppable::prettyPrint { + using namespace __internals::utils; + /** * @brief Represents a position in the console. */ @@ -66,6 +72,13 @@ namespace steppable::prettyPrint // PrintingAlignment alignment; // }; + enum class HorizontalAlignment : std::uint8_t + { + LEFT = 0, + CENTER = 1, + RIGHT = 2 + }; + /** * @brief Represents a console output buffer. */ @@ -76,7 +89,7 @@ namespace steppable::prettyPrint Position curPos; /// @brief The buffer object. - std::vector> buffer; + std::vector> buffer; /// @brief The height of the buffer. size_t height = 10; @@ -100,8 +113,14 @@ namespace steppable::prettyPrint * @param dLine The change in line. * @param dCol The change in column. * @param updatePos Whether to update the current position. + * @param color Color of the text to write. */ - void write(char c, long long dLine, long long dCol, bool updatePos = false); + void write(char c, + long long dLine, + long long dCol, + bool updatePos = false, + const ColorFunc& color = colors::keepOriginal, + const HorizontalAlignment& alignment = HorizontalAlignment::LEFT); /** * @brief Writes a character to the buffer. @@ -109,8 +128,13 @@ namespace steppable::prettyPrint * @param c The character to write. * @param pos The position to write to. * @param updatePos Whether to update the current position. + * @param color Color of the text to write. */ - void write(char c, const Position& pos, bool updatePos = false); + void write(char c, + const Position& pos, + bool updatePos = false, + const ColorFunc& color = colors::keepOriginal, + const HorizontalAlignment& alignment = HorizontalAlignment::LEFT); /** * @brief Writes a string to the buffer. @@ -118,8 +142,13 @@ namespace steppable::prettyPrint * @param s The string to write. * @param pos The position to write to. * @param updatePos Whether to update the current position. + * @param color Color of the text to write. */ - void write(const std::string& s, const Position& pos, bool updatePos = false); + void write(const std::string& s, + const Position& pos, + bool updatePos = false, + const ColorFunc& color = colors::keepOriginal, + const HorizontalAlignment& alignment = HorizontalAlignment::LEFT); /** * @brief Gets the buffer as a string. @@ -145,6 +174,112 @@ namespace steppable::prettyPrint size_t getStringHeight(const std::string& s); } // namespace steppable::prettyPrint +namespace steppable::__internals::stringUtils +{ + + /** + * @brief Checks if a Unicode code point represents a zero-width character. + * + * Zero-width characters do not consume display space (e.g., combining marks, zero-width spaces). + * + * @param codepoint The Unicode code point to check. + * @return true if the code point is a zero-width character, false otherwise. + */ + bool isZeroWidthCharacter(uint32_t codepoint); + + /** + * @brief Decodes a UTF-8 encoded string into a Unicode code point. + * + * This function reads one UTF-8 encoded character from the string, starting at index `i`, + * and advances the index to the next character. + * + * @param str The UTF-8 encoded string. + * @param i The current position in the string (updated after decoding). + * @return The Unicode code point of the character at the given position. + */ + uint32_t getCodepoint(const std::string& str, size_t& i); + + /** + * @brief Calculates the display width of a UTF-8 encoded string. + * + * This function computes the total display width of a string, correctly handling wide, narrow, + * zero-width, and CJK characters. CJK characters are counted as one character. + * + * @param utf8Str The UTF-8 encoded string. + * @return The total display width of the string. + */ + size_t getUnicodeDisplayWidth(const std::string& utf8Str); + + bool utf8Decode(const std::string& s, size_t& i, uint32_t& cp); + + bool isEmojiBase(uint32_t cp); + + bool isClusterExtender(uint32_t cp); + + class GraphemeIterator + { + public: + GraphemeIterator(std::string s) : str(std::move(s)) {} + + // Returns false when there are no more clusters + bool next(std::string& cluster) + { + if (pos >= str.size()) + return false; + size_t start = pos; + uint32_t cp = 0; + size_t temp = pos; + if (!utf8Decode(str, temp, cp)) + return false; + + // For emoji: group sequences with ZWJ or skin tone + if (isEmojiBase(cp) || (cp >= 0x1F1E6 && cp <= 0x1F1FF)) + { + pos = temp; + while (true) + { + size_t save = pos; + uint32_t nextcp = 0; + if (!utf8Decode(str, save, nextcp)) + break; + if (isClusterExtender(nextcp) || + // For flags: two regional indicators + ((cp >= 0x1F1E6 && cp <= 0x1F1FF) && (nextcp >= 0x1F1E6 && nextcp <= 0x1F1FF))) + { + pos = save; + } + else + { + break; + } + } + cluster = str.substr(start, pos - start); + return true; + } + + // For combining marks: group base + following combining + pos = temp; + while (true) + { + size_t save = pos; + uint32_t nextcp = 0; + if (!utf8Decode(str, save, nextcp)) + break; + if (isZeroWidthCharacter(nextcp)) + pos = save; + else + break; + } + cluster = str.substr(start, pos - start); + return true; + } + + private: + std::string str; + size_t pos{}; + }; +} // namespace steppable::__internals::stringUtils + /** * @namespace steppable::__internals::symbols * @brief The namespace containing various unicode symbols. @@ -249,6 +384,22 @@ namespace steppable::__internals::symbols * @return The surd expression. */ std::string makeSurd(const std::string& radicand); + + namespace BoxDrawing + { + constexpr std::string DOTTED_VERTICAL = "\u2575"; + constexpr std::string DOTTED_HORIZONTAL = "\u2574"; + + constexpr std::string HORIZONTAL = "\u2500"; + constexpr std::string VERTICAL = "\u2502"; + + constexpr std::string HORIZONTAL_UP = "\u2534"; + constexpr std::string VERTICAL_LEFT = "\u2524"; + + constexpr std::string BOTTOM_RIGHT_CORNER = "\u2518"; + + constexpr std::string CROSS = "\u253C"; + } // namespace BoxDrawing } // namespace steppable::__internals::symbols /** diff --git a/include/types/concepts.hpp b/include/types/concepts.hpp index 058c183fe..32d24ede7 100644 --- a/include/types/concepts.hpp +++ b/include/types/concepts.hpp @@ -27,6 +27,7 @@ */ #include +#include #include /** @@ -43,4 +44,10 @@ namespace steppable::concepts concept Presentable = requires(ObjectT object) { { object.present() } -> std::same_as; }; + + template + /// @brief Represents any streams with a `.operator<<()` method. + concept Stream = requires(ObjectT object) { + { object.operator<<() } -> std::same_as; + }; } // namespace steppable::concepts diff --git a/include/types/rounding.hpp b/include/types/rounding.hpp new file mode 100644 index 000000000..94a9f8b08 --- /dev/null +++ b/include/types/rounding.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace steppable +{ + /** + * @enum RoundingMode + * @brief Specifies how Steppable should round the number in operations. + */ + enum class RoundingMode : std::uint8_t + { + /// @brief Use the higher precision whenever possible. + USE_MAXIMUM_PREC = 0xFF, + + /// @brief Use the lower precision whenever possible. + USE_MINIMUM_PREC = 0x01, + + /// @brief Use the current precision. + USE_CURRENT_PREC = 0x02, + + /// @brief Use the other number's precision. + USE_OTHER_PREC = 0x03, + + /// @brief Do not append any decimal places. + DISCARD_ALL_DECIMALS = 0x00 + }; + + enum class Rounding : std::uint8_t + { + ROUND_DOWN = 0x00, ///< Rounds the number down. + ROUND_UP = 0x01, ///< Rounds the number up. + ROUND_OFF = 0x02, ///< Rounds the number off. + }; +} // namespace steppable diff --git a/src/colors.cpp b/src/colors.cpp index d7b777cde..74c1719e2 100644 --- a/src/colors.cpp +++ b/src/colors.cpp @@ -25,14 +25,14 @@ #include #ifdef WINDOWS -#include -#include -#include -#include -// ReSharper disable once CppInconsistentNaming -#define isatty _isatty -// ReSharper disable once CppInconsistentNaming -#define fileno _fileno + #include + #include + #include + #include + // ReSharper disable once CppInconsistentNaming + #define isatty _isatty + // ReSharper disable once CppInconsistentNaming + #define fileno _fileno #else #include #endif @@ -59,6 +59,12 @@ namespace steppable::__internals::utils namespace colors { + std::ostream& keepOriginal(std::ostream& stream) + { + stream << ""; + return stream; + } + std::ostream& black(std::ostream& stream) { if (isTerminal(stream)) diff --git a/src/matrix/ref/ref.cpp b/src/matrix/ref/ref.cpp index 205435ff7..e31cbce16 100644 --- a/src/matrix/ref/ref.cpp +++ b/src/matrix/ref/ref.cpp @@ -28,11 +28,10 @@ * @date 31st May 2025 */ +#include "conPlot/conPlot.hpp" +#include "fn/calc.hpp" #include "refReport.hpp" -#include "steppable/mat2d.hpp" -#include "steppable/number.hpp" -#include #include namespace steppable::__internals::matrix @@ -43,13 +42,17 @@ namespace steppable::__internals::matrix int main() { using namespace steppable; - std::vector> matrix = { { 2, 1, -1, 3, 2, 8 }, - { 1, -2, 1, 0, 2, -4 }, - { 3, 1, -3, 4, 1, 6 }, - { 1, 1, 1, 1, 1, 5 }, - { 2, -1, 2, -1, 3, 3 } }; - auto mat = steppable::Matrix(matrix); - mat = mat.rref(); + // std::vector> matrix = { { 2, 1, -1, 3, 2, 8 }, + // { 1, -2, 1, 0, 2, -4 }, + // { 3, 1, -3, 4, 1, 6 }, + // { 1, 1, 1, 1, 1, 5 }, + // { 2, -1, 2, -1, 3, 3 } }; + // auto mat = steppable::Matrix(matrix); + // mat = mat.rref(); - std::cout << mat.present(1) << "\n"; + // std::cout << mat.present(1) << "\n"; + graphing::conPlot({ [](const Number& x) { return steppable::__internals::calc::sin(x.present(), 2); }, + [](const Number& x) { return steppable::__internals::calc::cos(x.present(), 2); } }, + { .width = 90, .xMin = -3.14, .xMax = 3.14, .title = "Sine and cosine graphs from -pi to +pi" }, + { {}, { .lineColor = colors::red, .lineDot = "*" } }); } diff --git a/src/steppable/number.cpp b/src/steppable/number.cpp index 86f99dd38..38f2ea552 100644 --- a/src/steppable/number.cpp +++ b/src/steppable/number.cpp @@ -47,10 +47,6 @@ namespace steppable { using namespace steppable::__internals::calc; - Number::Number() : value("0"), prec(8) {} - - Number::Number(const Number& rhs) : value(rhs.value), prec(rhs.prec) {} - Number::Number(std::string value, const size_t prec, const RoundingMode mode) : value(std::move(value)), prec(prec), mode(mode) { @@ -165,3 +161,9 @@ namespace steppable std::string Number::present() const { return value; } } // namespace steppable + +std::ostream& operator<<(std::ostream& os, const steppable::Number& number) +{ + os << number.present(); + return os; +} diff --git a/src/symbols.cpp b/src/symbols.cpp index 4a38c84fa..7816254b2 100644 --- a/src/symbols.cpp +++ b/src/symbols.cpp @@ -22,6 +22,7 @@ #include "symbols.hpp" +#include "colors.hpp" #include "util.hpp" #include @@ -36,28 +37,84 @@ namespace steppable::prettyPrint { using namespace steppable::__internals::stringUtils; + using namespace steppable::__internals::utils; ConsoleOutput::ConsoleOutput(size_t height, size_t width) : height(height), width(width) { - buffer = std::vector>(height, std::vector(width, ' ')); + buffer = std::vector(height, std::vector(width, " "s)); } - void ConsoleOutput::write(const char c, const Position& pos, bool updatePos) + void ConsoleOutput::write(const std::string& s, + const Position& pos, + bool updatePos, + const ColorFunc& color, + const HorizontalAlignment& alignment) { - std::vector vector = buffer[pos.y]; - vector[pos.x] = c; - buffer[pos.y] = vector; + auto outputString = s; + switch (alignment) + { + case HorizontalAlignment::LEFT: + break; + case HorizontalAlignment::CENTER: + outputString = std::string((width - getUnicodeDisplayWidth(outputString)) / 2, ' ') + outputString; + break; + case HorizontalAlignment::RIGHT: + outputString = std::string(width - getUnicodeDisplayWidth(outputString), ' ') + outputString; + break; + } + + Position p = pos; + if (s == "\n") + { + p.y++; + p.x = pos.x; + return; + } + size_t stringWidth = getUnicodeDisplayWidth(outputString); + if (stringWidth <= 1) + { + std::stringstream ss; + color(ss); + ss << outputString << reset; + buffer[p.y][p.x] = ss.str(); + } + else + { + GraphemeIterator graphemeIterator(outputString); + std::string cluster; + size_t i = 0; + while (graphemeIterator.next(cluster)) + { + if (cluster == "\n") + { + i = 0; + p.y++; + p.x = pos.x; + continue; + } + std::stringstream ss; + color(ss); + ss << cluster << reset; + buffer[p.y][p.x + i] = ss.str(); + i++; + } + } if (updatePos) - curPos = pos; + curPos = p; } - void ConsoleOutput::write(const char c, const long long dLine, const long long dCol, bool updatePos) + void ConsoleOutput::write(const char c, + const long long dLine, + const long long dCol, + bool updatePos, + const ColorFunc& color, + const HorizontalAlignment& alignment) { if (dLine < 0) { // We need to add extra lines for printing at the very top - buffer.insert(buffer.begin(), -dLine, std::vector(width, ' ')); + buffer.insert(buffer.begin(), -dLine, std::vector(width, " "s)); curPos.y = 0; height -= dLine; } @@ -65,8 +122,8 @@ namespace steppable::prettyPrint { for (auto& i : buffer) { - std::vector vector = i; - vector.resize(vector.size() - dCol, ' '); + std::vector vector = i; + vector.resize(vector.size() - dCol, " "s); i = vector; } @@ -75,25 +132,16 @@ namespace steppable::prettyPrint } else curPos.x += dCol; - write(c, curPos, updatePos); + write(c, curPos, updatePos, color, alignment); } - void ConsoleOutput::write(const std::string& s, const Position& pos, bool updatePos) + void ConsoleOutput::write(const char c, + const Position& pos, + bool updatePos, + const ColorFunc& color, + const HorizontalAlignment& alignment) { - Position p = pos; - for (const auto& c : s) - { - if (c == '\n') - { - p.y++; - p.x = pos.x; - continue; - } - write(c, p, false); - p.x++; - if (updatePos) - curPos = p; - } + write(std::string(1, c), pos, updatePos, color, alignment); } std::string ConsoleOutput::asString() const @@ -113,13 +161,123 @@ namespace steppable::prettyPrint auto strings = split(s, '\n'); size_t max = 0; for (const auto& string : strings) - max = std::max(max, string.length()); + max = std::max(max, getUnicodeDisplayWidth(string)); return max; } size_t getStringHeight(const std::string& s) { return split(s, '\n').size(); } } // namespace steppable::prettyPrint +namespace steppable::__internals::stringUtils +{ + bool isZeroWidthCharacter(uint32_t codepoint) + { + return (codepoint >= 0x0300 && codepoint <= 0x036F) || // Combining diacritical marks + (codepoint >= 0x200B && codepoint <= 0x200F) || // Zero-width space, non-joiners + (codepoint == 0x2028 || codepoint == 0x2029 || + codepoint == 0x202F) || // Line separator, para separator, narrow no-break space + (codepoint >= 0xFE00 && codepoint <= 0xFE0F) || // Variation selectors + (codepoint >= 0xE0100 && codepoint <= 0xE01EF); // Supplemental variation selectors + } + + bool isEmojiBase(uint32_t cp) + { + return (cp >= 0x1F600 && cp <= 0x1F64F) || // Emoticons + (cp >= 0x1F300 && cp <= 0x1F5FF) || // Misc Symbols and Pictographs + (cp >= 0x1F680 && cp <= 0x1F6FF) || // Transport & Map + (cp >= 0x1F1E6 && cp <= 0x1F1FF) || // Regional indicators + (cp >= 0x2600 && cp <= 0x26FF) || // Misc symbols + (cp >= 0x2700 && cp <= 0x27BF) || // Dingbats + (cp >= 0x1F900 && cp <= 0x1F9FF) || // Supplemental Symbols and Pictographs + (cp >= 0x1FA70 && cp <= 0x1FAFF) || (cp == 0x200D); // ZWJ + } + + bool utf8Decode(const std::string& s, size_t& i, uint32_t& cp) + { + if (i >= s.size()) + return false; + unsigned char c = s[i]; + if (c < 0x80) + { + cp = c; + i += 1; + } + else if ((c & 0xE0) == 0xC0 && i + 1 < s.size()) + { + cp = ((c & 0x1F) << 6) | (s[i + 1] & 0x3F); + i += 2; + } + else if ((c & 0xF0) == 0xE0 && i + 2 < s.size()) + { + cp = ((c & 0x0F) << 12) | ((s[i + 1] & 0x3F) << 6) | (s[i + 2] & 0x3F); + i += 3; + } + else if ((c & 0xF8) == 0xF0 && i + 3 < s.size()) + { + cp = ((c & 0x07) << 18) | ((s[i + 1] & 0x3F) << 12) | ((s[i + 2] & 0x3F) << 6) | (s[i + 3] & 0x3F); + i += 4; + } + else + { + i += 1; + cp = 0xFFFD; + } + return true; + } + + bool isClusterExtender(uint32_t cp) + { + return (cp >= 0x1F3FB && cp <= 0x1F3FF) || // Skin tone + (cp == 0x200D) || // ZWJ + (cp == 0xFE0F) || // VS16 + isZeroWidthCharacter(cp); + } + + uint32_t getCodepoint(const std::string& str, size_t& i) + { + unsigned char c = str[i]; + if (c <= 0x7F) // Single-byte (ASCII) + return c; + if ((c & 0xE0) == 0xC0) // Two-byte sequence + return ((c & 0x1F) << 6) | (str[++i] & 0x3F); + if ((c & 0xF0) == 0xE0) // Three-byte sequence + { + unsigned char byte1 = c & 0x0F; + unsigned char byte2 = str[++i] & 0x3F; + unsigned char byte3 = str[++i] & 0x3F; + + return (byte1 << 12) | (byte2 << 6) | byte3; + } + if ((c & 0xF8) == 0xF0) // Four-byte sequence + { + unsigned char byte1 = (c & 0x07) << 18; // First byte + unsigned char byte2 = (str[++i] & 0x3F) << 12; // Second byte (increment i) + unsigned char byte3 = (str[++i] & 0x3F) << 6; // Third byte (increment i) + unsigned char byte4 = str[++i] & 0x3F; // Fourth byte (increment i) + + return byte1 | byte2 | byte3 | byte4; + } + return 0; // Invalid UTF-8 + } + + size_t getUnicodeDisplayWidth(const std::string& utf8Str) + { + size_t width = 0; + size_t len = utf8Str.size(); + for (size_t i = 0; i < len; ++i) + { + uint32_t codepoint = getCodepoint(utf8Str, i); + + if (isZeroWidthCharacter(codepoint)) + // Skip zero-width characters + continue; + width += 1; + } + + return width; + } +} // namespace steppable::__internals::stringUtils + namespace steppable::__internals::symbols { using namespace steppable::__internals::stringUtils; From 19da362a19a20b3978807095b965a66c22a3d7cc Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Mon, 30 Jun 2025 16:37:09 +0800 Subject: [PATCH 02/18] Add legends --- include/conPlot/conPlot.hpp | 21 +++++++++++++++++++++ src/matrix/ref/ref.cpp | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/include/conPlot/conPlot.hpp b/include/conPlot/conPlot.hpp index 1620090e4..8d6930dee 100644 --- a/include/conPlot/conPlot.hpp +++ b/include/conPlot/conPlot.hpp @@ -33,6 +33,26 @@ namespace steppable::graphing } } + void conPlotLegend(const GraphOptions* graphOptions, + const std::vector* lineOptions, + prettyPrint::ConsoleOutput* canvas) + { + long long yLoc = graphOptions->height + 7; + canvas->write("Legend", { .x = 0, .y = yLoc++ }, false, formats::bold); + for (size_t fnIdx = 0; fnIdx < lineOptions->size(); fnIdx++) + { + const auto& lineOption = lineOptions->at(fnIdx); + yLoc += fnIdx; + canvas->write(lineOption.title, { .x = 0, .y = yLoc }, false); + + for (int i = 0; i < graphOptions->xTickSpacing; i++) + canvas->write(lineOption.lineDot, + { .x = static_cast(lineOption.title.length()) + 1 + i, .y = yLoc }, + false, + lineOption.lineColor); + } + } + void conPlot(const std::vector& f, const GraphOptions& graphOptions, const std::vector& lineOptions) @@ -135,6 +155,7 @@ namespace steppable::graphing for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx) conPlotLine(xGridSize, yGridSize, yMax, &graphOptions, &lineOptions[fnIdx], &canvas, fnValues[fnIdx]); + conPlotLegend(&graphOptions, &lineOptions, &canvas); std::cout << canvas.asString() << "\n"; } } // namespace steppable::graphing \ No newline at end of file diff --git a/src/matrix/ref/ref.cpp b/src/matrix/ref/ref.cpp index e31cbce16..81606393e 100644 --- a/src/matrix/ref/ref.cpp +++ b/src/matrix/ref/ref.cpp @@ -54,5 +54,5 @@ int main() graphing::conPlot({ [](const Number& x) { return steppable::__internals::calc::sin(x.present(), 2); }, [](const Number& x) { return steppable::__internals::calc::cos(x.present(), 2); } }, { .width = 90, .xMin = -3.14, .xMax = 3.14, .title = "Sine and cosine graphs from -pi to +pi" }, - { {}, { .lineColor = colors::red, .lineDot = "*" } }); + { { .title = "Sine" }, { .lineColor = colors::red, .lineDot = "*", .title = "Cosine" } }); } From f7891e9fd75a53fb9a4f584327d637b442f39fc8 Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Mon, 30 Jun 2025 23:51:10 +0800 Subject: [PATCH 03/18] Allow interpolation on graphs. - Using cubic interpolation and linear interpolation to allow graphing with fewer sample points. --- include/conPlot/conPlot.hpp | 53 +++++++--- include/conPlot/conPlotInterpolation.hpp | 128 +++++++++++++++++++++++ include/conPlot/conPlotTypes.hpp | 3 +- src/matrix/ref/ref.cpp | 3 +- 4 files changed, 169 insertions(+), 18 deletions(-) create mode 100644 include/conPlot/conPlotInterpolation.hpp diff --git a/include/conPlot/conPlot.hpp b/include/conPlot/conPlot.hpp index 8d6930dee..a7e959c12 100644 --- a/include/conPlot/conPlot.hpp +++ b/include/conPlot/conPlot.hpp @@ -1,12 +1,11 @@ #include "colors.hpp" +#include "conPlot/conPlotInterpolation.hpp" #include "conPlotTypes.hpp" #include "steppable/number.hpp" #include "symbols.hpp" // Adjust the path as needed -#include "types/rounding.hpp" #include #include -#include #include #include #include @@ -22,14 +21,34 @@ namespace steppable::graphing std::map& fnValues) { // Plot function - for (long long i = 0; i < graphOptions->width; ++i) + std::map gridPos; + for (const auto& [i, y] : fnValues) { - Number x = graphOptions->xMin + Number(i) * xGridSize; - Number y = fnValues[x]; auto gridY = std::stoll(((yMax - y) / yGridSize).present()); + auto gridX = std::stoll(((graphOptions->xMax - i) / xGridSize).present()); + gridPos[gridX] = gridY; + } + if (lineOptions->samplesSpacing > 1) + cubicInterpolateFill(&gridPos, 0, graphOptions->width); + long long lastGridY = gridPos[0]; + for (const auto& [gridX, gridY] : gridPos) + { // Plot point - canvas->write(lineOptions->lineDot, { .x = i, .y = 3 + gridY }, false, lineOptions->lineColor); + const long long diffY = gridY - lastGridY; + const long long absDiffY = std::abs(diffY); + const long long sgn = diffY / absDiffY; + if (absDiffY > graphOptions->height / 2) + { + // Create more points to connect the dots + for (long long j = 1; j < absDiffY + 1; j++) + canvas->write(lineOptions->lineDot, + { .x = gridX, .y = 3 + gridY + (-sgn * j) }, + false, + lineOptions->lineColor); + } + canvas->write(lineOptions->lineDot, { .x = gridX, .y = 3 + gridY }, false, lineOptions->lineColor); + lastGridY = 3 + gridY; } } @@ -42,7 +61,7 @@ namespace steppable::graphing for (size_t fnIdx = 0; fnIdx < lineOptions->size(); fnIdx++) { const auto& lineOption = lineOptions->at(fnIdx); - yLoc += fnIdx; + yLoc += static_cast(fnIdx); canvas->write(lineOption.title, { .x = 0, .y = yLoc }, false); for (int i = 0; i < graphOptions->xTickSpacing; i++) @@ -67,7 +86,7 @@ namespace steppable::graphing canvas.write( graphOptions.title, { .x = 0, .y = 1 }, false, formats::bold, prettyPrint::HorizontalAlignment::CENTER); - // Ticks spacing + // Sample min/max Number yMin; for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx) { @@ -83,12 +102,14 @@ namespace steppable::graphing { for (long long i = 0; i <= graphOptions.width; ++i) { - Number x = graphOptions.xMin + Number(i) * xGridSize; - Number y = f[fnIdx](x); - fnValues[fnIdx][x] = y; - - yMin = std::min(y, yMin); - yMax = std::max(y, yMax); + if (i % lineOptions[fnIdx].samplesSpacing == 0) + { + Number x = graphOptions.xMin + Number(i) * xGridSize; + Number y = f[fnIdx](x); + fnValues[fnIdx][x] = y; + yMin = std::min(y, yMin); + yMax = std::max(y, yMax); + } // Write base frame canvas.write(BoxDrawing::HORIZONTAL, { .x = i, .y = 3 + graphOptions.height }); @@ -149,7 +170,7 @@ namespace steppable::graphing false, formats::bold); canvas.write(graphOptions.yAxisTitle, - { .x = graphOptions.width + 10, .y = graphOptions.height / 2 + 1 }, + { .x = graphOptions.width + 10, .y = (graphOptions.height / 2) + 1 }, false, formats::bold); @@ -158,4 +179,4 @@ namespace steppable::graphing conPlotLegend(&graphOptions, &lineOptions, &canvas); std::cout << canvas.asString() << "\n"; } -} // namespace steppable::graphing \ No newline at end of file +} // namespace steppable::graphing diff --git a/include/conPlot/conPlotInterpolation.hpp b/include/conPlot/conPlotInterpolation.hpp new file mode 100644 index 000000000..611238834 --- /dev/null +++ b/include/conPlot/conPlotInterpolation.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include "output.hpp" + +#include +#include +#include +#include +#include + +using namespace std::literals; + +namespace steppable::graphing +{ + void linearInterpolateFill(std::map* data, long long xMin, long long xMax) + { + if (data->size() < 2) + output::error("graphing::linearInterpolateFill"s, + "At least 2 points are required for linear interpolation."s); + + std::vector xs; + std::vector ys; + for (const auto& kv : *data) + { + xs.push_back(kv.first); + ys.push_back(kv.second); + } + auto n = static_cast(xs.size()); + + for (long long x = xMin; x <= xMax; ++x) + { + // Find the interval [x0, x1] for x (or clamp to endpoints) + auto it = std::lower_bound(xs.begin(), xs.end(), x); + long long idx1 = std::distance(xs.begin(), it); + long long i0 = 0; + long long i1 = 0; + if (x <= xs.front()) + { + i0 = 0; + i1 = 1; + } + else if (x >= xs.back()) + { + i0 = n - 2; + i1 = n - 1; + } + else + { + i0 = idx1 - 1; + i1 = idx1; + } + + long long x0 = xs[i0]; + long long x1 = xs[i1]; + long long y0 = ys[i0]; + long long y1 = ys[i1]; + long double t = 0; + if (x1 == x0) + t = 0.0; + else + t = static_cast(x - x0) / static_cast(x1 - x0); + long long y = llround(static_cast(y0) + (t * static_cast(y1 - y0))); + (*data)[x] = y; + } + } + + void cubicInterpolateFill(std::map* data, long long xMin, long long xMax) + { + if (data->size() < 4) + linearInterpolateFill(data, xMin, xMax); + std::vector xs; + std::vector ys; + for (const auto& kv : *data) + { + xs.push_back(kv.first); + ys.push_back(kv.second); + } + auto n = static_cast(xs.size()); + + for (long long x = xMin; x <= xMax; ++x) + { + // Find the correct window of 4 points for interpolation + auto it = std::ranges::lower_bound(xs, x); + long long idx1 = std::distance(xs.begin(), it); + long long i1 = std::clamp(idx1 - 1, 1LL, n - 3); + long long i0 = i1 - 1; + long long i2 = i1 + 1; + long long i3 = i1 + 2; + + if (x <= xs[1]) + { + i0 = 0; + i1 = 1; + i2 = 2; + i3 = 3; + } + if (x >= xs[n - 2]) + { + i0 = n - 4; + i1 = n - 3; + i2 = n - 2; + i3 = n - 1; + } + + long long x0 = xs[i0]; + long long x1 = xs[i1]; + long long x2 = xs[i2]; + long long x3 = xs[i3]; + long long y0 = ys[i0]; + long long y1 = ys[i1]; + long long y2 = ys[i2]; + long long y3 = ys[i3]; + + long double t0 = static_cast((x - x1) * (x - x2) * (x - x3)) / + static_cast((x0 - x1) * (x0 - x2) * (x0 - x3)); + long double t1 = static_cast((x - x0) * (x - x2) * (x - x3)) / + static_cast((x1 - x0) * (x1 - x2) * (x1 - x3)); + long double t2 = static_cast((x - x0) * (x - x1) * (x - x3)) / + static_cast((x2 - x0) * (x2 - x1) * (x2 - x3)); + long double t3 = static_cast((x - x0) * (x - x1) * (x - x2)) / + static_cast((x3 - x0) * (x3 - x1) * (x3 - x2)); + + long long y = llround((static_cast(y0) * t0) + (static_cast(y1) * t1) + + (static_cast(y2) * t2) + (static_cast(y3) * t3)); + (*data)[x] = y; + } + } +} // namespace steppable::graphing diff --git a/include/conPlot/conPlotTypes.hpp b/include/conPlot/conPlotTypes.hpp index 10e8ec6fa..831918b18 100644 --- a/include/conPlot/conPlotTypes.hpp +++ b/include/conPlot/conPlotTypes.hpp @@ -40,5 +40,6 @@ namespace steppable::graphing std::string lineDot = GraphDot::BLOCK; ColorFunc lineColor = colors::green; std::string title = "Line"; + long long samplesSpacing = 2; }; -} // namespace steppable::graphing \ No newline at end of file +} // namespace steppable::graphing diff --git a/src/matrix/ref/ref.cpp b/src/matrix/ref/ref.cpp index 81606393e..c7c45a273 100644 --- a/src/matrix/ref/ref.cpp +++ b/src/matrix/ref/ref.cpp @@ -54,5 +54,6 @@ int main() graphing::conPlot({ [](const Number& x) { return steppable::__internals::calc::sin(x.present(), 2); }, [](const Number& x) { return steppable::__internals::calc::cos(x.present(), 2); } }, { .width = 90, .xMin = -3.14, .xMax = 3.14, .title = "Sine and cosine graphs from -pi to +pi" }, - { { .title = "Sine" }, { .lineColor = colors::red, .lineDot = "*", .title = "Cosine" } }); + { { .title = "Sine", .samplesSpacing = 3 }, + { .lineColor = colors::red, .lineDot = "*", .title = "Cosine", .samplesSpacing = 3 } }); } From 15595c425d69c41ac4d3bf862cffab4acfb023b8 Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Tue, 1 Jul 2025 00:08:40 +0800 Subject: [PATCH 04/18] [BREAKING] Fix `constexpr` errors. - [BREAKING] Switched `ConsoleOutput::write` to using `std::string_view`. `std::string` should be automatically passed as `std::string_view`. --- include/conPlot/conPlotTypes.hpp | 14 +++++++------- include/symbols.hpp | 23 ++++++++++++++++++++--- src/symbols.cpp | 14 +++++++------- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/include/conPlot/conPlotTypes.hpp b/include/conPlot/conPlotTypes.hpp index 831918b18..14d44cf4f 100644 --- a/include/conPlot/conPlotTypes.hpp +++ b/include/conPlot/conPlotTypes.hpp @@ -2,9 +2,9 @@ #include "colors.hpp" #include "steppable/number.hpp" -#include "symbols.hpp" #include +#include namespace steppable::graphing { @@ -15,11 +15,11 @@ namespace steppable::graphing namespace GraphDot { - constexpr std::string ROUND_DOT = "\u25CF"; - constexpr std::string BLOCK = "\u2588"; - constexpr std::string LIGHT_BLOCK_1 = "\u2591"; - constexpr std::string LIGHT_BLOCK_2 = "\u2592"; - constexpr std::string LIGHT_BLOCK_3 = "\u2593"; + constexpr std::string_view ROUND_DOT = "\u25CF"; + constexpr std::string_view BLOCK = "\u2588"; + constexpr std::string_view LIGHT_BLOCK_1 = "\u2591"; + constexpr std::string_view LIGHT_BLOCK_2 = "\u2592"; + constexpr std::string_view LIGHT_BLOCK_3 = "\u2593"; } // namespace GraphDot struct GraphOptions @@ -37,7 +37,7 @@ namespace steppable::graphing struct LineOptions { - std::string lineDot = GraphDot::BLOCK; + std::string_view lineDot = GraphDot::BLOCK; ColorFunc lineColor = colors::green; std::string title = "Line"; long long samplesSpacing = 2; diff --git a/include/symbols.hpp b/include/symbols.hpp index d71a3a0f4..6cb8a6e56 100644 --- a/include/symbols.hpp +++ b/include/symbols.hpp @@ -97,6 +97,20 @@ namespace steppable::prettyPrint /// @brief The width of the buffer. size_t width = 10; + /** + * @brief Writes a string to the buffer. + * + * @param s The string to write. + * @param pos The position to write to. + * @param updatePos Whether to update the current position. + * @param color Color of the text to write. + */ + void _write(const std::string& s, + const Position& pos, + bool updatePos = false, + const ColorFunc& color = colors::keepOriginal, + const HorizontalAlignment& alignment = HorizontalAlignment::LEFT); + public: /** * @brief Creates a new console output buffer. @@ -137,18 +151,21 @@ namespace steppable::prettyPrint const HorizontalAlignment& alignment = HorizontalAlignment::LEFT); /** - * @brief Writes a string to the buffer. + * @brief Writes a `string_view` object to the buffer. * * @param s The string to write. * @param pos The position to write to. * @param updatePos Whether to update the current position. * @param color Color of the text to write. */ - void write(const std::string& s, + void write(const std::string_view& s, const Position& pos, bool updatePos = false, const ColorFunc& color = colors::keepOriginal, - const HorizontalAlignment& alignment = HorizontalAlignment::LEFT); + const HorizontalAlignment& alignment = HorizontalAlignment::LEFT) + { + _write(static_cast(s), pos, updatePos, color, alignment); + } /** * @brief Gets the buffer as a string. diff --git a/src/symbols.cpp b/src/symbols.cpp index 7816254b2..0a409aa6f 100644 --- a/src/symbols.cpp +++ b/src/symbols.cpp @@ -44,11 +44,11 @@ namespace steppable::prettyPrint buffer = std::vector(height, std::vector(width, " "s)); } - void ConsoleOutput::write(const std::string& s, - const Position& pos, - bool updatePos, - const ColorFunc& color, - const HorizontalAlignment& alignment) + void ConsoleOutput::_write(const std::string& s, + const Position& pos, + bool updatePos, + const ColorFunc& color, + const HorizontalAlignment& alignment) { auto outputString = s; switch (alignment) @@ -132,7 +132,7 @@ namespace steppable::prettyPrint } else curPos.x += dCol; - write(c, curPos, updatePos, color, alignment); + _write(std::string(1, c), curPos, updatePos, color, alignment); } void ConsoleOutput::write(const char c, @@ -141,7 +141,7 @@ namespace steppable::prettyPrint const ColorFunc& color, const HorizontalAlignment& alignment) { - write(std::string(1, c), pos, updatePos, color, alignment); + _write(std::string(1, c), pos, updatePos, color, alignment); } std::string ConsoleOutput::asString() const From 63f728cbaf9589cf5bd5fb73751c2ca2d0c02fe0 Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Tue, 1 Jul 2025 07:39:42 +0800 Subject: [PATCH 05/18] Fixed more constexpr errors --- include/symbols.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/include/symbols.hpp b/include/symbols.hpp index 6cb8a6e56..ddafc7861 100644 --- a/include/symbols.hpp +++ b/include/symbols.hpp @@ -404,18 +404,18 @@ namespace steppable::__internals::symbols namespace BoxDrawing { - constexpr std::string DOTTED_VERTICAL = "\u2575"; - constexpr std::string DOTTED_HORIZONTAL = "\u2574"; + constexpr std::string_view DOTTED_VERTICAL = "\u2575"; + constexpr std::string_view DOTTED_HORIZONTAL = "\u2574"; - constexpr std::string HORIZONTAL = "\u2500"; - constexpr std::string VERTICAL = "\u2502"; + constexpr std::string_view HORIZONTAL = "\u2500"; + constexpr std::string_view VERTICAL = "\u2502"; - constexpr std::string HORIZONTAL_UP = "\u2534"; - constexpr std::string VERTICAL_LEFT = "\u2524"; + constexpr std::string_view HORIZONTAL_UP = "\u2534"; + constexpr std::string_view VERTICAL_LEFT = "\u2524"; - constexpr std::string BOTTOM_RIGHT_CORNER = "\u2518"; + constexpr std::string_view BOTTOM_RIGHT_CORNER = "\u2518"; - constexpr std::string CROSS = "\u253C"; + constexpr std::string_view CROSS = "\u253C"; } // namespace BoxDrawing } // namespace steppable::__internals::symbols From c0a648b0516007f986465869918a11cc6ef735ed Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Tue, 1 Jul 2025 04:05:01 -0700 Subject: [PATCH 06/18] Add `Parameter` support --- include/conPlot/conPlot.hpp | 4 +- include/conPlot/conPlotTypes.hpp | 38 ++++++++++ include/steppable/parameter.hpp | 126 +++++++++++++++++++++++++++++++ src/calc/division/division.cpp | 4 + src/colors.cpp | 5 +- src/matrix/ref/ref.cpp | 13 ++-- src/util.cpp | 2 +- 7 files changed, 184 insertions(+), 8 deletions(-) create mode 100644 include/steppable/parameter.hpp diff --git a/include/conPlot/conPlot.hpp b/include/conPlot/conPlot.hpp index a7e959c12..3383692e4 100644 --- a/include/conPlot/conPlot.hpp +++ b/include/conPlot/conPlot.hpp @@ -37,7 +37,9 @@ namespace steppable::graphing // Plot point const long long diffY = gridY - lastGridY; const long long absDiffY = std::abs(diffY); - const long long sgn = diffY / absDiffY; + long long sgn = 1; + if (absDiffY != 0) + sgn = diffY / absDiffY; if (absDiffY > graphOptions->height / 2) { // Create more points to connect the dots diff --git a/include/conPlot/conPlotTypes.hpp b/include/conPlot/conPlotTypes.hpp index 14d44cf4f..d9321c97b 100644 --- a/include/conPlot/conPlotTypes.hpp +++ b/include/conPlot/conPlotTypes.hpp @@ -2,6 +2,7 @@ #include "colors.hpp" #include "steppable/number.hpp" +#include "steppable/parameter.hpp" #include #include @@ -10,6 +11,7 @@ namespace steppable::graphing { using namespace steppable::__internals::utils; using namespace steppable::__internals::symbols; + using namespace steppable::__internals::parameter; using GraphFn = std::function; @@ -33,6 +35,32 @@ namespace steppable::graphing std::string title = "Graph"; std::string xAxisTitle = "X Axis Title"; std::string yAxisTitle = "Y Axis Title"; + + template + GraphOptions(Params... params) + { + auto map = processParams(params...); + + PARAM_GET_FALLBACK(map, Number, _xMin, -1); + PARAM_GET_FALLBACK(map, Number, _xMax, 1); + PARAM_GET_FALLBACK(map, long long, _width, 30); + PARAM_GET_FALLBACK(map, long long, _height, 20); + PARAM_GET_FALLBACK(map, long long, _xTickSpacing, 10); + PARAM_GET_FALLBACK(map, long long, _yTickSpacing, 5); + PARAM_GET_FALLBACK(map, std::string, _title, "Graph"); + PARAM_GET_FALLBACK(map, std::string, _xAxisTitle, "X Axis Title"); + PARAM_GET_FALLBACK(map, std::string, _yAxisTitle, "Y Axis Title"); + + xMin = _xMin; + xMax = _xMax; + width = _width; + height = _height; + xTickSpacing = _xTickSpacing; + yTickSpacing = _yTickSpacing; + title = _title; + xAxisTitle = _xAxisTitle; + yAxisTitle = _yAxisTitle; + } }; struct LineOptions @@ -41,5 +69,15 @@ namespace steppable::graphing ColorFunc lineColor = colors::green; std::string title = "Line"; long long samplesSpacing = 2; + + template + LineOptions(Params... params) + { + auto map = processParams(params...); + PARAM_GET_FALLBACK(map, std::string_view, _lineDot, GraphDot::BLOCK); + PARAM_GET_FALLBACK(map, ColorFunc, _lineColor, colors::green); + PARAM_GET_FALLBACK(map, std::string, _title, "Line"); + PARAM_GET_FALLBACK(map, long long, _samplesSpacing, 2); + } }; } // namespace steppable::graphing diff --git a/include/steppable/parameter.hpp b/include/steppable/parameter.hpp new file mode 100644 index 000000000..60b220124 --- /dev/null +++ b/include/steppable/parameter.hpp @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::literals; + +namespace steppable::__internals::parameter +{ + using ValueMap = std::unordered_map; + using TypeMap = std::unordered_map; + + struct ValuedParameter + { + std::string name; + std::any value; + }; + + struct Parameter + { + std::string name; + + ValuedParameter operator=(std::any value) { return { name, value }; } + + Parameter& operator=(const Parameter&) = delete; + + explicit Parameter(const std::string& name) : name(name) {} + }; + + struct ParameterMap + { + ValueMap values; + + ParameterMap(ValueMap values) : values(values) {} + + void checkParameterOrder(const std::vector& names) + { + if (names.size() != values.size()) + throw std::runtime_error("Error"); + + auto it = values.begin(); + for (size_t i = 0; i < values.size(); i++) + { + std::advance(it, i); + if (names[i] != it->first) + throw std::runtime_error("Error"); + } + } + + void checkParameterNameUnordered(const std::vector& names) + { + if (names.size() != values.size()) + throw std::runtime_error("Error"); + for (const auto& pair : values) + if (std::find(names.begin(), names.end(), pair.first) == names.end()) + throw std::runtime_error("Error"); + } + + void checkParameterTypes() {} + + template + void checkParameterTypes(const std::string& name, const std::type_info* info, const Rest... rest) + { + static_assert((sizeof...(rest) % 2) == 0, "Arguments must come in pairs (std::string, std::type_info*)"); + + if (values.find(name) == values.end()) + throw std::runtime_error("Parameter name not found"); + + const auto& value = values[name]; + if (value.type() != *info) + throw std::runtime_error("Parameter type is wrong"); + checkParameterTypes(rest...); + } + + template + ValueT getItem(const std::string& name) + { + checkParameterTypes(name, &typeid(ValueT)); + return std::any_cast(values.at(name)); + } + + template + ValueT getItem(const std::string& name, ValueT fallback) + { + // Value not found + if (values.find(name) == values.end()) + return fallback; + + checkParameterTypes(name, &typeid(ValueT)); + return std::any_cast(values.at(name)); + } + }; + + Parameter operator""_p(const char* name, size_t) { return Parameter(name); } + + template + ParameterMap processParams(Params... params) + { + ValueMap values; + const std::vector vec = { params... }; + + for (const auto& elem : vec) + values[elem.name] = elem.value; + return { values }; + } +} // namespace steppable::__internals::parameter + +#define PARAM_GET(map, type, name) \ + type(name); \ + do \ + { \ + name = map.getItem(#name); \ + } while (0) + +#define PARAM_GET_FALLBACK(map, type, name, fallback) \ + type(name); \ + do \ + { \ + name = map.getItem(#name, fallback); \ + } while (0) diff --git a/src/calc/division/division.cpp b/src/calc/division/division.cpp index 1df54e640..2eba42298 100644 --- a/src/calc/division/division.cpp +++ b/src/calc/division/division.cpp @@ -130,6 +130,10 @@ namespace steppable::__internals::calc error("division", $("division", "977f3c9f-01c3-49e4-bf4a-94d7c58bbe82", { _number })); return "Infinity"; } + if (isZeroString(_number)) + { + return "0"; + } if (compare(_number, _divisor, 0) == "2") { diff --git a/src/colors.cpp b/src/colors.cpp index 74c1719e2..6bd31173e 100644 --- a/src/colors.cpp +++ b/src/colors.cpp @@ -25,10 +25,13 @@ #include #ifdef WINDOWS + // clang-format off + #include + // clang-format on + #include #include #include - #include // ReSharper disable once CppInconsistentNaming #define isatty _isatty // ReSharper disable once CppInconsistentNaming diff --git a/src/matrix/ref/ref.cpp b/src/matrix/ref/ref.cpp index c7c45a273..28fc75836 100644 --- a/src/matrix/ref/ref.cpp +++ b/src/matrix/ref/ref.cpp @@ -31,6 +31,7 @@ #include "conPlot/conPlot.hpp" #include "fn/calc.hpp" #include "refReport.hpp" +#include "steppable/parameter.hpp" #include @@ -42,6 +43,7 @@ namespace steppable::__internals::matrix int main() { using namespace steppable; + using namespace steppable::__internals::parameter; // std::vector> matrix = { { 2, 1, -1, 3, 2, 8 }, // { 1, -2, 1, 0, 2, -4 }, // { 3, 1, -3, 4, 1, 6 }, @@ -51,9 +53,10 @@ int main() // mat = mat.rref(); // std::cout << mat.present(1) << "\n"; - graphing::conPlot({ [](const Number& x) { return steppable::__internals::calc::sin(x.present(), 2); }, - [](const Number& x) { return steppable::__internals::calc::cos(x.present(), 2); } }, - { .width = 90, .xMin = -3.14, .xMax = 3.14, .title = "Sine and cosine graphs from -pi to +pi" }, - { { .title = "Sine", .samplesSpacing = 3 }, - { .lineColor = colors::red, .lineDot = "*", .title = "Cosine", .samplesSpacing = 3 } }); + graphing::conPlot( + { [](const Number& x) { return steppable::__internals::calc::sin(x.present(), 2); }, + [](const Number& x) { return steppable::__internals::calc::cos(x.present(), 2); } }, + { "width"_p = 90, "xMin"_p = -3.14, "xMax"_p = 3.14, "title"_p = "Sine and cosine graphs from -pi to +pi" }, + { { "title"_p = "Sine", "samplesSpacing"_p = 3 }, + { "lineColor"_p = colors::red, "lineDot"_p = "*", "title"_p = "Cosine", "samplesSpacing"_p = 3 } }); } diff --git a/src/util.cpp b/src/util.cpp index c874ce77a..df1762482 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -204,7 +204,7 @@ namespace steppable::__internals::numUtils auto removeLeadingZeros(const std::string& string) -> std::decay_t { - if (string == "0") + if (string == "0" or string.empty()) return "0"; auto out = string; bool negative = false; From 877373ef1735a90dbde5c12352364d89705691f1 Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Tue, 1 Jul 2025 21:08:14 +0800 Subject: [PATCH 07/18] Bug fixes to conPlot. --- include/conPlot/conPlotTypes.hpp | 49 ++++++++++++++++++-------------- include/steppable/parameter.hpp | 2 +- src/matrix/ref/ref.cpp | 11 +++++-- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/include/conPlot/conPlotTypes.hpp b/include/conPlot/conPlotTypes.hpp index d9321c97b..36798d637 100644 --- a/include/conPlot/conPlotTypes.hpp +++ b/include/conPlot/conPlotTypes.hpp @@ -41,25 +41,25 @@ namespace steppable::graphing { auto map = processParams(params...); - PARAM_GET_FALLBACK(map, Number, _xMin, -1); - PARAM_GET_FALLBACK(map, Number, _xMax, 1); - PARAM_GET_FALLBACK(map, long long, _width, 30); - PARAM_GET_FALLBACK(map, long long, _height, 20); - PARAM_GET_FALLBACK(map, long long, _xTickSpacing, 10); - PARAM_GET_FALLBACK(map, long long, _yTickSpacing, 5); - PARAM_GET_FALLBACK(map, std::string, _title, "Graph"); - PARAM_GET_FALLBACK(map, std::string, _xAxisTitle, "X Axis Title"); - PARAM_GET_FALLBACK(map, std::string, _yAxisTitle, "Y Axis Title"); + PARAM_GET_FALLBACK(map, Number, xMin, -1); + PARAM_GET_FALLBACK(map, Number, xMax, 1); + PARAM_GET_FALLBACK(map, long long, width, 30); + PARAM_GET_FALLBACK(map, long long, height, 20); + PARAM_GET_FALLBACK(map, long long, xTickSpacing, 10); + PARAM_GET_FALLBACK(map, long long, yTickSpacing, 5); + PARAM_GET_FALLBACK(map, std::string, title, "Graph"); + PARAM_GET_FALLBACK(map, std::string, xAxisTitle, "X Axis Title"); + PARAM_GET_FALLBACK(map, std::string, yAxisTitle, "Y Axis Title"); - xMin = _xMin; - xMax = _xMax; - width = _width; - height = _height; - xTickSpacing = _xTickSpacing; - yTickSpacing = _yTickSpacing; - title = _title; - xAxisTitle = _xAxisTitle; - yAxisTitle = _yAxisTitle; + this->xMin = xMin; + this->xMax = xMax; + this->width = width; + this->height = height; + this->xTickSpacing = xTickSpacing; + this->yTickSpacing = yTickSpacing; + this->title = title; + this->xAxisTitle = xAxisTitle; + this->yAxisTitle = yAxisTitle; } }; @@ -74,10 +74,15 @@ namespace steppable::graphing LineOptions(Params... params) { auto map = processParams(params...); - PARAM_GET_FALLBACK(map, std::string_view, _lineDot, GraphDot::BLOCK); - PARAM_GET_FALLBACK(map, ColorFunc, _lineColor, colors::green); - PARAM_GET_FALLBACK(map, std::string, _title, "Line"); - PARAM_GET_FALLBACK(map, long long, _samplesSpacing, 2); + PARAM_GET_FALLBACK(map, std::string_view, lineDot, GraphDot::BLOCK); + PARAM_GET_FALLBACK(map, ColorFunc, lineColor, colors::green); + PARAM_GET_FALLBACK(map, std::string, title, "Line"); + PARAM_GET_FALLBACK(map, long long, samplesSpacing, 2); + + this->lineDot = lineDot; + this->lineColor = lineColor; + this->title = title; + this->samplesSpacing = samplesSpacing; } }; } // namespace steppable::graphing diff --git a/include/steppable/parameter.hpp b/include/steppable/parameter.hpp index 60b220124..7bdf7d3fd 100644 --- a/include/steppable/parameter.hpp +++ b/include/steppable/parameter.hpp @@ -92,7 +92,7 @@ namespace steppable::__internals::parameter if (values.find(name) == values.end()) return fallback; - checkParameterTypes(name, &typeid(ValueT)); + // checkParameterTypes(name, &typeid(ValueT)); return std::any_cast(values.at(name)); } }; diff --git a/src/matrix/ref/ref.cpp b/src/matrix/ref/ref.cpp index 28fc75836..65c49a0b4 100644 --- a/src/matrix/ref/ref.cpp +++ b/src/matrix/ref/ref.cpp @@ -34,6 +34,9 @@ #include "steppable/parameter.hpp" #include +#include + +using namespace std::literals; namespace steppable::__internals::matrix { @@ -44,6 +47,8 @@ int main() { using namespace steppable; using namespace steppable::__internals::parameter; + + Utf8CodePage _; // std::vector> matrix = { { 2, 1, -1, 3, 2, 8 }, // { 1, -2, 1, 0, 2, -4 }, // { 3, 1, -3, 4, 1, 6 }, @@ -56,7 +61,7 @@ int main() graphing::conPlot( { [](const Number& x) { return steppable::__internals::calc::sin(x.present(), 2); }, [](const Number& x) { return steppable::__internals::calc::cos(x.present(), 2); } }, - { "width"_p = 90, "xMin"_p = -3.14, "xMax"_p = 3.14, "title"_p = "Sine and cosine graphs from -pi to +pi" }, - { { "title"_p = "Sine", "samplesSpacing"_p = 3 }, - { "lineColor"_p = colors::red, "lineDot"_p = "*", "title"_p = "Cosine", "samplesSpacing"_p = 3 } }); + { "width"_p = 90, "xMin"_p = -3.14, "xMax"_p = 3.14, "title"_p = "Sine and cosine graphs from -pi to +pi"s }, + { { "title"_p = "Sine"s }, + { "lineColor"_p = colors::red, "lineDot"_p = "*"sv, "title"_p = "Cosine"s } }); } From 9845b4ec876fdb5f208ab2b8b0e4588f4467294a Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Tue, 1 Jul 2025 22:36:01 +0800 Subject: [PATCH 08/18] Fix `std::any_cast` errors. --- include/conPlot/conPlotTypes.hpp | 16 +++--- include/steppable/parameter.hpp | 91 +++++++++++++------------------- src/matrix/ref/ref.cpp | 15 +++--- 3 files changed, 55 insertions(+), 67 deletions(-) diff --git a/include/conPlot/conPlotTypes.hpp b/include/conPlot/conPlotTypes.hpp index 36798d637..284713bda 100644 --- a/include/conPlot/conPlotTypes.hpp +++ b/include/conPlot/conPlotTypes.hpp @@ -43,10 +43,10 @@ namespace steppable::graphing PARAM_GET_FALLBACK(map, Number, xMin, -1); PARAM_GET_FALLBACK(map, Number, xMax, 1); - PARAM_GET_FALLBACK(map, long long, width, 30); - PARAM_GET_FALLBACK(map, long long, height, 20); - PARAM_GET_FALLBACK(map, long long, xTickSpacing, 10); - PARAM_GET_FALLBACK(map, long long, yTickSpacing, 5); + PARAM_GET_FALLBACK(map, long long, width, 30LL); + PARAM_GET_FALLBACK(map, long long, height, 20LL); + PARAM_GET_FALLBACK(map, long long, xTickSpacing, 10LL); + PARAM_GET_FALLBACK(map, long long, yTickSpacing, 5LL); PARAM_GET_FALLBACK(map, std::string, title, "Graph"); PARAM_GET_FALLBACK(map, std::string, xAxisTitle, "X Axis Title"); PARAM_GET_FALLBACK(map, std::string, yAxisTitle, "Y Axis Title"); @@ -70,14 +70,14 @@ namespace steppable::graphing std::string title = "Line"; long long samplesSpacing = 2; - template + template LineOptions(Params... params) { auto map = processParams(params...); PARAM_GET_FALLBACK(map, std::string_view, lineDot, GraphDot::BLOCK); - PARAM_GET_FALLBACK(map, ColorFunc, lineColor, colors::green); - PARAM_GET_FALLBACK(map, std::string, title, "Line"); - PARAM_GET_FALLBACK(map, long long, samplesSpacing, 2); + PARAM_GET_FALLBACK(map, ColorFunc, lineColor, (ColorFunc)colors::green); + PARAM_GET_FALLBACK(map, std::string, title, "Line"s); + PARAM_GET_FALLBACK(map, long long, samplesSpacing, 2LL); this->lineDot = lineDot; this->lineColor = lineColor; diff --git a/include/steppable/parameter.hpp b/include/steppable/parameter.hpp index 7bdf7d3fd..e824f269b 100644 --- a/include/steppable/parameter.hpp +++ b/include/steppable/parameter.hpp @@ -7,120 +7,105 @@ #include #include #include +#include #include using namespace std::literals; namespace steppable::__internals::parameter { - using ValueMap = std::unordered_map; - using TypeMap = std::unordered_map; - struct ValuedParameter { std::string name; std::any value; }; + using ValueMap = std::vector; + struct Parameter { std::string name; - ValuedParameter operator=(std::any value) { return { name, value }; } + template + ValuedParameter operator=(T value) // NOLINT(cppcoreguidelines-c-copy-assignment-signature) + { + return { .name = name, .value = std::move(value) }; + } Parameter& operator=(const Parameter&) = delete; - explicit Parameter(const std::string& name) : name(name) {} + explicit Parameter(std::string name) : name(std::move(name)) {} }; struct ParameterMap { ValueMap values; - ParameterMap(ValueMap values) : values(values) {} + ParameterMap(ValueMap values) : values(std::move(values)) {} void checkParameterOrder(const std::vector& names) { if (names.size() != values.size()) throw std::runtime_error("Error"); - auto it = values.begin(); for (size_t i = 0; i < values.size(); i++) - { - std::advance(it, i); - if (names[i] != it->first) + if (names[i] != values[i].name) throw std::runtime_error("Error"); - } } void checkParameterNameUnordered(const std::vector& names) { if (names.size() != values.size()) throw std::runtime_error("Error"); - for (const auto& pair : values) - if (std::find(names.begin(), names.end(), pair.first) == names.end()) + for (const auto& obj : values) + if (std::ranges::find(names, obj.name) == names.end()) throw std::runtime_error("Error"); } - void checkParameterTypes() {} - - template - void checkParameterTypes(const std::string& name, const std::type_info* info, const Rest... rest) - { - static_assert((sizeof...(rest) % 2) == 0, "Arguments must come in pairs (std::string, std::type_info*)"); - - if (values.find(name) == values.end()) - throw std::runtime_error("Parameter name not found"); - - const auto& value = values[name]; - if (value.type() != *info) - throw std::runtime_error("Parameter type is wrong"); - checkParameterTypes(rest...); - } - template ValueT getItem(const std::string& name) { - checkParameterTypes(name, &typeid(ValueT)); - return std::any_cast(values.at(name)); + ValuedParameter value; + for (const auto& obj : values) + if (obj.name == name) + value = obj; + if (value.name == "") + throw std::invalid_argument("Name not found"); + return std::any_cast(value.value); } template ValueT getItem(const std::string& name, ValueT fallback) { - // Value not found - if (values.find(name) == values.end()) + ValuedParameter value; + for (const auto& obj : values) + if (obj.name == name) + value = obj; + if (value.name == "") return fallback; - - // checkParameterTypes(name, &typeid(ValueT)); - return std::any_cast(values.at(name)); + return std::any_cast(value.value); } }; - Parameter operator""_p(const char* name, size_t) { return Parameter(name); } + Parameter operator""_p(const char* name, size_t /*unused*/) { return Parameter(name); } template ParameterMap processParams(Params... params) { - ValueMap values; - const std::vector vec = { params... }; - - for (const auto& elem : vec) - values[elem.name] = elem.value; - return { values }; + return { std::vector{ params... } }; } } // namespace steppable::__internals::parameter -#define PARAM_GET(map, type, name) \ - type(name); \ - do \ - { \ - name = map.getItem(#name); \ - } while (0) - -#define PARAM_GET_FALLBACK(map, type, name, fallback) \ - type(name); \ +#define PARAM_GET(map, type, name) \ + type name; \ do \ { \ - name = map.getItem(#name, fallback); \ + (name) = (map).template getItem(#name); \ + } while (0) + +#define PARAM_GET_FALLBACK(map, type, name, fallback) \ + type name; \ + do \ + { \ + (name) = (map).template getItem(#name, fallback); \ } while (0) diff --git a/src/matrix/ref/ref.cpp b/src/matrix/ref/ref.cpp index 65c49a0b4..5d1e4abbd 100644 --- a/src/matrix/ref/ref.cpp +++ b/src/matrix/ref/ref.cpp @@ -31,6 +31,7 @@ #include "conPlot/conPlot.hpp" #include "fn/calc.hpp" #include "refReport.hpp" +#include "steppable/number.hpp" #include "steppable/parameter.hpp" #include @@ -58,10 +59,12 @@ int main() // mat = mat.rref(); // std::cout << mat.present(1) << "\n"; - graphing::conPlot( - { [](const Number& x) { return steppable::__internals::calc::sin(x.present(), 2); }, - [](const Number& x) { return steppable::__internals::calc::cos(x.present(), 2); } }, - { "width"_p = 90, "xMin"_p = -3.14, "xMax"_p = 3.14, "title"_p = "Sine and cosine graphs from -pi to +pi"s }, - { { "title"_p = "Sine"s }, - { "lineColor"_p = colors::red, "lineDot"_p = "*"sv, "title"_p = "Cosine"s } }); + graphing::conPlot({ [](const Number& x) { return steppable::__internals::calc::sin(x.present(), 2); }, + [](const Number& x) { return steppable::__internals::calc::cos(x.present(), 2); } }, + { "width"_p = 90LL, + "xMin"_p = Number(-3.14), + "xMax"_p = Number(3.14), + "title"_p = "Sine and cosine graphs from -pi to +pi"s }, + { { "title"_p = "Sine"s }, + { "lineColor"_p = (ColorFunc)colors::red, "lineDot"_p = "*"sv, "title"_p = "Cosine"s } }); } From a911246c2c4ac05538d8c0e1f7fd4bde996c47cd Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Wed, 2 Jul 2025 11:24:36 +0800 Subject: [PATCH 09/18] Add better detection for Windows Signed-off-by: Andy Zhang --- src/colors.cpp | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/colors.cpp b/src/colors.cpp index 6bd31173e..84f281dca 100644 --- a/src/colors.cpp +++ b/src/colors.cpp @@ -36,6 +36,35 @@ #define isatty _isatty // ReSharper disable once CppInconsistentNaming #define fileno _fileno + +double* sysVersion = nullptr; + +// https://stackoverflow.com/a/52122386/14868780 +/** +* @brief Gets the Windows version as a double. +* @warning This method is only available on Windows. +* @returns The Windows version as double, for example, 10.0. +*/ +double getSysOpType() +{ + if (sysVersion != nullptr) + return *sysVersion; + + double ret = 0; + sysVersion = new double; + NTSTATUS(WINAPI * RtlGetVersion)(LPOSVERSIONINFOEXW); + OSVERSIONINFOEXW osInfo; + + *(FARPROC*)&RtlGetVersion = GetProcAddress(GetModuleHandleA("ntdll"), "RtlGetVersion"); + + if (NULL != RtlGetVersion) + { + osInfo.dwOSVersionInfoSize = sizeof(osInfo); + RtlGetVersion(&osInfo); + ret = (double)osInfo.dwMajorVersion; + } + *sysVersion = ret; +} #else #include #endif @@ -45,7 +74,7 @@ namespace steppable::__internals::utils bool isTerminal(const std::ostream& stream) { #ifdef WINDOWS - if (IsWindows10OrGreater()) + if (getSysOpType() >= 10.0) return isatty(fileno(stdout)) != 0; return false; // The Windows console in Windows 7 does not support ANSI escapes #else From 1198b2646510b336f09c7451d31ab12f06fdf806 Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Wed, 2 Jul 2025 22:59:05 +0800 Subject: [PATCH 10/18] Added documentation and code refactoring - Move out conPlot and parameter.hpp definitions to .cpp files - Fix CMakeLists.txt to avoid duplicate builds - Add documentation to conPlot and parameter.hpp --- .clang-tidy | 4 +- .clangd | 3 +- include/conPlot/conPlot.hpp | 203 ++++------------------ include/conPlot/conPlotInterpolation.hpp | 38 ++++- include/conPlot/conPlotTypes.hpp | 80 +++++++-- include/steppable/parameter.hpp | 150 ++++++++++++---- include/symbols.hpp | 23 ++- include/types/rounding.hpp | 22 +++ src/CMakeLists.txt | 6 +- src/conPlot/conPlot.cpp | 208 +++++++++++++++++++++++ src/steppable/parameter.cpp | 59 +++++++ 11 files changed, 556 insertions(+), 240 deletions(-) create mode 100644 src/conPlot/conPlot.cpp create mode 100644 src/steppable/parameter.cpp diff --git a/.clang-tidy b/.clang-tidy index 6e3af6a2f..65ded0451 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -12,12 +12,10 @@ Checks: ['bugprone-*', 'readability-*', 'zircon-*', # Remove the following: - '-bugprone-easily-swappable-parameters', - '-bugprone-suspicious-semicolon', '-cert-dcl37-c', '-cert-dcl50-cpp', '-cert-dcl51-cpp', - '-cppcoreguidelines-avoid-magic-numbers', + '-cppcoreguidelines-avoid-do-while', '-cppcoreguidelines-pro-bounds-array-to-pointer-decay', '-cppcoreguidelines-pro-type-vararg', '-modernize-use-trailing-return-type', diff --git a/.clangd b/.clangd index 41c6fa14d..ba40c50f4 100644 --- a/.clangd +++ b/.clangd @@ -28,7 +28,8 @@ Diagnostics: 'cert-dcl51-cpp', 'cppcoreguidelines-pro-type-vararg', 'readability-braces-around-statements', - 'readability-identifier-length' + 'readability-identifier-length', + 'cppcoreguidelines-avoid-do-while' ] --- diff --git a/include/conPlot/conPlot.hpp b/include/conPlot/conPlot.hpp index 3383692e4..89846ab5d 100644 --- a/include/conPlot/conPlot.hpp +++ b/include/conPlot/conPlot.hpp @@ -1,184 +1,39 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **************************************************************************************************/ + #include "colors.hpp" -#include "conPlot/conPlotInterpolation.hpp" -#include "conPlotTypes.hpp" -#include "steppable/number.hpp" -#include "symbols.hpp" // Adjust the path as needed +#include "conPlot/conPlotTypes.hpp" +#include "symbols.hpp" -#include #include -#include -#include #include +/** + * @namespace steppable::graphing + * @brief Graphing utilities for showing graphs in the console. + */ namespace steppable::graphing { - void conPlotLine(const Number& xGridSize, - const Number& yGridSize, - const Number& yMax, - const GraphOptions* graphOptions, - const LineOptions* lineOptions, - prettyPrint::ConsoleOutput* canvas, - std::map& fnValues) - { - // Plot function - std::map gridPos; - for (const auto& [i, y] : fnValues) - { - auto gridY = std::stoll(((yMax - y) / yGridSize).present()); - auto gridX = std::stoll(((graphOptions->xMax - i) / xGridSize).present()); - gridPos[gridX] = gridY; - } - if (lineOptions->samplesSpacing > 1) - cubicInterpolateFill(&gridPos, 0, graphOptions->width); - - long long lastGridY = gridPos[0]; - for (const auto& [gridX, gridY] : gridPos) - { - // Plot point - const long long diffY = gridY - lastGridY; - const long long absDiffY = std::abs(diffY); - long long sgn = 1; - if (absDiffY != 0) - sgn = diffY / absDiffY; - if (absDiffY > graphOptions->height / 2) - { - // Create more points to connect the dots - for (long long j = 1; j < absDiffY + 1; j++) - canvas->write(lineOptions->lineDot, - { .x = gridX, .y = 3 + gridY + (-sgn * j) }, - false, - lineOptions->lineColor); - } - canvas->write(lineOptions->lineDot, { .x = gridX, .y = 3 + gridY }, false, lineOptions->lineColor); - lastGridY = 3 + gridY; - } - } - - void conPlotLegend(const GraphOptions* graphOptions, - const std::vector* lineOptions, - prettyPrint::ConsoleOutput* canvas) - { - long long yLoc = graphOptions->height + 7; - canvas->write("Legend", { .x = 0, .y = yLoc++ }, false, formats::bold); - for (size_t fnIdx = 0; fnIdx < lineOptions->size(); fnIdx++) - { - const auto& lineOption = lineOptions->at(fnIdx); - yLoc += static_cast(fnIdx); - canvas->write(lineOption.title, { .x = 0, .y = yLoc }, false); - - for (int i = 0; i < graphOptions->xTickSpacing; i++) - canvas->write(lineOption.lineDot, - { .x = static_cast(lineOption.title.length()) + 1 + i, .y = yLoc }, - false, - lineOption.lineColor); - } - } - void conPlot(const std::vector& f, const GraphOptions& graphOptions, - const std::vector& lineOptions) - { - // Create buffer - prettyPrint::ConsoleOutput canvas( - graphOptions.height + 10, - graphOptions.width + 12 + - __internals::stringUtils::getUnicodeDisplayWidth(graphOptions.xAxisTitle)); // Extra space for labels - std::map> fnValues; - - canvas.write( - graphOptions.title, { .x = 0, .y = 1 }, false, formats::bold, prettyPrint::HorizontalAlignment::CENTER); - - // Sample min/max - Number yMin; - for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx) - { - yMin = f[fnIdx](graphOptions.xMin); - fnValues[fnIdx][graphOptions.xMin] = yMin; - } - Number yMax = yMin; - // 1 grid = 1 character on screen - Number xGridSize = (graphOptions.xMax - graphOptions.xMin) / graphOptions.width; - - // Calculate range of values - for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx) - { - for (long long i = 0; i <= graphOptions.width; ++i) - { - if (i % lineOptions[fnIdx].samplesSpacing == 0) - { - Number x = graphOptions.xMin + Number(i) * xGridSize; - Number y = f[fnIdx](x); - fnValues[fnIdx][x] = y; - yMin = std::min(y, yMin); - yMax = std::max(y, yMax); - } - - // Write base frame - canvas.write(BoxDrawing::HORIZONTAL, { .x = i, .y = 3 + graphOptions.height }); - } - - if ((yMax - yMin).abs() < 1e-12) - { - yMin -= 1.0; - yMax += 1.0; - } - } - - // Axis positions - Number yGridSize = (yMax - yMin) / graphOptions.height; - Number yTickValue = yMax; - - // Write side frame - for (long long j = 0; j < graphOptions.height; ++j) - { - canvas.write(BoxDrawing::VERTICAL, { .x = graphOptions.width, .y = 3 + j }); - yTickValue -= yGridSize; - - // Write y ticks - if (j % graphOptions.yTickSpacing == 0) - { - yTickValue.setPrec(2); - canvas.write(yTickValue.present(), { .x = graphOptions.width + 2, .y = 3 + j }); - for (long long i = 0; i < graphOptions.width; ++i) - canvas.write(BoxDrawing::DOTTED_HORIZONTAL, { .x = i, .y = 3 + j }); - canvas.write(BoxDrawing::VERTICAL_LEFT, { .x = graphOptions.width, .y = 3 + j }); - } - } - canvas.write(BoxDrawing::BOTTOM_RIGHT_CORNER, { .x = graphOptions.width, .y = 3 + graphOptions.height }); - - // Draw grid - for (long long i = 0; i < graphOptions.width - 2; ++i) - { - Number x = graphOptions.xMin + Number(i) * xGridSize; - // Write grid label - if (i % graphOptions.xTickSpacing == 0) - { - // Write vertical gridlines - for (long long j = 0; j < graphOptions.height; ++j) - canvas.write(BoxDrawing::DOTTED_VERTICAL, { .x = i, .y = 3 + j }); - canvas.write(BoxDrawing::HORIZONTAL_UP, { .x = i, .y = 3 + graphOptions.height }); - x.setPrec(2); - canvas.write(x.present(), { .x = i, .y = 3 + graphOptions.height + 1 }); - } - } - - // Axis Titles - canvas.write( - std::string( - (graphOptions.width - __internals::stringUtils::getUnicodeDisplayWidth(graphOptions.xAxisTitle)) / 2, - ' ') + - graphOptions.xAxisTitle, - { .x = 0, .y = 5 + graphOptions.height }, - false, - formats::bold); - canvas.write(graphOptions.yAxisTitle, - { .x = graphOptions.width + 10, .y = (graphOptions.height / 2) + 1 }, - false, - formats::bold); - - for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx) - conPlotLine(xGridSize, yGridSize, yMax, &graphOptions, &lineOptions[fnIdx], &canvas, fnValues[fnIdx]); - conPlotLegend(&graphOptions, &lineOptions, &canvas); - std::cout << canvas.asString() << "\n"; - } + const std::vector& lineOptions); } // namespace steppable::graphing diff --git a/include/conPlot/conPlotInterpolation.hpp b/include/conPlot/conPlotInterpolation.hpp index 611238834..eb0f297b0 100644 --- a/include/conPlot/conPlotInterpolation.hpp +++ b/include/conPlot/conPlotInterpolation.hpp @@ -1,3 +1,25 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **************************************************************************************************/ + #pragma once #include "output.hpp" @@ -12,6 +34,13 @@ using namespace std::literals; namespace steppable::graphing { + /** + * @brief Fill in integral values with linear interpolation. + * + * @param data An x-y corresponding table of values. Must have at least 2 items. + * @param xMin Minimum `x` value. + * @param xMax Maximum `x` value. + */ void linearInterpolateFill(std::map* data, long long xMin, long long xMax) { if (data->size() < 2) @@ -30,7 +59,7 @@ namespace steppable::graphing for (long long x = xMin; x <= xMax; ++x) { // Find the interval [x0, x1] for x (or clamp to endpoints) - auto it = std::lower_bound(xs.begin(), xs.end(), x); + auto it = std::ranges::lower_bound(xs, x); long long idx1 = std::distance(xs.begin(), it); long long i0 = 0; long long i1 = 0; @@ -64,6 +93,13 @@ namespace steppable::graphing } } + /** + * @brief Fill in integral values with cubic interpolation. + * + * @param data An x-y corresponding table of values. Must have at least 4 items. + * @param xMin Minimum `x` value. + * @param xMax Maximum `x` value. + */ void cubicInterpolateFill(std::map* data, long long xMin, long long xMax) { if (data->size() < 4) diff --git a/include/conPlot/conPlotTypes.hpp b/include/conPlot/conPlotTypes.hpp index 284713bda..e1f86755f 100644 --- a/include/conPlot/conPlotTypes.hpp +++ b/include/conPlot/conPlotTypes.hpp @@ -1,3 +1,25 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **************************************************************************************************/ + #pragma once #include "colors.hpp" @@ -15,27 +37,40 @@ namespace steppable::graphing using GraphFn = std::function; + /** + * @namespace steppable::graphing::GraphDot + * @brief Contains string values of dots to be graphed on the screen. + */ namespace GraphDot { - constexpr std::string_view ROUND_DOT = "\u25CF"; - constexpr std::string_view BLOCK = "\u2588"; - constexpr std::string_view LIGHT_BLOCK_1 = "\u2591"; - constexpr std::string_view LIGHT_BLOCK_2 = "\u2592"; - constexpr std::string_view LIGHT_BLOCK_3 = "\u2593"; + constexpr std::string_view ROUND_DOT = "\u25CF"; ///< Round dot + constexpr std::string_view BLOCK = "\u2588"; ///< Solid-filled block + constexpr std::string_view LIGHT_BLOCK_1 = "\u2591"; ///< Lightly-filled block + constexpr std::string_view LIGHT_BLOCK_2 = "\u2592"; ///< Medium-lightly-filled block + constexpr std::string_view LIGHT_BLOCK_3 = "\u2593"; ///< More densly-filled block } // namespace GraphDot + /** + * @struct steppable::graphing::GraphOptions + * @brief Stores the opotions for each graph shown on screen. + */ struct GraphOptions { - Number xMin = -1; - Number xMax = 1; - long long width = 30; - long long height = 20; - long long xTickSpacing = 10; - long long yTickSpacing = 5; - std::string title = "Graph"; - std::string xAxisTitle = "X Axis Title"; - std::string yAxisTitle = "Y Axis Title"; + Number xMin = -1; ///< Minimum `x` value. + Number xMax = 1; ///< Maximum `x` value. + long long width = 30; ///< Width of the graph. + long long height = 20; ///< Height of the graph. + long long xTickSpacing = 10; ///< Spacing between each `x` axis tick. + long long yTickSpacing = 5; ///< Spacing between each `y` axis tick. + std::string title = "Graph"; ///< Title of the graph. + std::string xAxisTitle = "X Axis Title"; ///< Title of the `x` axis. + std::string yAxisTitle = "Y Axis Title"; ///< Title of the `y` axis. + /** + * @brief Initializes a new `GraphOptions` instance. + * + * @param params The parameters to be passed to initialize the instance. + */ template GraphOptions(Params... params) { @@ -63,13 +98,22 @@ namespace steppable::graphing } }; + /** + * @struct steppable::graphing::LineOptions + * @brief Stores options for each line graphed on screen. + */ struct LineOptions { - std::string_view lineDot = GraphDot::BLOCK; - ColorFunc lineColor = colors::green; - std::string title = "Line"; - long long samplesSpacing = 2; + std::string_view lineDot = GraphDot::BLOCK; ///< Dot type to be drawn on screen. + ColorFunc lineColor = colors::green; ///< Color of the dot to output. + std::string title = "Line"; ///< Name of the line to be shown in the legend. + long long samplesSpacing = 2; ///< Frequency to take a sample, units: grids. + /** + * @brief Initializes a new `LineOptions` instance. + * + * @param params The parameters to be passed to initialize the instance. + */ template LineOptions(Params... params) { diff --git a/include/steppable/parameter.hpp b/include/steppable/parameter.hpp index e824f269b..b38192c9d 100644 --- a/include/steppable/parameter.hpp +++ b/include/steppable/parameter.hpp @@ -1,31 +1,71 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **************************************************************************************************/ + #pragma once +#include "output.hpp" +#include "platform.hpp" + #include #include -#include -#include #include -#include -#include #include #include using namespace std::literals; +/** + * @namespace steppable::__internals::parameter + * @brief Contains the parameter utilities to allow named parameters to be passed into functions. + */ namespace steppable::__internals::parameter { + /** + * @struct ValuedParameter + * @brief A parameter with a name and value. + */ struct ValuedParameter { - std::string name; - std::any value; + std::string name; ///< The name of the parameter. + std::any value; ///< The value of the parameter. }; + /// @brief A type containing all the parameters of a function, in calling order. using ValueMap = std::vector; - struct Parameter + /** + * @struct Parameter + * @brief Stores the name of a parameter + */ + struct Parameter // NOLINT(cppcoreguidelines-special-member-functions) { - std::string name; - + std::string name; ///< The name of the parameter. + + /** + * @brief Shorthand function to facilitate setting values of parameters. + * + * @param value The value to set for the parameter. + * @return A `ValuedParameter` object representing the current parameter. + */ template ValuedParameter operator=(T value) // NOLINT(cppcoreguidelines-c-copy-assignment-signature) { @@ -34,34 +74,53 @@ namespace steppable::__internals::parameter Parameter& operator=(const Parameter&) = delete; + /** + * @brief Initializes a new `Parameter` object. + * + * @param name The name of the parameter. + */ explicit Parameter(std::string name) : name(std::move(name)) {} }; + /** + * @struct ParameterMap + * @brief An object containing all parameters and their values passed into a function. + */ struct ParameterMap { - ValueMap values; + ValueMap values; ///< All `ValuedParameter` objects passed to a function. + /** + * @brief Initializes a new `ParameterMap` object. + * + * @param values All `ValuedParameter` objects passed to a function. + */ ParameterMap(ValueMap values) : values(std::move(values)) {} - void checkParameterOrder(const std::vector& names) - { - if (names.size() != values.size()) - throw std::runtime_error("Error"); - - for (size_t i = 0; i < values.size(); i++) - if (names[i] != values[i].name) - throw std::runtime_error("Error"); - } - - void checkParameterNameUnordered(const std::vector& names) - { - if (names.size() != values.size()) - throw std::runtime_error("Error"); - for (const auto& obj : values) - if (std::ranges::find(names, obj.name) == names.end()) - throw std::runtime_error("Error"); - } - + /** + * @brief Checks the presence of all the arguments, in the order which they should be specified. Prints an error + * message and exits the program if one of the parameter names is not specified according to the order, or the + * size of the name vector is not the same as the number of parameters passed into the function. + * + * @param names The names to check. + */ + void checkParameterOrder(const std::vector& names); + + /** + * @brief Checks the presence of all the arguments, no matter which order they are specified. Prints an error + * message and exits the program if one of the parameter names specified is not found, or the size of the name + * vector is not the same as the number of parameters passed into the function. + * + * @param names The names to check. + */ + void checkParameterNameUnordered(const std::vector& names); + + /** + * @brief Gets an item from the current parameter map. + * + * @param name The name of the parameter. + * @return A value from the parameter list. + */ template ValueT getItem(const std::string& name) { @@ -70,12 +129,22 @@ namespace steppable::__internals::parameter if (obj.name == name) value = obj; if (value.name == "") - throw std::invalid_argument("Name not found"); + { + output::error("ParameterMap::getItem"s, "Name {0} not found."s, { name }); + utils::programSafeExit(1); + } return std::any_cast(value.value); } + /** + * @brief Gets an item from the current parameter map. Returns the fallback the matching parameter if not found. + * + * @param name The name of the parameter. + * @param fallback If no matching parameter is specified, returns the fallback. + * @return A value from the parameter list or the fallback. + */ template - ValueT getItem(const std::string& name, ValueT fallback) + ValueT getItem(const std::string& name, const ValueT& fallback) { ValuedParameter value; for (const auto& obj : values) @@ -87,8 +156,21 @@ namespace steppable::__internals::parameter } }; - Parameter operator""_p(const char* name, size_t /*unused*/) { return Parameter(name); } - + /** + * @brief Convert a literal to a parameter, using a shorthand notation. + * + * @param name The name of the parameter, expressed as a simple string literal. + * @return A `Parameter` object to set the value of the parameter. + */ + inline Parameter operator""_p(const char* name, size_t /*unused*/) { return Parameter(name); } + + /** + * @brief Process parameters passed into a function. + * @details Takes in all arguments from a variadic function, and processes them as parameters. + * + * @param params The parameters passed into a function. + * @return An instance of `ParameterMap` containing a list of the parameters. + */ template ParameterMap processParams(Params... params) { @@ -96,6 +178,7 @@ namespace steppable::__internals::parameter } } // namespace steppable::__internals::parameter +/// @brief Get a parameter from a parameter list. #define PARAM_GET(map, type, name) \ type name; \ do \ @@ -103,6 +186,7 @@ namespace steppable::__internals::parameter (name) = (map).template getItem(#name); \ } while (0) +/// @brief Get a parameter from a parameter list. If not found, return the fallback. #define PARAM_GET_FALLBACK(map, type, name, fallback) \ type name; \ do \ diff --git a/include/symbols.hpp b/include/symbols.hpp index ddafc7861..fd3a27a58 100644 --- a/include/symbols.hpp +++ b/include/symbols.hpp @@ -402,20 +402,31 @@ namespace steppable::__internals::symbols */ std::string makeSurd(const std::string& radicand); + /** + * @namespace steppable::__internals::symbols::BoxDrawing + * @brief Defines easy ascess to Unicode box-drawing characters. + */ namespace BoxDrawing { - constexpr std::string_view DOTTED_VERTICAL = "\u2575"; - constexpr std::string_view DOTTED_HORIZONTAL = "\u2574"; + constexpr std::string_view DOTTED_VERTICAL = "\u2575"; ///< Dashed vertical line + constexpr std::string_view DOTTED_HORIZONTAL = "\u2574"; ///< Dashed horizontal line - constexpr std::string_view HORIZONTAL = "\u2500"; - constexpr std::string_view VERTICAL = "\u2502"; + constexpr std::string_view HORIZONTAL = "\u2500"; ///< Solid horizontal line + constexpr std::string_view VERTICAL = "\u2502"; ///< Solid vertical line + // region CONNECTORS + /// @brief Horizontal line that connects to a vertical one, i.e., in `_|_` shape. constexpr std::string_view HORIZONTAL_UP = "\u2534"; + + /// @brief Vertical line that connects to a left one, i.e., in `-|` shape. constexpr std::string_view VERTICAL_LEFT = "\u2524"; + // endregion - constexpr std::string_view BOTTOM_RIGHT_CORNER = "\u2518"; + // region CORNERS + constexpr std::string_view BOTTOM_RIGHT_CORNER = "\u2518"; ///< The bottom right corner + // endregion - constexpr std::string_view CROSS = "\u253C"; + constexpr std::string_view CROSS = "\u253C"; ///< A combining cross between a horizontal and vertical line. } // namespace BoxDrawing } // namespace steppable::__internals::symbols diff --git a/include/types/rounding.hpp b/include/types/rounding.hpp index 94a9f8b08..cf1478872 100644 --- a/include/types/rounding.hpp +++ b/include/types/rounding.hpp @@ -1,3 +1,25 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **************************************************************************************************/ + #pragma once #include diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 112145fc5..97425a7af 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,7 +35,7 @@ ADD_LIBRARY( ) SET_TARGET_PROPERTIES(util PROPERTIES POSITION_INDEPENDENT_CODE ON) -ADD_LIBRARY(steppable STATIC steppable/number.cpp steppable/fraction.cpp steppable/mat2d.cpp) +ADD_LIBRARY(steppable STATIC steppable/number.cpp steppable/fraction.cpp steppable/mat2d.cpp steppable/parameter.cpp) SET_TARGET_PROPERTIES(steppable PROPERTIES POSITION_INDEPENDENT_CODE ON) SET(CALCULATOR_FILES) @@ -63,9 +63,7 @@ ENDFOREACH() # FUNC: Library containing all Steppable functions. ADD_LIBRARY(func STATIC ${CALCULATOR_FILES} - steppable/fraction.cpp - steppable/mat2d.cpp - steppable/number.cpp + conPlot/conPlot.cpp rounding.cpp factors.cpp) SET_TARGET_PROPERTIES(func PROPERTIES POSITION_INDEPENDENT_CODE ON) diff --git a/src/conPlot/conPlot.cpp b/src/conPlot/conPlot.cpp new file mode 100644 index 000000000..140680fe8 --- /dev/null +++ b/src/conPlot/conPlot.cpp @@ -0,0 +1,208 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **************************************************************************************************/ + +#include "conPlot/conPlot.hpp" + +#include "conPlot/conPlotInterpolation.hpp" +#include "conPlot/conPlotTypes.hpp" +#include "steppable/number.hpp" +#include "symbols.hpp" // Adjust the path as needed + +#include +#include +#include + +namespace steppable::graphing +{ + namespace + { + void conPlotLegend(const GraphOptions* graphOptions, + const std::vector* lineOptions, + prettyPrint::ConsoleOutput* canvas) + { + long long yLoc = graphOptions->height + 7; + canvas->write("Legend", { .x = 0, .y = yLoc++ }, false, formats::bold); + for (size_t fnIdx = 0; fnIdx < lineOptions->size(); fnIdx++) + { + const auto& lineOption = lineOptions->at(fnIdx); + yLoc += static_cast(fnIdx); + canvas->write(lineOption.title, { .x = 0, .y = yLoc }, false); + + for (int i = 0; i < graphOptions->xTickSpacing; i++) + canvas->write(lineOption.lineDot, + { .x = static_cast(lineOption.title.length()) + 1 + i, .y = yLoc }, + false, + lineOption.lineColor); + } + } + + void conPlotLine(const Number& xGridSize, + const Number& yGridSize, + const Number& yMax, + const GraphOptions* graphOptions, + const LineOptions* lineOptions, + prettyPrint::ConsoleOutput* canvas, + std::map& fnValues) + { + // Plot function + std::map gridPos; + for (const auto& [i, y] : fnValues) + { + auto gridY = std::stoll(((yMax - y) / yGridSize).present()); + auto gridX = std::stoll(((graphOptions->xMax - i) / xGridSize).present()); + gridPos[gridX] = gridY; + } + if (lineOptions->samplesSpacing > 1) + cubicInterpolateFill(&gridPos, 0, graphOptions->width); + + long long lastGridY = gridPos[0]; + for (const auto& [gridX, gridY] : gridPos) + { + // Plot point + const long long diffY = gridY - lastGridY; + const long long absDiffY = std::abs(diffY); + long long sgn = 1; + if (absDiffY != 0) + sgn = diffY / absDiffY; + if (absDiffY > graphOptions->height / 2) + { + // Create more points to connect the dots + for (long long j = 1; j < absDiffY + 1; j++) + canvas->write(lineOptions->lineDot, + { .x = gridX, .y = 3 + gridY + (-sgn * j) }, + false, + lineOptions->lineColor); + } + canvas->write(lineOptions->lineDot, { .x = gridX, .y = 3 + gridY }, false, lineOptions->lineColor); + lastGridY = 3 + gridY; + } + } + } // namespace + + void conPlot(const std::vector& f, + const GraphOptions& graphOptions, + const std::vector& lineOptions) + { + // Create buffer + prettyPrint::ConsoleOutput canvas( + graphOptions.height + 10, + graphOptions.width + 12 + + __internals::stringUtils::getUnicodeDisplayWidth(graphOptions.xAxisTitle)); // Extra space for labels + std::map> fnValues; + + canvas.write( + graphOptions.title, { .x = 0, .y = 1 }, false, formats::bold, prettyPrint::HorizontalAlignment::CENTER); + + // Sample min/max + Number yMin; + for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx) + { + yMin = f[fnIdx](graphOptions.xMin); + fnValues[fnIdx][graphOptions.xMin] = yMin; + } + Number yMax = yMin; + // 1 grid = 1 character on screen + Number xGridSize = (graphOptions.xMax - graphOptions.xMin) / graphOptions.width; + + // Calculate range of values + for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx) + { + for (long long i = 0; i <= graphOptions.width; ++i) + { + if (i % lineOptions[fnIdx].samplesSpacing == 0) + { + Number x = graphOptions.xMin + Number(i) * xGridSize; + Number y = f[fnIdx](x); + fnValues[fnIdx][x] = y; + yMin = std::min(y, yMin); + yMax = std::max(y, yMax); + } + + // Write base frame + canvas.write(BoxDrawing::HORIZONTAL, { .x = i, .y = 3 + graphOptions.height }); + } + + if ((yMax - yMin).abs() < 1e-12) + { + yMin -= 1.0; + yMax += 1.0; + } + } + + // Axis positions + Number yGridSize = (yMax - yMin) / graphOptions.height; + Number yTickValue = yMax; + + // Write side frame + for (long long j = 0; j < graphOptions.height; ++j) + { + canvas.write(BoxDrawing::VERTICAL, { .x = graphOptions.width, .y = 3 + j }); + yTickValue -= yGridSize; + + // Write y ticks + if (j % graphOptions.yTickSpacing == 0) + { + yTickValue.setPrec(2); + canvas.write(yTickValue.present(), { .x = graphOptions.width + 2, .y = 3 + j }); + for (long long i = 0; i < graphOptions.width; ++i) + canvas.write(BoxDrawing::DOTTED_HORIZONTAL, { .x = i, .y = 3 + j }); + canvas.write(BoxDrawing::VERTICAL_LEFT, { .x = graphOptions.width, .y = 3 + j }); + } + } + canvas.write(BoxDrawing::BOTTOM_RIGHT_CORNER, { .x = graphOptions.width, .y = 3 + graphOptions.height }); + + // Draw grid + for (long long i = 0; i < graphOptions.width - 2; ++i) + { + Number x = graphOptions.xMin + Number(i) * xGridSize; + // Write grid label + if (i % graphOptions.xTickSpacing == 0) + { + // Write vertical gridlines + for (long long j = 0; j < graphOptions.height; ++j) + canvas.write(BoxDrawing::DOTTED_VERTICAL, { .x = i, .y = 3 + j }); + canvas.write(BoxDrawing::HORIZONTAL_UP, { .x = i, .y = 3 + graphOptions.height }); + x.setPrec(2); + canvas.write(x.present(), { .x = i, .y = 3 + graphOptions.height + 1 }); + } + } + + // Axis Titles + canvas.write( + std::string( + (graphOptions.width - __internals::stringUtils::getUnicodeDisplayWidth(graphOptions.xAxisTitle)) / 2, + ' ') + + graphOptions.xAxisTitle, + { .x = 0, .y = 5 + graphOptions.height }, + false, + formats::bold); + canvas.write(graphOptions.yAxisTitle, + { .x = graphOptions.width + 10, .y = (graphOptions.height / 2) + 1 }, + false, + formats::bold); + + for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx) + conPlotLine(xGridSize, yGridSize, yMax, &graphOptions, &lineOptions[fnIdx], &canvas, fnValues[fnIdx]); + conPlotLegend(&graphOptions, &lineOptions, &canvas); + std::cout << canvas.asString() << "\n"; + } +} // namespace steppable::graphing diff --git a/src/steppable/parameter.cpp b/src/steppable/parameter.cpp new file mode 100644 index 000000000..0014970d2 --- /dev/null +++ b/src/steppable/parameter.cpp @@ -0,0 +1,59 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **************************************************************************************************/ + +#include "steppable/parameter.hpp" + +namespace steppable::__internals::parameter +{ + void ParameterMap::checkParameterOrder(const std::vector& names) + { + if (names.size() != values.size()) + { + output::error("ParameterMap::checkParameterOrder"s, "Incorrect name vector size."s); + utils::programSafeExit(1); + } + + for (size_t i = 0; i < values.size(); i++) + if (names[i] != values[i].name) + { + output::error("ParameterMap::checkParameterOrder"s, + "Name {0} mismatch. Expect {1}"s, + { values[i].name, names[i] }); + utils::programSafeExit(1); + } + } + + void ParameterMap::checkParameterNameUnordered(const std::vector& names) + { + if (names.size() != values.size()) + { + output::error("ParameterMap::checkParameterNameUnordered"s, "Incorrect name vector size."s); + utils::programSafeExit(1); + } + for (const auto& obj : values) + if (std::ranges::find(names, obj.name) == names.end()) + { + output::error("ParameterMap::checkParameterNameUnordered"s, "Name {0} not found."s, { obj.name }); + utils::programSafeExit(1); + } + } +} From 60644157a612a5d05dedf9f7640471d87f7289f9 Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Wed, 2 Jul 2025 23:07:26 +0800 Subject: [PATCH 11/18] Fix build errors --- src/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 97425a7af..8c4e4fada 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,7 +35,7 @@ ADD_LIBRARY( ) SET_TARGET_PROPERTIES(util PROPERTIES POSITION_INDEPENDENT_CODE ON) -ADD_LIBRARY(steppable STATIC steppable/number.cpp steppable/fraction.cpp steppable/mat2d.cpp steppable/parameter.cpp) +ADD_LIBRARY(steppable STATIC steppable/number.cpp steppable/fraction.cpp steppable/mat2d.cpp) SET_TARGET_PROPERTIES(steppable PROPERTIES POSITION_INDEPENDENT_CODE ON) SET(CALCULATOR_FILES) @@ -63,6 +63,9 @@ ENDFOREACH() # FUNC: Library containing all Steppable functions. ADD_LIBRARY(func STATIC ${CALCULATOR_FILES} + steppable/fraction.cpp + steppable/mat2d.cpp + steppable/number.cpp conPlot/conPlot.cpp rounding.cpp factors.cpp) From 1c19951a8940ac852c9818b651a8b4f791b88d34 Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Thu, 3 Jul 2025 08:31:49 +0800 Subject: [PATCH 12/18] Refactoring: Test files are grouped by component --- CMakeLists.txt | 15 +- tests/CMakeLists.txt | 27 +- tests/{ => testBase}/testBaseConvert.cpp | 0 tests/{ => testBase}/testBaseConvert.vcxproj | 350 +++++++++--------- tests/{ => testBase}/testDecimalConvert.cpp | 0 .../{ => testBase}/testDecimalConvert.vcxproj | 350 +++++++++--------- tests/{ => testCalc}/testAbs.cpp | 0 tests/{ => testCalc}/testAbs.vcxproj | 330 ++++++++--------- tests/{ => testCalc}/testAdd.cpp | 0 tests/{ => testCalc}/testAdd.vcxproj | 0 tests/{ => testCalc}/testAtan2.cpp | 0 tests/{ => testCalc}/testComparison.cpp | 0 tests/{ => testCalc}/testComparison.vcxproj | 0 tests/{ => testCalc}/testDivision.cpp | 0 tests/{ => testCalc}/testDivision.vcxproj | 0 tests/{ => testCalc}/testFactorial.cpp | 0 tests/{ => testCalc}/testHyp.cpp | 0 tests/{ => testCalc}/testLog.cpp | 0 tests/{ => testCalc}/testMultiply.cpp | 0 tests/{ => testCalc}/testMultiply.vcxproj | 0 tests/{ => testCalc}/testPower.cpp | 0 tests/{ => testCalc}/testPower.vcxproj | 344 ++++++++--------- tests/{ => testCalc}/testRoot.cpp | 0 tests/{ => testCalc}/testRoot.vcxproj | 0 tests/{ => testCalc}/testSubtract.cpp | 0 tests/{ => testCalc}/testSubtract.vcxproj | 0 tests/{ => testCalc}/testTrig.cpp | 0 tests/{ => testCalc}/testTrig.vcxproj | 0 tests/{ => testCalculus}/testNInt.cpp | 0 tests/{ => testMatrix}/testRef.cpp | 0 tests/{ => testSteppable}/testFactors.cpp | 0 tests/{ => testSteppable}/testFactors.vcxproj | 0 tests/{ => testSteppable}/testFormat.cpp | 0 tests/{ => testSteppable}/testFraction.cpp | 0 tests/{ => testSteppable}/testMat2d.cpp | 0 tests/{ => testSteppable}/testNumber.cpp | 0 tests/{ => testSteppable}/testUtil.cpp | 0 tests/{ => testSteppable}/testUtil.vcxproj | 0 38 files changed, 710 insertions(+), 706 deletions(-) rename tests/{ => testBase}/testBaseConvert.cpp (100%) rename tests/{ => testBase}/testBaseConvert.vcxproj (98%) rename tests/{ => testBase}/testDecimalConvert.cpp (100%) rename tests/{ => testBase}/testDecimalConvert.vcxproj (98%) rename tests/{ => testCalc}/testAbs.cpp (100%) rename tests/{ => testCalc}/testAbs.vcxproj (98%) rename tests/{ => testCalc}/testAdd.cpp (100%) rename tests/{ => testCalc}/testAdd.vcxproj (100%) rename tests/{ => testCalc}/testAtan2.cpp (100%) rename tests/{ => testCalc}/testComparison.cpp (100%) rename tests/{ => testCalc}/testComparison.vcxproj (100%) rename tests/{ => testCalc}/testDivision.cpp (100%) rename tests/{ => testCalc}/testDivision.vcxproj (100%) rename tests/{ => testCalc}/testFactorial.cpp (100%) rename tests/{ => testCalc}/testHyp.cpp (100%) rename tests/{ => testCalc}/testLog.cpp (100%) rename tests/{ => testCalc}/testMultiply.cpp (100%) rename tests/{ => testCalc}/testMultiply.vcxproj (100%) rename tests/{ => testCalc}/testPower.cpp (100%) rename tests/{ => testCalc}/testPower.vcxproj (98%) rename tests/{ => testCalc}/testRoot.cpp (100%) rename tests/{ => testCalc}/testRoot.vcxproj (100%) rename tests/{ => testCalc}/testSubtract.cpp (100%) rename tests/{ => testCalc}/testSubtract.vcxproj (100%) rename tests/{ => testCalc}/testTrig.cpp (100%) rename tests/{ => testCalc}/testTrig.vcxproj (100%) rename tests/{ => testCalculus}/testNInt.cpp (100%) rename tests/{ => testMatrix}/testRef.cpp (100%) rename tests/{ => testSteppable}/testFactors.cpp (100%) rename tests/{ => testSteppable}/testFactors.vcxproj (100%) rename tests/{ => testSteppable}/testFormat.cpp (100%) rename tests/{ => testSteppable}/testFraction.cpp (100%) rename tests/{ => testSteppable}/testMat2d.cpp (100%) rename tests/{ => testSteppable}/testNumber.cpp (100%) rename tests/{ => testSteppable}/testUtil.cpp (100%) rename tests/{ => testSteppable}/testUtil.vcxproj (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d49f8fc60..a9a09a131 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,20 +115,7 @@ SET(COMPONENTS # NEW_COMPONENT: PATCH Do NOT remove the previous comment. SET(TARGETS ${COMPONENTS} util) -SET(TEST_TARGETS_TEMP util fraction number mat2d factors format ${COMPONENTS}) - -FOREACH(TEST_TARGET IN LISTS TEST_TARGETS_TEMP) - SET(TARGET_NAME "test") - STRING(REPLACE "::" ";" COMPONENT_PARTS ${TEST_TARGET}) - LIST(LENGTH COMPONENT_PARTS LEN) - IF(LEN EQUAL 2) - LIST(GET COMPONENT_PARTS 1 TEST_TARGET) - ENDIF() - - CAPITALIZE(${TEST_TARGET} FILE_NAME) - STRING(CONCAT TARGET_NAME ${TARGET_NAME} ${FILE_NAME}) - LIST(APPEND TEST_TARGETS ${TARGET_NAME}) -ENDFOREACH() +SET(TEST_TARGETS_TEMP steppable::util steppable::fraction steppable::number steppable::mat2d steppable::factors steppable::format ${COMPONENTS}) ADD_SUBDIRECTORY(src/) ADD_SUBDIRECTORY(lib/) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2e25b6389..c604cd149 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -22,12 +22,29 @@ ENABLE_TESTING() -FOREACH(ITEM IN LISTS TEST_TARGETS) - ADD_EXECUTABLE(${ITEM} ${ITEM}.cpp) - TARGET_LINK_LIBRARIES(${ITEM} PRIVATE func) - ADD_TEST(NAME ${ITEM} COMMAND ${CMAKE_BINARY_DIR}/tests/${ITEM}) - MESSAGE(TRACE "Added test case: ${ITEM}: ${ITEM}.cpp") +FOREACH(TEST_TARGET IN LISTS TEST_TARGETS_TEMP) + SET(TARGET_NAME "test") + STRING(REPLACE "::" ";" TEST_TARGET_PARTS ${TEST_TARGET}) + LIST(GET TEST_TARGET_PARTS 0 DIR_NAME) + LIST(GET TEST_TARGET_PARTS 1 FILE_NAME) + STRING(REPLACE "::" "_" TEST_TARGET ${TEST_TARGET}) + + CAPITALIZE(${DIR_NAME} DIR_NAME) + CAPITALIZE(${FILE_NAME} FILE_NAME) + CAPITALIZE(${TEST_TARGET} TEST_TARGET) + STRING(CONCAT DIR_NAME "test" ${DIR_NAME}) + STRING(CONCAT FILE_NAME "test" ${FILE_NAME}) + STRING(CONCAT TEST_TARGET "test" ${TEST_TARGET}) + + ADD_EXECUTABLE(${TEST_TARGET} ${DIR_NAME}/${FILE_NAME}.cpp) + TARGET_LINK_LIBRARIES(${TEST_TARGET} PRIVATE func) + ADD_TEST(NAME ${TEST_TARGET} COMMAND ${CMAKE_BINARY_DIR}/tests/${TEST_TARGET}) + + LIST(APPEND TEST_TARGETS ${TEST_TARGET}) + MESSAGE(TRACE "Added test case: ${TEST_TARGET}: ${DIR_NAME}/${FILE_NAME}.cpp") ENDFOREACH() +SET(TEST_TARGETS ${TEST_TARGETS} PARENT_SCOPE) + ADD_CUSTOM_TARGET(tests) ADD_DEPENDENCIES(tests ${TEST_TARGETS}) diff --git a/tests/testBaseConvert.cpp b/tests/testBase/testBaseConvert.cpp similarity index 100% rename from tests/testBaseConvert.cpp rename to tests/testBase/testBaseConvert.cpp diff --git a/tests/testBaseConvert.vcxproj b/tests/testBase/testBaseConvert.vcxproj similarity index 98% rename from tests/testBaseConvert.vcxproj rename to tests/testBase/testBaseConvert.vcxproj index 516edd035..b4531fb15 100644 --- a/tests/testBaseConvert.vcxproj +++ b/tests/testBase/testBaseConvert.vcxproj @@ -1,176 +1,176 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {F29B13B1-5B1D-46D6-BC8E-B0A882BC762F} - testAdd - 10.0 - - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(SolutionDir)$(Configuration)\$(ProjectName).build\ - - - false - $(SolutionDir)$(Configuration)\$(ProjectName).build\ - - - true - $(SolutionDir)$(Configuration)\ - $(SolutionDir)$(Configuration)\$(ProjectName).build\ - - - false - $(SolutionDir)$(Configuration)\ - $(SolutionDir)$(Configuration)\$(ProjectName).build\ - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - $(SolutionDir)\include - /utf-8 %(AdditionalOptions) - - - Console - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - $(SolutionDir)\include - /utf-8 %(AdditionalOptions) - - - Console - true - true - true - - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - $(SolutionDir)\include - /utf-8 %(AdditionalOptions) - - - Console - true - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - $(SolutionDir)\include - /utf-8 %(AdditionalOptions) - - - Console - true - true - true - - - - - - - - {867a7b37-c961-4581-ae5b-067fd464cc7c} - - - {0a900aaa-ac12-48cf-85cf-8bb0c0130512} - - - {f6af63f8-9e0f-4da2-89b3-3096b56ba251} - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {F29B13B1-5B1D-46D6-BC8E-B0A882BC762F} + testAdd + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Configuration)\$(ProjectName).build\ + + + false + $(SolutionDir)$(Configuration)\$(ProjectName).build\ + + + true + $(SolutionDir)$(Configuration)\ + $(SolutionDir)$(Configuration)\$(ProjectName).build\ + + + false + $(SolutionDir)$(Configuration)\ + $(SolutionDir)$(Configuration)\$(ProjectName).build\ + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(SolutionDir)\include + /utf-8 %(AdditionalOptions) + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(SolutionDir)\include + /utf-8 %(AdditionalOptions) + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(SolutionDir)\include + /utf-8 %(AdditionalOptions) + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(SolutionDir)\include + /utf-8 %(AdditionalOptions) + + + Console + true + true + true + + + + + + + + {867a7b37-c961-4581-ae5b-067fd464cc7c} + + + {0a900aaa-ac12-48cf-85cf-8bb0c0130512} + + + {f6af63f8-9e0f-4da2-89b3-3096b56ba251} + + + + + \ No newline at end of file diff --git a/tests/testDecimalConvert.cpp b/tests/testBase/testDecimalConvert.cpp similarity index 100% rename from tests/testDecimalConvert.cpp rename to tests/testBase/testDecimalConvert.cpp diff --git a/tests/testDecimalConvert.vcxproj b/tests/testBase/testDecimalConvert.vcxproj similarity index 98% rename from tests/testDecimalConvert.vcxproj rename to tests/testBase/testDecimalConvert.vcxproj index 56fc63aef..9acaa70fe 100644 --- a/tests/testDecimalConvert.vcxproj +++ b/tests/testBase/testDecimalConvert.vcxproj @@ -1,176 +1,176 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {F74A691E-785E-43F1-9D99-6474918BD7DE} - testAdd - 10.0 - - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(SolutionDir)$(Configuration)\$(ProjectName).build\ - - - false - $(SolutionDir)$(Configuration)\$(ProjectName).build\ - - - true - $(SolutionDir)$(Configuration)\ - $(SolutionDir)$(Configuration)\$(ProjectName).build\ - - - false - $(SolutionDir)$(Configuration)\ - $(SolutionDir)$(Configuration)\$(ProjectName).build\ - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - $(SolutionDir)\include - /utf-8 %(AdditionalOptions) - - - Console - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - $(SolutionDir)\include - /utf-8 %(AdditionalOptions) - - - Console - true - true - true - - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - $(SolutionDir)\include - /utf-8 %(AdditionalOptions) - - - Console - true - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - $(SolutionDir)\include - /utf-8 %(AdditionalOptions) - - - Console - true - true - true - - - - - - - - {867a7b37-c961-4581-ae5b-067fd464cc7c} - - - {0a900aaa-ac12-48cf-85cf-8bb0c0130512} - - - {f6af63f8-9e0f-4da2-89b3-3096b56ba251} - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {F74A691E-785E-43F1-9D99-6474918BD7DE} + testAdd + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Configuration)\$(ProjectName).build\ + + + false + $(SolutionDir)$(Configuration)\$(ProjectName).build\ + + + true + $(SolutionDir)$(Configuration)\ + $(SolutionDir)$(Configuration)\$(ProjectName).build\ + + + false + $(SolutionDir)$(Configuration)\ + $(SolutionDir)$(Configuration)\$(ProjectName).build\ + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(SolutionDir)\include + /utf-8 %(AdditionalOptions) + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(SolutionDir)\include + /utf-8 %(AdditionalOptions) + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(SolutionDir)\include + /utf-8 %(AdditionalOptions) + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(SolutionDir)\include + /utf-8 %(AdditionalOptions) + + + Console + true + true + true + + + + + + + + {867a7b37-c961-4581-ae5b-067fd464cc7c} + + + {0a900aaa-ac12-48cf-85cf-8bb0c0130512} + + + {f6af63f8-9e0f-4da2-89b3-3096b56ba251} + + + + + \ No newline at end of file diff --git a/tests/testAbs.cpp b/tests/testCalc/testAbs.cpp similarity index 100% rename from tests/testAbs.cpp rename to tests/testCalc/testAbs.cpp diff --git a/tests/testAbs.vcxproj b/tests/testCalc/testAbs.vcxproj similarity index 98% rename from tests/testAbs.vcxproj rename to tests/testCalc/testAbs.vcxproj index eef79804b..3492adb03 100644 --- a/tests/testAbs.vcxproj +++ b/tests/testCalc/testAbs.vcxproj @@ -1,166 +1,166 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {E84B0149-B3A3-41AA-AC7B-FC13A7FB02D8} - testAdd - 10.0 - - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - - - false - - - true - $(SolutionDir)$(Configuration)\ - $(SolutionDir)$(Configuration)\$(ProjectName).build\ - - - false - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - $(SolutionDir)\include - stdcpp20 - - - Console - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - $(SolutionDir)\include - stdcpp20 - - - Console - true - true - true - - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - $(SolutionDir)\include - stdcpp20 - /utf-8 %(AdditionalOptions) - - - Console - true - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - $(SolutionDir)\include - stdcpp20 - - - Console - true - true - true - - - - - {0a900aaa-ac12-48cf-85cf-8bb0c0130512} - - - {f6af63f8-9e0f-4da2-89b3-3096b56ba251} - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {E84B0149-B3A3-41AA-AC7B-FC13A7FB02D8} + testAdd + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + $(SolutionDir)$(Configuration)\ + $(SolutionDir)$(Configuration)\$(ProjectName).build\ + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)\include + stdcpp20 + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)\include + stdcpp20 + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)\include + stdcpp20 + /utf-8 %(AdditionalOptions) + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)\include + stdcpp20 + + + Console + true + true + true + + + + + {0a900aaa-ac12-48cf-85cf-8bb0c0130512} + + + {f6af63f8-9e0f-4da2-89b3-3096b56ba251} + + + + + + + + \ No newline at end of file diff --git a/tests/testAdd.cpp b/tests/testCalc/testAdd.cpp similarity index 100% rename from tests/testAdd.cpp rename to tests/testCalc/testAdd.cpp diff --git a/tests/testAdd.vcxproj b/tests/testCalc/testAdd.vcxproj similarity index 100% rename from tests/testAdd.vcxproj rename to tests/testCalc/testAdd.vcxproj diff --git a/tests/testAtan2.cpp b/tests/testCalc/testAtan2.cpp similarity index 100% rename from tests/testAtan2.cpp rename to tests/testCalc/testAtan2.cpp diff --git a/tests/testComparison.cpp b/tests/testCalc/testComparison.cpp similarity index 100% rename from tests/testComparison.cpp rename to tests/testCalc/testComparison.cpp diff --git a/tests/testComparison.vcxproj b/tests/testCalc/testComparison.vcxproj similarity index 100% rename from tests/testComparison.vcxproj rename to tests/testCalc/testComparison.vcxproj diff --git a/tests/testDivision.cpp b/tests/testCalc/testDivision.cpp similarity index 100% rename from tests/testDivision.cpp rename to tests/testCalc/testDivision.cpp diff --git a/tests/testDivision.vcxproj b/tests/testCalc/testDivision.vcxproj similarity index 100% rename from tests/testDivision.vcxproj rename to tests/testCalc/testDivision.vcxproj diff --git a/tests/testFactorial.cpp b/tests/testCalc/testFactorial.cpp similarity index 100% rename from tests/testFactorial.cpp rename to tests/testCalc/testFactorial.cpp diff --git a/tests/testHyp.cpp b/tests/testCalc/testHyp.cpp similarity index 100% rename from tests/testHyp.cpp rename to tests/testCalc/testHyp.cpp diff --git a/tests/testLog.cpp b/tests/testCalc/testLog.cpp similarity index 100% rename from tests/testLog.cpp rename to tests/testCalc/testLog.cpp diff --git a/tests/testMultiply.cpp b/tests/testCalc/testMultiply.cpp similarity index 100% rename from tests/testMultiply.cpp rename to tests/testCalc/testMultiply.cpp diff --git a/tests/testMultiply.vcxproj b/tests/testCalc/testMultiply.vcxproj similarity index 100% rename from tests/testMultiply.vcxproj rename to tests/testCalc/testMultiply.vcxproj diff --git a/tests/testPower.cpp b/tests/testCalc/testPower.cpp similarity index 100% rename from tests/testPower.cpp rename to tests/testCalc/testPower.cpp diff --git a/tests/testPower.vcxproj b/tests/testCalc/testPower.vcxproj similarity index 98% rename from tests/testPower.vcxproj rename to tests/testCalc/testPower.vcxproj index 8c8ac6560..77a344e46 100644 --- a/tests/testPower.vcxproj +++ b/tests/testCalc/testPower.vcxproj @@ -1,173 +1,173 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {AD080EEA-19BA-4944-B8ED-2B21F1E5F0D7} - testAdd - 10.0 - - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(SolutionDir)$(Configuration)\$(ProjectName).build\ - - - false - $(SolutionDir)$(Configuration)\$(ProjectName).build\ - - - true - $(SolutionDir)$(Configuration)\ - $(SolutionDir)$(Configuration)\$(ProjectName).build\ - - - false - $(SolutionDir)$(Configuration)\ - $(SolutionDir)$(Configuration)\$(ProjectName).build\ - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - $(SolutionDir)\include - /utf-8 %(AdditionalOptions) - - - Console - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - $(SolutionDir)\include - /utf-8 %(AdditionalOptions) - - - Console - true - true - true - - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - $(SolutionDir)\include - /utf-8 %(AdditionalOptions) - - - Console - true - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - $(SolutionDir)\include - /utf-8 %(AdditionalOptions) - - - Console - true - true - true - - - - - {0a900aaa-ac12-48cf-85cf-8bb0c0130512} - - - {f6af63f8-9e0f-4da2-89b3-3096b56ba251} - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {AD080EEA-19BA-4944-B8ED-2B21F1E5F0D7} + testAdd + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Configuration)\$(ProjectName).build\ + + + false + $(SolutionDir)$(Configuration)\$(ProjectName).build\ + + + true + $(SolutionDir)$(Configuration)\ + $(SolutionDir)$(Configuration)\$(ProjectName).build\ + + + false + $(SolutionDir)$(Configuration)\ + $(SolutionDir)$(Configuration)\$(ProjectName).build\ + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(SolutionDir)\include + /utf-8 %(AdditionalOptions) + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(SolutionDir)\include + /utf-8 %(AdditionalOptions) + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(SolutionDir)\include + /utf-8 %(AdditionalOptions) + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(SolutionDir)\include + /utf-8 %(AdditionalOptions) + + + Console + true + true + true + + + + + {0a900aaa-ac12-48cf-85cf-8bb0c0130512} + + + {f6af63f8-9e0f-4da2-89b3-3096b56ba251} + + + + + + + + \ No newline at end of file diff --git a/tests/testRoot.cpp b/tests/testCalc/testRoot.cpp similarity index 100% rename from tests/testRoot.cpp rename to tests/testCalc/testRoot.cpp diff --git a/tests/testRoot.vcxproj b/tests/testCalc/testRoot.vcxproj similarity index 100% rename from tests/testRoot.vcxproj rename to tests/testCalc/testRoot.vcxproj diff --git a/tests/testSubtract.cpp b/tests/testCalc/testSubtract.cpp similarity index 100% rename from tests/testSubtract.cpp rename to tests/testCalc/testSubtract.cpp diff --git a/tests/testSubtract.vcxproj b/tests/testCalc/testSubtract.vcxproj similarity index 100% rename from tests/testSubtract.vcxproj rename to tests/testCalc/testSubtract.vcxproj diff --git a/tests/testTrig.cpp b/tests/testCalc/testTrig.cpp similarity index 100% rename from tests/testTrig.cpp rename to tests/testCalc/testTrig.cpp diff --git a/tests/testTrig.vcxproj b/tests/testCalc/testTrig.vcxproj similarity index 100% rename from tests/testTrig.vcxproj rename to tests/testCalc/testTrig.vcxproj diff --git a/tests/testNInt.cpp b/tests/testCalculus/testNInt.cpp similarity index 100% rename from tests/testNInt.cpp rename to tests/testCalculus/testNInt.cpp diff --git a/tests/testRef.cpp b/tests/testMatrix/testRef.cpp similarity index 100% rename from tests/testRef.cpp rename to tests/testMatrix/testRef.cpp diff --git a/tests/testFactors.cpp b/tests/testSteppable/testFactors.cpp similarity index 100% rename from tests/testFactors.cpp rename to tests/testSteppable/testFactors.cpp diff --git a/tests/testFactors.vcxproj b/tests/testSteppable/testFactors.vcxproj similarity index 100% rename from tests/testFactors.vcxproj rename to tests/testSteppable/testFactors.vcxproj diff --git a/tests/testFormat.cpp b/tests/testSteppable/testFormat.cpp similarity index 100% rename from tests/testFormat.cpp rename to tests/testSteppable/testFormat.cpp diff --git a/tests/testFraction.cpp b/tests/testSteppable/testFraction.cpp similarity index 100% rename from tests/testFraction.cpp rename to tests/testSteppable/testFraction.cpp diff --git a/tests/testMat2d.cpp b/tests/testSteppable/testMat2d.cpp similarity index 100% rename from tests/testMat2d.cpp rename to tests/testSteppable/testMat2d.cpp diff --git a/tests/testNumber.cpp b/tests/testSteppable/testNumber.cpp similarity index 100% rename from tests/testNumber.cpp rename to tests/testSteppable/testNumber.cpp diff --git a/tests/testUtil.cpp b/tests/testSteppable/testUtil.cpp similarity index 100% rename from tests/testUtil.cpp rename to tests/testSteppable/testUtil.cpp diff --git a/tests/testUtil.vcxproj b/tests/testSteppable/testUtil.vcxproj similarity index 100% rename from tests/testUtil.vcxproj rename to tests/testSteppable/testUtil.vcxproj From ba9a052200a6a9b86e3011e2e0635abe9f71be5f Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Thu, 3 Jul 2025 13:18:56 +0800 Subject: [PATCH 13/18] Add support for bar plots --- CMakeLists.txt | 4 +- include/conPlot/conPlot.hpp | 9 +- include/conPlot/conPlotInternals.hpp | 51 +++++++ include/conPlot/conPlotInterpolation.hpp | 122 +--------------- include/conPlot/conPlotTypes.hpp | 95 +++++++++++-- include/debugging.hpp | 48 +++++++ include/types/concepts.hpp | 2 + include/util.hpp | 15 ++ lib/CMakeLists.txt | 38 ++--- src/CMakeLists.txt | 23 +-- src/conPlot/CMakeLists.txt | 29 ++++ src/conPlot/conPlot.cpp | 169 +++++++++-------------- src/conPlot/conPlotInternals.cpp | 148 ++++++++++++++++++++ src/conPlot/conPlotInterpolation.cpp | 148 ++++++++++++++++++++ src/matrix/ref/ref.cpp | 45 +++++- tests/CMakeLists.txt | 15 +- 16 files changed, 685 insertions(+), 276 deletions(-) create mode 100644 include/conPlot/conPlotInternals.hpp create mode 100644 include/debugging.hpp create mode 100644 src/conPlot/CMakeLists.txt create mode 100644 src/conPlot/conPlotInternals.cpp create mode 100644 src/conPlot/conPlotInterpolation.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a9a09a131..4c71e3a0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,11 +111,11 @@ SET(COMPONENTS calc::root calc::subtract calc::trig - matrix::ref) + matrix::ref +) # NEW_COMPONENT: PATCH Do NOT remove the previous comment. SET(TARGETS ${COMPONENTS} util) -SET(TEST_TARGETS_TEMP steppable::util steppable::fraction steppable::number steppable::mat2d steppable::factors steppable::format ${COMPONENTS}) ADD_SUBDIRECTORY(src/) ADD_SUBDIRECTORY(lib/) diff --git a/include/conPlot/conPlot.hpp b/include/conPlot/conPlot.hpp index 89846ab5d..770d501a6 100644 --- a/include/conPlot/conPlot.hpp +++ b/include/conPlot/conPlot.hpp @@ -20,12 +20,9 @@ * SOFTWARE. * **************************************************************************************************/ -#include "colors.hpp" #include "conPlot/conPlotTypes.hpp" -#include "symbols.hpp" #include -#include /** * @namespace steppable::graphing @@ -35,5 +32,9 @@ namespace steppable::graphing { void conPlot(const std::vector& f, const GraphOptions& graphOptions, - const std::vector& lineOptions); + const std::vector& linesOptions); + + void conPlotBar(const std::vector>& numbers, + const BarGraphOptions& graphOptions, + const std::vector& barsOptions); } // namespace steppable::graphing diff --git a/include/conPlot/conPlotInternals.hpp b/include/conPlot/conPlotInternals.hpp new file mode 100644 index 000000000..b2e56013f --- /dev/null +++ b/include/conPlot/conPlotInternals.hpp @@ -0,0 +1,51 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **************************************************************************************************/ + +#pragma once + +#include "conPlot/conPlotTypes.hpp" +#include "steppable/number.hpp" + +#include + +/** + * @namespace steppable::graphing::__internals + * @brief Graphing utilities for showing graphs in the console. + */ +namespace steppable::graphing::__internals +{ + void conPlotLine(const Number& xGridSize, + const Number& yGridSize, + const Number& yMax, + const GraphOptions* graphOptions, + const LineOptions* lineOptions, + prettyPrint::ConsoleOutput* canvas, + std::map& fnValues); + + void drawTicks(prettyPrint::ConsoleOutput* canvas, + const Number& xGridSize, + const Number& yGridSize, + const Number& yMax, + const GraphOptionsBase* graphOptions); + + void drawGrid(prettyPrint::ConsoleOutput* canvas, const GraphOptionsBase* graphOptions); +} // namespace steppable::graphing::__internals diff --git a/include/conPlot/conPlotInterpolation.hpp b/include/conPlot/conPlotInterpolation.hpp index eb0f297b0..2ca2c0a47 100644 --- a/include/conPlot/conPlotInterpolation.hpp +++ b/include/conPlot/conPlotInterpolation.hpp @@ -22,15 +22,7 @@ #pragma once -#include "output.hpp" - -#include -#include #include -#include -#include - -using namespace std::literals; namespace steppable::graphing { @@ -41,57 +33,7 @@ namespace steppable::graphing * @param xMin Minimum `x` value. * @param xMax Maximum `x` value. */ - void linearInterpolateFill(std::map* data, long long xMin, long long xMax) - { - if (data->size() < 2) - output::error("graphing::linearInterpolateFill"s, - "At least 2 points are required for linear interpolation."s); - - std::vector xs; - std::vector ys; - for (const auto& kv : *data) - { - xs.push_back(kv.first); - ys.push_back(kv.second); - } - auto n = static_cast(xs.size()); - - for (long long x = xMin; x <= xMax; ++x) - { - // Find the interval [x0, x1] for x (or clamp to endpoints) - auto it = std::ranges::lower_bound(xs, x); - long long idx1 = std::distance(xs.begin(), it); - long long i0 = 0; - long long i1 = 0; - if (x <= xs.front()) - { - i0 = 0; - i1 = 1; - } - else if (x >= xs.back()) - { - i0 = n - 2; - i1 = n - 1; - } - else - { - i0 = idx1 - 1; - i1 = idx1; - } - - long long x0 = xs[i0]; - long long x1 = xs[i1]; - long long y0 = ys[i0]; - long long y1 = ys[i1]; - long double t = 0; - if (x1 == x0) - t = 0.0; - else - t = static_cast(x - x0) / static_cast(x1 - x0); - long long y = llround(static_cast(y0) + (t * static_cast(y1 - y0))); - (*data)[x] = y; - } - } + void linearInterpolateFill(std::map* data, long long xMin, long long xMax); /** * @brief Fill in integral values with cubic interpolation. @@ -100,65 +42,5 @@ namespace steppable::graphing * @param xMin Minimum `x` value. * @param xMax Maximum `x` value. */ - void cubicInterpolateFill(std::map* data, long long xMin, long long xMax) - { - if (data->size() < 4) - linearInterpolateFill(data, xMin, xMax); - std::vector xs; - std::vector ys; - for (const auto& kv : *data) - { - xs.push_back(kv.first); - ys.push_back(kv.second); - } - auto n = static_cast(xs.size()); - - for (long long x = xMin; x <= xMax; ++x) - { - // Find the correct window of 4 points for interpolation - auto it = std::ranges::lower_bound(xs, x); - long long idx1 = std::distance(xs.begin(), it); - long long i1 = std::clamp(idx1 - 1, 1LL, n - 3); - long long i0 = i1 - 1; - long long i2 = i1 + 1; - long long i3 = i1 + 2; - - if (x <= xs[1]) - { - i0 = 0; - i1 = 1; - i2 = 2; - i3 = 3; - } - if (x >= xs[n - 2]) - { - i0 = n - 4; - i1 = n - 3; - i2 = n - 2; - i3 = n - 1; - } - - long long x0 = xs[i0]; - long long x1 = xs[i1]; - long long x2 = xs[i2]; - long long x3 = xs[i3]; - long long y0 = ys[i0]; - long long y1 = ys[i1]; - long long y2 = ys[i2]; - long long y3 = ys[i3]; - - long double t0 = static_cast((x - x1) * (x - x2) * (x - x3)) / - static_cast((x0 - x1) * (x0 - x2) * (x0 - x3)); - long double t1 = static_cast((x - x0) * (x - x2) * (x - x3)) / - static_cast((x1 - x0) * (x1 - x2) * (x1 - x3)); - long double t2 = static_cast((x - x0) * (x - x1) * (x - x3)) / - static_cast((x2 - x0) * (x2 - x1) * (x2 - x3)); - long double t3 = static_cast((x - x0) * (x - x1) * (x - x2)) / - static_cast((x3 - x0) * (x3 - x1) * (x3 - x2)); - - long long y = llround((static_cast(y0) * t0) + (static_cast(y1) * t1) + - (static_cast(y2) * t2) + (static_cast(y3) * t3)); - (*data)[x] = y; - } - } + void cubicInterpolateFill(std::map* data, long long xMin, long long xMax); } // namespace steppable::graphing diff --git a/include/conPlot/conPlotTypes.hpp b/include/conPlot/conPlotTypes.hpp index e1f86755f..982297b84 100644 --- a/include/conPlot/conPlotTypes.hpp +++ b/include/conPlot/conPlotTypes.hpp @@ -50,22 +50,28 @@ namespace steppable::graphing constexpr std::string_view LIGHT_BLOCK_3 = "\u2593"; ///< More densly-filled block } // namespace GraphDot - /** - * @struct steppable::graphing::GraphOptions - * @brief Stores the opotions for each graph shown on screen. - */ - struct GraphOptions + struct GraphOptionsBase { - Number xMin = -1; ///< Minimum `x` value. - Number xMax = 1; ///< Maximum `x` value. + std::string title = "Graph"; ///< Title of the graph. + std::string xAxisTitle = "X Axis Title"; ///< Title of the `x` axis. + std::string yAxisTitle = "Y Axis Title"; ///< Title of the `y` axis. + long long width = 30; ///< Width of the graph. long long height = 20; ///< Height of the graph. + long long xTickSpacing = 10; ///< Spacing between each `x` axis tick. long long yTickSpacing = 5; ///< Spacing between each `y` axis tick. - std::string title = "Graph"; ///< Title of the graph. - std::string xAxisTitle = "X Axis Title"; ///< Title of the `x` axis. - std::string yAxisTitle = "Y Axis Title"; ///< Title of the `y` axis. + Number xMin = -1; ///< Minimum `x` value. + Number xMax = 1; ///< Maximum `x` value. + }; + + /** + * @struct steppable::graphing::GraphOptions + * @brief Stores the opotions for each graph shown on screen. + */ + struct GraphOptions : GraphOptionsBase + { /** * @brief Initializes a new `GraphOptions` instance. * @@ -98,15 +104,57 @@ namespace steppable::graphing } }; + struct BarGraphOptions : GraphOptionsBase + { + long long yTickSpacing = 5; ///< Spacing between each `y` axis tick. + long long xTickSpacing = 2; ///< Width of a single bar + Number xMin = 0; + Number xMax = 100; + + /** + * @brief Initializes a new `GraphOptions` instance. + * + * @param params The parameters to be passed to initialize the instance. + */ + template + BarGraphOptions(Params... params) + { + auto map = processParams(params...); + + PARAM_GET_FALLBACK(map, long long, width, 30LL); + PARAM_GET_FALLBACK(map, long long, height, 20LL); + PARAM_GET_FALLBACK(map, long long, yTickSpacing, 5LL); + PARAM_GET_FALLBACK(map, long long, barWidth, 2LL); + PARAM_GET_FALLBACK(map, std::string, title, "Bar Graph"); + PARAM_GET_FALLBACK(map, std::string, xAxisTitle, "X Axis Title"); + PARAM_GET_FALLBACK(map, std::string, yAxisTitle, "Y Axis Title"); + + this->width = width; + this->height = height; + this->yTickSpacing = yTickSpacing; + this->title = title; + this->xAxisTitle = xAxisTitle; + this->yAxisTitle = yAxisTitle; + this->xTickSpacing = barWidth; + } + }; + + struct LineOptionsBase + { + std::string_view lineDot = GraphDot::BLOCK; ///< Dot type to be drawn on screen. + ColorFunc lineColor = colors::green; ///< Color of the dot to output. + std::string title = "Line"; ///< Name of the line to be shown in the legend. + }; + + template + concept LineOptionsDerivation = std::derived_from; + /** * @struct steppable::graphing::LineOptions * @brief Stores options for each line graphed on screen. */ - struct LineOptions + struct LineOptions : LineOptionsBase { - std::string_view lineDot = GraphDot::BLOCK; ///< Dot type to be drawn on screen. - ColorFunc lineColor = colors::green; ///< Color of the dot to output. - std::string title = "Line"; ///< Name of the line to be shown in the legend. long long samplesSpacing = 2; ///< Frequency to take a sample, units: grids. /** @@ -129,4 +177,23 @@ namespace steppable::graphing this->samplesSpacing = samplesSpacing; } }; + + struct BarOptions : LineOptionsBase + { + std::string title = "Bar"; + + template + BarOptions(Params... params) + { + auto map = processParams(params...); + PARAM_GET_FALLBACK(map, std::string_view, block, GraphDot::BLOCK); + PARAM_GET_FALLBACK(map, ColorFunc, color, colors::keepOriginal); + PARAM_GET_FALLBACK(map, long long, barWidth, 2); + PARAM_GET_FALLBACK(map, std::string, title, "Bar"s); + + this->lineDot = block; + this->lineColor = color; + this->title = title; + } + }; } // namespace steppable::graphing diff --git a/include/debugging.hpp b/include/debugging.hpp new file mode 100644 index 000000000..c7f7d1611 --- /dev/null +++ b/include/debugging.hpp @@ -0,0 +1,48 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **************************************************************************************************/ + +#pragma once +#include +#include + +namespace steppable::inspect +{ + template + void printVector(const std::vector& vector) + { +#ifndef DEBUG + return; +#endif + + std::cout << "["; + for (size_t i = 0; i < vector.size(); i++) + { + std::cout << vector[i]; + + if (i != vector.size() - 1) + std::cout << ", "; + if (i % 5 == 0) + std::cout << "\n"; + } + std::cout << "]\n"; + } +} // namespace steppable::inspect \ No newline at end of file diff --git a/include/types/concepts.hpp b/include/types/concepts.hpp index 32d24ede7..b2458cf29 100644 --- a/include/types/concepts.hpp +++ b/include/types/concepts.hpp @@ -26,6 +26,8 @@ * @date 23 Jun 2025 */ +#pragma once + #include #include #include diff --git a/include/util.hpp b/include/util.hpp index b553f2da7..e071ea9cf 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -41,9 +41,11 @@ #include "colors.hpp" #include "output.hpp" #include "platform.hpp" +#include "types/concepts.hpp" #include #include +#include #include #include #include @@ -607,4 +609,17 @@ namespace steppable::__internals::stringUtils } return out.str(); } + + template + T toNumeric(const std::string& s) + { + auto value = T{}; + + if (auto result = std::from_chars(s.data(), s.data() + s.size(), value); result.ec != std::errc{}) + { + output::error("toNumeric"s, "Invalid argument"s); + utils::programSafeExit(1); + } + return value; + } } // namespace steppable::__internals::stringUtils diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 2c731b357..a489817e9 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -20,27 +20,31 @@ # SOFTWARE. # ##################################################################################################### -if(NOT ${STP_NO_BINDINGS}) # Only create the bindings if needed - set(Python_EXECUTABLE ${Python3_EXECUTABLE}) +IF(NOT ${STP_NO_BINDINGS}) # Only create the bindings if needed + SET(Python_EXECUTABLE ${Python3_EXECUTABLE}) # Detect the installed nanobind package and import it into CMake - execute_process( + EXECUTE_PROCESS( COMMAND "${Python3_EXECUTABLE}" -m nanobind --cmake_dir OUTPUT_STRIP_TRAILING_WHITESPACE - OUTPUT_VARIABLE NB_DIR) - message(TRACE "Found nanobind: ${NB_DIR}") - list(APPEND CMAKE_PREFIX_PATH "${NB_DIR}") - find_package(nanobind CONFIG REQUIRED) + OUTPUT_VARIABLE NB_DIR + ) + MESSAGE(TRACE "Found nanobind: ${NB_DIR}") + LIST(APPEND CMAKE_PREFIX_PATH "${NB_DIR}") + FIND_PACKAGE(nanobind CONFIG REQUIRED) # Python bindings for Steppable. - set(Python_INTERPRETER_ID ${Python3_INTERPRETER_ID} CACHE INTERNAL "Compatibility for nanobind") - nanobind_add_module(steppyble STABLE_ABI NB_STATIC LTO bindings.cpp) + SET(Python_INTERPRETER_ID + ${Python3_INTERPRETER_ID} + CACHE INTERNAL "Compatibility for nanobind" + ) + NANOBIND_ADD_MODULE(steppyble STABLE_ABI NB_STATIC LTO bindings.cpp) - if(NOT ${WINDOWS}) - target_link_options(steppyble PRIVATE -Bsymbolic) - endif() - set_target_properties(steppyble PROPERTIES POSITION_INDEPENDENT_CODE ON) - target_link_libraries(steppyble PRIVATE func steppable) - target_compile_definitions(steppyble PRIVATE NO_MAIN STP_BINDINGS) - target_include_directories(steppyble PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include) -endif() + IF(NOT ${WINDOWS}) + TARGET_LINK_OPTIONS(steppyble PRIVATE -Bsymbolic) + ENDIF() + SET_TARGET_PROPERTIES(steppyble PROPERTIES POSITION_INDEPENDENT_CODE ON) + TARGET_LINK_LIBRARIES(steppyble PRIVATE func steppable) + TARGET_COMPILE_DEFINITIONS(steppyble PRIVATE NO_MAIN STP_BINDINGS) + TARGET_INCLUDE_DIRECTORIES(steppyble PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include) +ENDIF() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8c4e4fada..dff57541d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,6 +20,8 @@ # SOFTWARE. # ##################################################################################################### +ADD_SUBDIRECTORY(conPlot) + ADD_LIBRARY( util STATIC argParse.cpp @@ -62,13 +64,16 @@ FOREACH(COMPONENT IN LISTS COMPONENTS) ENDFOREACH() # FUNC: Library containing all Steppable functions. -ADD_LIBRARY(func STATIC ${CALCULATOR_FILES} - steppable/fraction.cpp - steppable/mat2d.cpp - steppable/number.cpp - conPlot/conPlot.cpp - rounding.cpp - factors.cpp) +ADD_LIBRARY( + func STATIC + ${CALCULATOR_FILES} + ${CONPLOT_FILES} + steppable/fraction.cpp + steppable/mat2d.cpp + steppable/number.cpp + rounding.cpp + factors.cpp +) SET_TARGET_PROPERTIES(func PROPERTIES POSITION_INDEPENDENT_CODE ON) TARGET_INCLUDE_DIRECTORIES(steppable PRIVATE ${STP_BASE_DIRECTORY}/include/) @@ -76,6 +81,8 @@ TARGET_INCLUDE_DIRECTORIES(func PRIVATE ${STP_BASE_DIRECTORY}/include/) TARGET_INCLUDE_DIRECTORIES(util PRIVATE ${STP_BASE_DIRECTORY}/include/) TARGET_LINK_LIBRARIES(steppable PRIVATE util) -TARGET_LINK_LIBRARIES(func PRIVATE steppable) +TARGET_LINK_LIBRARIES(steppable PRIVATE conPlot) +TARGET_LINK_LIBRARIES(func PRIVATE util) +TARGET_LINK_LIBRARIES(func PRIVATE conPlot) TARGET_COMPILE_DEFINITIONS(func PRIVATE NO_MAIN) TARGET_COMPILE_DEFINITIONS(steppable PRIVATE NO_MAIN) diff --git a/src/conPlot/CMakeLists.txt b/src/conPlot/CMakeLists.txt new file mode 100644 index 000000000..c1aa9e841 --- /dev/null +++ b/src/conPlot/CMakeLists.txt @@ -0,0 +1,29 @@ +##################################################################################################### +# Copyright (c) 2023-2025 NWSOFT # +# # +# Permission is hereby granted, free of charge, to any person obtaining a copy # +# of this software and associated documentation files (the "Software"), to deal # +# in the Software without restriction, including without limitation the rights # +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # +# copies of the Software, and to permit persons to whom the Software is # +# furnished to do so, subject to the following conditions: # +# # +# The above copyright notice and this permission notice shall be included in all # +# copies or substantial portions of the Software. # +# # +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # +# SOFTWARE. # +##################################################################################################### + +SET(CONPLOT_FILES ${STP_BASE_DIRECTORY}/src/conPlot/conPlot.cpp ${STP_BASE_DIRECTORY}/src/conPlot/conPlotInternals.cpp + ${STP_BASE_DIRECTORY}/src/conPlot/conPlotInterpolation.cpp +) + +ADD_LIBRARY(conPlot STATIC ${CONPLOT_FILES}) +SET_TARGET_PROPERTIES(conPlot PROPERTIES POSITION_INDEPENDENT_CODE ON) +TARGET_INCLUDE_DIRECTORIES(conPlot PRIVATE ${STP_BASE_DIRECTORY}/include/) diff --git a/src/conPlot/conPlot.cpp b/src/conPlot/conPlot.cpp index 140680fe8..ec9d7018e 100644 --- a/src/conPlot/conPlot.cpp +++ b/src/conPlot/conPlot.cpp @@ -22,12 +22,12 @@ #include "conPlot/conPlot.hpp" -#include "conPlot/conPlotInterpolation.hpp" +#include "conPlot/conPlotInternals.hpp" #include "conPlot/conPlotTypes.hpp" +#include "debugging.hpp" #include "steppable/number.hpp" -#include "symbols.hpp" // Adjust the path as needed +#include "symbols.hpp" -#include #include #include @@ -35,8 +35,9 @@ namespace steppable::graphing { namespace { - void conPlotLegend(const GraphOptions* graphOptions, - const std::vector* lineOptions, + template + void conPlotLegend(const GraphOptionsBase* graphOptions, + const std::vector* lineOptions, prettyPrint::ConsoleOutput* canvas) { long long yLoc = graphOptions->height + 7; @@ -54,64 +55,74 @@ namespace steppable::graphing lineOption.lineColor); } } + } // namespace - void conPlotLine(const Number& xGridSize, - const Number& yGridSize, - const Number& yMax, - const GraphOptions* graphOptions, - const LineOptions* lineOptions, - prettyPrint::ConsoleOutput* canvas, - std::map& fnValues) + void conPlotBar(const std::vector>& numbers, + const BarGraphOptions& graphOptions, + const std::vector& barsOptions) + { + // Create buffer + prettyPrint::ConsoleOutput canvas(graphOptions.height + 10, + graphOptions.width + 12 + + steppable::__internals::stringUtils::getUnicodeDisplayWidth( + graphOptions.yAxisTitle)); // Extra space for labels + + Number maxValue = 0; + size_t datasetSize = numbers[0].size(); + for (const auto& dataset : numbers) { - // Plot function - std::map gridPos; - for (const auto& [i, y] : fnValues) + if (dataset.size() != datasetSize) { - auto gridY = std::stoll(((yMax - y) / yGridSize).present()); - auto gridX = std::stoll(((graphOptions->xMax - i) / xGridSize).present()); - gridPos[gridX] = gridY; + output::error("conPlotBar"s, "Bar plot datasets are not of the same size"s); + programSafeExit(1); } - if (lineOptions->samplesSpacing > 1) - cubicInterpolateFill(&gridPos, 0, graphOptions->width); + for (const auto& number : dataset) + maxValue = std::max(maxValue, number); + } + const auto& gridSize = maxValue / graphOptions.height; + + __internals::drawGrid(&canvas, &graphOptions); + __internals::drawTicks(&canvas, 0, gridSize, maxValue, &graphOptions); + + for (long long i = 0; i < numbers.size(); i++) + { + const auto& dataset = numbers[i]; - long long lastGridY = gridPos[0]; - for (const auto& [gridX, gridY] : gridPos) + std::vector yValues; + std::vector xValues; + long long xGridLoc = graphOptions.xTickSpacing * (1 + i); + for (const auto& number : dataset) { - // Plot point - const long long diffY = gridY - lastGridY; - const long long absDiffY = std::abs(diffY); - long long sgn = 1; - if (absDiffY != 0) - sgn = diffY / absDiffY; - if (absDiffY > graphOptions->height / 2) - { - // Create more points to connect the dots - for (long long j = 1; j < absDiffY + 1; j++) - canvas->write(lineOptions->lineDot, - { .x = gridX, .y = 3 + gridY + (-sgn * j) }, - false, - lineOptions->lineColor); - } - canvas->write(lineOptions->lineDot, { .x = gridX, .y = 3 + gridY }, false, lineOptions->lineColor); - lastGridY = 3 + gridY; + long long yGridLoc = std::stoll(((maxValue - number) / gridSize).present()); + yValues.emplace_back(yGridLoc); + xValues.emplace_back(xGridLoc); + + canvas.write( + barsOptions[i].lineDot, { .x = xGridLoc, .y = 3 + yGridLoc }, false, barsOptions[i].lineColor); + for (long long x = 0; x < graphOptions.xTickSpacing; x++) + for (long long y = yGridLoc; y < graphOptions.height; y++) + canvas.write( + barsOptions[i].lineDot, { .x = xGridLoc + x, .y = 3 + y }, false, barsOptions[i].lineColor); + + xGridLoc += graphOptions.xTickSpacing * (1 + numbers.size()); } } - } // namespace + + conPlotLegend(&graphOptions, &barsOptions, &canvas); + std::cout << canvas.asString() << "\n"; + } void conPlot(const std::vector& f, const GraphOptions& graphOptions, - const std::vector& lineOptions) + const std::vector& linesOptions) { // Create buffer - prettyPrint::ConsoleOutput canvas( - graphOptions.height + 10, - graphOptions.width + 12 + - __internals::stringUtils::getUnicodeDisplayWidth(graphOptions.xAxisTitle)); // Extra space for labels + prettyPrint::ConsoleOutput canvas(graphOptions.height + 10, + graphOptions.width + 12 + + steppable::__internals::stringUtils::getUnicodeDisplayWidth( + graphOptions.yAxisTitle)); // Extra space for labels std::map> fnValues; - canvas.write( - graphOptions.title, { .x = 0, .y = 1 }, false, formats::bold, prettyPrint::HorizontalAlignment::CENTER); - // Sample min/max Number yMin; for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx) @@ -128,7 +139,7 @@ namespace steppable::graphing { for (long long i = 0; i <= graphOptions.width; ++i) { - if (i % lineOptions[fnIdx].samplesSpacing == 0) + if (i % linesOptions[fnIdx].samplesSpacing == 0) { Number x = graphOptions.xMin + Number(i) * xGridSize; Number y = f[fnIdx](x); @@ -136,9 +147,6 @@ namespace steppable::graphing yMin = std::min(y, yMin); yMax = std::max(y, yMax); } - - // Write base frame - canvas.write(BoxDrawing::HORIZONTAL, { .x = i, .y = 3 + graphOptions.height }); } if ((yMax - yMin).abs() < 1e-12) @@ -150,59 +158,14 @@ namespace steppable::graphing // Axis positions Number yGridSize = (yMax - yMin) / graphOptions.height; - Number yTickValue = yMax; - - // Write side frame - for (long long j = 0; j < graphOptions.height; ++j) - { - canvas.write(BoxDrawing::VERTICAL, { .x = graphOptions.width, .y = 3 + j }); - yTickValue -= yGridSize; - - // Write y ticks - if (j % graphOptions.yTickSpacing == 0) - { - yTickValue.setPrec(2); - canvas.write(yTickValue.present(), { .x = graphOptions.width + 2, .y = 3 + j }); - for (long long i = 0; i < graphOptions.width; ++i) - canvas.write(BoxDrawing::DOTTED_HORIZONTAL, { .x = i, .y = 3 + j }); - canvas.write(BoxDrawing::VERTICAL_LEFT, { .x = graphOptions.width, .y = 3 + j }); - } - } - canvas.write(BoxDrawing::BOTTOM_RIGHT_CORNER, { .x = graphOptions.width, .y = 3 + graphOptions.height }); - - // Draw grid - for (long long i = 0; i < graphOptions.width - 2; ++i) - { - Number x = graphOptions.xMin + Number(i) * xGridSize; - // Write grid label - if (i % graphOptions.xTickSpacing == 0) - { - // Write vertical gridlines - for (long long j = 0; j < graphOptions.height; ++j) - canvas.write(BoxDrawing::DOTTED_VERTICAL, { .x = i, .y = 3 + j }); - canvas.write(BoxDrawing::HORIZONTAL_UP, { .x = i, .y = 3 + graphOptions.height }); - x.setPrec(2); - canvas.write(x.present(), { .x = i, .y = 3 + graphOptions.height + 1 }); - } - } - - // Axis Titles - canvas.write( - std::string( - (graphOptions.width - __internals::stringUtils::getUnicodeDisplayWidth(graphOptions.xAxisTitle)) / 2, - ' ') + - graphOptions.xAxisTitle, - { .x = 0, .y = 5 + graphOptions.height }, - false, - formats::bold); - canvas.write(graphOptions.yAxisTitle, - { .x = graphOptions.width + 10, .y = (graphOptions.height / 2) + 1 }, - false, - formats::bold); + __internals::drawGrid(&canvas, &graphOptions); + __internals::drawTicks(&canvas, xGridSize, yGridSize, yMax, &graphOptions); + // Plot function for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx) - conPlotLine(xGridSize, yGridSize, yMax, &graphOptions, &lineOptions[fnIdx], &canvas, fnValues[fnIdx]); - conPlotLegend(&graphOptions, &lineOptions, &canvas); + __internals::conPlotLine( + xGridSize, yGridSize, yMax, &graphOptions, &linesOptions[fnIdx], &canvas, fnValues[fnIdx]); + conPlotLegend(&graphOptions, &linesOptions, &canvas); std::cout << canvas.asString() << "\n"; } } // namespace steppable::graphing diff --git a/src/conPlot/conPlotInternals.cpp b/src/conPlot/conPlotInternals.cpp new file mode 100644 index 000000000..79dad2cc2 --- /dev/null +++ b/src/conPlot/conPlotInternals.cpp @@ -0,0 +1,148 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **************************************************************************************************/ + +#include "conPlot/conPlotInternals.hpp" + +#include "conPlot/conPlot.hpp" +#include "conPlot/conPlotInterpolation.hpp" +#include "conPlot/conPlotTypes.hpp" +#include "steppable/number.hpp" +#include "symbols.hpp" + +#include +#include +#include + +namespace steppable::graphing::__internals +{ + void conPlotLine(const Number& xGridSize, + const Number& yGridSize, + const Number& yMax, + const GraphOptions* graphOptions, + const LineOptions* lineOptions, + prettyPrint::ConsoleOutput* canvas, + std::map& fnValues) + { + // Plot function + std::map gridPos; + for (const auto& [i, y] : fnValues) + { + auto gridY = std::stoll(((yMax - y) / yGridSize).present()); + auto gridX = std::stoll(((graphOptions->xMax - i) / xGridSize).present()); + gridPos[gridX] = gridY; + } + if (lineOptions->samplesSpacing > 1) + cubicInterpolateFill(&gridPos, 0, graphOptions->width); + + long long lastGridY = gridPos[0]; + for (const auto& [gridX, gridY] : gridPos) + { + // Plot point + const long long diffY = gridY - lastGridY; + const long long absDiffY = std::abs(diffY); + long long sgn = 1; + if (absDiffY != 0) + sgn = diffY / absDiffY; + if (absDiffY > graphOptions->height / 2) + { + // Create more points to connect the dots + for (long long j = 1; j < absDiffY + 1; j++) + canvas->write(lineOptions->lineDot, + { .x = gridX, .y = 3 + gridY + (-sgn * j) }, + false, + lineOptions->lineColor); + } + canvas->write(lineOptions->lineDot, { .x = gridX, .y = 3 + gridY }, false, lineOptions->lineColor); + lastGridY = 3 + gridY; + } + } + + void drawTicks(prettyPrint::ConsoleOutput* canvas, + const Number& xGridSize, + const Number& yGridSize, + const Number& yMax, + const GraphOptionsBase* graphOptions) + { + // Axis positions + Number yTickValue = yMax; + + for (long long j = 0; j < graphOptions->height; ++j) + { + yTickValue -= yGridSize; + // Write y ticks + if (j % graphOptions->yTickSpacing == 0 and yGridSize != 0) + { + yTickValue.setPrec(2); + canvas->write(yTickValue.present(), { .x = graphOptions->width + 2, .y = 3 + j }); + for (long long i = 0; i < graphOptions->width; ++i) + canvas->write(BoxDrawing::DOTTED_HORIZONTAL, { .x = i, .y = 3 + j }); + canvas->write(BoxDrawing::VERTICAL_LEFT, { .x = graphOptions->width, .y = 3 + j }); + } + } + + // Draw grid + for (long long i = 0; i < graphOptions->width - 2; ++i) + { + Number x = graphOptions->xMin + Number(i) * xGridSize; + // Write grid label + if (i % graphOptions->xTickSpacing == 0 and xGridSize != 0) + { + // Write vertical gridlines + for (long long j = 0; j < graphOptions->height; ++j) + canvas->write(BoxDrawing::DOTTED_VERTICAL, { .x = i, .y = 3 + j }); + canvas->write(BoxDrawing::HORIZONTAL_UP, { .x = i, .y = 3 + graphOptions->height }); + x.setPrec(2); + canvas->write(x.present(), { .x = i, .y = 3 + graphOptions->height + 1 }); + } + } + } + + void drawGrid(prettyPrint::ConsoleOutput* canvas, const GraphOptionsBase* graphOptions) + { + canvas->write( + graphOptions->title, { .x = 0, .y = 1 }, false, formats::bold, prettyPrint::HorizontalAlignment::CENTER); + for (long long i = 0; i <= graphOptions->width; ++i) + { + // Write base frame + canvas->write(BoxDrawing::HORIZONTAL, { .x = i, .y = 3 + graphOptions->height }); + } + + // Write side frame + for (long long j = 0; j < graphOptions->height; ++j) + canvas->write(BoxDrawing::VERTICAL, { .x = graphOptions->width, .y = 3 + j }); + + // Axis Titles + canvas->write(BoxDrawing::BOTTOM_RIGHT_CORNER, { .x = graphOptions->width, .y = 3 + graphOptions->height }); + canvas->write(std::string((graphOptions->width - ::steppable::__internals::stringUtils::getUnicodeDisplayWidth( + graphOptions->xAxisTitle)) / + 2, + ' ') + + graphOptions->xAxisTitle, + { .x = 0, .y = 5 + graphOptions->height }, + false, + formats::bold); + canvas->write(graphOptions->yAxisTitle, + { .x = graphOptions->width + 10, .y = (graphOptions->height / 2) + 1 }, + false, + formats::bold); + } +} // namespace steppable::graphing::__internals diff --git a/src/conPlot/conPlotInterpolation.cpp b/src/conPlot/conPlotInterpolation.cpp new file mode 100644 index 000000000..485cd59b4 --- /dev/null +++ b/src/conPlot/conPlotInterpolation.cpp @@ -0,0 +1,148 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **************************************************************************************************/ + +#include "output.hpp" + +#include +#include +#include +#include +#include + +using namespace std::literals; + +namespace steppable::graphing +{ + void linearInterpolateFill(std::map* data, long long xMin, long long xMax) + { + if (data->size() < 2) + output::error("graphing::linearInterpolateFill"s, + "At least 2 points are required for linear interpolation."s); + + std::vector xs; + std::vector ys; + for (const auto& kv : *data) + { + xs.push_back(kv.first); + ys.push_back(kv.second); + } + auto n = static_cast(xs.size()); + + for (long long x = xMin; x <= xMax; ++x) + { + // Find the interval [x0, x1] for x (or clamp to endpoints) + auto it = std::ranges::lower_bound(xs, x); + long long idx1 = std::distance(xs.begin(), it); + long long i0 = 0; + long long i1 = 0; + if (x <= xs.front()) + { + i0 = 0; + i1 = 1; + } + else if (x >= xs.back()) + { + i0 = n - 2; + i1 = n - 1; + } + else + { + i0 = idx1 - 1; + i1 = idx1; + } + + long long x0 = xs[i0]; + long long x1 = xs[i1]; + long long y0 = ys[i0]; + long long y1 = ys[i1]; + long double t = 0; + if (x1 == x0) + t = 0.0; + else + t = static_cast(x - x0) / static_cast(x1 - x0); + long long y = llround(static_cast(y0) + (t * static_cast(y1 - y0))); + (*data)[x] = y; + } + } + + void cubicInterpolateFill(std::map* data, long long xMin, long long xMax) + { + if (data->size() < 4) + linearInterpolateFill(data, xMin, xMax); + std::vector xs; + std::vector ys; + for (const auto& kv : *data) + { + xs.push_back(kv.first); + ys.push_back(kv.second); + } + auto n = static_cast(xs.size()); + + for (long long x = xMin; x <= xMax; ++x) + { + // Find the correct window of 4 points for interpolation + auto it = std::ranges::lower_bound(xs, x); + long long idx1 = std::distance(xs.begin(), it); + long long i1 = std::clamp(idx1 - 1, 1LL, n - 3); + long long i0 = i1 - 1; + long long i2 = i1 + 1; + long long i3 = i1 + 2; + + if (x <= xs[1]) + { + i0 = 0; + i1 = 1; + i2 = 2; + i3 = 3; + } + if (x >= xs[n - 2]) + { + i0 = n - 4; + i1 = n - 3; + i2 = n - 2; + i3 = n - 1; + } + + long long x0 = xs[i0]; + long long x1 = xs[i1]; + long long x2 = xs[i2]; + long long x3 = xs[i3]; + long long y0 = ys[i0]; + long long y1 = ys[i1]; + long long y2 = ys[i2]; + long long y3 = ys[i3]; + + long double t0 = static_cast((x - x1) * (x - x2) * (x - x3)) / + static_cast((x0 - x1) * (x0 - x2) * (x0 - x3)); + long double t1 = static_cast((x - x0) * (x - x2) * (x - x3)) / + static_cast((x1 - x0) * (x1 - x2) * (x1 - x3)); + long double t2 = static_cast((x - x0) * (x - x1) * (x - x3)) / + static_cast((x2 - x0) * (x2 - x1) * (x2 - x3)); + long double t3 = static_cast((x - x0) * (x - x1) * (x - x2)) / + static_cast((x3 - x0) * (x3 - x1) * (x3 - x2)); + + long long y = llround((static_cast(y0) * t0) + (static_cast(y1) * t1) + + (static_cast(y2) * t2) + (static_cast(y3) * t3)); + (*data)[x] = y; + } + } +} // namespace steppable::graphing diff --git a/src/matrix/ref/ref.cpp b/src/matrix/ref/ref.cpp index 5d1e4abbd..a26413cb0 100644 --- a/src/matrix/ref/ref.cpp +++ b/src/matrix/ref/ref.cpp @@ -59,12 +59,43 @@ int main() // mat = mat.rref(); // std::cout << mat.present(1) << "\n"; - graphing::conPlot({ [](const Number& x) { return steppable::__internals::calc::sin(x.present(), 2); }, - [](const Number& x) { return steppable::__internals::calc::cos(x.present(), 2); } }, - { "width"_p = 90LL, - "xMin"_p = Number(-3.14), - "xMax"_p = Number(3.14), - "title"_p = "Sine and cosine graphs from -pi to +pi"s }, + graphing::conPlot({ + [](const Number& x) { return steppable::__internals::calc::sin(x.present(), 2); }, + [](const Number& x) { return steppable::__internals::calc::cos(x.present(), 2); }, + }, + { + "width"_p = 90LL, + "xMin"_p = Number(-3.14), + "xMax"_p = Number(3.14), + "title"_p = "Sine and cosine graphs from -pi to +pi"s, + }, { { "title"_p = "Sine"s }, - { "lineColor"_p = (ColorFunc)colors::red, "lineDot"_p = "*"sv, "title"_p = "Cosine"s } }); + { + "lineColor"_p = (ColorFunc)colors::red, + "lineDot"_p = "*"sv, + "title"_p = "Cosine"s, + } }); + + graphing::conPlotBar( + { + { 10, 12, 50, 12, 56, 6 }, + { 13, 14, 42, 64, 23, 10 }, + }, + { + "width"_p = 110LL, + "height"_p = 20LL, + "title"_p = "Bar plot"s, + "xAxisTitle"_p = "X axis"s, + "barWidth"_p = 5LL, + }, + { + { + "title"_p = "Bar 1"s, + "color"_p = (ColorFunc)colors::blue, + }, + { + "title"_p = "Bar 2"s, + "color"_p = (ColorFunc)colors::red, + }, + }); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c604cd149..a0fc1afca 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -22,6 +22,16 @@ ENABLE_TESTING() +SET(TEST_TARGETS_TEMP + steppable::util + steppable::fraction + steppable::number + steppable::mat2d + steppable::factors + steppable::format + ${COMPONENTS} +) + FOREACH(TEST_TARGET IN LISTS TEST_TARGETS_TEMP) SET(TARGET_NAME "test") STRING(REPLACE "::" ";" TEST_TARGET_PARTS ${TEST_TARGET}) @@ -44,7 +54,10 @@ FOREACH(TEST_TARGET IN LISTS TEST_TARGETS_TEMP) MESSAGE(TRACE "Added test case: ${TEST_TARGET}: ${DIR_NAME}/${FILE_NAME}.cpp") ENDFOREACH() -SET(TEST_TARGETS ${TEST_TARGETS} PARENT_SCOPE) +SET(TEST_TARGETS + ${TEST_TARGETS} + PARENT_SCOPE +) ADD_CUSTOM_TARGET(tests) ADD_DEPENDENCIES(tests ${TEST_TARGETS}) From ae7c3b00783179172a9ce909513a6670315292fe Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Thu, 3 Jul 2025 18:33:21 +0800 Subject: [PATCH 14/18] Add shading support for simple curves --- include/conPlot/conPlot.hpp | 4 +-- include/conPlot/conPlotTypes.hpp | 25 +++++++++++++ src/conPlot/conPlotInternals.cpp | 62 ++++++++++++++++++++++++++++++++ src/matrix/ref/ref.cpp | 14 +++++--- 4 files changed, 98 insertions(+), 7 deletions(-) diff --git a/include/conPlot/conPlot.hpp b/include/conPlot/conPlot.hpp index 770d501a6..0ac74f407 100644 --- a/include/conPlot/conPlot.hpp +++ b/include/conPlot/conPlot.hpp @@ -20,9 +20,9 @@ * SOFTWARE. * **************************************************************************************************/ -#include "conPlot/conPlotTypes.hpp" +#pragma once -#include +#include "conPlot/conPlotTypes.hpp" /** * @namespace steppable::graphing diff --git a/include/conPlot/conPlotTypes.hpp b/include/conPlot/conPlotTypes.hpp index 982297b84..5d94ddb7a 100644 --- a/include/conPlot/conPlotTypes.hpp +++ b/include/conPlot/conPlotTypes.hpp @@ -28,6 +28,7 @@ #include #include +#include namespace steppable::graphing { @@ -66,6 +67,19 @@ namespace steppable::graphing Number xMax = 1; ///< Maximum `x` value. }; + enum class ShadeOptions : std::uint8_t + { + NO_SHADE = 0, + SHADE_ABOVE_FIRST = 1, + SHADE_BELOW_SECOND = 2, + SHADE_BETWEEN_BOTH = 3, + SHADE_OUTSIDE_BOTH = 4, + SHADE_ABOVE_SECOND = 5, + SHADE_BELOW_FIRST = 6, + }; + + using NumberPair = std::pair; + /** * @struct steppable::graphing::GraphOptions * @brief Stores the opotions for each graph shown on screen. @@ -156,6 +170,9 @@ namespace steppable::graphing struct LineOptions : LineOptionsBase { long long samplesSpacing = 2; ///< Frequency to take a sample, units: grids. + NumberPair shadeValues = { 0, 0 }; + ShadeOptions shadeOptions = ShadeOptions::NO_SHADE; + std::string_view shadeDot = GraphDot::LIGHT_BLOCK_1; /** * @brief Initializes a new `LineOptions` instance. @@ -171,10 +188,18 @@ namespace steppable::graphing PARAM_GET_FALLBACK(map, std::string, title, "Line"s); PARAM_GET_FALLBACK(map, long long, samplesSpacing, 2LL); + PARAM_GET_FALLBACK(map, NumberPair, shadeValues, {}); + PARAM_GET_FALLBACK(map, ShadeOptions, shadeOptions, ShadeOptions::NO_SHADE); + PARAM_GET_FALLBACK(map, std::string_view, shadeBlock, GraphDot::LIGHT_BLOCK_1); + this->lineDot = lineDot; this->lineColor = lineColor; this->title = title; this->samplesSpacing = samplesSpacing; + + this->shadeValues = shadeValues; + this->shadeOptions = shadeOptions; + this->shadeDot = shadeBlock; } }; diff --git a/src/conPlot/conPlotInternals.cpp b/src/conPlot/conPlotInternals.cpp index 79dad2cc2..2b33f2fda 100644 --- a/src/conPlot/conPlotInternals.cpp +++ b/src/conPlot/conPlotInternals.cpp @@ -42,6 +42,18 @@ namespace steppable::graphing::__internals prettyPrint::ConsoleOutput* canvas, std::map& fnValues) { + const auto& yMin = yMax - yGridSize * graphOptions->height; + long long minShadeGrid = 0; + long long maxShadeGrid = 0; + if (lineOptions->shadeOptions != ShadeOptions::NO_SHADE) + { + auto [minShade, maxShade] = lineOptions->shadeValues; + if (minShade > maxShade) + std::swap(minShade, maxShade); + + minShadeGrid = 3 + std::stoll(((yMax - minShade) / yGridSize).present()); + maxShadeGrid = 3 + std::stoll(((yMax - maxShade) / yGridSize).present()); + } // Plot function std::map gridPos; for (const auto& [i, y] : fnValues) @@ -73,6 +85,56 @@ namespace steppable::graphing::__internals } canvas->write(lineOptions->lineDot, { .x = gridX, .y = 3 + gridY }, false, lineOptions->lineColor); lastGridY = 3 + gridY; + + switch (lineOptions->shadeOptions) + { + case ShadeOptions::NO_SHADE: + break; + + case ShadeOptions::SHADE_BELOW_FIRST: + { + for (long long j = lastGridY - 1; j >= minShadeGrid; j--) + canvas->write(lineOptions->shadeDot, { .x = gridX, .y = j }, false, lineOptions->lineColor); + break; + } + case ShadeOptions::SHADE_ABOVE_FIRST: + { + for (long long j = lastGridY + 1; j <= minShadeGrid; j++) + canvas->write(lineOptions->shadeDot, { .x = gridX, .y = j }, false, lineOptions->lineColor); + break; + } + case ShadeOptions::SHADE_BELOW_SECOND: + { + for (long long j = lastGridY - 1; j >= maxShadeGrid; j--) + canvas->write(lineOptions->shadeDot, { .x = gridX, .y = j }, false, lineOptions->lineColor); + break; + } + case ShadeOptions::SHADE_ABOVE_SECOND: + { + for (long long j = lastGridY + 1; j <= maxShadeGrid; j++) + canvas->write(lineOptions->shadeDot, { .x = gridX, .y = j }, false, lineOptions->lineColor); + break; + } + case ShadeOptions::SHADE_BETWEEN_BOTH: + { + for (long long j = std::max(lastGridY + 1, maxShadeGrid); j <= minShadeGrid; j++) + canvas->write(lineOptions->shadeDot, { .x = gridX, .y = j }, false, lineOptions->lineColor); + break; + } + case ShadeOptions::SHADE_OUTSIDE_BOTH: + { + // Shade below first + for (long long j = lastGridY - 1; j >= minShadeGrid; j--) + canvas->write(lineOptions->shadeDot, { .x = gridX, .y = j }, false, lineOptions->lineColor); + + // Shade above second + for (long long j = lastGridY + 1; j <= maxShadeGrid; j++) + canvas->write(lineOptions->shadeDot, { .x = gridX, .y = j }, false, lineOptions->lineColor); + break; + } + default: + break; + } } } diff --git a/src/matrix/ref/ref.cpp b/src/matrix/ref/ref.cpp index a26413cb0..5d7d65ce8 100644 --- a/src/matrix/ref/ref.cpp +++ b/src/matrix/ref/ref.cpp @@ -65,15 +65,18 @@ int main() }, { "width"_p = 90LL, - "xMin"_p = Number(-3.14), - "xMax"_p = Number(3.14), + "xMin"_p = Number(-6.28), + "xMax"_p = Number(6.28), "title"_p = "Sine and cosine graphs from -pi to +pi"s, }, { { "title"_p = "Sine"s }, { "lineColor"_p = (ColorFunc)colors::red, - "lineDot"_p = "*"sv, + "lineDot"_p = graphing::GraphDot::LIGHT_BLOCK_2, + "shadeDot"_p = graphing::GraphDot::LIGHT_BLOCK_1, "title"_p = "Cosine"s, + "shadeOptions"_p = graphing::ShadeOptions::SHADE_OUTSIDE_BOTH, + "shadeValues"_p = graphing::NumberPair{ 0.1, 0.5 }, } }); graphing::conPlotBar( @@ -91,11 +94,12 @@ int main() { { "title"_p = "Bar 1"s, - "color"_p = (ColorFunc)colors::blue, + "color"_p = static_cast(colors::blue), + "block"_p = graphing::GraphDot::LIGHT_BLOCK_1, }, { "title"_p = "Bar 2"s, - "color"_p = (ColorFunc)colors::red, + "color"_p = static_cast(colors::red), }, }); } From d6e09560bcee8b77d0b3d374c1616856c842abdc Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Thu, 10 Jul 2025 11:11:47 +0800 Subject: [PATCH 15/18] [#59] Fix addition. --- README.md | 7 ++++++- src/calc/add/add.cpp | 13 ++++++------- src/calc/add/addReport.cpp | 8 ++++++-- src/conPlot/conPlot.cpp | 2 ++ 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 4596218a5..43c2a4f70 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,9 @@ [![CMake Build](https://github.com/ZCG-coder/Steppable/actions/workflows/cmake-multi-platform.yml/badge.svg)](https://github.com/ZCG-coder/Steppable/actions/workflows/cmake-multi-platform.yml) [![Doxygen Deploy](https://github.com/ZCG-coder/Steppable/actions/workflows/doxygen-gh-pages.yml/badge.svg)](https://github.com/ZCG-coder/Steppable/actions/workflows/doxygen-gh-pages.yml) -This project aims to make a Computer Algebra System (CAS) from scratch, and without any external libraries. +This project aims to build a Computer Algebra System (CAS) from scratch, without any external libraries. + +All documentation, usage instructions, and developer guides are available in the [project Wiki](https://github.com/ZCG-coder/Steppable/wiki). + +--- +For contribution guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/src/calc/add/add.cpp b/src/calc/add/add.cpp index 1b3a8a3fc..009f51990 100644 --- a/src/calc/add/add.cpp +++ b/src/calc/add/add.cpp @@ -92,14 +92,8 @@ namespace steppable::__internals::calc const bool aIsDecimal = not isZeroString(aDecimal); const bool bIsDecimal = not isZeroString(bDecimal); - if (negative) + if (negative or (aIsNegative and bIsNegative)) resultIsNegative = true; - else if (aIsNegative and bIsNegative) - { - resultIsNegative = true; - aIsNegative = false; - bIsNegative = false; - } else if (aIsNegative) { if (steps == 2) @@ -158,6 +152,11 @@ namespace steppable::__internals::calc if (sumDigits.front() == 0 and properlyFormat) sumDigits.erase(sumDigits.begin()); + if (aIsNegative) + aInteger = "-" + aInteger; + if (bIsNegative) + bInteger = "-" + bInteger; + return reportAdd( aInteger, aDecimal, bInteger, bDecimal, sumDigits, carries, resultIsNegative, steps, properlyFormat); } diff --git a/src/calc/add/addReport.cpp b/src/calc/add/addReport.cpp index bfd0f5ce1..37d8a2d98 100644 --- a/src/calc/add/addReport.cpp +++ b/src/calc/add/addReport.cpp @@ -74,13 +74,15 @@ std::string reportAdd(const std::string& aInteger, if (bOut.length() > aOut.length()) ss << std::string(bOut.length() - aOut.length(), ' '); for (const char aChar : aOut) - ss << aChar << " "; + if (aChar != '-') + ss << aChar << " "; ss << '\n' << "+ "; // Print an add sign before printing b if (aOut.length() > bOut.length()) ss << std::string(aOut.length() - bOut.length(), ' '); for (const char bChar : bOut) - ss << bChar << " "; + if (bChar != '-') + ss << bChar << " "; ss << '\n' << " "; for (const int c : carries) @@ -106,6 +108,8 @@ std::string reportAdd(const std::string& aInteger, ss << aOut << " + " << bOut << " = "; std::string outStr; + if (resultIsNegative) + outStr = "-"; for (const auto c : sumDigits) if (c == -1) outStr += "."; diff --git a/src/conPlot/conPlot.cpp b/src/conPlot/conPlot.cpp index ec9d7018e..5e2795608 100644 --- a/src/conPlot/conPlot.cpp +++ b/src/conPlot/conPlot.cpp @@ -168,4 +168,6 @@ namespace steppable::graphing conPlotLegend(&graphOptions, &linesOptions, &canvas); std::cout << canvas.asString() << "\n"; } + + void pieChart() {} } // namespace steppable::graphing From 13a9d808a17f865228cea5ac85bb30f6dd4354eb Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Thu, 10 Jul 2025 11:40:35 +0800 Subject: [PATCH 16/18] Fix segmentation fault in subtract. --- src/calc/subtract/subtract.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calc/subtract/subtract.cpp b/src/calc/subtract/subtract.cpp index c073a6707..9f849140f 100644 --- a/src/calc/subtract/subtract.cpp +++ b/src/calc/subtract/subtract.cpp @@ -76,7 +76,7 @@ namespace steppable::__internals::calc { if (steps == 2) // Adding {0} and {1} since {0} is negative - std::cout << $("subtract", "063f0bd2-a4ca-4433-97c0-8baa73cd0e7c", { a.substr(1), b }) << "\n"; + std::cout << $("subtract", "063f0bd2-a4ca-4433-97c0-8baa73cd0e7c", { a.substr(1), b, b }) << "\n"; auto addResult = add(a.substr(1), b, steps, true); auto res = addResult.substr(addResult.find_last_of(' ') + 1); if (steps == 2) From e68d818b965550418af6c3c743df0321474155fe Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Fri, 11 Jul 2025 21:36:15 +0800 Subject: [PATCH 17/18] Fix result issue for adding negative numbers - Fix wrong results for input like -1, 1 --- .idea/runConfigurations/Run_Tests.xml | 7 +++++++ src/calc/subtract/subtract.cpp | 9 ++++----- src/steppable/mat2d.cpp | 10 +++++----- 3 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 .idea/runConfigurations/Run_Tests.xml diff --git a/.idea/runConfigurations/Run_Tests.xml b/.idea/runConfigurations/Run_Tests.xml new file mode 100644 index 000000000..00d0cd790 --- /dev/null +++ b/.idea/runConfigurations/Run_Tests.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/src/calc/subtract/subtract.cpp b/src/calc/subtract/subtract.cpp index 9f849140f..4bd074e9f 100644 --- a/src/calc/subtract/subtract.cpp +++ b/src/calc/subtract/subtract.cpp @@ -84,23 +84,22 @@ namespace steppable::__internals::calc // Replace last line with a subtraction report std::stringstream ss; ss << addResult.substr(0, addResult.find_last_of('\n')) << '\n'; - ss << THEREFORE << ' ' << a << " - " << b << " = -" << res; + ss << THEREFORE << ' ' << a << " - " << b << " = " << res; return ss.str(); } if (steps == 1) { std::stringstream ss; - ss << THEREFORE << ' ' << a << " - " << b << " = -" << res; + ss << THEREFORE << ' ' << a << " - " << b << " = " << res; return ss.str(); } - return "-" + addResult; + return standardizeNumber(addResult); } if (bIsNegative) { if (steps == 2) // Adding {0} and {1} since {2} is negative std::cout << $("subtract", "063f0bd2-a4ca-4433-97c0-8baa73cd0e7c", { a, b.substr(1), b }) << "\n"; - resultIsNegative = false; return add(a, b.substr(1), steps); } if (compare(a, b, 0) == "0") @@ -122,7 +121,7 @@ namespace steppable::__internals::calc << subtractResult.substr(subtractResult.find_last_of(' ') + 1); return ss.str(); } - return "-" + subtractResult; + return standardizeNumber("-" + subtractResult); } std::string aStr = aInteger + aDecimal; diff --git a/src/steppable/mat2d.cpp b/src/steppable/mat2d.cpp index a29dc0d4c..6ebd70820 100644 --- a/src/steppable/mat2d.cpp +++ b/src/steppable/mat2d.cpp @@ -241,11 +241,11 @@ namespace steppable MatVec2D mat = data; mat = roundOffValues(mat, static_cast(prec) + 3); - for (int col = 0, row = 0; col < _cols && row < _rows; ++col) + for (long long signed col = 0, row = 0; col < _cols && row < _rows; ++col) { // Find first non-zero in column col, at or below row - int sel = -1; - for (int i = row; i < _rows; ++i) + long long signed sel = -1; + for (long long signed i = row; i < _rows; ++i) if (mat[i][col] != 0.0) { sel = i; @@ -258,10 +258,10 @@ namespace steppable std::swap(mat[row], mat[sel]); // Swap if needed // Eliminate below - for (int i = row + 1; i < _rows; ++i) + for (long long signed i = row + 1; i < _rows; ++i) { Number factor = mat[i][col] / mat[row][col]; - for (int j = col; j < _cols; ++j) + for (long long signed j = col; j < _cols; ++j) mat[i][j] -= factor * mat[row][j]; } ++row; From 8b371b2dba7093507c1472cb3cc71fe30d689f54 Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Sat, 12 Jul 2025 05:53:03 +0800 Subject: [PATCH 18/18] Add translations for `mat2d` --- res/translations/en-US/mat2d.stp_localized | 35 ++++++++++++++++ res/translations/mat2d.stp_strings | 35 ++++++++++++++++ res/translations/zh-HK/mat2d.stp_localized | 35 ++++++++++++++++ src/steppable/mat2d.cpp | 46 ++++++++++++---------- 4 files changed, 130 insertions(+), 21 deletions(-) create mode 100644 res/translations/en-US/mat2d.stp_localized create mode 100644 res/translations/mat2d.stp_strings create mode 100644 res/translations/zh-HK/mat2d.stp_localized diff --git a/res/translations/en-US/mat2d.stp_localized b/res/translations/en-US/mat2d.stp_localized new file mode 100644 index 000000000..24530dba2 --- /dev/null +++ b/res/translations/en-US/mat2d.stp_localized @@ -0,0 +1,35 @@ +##################################################################################################### +# Copyright (c) 2023-2025 NWSOFT # +# # +# Permission is hereby granted, free of charge, to any person obtaining a copy # +# of this software and associated documentation files (the "Software"), to deal # +# in the Software without restriction, including without limitation the rights # +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # +# copies of the Software, and to permit persons to whom the Software is # +# furnished to do so, subject to the following conditions: # +# # +# The above copyright notice and this permission notice shall be included in all # +# copies or substantial portions of the Software. # +# # +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # +# SOFTWARE. # +##################################################################################################### + +# NOTE: This file is generated. Do not edit it manually. Any changes will be overwritten. +# STR_GUID: (key) / STRING TRANSLATED: (string) +# eg: a491b7b2-1239-4acb-9045-0747d806b96f >> "Hello World!" +# Recommended syntax highlighting: Bash Script +75953952-0eea-4716-a006-d0f2e7a8f6c9 >> "Matrix has non-uniform dimensions." +8d4e4757-415b-4aed-8f5e-26b3503a95dd >> "Incorrect x parameter. {0} exceeds the number of columns in this matrix ({1})." +e7cb3f0b-11d8-4e12-8c93-4a1021b15e10 >> "Incorrect y parameter. {0} exceeds the number of rows in this matrix ({1})." +fe78bdc2-b409-4078-8e0e-313c46977f25 >> "Matrix is not a square." +88331f88-3a4c-4b7e-9b43-b51a1d1020e2 >> "Matrix dimensions mismatch. Expect {0} columns. Got {1} columns." +34e92306-a4d8-4ff0-8441-bfcd29771e94 >> "Matrix dimensions mismatch. Expect {0} rows. Got {1} rows." +17b6aadd-bce1-4558-a7cc-7a099f00e57c >> "Incorrect matrix dimensions for multiplication." +8966ce13-8ae9-4f14-ba4e-837b98a4c9fa >> "For matrix multiplication, the number of columns in the first matrix must be equal to the number of rows in the second matrix." +f255d307-9482-442b-a523-61a1c7465f9c >> "Incorrect RHS matrix dimensions. Expect {0} rows, got {1}." \ No newline at end of file diff --git a/res/translations/mat2d.stp_strings b/res/translations/mat2d.stp_strings new file mode 100644 index 000000000..5f6120909 --- /dev/null +++ b/res/translations/mat2d.stp_strings @@ -0,0 +1,35 @@ +##################################################################################################### +# Copyright (c) 2023-2025 NWSOFT # +# # +# Permission is hereby granted, free of charge, to any person obtaining a copy # +# of this software and associated documentation files (the "Software"), to deal # +# in the Software without restriction, including without limitation the rights # +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # +# copies of the Software, and to permit persons to whom the Software is # +# furnished to do so, subject to the following conditions: # +# # +# The above copyright notice and this permission notice shall be included in all # +# copies or substantial portions of the Software. # +# # +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # +# SOFTWARE. # +##################################################################################################### + +# NOTE: This file is generated. Do not edit it manually. Any changes will be overwritten. +# STR_GUID: (key) / STRING ORIGINAL: (string) +# eg: a491b7b2-1239-4acb-9045-0747d806b96f >> "Hello World!" +# Recommended syntax highlighting: Bash Script +75953952-0eea-4716-a006-d0f2e7a8f6c9 >> "Matrix has non-uniform dimensions." +8d4e4757-415b-4aed-8f5e-26b3503a95dd >> "Incorrect x parameter. {0} exceeds the number of columns in this matrix ({1})." +e7cb3f0b-11d8-4e12-8c93-4a1021b15e10 >> "Incorrect y parameter. {0} exceeds the number of rows in this matrix ({1})." +fe78bdc2-b409-4078-8e0e-313c46977f25 >> "Matrix is not a square." +88331f88-3a4c-4b7e-9b43-b51a1d1020e2 >> "Matrix dimensions mismatch. Expect {0} columns. Got {1} columns." +34e92306-a4d8-4ff0-8441-bfcd29771e94 >> "Matrix dimensions mismatch. Expect {0} rows. Got {1} rows." +17b6aadd-bce1-4558-a7cc-7a099f00e57c >> "Incorrect matrix dimensions for multiplication." +8966ce13-8ae9-4f14-ba4e-837b98a4c9fa >> "For matrix multiplication, the number of columns in the first matrix must be equal to the number of rows in the second matrix." +f255d307-9482-442b-a523-61a1c7465f9c >> "Incorrect RHS matrix dimensions. Expect {0} rows, got {1}." diff --git a/res/translations/zh-HK/mat2d.stp_localized b/res/translations/zh-HK/mat2d.stp_localized new file mode 100644 index 000000000..7db1b9c58 --- /dev/null +++ b/res/translations/zh-HK/mat2d.stp_localized @@ -0,0 +1,35 @@ +##################################################################################################### +# Copyright (c) 2023-2025 NWSOFT # +# # +# Permission is hereby granted, free of charge, to any person obtaining a copy # +# of this software and associated documentation files (the "Software"), to deal # +# in the Software without restriction, including without limitation the rights # +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # +# copies of the Software, and to permit persons to whom the Software is # +# furnished to do so, subject to the following conditions: # +# # +# The above copyright notice and this permission notice shall be included in all # +# copies or substantial portions of the Software. # +# # +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # +# SOFTWARE. # +##################################################################################################### + +# NOTE: This file is generated. Do not edit it manually. Any changes will be overwritten. +# STR_GUID: (key) / STRING TRANSLATED: (string) +# eg: a491b7b2-1239-4acb-9045-0747d806b96f >> "Hello World!" +# Recommended syntax highlighting: Bash Script +75953952-0eea-4716-a006-d0f2e7a8f6c9 >> "矩陣大小不均勻。" +8d4e4757-415b-4aed-8f5e-26b3503a95dd >> "x 參數錯誤。{0} 超過了矩陣中的列數({1})。" +e7cb3f0b-11d8-4e12-8c93-4a1021b15e10 >> "y 參數錯誤。{0} 超過了矩陣中的行數({1})。" +fe78bdc2-b409-4078-8e0e-313c46977f25 >> "矩陣不為方形。" +88331f88-3a4c-4b7e-9b43-b51a1d1020e2 >> "矩陣大小不匹配。需要 {0} 列,輸入為 {1} 列。" +34e92306-a4d8-4ff0-8441-bfcd29771e94 >> "矩陣大小不匹配。需要 {0} 行,輸入為 {1} 行。" +17b6aadd-bce1-4558-a7cc-7a099f00e57c >> "矩陣大小不適用乘法。" +8966ce13-8ae9-4f14-ba4e-837b98a4c9fa >> "兩個矩陣的乘法僅當第一個矩陣的列數和B的行數相等時才能定義。" +f255d307-9482-442b-a523-61a1c7465f9c >> "矩陣 B 大小錯誤。需要 {0} 行,輸入為 {1} 行。" diff --git a/src/steppable/mat2d.cpp b/src/steppable/mat2d.cpp index 6ebd70820..6882decbd 100644 --- a/src/steppable/mat2d.cpp +++ b/src/steppable/mat2d.cpp @@ -29,6 +29,7 @@ #include "steppable/mat2d.hpp" +#include "getString.hpp" #include "output.hpp" #include "platform.hpp" #include "rounding.hpp" @@ -47,6 +48,7 @@ namespace steppable { using namespace __internals; using namespace __internals::numUtils; + using namespace localization; namespace prettyPrint::printers { @@ -106,7 +108,7 @@ namespace steppable const size_t rowSize = row.size(); if (rowSize != size) { - output::error("Matrix::_checkDataSanity"s, "Matrix has non-uniform dimensions."s); + output::error("Matrix::_checkDataSanity"s, $("mat2d", "75953952-0eea-4716-a006-d0f2e7a8f6c9")); utils::programSafeExit(1); } size = rowSize; @@ -137,16 +139,16 @@ namespace steppable if (x > _cols) { - output::error("Matrix::operator[]"s, - "Incorrect x parameter. {0} exceeds the number of columns in this matrix ({1})."s, - { std::to_string(x), std::to_string(_cols) }); + output::error( + "Matrix::operator[]"s, + $("mat2d", "8d4e4757-415b-4aed-8f5e-26b3503a95dd"s, { std::to_string(x), std::to_string(_cols) })); utils::programSafeExit(1); } if (y > _rows) { - output::error("Matrix::operator[]"s, - "Incorrect y parameter. {0} exceeds the number of rows in this matrix ({1})."s, - { std::to_string(y), std::to_string(_rows) }); + output::error( + "Matrix::operator[]"s, + $("mat2d", "e7cb3f0b-11d8-4e12-8c93-4a1021b15e10"s, { std::to_string(y), std::to_string(_rows) })); utils::programSafeExit(1); } } @@ -274,7 +276,7 @@ namespace steppable { if (_rows != _cols) { - output::error("Matrix::det"s, "Matrix is not a square."s); + output::error("Matrix::det"s, $("mat2d", "fe78bdc2-b409-4078-8e0e-313c46977f25")); utils::programSafeExit(1); } int sign = 1; @@ -323,15 +325,17 @@ namespace steppable if (rhs._cols != _cols) { output::error("Matrix::operator+"s, - "Matrix dimensions mismatch. Expect {0} columns. Got {1} columns."s, - { std::to_string(_cols), std::to_string(rhs._cols) }); + $("mat2d", + "88331f88-3a4c-4b7e-9b43-b51a1d1020e2", + { std::to_string(_cols), std::to_string(rhs._cols) })); utils::programSafeExit(1); } if (rhs._rows != _rows) { output::error("Matrix::operator+"s, - "Matrix dimensions mismatch. Expect {0} rows. Got {1} rows."s, - { std::to_string(_rows), std::to_string(rhs._rows) }); + $("mat2d", + "34e92306-a4d8-4ff0-8441-bfcd29771e94", + { std::to_string(_rows), std::to_string(rhs._rows) })); utils::programSafeExit(1); } @@ -381,11 +385,9 @@ namespace steppable { if (_cols != rhs._rows) { - output::error("Matrix::operator*"s, "Incorrect matrix dimensions for multiplication."s); + output::error("Matrix::operator*"s, $("mat2d", "17b6aadd-bce1-4558-a7cc-7a099f00e57c")); // https://en.wikipedia.org/wiki/Matrix_multiplication - output::info("Matrix::operator*"s, - "For matrix multiplication, the number of columns in the first matrix must be equal to the " - "number of rows in the second matrix"s); + output::info("Matrix::operator*"s, $("mat2d", "8966ce13-8ae9-4f14-ba4e-837b98a4c9fa")); utils::programSafeExit(1); } Matrix matrix = Matrix::zeros(_rows, rhs._cols); @@ -401,8 +403,9 @@ namespace steppable if (rhs._rows != _rows) { output::error("Matrix::operator<<"s, - "Incorrect RHS matrix dimensions. Expect {0} rows, got {1}"s, - { std::to_string(rhs._rows), std::to_string(rhs._rows) }); + $("mat2d", + "f255d307-9482-442b-a523-61a1c7465f9c", + { std::to_string(rhs._rows), std::to_string(rhs._rows) })); utils::programSafeExit(1); } @@ -425,9 +428,10 @@ namespace steppable { if (rhs._rows != _rows) { - output::error("Matrix::operator>>"s, - "Incorrect RHS matrix dimensions. Expect {0} rows, got {1}"s, - { std::to_string(rhs._rows), std::to_string(rhs._rows) }); + output::error("Matrix::operator<<"s, + $("mat2d", + "f255d307-9482-442b-a523-61a1c7465f9c", + { std::to_string(rhs._rows), std::to_string(rhs._rows) })); utils::programSafeExit(1); }