diff --git a/src/backend/BackendSVG.cpp b/src/backend/BackendSVG.cpp index a7eeb2f..19aa6eb 100644 --- a/src/backend/BackendSVG.cpp +++ b/src/backend/BackendSVG.cpp @@ -258,6 +258,10 @@ void BackendSVG::clear_tooltip() { m_onmouseout_tooltip.clear(); } +void BackendSVG::stroke_style(const std::string &style) { + m_line_style = "stroke-dasharray=\"" + style + "\""; +} + void BackendSVG::stroke_width(const float lw) { m_linewidth = std::string("stroke-width=\"") + std::to_string(lw) + std::string("\""); @@ -269,7 +273,7 @@ void BackendSVG::fill_color(const RGBA &color) { void BackendSVG::stroke() { m_out << " get_ticks() const { return {m_nx_ticks, m_ny_ticks}; } + /// get the number of maximum yticks length (in pixels) + int max_ytick_pixels() const { + /// Convertion from length * font_size to pixels + /// The scale factor, i.e., 1.25, is for Roboto font (https://bugbox.clickteam.com/threads/82514-Useful-Roboto-Font-Size-Pixel-Heights!) + return int(this->max_ytick_char_len() * m_style.font_size() / 1.25); + } + + // get the number of maximum char length of yticks + int max_ytick_char_len() const { return m_max_ytick_char_len; } + + // get default font size + float font_size() const { return m_style.font_size(); } + + // get maximum char + std::string max_ytick_char() const { return m_max_ytick_char; } + private: /// Create a new Geometry on this axis and return a shared pointer to it. /// diff --git a/src/frontend/Axis.tcc b/src/frontend/Axis.tcc index 78eddf1..232655f 100644 --- a/src/frontend/Axis.tcc +++ b/src/frontend/Axis.tcc @@ -37,6 +37,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "frontend/Axis.hpp" #include "frontend/Geometry.hpp" #include "frontend/Line.hpp" +#include +#include namespace trase { @@ -96,18 +98,28 @@ template void Axis::draw_common_ticks(Backend &backend) { backend.text_align(ALIGN_RIGHT | ALIGN_MIDDLE); + size_t max_char_len = this->max_ytick_char_len(); // y ticks for (std::size_t i = 0; i < m_tick_info.y_pos.size(); ++i) { + const float pos = m_tick_info.y_pos[i]; const float val = m_tick_info.y_val[i]; backend.move_to(vfloat2_t(m_pixels.bmin[0] - m_tick_len / 2, pos)); backend.line_to(vfloat2_t(m_pixels.bmin[0], pos)); std::snprintf(buffer, sizeof(buffer), "%.*g", m_sig_digits + 1, val); + + if (strlen(buffer) > max_char_len) { + max_char_len = strlen(buffer); + m_max_ytick_char = std::string(buffer); + } + backend.text(vfloat2_t(m_pixels.bmin[0] - m_tick_len / 2, pos), buffer, NULL); } + m_max_ytick_char_len = max_char_len; + backend.stroke_color(RGBA(0, 0, 0, 255)); backend.stroke_width(m_style.line_width() / 2); backend.stroke(); @@ -167,8 +179,7 @@ template void Axis::draw_common_ylabel(Backend &backend) { } backend.text_align(ALIGN_CENTER | ALIGN_BOTTOM); - const vfloat2_t point = vfloat2_t( - m_pixels.bmin[0] - 0.05f * (m_pixels.bmax[0] - m_pixels.bmin[0]), + const vfloat2_t point = vfloat2_t(m_pixels.bmin[0] - this->max_ytick_pixels(), 0.5f * (m_pixels.bmax[1] + m_pixels.bmin[1])); backend.translate(point); backend.rotate(-pi / 2.0f); diff --git a/src/frontend/Line.tcc b/src/frontend/Line.tcc index c001a8a..255d05c 100644 --- a/src/frontend/Line.tcc +++ b/src/frontend/Line.tcc @@ -174,6 +174,7 @@ template void Line::draw_plot(Backend &backend) { backend.stroke_color(m_style.color()); backend.stroke_width(m_style.line_width()); + backend.stroke_style(m_style.line_style()); backend.stroke(); } diff --git a/src/util/Style.cpp b/src/util/Style.cpp index 067d461..21ae9db 100644 --- a/src/util/Style.cpp +++ b/src/util/Style.cpp @@ -37,6 +37,17 @@ namespace trase { float Style::line_width() const noexcept { return m_line_width; } +std::string Style::line_style() const noexcept { + if (m_line_style == "solid") + return "0"; + else if (m_line_style == "dashed") + return "10,5"; + else if (m_line_style == "dotted") + return "10,3,2"; + else + return "0"; +} + float Style::font_size() const noexcept { return m_font_size; } RGBA Style::color() const noexcept { return m_color; } @@ -48,6 +59,11 @@ Style &Style::line_width(const float lineWidth) noexcept { return *this; } +Style &Style::line_style(const std::string lineStyle) noexcept { + m_line_style = lineStyle; + return *this; +} + Style &Style::font_size(const float fontSize) noexcept { m_font_size = fontSize; return *this; diff --git a/src/util/Style.hpp b/src/util/Style.hpp index b7473bb..56b9021 100644 --- a/src/util/Style.hpp +++ b/src/util/Style.hpp @@ -37,6 +37,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define STYLE_H_ #include "util/Colors.hpp" +#include namespace trase { @@ -45,6 +46,9 @@ class Style { /// the line width float m_line_width{3.f}; + /// the line style + std::string m_line_style{"solid"}; + /// the font size float m_font_size{18.f}; @@ -61,6 +65,9 @@ class Style { /// get the current line width float line_width() const noexcept; + /// get the current line width + std::string line_style() const noexcept; + /// get the current font size float font_size() const noexcept; @@ -73,6 +80,9 @@ class Style { /// set the new line width Style &line_width(float lineWidth) noexcept; + /// set the new line style + Style &line_style(std::string lineStyle) noexcept; + /// set the new font size Style &font_size(float fontSize) noexcept; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 80e4b10..5d2588e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,13 +24,13 @@ add_executable ( TestTransformMatrix.cpp TestVector.cpp TestLegend.cpp + TestLines.cpp ) + if (CURL_FOUND) target_sources(trase_test PRIVATE TestCSVDownloader.cpp) endif (CURL_FOUND) target_include_directories (trase_test PRIVATE tests) target_link_libraries (trase_test PRIVATE trase) -add_test (the_trase_test trase_test) - - +add_test (the_trase_test trase_test) \ No newline at end of file diff --git a/tests/TestAxis.cpp b/tests/TestAxis.cpp index 27e602b..077fd33 100644 --- a/tests/TestAxis.cpp +++ b/tests/TestAxis.cpp @@ -137,3 +137,36 @@ TEST_CASE("set number of ticks", "[axis]") { CHECK(ax->get_ticks()[0] == 10); CHECK(ax->get_ticks()[1] == 10); } + + +TEST_CASE("ylabel position: case 1", "[axis]") { + std::vector predicted_data = { + -13.8881, -13.8671, -13.8361, -13.7881, -13.7681, -13.7381, -12.6881, -12.661, -12.6481 + }; + std::vector exp_data = { + -13.8881, -13.8671, -13.8361, -13.7881, -13.7681, -13.7381, -12.6881, -12.661, -12.6481 + }; + + auto fig = figure(); + auto ax = fig->axis(); + int predicted_size = predicted_data.size(); + int exp_size = exp_data.size(); + + std::vector x(predicted_size); + std::vector y(exp_size); + for (int i = 0; i < predicted_size; ++i) { + x[i] = predicted_data[i]; + } + for (int i = 0; i < exp_size; ++i) { + y[i] = exp_data[i]; + } + + auto data = create_data().x(x).y(y); + auto plt = ax->points(data); + + ax->xlabel("Pred. Target"); + ax->ylabel("Exp. Target"); + + DummyDraw::draw("axis", fig); +} + diff --git a/tests/TestLines.cpp b/tests/TestLines.cpp new file mode 100644 index 0000000..e3cae76 --- /dev/null +++ b/tests/TestLines.cpp @@ -0,0 +1,101 @@ +/* +Copyright (c) 2018, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of the Oxford RSE C++ Template project. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "catch.hpp" + +#include "DummyDraw.hpp" + +#include "trase.hpp" +#include +#include + +using namespace trase; + +TEST_CASE("Lines creation", "[lines]") { + // create figure and axis + auto fig = figure(); + auto ax = fig->axis(); + + // create x and y vectors + const int n = 100; + std::vector x(n); + std::vector y(n); + const float k = 1.f; + auto logistic = [k](const float x) { return 1.f / (1.f + std::exp(-k * x)); }; + for (int i = 0; i < n; ++i) { + x[i] = -6.f + 12.f * static_cast(i) / n; + y[i] = logistic(x[i]); + } + + // create a trase dataset and then plot it using a line geometry + auto data = create_data().x(x).y(y); + auto plt = ax->line(data); + + // set line color = red and line width = 10 + plt->style().color(RGBA(255, 0, 0)).line_width(10).line_style("dashed"); + + // label axis + ax->xlabel("x"); + ax->ylabel("y"); + + DummyDraw::draw("lines", fig); +} + +TEST_CASE("Hybrids lines creation", "[lines]") { + + auto fig = figure(); + auto ax = fig->axis(); + + { + auto data = create_data().x(std::vector{0, 1}).y(std::vector{0, 1}); + auto plt = ax->line(data); + plt->style().color(RGBA(255, 0, 0)).line_width(5).line_style("solid"); + } + + { + auto data = create_data().x(std::vector{0, 1}).y(std::vector{0.5, 1.5}); + auto plt = ax->line(data); + plt->style().color(RGBA(255, 0, 0)).line_width(5).line_style("dashed"); + } + + { + auto data = create_data().x(std::vector{0, 1}).y(std::vector{-0.5, 0.5}); + auto plt = ax->line(data); + plt->style().color(RGBA(255, 0, 0)).line_width(5).line_style("dotted"); + } + + ax->xlabel("Pred. Target"); + ax->ylabel("Exp. Target"); + + DummyDraw::draw("lines", fig); +}