├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── CMakeLists.txt ├── CMakeSettings.json ├── LICENSE ├── README.md ├── conanfile.txt ├── docs ├── .gitignore ├── requirements.txt └── source │ ├── about.rst │ ├── conf.py │ ├── favicon.ico │ ├── images │ └── digital_twin_component_basic.svg │ ├── index.rst │ ├── installation.rst │ ├── license.rst │ ├── quick_start.rst │ └── version_history.rst ├── examples ├── test_bar_and_scatter_plot.json ├── test_bar_plot.json ├── test_fit_lewkowicz_speed.json ├── test_layout_no_title_plot.json ├── test_layout_plot.json ├── test_mandelbrot_plot.json ├── test_scatter_plot.json ├── test_scatter_with_legend_plot.json ├── test_surface_plot.json └── test_wolfgang.json ├── source ├── CMakeLists.txt ├── cpplot.cpp ├── cpplot.h ├── eigen.h ├── exceptions.h ├── layout.h ├── main.cpp ├── online.h └── plot_types │ ├── bar.cpp │ ├── bar.h │ ├── scatter.cpp │ ├── scatter.h │ └── surface.h └── test ├── CMakeLists.txt └── unit ├── cpplot_test.cpp └── main_test.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | ####################### Build directories 2 | lib 3 | bin 4 | cmake-build-debug 5 | cmake-build-release 6 | 7 | 8 | ####################### Jetbrains CLion IDE 9 | 10 | # User-specific stuff: 11 | .idea 12 | 13 | ## Plugin-specific files: 14 | 15 | # IntelliJ 16 | /out/ 17 | 18 | # mpeltonen/sbt-idea plugin 19 | .idea_modules/ 20 | 21 | # JIRA plugin 22 | atlassian-ide-plugin.xml 23 | 24 | # Crashlytics plugin (for Android Studio and IntelliJ) 25 | com_crashlytics_export_strings.xml 26 | crashlytics.properties 27 | crashlytics-build.properties 28 | fabric.properties 29 | 30 | ####################### Microsoft Visual Studio 31 | 32 | # User-specific stuff: 33 | .vs 34 | 35 | ####################### ECLIPSE IDE 36 | 37 | .metadata 38 | tmp/ 39 | *.tmp 40 | *.bak 41 | *.swp 42 | *~.nib 43 | local.properties 44 | .settings/ 45 | .loadpath 46 | .recommenders 47 | 48 | # Eclipse Core 49 | .project 50 | 51 | # External tool builders 52 | .externalToolBuilders/ 53 | 54 | # Locally stored "Eclipse launch configurations" 55 | *.launch 56 | 57 | # PyDev specific (Python IDE for Eclipse) 58 | *.pydevproject 59 | 60 | # CDT-specific (C/C++ Development Tooling) 61 | .cproject 62 | 63 | # JDT-specific (Eclipse Java Development Tools) 64 | .classpath 65 | 66 | # Java annotation processor (APT) 67 | .factorypath 68 | 69 | # PDT-specific (PHP Development Tools) 70 | .buildpath 71 | 72 | # sbteclipse plugin 73 | .target 74 | 75 | # Tern plugin 76 | .tern-project 77 | 78 | # TeXlipse plugin 79 | .texlipse 80 | 81 | # STS (Spring Tool Suite) 82 | .springBeans 83 | 84 | # Code Recommenders 85 | .recommenders/ 86 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thclark/cpplot/c6be864d61fff1ed644b99c927afb14a24a82137/.gitmodules -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: 'build|docs|node_modules|.git|.tox|dist' 2 | default_stages: [commit] 3 | fail_fast: true 4 | default_language_version: 5 | python: python3 # force all unspecified python hooks to run python3 6 | repos: 7 | - repo: https://github.com/pre-commit/pre-commit-hooks 8 | rev: master 9 | hooks: 10 | - id: trailing-whitespace 11 | - id: end-of-file-fixer 12 | - id: check-yaml 13 | language_version: python3 14 | 15 | - repo: https://github.com/thclark/pre-commit-sphinx 16 | rev: 0.0.1 17 | hooks: 18 | - id: build-docs 19 | language_version: python3 20 | 21 | - repo: https://github.com/windpioneers/pre-commit-hooks 22 | rev: 0.0.5 23 | hooks: 24 | - id: check-branch-name 25 | args: 26 | - '^master$' 27 | - '^dev$' 28 | - '^devops/([a-z][a-z0-9]*)(-[a-z0-9]+)*$' 29 | - '^doc/([a-z][a-z0-9]*)(-[a-z0-9]+)*$' 30 | - '^feature/([a-z][a-z0-9]*)(-[a-z0-9]+)*$' 31 | - '^fix/([a-z][a-z0-9]*)(-[a-z0-9]+)*$' 32 | - '^hotfix/([a-z][a-z0-9]*)(-[a-z0-9]+)*$' 33 | - '^review/([a-z][a-z0-9]*)(-[a-z0-9]+)*$' 34 | - '^release/(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$' 35 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | project(cpplot) 3 | 4 | 5 | # Add cmake modules 6 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") 7 | 8 | 9 | # Set make options 10 | set (CMAKE_CXX_STANDARD 11) 11 | set(CMAKE_VERBOSE_MAKEFILE ON) 12 | 13 | # Lets glog play more nicely with Windows 14 | IF(CMAKE_SYSTEM_NAME STREQUAL Windows) 15 | add_definitions(-DGLOG_NO_ABBREVIATED_SEVERITIES) 16 | endif() 17 | 18 | # Add third party tools required by conan 19 | message("-- Adding dependencies with conan. Make sure you've called `conan install . --install-folder ${CMAKE_BINARY_DIR}`") 20 | include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) 21 | conan_basic_setup() 22 | 23 | 24 | # Add include directories so the test routines can pick up the library contents 25 | include_directories(source) 26 | 27 | 28 | # Add the source and test directories 29 | add_subdirectory(source) 30 | add_subdirectory(test) 31 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file. 3 | "configurations": [ 4 | { 5 | "name": "x86-Debug", 6 | "generator": "Visual Studio 15 2017", 7 | "configurationType": "Debug", 8 | "buildRoot": "${projectDir}\\cmake-build-debug\\bin\\${name}", 9 | "cmakeCommandArgs": "", 10 | "buildCommandArgs": "-m -v:minimal", 11 | "ctestCommandArgs": "" 12 | }, 13 | { 14 | "name": "x86-Release", 15 | "generator": "Visual Studio 15 2017", 16 | "configurationType": "Release", 17 | "buildRoot": "${projectDir}\\cmake-build-debug\\bin\\${name}", 18 | "cmakeCommandArgs": "", 19 | "buildCommandArgs": "-m -v:minimal", 20 | "ctestCommandArgs": "" 21 | }, 22 | { 23 | "name": "x64-Debug", 24 | "generator": "Visual Studio 15 2017 Win64", 25 | "configurationType": "Debug", 26 | "buildRoot": "${projectDir}\\cmake-build-debug\\bin\\${name}", 27 | "cmakeCommandArgs": "-T host=x64", 28 | "buildCommandArgs": "-m -v:minimal", 29 | "ctestCommandArgs": "" 30 | 31 | }, 32 | { 33 | "name": "x64-Release", 34 | "generator": "Visual Studio 15 2017 Win64", 35 | "configurationType": "Release", 36 | "buildRoot": "${projectDir}\\cmake-build-debug\\bin\\${name}", 37 | "cmakeCommandArgs": "-T host=x64", 38 | "buildCommandArgs": "-m -v:minimal", 39 | "ctestCommandArgs": "" 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tom Clark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) 2 | [![black-girls-code](https://img.shields.io/badge/black%20girls-code-f64279.svg)](https://www.blackgirlscode.com/) 3 | 4 | 5 | # cpplot 6 | 7 | Interactive graphs and charts for C++ 11 upward, 8 | [viewable in-browser using cpplot-viewer](https://cpplot.herokuapp.com). 9 | 10 | Full documentation is at [https://cpplot.readthedocs.io](https://cpplot.readthedocs.io) 11 | 12 | 13 | 14 | 15 | ## For Developers 16 | 17 | You should only need to read the following if you plan to develop on `cpplot`. 18 | 19 | ### Style guide 20 | 21 | We use the [Google C++ style guide](https://google.github.io/styleguide/cppguide.html) with the following exceptions: 22 | - Don't care about line width so long as its within reason 23 | - Use 4 space indenting, not 2 as suggested by the style guide, because we're not [total monsters](https://www.youtube.com/watch?v=SsoOG6ZeyUI) (just kidding xx). 24 | 25 | ### Pre-Commit 26 | 27 | You need to install pre-commit to get the hooks working. Do: 28 | ``` 29 | pip install pre-commit 30 | pre-commit install 31 | ``` 32 | 33 | Once that's done, each time you make a commit, the following checks are made: 34 | 35 | - valid github repo and files 36 | - code style 37 | - documentation builds correctly 38 | 39 | Upon failure, the commit will halt. **Re-running the commit will automatically fix most issues** except: 40 | - You'll have to fix documentation yourself prior to a successful commit (there's no auto fix for that!!). 41 | 42 | You can run pre-commit hooks without making a commit, like: 43 | ``` 44 | pre-commit run build-docs 45 | ``` 46 | which is useful to locally build documents without crazy efforts setting up an environment for sphinx. 47 | 48 | 49 | ### Compilation with cmake 50 | 51 | A cross-platform compilation file is provided using cmake, but it's **not tested on windows**. 52 | 53 | PRs to fix or improve it for windows are most welcome. 54 | -------------------------------------------------------------------------------- /conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | eigen/3.3.7 3 | nlohmann_json/3.8.0 4 | boost/1.73.0 5 | glog/0.4.0 6 | gtest/1.10.0 7 | tbb/2020.1 8 | [generators] 9 | cmake 10 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | doctrees 2 | html 3 | source/doxyoutput* 4 | source/library_api* 5 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | # Required by the python script for building documentation. You probably won't have to install this (because you can 3 | # pre-commit hook builds docs for you and manages the environment). 4 | Sphinx==1.8.3 5 | sphinx-rtd-theme==0.4.2 6 | sphinx-tabs==1.1.10 7 | breathe==4.11.1 8 | exhale==0.2.1 9 | -------------------------------------------------------------------------------- /docs/source/about.rst: -------------------------------------------------------------------------------- 1 | .. _about: 2 | 3 | ===== 4 | About 5 | ===== 6 | 7 | .. _why: 8 | 9 | Why? 10 | ==== 11 | 12 | For most engineering and scientific applications that come across my 13 | desk, it's a case of "prototype in MATLAB or Python, rewrite in C++". 14 | But: 15 | 16 | * I'm long past bothering with MATLAB (there's no credible CI/CD options, no sensible test framework, the slug size of the runtime library is 3.7Gb with a huge memory requirement making it undeployable on any sensible server, the license costs would make Bezos flinch, mex interfaces are grossly unwieldy, etc etc etc etc). 17 | 18 | * I'm getting really really sick of writing the damn code twice. 19 | 20 | * Most of my engineering and science apps are now getting deployed to cloud services. So displaying natively generated figures like from matplotlib in-browser is a nightmare. 21 | 22 | What I need is a solution where I can prototype straight into a language 23 | which is fast, capable and has a pretty good ecosystem of third party 24 | libraries to help me. 25 | 26 | Now, with: 27 | 28 | * the great improvements to C++ in the C++11 and 14 standards (which make it much easier to handle threading and generally connect APIs of various libraries together), 29 | 30 | * availability of libraries like Eigen, cpr, ceres-solver, Intel MKL (I could go on and on), 31 | 32 | * great utilities like googletest and Cmake to remove all the old-skool build and test agony... 33 | 34 | C++ is really getting there. The one thing missing is a plotting 35 | library, which allows me to: 36 | 37 | * quickly and easily prototype new algorithms (visually diagnose what's wrong etc etc) 38 | 39 | * display analysis results on the web 40 | 41 | * produce the same figures on different platforms 42 | 43 | * save publication quality figures easily 44 | 45 | In all the other languages I use, I now use ``plotly`` to do those 46 | things. So now it's here for C++ too. 47 | 48 | 49 | .. _about_plotly: 50 | 51 | About Plotly 52 | ============ 53 | 54 | **cpplot** is based on the plot.ly json schema for figures and charts. 55 | 56 | 57 | .. _figure_json_schema 58 | 59 | Figure json schema 60 | ------------------ 61 | 62 | At the core of plotly is a set of open specifications for JSON data 63 | which can be rendered into various figure types. The plotly team 64 | have hung a lot of services and offerings around it, 65 | but basically everything boils down to a `json schema `__. 66 | **cpplot** helps generate JSON which is compliant with that schema. 67 | 68 | 69 | .. _plotly_offline: 70 | 71 | Plotly Offline 72 | -------------- 73 | 74 | ``Plotly.js`` is an excellent javascript library for rendering figures, which can 75 | be used offline or in any third party app. This is what's used by the browser viewer. 76 | 77 | 78 | .. _plotly_online: 79 | 80 | Plotly Online 81 | ------------- 82 | 83 | Plot.ly have a range of online services, and anything produced by **cpplot** will be 84 | importable there, since it's compliant with their schema. 85 | 86 | I'm not a marketing person for plotly, so how to do that is out of scope here (check their docs)!! 87 | 88 | I have some code using the Plotly API (`see online.h `__ ), 89 | which might be the very early beginning of a client SDK for C++, 90 | enabling direct manipulation of online data. However, I have very little 91 | appetite for this (I personally don't use plotly online, because I've 92 | found the support to be appalling in the past) but collaborators 93 | committed to developing and maintaining that would be welcome. File an issue if you're interested. 94 | 95 | 96 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Documentation build configuration file 4 | # 5 | # This file is execfile()d with the current directory set to its containing dir. 6 | # 7 | # Note that not all possible configuration values are present in this 8 | # autogenerated file. 9 | # 10 | # All configuration values have a default; values that are commented out 11 | # serve to show the default. 12 | 13 | import os 14 | import sphinx_rtd_theme 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | # needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | # Breathe and exhale are added as recommended by: 29 | # https://exhale.readthedocs.io/en/latest/usage.html#usage-quickstart-guide 30 | extensions = [ 31 | 'sphinx.ext.todo', 32 | 'sphinx_tabs.tabs', 33 | 'sphinx.ext.mathjax', 34 | 'sphinx.ext.ifconfig', 35 | 'breathe', 36 | 'exhale', 37 | ] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # The suffix of source filenames. 43 | source_suffix = '.rst' 44 | 45 | # The encoding of source files. 46 | # source_encoding = 'utf-8-sig' 47 | 48 | # The master toctree document. 49 | master_doc = 'index' 50 | 51 | # General information about the project. 52 | project = u'cpplot' 53 | copyright = u'2017-20 Tom Clark' 54 | 55 | # The version info for the project you're documenting, acts as replacement for 56 | # |version| and |release|, also used in various other places throughout the 57 | # built documents. 58 | 59 | # The full version, including alpha/beta/rc tags. 60 | release = os.getenv('RELEASE_TAG', 'x.y.unknown') 61 | 62 | # The short X.Y version. 63 | version = '.'.join(release.split('.')[0:2]) 64 | 65 | # The language for content autogenerated by Sphinx. Refer to documentation 66 | # for a list of supported languages. 67 | # language = None 68 | 69 | # There are two options for replacing |today|: either, you set today to some 70 | # non-false value, then it is used: 71 | # today = '' 72 | # Else, today_fmt is used as the format for a strftime call. 73 | # today_fmt = '%B %d, %Y' 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | exclude_patterns = [] 78 | 79 | # The reST default role (used for this markup: `text`) to use for all documents. 80 | # default_role = None 81 | 82 | # If true, '()' will be appended to :func: etc. cross-reference text. 83 | # add_function_parentheses = True 84 | 85 | # If true, the current module name will be prepended to all description 86 | # unit titles (such as .. function::). 87 | # add_module_names = True 88 | 89 | # If true, sectionauthor and moduleauthor directives will be shown in the 90 | # output. They are ignored by default. 91 | # show_authors = False 92 | 93 | # The name of the Pygments (syntax highlighting) style to use. 94 | pygments_style = 'sphinx' 95 | 96 | # A list of ignored prefixes for module index sorting. 97 | # modindex_common_prefix = [] 98 | 99 | # -- Breathe and Exhale Configuration ------------------------------------------ 100 | 101 | # Setup the breathe extension 102 | breathe_projects = { 103 | "My Project": "./doxyoutput/xml" 104 | } 105 | breathe_default_project = "My Project" 106 | 107 | # Setup the exhale extension 108 | # TODO reset the doxygen input to ../../source once conan installation done, this should resent the build correctly 109 | exhale_args = { 110 | # These arguments are required 111 | "containmentFolder": "./library_api", 112 | "rootFileName": "library_root.rst", 113 | "rootFileTitle": "Library API", 114 | "doxygenStripFromPath": "../../", 115 | # Suggested optional arguments 116 | "createTreeView": True, 117 | # TIP: if using the sphinx-bootstrap-theme, you need 118 | # "treeViewIsBootstrap": True, 119 | "exhaleExecutesDoxygen": True, 120 | "exhaleDoxygenStdin": "INPUT = ../../source ../../source/plot_types" 121 | } 122 | 123 | # Tell sphinx what the primary language being documented is 124 | primary_domain = 'cpp' 125 | 126 | # Tell sphinx what the pygments highlight language should be 127 | highlight_language = 'cpp' 128 | 129 | 130 | # -- Options for HTML output --------------------------------------------------- 131 | 132 | # The theme to use for HTML and HTML Help pages. See the documentation for 133 | # a list of builtin themes. 134 | html_theme = 'sphinx_rtd_theme' 135 | 136 | # Theme options are theme-specific and customize the look and feel of a theme 137 | # further. For a list of options available for each theme, see the 138 | # documentation. 139 | # html_theme_options = {} 140 | 141 | # Add any paths that contain custom themes here, relative to this directory. 142 | # html_theme_path = ["_themes",] 143 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 144 | 145 | # The name for this set of Sphinx documents. If None, it defaults to 146 | # " v documentation". 147 | html_title = "cpplot: JSON based figures for C++" 148 | 149 | # A shorter title for the navigation bar. Default is the same as html_title. 150 | html_short_title = "cpplot" 151 | 152 | # The name of an image file (relative to this directory) to place at the top 153 | # of the sidebar. 154 | # html_logo = None 155 | 156 | # The name of an image file (within the static path) to use as favicon of the 157 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 158 | # pixels large. 159 | html_favicon = 'favicon.ico' 160 | 161 | # Add any paths that contain custom static files (such as style sheets) here, 162 | # relative to this directory. They are copied after the builtin static files, 163 | # so a file named "default.css" will overwrite the builtin "default.css". 164 | html_static_path = ['_static'] 165 | 166 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 167 | # using the given strftime format. 168 | # html_last_updated_fmt = '%b %d, %Y' 169 | 170 | # If true, SmartyPants will be used to convert quotes and dashes to 171 | # typographically correct entities. 172 | html_use_smartypants = True 173 | 174 | # Custom sidebar templates, maps document names to template names. 175 | # html_sidebars = {} 176 | 177 | # Additional templates that should be rendered to pages, maps page names to 178 | # template names. 179 | # html_additional_pages = {} 180 | 181 | # If false, no module index is generated. 182 | html_domain_indices = True 183 | 184 | # If false, no index is generated. 185 | html_use_index = True 186 | 187 | # If true, the index is split into individual pages for each letter. 188 | html_split_index = False 189 | 190 | # If true, links to the reST sources are added to the pages. 191 | html_show_sourcelink = False 192 | 193 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 194 | html_show_sphinx = False 195 | 196 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 197 | html_show_copyright = True 198 | 199 | # If true, an OpenSearch description file will be output, and all pages will 200 | # contain a tag referring to it. The value of this option must be the 201 | # base URL from which the finished HTML is served. 202 | # html_use_opensearch = '' 203 | 204 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 205 | # html_file_suffix = None 206 | 207 | # Output file base name for HTML help builder. 208 | htmlhelp_basename = 'libraryDoc' 209 | 210 | # -- Options for LaTeX output -------------------------------------------------- 211 | 212 | latex_elements = { 213 | # The paper size ('letterpaper' or 'a4paper'). 214 | # 'papersize': 'letterpaper', 215 | 216 | # The font size ('10pt', '11pt' or '12pt'). 217 | # 'pointsize': '10pt', 218 | 219 | # Additional stuff for the LaTeX preamble. 220 | # 'preamble': '', 221 | } 222 | 223 | # Grouping the document tree into LaTeX files. List of tuples 224 | # (source start file, target name, title, author, documentclass [howto/manual]). 225 | latex_documents = [ 226 | ('index', 'cpplot.tex', u'cpplot', 227 | u'Tom Clark', 'manual'), 228 | ] 229 | 230 | # The name of an image file (relative to this directory) to place at the top of 231 | # the title page. 232 | # latex_logo = None 233 | 234 | # For "manual" documents, if this is true, then toplevel headings are parts, 235 | # not chapters. 236 | # latex_use_parts = False 237 | 238 | # If true, show page references after internal links. 239 | # latex_show_pagerefs = False 240 | 241 | # If true, show URL addresses after external links. 242 | # latex_show_urls = False 243 | 244 | # Documents to append as an appendix to all manuals. 245 | # latex_appendices = [] 246 | 247 | # If false, no module index is generated. 248 | # latex_domain_indices = True 249 | 250 | 251 | # -- Options for manual page output -------------------------------------------- 252 | 253 | # One entry per manual page. List of tuples 254 | # (source start file, name, description, authors, manual section). 255 | man_pages = [ 256 | ('index', 'cpplot', u'cpplot', 257 | [u'Tom Clark'], 1) 258 | ] 259 | 260 | # If true, show URL addresses after external links. 261 | # man_show_urls = False 262 | 263 | 264 | # -- Options for Texinfo output ------------------------------------------------ 265 | 266 | # Grouping the document tree into Texinfo files. List of tuples 267 | # (source start file, target name, title, author, 268 | # dir menu entry, description, category) 269 | texinfo_documents = [ 270 | ('index', 'cpplot', u'cpplot', 271 | u'Tom Clark', 'cpplot', 'JSON based figures for C++', 272 | 'Miscellaneous'), 273 | ] 274 | 275 | # Documents to append as an appendix to all manuals. 276 | # texinfo_appendices = [] 277 | 278 | # If false, no module index is generated. 279 | # texinfo_domain_indices = True 280 | 281 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 282 | # texinfo_show_urls = 'footnote' 283 | -------------------------------------------------------------------------------- /docs/source/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thclark/cpplot/c6be864d61fff1ed644b99c927afb14a24a82137/docs/source/favicon.ico -------------------------------------------------------------------------------- /docs/source/images/digital_twin_component_basic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | |pre-commit| |black-girls-code| |license| 2 | 3 | .. ATTENTION:: 4 | This library is in use in several projects, but it's still early days. 5 | Like the idea of it? Please 6 | `star us on GitHub `_ and contribute via the 7 | `issues board `_ and 8 | `roadmap `_. 9 | 10 | 11 | ====== 12 | cpplot 13 | ====== 14 | 15 | .. epigraph:: 16 | *"cpplot" ~ interactive figures you can show on the web* 17 | 18 | This library allows you to create interactive graphs and charts in C++ 11 upward, which are 19 | `viewable in-browser `_. 20 | 21 | You can save figures to disc as ``\*.json`` files. Their contents are compliant with the 22 | ``plotly.js`` library schema so figures can be rendered in javascript. 23 | 24 | Get going with the :ref:`quick start `, or :ref:`read about why I started this `... 25 | 26 | Contents 27 | ======== 28 | 29 | .. toctree:: 30 | :maxdepth: 2 31 | 32 | quick_start 33 | installation 34 | about 35 | license 36 | library_api/library_root 37 | version_history 38 | 39 | 40 | .. |pre-commit| image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white 41 | :target: https://github.com/pre-commit/pre-commit 42 | .. |black-girls-code| image:: https://img.shields.io/badge/black%20girls-code-f64279.svg 43 | :target: https://www.blackgirlscode.com/ 44 | .. |license| image:: https://img.shields.io/badge/license-MIT-blue.svg 45 | :target: https://github.com/thclark/cpplot/blob/master/LICENSE -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | .. _upgrading_from_previous_versions: 8 | 9 | Upgrading From Previous Versions 10 | ================================ 11 | 12 | Please see the note on version stability and backward compatibility :ref:`here `. 13 | 14 | 15 | .. _installation_with_cmake: 16 | 17 | Installation With Cmake 18 | ====================== 19 | 20 | .. ATTENTION:: 21 | We're working on getting cpplot added as a package in conan-central. 22 | `Follow the work in progress here. `_ 23 | Until we do that, installation is via cmake. 24 | 25 | A cross-platform compilation file is provided using cmake, it's 26 | **not tested on windows** but might actually work, since it uses conan to handle 27 | all the tricky dependencies. 28 | 29 | .. code:: bash 30 | 31 | git clone https://github.com/thclark/cpplot 32 | cd cpplot && mkdir -p build && cmake . -B build 33 | 34 | This process will die first time around, and you'll get a message like: 35 | 36 | .. code:: bash 37 | 38 | -- Adding dependencies with conan. Make sure you've called `conan install . --install-folder /Users/you/cpplot/cmake-build-debug` 39 | CMake Error at CMakeLists.txt:16 (include): 40 | include could not find load file: 41 | /Users/you/cpplot/cmake-build-debug/conanbuildinfo.cmake 42 | 43 | It'll give you the conan install command to install the third party dependencies. Enter that, run it and re-run cmake. 44 | 45 | If you don't have conan installed, its pretty easy to 46 | `get started `__. 47 | 48 | 49 | .. _third_party_dependencies: 50 | 51 | Third Party Dependencies 52 | ======================== 53 | 54 | The dependencies we use are as follows: 55 | 56 | `Eigen `__ provides a linear algebra 57 | library. It isn't as consistent with MATLAB's API as armadillo (an 58 | alternative), but the API is very understandable and is used extensively 59 | in the C++ community. 60 | 61 | `glog `__ google's asynchronous 62 | logging library, used for logging to file. 63 | 64 | `googletest `__ for unit 65 | testing. 66 | 67 | `cpr `__ will probably disappear 68 | soon as it's a dependency of some :ref:`old work with plotly online `. 69 | 70 | `json `__ is a fast and popular 71 | json library for C++. Whoever nlohmann is, they rock. 72 | 73 | `boost `__ is a library of general utilities. 74 | cpplot uses `boost 75 | stacktrace `__ 76 | to provide more helpful errors and `boost filesystem `__ utilities to ease the pain 77 | of reading, writing and handling files. 78 | 79 | `tbb `__ 80 | is intel's threading library which allows us to write files out 81 | of the main thread, preventing us from blocking execution. 82 | -------------------------------------------------------------------------------- /docs/source/license.rst: -------------------------------------------------------------------------------- 1 | .. _license: 2 | 3 | ======= 4 | License 5 | ======= 6 | 7 | 8 | The boring bit 9 | ============== 10 | 11 | Your license here. 12 | 13 | 14 | Third Party Libraries 15 | ===================== 16 | 17 | **cpplot** includes or is linked against the following third party libraries: 18 | 19 | Stuff here. 20 | 21 | -------------------------------------------------------------------------------- /docs/source/quick_start.rst: -------------------------------------------------------------------------------- 1 | .. _quick_start: 2 | 3 | =========== 4 | Quick Start 5 | =========== 6 | 7 | .. _viewing_figures: 8 | 9 | Viewing Figures 10 | =============== 11 | 12 | A browser based viewer is supplied at `https://cpplot.herokuapp.com `__. 13 | Navigate to that link and you'll be able to view and export figures created with **cpplot**. 14 | 15 | The viewer always keeps your data local (files are never uploaded to a server). 16 | 17 | If you've not created figure files yourself yet, you can 18 | `download the examples from here `__ 19 | and then select them in the viewer. Some are simple, some are amazing ;) 20 | 21 | 22 | .. _create_your_first_figure: 23 | 24 | Create Your First Figure 25 | ======================== 26 | 27 | To create your first figure with **cpplot**, you can compile and run the program below: 28 | 29 | .. code-block:: cpp 30 | 31 | /* 32 | * main.cpp - An example program to create your first figure as a Plotly-compliant .json file. 33 | * 34 | * Author: YOU 35 | * 36 | * Copyright (c) YEAR YOU. All Rights Reserved. 37 | * 38 | */ 39 | #include 40 | #include 41 | #include 42 | #include "cpplot.h" // TODO these may need to change to something like for users 43 | #include "eigen.h" // who have installed the library rather than linking against source code 44 | #include "exceptions.h" // directly 45 | 46 | using namespace std; 47 | using namespace Eigen; 48 | using namespace cpplot; 49 | 50 | int main(int argc, char* argv[]) { 51 | 52 | // Create a figure object 53 | Figure fig = Figure(); 54 | 55 | // Create a scatter plot object and set its values 56 | ScatterPlot p1 = ScatterPlot(); 57 | p1.x = Eigen::VectorXd::LinSpaced(10, 0.0, 1.0); 58 | p1.y = Eigen::VectorXd::LinSpaced(10, 1.0, 2.0); 59 | 60 | // Alter its properties 61 | p1.name = "my first trace"; 62 | p.setWidth(1); 63 | p.setDash("dash"); 64 | p.setColor("#e377c2"); // "Raspberry yoghurt pink", obviously the best selection 65 | 66 | // Add it to the figure 67 | fig.add(p1); 68 | 69 | // You can add multiple plots to a figure... 70 | ScatterPlot p2 = ScatterPlot(); 71 | p2.name = "my second trace"; 72 | fig.add(p2); 73 | 74 | // And whenever you want, you can write a figure to disc. 75 | // Note, writing is an async task so your calculations can 76 | // proceed whilst writing goes into the background. 77 | // 78 | // In this example we allow the write() method to automatically append a `.json` extension 79 | // to the file name (if not already present), and we prevent it from printing the json to stdout 80 | // (which can be a useful feature to enable if you want to use your program with bash pipes) 81 | std::cout << "Writing figure to current directory: " << std::endl; 82 | fig.write("my_first_cpplot_figure", true, false); 83 | 84 | } 85 | 86 | 87 | .. _building_web_tools: 88 | 89 | Building Your Own Web Tools 90 | =========================== 91 | 92 | You (or the frontend team you work with!) will probably want to display figures in your own web tools, rather than 93 | always our (fairly basic, right now) viewer utility. 94 | 95 | The `browser viewer `__ is built in React (using ``react-plotly.js``). 96 | It is also open-source, so you can use its components as a basis for your own. See 97 | `https://github.com/thclark/cpplot-viewer `__ 98 | -------------------------------------------------------------------------------- /docs/source/version_history.rst: -------------------------------------------------------------------------------- 1 | .. _version_history: 2 | 3 | =============== 4 | Version History 5 | =============== 6 | 7 | .. _origins: 8 | 9 | Origins 10 | ======= 11 | 12 | This library was hacked together / extracted from other repositories such as `the es-flow library `__ due to the 13 | need for a plotting library enabling me to deliver results over the web. 14 | 15 | As more stars are being added on GitHub (14 at the time of writing, woot woot!) it's now worth properly versioning 16 | and maintaining the effort, since it seems like it's useful to more people than just me. 17 | 18 | .. _stability: 19 | 20 | Stability 21 | ========= 22 | 23 | The library uses semver versioning, like `version x.y.z`. 24 | 25 | Theoretically, only `x` increments should break backward compatibility. But, it's still early days as noted in 26 | `issue 10 `__ and I don't want to end up on version 200.4.5 in 27 | the next five minutes... 28 | 29 | **Let's be pragmatic:** whilst still on version `0.0.z` please consider the API as unstable 30 | version-to-version (z increments). This is because I (or we - 31 | `please help `_!) will be still architecting the library sensibly. 32 | 33 | **When I break it, I'll at least tell you what's breaking! Check back here as you update.** 34 | 35 | For `versions >= 0.x.y` expect `y` increments not to break, breaking changes might occur on `x` increments. 36 | 37 | For `versions >= x.y.z` where `x >= 1` expect `x` increments to break backward compatibility. 38 | 39 | 40 | .. _version_0.0.1: 41 | 42 | 0.0.1 43 | ===== 44 | 45 | Initial version applied to the library 46 | 47 | New Features 48 | ------------ 49 | #. Documentation in RestructuredText and hooked for public viewing on `readthedocs `__. 50 | #. Pre-commit hooks added to ensure docs always build (whatever commit you're at) and to apply consistent file formats. 51 | #. Implemented semver versioning system which will be done with GitHub releases. 52 | 53 | Backward Incompatible API Changes 54 | --------------------------------- 55 | #. n/a (Initial release) 56 | 57 | Bug Fixes & Minor Changes 58 | ------------------------- 59 | #. n/a (Initial Release) 60 | 61 | 62 | .. _version_0.0.2: 63 | 64 | 0.0.2 65 | ===== 66 | 67 | Updated build system to use conan for third party dependencies 68 | 69 | New Features 70 | ------------ 71 | #. Documentation updated with a correct installation method 72 | #. Conan package manager built into cmake for easier dependency management 73 | #. Huge amount of custom cmake code (there to find and cope with third party deps removed) 74 | 75 | Backward Incompatible API Changes 76 | --------------------------------- 77 | #. No API changes 78 | #. Build systems updated; now requires conan. 79 | 80 | Bug Fixes & Minor Changes 81 | ------------------------- 82 | #. n/a (Initial Release) 83 | 84 | 85 | 0.0.3 86 | ===== 87 | 88 | Minor fixes to build system and docs 89 | 90 | New Features 91 | ------------ 92 | #. n/a 93 | 94 | Backward Incompatible API Changes 95 | --------------------------------- 96 | #. n/a 97 | 98 | Bug Fixes & Minor Changes 99 | ------------------------- 100 | #. Fix for building against glog on windows 101 | #. Corrected build instructions in documentation 102 | 103 | 104 | 0.0.4 105 | ===== 106 | 107 | Removed unused cpr dependency from build system 108 | 109 | New Features 110 | ------------ 111 | #. n/a 112 | 113 | Backward Incompatible API Changes 114 | --------------------------------- 115 | #. n/a 116 | 117 | Bug Fixes & Minor Changes 118 | ------------------------- 119 | #. Removed unused cpr dependency from build system 120 | #. Updated docs to reflect the removed dep and to close #17 121 | #. Added a branch naming rule to the git pre-commit 122 | -------------------------------------------------------------------------------- /examples/test_bar_and_scatter_plot.json: -------------------------------------------------------------------------------- 1 | {"data":[{"type":"bar","x":["cppgiraffes","orangutans","monkeys"],"y":[2,2.5,3]},{"type":"scatter","x":[0,1,2],"y":[1,2,3]}],"layout":{},"meta":{"caption":"","id":"","name":"","short_caption":""}} 2 | -------------------------------------------------------------------------------- /examples/test_bar_plot.json: -------------------------------------------------------------------------------- 1 | {"data":[{"type":"bar","x":["cppgiraffes","orangutans","monkeys"],"y":[2,2.5,3]}],"layout":{},"meta":{"caption":"","id":"","name":"","short_caption":""}} 2 | -------------------------------------------------------------------------------- /examples/test_fit_lewkowicz_speed.json: -------------------------------------------------------------------------------- 1 | {"data":[{"name":"Correct","type":"scatter","x":[3.98562679275706,8.98531529795,10.3249687429854,11.132462597911,11.7131949308655,12.1677336610463,12.5418958372323,12.8604512203924,13.1383193323542,13.385187103899,13.6077021288232,13.8106248208217,13.9974814109073,14.1709567225646,14.3331420297241,14.4856977733145,14.6299639875838,14.767037388778,14.8978265190468,15.0230920398678,15.1434767298531,15.259528190879,15.3717162911932,15.4804467447606,15.5860718104452,15.6888988143491,15.7891970060689,15.8872031250629,15.9831259578047,16.0771500976311,16.1694390690368,16.2601379411257,16.3493755272872,16.437266247303,16.5239117122028,16.6094020799761,16.6938172207822,16.7772277229046,16.8596957648765,16.9412758745884],"y":[1,11.2307692307692,21.4615384615385,31.6923076923077,41.9230769230769,52.1538461538461,62.3846153846154,72.6153846153846,82.8461538461538,93.0769230769231,103.307692307692,113.538461538462,123.769230769231,134,144.230769230769,154.461538461538,164.692307692308,174.923076923077,185.153846153846,195.384615384615,205.615384615385,215.846153846154,226.076923076923,236.307692307692,246.538461538462,256.769230769231,267,277.230769230769,287.461538461538,297.692307692308,307.923076923077,318.153846153846,328.384615384615,338.615384615385,348.846153846154,359.076923076923,369.307692307692,379.538461538461,389.769230769231,400]},{"name":"Added noise","type":"scatter","x":[3.73563070594169,8.80108419202159,10.4527714040829,11.1117876638727,11.7295785495715,12.0272132542103,12.3154181453395,12.9498835788265,13.2279675352725,13.6025335518694,13.5494531675681,13.8203330068557,14.1629640839634,13.9382427778284,14.1098728472463,14.5005478699821,14.7155386796224,14.5208864818836,14.8395343444243,14.8065131586271,15.102219717082,15.3529145470593,15.4162046126216,15.6956649921245,15.7591552556995,15.7023632031422,15.5851794514476,15.9641626062124,15.9411256362301,16.1777453948534,16.3745994842817,16.3912369609684,16.2306020241193,16.2109985041569,16.6419526544955,16.5235191930411,16.760136504726,16.9054329658804,17.1052144576435,16.8739452099512],"y":[1,11.2307692307692,21.4615384615385,31.6923076923077,41.9230769230769,52.1538461538461,62.3846153846154,72.6153846153846,82.8461538461538,93.0769230769231,103.307692307692,113.538461538462,123.769230769231,134,144.230769230769,154.461538461538,164.692307692308,174.923076923077,185.153846153846,195.384615384615,205.615384615385,215.846153846154,226.076923076923,236.307692307692,246.538461538462,256.769230769231,267,277.230769230769,287.461538461538,297.692307692308,307.923076923077,318.153846153846,328.384615384615,338.615384615385,348.846153846154,359.076923076923,369.307692307692,379.538461538461,389.769230769231,400]},{"name":"Fitted","type":"scatter","x":[3.78582127134044,8.88023661004735,10.2452698377713,11.0680594258246,11.6597897625109,12.1229342282899,12.5041779839447,12.8287601254389,13.1118828798997,13.3634167429852,13.5901352879101,13.7968891519203,13.9872714058691,14.1640177711715,14.3292591738722,14.4846875441437,14.6316683348227,14.7713190706667,14.9045655370554,15.032182836843,15.1548259565252,15.273052902575,15.3873424750398,15.4981081041711,15.605708752321,15.7104575977509,15.8126290207924,15.9124642756783,16.0101761340391,16.1059527159886,16.1999606736123,16.2923478539365,16.3832455402802,16.4727703496435,16.56102584759,16.6481039296462,16.7340860085876,16.8190440394528,16.9030414081929,16.9861337051619],"y":[1,11.2307692307692,21.4615384615385,31.6923076923077,41.9230769230769,52.1538461538461,62.3846153846154,72.6153846153846,82.8461538461538,93.0769230769231,103.307692307692,113.538461538462,123.769230769231,134,144.230769230769,154.461538461538,164.692307692308,174.923076923077,185.153846153846,195.384615384615,205.615384615385,215.846153846154,226.076923076923,236.307692307692,246.538461538462,256.769230769231,267,277.230769230769,287.461538461538,297.692307692308,307.923076923077,318.153846153846,328.384615384615,338.615384615385,348.846153846154,359.076923076923,369.307692307692,379.538461538461,389.769230769231,400]}],"layout":{"xaxis":{"title":"u_bar (m/s)"},"yaxis":{"title":"z (m)"}},"meta":{"caption":"","id":"","name":"","short_caption":""}} 2 | -------------------------------------------------------------------------------- /examples/test_layout_no_title_plot.json: -------------------------------------------------------------------------------- 1 | {"data":[{"colorscale":"YlGnBu","type":"surface","x":[[-1.5,-0.45,0.6],[-1.5,-0.45,0.6],[-1.5,-0.45,0.6]],"y":[[-1.26,-1.26,-1.26],[0,0,0],[1.26,1.26,1.26]],"z":[[0.99,-1.0575,-0.9],[2.25,0.2025,0.36],[3.51,1.4625,1.62]]}],"layout":{"xaxis":{"title":"ecks"},"yaxis":{"title":"why"}},"meta":{"caption":"","id":"","name":"","short_caption":""}} 2 | -------------------------------------------------------------------------------- /examples/test_layout_plot.json: -------------------------------------------------------------------------------- 1 | {"data":[{"type":"scatter","x":[0,1,2],"y":[1,2,3]}],"layout":{"title":"Graph Title","xaxis":{"title":"ecks"},"yaxis":{"title":"why"}},"meta":{"caption":"","id":"","name":"","short_caption":""}} 2 | -------------------------------------------------------------------------------- /examples/test_scatter_plot.json: -------------------------------------------------------------------------------- 1 | {"data":[{"type":"scatter","x":[0,1,2],"y":[1,2,3]}],"layout":{},"meta":{"caption":"","id":"","name":"","short_caption":""}} 2 | -------------------------------------------------------------------------------- /examples/test_scatter_with_legend_plot.json: -------------------------------------------------------------------------------- 1 | {"data":[{"name":"my first trace","type":"scatter","x":[0,1,2],"y":[1,2,3]},{"name":"my second trace","type":"scatter","x":[0,1,2],"y":[1,2,3]}],"layout":{},"meta":{"caption":"","id":"","name":"","short_caption":""}} 2 | -------------------------------------------------------------------------------- /examples/test_surface_plot.json: -------------------------------------------------------------------------------- 1 | {"data":[{"colorscale":"YlGnBu","type":"surface","x":[[-1.5,-0.45,0.6],[-1.5,-0.45,0.6],[-1.5,-0.45,0.6]],"y":[[-1.26,-1.26,-1.26],[0,0,0],[1.26,1.26,1.26]],"z":[[0.99,-1.0575,-0.9],[2.25,0.2025,0.36],[3.51,1.4625,1.62]]}],"layout":{},"meta":{"caption":"","id":"","name":"","short_caption":""}} 2 | -------------------------------------------------------------------------------- /examples/test_wolfgang.json: -------------------------------------------------------------------------------- 1 | {"data":[{"name":"H","type":"scatter","x":[0,0,0,0.8,0.8,0.8],"y":[1.1,2.1,1.6,1.6,2.1,1.1]},{"name":"A","type":"scatter","x":[1.2,1.6,1.7,1.5,1.7,2],"y":[1.1,2.1,1.6,1.6,1.6,1.1]},{"name":"P","type":"scatter","x":[2.4,2.4,3.2,3.2,2.4],"y":[1.1,2.1,2.1,1.7,1.7]},{"name":"P","type":"scatter","x":[3.6,3.6,4.4,4.4,3.6],"y":[1.1,2.1,2.1,1.7,1.7]},{"name":"Y","type":"scatter","x":[4.8,4.8,5.6,5.6,5.6,5.2],"y":[2.1,1.7,1.7,2.1,1.1,1.1]},{"name":"B","type":"scatter","x":[7.2,7.2,8,7.2,8,7.2],"y":[1.1,2.1,1.76,1.6,1.43,1.1]},{"name":"I","type":"scatter","x":[8.4,9.2,8.8,8.8,8.4,9.2],"y":[2.1,2.1,2.1,1.1,1.1,1.1]},{"name":"R","type":"scatter","x":[9.6,9.6,10.4,10.4,9.6,10.4],"y":[1.1,2.1,2.1,1.7,1.7,1.1]},{"name":"T","type":"scatter","x":[11.1,11.1,10.8,11.6,11.1,11.1,11.6],"y":[2.1,1.7,1.7,1.7,1.7,1.1,1.1]},{"name":"H","type":"scatter","x":[12,12,12,12.8,12.8,12.8],"y":[1.1,2.1,1.6,1.6,2.1,1.1]},{"name":"D","type":"scatter","x":[13.2,13.2,14,14,13.2],"y":[1.1,2.1,1.9,1.3,1.1]},{"name":"A","type":"scatter","x":[14.4,14.8,14.9,14.7,14.9,15.2],"y":[1.1,2.1,1.6,1.6,1.6,1.1]},{"name":"Y","type":"scatter","x":[15.6,15.6,16.4,16.4,16.4,16],"y":[2.1,1.7,1.7,2.1,1.1,1.1]},{"name":"W","type":"scatter","x":[0,0.2,0.4,0.6,0.8],"y":[1,0,0.6,0,1]},{"name":"O","type":"scatter","x":[1.2,2,2,1.2,1.2],"y":[0,0,1,1,0]},{"name":"L","type":"scatter","x":[2.4,2.4,3.2],"y":[1,0,0]},{"name":"F","type":"scatter","x":[3.6,3.6,4.4,3.6,3.6,4.2],"y":[0,1,1,1,0.6,0.6]},{"name":"G","type":"scatter","x":[4.8,5.6,5.6,4.8,4.75,4.8,4.8,5.6],"y":[0,0,1,1,1.05,1,0.6,0.6]},{"name":"A","type":"scatter","x":[6,6.4,6.5,6.3,6.5,6.8],"y":[0,1,0.5,0.5,0.5,0]},{"name":"N","type":"scatter","x":[7.2,7.2,8,8],"y":[0,1,0,1]},{"name":"G","type":"scatter","x":[8.4,9.2,9.2,8.4,8.35,8.4,8.4,9.2],"y":[0,0,1,1,1.05,1,0.6,0.6]}],"layout":{"xaxis":{"title":"Wishes"},"yaxis":{"title":"Best"}},"meta":{"caption":"","id":"","name":"","short_caption":""}} 2 | -------------------------------------------------------------------------------- /source/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # The cpr dependency uses curl. If there's a system curl, use it (faster) 3 | find_package(CURL) 4 | if(CURL_FOUND) 5 | set(USE_SYSTEM_CURL ON CACHE BOOL "Use the system curl for faster builds") 6 | endif() 7 | 8 | # Add boost as multithreaded 9 | set(Boost_USE_MULTITHREADED TRUE) 10 | 11 | # Add the library 12 | set(SOURCE_FILES 13 | exceptions.h 14 | cpplot.h 15 | cpplot.cpp 16 | main.cpp 17 | plot_types/bar.h 18 | plot_types/scatter.h 19 | plot_types/surface.h 20 | online.h 21 | eigen.h 22 | layout.h) 23 | add_library(cpplot STATIC ${SOURCE_FILES}) 24 | message("-- Source files for library cpplot: ${SOURCE_FILES}") 25 | 26 | # Include headers from the third party libraries 27 | target_include_directories(cpplot PUBLIC ${CONAN_INCLUDE_DIRS}) 28 | 29 | # Build the executable wrapping the library and generating the example figures 30 | add_executable(cpplot-examples main.cpp) 31 | 32 | # Link against the third party libraries, and link cpplot-examples against cpplot 33 | target_link_libraries(cpplot ${CONAN_LIBS}) 34 | target_link_libraries(cpplot-examples cpplot ${CONAN_LIBS}) 35 | 36 | # Install the library 37 | message("-- Install directory is " ${CMAKE_INSTALL_PREFIX}) 38 | install (TARGETS cpplot DESTINATION lib) 39 | install (FILES cpplot.h DESTINATION include) 40 | -------------------------------------------------------------------------------- /source/cpplot.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * figures.cpp Implementation of Figure and associated classes for plotting with Plotly from C++ 3 | * 4 | * References: 5 | * 6 | * [1] 7 | * 8 | * Future Improvements: 9 | * 10 | * [1] 11 | * 12 | * Author: Tom Clark (thclark@github) 13 | * 14 | * Copyright (c) 2017-8 T Clark. All Rights Reserved. 15 | * 16 | */ 17 | 18 | 19 | #include "cpplot.h" 20 | 21 | 22 | namespace cpplot { 23 | 24 | 25 | } // end namespace 26 | -------------------------------------------------------------------------------- /source/cpplot.h: -------------------------------------------------------------------------------- 1 | /* 2 | * figures.h Allows flexible plotting in c++ using Plotly 3 | * 4 | * References: 5 | * 6 | * [1] 7 | * 8 | * Future Improvements: 9 | * 10 | * [1] 11 | * 12 | * Author: Tom Clark (thclark@github) 13 | * 14 | * Copyright (c) 2017-8 T Clark. All Rights Reserved. 15 | * 16 | */ 17 | 18 | #ifndef CPPLOT_FIGURES_H 19 | #define CPPLOT_FIGURES_H 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include "exceptions.h" 35 | #include "eigen.h" 36 | #include "layout.h" 37 | #include "plot_types/bar.h" 38 | #include "plot_types/scatter.h" 39 | #include "plot_types/surface.h" 40 | 41 | 42 | namespace cpplot { 43 | 44 | // Enumerate the different plot types that are possible with plotly 45 | enum PlotType { bar, scatter }; 46 | 47 | // Base class for managing the figure creation process 48 | class Figure { 49 | 50 | protected: 51 | 52 | nlohmann::json data; 53 | nlohmann::json layout; 54 | nlohmann::json meta; 55 | 56 | public: 57 | 58 | Figure(){ 59 | data = nlohmann::json::array(); 60 | layout = nlohmann::json::object(); 61 | meta = nlohmann::json::object(); 62 | } 63 | 64 | /// Optional metadata - Figure ID 65 | std::string id = ""; 66 | 67 | /// Optional metadata - Figure name used for display (e.g. in cpplot-viewer) 68 | std::string name = ""; 69 | 70 | /// Optional metadata - Short_caption used in report contents tables 71 | std::string short_caption = ""; 72 | 73 | /// Optional metadata - Long caption used beside the figure (e.g. in a report) 74 | std::string caption = ""; 75 | 76 | /** @brief Add plot data to the figure 77 | * 78 | * @tparam T type of the plot data to add 79 | * @param plot 80 | */ 81 | template 82 | void add(T &plot) { 83 | // Data is a json array of plot data 84 | data.push_back(plot); 85 | } 86 | 87 | /** @brief Add layout data to the figure 88 | * 89 | * 90 | */ 91 | void setLayout(Layout &lay) { 92 | layout.update(lay); 93 | } 94 | 95 | /** @brief Write the figure to a file 96 | * 97 | * **CAUTION:** Overwrites any existing file contents. 98 | * 99 | * @param[in] file_name The file name to write to, including any absolute or relative path 100 | * @param[in] append_extension If true, a .json extension will be appended to file_name (if not already present) 101 | * @param[in] print_to_stdout Default false. If true, the json will also be printed to std::cout (useful for debugging) 102 | */ 103 | void write(std::string file_name, bool append_extension = true, bool print_to_stdout = false){ 104 | 105 | // Compile metadata into a json object. NB any other fields already added to meta will be kept, allowing addition of arbitrary metadata to figures. 106 | meta["id"] = id; 107 | meta["name"] = name; 108 | meta["caption"] = caption; 109 | meta["short_caption"] = short_caption; 110 | 111 | // Convert the figure to JSON: copy each value into the JSON object 112 | nlohmann::json j; 113 | j["data"] = data; 114 | j["layout"] = layout; 115 | j["meta"] = meta; 116 | 117 | // Get the file name 118 | if (!boost::algorithm::ends_with(file_name, ".json") && append_extension) { 119 | file_name.append(".json"); 120 | } 121 | 122 | // Open the file and stream the string into it, overwriting any existing contents 123 | std::ofstream file; 124 | file.open(file_name); 125 | file << j; 126 | file.close(); 127 | 128 | // Also write to stdout 129 | if (print_to_stdout) { 130 | std::cout << "Figure json:" << std::endl << j << std::endl; 131 | } 132 | 133 | } 134 | // TODO Represent Figure class in ostream 135 | /* 136 | ::std::ostream& operator<<(::std::ostream& os, const Figure& fig) { 137 | // Represent in logs or ostream 138 | return os << "debug statement for figure class"; 139 | } 140 | */ 141 | 142 | }; 143 | 144 | // TODO Represent Figure class in ostream 145 | /* 146 | ::std::ostream& operator<<(::std::ostream& os, const Figure& fig) { 147 | // Represent in logs or ostream 148 | return os << "debug statement for figure class"; 149 | } 150 | */ 151 | 152 | 153 | } // end namespace 154 | 155 | #endif // CPPLOT_FIGURES_H 156 | -------------------------------------------------------------------------------- /source/eigen.h: -------------------------------------------------------------------------------- 1 | /* 2 | * eigen.h Serializers for dense matrices/arrays/vectors from Eigen. 3 | * 4 | * References: 5 | * 6 | * [1] 7 | * 8 | * Future Improvements: 9 | * 10 | * [1] 11 | * 12 | * 13 | * Author: Tom Clark (thclark@github) 14 | * 15 | * Copyright (c) 2017-8 T Clark. All Rights Reserved. 16 | * 17 | */ 18 | 19 | #ifndef CPPLOT_EIGEN_H 20 | #define CPPLOT_EIGEN_H 21 | 22 | #include 23 | 24 | 25 | namespace cpplot { 26 | 27 | 28 | /** @brief A serialiser to convert dense eigen arrays to json objects using nlohmann::json 29 | * 30 | * Turns eigen array into a valid json array. 31 | * 1D arrays become lists: 32 | * [24.23, 3.4, 8.4] 33 | * 2D arrays become lists of lists: 34 | * [[24.23, 3.4, 8.4],[45.23, 5.4, 9.4]] 35 | * 36 | * TODO manage tensors for 3d volumetric plots 37 | * 38 | * Output is written as a row major array. So (assuming eigen's default settings for orientation), doing: 39 | * 40 | * Eigen::ArrayXXd arr(5,2); 41 | * arr << 0.0, 0.1, 42 | * 1.0, 1.1, 43 | * 2.0, 2.1, 44 | * 3.0, 3.1, 45 | * 4.0, 4.1; 46 | * nlohmann::json j; 47 | * to_json(j, arr); 48 | * std::cout << "array element 0,1: " << arr(0,1) << std::endl; 49 | * std::cout << "array element 1,0: " << arr(1,0) << std::endl; 50 | * std::cout << j << std::endl; 51 | * 52 | * Prints: 53 | * 54 | * array 0,1: 0.1 55 | * array 1,0: 1 56 | * [[0,0.1],[1,1.1],[2,2.1],[3,3.1],[4,4.1]] 57 | * 58 | * @tparam Derived The derived array or matrix type 59 | * @param j An nlohmann::json array 60 | * @param[in] in The matrix or array to serialise into json array 61 | */ 62 | template 63 | void to_json(nlohmann::json& j, const Eigen::DenseBase& in) { 64 | 65 | // Use the eigen matrix formatting code to stream the matrix to a string. 66 | // This'll likely be faster than anything I can do here. 67 | Eigen::IOFormat vector_format(Eigen::FullPrecision, Eigen::DontAlignCols, ", ", ", ", "[", "]"); 68 | Eigen::IOFormat matrix_format(Eigen::FullPrecision, Eigen::DontAlignCols, ", ", ", ", "[", "]", "[", "]"); 69 | 70 | // Stream the eigen matrix, vector or array 71 | std::stringstream value_stream; 72 | if (in.cols() == 1 && in.rows() > 0) { 73 | value_stream << in.transpose().format(vector_format); 74 | } else if (in.rows() == 1) { 75 | value_stream << in.format(vector_format); 76 | } else { 77 | value_stream << in.format(matrix_format); 78 | } 79 | 80 | // Explicitly add the string as the json object 81 | // TODO - reparsing this string object is pretty inefficient. Maybe best to write my own after all 82 | j = nlohmann::json::parse(value_stream.str()); 83 | 84 | } 85 | 86 | 87 | } // end namespace 88 | 89 | #endif //CPPLOT_EIGEN_H 90 | -------------------------------------------------------------------------------- /source/exceptions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * exceptions.h Customised exceptions for appropriate and fine grained error handling 3 | * 4 | * Author: Tom Clark (thclark@github) 5 | * 6 | * Copyright (c) 2017-8 T Clark. All Rights Reserved. 7 | * 8 | */ 9 | 10 | #ifndef CPPLOT_EXCEPTIONS_H 11 | #define CPPLOT_EXCEPTIONS_H 12 | 13 | #include 14 | 15 | 16 | struct NotImplementedException : public std::exception { 17 | std::string message = "Not yet implemented"; 18 | const char * what () const throw () { 19 | return message.c_str(); 20 | } 21 | }; 22 | 23 | 24 | struct InvalidAxisException : public std::exception { 25 | std::string message = "Axis invalid or not present"; 26 | const char * what () const throw () { 27 | return message.c_str(); 28 | } 29 | 30 | }; 31 | 32 | 33 | struct InvalidOrMissingPlotlyCredentialsException : public std::exception { 34 | std::string message = "Invalid or missing plotly credentials. Try setting the environment variables PLOTLY_USERNAME and PLOTLY_PASSWORD."; 35 | const char * what () const throw () { 36 | return message.c_str(); 37 | } 38 | }; 39 | 40 | 41 | struct ErrorInPlotlyOnlineException : public std::exception { 42 | std::string message = ""; 43 | const char * what () const throw () { 44 | return message.c_str(); 45 | } 46 | ErrorInPlotlyOnlineException() { 47 | message = "Error in plotly online"; 48 | } 49 | ErrorInPlotlyOnlineException(const std::string msg) { 50 | message = msg; 51 | } 52 | }; 53 | 54 | #endif // CPPLOT_EXCEPTIONS_H 55 | -------------------------------------------------------------------------------- /source/layout.h: -------------------------------------------------------------------------------- 1 | /* 2 | * layout.h 3 | * 4 | * Author: Tom Clark (thclark @ github) 5 | * 6 | * Copyright (c) 2019 Octue Ltd. All Rights Reserved. 7 | * 8 | */ 9 | 10 | #ifndef CPPLOT_LAYOUT_H 11 | #define CPPLOT_LAYOUT_H 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | using nlohmann::json; 23 | 24 | 25 | namespace cpplot { 26 | 27 | 28 | /** @brief Enum of the three possible axis directions, X, Y, Z. 29 | */ 30 | enum AxisDirection { X, Y, Z}; 31 | 32 | /** @brief Axis class for use in layout of plots. 33 | * 34 | * Serialises to something like: 35 | * @code 36 | * "xaxis": {"title": "x1"} 37 | * @endcode 38 | */ 39 | class Axis { 40 | public: 41 | // Allow the serialiser function to access protected members 42 | friend void to_json(nlohmann::json& j, const Axis& p); 43 | 44 | explicit Axis(const AxisDirection dir) : direction(dir){ 45 | switch(dir){ 46 | case X: 47 | key = "xaxis"; 48 | break; 49 | case Y: 50 | key = "yaxis"; 51 | break; 52 | case Z: 53 | key = "zaxis"; 54 | break; 55 | } 56 | }; 57 | 58 | /** @brief Sets the direction of the axis, as X, Y, or Z 59 | * 60 | * @param dir Axis direction (an enum X, Y or Z) 61 | */ 62 | void setDirection(AxisDirection dir) { 63 | direction = dir; 64 | }; 65 | 66 | void setTitle(std::string label) { 67 | title = label; 68 | }; 69 | 70 | void setLog() { 71 | is_log = true; 72 | }; 73 | 74 | AxisDirection getDirection() { 75 | return direction; 76 | }; 77 | 78 | std::string getTitle() { 79 | return title; 80 | }; 81 | 82 | bool isLog() { 83 | return is_log; 84 | }; 85 | 86 | protected: 87 | AxisDirection direction; 88 | std::string title; 89 | std::string key; 90 | bool is_log = false; 91 | }; 92 | 93 | 94 | /** @brief Serialise axis into a valid json string. 95 | * 96 | * Produces json like: 97 | * @code 98 | * "xaxis": {"title": "x1"} 99 | * @endcode 100 | */ 101 | void to_json(nlohmann::json& j, const Axis& p) { 102 | nlohmann::json axis; 103 | axis["title"] = p.title; 104 | if (p.is_log) { 105 | axis["type"] = "log"; 106 | }; 107 | j[p.key] = axis; 108 | } 109 | 110 | 111 | class Layout { 112 | public: 113 | 114 | // Allow the serialiser function to access protected members 115 | friend void to_json(nlohmann::json& j, const Layout& p); 116 | 117 | /** @brief Construct with default basic data (for test plots) 118 | * 119 | * Example use: 120 | * @code 121 | * Layout my_layout = Layout("a graph title"); // Default constructor Layout() also works 122 | * my_layout.xLabel("ecks"); 123 | * my_layout.yLabel("why"; 124 | * @endcode 125 | * 126 | * 127 | */ 128 | explicit Layout(const std::string title="", const bool is_scene=false) : title(title), is_scene(is_scene) {}; 129 | 130 | /** @brief get an axis in the layout by its direction. Optionally, create if not found. 131 | * 132 | * Example use: 133 | * @code 134 | * Layout my_layout = Layout("a graph title"); // Default constructor Layout() also works 135 | * axis = my_layout.getAxis(X); // raises error 136 | * axis = my_layout.getAxis(X, true); // creates the axis and adds it to the layout 137 | * @endcode 138 | * 139 | * @param dir 140 | * @return 141 | */ 142 | Axis* getAxis(const AxisDirection &dir, bool create=false) { 143 | for (auto &axis: axes) { 144 | if (axis.getDirection() == dir) { 145 | return &axis; 146 | }; 147 | }; 148 | if (create) { 149 | Axis ax(dir); 150 | axes.push_back(ax); 151 | // If a Z axis is created, turn the plot into a 3D scene 152 | if (dir == Z) { 153 | is_scene = true; 154 | } 155 | return &axes.back(); 156 | } else { 157 | InvalidAxisException e; 158 | throw (e); 159 | }; 160 | } 161 | 162 | /** @brief set the title of the x axis 163 | * 164 | * @param[in] label the title of the axis. Can use latex within dollar signs, like "Normalised distance \$\eta\$" 165 | */ 166 | void xTitle(const std::string label) { 167 | AxisDirection dir = X; 168 | getAxis(dir, true)->setTitle(label); 169 | } 170 | 171 | /** @brief set the title of the y axis 172 | * 173 | * @param[in] label the title of the axis. Can use latex within dollar signs, like "Normalised distance \$\eta\$" 174 | */ 175 | void yTitle(const std::string label) { 176 | AxisDirection dir = Y; 177 | getAxis(dir, true)->setTitle(label); 178 | } 179 | 180 | /** @brief set the title of the z axis 181 | * 182 | * @param[in] label the title of the axis. Can use latex within dollar signs, like "Normalised distance \$\eta\$" 183 | */ 184 | void zTitle(const std::string label) { 185 | AxisDirection dir = Z; 186 | getAxis(dir, true)->setTitle(label); 187 | } 188 | 189 | /** @brief change the type of the x axis from its default, 'linear', to 'log' 190 | */ 191 | void xLog() { 192 | AxisDirection dir = X; 193 | getAxis(dir, true)->setLog(); 194 | } 195 | 196 | /** @brief change the type of the y axis from its default, 'linear', to 'log' 197 | */ 198 | void yLog() { 199 | AxisDirection dir = Y; 200 | getAxis(dir, true)->setLog(); 201 | } 202 | 203 | /** @brief change the type of the z axis from its default, 'linear', to 'log' 204 | */ 205 | void zLog() { 206 | AxisDirection dir = Z; 207 | getAxis(dir, true)->setLog(); 208 | } 209 | 210 | protected: 211 | 212 | std::vector axes; 213 | std::string title; 214 | bool is_scene; 215 | 216 | }; 217 | 218 | 219 | /** @brief Serialise layout into a valid json string. 220 | * 221 | */ 222 | void to_json(nlohmann::json& j, const Layout& p) { 223 | 224 | if (!p.title.empty()) { 225 | j["title"] = p.title; 226 | }; 227 | nlohmann::json axes; 228 | for (auto &axis : p.axes) { 229 | nlohmann::json ax; 230 | to_json(ax, axis); 231 | axes.update(ax); 232 | }; 233 | if (p.is_scene) { 234 | j["scene"] = axes; 235 | } else { 236 | j.update(axes); 237 | }; 238 | }; 239 | 240 | 241 | }; // end namespace cpplot 242 | 243 | #endif // CPPLOT_LAYOUT_H 244 | -------------------------------------------------------------------------------- /source/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * main.cpp - An example program to create and save various types of figures as Plotly-compliant .json files. 3 | * 4 | * Author: Tom Clark (thclark@github) 5 | * 6 | * Copyright (c) 2017-9 T Clark. All Rights Reserved. 7 | * 8 | */ 9 | #include 10 | #include "glog/logging.h" 11 | 12 | 13 | using namespace std; 14 | using namespace google; 15 | 16 | 17 | int main(int argc, char* argv[]) { 18 | 19 | google::InitGoogleLogging(argv[0]); 20 | 21 | /* 22 | values << 0.01, 0.02, 0.05, 0.08, 0.01, 23 | 0.02, 0.05, 0.46, 0.25, 0.75, 24 | 0.587, 0.5, 0.12, 0.99, 0.01, 25 | 0.01, 0.02, 0.05, 0.08, 0.01, 26 | 0.02, 0.05, 0.46, 0.25, 0.75, 27 | 0.587, 0.5, 0.12, 0.99, 0.01; 28 | */ 29 | 30 | } 31 | -------------------------------------------------------------------------------- /source/online.h: -------------------------------------------------------------------------------- 1 | /* 2 | * online.h TODO - this is a collection of snippets from first working with online plotly API. 3 | * They could be used to form the basis of a plotly client in C++ 4 | * 5 | * References: 6 | * 7 | * [1] 8 | * 9 | * Future Improvements: 10 | * 11 | * [1] 12 | * 13 | * 14 | * Author: Tom Clark (thclark@github) 15 | * 16 | * Copyright (c) 2017-8 T Clark. All Rights Reserved. 17 | * 18 | */ 19 | 20 | #ifndef CPPLOT_ONLINE_H 21 | #define CPPLOT_ONLINE_H 22 | 23 | // Encapsulate plotly config variables. Obtain credentials from the environment upon instantiation. 24 | /* Leaving as a comment - focussing on offline first. 25 | struct Config { 26 | 27 | std::string url = "https://api.plot.ly/v2/"; 28 | std::string user = ""; 29 | std::string password = ""; 30 | 31 | Config() { 32 | // Get configuration from environment variables. PLOTLY_URL is optional, allowing you to run your own server 33 | char *c_url = getenv("PLOTLY_URL"); 34 | char *c_user = getenv("PLOTLY_USERNAME"); 35 | char *c_password = getenv("PLOTLY_API_KEY"); 36 | if ((c_user == NULL) || (c_password == NULL)) { 37 | InvalidOrMissingPlotlyCredentialsException e; 38 | throw (e); 39 | } 40 | if (c_url != NULL) { 41 | url = std::string(c_url); 42 | } 43 | user = std::string(c_user); 44 | password = std::string(c_password); 45 | if (url.back() == '/') { 46 | url.pop_back(); 47 | } 48 | } 49 | }; 50 | 51 | // Base class for managing the figure creation process 52 | template 53 | class Figure { 54 | 55 | private: 56 | 57 | ContentsType contents; 58 | 59 | Config configuration; 60 | 61 | void setPlotlyIdFromUrl(std::string url); 62 | 63 | protected: 64 | 65 | // Plotly figure id 66 | std::string plotly_id; 67 | 68 | // Plotly username 69 | std::string plotly_usr; 70 | 71 | // Refers to the python plotly library used to generate the figure 72 | // TODO figure out how to get the version of plotly used to render the figure through their API; set it here 73 | std::string plotly_version = "unknown"; 74 | 75 | public: 76 | 77 | Figure(ContentsType contents); 78 | 79 | // Gets the plotly URL string 80 | std::string plotlyUrl(); 81 | 82 | // Gets the plotly embed URL string 83 | std::string embedUrl(); 84 | 85 | // Not implemented, as we don't use C++ to serve web content directly 86 | // html(self, **kwargs): 87 | 88 | // Gets the plotly PDF URL string (an expiring link to the pdf file containing the figure) 89 | std::string pdfUrl(); 90 | 91 | // Update the figure json from the plotly server 92 | // Not implemented - one-way so far 93 | // void pull(); 94 | 95 | // Plot the figure by POSTing its data to the plotly server 96 | void plot(); 97 | 98 | }; 99 | 100 | 101 | template 102 | Figure::Figure(ContentsType contents) { 103 | 104 | contents = contents; 105 | 106 | // Get plotly server access configuration and credentials from the environment 107 | configuration = Config(); 108 | 109 | } 110 | 111 | template 112 | std::string Figure::plotlyUrl() { 113 | // Returns the URL to this figure on plotly, if the figure is rendered 114 | if (plotly_usr == "") { 115 | return ""; 116 | } 117 | return configuration.url + "/~" + plotly_usr + "/" + plotly_id; 118 | } 119 | 120 | template 121 | void Figure::setPlotlyIdFromUrl(std::string url) { 122 | 123 | // Remove any trailing / in case the url is given as, e.g. https://plot.ly/~usr_name/123/ 124 | if (url.back() == '/') { 125 | url.pop_back(); 126 | } 127 | 128 | // Split the URL into its components. Use ~ as a splitter as well as / to avoid extra op of removing it from user 129 | std::vector url_parts; 130 | boost::split(url_parts, url, boost::is_any_of("/~")); 131 | 132 | // If the url is given as, e.g. plot.ly/~usr_name/123.embed, remove the .* from the trailing figure id 133 | std::vector trailing_id_parts; 134 | boost::split(trailing_id_parts, url_parts.back(), boost::is_any_of(".")); 135 | 136 | // Assign the Figure properties from the url 137 | plotly_id = trailing_id_parts[0]; 138 | plotly_usr = url_parts[url_parts.size() - 2]; 139 | 140 | } 141 | 142 | template 143 | std::string Figure::embedUrl() { 144 | return plotlyUrl() + ".embed"; 145 | } 146 | 147 | template 148 | std::string Figure::pdfUrl() { 149 | return plotlyUrl() + ".pdf"; 150 | } 151 | 152 | template 153 | void Figure::plot() { 154 | // Plot the figure by POSTing its data to the plotly server 155 | 156 | // Get the json data for the figure contents 157 | std::string body_str = contents.toJson(); 158 | 159 | // POST to configuration.url, with configured credentials 160 | auto r = cpr::Post( cpr::Url{configuration.url}, 161 | cpr::Body{body_str}, 162 | cpr::Authentication{configuration.user, configuration.password}, 163 | cpr::Header{{"Accept", "application/json"}, {"Plotly-Client-Platform", "cpp 0.0.1"}}); 164 | 165 | // Remove any trailing / in case the url is given as, e.g. https://plot.ly/~usr_name/123/ 166 | if (r.status_code != 200) { 167 | std::stringstream msg; 168 | msg << "Error received from plotly..." < 19 | //#include "bar.h" 20 | //#include "figures.h" 21 | // 22 | // 23 | //using nlohmann::json; 24 | 25 | 26 | namespace cpplot { 27 | 28 | 29 | } // end namespace 30 | -------------------------------------------------------------------------------- /source/plot_types/bar.h: -------------------------------------------------------------------------------- 1 | /* 2 | * bar.h Bar chart data class 3 | * 4 | * References: 5 | * 6 | * [1] 7 | * 8 | * Future Improvements: 9 | * 10 | * [1] 11 | * 12 | * 13 | * Author: Tom Clark (thclark@github) 14 | * 15 | * Copyright (c) 2017-8 T Clark. All Rights Reserved. 16 | * 17 | */ 18 | 19 | #ifndef CPPLOT_BAR_H 20 | #define CPPLOT_BAR_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | 31 | using nlohmann::json; 32 | 33 | 34 | namespace cpplot { 35 | 36 | 37 | class BarPlot { 38 | public: 39 | 40 | std::vector x; 41 | Eigen::VectorXd y; 42 | std::string type = "bar"; 43 | std::string name = ""; 44 | 45 | /** @brief Construct with default basic data (for test plots) 46 | */ 47 | BarPlot() { 48 | x = {"cppgiraffes", "orangutans", "monkeys"}; 49 | y = Eigen::VectorXd::LinSpaced(3, 2.0, 3.0); 50 | } 51 | 52 | }; 53 | 54 | 55 | /** @brief Serialise bar plot data into a valid json string. 56 | * 57 | * Produces figure data JSON similar to: 58 | * {"x": ["giraffes", "orangutans", "monkeys"], 59 | * "y": [20.1, 14.4, 23.3], 60 | * "type": "bar"} 61 | */ 62 | void to_json(nlohmann::json& j, const BarPlot& p) { 63 | 64 | nlohmann::json y; 65 | to_json(y, p.y); 66 | j["x"] = p.x; 67 | j["y"] = y; 68 | j["type"] = p.type; 69 | if (!p.name.empty()) { 70 | j["name"] = p.name; 71 | } 72 | 73 | } 74 | 75 | 76 | } // end namespace 77 | 78 | #endif //CPPLOT_BAR_H 79 | -------------------------------------------------------------------------------- /source/plot_types/scatter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * scatter.cpp Implementation of ScatterPlot methods and operators 3 | * 4 | * References: 5 | * 6 | * [1] 7 | * 8 | * Future Improvements: 9 | * 10 | * [1] 11 | * 12 | * Author: Tom Clark (thclark@github) 13 | * 14 | * Copyright (c) 2017-8 T Clark. All Rights Reserved. 15 | * 16 | */ 17 | 18 | //#include 19 | //#include "scatter.h" 20 | //#include "figures.h" 21 | // 22 | // 23 | //using nlohmann::json; 24 | 25 | 26 | namespace cpplot { 27 | 28 | } // end namespace 29 | -------------------------------------------------------------------------------- /source/plot_types/scatter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * scatter.h ScatterPlot data class 3 | * 4 | * Author: Tom Clark (thclark@github) 5 | * 6 | * Copyright (c) 2017-9 T Clark. All Rights Reserved. 7 | * 8 | */ 9 | 10 | #ifndef CPPLOT_SCATTER_H 11 | #define CPPLOT_SCATTER_H 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "eigen.h" 19 | #include "exceptions.h" 20 | 21 | 22 | namespace cpplot { 23 | 24 | 25 | /** @brief Class for managing and serialising properties of a line or lines. 26 | */ 27 | class Line { 28 | public: 29 | // Allow the serialiser function to access protected members 30 | friend void to_json(nlohmann::json& j, const Line& p); 31 | 32 | /** @brief Constructor initialises a default Line type. 33 | */ 34 | Line() { 35 | is_empty = true; 36 | width = -1; // This is "empty" i.e. not set. 37 | } 38 | 39 | /** @brief set solid line colour as a string. 40 | * 41 | * TODO Implement colour scaled values 42 | * 43 | * See https://plot.ly/ipython-notebooks/color-scales/#6-colors for an example of different colour scales and values available 44 | * 45 | * For reference, plotly's default colours are: 46 | * [ 47 | * '#1f77b4', # muted blue 48 | * '#ff7f0e', # safety orange 49 | * '#2ca02c', # cooked asparagus green 50 | * '#d62728', # brick red 51 | * '#9467bd', # muted purple 52 | * '#8c564b', # chestnut brown 53 | * '#e377c2', # raspberry yogurt pink 54 | * '#7f7f7f', # middle gray 55 | * '#bcbd22', # curry yellow-green 56 | * '#17becf' # blue-teal 57 | * ] 58 | * 59 | * @param value std::string plotly colour value, e.g. setColor("#1f77b4") 60 | */ 61 | void setColor(const std::string &value) { 62 | color = value; 63 | is_empty = false; 64 | } 65 | 66 | /** @brief set the line dash mode. 67 | * 68 | * Accepts "solid", "dot", "dash", "longdash", "dashdot", or "longdashdot" as dash_type input 69 | * 70 | * @param[in] value std::string The dash type to use 71 | */ 72 | void setDash(const std::string &value) { 73 | // TODO check the string is valid, or enumerate. 74 | dash = value; 75 | is_empty = false; 76 | } 77 | 78 | /** @brief set the line width in pixels. 79 | * 80 | * @param[in] value int the width in pixels for the line 81 | */ 82 | void setWidth(const int value) { 83 | width = value; 84 | is_empty = false; 85 | } 86 | 87 | /** @brief return boolean, false if any line properties are changed from default. 88 | */ 89 | bool empty() const { 90 | return is_empty; 91 | } 92 | 93 | protected: 94 | std::string dash; 95 | int width; 96 | std::string color; 97 | bool is_empty; 98 | 99 | }; 100 | 101 | 102 | /** @brief Serialise line data into a valid json string. 103 | */ 104 | void to_json(nlohmann::json& j, const Line& p) { 105 | if (!p.dash.empty()) { 106 | j["dash"] = p.dash; 107 | }; 108 | if (p.width != -1) { 109 | j["width"] = p.width; 110 | }; 111 | if (!p.color.empty()) { 112 | j["color"] = p.color; 113 | } 114 | } 115 | 116 | 117 | class ScatterPlot { 118 | public: 119 | // Allow the serialiser function to access protected members 120 | friend void to_json(nlohmann::json& j, const ScatterPlot& p); 121 | 122 | // TODO move to getter/setters 123 | Eigen::VectorXd x; 124 | Eigen::VectorXd y; 125 | std::string name = ""; 126 | 127 | /** @brief Construct with default basic data (for test plots). 128 | */ 129 | ScatterPlot() { 130 | x = Eigen::VectorXd::LinSpaced(3, 0.0, 2.0); 131 | y = Eigen::VectorXd::LinSpaced(3, 1.0, 3.0); 132 | line = Line(); 133 | } 134 | 135 | /** @brief set solid line colour as a string. 136 | * 137 | * See https://plot.ly/ipython-notebooks/color-scales/#6-colors for an example of different colour scales and values available 138 | * See https://github.com/plotly/plotly.py/blob/master/plotly/colors.py#L83-L87 for plotly's master colour reference 139 | * For reference, plotly's default colours are: 140 | * [ 141 | * '#1f77b4', # muted blue 142 | * '#ff7f0e', # safety orange 143 | * '#2ca02c', # cooked asparagus green 144 | * '#d62728', # brick red 145 | * '#9467bd', # muted purple 146 | * '#8c564b', # chestnut brown 147 | * '#e377c2', # raspberry yogurt pink 148 | * '#7f7f7f', # middle gray 149 | * '#bcbd22', # curry yellow-green 150 | * '#17becf' # blue-teal 151 | * ] 152 | * 153 | * @param value std::string plotly colour value, e.g. setColor("#1f77b4") 154 | */ 155 | void setColor(const std::string &value) { 156 | line.setColor(value); 157 | } 158 | 159 | /** @brief set the line dash mode for this scatter plot. 160 | * 161 | * This is a helper function, which directly modifies a 'line' object, allowing a clean API for the user to update 162 | * scatter plots. 163 | * 164 | * Accepts "solid", "dot", "dash", "longdash", "dashdot", or "longdashdot" 165 | * 166 | * @param[in] dash_type std::string selected dash type 167 | */ 168 | void setDash(const std::string &value) { 169 | line.setDash(value); 170 | } 171 | 172 | /** @brief set the line width in pixels. 173 | * 174 | * @param[in] value int the width in pixels for the line 175 | */ 176 | void setWidth(const int value) { 177 | line.setWidth(value); 178 | } 179 | 180 | protected: 181 | Line line; 182 | 183 | }; 184 | 185 | 186 | /** @brief Serialise scatter plot data into a valid json string. 187 | * 188 | * Produces figure data JSON similar to: 189 | * {"x": [24.23, 3.4, 8.4], 190 | * "y": [20.1, 14.4, 23.3], 191 | * "type": "scatter"} 192 | */ 193 | void to_json(nlohmann::json& j, const ScatterPlot& p) { 194 | 195 | nlohmann::json x; 196 | nlohmann::json y; 197 | to_json(x, p.x); 198 | to_json(y, p.y); 199 | j["x"] = x; 200 | j["y"] = y; 201 | j["type"] = "scatter"; 202 | if (!p.name.empty()) { 203 | j["name"] = p.name; 204 | } 205 | if (!p.line.empty()) { 206 | nlohmann::json line; 207 | to_json(line, p.line); 208 | j["line"] = line; 209 | } 210 | }; 211 | 212 | 213 | } // end namespace 214 | 215 | #endif //CPPLOT_SCATTER_H 216 | -------------------------------------------------------------------------------- /source/plot_types/surface.h: -------------------------------------------------------------------------------- 1 | /* 2 | * scatter.h ScatterPlot data class 3 | * 4 | * Author: Tom Clark (thclark@github) 5 | * 6 | * Copyright (c) 2017-9 T Clark. All Rights Reserved. 7 | * 8 | */ 9 | 10 | #ifndef CPPLOT_SURFACE_H 11 | #define CPPLOT_SURFACE_H 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "eigen.h" 19 | #include "exceptions.h" 20 | 21 | 22 | namespace cpplot { 23 | 24 | /** @brief Default color scales available in plotly 25 | * TODO refactor these to separate file and allow fo custom colourmaps 26 | */ 27 | enum ColorScale { 28 | Blackbody, 29 | Bluered, 30 | Blues, 31 | Earth, 32 | Electric, 33 | Greens, 34 | Greys, 35 | Hot, 36 | Jet, 37 | Picnic, 38 | Portland, 39 | Rainbow, 40 | RdBu, 41 | Reds, 42 | Viridis, 43 | YlGnBu, 44 | YYlOrRd 45 | }; 46 | const std::vector colour_names = { 47 | "Blackbody", 48 | "Bluered", 49 | "Blues", 50 | "Earth", 51 | "Electric", 52 | "Greens", 53 | "Greys", 54 | "Hot", 55 | "Jet", 56 | "Picnic", 57 | "Portland", 58 | "Rainbow", 59 | "RdBu", 60 | "Reds", 61 | "Viridis", 62 | "YlGnBu", 63 | "YlOrRd" 64 | }; 65 | 66 | /** @brief Serialise color scale data into a valid json field or fields 67 | * 68 | * Produces figure data JSON similar to: 69 | * 70 | */ 71 | void to_json(nlohmann::json& j, const ColorScale& c) { 72 | j["colorscale"] = colour_names[c]; 73 | } 74 | 75 | 76 | class SurfacePlot { 77 | 78 | public: 79 | 80 | ColorScale colorscale = YlGnBu; 81 | Eigen::ArrayXXd x; 82 | Eigen::ArrayXXd y; 83 | Eigen::ArrayXXd z; 84 | std::string type = "surface"; 85 | std::string name = ""; 86 | 87 | /** @brief Construct with default basic data (for test plots) 88 | */ 89 | SurfacePlot() { 90 | x = Eigen::RowVectorXd::LinSpaced(3, -1.5, 0.6).replicate(3,1).array(); 91 | y = Eigen::VectorXd::LinSpaced(3, -1.26, 1.26).replicate(1,3).array(); 92 | z = x.pow(2) + y; 93 | }; 94 | 95 | }; 96 | 97 | 98 | /** @brief Serialise surface plot data into a valid json string. 99 | * 100 | * Produces figure data JSON similar to: 101 | * 102 | */ 103 | void to_json(nlohmann::json& j, const SurfacePlot& p) { 104 | 105 | nlohmann::json x; 106 | nlohmann::json y; 107 | nlohmann::json z; 108 | to_json(x, p.x); 109 | to_json(y, p.y); 110 | to_json(z, p.z); 111 | j["x"] = x; 112 | j["y"] = y; 113 | j["z"] = z; 114 | to_json(j, p.colorscale); 115 | j["type"] = p.type; 116 | if (!p.name.empty()) { 117 | j["name"] = p.name; 118 | } 119 | } 120 | 121 | 122 | } // end namespace 123 | 124 | #endif //CPPLOT_SURFACE_H 125 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # Add boost for stacktrace and filesystem dependency 3 | # TODO Resolve whether boost (now installed with conan) will actually work as multithreaded like this 4 | set(Boost_USE_MULTITHREADED TRUE) 5 | #find_package(Boost REQUIRED COMPONENTS filesystem system) 6 | #include_directories(${Boost_INCLUDE_DIR}) 7 | 8 | # List the unit test files 9 | set(UNIT_TEST_SOURCE_FILES 10 | unit/cpplot_test.cpp 11 | unit/main_test.cpp) 12 | 13 | # Include google test 14 | enable_testing() 15 | 16 | # The unit test executable 17 | add_executable(cpplot_test ${UNIT_TEST_SOURCE_FILES}) 18 | 19 | # Link the google test and boost libraries 20 | target_link_libraries(cpplot_test ${CONAN_LIBS}) 21 | 22 | # Link the library to test 23 | target_link_libraries(cpplot_test cpplot) 24 | -------------------------------------------------------------------------------- /test/unit/cpplot_test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * figures_test.cpp - Unit testing for the cpplot library 3 | * 4 | * References: 5 | * 6 | * [1] 7 | * 8 | * Future Improvements: 9 | * 10 | * [1] 11 | * 12 | * Author: Tom Clark (thclark@github) 13 | * 14 | * Copyright (c) 2017-8 T Clark. All Rights Reserved. 15 | * 16 | */ 17 | 18 | #define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "cpplot.h" 28 | #include "eigen.h" 29 | #include "exceptions.h" 30 | #include "gtest/gtest.h" 31 | #include "glog/logging.h" 32 | 33 | 34 | using namespace Eigen; 35 | using namespace google; 36 | using namespace cpplot; 37 | 38 | 39 | // TODO inherit from a base test class 40 | class FigureTest : public ::testing::Test { 41 | 42 | protected: 43 | 44 | /* Returns environment variable TEST_DATA_DIR, which contains a system-dependent path to a directory where test 45 | * fixture data may be stored, and which may be used to write output test files. If no environment variable is set, 46 | * defaults to the current working directory in which the test framework is being run. 47 | * A trailing slash is always appended. 48 | */ 49 | std::string TestDataDir() { 50 | 51 | const char *tmp = std::getenv("TEST_DATA_DIR"); 52 | std::string test_data_dir; 53 | if (tmp) { 54 | std::string s(tmp); 55 | test_data_dir = s; 56 | } else { 57 | boost::filesystem::path cwd(boost::filesystem::current_path()); 58 | test_data_dir = cwd.string(); 59 | } 60 | if (!boost::algorithm::ends_with(test_data_dir, "/")) { 61 | test_data_dir.append("/"); 62 | } 63 | 64 | return test_data_dir; 65 | } 66 | 67 | // Define the matrix output format for this set of tests 68 | IOFormat test_format; 69 | 70 | virtual void SetUp() { 71 | // Code here will be called immediately after the constructor (right before each test) 72 | 73 | IOFormat debug_format(FullPrecision, 0, ", ", ";\n", "", "", "[", "]"); 74 | test_format = debug_format; 75 | std::cout << "Test Data Output Dir is: " << TestDataDir() << std::endl; 76 | 77 | } 78 | 79 | virtual void TearDown() { 80 | // Code here will be called immediately after each test (right before the destructor) 81 | } 82 | 83 | }; 84 | 85 | 86 | TEST_F(FigureTest, test_bar_plot) { 87 | 88 | BarPlot p = BarPlot(); 89 | Figure fig = Figure(); 90 | fig.add(p); 91 | std::cout << "Writing to TestDataDir(): " << TestDataDir() << std::endl; 92 | fig.write(TestDataDir().append("test_bar_plot.json")); 93 | 94 | } 95 | 96 | 97 | TEST_F(FigureTest, test_scatter_plot) { 98 | 99 | ScatterPlot p = ScatterPlot(); 100 | Figure fig = Figure(); 101 | fig.add(p); 102 | fig.write(TestDataDir().append("test_scatter_plot.json"), true, true); 103 | 104 | } 105 | 106 | 107 | TEST_F(FigureTest, test_wide_coloured_dashed_scatter_plot) { 108 | 109 | ScatterPlot p = ScatterPlot(); 110 | p.setWidth(1); 111 | p.setDash("dash"); 112 | p.setColor("#e377c2"); 113 | Figure fig = Figure(); 114 | fig.add(p); 115 | fig.write(TestDataDir().append("test_wide_coloured_dashed_scatter_plot"), true, true); 116 | 117 | } 118 | 119 | 120 | TEST_F(FigureTest, test_bar_and_scatter_plot) { 121 | 122 | Figure fig = Figure(); 123 | BarPlot p1 = BarPlot(); 124 | fig.add(p1); 125 | ScatterPlot p2 = ScatterPlot(); 126 | fig.add(p2); 127 | std::cout << "Writing to TestDataDir(): " << TestDataDir() << std::endl; 128 | fig.write(TestDataDir().append("test_bar_and_scatter_plot.json"), true, true); 129 | 130 | } 131 | 132 | TEST_F(FigureTest, test_scatter_with_legend_plot) { 133 | 134 | Figure fig = Figure(); 135 | ScatterPlot p1 = ScatterPlot(); 136 | p1.name = "my first trace"; 137 | fig.add(p1); 138 | ScatterPlot p2 = ScatterPlot(); 139 | p2.name = "my second trace"; 140 | fig.add(p2); 141 | std::cout << "Writing to TestDataDir(): " << TestDataDir() << std::endl; 142 | fig.write(TestDataDir().append("test_scatter_with_legend_plot.json"), true, true); 143 | 144 | } 145 | 146 | 147 | TEST_F(FigureTest, test_surface_plot) { 148 | 149 | Figure fig = Figure(); 150 | SurfacePlot p = SurfacePlot(); 151 | fig.add(p); 152 | fig.write(TestDataDir().append("test_surface_plot.json"), true, true); 153 | 154 | } 155 | 156 | 157 | TEST_F(FigureTest, test_mandelbrot_plot) { 158 | 159 | Figure fig = Figure(); 160 | SurfacePlot p = SurfacePlot(); 161 | 162 | int height = 600; 163 | int width = 600; 164 | int max_iterations = 16; 165 | 166 | p.x = Eigen::RowVectorXd::LinSpaced(width, -1.5, 0.6).replicate(height, 1).array(); 167 | p.y = Eigen::VectorXd::LinSpaced(height, -1.26, 1.26).replicate(1, width).array(); 168 | p.z = Eigen::ArrayXXd(width, height); 169 | 170 | // Loop to produce the fractal set 171 | for (int i = 0; i < width; i++) { 172 | for (int j = 0; j < height; j++) { 173 | double a = p.x(i,j); 174 | double b = p.y(i,j); 175 | double xn = 0.0; 176 | double yn = 0.0; 177 | int k = 1; 178 | while ((k <= max_iterations) && ((pow(xn, 2.0) - pow(yn, 2.0)) < 4.0)) { 179 | double xnew = pow(xn, 2.0) - pow(yn, 2.0) + a; 180 | double ynew = 2.0 * xn * yn + b; 181 | xn = xnew; 182 | yn = ynew; 183 | k = k + 1; 184 | }; 185 | p.z(i,j) = k; 186 | }; 187 | }; 188 | 189 | fig.add(p); 190 | fig.write(TestDataDir().append("test_mandelbrot_plot.json"), true, true); 191 | 192 | } 193 | 194 | 195 | TEST_F(FigureTest, test_eigen_serialiser) { 196 | 197 | nlohmann::json j; 198 | 199 | Eigen::ArrayXd arr1(5); 200 | arr1 << 0.001, 0.1, 0.3, 0.6, 1.0; 201 | nlohmann::json j1; 202 | to_json(j1, arr1); 203 | j["array1"] = j1; 204 | 205 | // NB Eigen uses column major indexing, but you have to init arrays in a row major way. 206 | Eigen::ArrayXXd arr2(5,2); 207 | arr2 << 0.0, 0.1, 208 | 1.0, 1.1, 209 | 2.0, 2.1, 210 | 3.0, 3.1, 211 | 4.0, 4.1; 212 | nlohmann::json j2; 213 | to_json(j2, arr2); 214 | j["array2"] = j2; 215 | std::cout << "array 0,1: " << arr2(0,1) << std::endl; 216 | std::cout << "array 1,0: " << arr2(1,0) << std::endl; 217 | 218 | std::cout << j.dump() << std::endl; 219 | EXPECT_EQ(j.dump(), "{\"array1\":[0.001,0.1,0.3,0.6,1],\"array2\":[[0,0.1],[1,1.1],[2,2.1],[3,3.1],[4,4.1]]}"); 220 | 221 | } 222 | 223 | 224 | TEST_F(FigureTest, test_layout) { 225 | 226 | Figure fig = Figure(); 227 | ScatterPlot p = ScatterPlot(); 228 | fig.add(p); 229 | Layout lay = Layout("Graph Title"); 230 | lay.xTitle("ecks"); 231 | lay.yTitle("why"); 232 | fig.setLayout(lay); 233 | fig.write(TestDataDir().append("test_layout_plot.json"), true, true); 234 | 235 | } 236 | 237 | 238 | TEST_F(FigureTest, test_3d_axes_labels) { 239 | 240 | Figure fig = Figure(); 241 | SurfacePlot p = SurfacePlot(); 242 | fig.add(p); 243 | Layout lay = Layout("Graph Title"); 244 | lay.xTitle("ecks"); 245 | lay.yTitle("why"); 246 | lay.zTitle("zedd"); 247 | fig.setLayout(lay); 248 | fig.write(TestDataDir().append("test_3d_axes_labels.json"), true, true); 249 | 250 | } 251 | 252 | 253 | TEST_F(FigureTest, test_layout_no_title) { 254 | 255 | Figure fig = Figure(); 256 | ScatterPlot p = ScatterPlot(); 257 | fig.add(p); 258 | Layout lay = Layout(); 259 | lay.xTitle("ecks"); 260 | lay.yTitle("why"); 261 | fig.setLayout(lay); 262 | fig.write(TestDataDir().append("test_layout_no_title_plot.json"), true, true); 263 | 264 | } 265 | 266 | 267 | TEST_F(FigureTest, test_latex_labels) { 268 | 269 | Figure fig = Figure(); 270 | ScatterPlot p = ScatterPlot(); 271 | p.name = "$\\eta_{12}$"; 272 | fig.add(p); 273 | Layout lay = Layout(); 274 | lay.xTitle("$x + y$"); 275 | lay.yTitle("$d, r \\\\text{(solar radius)}$"); 276 | fig.setLayout(lay); 277 | fig.write(TestDataDir().append("test_latex_labels.json"), true, true); 278 | 279 | } 280 | 281 | 282 | TEST_F(FigureTest, test_log_axes) { 283 | 284 | Figure fig = Figure(); 285 | ScatterPlot p = ScatterPlot(); 286 | p.x = Eigen::RowVectorXd::LinSpaced(100, 1, 100000); 287 | p.y = 2.0 * p.x; 288 | p.name = "relationship"; 289 | fig.add(p); 290 | Layout lay = Layout(); 291 | lay.xTitle("x"); 292 | lay.yTitle("y"); 293 | lay.xLog(); 294 | lay.yLog(); 295 | fig.setLayout(lay); 296 | fig.write(TestDataDir().append("test_log_axes.json"), true, true); 297 | 298 | } 299 | 300 | 301 | TEST_F(FigureTest, test_wolfgang) { 302 | 303 | double dx = 1.2; 304 | double dy = 1.1; 305 | 306 | 307 | Figure fig = Figure(); 308 | ScatterPlot h = ScatterPlot(); 309 | h.x = Eigen::ArrayXd(6); 310 | h.x << 0, 0, 0, 0.8, 0.8, 0.8; 311 | h.y = Eigen::ArrayXd(6); 312 | h.y << 0, 1, 0.5, 0.5, 1, 0; 313 | h.y = h.y.array() + dy; 314 | h.name = "H"; 315 | fig.add(h); 316 | 317 | ScatterPlot a = ScatterPlot(); 318 | a.x = Eigen::ArrayXd(6); 319 | a.x << 0, 0.4, 0.5, 0.3, 0.5, 0.8; 320 | a.x = a.x.array() + dx; 321 | a.y = Eigen::ArrayXd(6); 322 | a.y << 0, 1, 0.5, 0.5, 0.5, 0; 323 | a.y = a.y.array() + dy; 324 | a.name = "A"; 325 | fig.add(a); 326 | 327 | ScatterPlot p = ScatterPlot(); 328 | p.x = Eigen::ArrayXd(5); 329 | p.x << 0, 0, 0.8, 0.8, 0; 330 | p.x = p.x.array() + 2*dx; 331 | p.y = Eigen::ArrayXd(5); 332 | p.y << 0, 1, 1, 0.6, 0.6; 333 | p.y = p.y.array() + dy; 334 | p.name = "P"; 335 | fig.add(p); 336 | 337 | ScatterPlot p2 = ScatterPlot(); 338 | p2.x = Eigen::ArrayXd(5); 339 | p2.x << 0, 0, 0.8, 0.8, 0; 340 | p2.x = p2.x.array() + 3*dx; 341 | p2.y = Eigen::ArrayXd(5); 342 | p2.y << 0, 1, 1, 0.6, 0.6; 343 | p2.y = p2.y.array() + dy; 344 | p2.name = "P"; 345 | fig.add(p2); 346 | 347 | ScatterPlot y = ScatterPlot(); 348 | y.x = Eigen::ArrayXd(6); 349 | y.x << 0, 0, 0.8, 0.8, 0.8, 0.4; 350 | y.x = y.x.array() + 4*dx; 351 | y.y = Eigen::ArrayXd(6); 352 | y.y << 1, 0.6, 0.6, 1, 0, 0; 353 | y.y = y.y.array() + dy; 354 | y.name = "Y"; 355 | fig.add(y); 356 | 357 | ScatterPlot b = ScatterPlot(); 358 | b.x = Eigen::ArrayXd(6); 359 | b.x << 0, 0, 0.8, 0, 0.8, 0; 360 | b.x = b.x.array() + 6*dx; 361 | b.y = Eigen::ArrayXd(6); 362 | b.y << 0, 1, 0.66, 0.5, 0.33, 0; 363 | b.y = b.y.array() + dy; 364 | b.name = "B"; 365 | fig.add(b); 366 | 367 | ScatterPlot i = ScatterPlot(); 368 | i.x = Eigen::ArrayXd(6); 369 | i.x << 0, 0.8, 0.4, 0.4, 0, 0.8; 370 | i.x = i.x.array() + 7*dx; 371 | i.y = Eigen::ArrayXd(6); 372 | i.y << 1, 1, 1, 0, 0, 0; 373 | i.y = i.y.array() + dy; 374 | i.name = "I"; 375 | fig.add(i); 376 | 377 | ScatterPlot r = ScatterPlot(); 378 | r.x = Eigen::ArrayXd(6); 379 | r.x << 0, 0, 0.8, 0.8, 0, 0.8; 380 | r.x = r.x.array() + 8*dx; 381 | r.y = Eigen::ArrayXd(6); 382 | r.y << 0, 1, 1, 0.6, 0.6, 0; 383 | r.y = r.y.array() + dy; 384 | r.name = "R"; 385 | fig.add(r); 386 | 387 | ScatterPlot t = ScatterPlot(); 388 | t.x = Eigen::ArrayXd(7); 389 | t.x << 0.3, 0.3, 0, 0.8, 0.3, 0.3, 0.8; 390 | t.x = t.x.array() + 9*dx; 391 | t.y = Eigen::ArrayXd(7); 392 | t.y << 1, 0.6, 0.6, 0.6, 0.6, 0, 0; 393 | t.y = t.y.array() + dy; 394 | t.name = "T"; 395 | fig.add(t); 396 | 397 | ScatterPlot h2 = ScatterPlot(); 398 | h2.x = Eigen::ArrayXd(6); 399 | h2.x << 0, 0, 0, 0.8, 0.8, 0.8; 400 | h2.x = h2.x.array() + 10*dx; 401 | h2.y = Eigen::ArrayXd(6); 402 | h2.y << 0, 1, 0.5, 0.5, 1, 0; 403 | h2.y = h2.y.array() + dy; 404 | h2.name = "H"; 405 | fig.add(h2); 406 | 407 | ScatterPlot d = ScatterPlot(); 408 | d.x = Eigen::ArrayXd(5); 409 | d.x << 0, 0, 0.8, 0.8, 0; 410 | d.x = d.x.array() + 11*dx; 411 | d.y = Eigen::ArrayXd(5); 412 | d.y << 0, 1, 0.8, 0.2, 0; 413 | d.y = d.y.array() + dy; 414 | d.name = "D"; 415 | fig.add(d); 416 | 417 | ScatterPlot a2 = ScatterPlot(); 418 | a2.x = Eigen::ArrayXd(6); 419 | a2.x << 0, 0.4, 0.5, 0.3, 0.5, 0.8; 420 | a2.x = a2.x.array() + 12*dx; 421 | a2.y = Eigen::ArrayXd(6); 422 | a2.y << 0, 1, 0.5, 0.5, 0.5, 0; 423 | a2.y = a2.y.array() + dy; 424 | a2.name = "A"; 425 | fig.add(a2); 426 | 427 | ScatterPlot y2 = ScatterPlot(); 428 | y2.x = Eigen::ArrayXd(6); 429 | y2.x << 0, 0, 0.8, 0.8, 0.8, 0.4; 430 | y2.x = y2.x.array() + 13*dx; 431 | y2.y = Eigen::ArrayXd(6); 432 | y2.y << 1, 0.6, 0.6, 1, 0, 0; 433 | y2.y = y2.y.array() + dy; 434 | y2.name = "Y"; 435 | fig.add(y2); 436 | 437 | ScatterPlot w = ScatterPlot(); 438 | w.x = Eigen::ArrayXd(5); 439 | w.x << 0, 0.2, 0.4, 0.6, 0.8; 440 | w.x = w.x.array() + 0*dx; 441 | w.y = Eigen::ArrayXd(5); 442 | w.y << 1, 0, 0.6, 0, 1; 443 | w.name = "W"; 444 | fig.add(w); 445 | 446 | ScatterPlot o = ScatterPlot(); 447 | o.x = Eigen::ArrayXd(5); 448 | o.x << 0, 0.8, 0.8, 0, 0; 449 | o.x = o.x.array() + 1*dx; 450 | o.y = Eigen::ArrayXd(5); 451 | o.y << 0, 0, 1, 1, 0; 452 | o.name = "O"; 453 | fig.add(o); 454 | 455 | ScatterPlot l = ScatterPlot(); 456 | l.x = Eigen::ArrayXd(3); 457 | l.x << 0, 0, 0.8; 458 | l.x = l.x.array() + 2*dx; 459 | l.y = Eigen::ArrayXd(3); 460 | l.y << 1, 0, 0; 461 | l.name = "L"; 462 | fig.add(l); 463 | 464 | ScatterPlot f = ScatterPlot(); 465 | f.x = Eigen::ArrayXd(6); 466 | f.x << 0, 0, 0.8, 0, 0, 0.6; 467 | f.x = f.x.array() + 3*dx; 468 | f.y = Eigen::ArrayXd(6); 469 | f.y << 0, 1, 1, 1, 0.6, 0.6; 470 | f.name = "F"; 471 | fig.add(f); 472 | 473 | ScatterPlot g = ScatterPlot(); 474 | g.x = Eigen::ArrayXd(8); 475 | g.x << 0, 0.8, 0.8, 0, -0.05, 0, 0, 0.8; 476 | g.x = g.x.array() + 4*dx; 477 | g.y = Eigen::ArrayXd(8); 478 | g.y << 0, 0, 1, 1, 1.05, 1, 0.6, 0.6; 479 | g.name = "G"; 480 | fig.add(g); 481 | 482 | ScatterPlot a3 = ScatterPlot(); 483 | a3.x = Eigen::ArrayXd(6); 484 | a3.x << 0, 0.4, 0.5, 0.3, 0.5, 0.8; 485 | a3.x = a3.x.array() + 5*dx; 486 | a3.y = Eigen::ArrayXd(6); 487 | a3.y << 0, 1, 0.5, 0.5, 0.5, 0; 488 | a3.name = "A"; 489 | fig.add(a3); 490 | ScatterPlot n = ScatterPlot(); 491 | n.x = Eigen::ArrayXd(4); 492 | n.x << 0, 0, 0.8, 0.8; 493 | n.x = n.x.array() + 6*dx; 494 | n.y = Eigen::ArrayXd(4); 495 | n.y << 0, 1, 0, 1; 496 | n.name = "N"; 497 | fig.add(n); 498 | 499 | ScatterPlot g2 = ScatterPlot(); 500 | g2.x = Eigen::ArrayXd(8); 501 | g2.x << 0, 0.8, 0.8, 0, -0.05, 0, 0, 0.8; 502 | g2.x = g2.x.array() + 7*dx; 503 | g2.y = Eigen::ArrayXd(8); 504 | g2.y << 0, 0, 1, 1, 1.05, 1, 0.6, 0.6; 505 | g2.name = "G"; 506 | fig.add(g2); 507 | 508 | Layout lay = Layout(); 509 | lay.xTitle("Wishes"); 510 | lay.yTitle("Best"); 511 | fig.setLayout(lay); 512 | fig.write(TestDataDir().append("test_wolfgang.json"), true, true); 513 | 514 | } 515 | -------------------------------------------------------------------------------- /test/unit/main_test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * main_test.cpp - The main unit test runner, with a test case printer utility which gives you a stacktrace output. 3 | * 4 | * References: 5 | * 6 | * [1] 7 | * 8 | * Future Improvements: 9 | * 10 | * [1] 11 | * 12 | * Author: Tom Clark (thclark@github) 13 | * 14 | * Copyright (c) 2017-8 T Clark. All Rights Reserved. 15 | * 16 | */ 17 | 18 | #define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "gtest/gtest.h" 27 | #include "glog/logging.h" 28 | 29 | 30 | using namespace Eigen; 31 | using namespace google; 32 | 33 | 34 | class TestCasePrinter : public ::testing::EmptyTestEventListener { 35 | 36 | virtual void OnTestStart(const ::testing::TestInfo& test_info) { 37 | printf("_____________________________________________________________________________________\n"); 38 | printf("Setting up test %s.%s\n\n", test_info.test_case_name(), test_info.name()); 39 | } 40 | 41 | const char* PrintStackTrace(void) { 42 | printf("Failed with stack trace:\n\n"); 43 | // TODO implement stack trace printer 44 | // See: 45 | // https://github.com/google/googletest/issues/1140 46 | // http://boostorg.github.io/stacktrace/stacktrace/getting_started.html#stacktrace.getting_started.handle_terminates_aborts_and_seg 47 | std::cout << boost::stacktrace::stacktrace(); 48 | return "Failed"; 49 | } 50 | 51 | // Called after a failed assertion or a SUCCEED() invocation 52 | virtual void OnTestPartResult(const ::testing::TestPartResult& test_part_result) { 53 | printf("%s in %s:%d\n", 54 | test_part_result.failed() ? PrintStackTrace() : "Success", 55 | test_part_result.file_name(), 56 | test_part_result.line_number()); 57 | } 58 | 59 | virtual void OnTestEnd(const ::testing::TestInfo& test_info) { 60 | printf("Torn down test %s.%s\n", test_info.test_case_name(), test_info.name()); 61 | printf("_____________________________________________________________________________________\n\n\n"); 62 | } 63 | 64 | }; 65 | 66 | 67 | int main(int argc, char **argv) { 68 | 69 | ::testing::InitGoogleTest(&argc, argv); 70 | 71 | // Some code being tested may support logging, so we need to initialise the google logger here where we have the 72 | // executable name in argv[0] 73 | google::InitGoogleLogging(argv[0]); 74 | 75 | // Get hold of the event listener list 76 | ::testing::TestEventListeners& listeners = ::testing::UnitTest::GetInstance()->listeners(); 77 | 78 | // Removing the default result printer prevents CLion from picking up the unit tests, so the custom printer is 79 | // added, rather than simply replacing the default 80 | // delete listeners.Release(listeners.default_result_printer()); 81 | 82 | // Adds a listener for printing outputs (including e.g. stack traces) 83 | listeners.Append(new TestCasePrinter); 84 | 85 | int a = RUN_ALL_TESTS(); 86 | 87 | return a; 88 | 89 | } 90 | --------------------------------------------------------------------------------