diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 6caaa3e9..59fb9a66 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -388,6 +388,12 @@ if(USE_GLEW) target_link_libraries(graph_twinax GLEW::GLEW) endif() +add_executable(graph_rightaxis graph_rightaxis.cpp) +target_link_libraries(graph_rightaxis OpenGL::GL glfw Freetype::Freetype) +if(USE_GLEW) + target_link_libraries(graph_rightaxis GLEW::GLEW) +endif() + add_executable(graph_dynamic_sine graph_dynamic_sine.cpp) target_link_libraries(graph_dynamic_sine OpenGL::GL glfw Freetype::Freetype) if(USE_GLEW) diff --git a/examples/graph_rightaxis.cpp b/examples/graph_rightaxis.cpp new file mode 100644 index 00000000..ce066675 --- /dev/null +++ b/examples/graph_rightaxis.cpp @@ -0,0 +1,34 @@ +// A graph with the axis on the right +#include +#include +#include + +using morph::unicode; + +int main() +{ + // Set up a morph::Visual 'scene environment'. + morph::Visual v(1024, 768, "Right-axis only GraphVisual example"); + // Create a new GraphVisual with offset within the scene of 0,0,0 + auto gv = std::make_unique> (morph::vec({0,0,0})); + v.bindmodel (gv); + gv->axisstyle = morph::axisstyle::box; // rather than twinax; + // Data for the x axis. A vvec is like std::vector, but with built-in maths methods + morph::vvec x; + // This works like numpy's linspace() (the 3 args are "start", "end" and "num"): + x.linspace (-0.5, 0.8, 14); + + // Set a graph up of x^3 + std::string ds1legend = unicode::toUtf8 (unicode::alpha) + "(x) = x" + unicode::toUtf8 (unicode::ss3); + gv->setdata (x, x.pow(3), ds1legend, morph::axisside::right); + gv->ylabel = unicode::toUtf8 (unicode::alpha); + + // finalize() makes the GraphVisual compute the vertices of the OpenGL model + gv->finalize(); + // Add the GraphVisual OpenGL model to the Visual scene + v.addVisualModel (gv); + // Render the scene on the screen until user quits with 'Ctrl-q' + v.keepOpen(); + // When v goes out of scope, gv will be deallocated + return 0; +} diff --git a/morph/GraphVisual.h b/morph/GraphVisual.h index 261eb04f..55d0317f 100644 --- a/morph/GraphVisual.h +++ b/morph/GraphVisual.h @@ -1355,7 +1355,7 @@ namespace morph { this->texts.push_back (std::move(lbl)); } } - if (this->axisstyle == axisstyle::twinax && !this->omit_y_tick_labels) { + if ((this->axisstyle == axisstyle::twinax || !this->ytick_posns2.empty()) && !this->omit_y_tick_labels) { x_for_yticks = this->width; this->ytick_label_width2 = 0.0f; for (unsigned int i = 0; i < this->ytick_posns2.size(); ++i) { @@ -1473,7 +1473,7 @@ namespace morph { {this->width + tl, (float)yt, -this->thickness}, this->uz, this->axiscolour, this->axislinewidth*0.5f); } - } else if (this->axisstyle == axisstyle::twinax) { + } else if (this->axisstyle == axisstyle::twinax || !this->ytick_posns2.empty()) { // Draw ticks for y2 for (auto yt : this->ytick_posns2) { this->computeFlatLine ({this->width, (float)yt, -this->thickness}, @@ -1699,14 +1699,22 @@ namespace morph { if (this->manualticks == true) { std::cout << "Writeme: Implement a manual tick-setting scheme\n"; } else { - if (!(this->abscissa_scale.ready() && this->ord1_scale.ready())) { + if (this->ord2_scale.ready()) { + if (!this->abscissa_scale.ready()) { + throw std::runtime_error ("abscissa scale is not set (though ord2 scale is set). Is there abscissa (x) data?"); + } + } else if (!(this->abscissa_scale.ready() && this->ord1_scale.ready())) { throw std::runtime_error ("abscissa and ordinate scales not set. Is there data?"); } // Compute locations for ticks... Flt _xmin = this->abscissa_scale.inverse_one (this->abscissa_scale.output_range.min); Flt _xmax = this->abscissa_scale.inverse_one (this->abscissa_scale.output_range.max); - Flt _ymin = this->ord1_scale.inverse_one (this->ord1_scale.output_range.min); - Flt _ymax = this->ord1_scale.inverse_one (this->ord1_scale.output_range.max); + Flt _ymin = Flt{0}; + Flt _ymax = Flt{1}; + if (this->ord1_scale.ready()) { + _ymin = this->ord1_scale.inverse_one (this->ord1_scale.output_range.min); + _ymax = this->ord1_scale.inverse_one (this->ord1_scale.output_range.max); + } Flt _ymin2 = Flt{0}; Flt _ymax2 = Flt{1}; if (this->ord2_scale.ready()) { @@ -1724,11 +1732,13 @@ namespace morph { this->xtick_posns.resize (this->xticks.size()); this->abscissa_scale.transform (xticks, xtick_posns); - realmin = this->ord1_scale.inverse_one (0); - realmax = this->ord1_scale.inverse_one (this->height); - this->yticks = this->maketicks (_ymin, _ymax, realmin, realmax, this->num_ticks_range); - this->ytick_posns.resize (this->yticks.size()); - this->ord1_scale.transform (yticks, ytick_posns); + if (this->ord1_scale.ready()) { + realmin = this->ord1_scale.inverse_one (0); + realmax = this->ord1_scale.inverse_one (this->height); + this->yticks = this->maketicks (_ymin, _ymax, realmin, realmax, this->num_ticks_range); + this->ytick_posns.resize (this->yticks.size()); + this->ord1_scale.transform (yticks, ytick_posns); + } if (this->ord2_scale.ready()) { realmin = this->ord2_scale.inverse_one (0); diff --git a/morph/scale.h b/morph/scale.h index a2db2a94..77432ec5 100644 --- a/morph/scale.h +++ b/morph/scale.h @@ -320,10 +320,10 @@ namespace morph { virtual S transform_one (const T& datum) const { if (this->type != scaling_function::Linear) { - throw std::runtime_error ("This transform function is for Linear scaling only"); + throw std::runtime_error ("scale_impl<0=vector>::transform_one(): This transform function is for Linear scaling only"); } if (params.size() != 2) { - throw std::runtime_error ("For linear scaling of ND vector lengths, need 2 params (set do_autoscale or call setParams())"); + throw std::runtime_error ("scale_impl<0=vector>::transform_one(): For linear scaling of ND vector lengths, need 2 params (set do_autoscale or call setParams())"); } S rtn(datum); T_el vec_len = this->vec_length (datum); @@ -364,7 +364,7 @@ namespace morph { } else if (this->type == scaling_function::Linear) { rtn = this->inverse_one_linear (datum); } else { - throw std::runtime_error ("Unknown scaling"); + throw std::runtime_error ("scale_impl<0=vector>::inverse_one(): Unknown scaling"); } return rtn; } @@ -380,7 +380,7 @@ namespace morph { virtual void compute_scaling (const T input_min, const T input_max) { if (this->type != scaling_function::Linear) { - throw std::runtime_error ("This scaling function is for Linear scaling only"); + throw std::runtime_error ("scale_impl<0=vector>::compute_scaling)(): This scaling function is for Linear scaling only"); } this->params.resize (2, T_el{0}); // Vector version: get lengths of input_min/max @@ -445,7 +445,7 @@ namespace morph { //! The inverse linear transform; x = (y-c)/m T inverse_one_linear (const T& datum) const { - if (this->params.size() < 2) { throw std::runtime_error ("Scaling params not set"); } + if (this->params.size() < 2) { throw std::runtime_error ("scale_impl<0=vector>::inverse_one_linear(): scaling params not set"); } T rtn (datum); std::size_t i = 0; for (auto el : datum) { @@ -499,7 +499,7 @@ namespace morph { } else if (this->type == scaling_function::Linear) { rtn = this->transform_one_linear (datum); } else { - throw std::runtime_error ("Unknown scaling"); + throw std::runtime_error ("scale_impl<1=scalar>::transform_one(): Unknown scaling"); } return rtn; } @@ -533,7 +533,7 @@ namespace morph { } else if (this->type == scaling_function::Linear) { rtn = this->inverse_one_linear (datum); } else { - throw std::runtime_error ("Unknown scaling"); + throw std::runtime_error ("scale_impl<1=scalar>::inverse_one(): Unknown scaling"); } return rtn; } @@ -553,7 +553,7 @@ namespace morph { } else if (this->type == scaling_function::Linear) { this->compute_scaling_linear (input_min, input_max); } else { - throw std::runtime_error ("Unknown scaling"); + throw std::runtime_error ("scale_impl<1=scalar>::compute_scaling(): Unknown scaling"); } } @@ -589,7 +589,7 @@ namespace morph { //! Linear transform for scalar type; y = mx + c S transform_one_linear (const T& datum) const { - if (this->params.size() < 2) { throw std::runtime_error ("Scaling params not set"); } + if (this->params.size() < 2) { throw std::runtime_error ("scale_impl<1=scalar>::transform_one_linear(): (scalar) scaling params not set"); } return (datum * this->params[0] + this->params[1]); } @@ -602,7 +602,7 @@ namespace morph { //! The inverse linear transform; x = (y-c)/m T inverse_one_linear (const S& datum) const { - if (this->params.size() < 2) { throw std::runtime_error ("Scaling params not set"); } + if (this->params.size() < 2) { throw std::runtime_error ("scale_impl<1=scalar>::inverse_one_linear(): (scalar) scaling params not set"); } return ((datum-this->params[1])/this->params[0]); } @@ -634,7 +634,7 @@ namespace morph { // Have to check here as scale_impl<2, T, S> is built from scale_impl<1, T, S> but <= operator makes no sense if constexpr (morph::number_type::value == 1) { if (input_min <= T{0} || input_max <= T{0}) { - throw std::runtime_error ("Can't logarithmically autoscale a range which includes zeros or negatives"); + throw std::runtime_error ("scale_impl<1=scalar>::compute_scaling_log(): Can't logarithmically autoscale a range which includes zeros or negatives"); } } T ln_imin = std::log(input_min); @@ -702,7 +702,7 @@ namespace morph { { // Log scaling complex range? if (std::abs(input_min) == T{0} || std::abs(input_max) == T{0}) { - throw std::runtime_error ("Can't logarithmically autoscale a complex range which includes zeros"); + throw std::runtime_error ("scale_impl<2, T, S>::compute_scaling_log(): Can't logarithmically autoscale a complex range which includes zeros"); } T ln_imin = std::log(input_min); T ln_imax = std::log(input_max);