diff --git a/MANIFEST.in b/MANIFEST.in index c652f2e..fa69952 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,5 @@ include README.md include LICENSE -include examples/example.py \ No newline at end of file +include examples/example.py +include plottools/cs_blank.svg +include plottools/cm/cm_blank.svg \ No newline at end of file diff --git a/README.rst b/README.rst index 59979cd..ec305fb 100644 --- a/README.rst +++ b/README.rst @@ -2,3 +2,15 @@ plottools ========= A package with usefull tools for plotting with matplotlib + + +Installation +------------ +.. code-block:: + + pip install plottools + + +Documentation +------------- +Some documentation is available on `pythonhosted `_. diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..4375c93 --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,2 @@ +build +source/generated \ No newline at end of file diff --git a/doc/source/cm.rst b/doc/source/cm.rst new file mode 100644 index 0000000..5ce94c9 --- /dev/null +++ b/doc/source/cm.rst @@ -0,0 +1,7 @@ +cm +== +.. toctree:: + :maxdepth: 3 + +Some additional color maps + \ No newline at end of file diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..87f66b1 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,368 @@ +# -*- coding: utf-8 -*- +# +# Someproject documentation build configuration file, created by +# sphinx-quickstart on Mon Sep 05 08:56:23 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. + +import sys +import os + +sys.path.insert(0, os.path.abspath('../..')) +sys.path.append(os.path.abspath('sphinxext')) + +# import the version string +from plottools.__version__ import version as __version__ + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.mathjax', + 'sphinx.ext.viewcode', + 'sphinx.ext.autosummary', + 'matplotlib.sphinxext.plot_directive', + 'numpydoc', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +# +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'plottools' +copyright = u'2016, Brecht Baeten' +author = u'Brecht Baeten' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = unicode(__version__,'utf-8') +# The full version, including alpha/beta/rc tags. +release = unicode(__version__,'utf-8') + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# +# today = '' +# +# Else, today_fmt is used as the format for a strftime call. +# +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. +# " v documentation" by default. +# +# html_title = u'Someproject v3.1.2' + +# A shorter title for the navigation bar. Default is the same as html_title. +# +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# +# html_logo = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +# html_last_updated_fmt = None + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# +html_sidebars = { + '**': [ + 'about.html', + 'navigation.html', + 'relations.html', + 'searchbox.html', + 'donate.html' + ] +} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# +# html_additional_pages = {} + +# If false, no module index is generated. +# +# html_domain_indices = True + +# If false, no index is generated. +# +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' +# +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Someprojectdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'plottools.tex', u'plottools Documentation', + u'Brecht Baeten', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# +# latex_use_parts = False + +# If true, show page references after internal links. +# +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# It false, will not define \strong, \code, itleref, \crossref ... but only +# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added +# packages. +# +# latex_keep_old_macro_names = True + +# If false, no module index is generated. +# +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'plottools', u'plottools Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'plottools', u'plottools Documentation', + author, 'plottools', 'some descr', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False + +autosummary_generate = True + +# -- Options for plot directive ------------------------------------------- +plot_include_source = True \ No newline at end of file diff --git a/doc/source/cs.rst b/doc/source/cs.rst new file mode 100644 index 0000000..987f784 --- /dev/null +++ b/doc/source/cs.rst @@ -0,0 +1,32 @@ +cs +== +.. toctree:: + :maxdepth: 3 + +colorscheme +----------- + +.. currentmodule:: plottools.cs.colorscheme + +.. autosummary:: + :nosignatures: + :toctree: generated + + Colorscheme + ColorschemeTool + + +util +---- + +.. currentmodule:: plottools.cs.util + +.. autosummary:: + :nosignatures: + :toctree: generated + + plot_colors + to_greyscale + change_lightness_to_match_greyscale + prepare_print_scan + analyse_print_scan diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..a8218f8 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,29 @@ +.. plottools documentation master file, created by python-git-package. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +plottools +========= + +:Release: |version| +:Date: |today| + +A package with usefull tools for plotting with matplotlib + + +.. toctree:: + :maxdepth: 3 + + installation + quickstart + reference + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/doc/source/installation.rst b/doc/source/installation.rst new file mode 100644 index 0000000..555299b --- /dev/null +++ b/doc/source/installation.rst @@ -0,0 +1,10 @@ +Installation +============ +.. toctree:: + :maxdepth: 3 + +Install using pip + +.. code-block:: bash + + pip install plottools \ No newline at end of file diff --git a/doc/source/plottools.rst b/doc/source/plottools.rst new file mode 100644 index 0000000..adaf18c --- /dev/null +++ b/doc/source/plottools.rst @@ -0,0 +1,17 @@ +plottools +========= +.. toctree:: + :maxdepth: 3 + +.. currentmodule:: plottools + + +.. autosummary:: + :nosignatures: + :toctree: generated + + categorized_xticklabels + zoom_axes + set_publication_rc + + \ No newline at end of file diff --git a/doc/source/quickstart.rst b/doc/source/quickstart.rst new file mode 100644 index 0000000..7084a9a --- /dev/null +++ b/doc/source/quickstart.rst @@ -0,0 +1,106 @@ +Quickstart +========== +.. toctree:: + :maxdepth: 3 + +Tools +----- + + +Colorscheme +----------- +Plottools offers a colorscheme class which can be used to define a set of colors +and an order of appearance. The colorscheme object can then be used to cycle +through colors using a convinient ``next`` method or by indexing using a short +or longname. + +When a colorscheme is defined in the beginning of a script and referenced in the +subsequent plot commands, it allows easy changes of colorscheme if required. + +.. plot:: + + import numpy as np + import matplotlib.pyplot as plt + import plottools as pt + + colorscheme = pt.color + + plt.figure() + x = np.arange(6) + labels = ['label {}'.format(i+1) for i in range(len(colorscheme.keys()))] + + bars = [] + y_tot = np.zeros_like(x) + for l in labels: + y = np.random.random_integers(1,high=10,size=x.shape) + b = plt.bar(x, y, 0.8, bottom=y_tot , color=colorscheme.next()) + bars.append(b[0]) + y_tot += y + + plt.legend(bars,labels) + plt.show() + + +Plottools comes with a default colorscheme ``plottools.color`` which +includes 6 colors and a shade of grey which looks better than the default colors +and have good contrast when printed in greyscale. + +.. plot:: + + import matplotlib.pyplot as plt + import numpy as np + + import plottools as pt + from plottools.color.util import plot_colors, to_greyscale + + fig = plt.figure() + ax1 = fig.add_subplot(211) + plot_colors(ax1,pt.color.colors,['k','p','b','r','g','o','y']) + + ax2 = fig.add_subplot(212) + plot_colors(ax2,{key: to_greyscale(val) for key,val in pt.color.items()} ,['k','p','b','r','g','o','y']) + plt.show() + + +A colorscheme can also be used as the default color cycler by calling the method +``set_as_default()``. + +.. plot:: + + import numpy as np + import matplotlib.pyplot as plt + import plottools as pt + + plt.figure() + x = np.linspace(0,2*np.pi,100) + + pt.color.set_as_default() + + for i,key in enumerate( pt.color.keys() ): + plt.plot(x,np.sin(x-i*2*2*np.pi/14)) + + plt.show() + +When a colorscheme is defined, a lighter and darker variation are automatically +created. These can be accessed as a color scheme object by the ``light`` and +``dark`` attributes respectively. + +.. plot:: + + import numpy as np + import matplotlib.pyplot as plt + import plottools as pt + + plt.figure() + x = np.linspace(0,2*np.pi,100) + for i,key in enumerate( pt.color.default.keys() ): + plt.plot(x,np.sin(x-i*2*2*np.pi/14),color=pt.color[key],label=key) + plt.plot(x,np.sin(x-i*2*2*np.pi/14)-0.1,color=pt.color.light[key]) + plt.plot(x,np.sin(x-i*2*2*np.pi/14)+0.1,color=pt.color.dark[key]) + + plt.legend() + plt.show() + + +Styles +------ \ No newline at end of file diff --git a/doc/source/reference.rst b/doc/source/reference.rst new file mode 100644 index 0000000..b047faf --- /dev/null +++ b/doc/source/reference.rst @@ -0,0 +1,9 @@ +Reference +========= +.. toctree:: + :maxdepth: 3 + + plottools + style + cs + cm \ No newline at end of file diff --git a/doc/source/style.rst b/doc/source/style.rst new file mode 100644 index 0000000..6640bb9 --- /dev/null +++ b/doc/source/style.rst @@ -0,0 +1,14 @@ +style +===== +.. toctree:: + :maxdepth: 3 + +.. currentmodule:: plottools.style + +.. autosummary:: + :nosignatures: + :toctree: generated + + horizontalgrid + noxticks + set \ No newline at end of file diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..f08278d --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1 @@ +*.pdf \ No newline at end of file diff --git a/examples/categorized_xticklabels.py b/examples/categorized_xticklabels.py new file mode 100644 index 0000000..481310c --- /dev/null +++ b/examples/categorized_xticklabels.py @@ -0,0 +1,38 @@ +import matplotlib.pyplot as plt +import numpy as np +import plottools as pt + + +pt.set_publication_rc() + + +# generate data +C,B,A = np.meshgrid([10,20],[0.4,0.6,0.8],[1,2,3],indexing='ij') +xticklabels = [ A.reshape((-1,)), B.reshape((-1,)), C.reshape((-1,)) ] +values = [np.random.random(len(t)) for t in xticklabels] +xticks = np.arange(len(values[0])) + +xticklabelnames = ['coord','$B_\mathrm{value}$','CCC'] +labels = ['set1','set2','set3'] +fmt = ['${:.2f}$','${}$ m','$10^{{{:.0f}}}$'] +rotation = [70,0,0] + + +# create the figure +plt.figure() +bottom = np.zeros_like(values[0]) +for val,lab in zip(values,labels): + plt.bar(xticks+0.05,val,0.9,bottom=bottom,label=lab,color=pt.color.next()) + bottom += val + +plt.legend(framealpha=0.7,loc='upper right') +plt.ylabel('y-label') + +# set the figure style +pt.style.set(['horizontalgrid','noxticks']) + +# add categories on the x-axis +pt.categorized_xticklabels(xticks+0.5,xticklabels,xticklabelnames=xticklabelnames,fmt=fmt,rotation=rotation) +plt.tight_layout() +plt.savefig('categorized_xticklabels.pdf') +plt.show() \ No newline at end of file diff --git a/examples/cm2svg.py b/examples/cm2svg.py deleted file mode 100644 index 674599d..0000000 --- a/examples/cm2svg.py +++ /dev/null @@ -1,11 +0,0 @@ -import matplotlib.pyplot as plt -import numpy as np -import plottools as pt - - -# viridis -pt.cm.cm2svg(plt.cm.viridis,'viridis.svg') - -# hot water -pt.cm.cm2svg(pt.cm.hotwater,'hotwater.svg',stops=5) - diff --git a/examples/cm_to_svg.py b/examples/cm_to_svg.py new file mode 100644 index 0000000..479b2e4 --- /dev/null +++ b/examples/cm_to_svg.py @@ -0,0 +1,11 @@ +import matplotlib.pyplot as plt +import numpy as np +import plottools as pt + + +# viridis +pt.cm.cm_to_svg(plt.cm.viridis,'viridis.svg') + +# hot water +pt.cm.cm_to_svg(pt.cm.hotwater,'hotwater.svg',stops=5) + diff --git a/examples/color.py b/examples/color.py index 91f8bbf..b8296db 100644 --- a/examples/color.py +++ b/examples/color.py @@ -12,39 +12,37 @@ n = len(pt.color.cycle) for i in range(n): - plt.plot(x,np.sin(x-i*2*np.pi/n),label=pt.color.cycle[i],linewidth=2) - + plt.plot(x,np.sin(x-i*2*np.pi/n),label=pt.color.cycle[i],linewidth=2) + plt.legend() -# some lines +# lines light and dark plt.figure() x = np.linspace(0,2*np.pi,100) -plt.plot(x,np.sin(x- 0*2*np.pi/12),color=pt.color['l'],label='l') -plt.plot(x,np.sin(x- 2*2*np.pi/12),color=pt.color['b'],label='b') -plt.plot(x,np.sin(x- 4*2*np.pi/12),color=pt.color['r'],label='r') -plt.plot(x,np.sin(x- 6*2*np.pi/12),color=pt.color['y'],label='y') -plt.plot(x,np.sin(x- 8*2*np.pi/12),color=pt.color['o'],label='o') -plt.plot(x,np.sin(x-10*2*np.pi/12),color=pt.color['g'],label='g') +for i,key in enumerate( pt.color.keys() ): + plt.plot(x,np.sin(x- i*2*2*np.pi/12),color=pt.color[key],label=key) + plt.plot(x,np.sin(x- i*2*2*np.pi/12)-0.1,color=pt.color.light[key]) + plt.plot(x,np.sin(x- i*2*2*np.pi/12)+0.1,color=pt.color.dark[key]) plt.legend() # bar plot -pt.lightcolor.reset_index() +pt.color.reset_index() plt.figure() x = np.arange(6) -cat = ['cat1','cat2','cat3','cat4','cat5','cat6','cat7','cat8'] +cat = ['cat{}'.format(i+1) for i in range(n)] p = [] yy = np.zeros_like(x) for c in cat: - y = np.random.random_integers(1,high=10,size=x.shape) - pl = plt.bar(x, y, 0.8, bottom=yy , color=pt.lightcolor.next()) - p.append(pl[0]) - yy = yy+y - + y = np.random.random_integers(1,high=10,size=x.shape) + pl = plt.bar(x, y, 0.8, bottom=yy , color=pt.color.next()) + p.append(pl[0]) + yy = yy+y + plt.legend(p,cat) # show plots -plt.show() \ No newline at end of file +plt.show() diff --git a/examples/color.svg b/examples/color.svg deleted file mode 100644 index e45ffea..0000000 --- a/examples/color.svg +++ /dev/null @@ -1,1460 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - r - - o - - y - - g - - l - - t - - a - - n - - b - - p - - f - - m - - k - - r - - o - - y - - g - - l - - t - - a - - n - - b - - p - - f - - m - - k - - r - - o - - y - - g - - l - - t - - a - - n - - b - - p - - f - - m - - k - o - y - l - t - a - n - b - p - f - m - k - g - r - r - o - y - g - l - t - a - n - b - p - f - m - k - color - lightcolor - cycle - edited - original - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/cs_to_svg.py b/examples/cs_to_svg.py new file mode 100644 index 0000000..8efddc9 --- /dev/null +++ b/examples/cs_to_svg.py @@ -0,0 +1,6 @@ + +import plottools as pt + +pt.color.to_svg('plottools_default_color.svg') +pt.color.light.to_svg('plottools_default_light_color.svg') +pt.color.dark.to_svg('plottools_default_dark_color.svg') \ No newline at end of file diff --git a/examples/grayscale.py b/examples/grayscale.py deleted file mode 100644 index c3e6f18..0000000 --- a/examples/grayscale.py +++ /dev/null @@ -1,47 +0,0 @@ -import matplotlib.pyplot as plt -import matplotlib.patches as patches -import numpy as np - -import plottools as pt - - -# get the grayscale values of all colors -n = len(pt.color.cycle) - -colors = [] -names = [] -grayscales = [] -for i in range(n): - names.append( pt.color.cycle[i] ) - colors.append( pt.color[i] ) - grayscales.append( 0.21*pt.color[i][0] + 0.72*pt.color[i][1] + 0.07*pt.color[i][2] ) - -colors = np.array(colors) -names = np.array(names) -grayscales = np.array(grayscales) - - -index = np.argsort( grayscales ) - -# create a figure with the colors sorted by grayscale -fig = plt.figure() -ax = fig.add_subplot(111) -for i,ind in enumerate(index): - ax.add_patch( patches.Rectangle( (i, 0), 1.0, 5.0, facecolor=colors[ind,:], edgecolor='none') ) - -plt.xlim([0,n]) -plt.ylim([0,5]) - - -# plot the grayscale values in order -plt.figure() -plt.plot(np.arange(n),grayscales[index],'o',color=pt.color['k']) -for i,ind in enumerate(index): - plt.text(i+0.12,grayscales[ind],names[ind]) - -plt.xlim([0,n]) -plt.ylim([0.0,0.9]) - -plt.show() - - diff --git a/examples/greyscale.py b/examples/greyscale.py new file mode 100644 index 0000000..4fa5f39 --- /dev/null +++ b/examples/greyscale.py @@ -0,0 +1,57 @@ +import matplotlib.pyplot as plt +import matplotlib.patches as patches +import numpy as np + +import plottools as pt +from plottools.cs.util import to_greyscale + +# get the greyscale values of all colors +n = len(pt.color.cycle) + +colors = [] +names = [] +greyscales = [] +for i in range(n): + names.append( pt.color.cycle[i] ) + colors.append( pt.color[i] ) + greyscales.append( np.mean(to_greyscale(pt.color[i])) ) + +colors = np.array(colors) +names = np.array(names) +greyscales = np.array(greyscales) + + +index = np.argsort( greyscales ) + +# create a figure with the colors sorted by greyscale +fig = plt.figure() +ax = fig.add_subplot(111) +for i,ind in enumerate(index): + ax.add_patch( patches.Rectangle( (i, 0), 1.0, 5.0, facecolor=colors[ind,:], edgecolor='none') ) + +plt.xlim([0,n]) +plt.ylim([0,5]) + +# create a figure with the colors sorted by greyscale as lines +fig = plt.figure() +ax = fig.add_subplot(111) +for i,ind in enumerate(index): + plt.plot( [0.0,5.0],[i+0.5,i+0.5], linewidth=2, color=colors[ind,:], label=names[ind] ) + +plt.legend() +plt.xlim([0,5]) +plt.ylim([0,n]) + + +# plot the greyscale values in order +plt.figure() +plt.plot(np.arange(n),greyscales[index],'o',color=pt.color['k']) +for i,ind in enumerate(index): + plt.text(i+0.12,greyscales[ind],names[ind]) + +plt.xlim([0,n]) +plt.ylim([0.0,0.9]) + +plt.show() + + diff --git a/examples/plottools_default_color.svg b/examples/plottools_default_color.svg new file mode 100644 index 0000000..bc68faf --- /dev/null +++ b/examples/plottools_default_color.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + diff --git a/examples/plottools_default_dark_color.svg b/examples/plottools_default_dark_color.svg new file mode 100644 index 0000000..7b6d1ab --- /dev/null +++ b/examples/plottools_default_dark_color.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + diff --git a/examples/plottools_default_light_color.svg b/examples/plottools_default_light_color.svg new file mode 100644 index 0000000..438ee64 --- /dev/null +++ b/examples/plottools_default_light_color.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + diff --git a/examples/print_scan_default.jpg b/examples/print_scan_default.jpg new file mode 100644 index 0000000..0571712 Binary files /dev/null and b/examples/print_scan_default.jpg differ diff --git a/examples/print_scan_default.py b/examples/print_scan_default.py new file mode 100644 index 0000000..2e51d59 --- /dev/null +++ b/examples/print_scan_default.py @@ -0,0 +1,14 @@ +import matplotlib.pyplot as plt +import plottools as pt + + + +pt.cs.prepare_print_scan(pt.color.colors) +plt.savefig('print_scan_default.jpg') + +# print the image and then scan it +pt.cs.analyse_print_scan('print_scan_default_inkjet.jpg',pt.color.colors) + +pt.cs.analyse_print_scan('print_scan_default_laserjet.jpg',pt.color.colors) + +plt.show() diff --git a/examples/print_scan_default_inkjet.jpg b/examples/print_scan_default_inkjet.jpg new file mode 100644 index 0000000..2315478 Binary files /dev/null and b/examples/print_scan_default_inkjet.jpg differ diff --git a/examples/print_scan_default_laserjet.jpg b/examples/print_scan_default_laserjet.jpg new file mode 100644 index 0000000..723f615 Binary files /dev/null and b/examples/print_scan_default_laserjet.jpg differ diff --git a/examples/style.py b/examples/style.py new file mode 100644 index 0000000..ba55440 --- /dev/null +++ b/examples/style.py @@ -0,0 +1,54 @@ +import matplotlib.pyplot as plt +import matplotlib as mpl +import numpy as np + +import plottools as pt +pt.set_publication_rc() + + +# bar chart +plt.figure() +pt.color.reset_index() +x = np.arange(6) +cat = ['cat1','cat2','cat3','cat4'] + +p = [] +yy = np.zeros_like(x) +for c in cat: + y = np.random.random_integers(1,high=10,size=x.shape) + plt.bar(x, y, 0.8, bottom=yy, color=pt.color.next(),label=c ) + yy += y + +plt.xticks(x+0.4,x) +plt.xlim([x[0]-0.2, x[-1]+0.8+0.2]) + +plt.ylabel('y-label') +plt.legend(framealpha=0.7) +pt.style.set(['horizontalgrid','noxticks']) + +plt.savefig('style_horizontalgridwithoutticks.pdf') + + + +# line chart +plt.figure() +pt.color.reset_index() +x = np.arange(20) +cat = ['cat1','cat2','cat3','cat4'] + +for c in cat: + y = np.cumsum( np.random.random_integers(-3,high=5,size=x.shape) ) + plt.plot(x, y, color=pt.color.next(), label=c ) + +plt.ylabel('y-label') +plt.xlabel('x-label') +plt.legend(framealpha=0.7,loc='upper left') +pt.style.set(['horizontalgrid']) + +plt.savefig('style_horizontalgrid.pdf') + + + + +# show plots +plt.show() \ No newline at end of file diff --git a/examples/zoom_axes.py b/examples/zoom_axes.py index 1e32287..a954087 100644 --- a/examples/zoom_axes.py +++ b/examples/zoom_axes.py @@ -1,4 +1,5 @@ import matplotlib.pyplot as plt +import numpy as np import plottools as pt @@ -15,23 +16,40 @@ ax = plt.gca() ax1 = pt.zoom_axes(fig,ax,[0.1,0.3],[1.0,1.2],[1.0,1.9],[1.5,2.0]) -ax1.plot([0,2],[1,2]) -ax1.plot([0,2],[2,1]) +plt.plot([0,2],[1,2]) +plt.plot([0,2],[2,1]) -# small figure, box on the right -plt.figure(figsize=(8/2.54,6/2.54)) -plt.plot([0,2],[1,2]) -plt.plot([0,2],[2,1]) + +# axes all around the box +def draw_testlines(): + t = np.linspace(0,30,500) + plt.plot(np.cos(2*np.pi*t/6),np.sin(2*np.pi*t/5)) + + +plt.figure(figsize=(16/2.54,12/2.54)) +draw_testlines() plt.xlabel('xlabel') plt.ylabel('ylabel') +plt.xlim([-2.2,2.2]) +plt.ylim([-2.2,2.2]) + fig = plt.gcf() ax = plt.gca() -ax1 = pt.zoom_axes(fig,ax,[1.5,1.6],[1.7,1.8],[0.15,0.9],[1.4,1.9]) -plt.plot([0,2],[1,2]) -plt.plot([0,2],[2,1]) + +count = 0 +for dy in [0.8,0,-0.8]: + for dx in [-0.8,0,0.8]: + count+=1 + + if not (dx==0 and dy==0): + + s = 2 + ax1 = pt.zoom_axes(fig,ax,[-0.2+dx,0.2+dx],[-0.2+dy,0.2+dy],[-0.4+s*dx,0.4+s*dx],[-0.4+s*dy,0.4+s*dy]) + draw_testlines() + # show plots diff --git a/plottools/__init__.py b/plottools/__init__.py index fa65ce1..8a46a18 100644 --- a/plottools/__init__.py +++ b/plottools/__init__.py @@ -1,22 +1,32 @@ -#!/usr/bin/python -###################################################################################### -# Copyright 2015 Brecht Baeten +#!/usr/bin/env/ python +################################################################################ +# Copyright 2016 Brecht Baeten # This file is part of plottools. -# +# # plottools is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. -# +# # plottools is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with plottools. If not, see . -###################################################################################### +################################################################################ + +from .__version__ import version as __version__ +from .plottools import * +import cs +import cm +import style + + +################################################################################ +# create default color schemes +################################################################################ +color = cs.Colorscheme(cs.default.colors,longnames=cs.default.longnames,cycle=cs.default.cycle) + -from plottools import * -from plotcolor import * -import cm \ No newline at end of file diff --git a/plottools/__main__.py b/plottools/__main__.py new file mode 100644 index 0000000..3195a58 --- /dev/null +++ b/plottools/__main__.py @@ -0,0 +1,32 @@ +#!/usr/bin/env/ python +################################################################################ +# Copyright 2016 Brecht Baeten +# This file is part of plottools. +# +# plottools is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# plottools is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with plottools. If not, see . +################################################################################ + +import sys +import color.colorscheme +import color.default +import matplotlib.pyplot as plt + +if 'cst' in sys.argv: + print('Starting Color Scheme Tool') + cst = color.colorscheme.ColorschemeTool(color.default.colors) + +if 'cs' in sys.argv: + print('Plotting the default Color Scheme') + color.default.plot() + plt.show() \ No newline at end of file diff --git a/plottools/__version__.py b/plottools/__version__.py new file mode 100644 index 0000000..6d009dd --- /dev/null +++ b/plottools/__version__.py @@ -0,0 +1 @@ +version = '0.2.0' \ No newline at end of file diff --git a/plottools/cm/__init__.py b/plottools/cm/__init__.py index 25c7037..5ead4ca 100644 --- a/plottools/cm/__init__.py +++ b/plottools/cm/__init__.py @@ -22,51 +22,51 @@ import numbers import numpy as np -from hotwater import hotwater -from coldhot import coldhot +from hotwater import test_cm as hotwater +from coldhot import test_cm as coldhot +from hue import test_cm as hue +def cm_to_svg(colormap,filename,stops=3): + """ + creates a svg file with the colormap defined as a gradient + + Arguments: + colormap: a matplotlib colormap instance + filename: string, the file to write the svg file to + stops=3: int or list fo floats, number of stops to use in the gradient or the position of the stops in the range of 0.0 to 1.0 + + Example: + plottools.cm.cm_to_svg(plottools.cm.hotwater,'hotwater.svg',stops=5) + """ + + # input handling + if isinstance(stops, numbers.Number): + stops = np.linspace(0.0,1.0,max(2,stops)) + + + # get the colors of the colormap at the stops + colors = ['#{:02x}{:02x}{:02x}'.format(int(colormap(s)[0]*255),int(colormap(s)[1]*255),int(colormap(s)[2]*255)) for s in stops] -def cm2svg(colormap,filename,stops=3): - """ - creates a svg file with the colormap defined as a gradient - - Arguments: - colormap: a matplotlib colormap instance - filename: string, the file to write the svg file to - stops=3: int or list fo floats, number of stops to use in the gradient or the position of the stops in the range of 0.0 to 1.0 - - Example: - plottools.cm.cm2svg(plottools.cm.hotwater,'hotwater.svg',stops=5) - """ - - # input handling - if isinstance(stops, numbers.Number): - stops = np.linspace(0.0,1.0,max(2,stops)) - - - # get the colors of the colormap at the stops - colors = ['#{:02x}{:02x}{:02x}'.format(int(colormap(s)[0]*255),int(colormap(s)[1]*255),int(colormap(s)[2]*255)) for s in stops] + + # current path + modulepath = os.path.dirname(sys.modules[__name__].__file__) - - # current path - modulepath = os.path.dirname(sys.modules[__name__].__file__) + # open the cm_blank svg file + blank_file = open(os.path.join(modulepath,'cm_blank.svg'), 'r') + content = blank_file.read() + blank_file.close() - # open the cm_blank svg file - blank_file = open(os.path.join(modulepath,'cm_blank.svg'), 'r') - content = blank_file.read() - blank_file.close() + # find the index where the stops must come + ind = content.find(' ') + before = content[:ind] + after = content[ind:] + + new_content = before + for s,c in zip(stops,colors): + new_content = new_content + ' \n'.format(s*1000,s,c) - # find the index where the stops must come - ind = content.find(' ') - before = content[:ind] - after = content[ind:] - - new_content = before - for s,c in zip(stops,colors): - new_content = new_content + ' \n'.format(s*1000,s,c) - - new_content = new_content+after - - new_file = open(filename, 'w') - new_file.write(new_content) - new_file.close() \ No newline at end of file + new_content = new_content+after + + new_file = open(filename, 'w') + new_file.write(new_content) + new_file.close() \ No newline at end of file diff --git a/plottools/cm/coldhot.py b/plottools/cm/coldhot.py index 4ccc95e..db76a1e 100644 --- a/plottools/cm/coldhot.py +++ b/plottools/cm/coldhot.py @@ -265,7 +265,7 @@ [ 0.89594311, 0.06741927, 0.05408478], [ 0.8985074 , 0.06502592, 0.04455319]] -coldhot = ListedColormap(cm_data, name=__file__) +test_cm = ListedColormap(cm_data, name=__file__) if __name__ == "__main__": @@ -274,9 +274,9 @@ try: from viscm import viscm - viscm(coldhot) + viscm(test_cm) except ImportError: print("viscm not found, falling back on simple display") plt.imshow(np.linspace(0, 100, 256)[None, :], aspect='auto', - cmap=coldhot) + cmap=test_cm) plt.show() diff --git a/plottools/cm/hotwater.py b/plottools/cm/hotwater.py index 218beb0..e5af9d7 100644 --- a/plottools/cm/hotwater.py +++ b/plottools/cm/hotwater.py @@ -265,7 +265,7 @@ [ 0.97993936, 0.07651969, 0.06070177], [ 0.98318108, 0.07928898, 0.05174878]] -hotwater = ListedColormap(cm_data, name=__file__) +test_cm = ListedColormap(cm_data, name=__file__) if __name__ == "__main__": @@ -274,9 +274,9 @@ try: from viscm import viscm - viscm(hotwater) + viscm(test_cm) except ImportError: print("viscm not found, falling back on simple display") plt.imshow(np.linspace(0, 100, 256)[None, :], aspect='auto', - cmap=hotwater) + cmap=test_cm) plt.show() diff --git a/plottools/cm/hotwater_new.py b/plottools/cm/hotwater_new.py new file mode 100644 index 0000000..319373e --- /dev/null +++ b/plottools/cm/hotwater_new.py @@ -0,0 +1,282 @@ + +from matplotlib.colors import ListedColormap +from numpy import nan, inf + +# Used to reconstruct the colormap in viscm +parameters = {'xp': [-4.9044626459399012, 9.97648973501245, 34.597701856224575, 41.361771120293838, 42.985147743670467, 40.820645579168314, 34.868264626787351, 10.517615276138002, 9.4353641938869259], + 'yp': [-30.735930735930708, -35.064935064935042, -19.101731601731586, -2.3268398268398016, 14.718614718614731, 23.647186147186176, 30.140692640692663, 28.78787878787881, 23.917748917748924], + 'min_Jp': 18.1236038719, + 'max_Jp': 75.7557706627} + +cm_data = [[ 0.05309761, 0.02765358, 0.57567508], + [ 0.06660787, 0.02434601, 0.57973405], + [ 0.0785568 , 0.02135181, 0.58346076], + [ 0.08939909, 0.01867147, 0.58687144], + [ 0.09940638, 0.01630231, 0.58998262], + [ 0.10875606, 0.01423902, 0.59281085], + [ 0.11757076, 0.01247489, 0.59537244], + [ 0.12592751, 0.01101599, 0.59768191], + [ 0.13390902, 0.00983502, 0.59975751], + [ 0.1415683 , 0.00892002, 0.60161437], + [ 0.14894758, 0.00825842, 0.60326698], + [ 0.15608122, 0.00783726, 0.60472917], + [ 0.16298542, 0.00765897, 0.60601441], + [ 0.16969712, 0.00769376, 0.60713563], + [ 0.17623673, 0.00792811, 0.60810447], + [ 0.18262151, 0.0083491 , 0.60893196], + [ 0.18886137, 0.00895062, 0.60962923], + [ 0.19496922, 0.00972059, 0.6102065 ], + [ 0.20096261, 0.01063905, 0.61067195], + [ 0.20685149, 0.0116947 , 0.61103397], + [ 0.21264204, 0.01288018, 0.61130119], + [ 0.21833889, 0.01418948, 0.61148226], + [ 0.22395598, 0.01560351, 0.61158205], + [ 0.22949954, 0.017113 , 0.6116068 ], + [ 0.23497253, 0.01871268, 0.6115635 ], + [ 0.24037845, 0.02039634, 0.61145852], + [ 0.24572711, 0.0221491 , 0.61129471], + [ 0.25102262, 0.02396359, 0.61107668], + [ 0.2562652 , 0.02583764, 0.61081062], + [ 0.26146007, 0.02776231, 0.61049975], + [ 0.26661301, 0.02972782, 0.61014626], + [ 0.27172685, 0.0317284 , 0.60975347], + [ 0.27679986, 0.03376471, 0.60932731], + [ 0.28183936, 0.03582485, 0.60886756], + [ 0.28684781, 0.03790366, 0.6083766 ], + [ 0.29182577, 0.0399988 , 0.60785786], + [ 0.29677486, 0.04205962, 0.60731377], + [ 0.30169931, 0.04405991, 0.60674447], + [ 0.30660069, 0.04600647, 0.60615172], + [ 0.31147816, 0.04790318, 0.60553906], + [ 0.31633589, 0.04974712, 0.60490571], + [ 0.32117539, 0.05153889, 0.60425274], + [ 0.32599699, 0.05328063, 0.60358207], + [ 0.33080215, 0.05497281, 0.60289446], + [ 0.33559309, 0.05661499, 0.60218974], + [ 0.34037066, 0.0582083 , 0.60146879], + [ 0.34513502, 0.05975469, 0.60073301], + [ 0.34988845, 0.06125339, 0.59998161], + [ 0.35463173, 0.06270541, 0.59921506], + [ 0.35936533, 0.06411205, 0.59843401], + [ 0.36409055, 0.06547354, 0.59763816], + [ 0.36880824, 0.06679056, 0.59682752], + [ 0.37351904, 0.06806398, 0.59600219], + [ 0.37822389, 0.06929426, 0.59516186], + [ 0.38292342, 0.07048213, 0.59430642], + [ 0.38761812, 0.07162843, 0.59343582], + [ 0.39230921, 0.07273308, 0.59254913], + [ 0.39699686, 0.07379718, 0.59164645], + [ 0.40168146, 0.07482151, 0.5907276 ], + [ 0.40636404, 0.07580601, 0.58979155], + [ 0.41104513, 0.07675118, 0.58883778], + [ 0.41572466, 0.07765821, 0.58786642], + [ 0.42040316, 0.07852746, 0.58687679], + [ 0.42508217, 0.07935804, 0.58586694], + [ 0.42976084, 0.08015195, 0.58483778], + [ 0.43443942, 0.08090976, 0.58378881], + [ 0.43911958, 0.08163023, 0.58271766], + [ 0.44380089, 0.08231471, 0.58162458], + [ 0.44848307, 0.08296435, 0.58050964], + [ 0.45316687, 0.08357889, 0.57937146], + [ 0.45785367, 0.08415726, 0.57820778], + [ 0.46254209, 0.08470188, 0.57702 ], + [ 0.46723226, 0.08521324, 0.57580751], + [ 0.4719262 , 0.08568923, 0.57456694], + [ 0.47662293, 0.08613168, 0.57329911], + [ 0.48132194, 0.08654182, 0.57200418], + [ 0.48602379, 0.08691939, 0.57068079], + [ 0.49073037, 0.08726222, 0.56932546], + [ 0.49543959, 0.08757359, 0.56794064], + [ 0.50015148, 0.08785391, 0.56652563], + [ 0.50486763, 0.08810128, 0.56507733], + [ 0.50958796, 0.08831615, 0.5635951 ], + [ 0.51431112, 0.08850072, 0.56208028], + [ 0.51903711, 0.08865533, 0.56053219], + [ 0.52376837, 0.08877665, 0.5589461 ], + [ 0.5285031 , 0.08886753, 0.55732411], + [ 0.53324065, 0.08892916, 0.5556665 ], + [ 0.53798094, 0.08896186, 0.55397262], + [ 0.54272702, 0.08896118, 0.5522365 ], + [ 0.54747586, 0.08893179, 0.55046235], + [ 0.5522273 , 0.08887416, 0.54864969], + [ 0.55698123, 0.08878855, 0.54679786], + [ 0.56174073, 0.08867016, 0.54490051], + [ 0.56650253, 0.08852422, 0.54296255], + [ 0.57126649, 0.08835106, 0.54098339], + [ 0.57603245, 0.088151 , 0.53896249], + [ 0.58080312, 0.08791955, 0.53689381], + [ 0.58557589, 0.08766106, 0.53478136], + [ 0.59035019, 0.08737647, 0.53262529], + [ 0.59512585, 0.08706617, 0.53042509], + [ 0.59990447, 0.08672733, 0.52817666], + [ 0.60468542, 0.08636096, 0.52588021], + [ 0.60946713, 0.08596987, 0.523538 ], + [ 0.61424936, 0.0855545 , 0.52114958], + [ 0.61903188, 0.08511531, 0.51871456], + [ 0.62381748, 0.08464706, 0.51622597], + [ 0.6286029 , 0.08415581, 0.51368973], + [ 0.63338785, 0.08364216, 0.51110554], + [ 0.63817203, 0.08310674, 0.50847304], + [ 0.64295538, 0.08254979, 0.50579147], + [ 0.64774008, 0.081967 , 0.50305476], + [ 0.65252312, 0.08136444, 0.50026868], + [ 0.65730419, 0.08074292, 0.49743294], + [ 0.66208296, 0.08010337, 0.4945473 ], + [ 0.6668591 , 0.07944675, 0.49161152], + [ 0.67163395, 0.07877048, 0.4886212 ], + [ 0.67640634, 0.07807733, 0.48557796], + [ 0.68117498, 0.07737055, 0.48248389], + [ 0.68593948, 0.07665153, 0.47933882], + [ 0.69069946, 0.07592179, 0.47614262], + [ 0.69545451, 0.07518297, 0.47289514], + [ 0.70020514, 0.07443463, 0.46959368], + [ 0.70495119, 0.07367802, 0.46623733], + [ 0.70969093, 0.07291827, 0.46282941], + [ 0.71442389, 0.07215771, 0.45936986], + [ 0.7191496 , 0.07139891, 0.45585863], + [ 0.72386756, 0.07064463, 0.4522957 ], + [ 0.72857726, 0.0698979 , 0.44868106], + [ 0.73327815, 0.06916198, 0.44501469], + [ 0.73797067, 0.0684378 , 0.44129359], + [ 0.74265376, 0.06773039, 0.43751914], + [ 0.74732625, 0.06704543, 0.4336932 ], + [ 0.7519875 , 0.0663874 , 0.42981587], + [ 0.75663688, 0.06576107, 0.42588722], + [ 0.76127372, 0.06517155, 0.42190739], + [ 0.76589734, 0.0646243 , 0.41787648], + [ 0.77050701, 0.0641251 , 0.41379466], + [ 0.77510198, 0.06368005, 0.40966209], + [ 0.77968148, 0.0632956 , 0.40547895], + [ 0.78424469, 0.06297845, 0.40124544], + [ 0.78879077, 0.06273562, 0.3969618 ], + [ 0.79331883, 0.06257433, 0.39262826], + [ 0.79782796, 0.06250201, 0.38824511], + [ 0.80231719, 0.0625262 , 0.38381264], + [ 0.80678552, 0.06265454, 0.37933118], + [ 0.81123191, 0.06289467, 0.37480109], + [ 0.81565526, 0.06325413, 0.37022275], + [ 0.82005444, 0.06374032, 0.36559658], + [ 0.82442824, 0.06436042, 0.36092305], + [ 0.82877542, 0.06512124, 0.35620264], + [ 0.83309468, 0.06602922, 0.3514359 ], + [ 0.83738465, 0.0670903 , 0.34662342], + [ 0.84164393, 0.06830988, 0.34176582], + [ 0.84587101, 0.06969273, 0.33686379], + [ 0.85006436, 0.07124301, 0.33191806], + [ 0.85422236, 0.07296419, 0.32692944], + [ 0.85834332, 0.07485904, 0.32189878], + [ 0.86242549, 0.07692968, 0.31682702], + [ 0.86646704, 0.07917755, 0.31171513], + [ 0.87046607, 0.08160343, 0.30656421], + [ 0.87442119, 0.08420662, 0.30137077], + [ 0.87833001, 0.08698756, 0.29613785], + [ 0.88219009, 0.08994599, 0.2908692 ], + [ 0.88599921, 0.09308064, 0.28556626], + [ 0.88975507, 0.09638984, 0.28023058], + [ 0.89345533, 0.09987153, 0.2748638 ], + [ 0.89709804, 0.10352348, 0.26946107], + [ 0.90068031, 0.10734356, 0.26402769], + [ 0.90419933, 0.11132898, 0.25856871], + [ 0.90765249, 0.11547665, 0.25308626], + [ 0.91103723, 0.11978374, 0.24757981], + [ 0.91435089, 0.12424795, 0.24204682], + [ 0.91759042, 0.12886475, 0.23649776], + [ 0.92075309, 0.13363034, 0.23093538], + [ 0.92383614, 0.13854243, 0.22535634], + [ 0.92683669, 0.14359677, 0.21976584], + [ 0.92975211, 0.14878801, 0.21417154], + [ 0.93257967, 0.15411281, 0.20857341], + [ 0.9353166 , 0.15956763, 0.20297249], + [ 0.93796078, 0.1651455 , 0.19737897], + [ 0.94050971, 0.1708426 , 0.19179353], + [ 0.94296107, 0.17665432, 0.18621905], + [ 0.9453134 , 0.18257312, 0.18066473], + [ 0.94756459, 0.18859492, 0.17513136], + [ 0.94971337, 0.19471298, 0.16962553], + [ 0.95175887, 0.20092016, 0.16415405], + [ 0.95369972, 0.20721165, 0.15871899], + [ 0.95553588, 0.21357931, 0.15332885], + [ 0.95726703, 0.22001679, 0.14798859], + [ 0.95889329, 0.22651724, 0.1427042 ], + [ 0.96041508, 0.23307393, 0.13748175], + [ 0.96183341, 0.2396794 , 0.13232879], + [ 0.96314915, 0.24632745, 0.12725128], + [ 0.9643634 , 0.25301197, 0.12225572], + [ 0.96547871, 0.25972444, 0.11735313], + [ 0.96649572, 0.2664613 , 0.1125477 ], + [ 0.96741698, 0.2732154 , 0.10785035], + [ 0.96824515, 0.27998027, 0.10327209], + [ 0.96898111, 0.28675336, 0.09881957], + [ 0.96962858, 0.29352721, 0.09450826], + [ 0.97018982, 0.30029774, 0.09035031], + [ 0.97066588, 0.30706328, 0.08635642], + [ 0.97106075, 0.3138174 , 0.08254584], + [ 0.97137701, 0.32055663, 0.07893557], + [ 0.97161529, 0.3272808 , 0.07554017], + [ 0.9717784 , 0.33398642, 0.07238057], + [ 0.97187122, 0.34066724, 0.06948243], + [ 0.97189286, 0.34732594, 0.0668615 ], + [ 0.97184539, 0.35396069, 0.06454003], + [ 0.97173225, 0.36056797, 0.06254211], + [ 0.97155612, 0.36714556, 0.06088925], + [ 0.97131664, 0.3736954 , 0.05959704], + [ 0.97101554, 0.38021654, 0.05868167], + [ 0.97065469, 0.38670795, 0.05815594], + [ 0.97023823, 0.39316591, 0.05803019], + [ 0.96976463, 0.39959372, 0.05830448], + [ 0.96923523, 0.40599107, 0.05897716], + [ 0.96865129, 0.41235775, 0.06004171], + [ 0.96801398, 0.41869364, 0.06148723], + [ 0.96732635, 0.42499659, 0.06329967], + [ 0.96658718, 0.43126916, 0.06546049], + [ 0.9657973 , 0.43751154, 0.06795034], + [ 0.96495753, 0.44372393, 0.07074881], + [ 0.96406862, 0.44990655, 0.07383522], + [ 0.96313126, 0.45605966, 0.07718926], + [ 0.96214662, 0.46218303, 0.0807914 ], + [ 0.96111424, 0.46827798, 0.08462368], + [ 0.96003459, 0.47434484, 0.08866957], + [ 0.9589081 , 0.48038396, 0.09291423], + [ 0.95773516, 0.48639566, 0.09734451], + [ 0.95651613, 0.49238026, 0.10194901], + [ 0.95525034, 0.49833892, 0.10671854], + [ 0.95393783, 0.5042721 , 0.11164532], + [ 0.95257931, 0.51017962, 0.11672276], + [ 0.95117503, 0.51606169, 0.121946 ], + [ 0.94972529, 0.52191839, 0.12731163], + [ 0.94823044, 0.52774975, 0.13281765], + [ 0.94668519, 0.53356011, 0.1384677 ], + [ 0.94509381, 0.53934613, 0.14426019], + [ 0.94345723, 0.54510718, 0.15019731], + [ 0.94177633, 0.55084259, 0.15628289], + [ 0.94004523, 0.55655653, 0.16252849], + [ 0.93826433, 0.56224826, 0.16894303], + [ 0.93644119, 0.56791198, 0.17553146], + [ 0.93457307, 0.5735491 , 0.18231101], + [ 0.93264972, 0.57916572, 0.18931129], + [ 0.93069317, 0.58474582, 0.19653516], + [ 0.92868255, 0.59030183, 0.20403697], + [ 0.92664509, 0.59581404, 0.21183032], + [ 0.92456249, 0.60129151, 0.21999539], + [ 0.92247312, 0.60670657, 0.22856835], + [ 0.92039123, 0.61204575, 0.23764896], + [ 0.918349 , 0.61728213, 0.24737718], + [ 0.91645204, 0.62234256, 0.25788678], + [ 0.91489602, 0.62709876, 0.26932624], + [ 0.91410321, 0.63129811, 0.28157637]] + +test_cm = ListedColormap(cm_data, name=__file__) + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + import numpy as np + + try: + from viscm import viscm + viscm(test_cm) + except ImportError: + print("viscm not found, falling back on simple display") + plt.imshow(np.linspace(0, 100, 256)[None, :], aspect='auto', + cmap=test_cm) + plt.show() diff --git a/plottools/cm/hue.py b/plottools/cm/hue.py new file mode 100644 index 0000000..7cbb6c8 --- /dev/null +++ b/plottools/cm/hue.py @@ -0,0 +1,27 @@ + +import numpy as np +from matplotlib.colors import ListedColormap +from colorspacious import cspace_converter + + +_JCh_to_sRGB1 = cspace_converter('JCh', 'sRGB1') + +cm_data = [] +for i in range(256): + RGBi = np.clip(_JCh_to_sRGB1( (50,100,360.*i/255) ),0,1) + cm_data.append( list(RGBi) ) + +test_cm = ListedColormap(cm_data, name=__file__) + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + + try: + from viscm import viscm + viscm(test_cm) + except ImportError: + print("viscm not found, falling back on simple display") + plt.imshow(np.linspace(0, 100, 256)[None, :], aspect='auto', + cmap=test_cm) + plt.show() diff --git a/plottools/cs/__init__.py b/plottools/cs/__init__.py new file mode 100644 index 0000000..fdac77e --- /dev/null +++ b/plottools/cs/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env/ python +################################################################################ +# Copyright 2016 Brecht Baeten +# This file is part of plottools. +# +# plottools is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# plottools is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with plottools. If not, see . +################################################################################ + +from .colorscheme import * +from .util import * +from . import default \ No newline at end of file diff --git a/plottools/cs/colorscheme.py b/plottools/cs/colorscheme.py new file mode 100644 index 0000000..b10afdd --- /dev/null +++ b/plottools/cs/colorscheme.py @@ -0,0 +1,380 @@ +#!/usr/bin/env/ python +################################################################################ +# Copyright 2016 Brecht Baeten +# This file is part of plottools. +# +# plottools is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# plottools is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with plottools. If not, see . +################################################################################ + +import sys +import os +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.patches as patches +from matplotlib.widgets import Button, Slider + +from cycler import cycler + +import util +from .. import cm + +################################################################################ +# Colorscheme class +################################################################################ +class Colorscheme(object): + """ + A colorscheme class useful for plotting + + """ + + def __init__(self,colors,longnames=None,cycle=None,base=None): + """ + defines a colorscheme object useful for plotting + + Parameters + ---------- + colors : dict + Dictionary of named colors as RGB (0-1) tupples + + longnames : dict, optional + Dictionary of long names of the colors specified in colors + + cycle : list, optional + List with the cycle order + + Examples + -------- + >>> cs = Colorscheme({'r':(1.,0.,0.),'g':(0.,1.,0.),'b':(0.,0.,1.)},['b','g','r']) + >>> print( cs[0] ) + >>> + >>> print( cs.next() ) + >>> print( cs.next() ) + >>> + >>> cs.reset_index() + >>> print( cs.next() ) + """ + + + if longnames == None: + longnames = {k:k for k in colors.keys()} + + if cycle == None: + cycle = colors.keys() + + + self.colors = colors + self.longnames = longnames + self.cycle = cycle + self.currentindex = 0 + self.base = base + + # create light and dark variants + if base==None: + lightcolors = {} + darkcolors = {} + for key,val in colors.items(): + JCh = util._sRGB1_to_JCh(val) + JCh[0] = min(JCh[0]+30,100) + lightcolors[key] = np.clip(util._JCh_to_sRGB1(JCh),0,1) + + JCh = util._sRGB1_to_JCh(val) + JCh[0] = max(JCh[0]-20,1e-6) + darkcolors[key] = np.clip(util._JCh_to_sRGB1(JCh),0,1) + + self.light = Colorscheme(lightcolors,longnames=longnames,cycle=cycle,base=self) + self.dark = Colorscheme(darkcolors,longnames=longnames,cycle=cycle,base=self) + + + def next(self): + """ + get the next color in the cycle + """ + c = self.colors[self.cycle[self.currentindex]] + self.currentindex += 1 + if self.currentindex >= len(self.cycle): + self.currentindex = 0 + + return c + + def reset_index(self): + """ + resets the current color index to 0 + """ + + self.currentindex = 0 + + def set_as_default(self): + """ + sets the colorscheme as the default color cycle in matplotlib figures + """ + + plt.rc('axes',prop_cycle=cycler('color', [self.colors[c] for c in self.cycle]) ) + + def __getitem__(self,key): + if isinstance(key,int): + self.currentindex = key+1 + return self.colors[self.cycle[key]] + else: + if key in self.longnames: + key = self.longnames[key] + + self.currentindex = self.cycle.index(key)+1 + return self.colors[key] + + def keys(self): + return self.colors.keys() + + def values(self): + return [self.colors[key] for key in self.keys()] + + def items(self): + return zip(self.keys(),self.values()) + + def plot(self,ax=None): + """ + plots the current colorscheme main colors + """ + if ax == None: + fig,ax = plt.subplots() + + util.plot_colors(ax,self.colors,order=self.cycle) + + def to_svg(self,filename): + """ + converts the colorscheme to an svg file with block of colors ordered + according to the cycle + + parameters + ---------- + filename : string + the file to write the svg file to + + Examples + -------- + >>>> plottools.colors.to_svg('defaultcolors.svg') + + """ + + # get the colors of the colorscheme + colors = ['#{:02x}{:02x}{:02x}'.format(int(self.colors[k][0]*255),int(self.colors[k][1]*255),int(self.colors[k][2]*255)) for k in self.cycle] + + width = 100./len(self.cycle) + x = [i*width for i in range(len(self.cycle))] + + # current path + modulepath = os.path.dirname(sys.modules[__name__].__file__) + + # open the cs_blank svg file + blank_file = open(os.path.join(modulepath,'cs_blank.svg'), 'r') + content = blank_file.read() + blank_file.close() + + # find the rectangle template + ind0 = content.find(' ') + before = content[:ind0] + after = content[ind1:] + + + new_content = before + for k,c,x in zip(self.cycle,colors,x): + new_content = new_content + ' \n'.format(c,k,width,x) + + new_content = new_content+after + + new_file = open(filename, 'w') + new_file.write(new_content) + new_file.close() + + + + +class ColorschemeTool(object): + """ + """ + def __init__(self,colorscheme): + """ + """ + + if colorscheme.__class__ == Colorscheme: + colors = colorscheme.colors + else: + colors = colorscheme + + self.order = util.order_by_J(colors) + self.RGB = colors + self.grey = self._to_greyscale(colors) + self.exampledata = self._generate_exampledata(self.order) + + + plt.figure(figsize=(12,10)) + axcolor = 'None' + + x = 0.70 + y = 0.40 + w = 0.10 + h = 0.50 + + self.ax_col = plt.axes([x, y, w, h]) + self.ax_col.get_xaxis().set_visible(False) + self.ax_col.get_yaxis().set_visible(False) + + self.ax_grey = plt.axes([x+w+0.05, y, w, h]) + self.ax_grey.get_xaxis().set_visible(False) + self.ax_grey.get_yaxis().set_visible(False) + + + self.ax_distribution = plt.axes([0.05, 0.05, 0.45, y-0.10]) + self.ax_example_color = plt.axes([0.55, 0.23, 0.40, 0.12]) + self.ax_example_grey = plt.axes([0.55, 0.05, 0.40, 0.12]) + + + ax_btn_print_rgb = plt.axes([0.70, 0.92, 0.25, 0.05]) + self.btn_print_rgb = Button(ax_btn_print_rgb, 'Print RGB values') + self.btn_print_rgb.on_clicked(self.print_rgb) + + # create initial plots + self.plot_colors() + self.plot_distribution() + self.plot_example() + + + # create JCh sliders + self.slider_J = {} + self.slider_C = {} + self.slider_h = {} + + for i,key in enumerate(self.order): + RGB = self.RGB[key] + JCh = util._sRGB1_to_JCh(RGB) + + # J + aJ = plt.axes([0.05, y+i*h/len(self.order)+0.02, x-0.15, 0.05]) + aJ.imshow( np.linspace(0, 100, 101).reshape(1, -1), cmap='gray' ) + sJ = Slider(aJ, r'J', 0, 100, valinit=JCh[0]) + sJ.on_changed(self._slider_update) + + self.slider_J[key] = sJ + + # C + aC = plt.axes([0.05, y+i*h/len(self.order)+0.00, x-0.15, 0.05]) + aC.imshow( np.linspace(0, 100, 101).reshape(1, -1), cmap='gray' ) + sC = Slider(aC, r'C', 0, 100, valinit=JCh[1]) + sC.on_changed(self._slider_update) + + self.slider_C[key] = sC + + # h + ah = plt.axes([0.05, y+i*h/len(self.order)-0.02, x-0.15, 0.05]) + ah.imshow( np.linspace(0, 100, 101).reshape(1, -1), cmap=cm.hue ) + sh = Slider(ah, r'h', 0, 100, valinit=JCh[2]/360.*100.) + sh.on_changed(self._slider_update) + + self.slider_h[key] = sh + + plt.show() + + def plot_colors(self): + + # clear the axes + self.ax_col.cla() + self.ax_grey.cla() + + # draw patches + for i,key in enumerate(self.order): + self.ax_col.add_patch( patches.Rectangle( (0, i), 1.0, 1.0, facecolor=self.RGB[key], edgecolor='none') ) + self.ax_col.text(0.5,i+0.5,key,ha='center') + + self.ax_grey.add_patch( patches.Rectangle( (0, i), 1.0, 1.0, facecolor=self.grey[key], edgecolor='none') ) + self.ax_grey.text(0.5,i+0.5,key,ha='center') + + + self.ax_col.set_xlim([0,1]) + self.ax_col.set_ylim([0,len(self.order)]) + + self.ax_grey.set_xlim([0,1]) + self.ax_grey.set_ylim([0,len(self.order)]) + + def plot_distribution(self): + + self.ax_distribution.cla() + + x = np.arange(len(self.order)) + y = np.array([ np.mean(util.to_greyscale(self.RGB[key])) for key in self.order]) + + # add a line connecting 1st and last point + self.ax_distribution.plot([x[0],x[-1]],[y[0],y[-1]],'b--') + + # add greyscale points + self.ax_distribution.plot(x,y,'bo',label='greyscale') + + # add J points + j = np.array([ 1.*util._sRGB1_to_JCh(self.RGB[key])[0]/100 for key in self.order]) + self.ax_distribution.plot([x[0],x[-1]],[j[0],j[-1]],'r--') + self.ax_distribution.plot(x,j,'rs',label='lightness') + + # add labels + for xi,yi,key in zip(x,j,self.order): + self.ax_distribution.text(xi,yi-0.10,key) + + self.ax_distribution.set_xlim([-0.5,len(self.order)-1+0.5]) + self.ax_distribution.set_ylim([0.,1.]) + + self.ax_distribution.legend(loc='lower right',numpoints=1) + + + def plot_example(self): + + self.ax_example_color.cla() + for key in self.order: + self.ax_example_color.plot(self.exampledata[key],color=self.RGB[key],linewidth=2,label=key) + #self.ax_example_color.legend() + + self.ax_example_grey.cla() + for key in self.order: + self.ax_example_grey.plot(self.exampledata[key],color=self.grey[key],linewidth=2,label=key) + #self.ax_example_grey.legend() + + def print_rgb(self,event): + print('') + print('RGB values:') + for k,c in self.RGB.items(): + print( '\'{}\': ({:>3.0f}./255, {:>3.0f}./255, {:>3.0f}./255),'.format(k,c[0]*255,c[1]*255,c[2]*255)) + + def _generate_exampledata(self,keys): + exampledata = {} + for i,key in enumerate(keys): + exampledata[key] = np.cumsum(np.random.random(100)-0.5) + i + return exampledata + + def _to_greyscale(self,colors): + grey = {} + for key,val in colors.items(): + grey[key] = util.to_greyscale(val) + + return grey + + def _slider_update(self, val): + for key in self.order: + jp = self.slider_J[key].val + cp = self.slider_C[key].val + hp = self.slider_h[key].val*360./100. + + self.RGB[key] = np.clip(util._JCh_to_sRGB1([jp,cp,hp]),0,1) + + self.grey = self._to_greyscale(self.RGB) + + self.plot_colors() + self.plot_distribution() + self.plot_example() diff --git a/plottools/cs/cs_blank.svg b/plottools/cs/cs_blank.svg new file mode 100644 index 0000000..b86ea9b --- /dev/null +++ b/plottools/cs/cs_blank.svg @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/plottools/cs/default.py b/plottools/cs/default.py new file mode 100644 index 0000000..e7b6410 --- /dev/null +++ b/plottools/cs/default.py @@ -0,0 +1,42 @@ +#!/usr/bin/env/ python +################################################################################ +# Copyright 2016 Brecht Baeten +# This file is part of plottools. +# +# plottools is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# plottools is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with plottools. If not, see . +################################################################################ + + +# colors that look nice and have considerable contrast in greyscale +colors = { + 'b': ( 71./255, 116./255, 190./255), + 'g': (110./255, 197./255, 128./255), + 'k': ( 51./255, 51./255, 51./255), + 'o': (255./255, 185./255, 59./255), + 'p': (118./255, 56./255, 119./255), + 'r': (242./255, 92./255, 61./255), + 'y': (248./255, 232./255, 73./255), +} + +longnames = { + 'black': 'k', + 'purple': 'p', + 'blue': 'b', + 'green': 'g', + 'red': 'r', + 'orange': 'o', + 'yellow': 'y' +} + +cycle = ['b','g','r','o','y','p','k'] \ No newline at end of file diff --git a/plottools/cs/util.py b/plottools/cs/util.py new file mode 100644 index 0000000..5407613 --- /dev/null +++ b/plottools/cs/util.py @@ -0,0 +1,171 @@ +#!/usr/bin/env/ python +################################################################################ +# Copyright 2016 Brecht Baeten +# This file is part of plottools. +# +# plottools is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# plottools is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with plottools. If not, see . +################################################################################ + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.patches as patches +from colorspacious import cspace_converter +from PIL import Image + + + +def plot_colors(ax,colors,order=None): + + if order == None: + order = colors.keys() + elif order == 'J': + order = order_by_J(colors) + + for i,key in enumerate(order): + ax.add_patch( patches.Rectangle( (i, 0), 1.0, 1.0, facecolor=colors[key], edgecolor='none') ) + ax.text(i+0.5,0.5,key,ha='center') + + plt.xlim([0,len(colors)]) + plt.ylim([0,1]) + + +# derived from matplotlib viscm +_sRGB1_to_JCh = cspace_converter('sRGB1', 'JCh') +_JCh_to_sRGB1 = cspace_converter('JCh', 'sRGB1') + +def to_greyscale(sRGB1): + JCh = _sRGB1_to_JCh(sRGB1) + JCh[..., 1] = 0 + return _JCh_to_sRGB1(JCh) + + +def change_lightness_to_match_greyscale(col,grey): + JCh = _sRGB1_to_JCh(col) + Js = np.linspace(1e-6,100,20) + gs = [] + + for Ji in Js: + JCh[..., 0] = Ji + RGBi = _JCh_to_sRGB1(JCh) + RGBi = np.clip(RGBi,0,1) + gi = np.mean(to_greyscale(RGBi)) + + gs.append(gi) + + J = np.interp(grey,gs,Js) + JCh[..., 0] = J + newcol = _JCh_to_sRGB1( JCh ) + newcol = np.clip(newcol,0,1) + + return newcol + +def order_by_J(colors): + keys = colors.keys() + J = [_sRGB1_to_JCh(colors[key])[0] for key in keys] + order = [k for (j,k) in sorted(zip(J,keys))] + + return order + +def prepare_print_scan(colors): + + order = order_by_J(colors) + + colors_grey = {} + + for key,val in colors.items(): + colors_grey[key] = to_greyscale(val) + + fig = plt.figure(figsize=(16./2.54,24./2.54)) + ax1 = fig.add_subplot(211) + plot_colors(ax1,colors,order) + plt.title('print color') + + ax2 = fig.add_subplot(212) + plot_colors(ax2,colors_grey,order) + plt.title('print greyscale') + + +def analyse_print_scan(scanfile,colors): + + im = Image.open(scanfile).convert('RGB') + + # order the colors according to lightness + keys = colors.keys() + J = [_sRGB1_to_JCh(colors[key])[0] for key in keys] + order = [k for (j,k) in sorted(zip(J,keys))] + + width,height = im.size + + scan_colors = {} + scan_colors_grey = {} + for i,key in enumerate(order): + # get the colors from the scan file + x = int(0.20*width + 0.66*i/len(order)*width) + + y = int(0.25*height) + r = [ im.getpixel( (xi, yi) )[0] for xi in np.array(x)+np.arange(-int(0.02*width),int(0.02*width)) for yi in np.array(y)+np.arange(-int(0.02*height),int(0.02*height)) ] + g = [ im.getpixel( (xi, yi) )[1] for xi in np.array(x)+np.arange(-int(0.02*width),int(0.02*width)) for yi in np.array(y)+np.arange(-int(0.02*height),int(0.02*height)) ] + b = [ im.getpixel( (xi, yi) )[2] for xi in np.array(x)+np.arange(-int(0.02*width),int(0.02*width)) for yi in np.array(y)+np.arange(-int(0.02*height),int(0.02*height)) ] + + + scan_colors[key] = (np.mean(r)/255,np.mean(g)/255,np.mean(b)/255) + + y = int(0.80*height) + r = [ im.getpixel( (xi, yi) )[0] for xi in np.array(x)+np.arange(-int(0.02*width),int(0.02*width)) for yi in np.array(y)+np.arange(-int(0.02*height),int(0.02*height)) ] + g = [ im.getpixel( (xi, yi) )[1] for xi in np.array(x)+np.arange(-int(0.02*width),int(0.02*width)) for yi in np.array(y)+np.arange(-int(0.02*height),int(0.02*height)) ] + b = [ im.getpixel( (xi, yi) )[2] for xi in np.array(x)+np.arange(-int(0.02*width),int(0.02*width)) for yi in np.array(y)+np.arange(-int(0.02*height),int(0.02*height)) ] + + scan_colors_grey[key] = (np.mean(r)/255,np.mean(g)/255,np.mean(b)/255) + + + fig = plt.figure(figsize=(16./2.54,24./2.54)) + ax1 = fig.add_subplot(211) + plot_colors(ax1,scan_colors,order) + plt.title('scan color') + + ax2 = fig.add_subplot(212) + plot_colors(ax2,scan_colors_grey,order) + plt.title('scan greyscale') + + + # compare the printed values with to_greyscale + greyscales = {} + for key,val in colors.items(): + greyscales[key] = np.mean(to_greyscale(val)) + + scan_greyscales= {} + for key,val in scan_colors.items(): + scan_greyscales[key] = np.mean(val) + + + # rescale so first and last value are equal + x = np.arange(len(order)) + y1 = np.array([greyscales[key] for key in order]) + y2 = np.array([scan_greyscales[key] for key in order]) + # rescale so first and last value are equal + y2 = min(y1) + (y2-min(y2))*(max(y1)-min(y1))/(max(y2)-min(y2)) + + plt.figure() + plt.plot([x[0],x[-1]],[y1[0],y1[-1]],'k--') + + plt.plot(x,y1,'o',color='b',label='greyscale') + plt.plot(x,y2,'s',color='r',label='printed') + + for xi,yi,key in zip(x,y1,order): + plt.text(xi,yi-0.10,key) + + plt.legend(loc='lower right',numpoints=1) + plt.xlim([-0.5,len(order)-1+0.5]) + plt.ylim([-0.02,1.02]) + \ No newline at end of file diff --git a/plottools/linearize_grayscale.py b/plottools/linearize_grayscale.py deleted file mode 100644 index 0f247d8..0000000 --- a/plottools/linearize_grayscale.py +++ /dev/null @@ -1,139 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt -import matplotlib.patches as patches -import colorsys - - - -def plot_colors(ax,coldict,order={}): - n = len(coldict.keys()) - - if order == {}: - for i,k in enumerate(coldict.keys()): - order[k] = i - - for k,c in coldict.items(): - o = order[k] - ax.add_patch( patches.Rectangle( (o, 0), 1.0, 5.0, facecolor=c, edgecolor='none') ) - ax.text(o+0.5,2.5,k,ha='center') - - plt.xlim([0,n]) - plt.ylim([0,5]) - - -def rgb_to_gray(r,g,b): - return 0.21*r + 0.72*g + 0.07*b - - -def change_lightness_to_match_grayscale(col,gray): - h,l,s = colorsys.rgb_to_hls(col[0],col[1],col[2]) - - ls = np.linspace(0,1,100) - gs = [] - - for li in ls: - ri,gi,bi = colorsys.hls_to_rgb(h,li,s) - gs.append(rgb_to_gray(ri,gi,bi)) - - - ll = np.interp(gray,gs,ls) - - newcol = colorsys.hls_to_rgb(h,ll,s) - - return newcol - - - - -def linearize_grayscale(colors_rgb,gray_min,gray_max,order={},plot=False): - # convert all colors ro grayscale and determine the order - colors_gray_rgb = {} - gray = [] - keys = [] - for k,c in colors_rgb.items(): - g = rgb_to_gray(c[0],c[1],c[2]) - keys.append(k) - gray.append(g) - colors_gray_rgb[k] = (g,g,g) - - - # define the order - if order == {}: - for i,o in enumerate(np.argsort(gray)): - order[keys[o]] = i - - - - - # rescale to grayscale min-max - gray_edit = np.linspace(gray_min,gray_max,len(order.keys())) - colors_edit_rgb = {} - for k,c in colors_rgb.items(): - colors_edit_rgb[k] = change_lightness_to_match_grayscale(c,gray_edit[order[k]]) - - - # convert all colors ro grayscale and determine the order - colors_edit_gray_rgb = {} - for k,c in colors_edit_rgb.items(): - g = rgb_to_gray(c[0],c[1],c[2]) - colors_edit_gray_rgb[k] = (g,g,g) - - - # print final colors - if plot: - for k,c in colors_edit_rgb.items(): - print( '\'{}\': ({:>3.0f}./255,{:>3.0f}./255,{:>3.0f}./255),'.format(k,c[0]*255,c[1]*255,c[2]*255)) - - fig = plt.figure() - ax1 = fig.add_subplot(221) - plot_colors(ax1,colors_rgb,order) - plt.title('original') - - ax2 = fig.add_subplot(223) - plot_colors(ax2,colors_gray_rgb,order) - - ax3 = fig.add_subplot(222) - plot_colors(ax3,colors_edit_rgb,order) - plt.title('edited') - - ax4 = fig.add_subplot(224) - plot_colors(ax4,colors_edit_gray_rgb,order) - - - return colors_edit_rgb - - -if __name__ == '__main__': - - colors_rgb = { - 'r': (245./255, 82./255, 45./255), - 'o': (244./255,154./255, 26./255), - 'y': (242./255,244./255, 66./255), - 'g': ( 32./255, 81./255, 37./255), - 'l': ( 80./255,180./255, 54./255), - 'a': ( 95./255,173./255,209./255), - 'b': ( 31./255, 57./255,101./255), - 'p': (114./255, 31./255,119./255), - 'k': ( 49./255, 49./255, 49./255) - } - - order = { - 'k': 0, - 'p': 1, - 'b': 2, - 'r': 3, - 'g': 4, - 'o': 5, - 'l': 6, - 'a': 7, - 'y': 8 - } - - print('\nbase colors:') - linearize_grayscale(colors_rgb,0.20,0.90,order=order,plot=True) - - print('\nlight colors:') - linearize_grayscale(colors_rgb,0.40,0.95,order=order,plot=True) - - - plt.show() diff --git a/plottools/plotcolor.py b/plottools/plotcolor.py deleted file mode 100644 index 285b4b4..0000000 --- a/plottools/plotcolor.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/python -################################################################################ -# Copyright 2015 Brecht Baeten -# This file is part of plottools. -# -# plottools is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# plottools is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with plottools. If not, see . -################################################################################ - -import matplotlib.pyplot as plt -from cycler import cycler - -################################################################################ -# Color definitions -################################################################################ -# colors that look ok and have considerable contrast in grayscale -basecolors = { - 'a': (175./255,214./255,232./255), - 'b': ( 54./255,100./255,177./255), - 'g': ( 66./255,168./255, 77./255), - 'k': ( 51./255, 51./255, 51./255), - 'l': (126./255,210./255,104./255), - 'o': (244./255,152./255, 23./255), - 'p': (153./255, 42./255,160./255), - 'r': (245./255, 87./255, 51./255), - 'y': (241./255,243./255, 53./255) -} - -lightcolors = { - 'a': (204./255,229./255,240./255), - 'b': (101./255,141./255,208./255), - 'g': (107./255,197./255,116./255), - 'k': (102./255,102./255,102./255), - 'l': (167./255,224./255,152./255), - 'o': (247./255,183./255, 91./255), - 'p': (204./255, 86./255,211./255), - 'r': (248./255,132./255,106./255), - 'y': (248./255,249./255,154./255) -} - -longnames = { - 'black': 'k', - 'purple': 'p', - 'blue': 'b', - 'green': 'g', - 'red': 'r', - 'orange': 'o', - 'lime': 'l', - 'aqua': 'a', - 'yellow': 'y' -} - -basecycle = ['b','r','g','o','y','p','l','a','k'] - - -################################################################################ -# Colorscheme class -################################################################################ -class Colorscheme(object): - def __init__(self,colors=basecolors,longnames=longnames,cycle=basecycle): - """ - defines a colorscheme object useful for plotting - - Arguments: - colors: dict, dictionary of named colors as RGB (0-1) tupples - cycle: list, list with the cycle order - - Example: - cs = Colorscheme({'r':(1.,0.,0.),'g':(0.,1.,0.),'b':(0.,0.,1.)},['b','g','r']) - print( cs[0] ) - - print( cs.next() ) - print( cs.next() ) - - cs.reset_index() - print( cs.next() ) - """ - - self.colors = colors - self.longnames = longnames - self.cycle = cycle - self.currentindex = 0 - - def next(self): - """ - get the next color in the cycle - """ - c = self.colors[self.cycle[self.currentindex]] - self.currentindex += 1 - if self.currentindex >= len(self.cycle): - self.currentindex = 0 - - return c - - def reset_index(self): - """ - resets the current color index to 0 - """ - - self.currentindex = 0 - - def set_as_default(self): - """ - sets the colorscheme as the default color cycle in matplotlib figures - """ - - plt.rc('axes',prop_cycle=cycler('color', [self.colors[c] for c in self.cycle]) ) - - def __getitem__(self,key): - if isinstance(key,int): - self.currentindex = key+1 - return self.colors[self.cycle[key]] - else: - if key in self.longnames: - key = self.longnames[key] - - self.currentindex = self.cycle.index(key)+1 - return self.colors[key] - - - -################################################################################ -# create default color schemes -################################################################################ -color = Colorscheme() -lightcolor = Colorscheme(colors=lightcolors) diff --git a/plottools/plottools.py b/plottools/plottools.py index 3144c4c..2e0f1d7 100644 --- a/plottools/plottools.py +++ b/plottools/plottools.py @@ -1,64 +1,262 @@ -#!/usr/bin/python +#!/usr/bin/env/ python ################################################################################ -# Copyright 2015 Brecht Baeten +# Copyright 2016 Brecht Baeten # This file is part of plottools. -# +# # plottools is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. -# +# # plottools is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with plottools. If not, see . ################################################################################ import matplotlib.pyplot as plt import numpy as np +import itertools def set_publication_rc(): + """ + Sets rc parameters for creating plots suitable for publication + + Notes + ----- + The computer modern fonts are not installed by default on windows. But can + be downloaded at https://sourceforge.net/projects/cm-unicode/ + To use new installed fonts in matplotlib you must delete the font cache file + located at C:\Users\yourusername\.matplotlib + + Examples + -------- + .. plot:: + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> import plottools + >>> + >>> plottools.set_publication_rc() + >>> plt.plot(np.arange(10),10*np.random.random(10)) + >>> plt.xlabel('x-label') + >>> plt.ylabel('y-label') + >>> plt.show() + + """ + # figure - plt.rc('figure', autolayout=True) - plt.rc('savefig', format='pdf', dpi=150, bbox='tight', pad_inches=0.01) + plt.rc('figure', autolayout=True, figsize=(80/25.4,50/25.4)) + plt.rc('savefig', format='pdf', dpi=150, bbox='tight', pad_inches=0.02) + + # text + #plt.rc('text', usetex=True) + # font - plt.rc('font', family='serif', serif=['computer modern roman'], size=6) + plt.rc('font', size=6) + plt.rc('font', **{'family':'sans-serif', 'sans-serif':['computer modern sans serif', 'CMU Sans Serif'], 'serif':['computer modern roman', 'CMU Serif']} ) + # axes plt.rc('axes', linewidth=0.4, labelsize=8) plt.rc('axes.formatter', useoffset=False) + # legend plt.rc('legend', fontsize=8, frameon=True) + # lines plt.rc('lines', linewidth=0.8,markersize=4) - # text - plt.rc('text', usetex=True) + + # patch + plt.rc('patch', linewidth=0.4, edgecolor=(1.0,1.0,1.0)) + # ticks - plt.rc('xtick.major', size=3, width=0.3, pad=3) - plt.rc('ytick.major', size=3, width=0.3, pad=3) - plt.rc('xtick.minor', size=2, width=0.3, pad=3) - plt.rc('ytick.minor', size=2, width=0.3, pad=3) + plt.rc('xtick.major', size=2, width=0.3, pad=3) + plt.rc('ytick.major', size=2, width=0.3, pad=3) + plt.rc('xtick.minor', size=1, width=0.3, pad=3) + plt.rc('ytick.minor', size=1, width=0.3, pad=3) -def zoom_axes(fig,ax,zoom_x,zoom_y,axes_x,axes_y,box=True,box_color='k',box_alpha=0.8,connect=True,connect_color='k',connect_alpha=0.3,spacing_zoom=2,spacing_axes=20): +def savefig(filename,width=None,height=None,ratio=8./5.,**kwargs): """ - Creates a new axes which zooms in on a part of a given axes + Creates a new figure with a specified width and 8:5, width:height ratio + + If no width or height are specified a 8cm x 5cm figure is saved. If the + width and height are specified, a figure of that size is saved. + If one of width or height is specified, the ratio is used to define the + other. + + Parameters + ---------- + width : number + figure width in cm + + height : number + figure height in cm + + ratio : number + figure height in cm + + """ + + if width is None and height is None: + width = 8. + height = width/ratio + elif not width is None and height is None: + height = width/ratio + elif not height is None and width is None: + width = height*ratio + + + plt.gcf().set_size_inches(width/2.54,height/2.54) + plt.savefig(filename,**kwargs) + + +def set_style(style,axes=None): + """ + Sets the style of a single axes object from some specification on top of other rc parameters + + Parameters + ---------- + style : {'horizontalgrid','horizontalgridwithoutticks'} + style string 'horizontalgrid','horizontalgridwithoutticks' + + axes : matplotlib axes object + the axes to which to apply the style, if omitted the current axis + obtained with plt.gca() it styled + + Examples + -------- + >>> plt.plot(np.arange(10),10*np.random.random(10)) + >>> plottools.set_style('horizontalgrid') + >>> plt.show() + + """ + + if axes == None: + axes = plt.gca() + + if style in ['horizontalgrid','horizontalgridwithoutticks']: + # hide the spines except the bottom one + axes.spines['top'].set_visible(False) + # axes.spines['bottom'].set_visible(False) + axes.spines['right'].set_visible(False) + axes.spines['left'].set_visible(False) - Arguments: - fig: matplotlib figure - ax: matplotlib axes - zoom_x: list, specifying the zooming horizontal area - zoom_y: list, specifying the zooming vertical area - axes_x: list, specifying the new axes horizontal location in data coordinates - axes_y: list, specifying the new axes vertical location in data coordinates + # show ticks only on the left bottom + axes.get_xaxis().tick_bottom() + axes.get_yaxis().tick_left() + + # add horizontal lines + yticks = axes.get_yticks() + xlim = axes.get_xlim() + + for y in yticks: + axes.plot(xlim, [y,y], '-', linewidth=0.3, color='k', alpha=0.3, zorder=-10) + + axes.yaxis.set_tick_params(which='both', bottom='off', top='off', labelbottom='on', left='off', right='off', labelleft='on') + + + if style == 'horizontalgridwithoutticks': + axes.xaxis.set_tick_params(which='both', bottom='off', top='off', labelbottom='on', left='off', right='off', labelleft='on') + + + +def zoom_axes(fig,ax,zoom_x,zoom_y,axes_x,axes_y,box=True,box_color='k',box_alpha=0.8,connect=True,connect_color='k',connect_alpha=0.3,spacing=4,tick_width=20,tick_height=12): + """ + Creates a new axes which zooms in on a part of a given axes. + + A box is drawn around the area to be zoomed specified in data coordinates. A + new empty axes is created at the specified location, supplied in data + coordinates. The new axis limits are set so that they match the zoom box. + + The zoom box and axis can be connected with two lines, connecting the outer + most corner points while leaving space for the axis ticks. + - Returns: - ax1: a new axes + Parameters + ---------- + fig : matplotlib figure + the figure in which to create a zoom axis + + ax : matplotlib axes + the axis in which to create a zoom axis + + zoom_x : list + [min, max] specifying the zooming horizontal area in data + coordinates + + zoom_y : list + [min, max] specifying the zooming vertical area in data coordinates + + axes_x : list + [min, max] specifying the new axes horizontal location in data + coordinates + + axes_y : list + [min, max] specifying the new axes vertical location in data + coordinates + + box : bool, optional + specifies whether a box is drawn + + box_color : color string or tuple,optional + specifies the box color + + box_alpha : number + between 0 and 1, specifies the box alpha - Example: + connect : bool, optional + specifies whether the connecting lines are drawn + + connect_color : color string or tuple,optional + specifies the connecting lines color + + connect_alpha : number + between 0 and 1, specifies the connecting lines alpha + + spacing : number + specifies the spacing between the box, axis and the connecting lines + in points + + tick_width : number + specifies the width of the tick labels in points to avoid drawing + connecting lines through the tick labels + + tick_height : number + specifies the height of the tick labels in points to avoid drawing + connecting lines through the tick labels + + + Returns + ------- + ax_zoom : matplotlib axes + the new axes + Notes + ----- + * Axes limits should not be changed after a zoom axes has been added + * :code:`zoom_axes` does not give the expected results when used on a + subfigure + + Examples + -------- + .. plot:: + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> import plottools + >>> + >>> fig,ax = plt.subplots() + >>> x = np.linspace(0,1,100) + >>> y = 1-x + 0.02*(2*np.random.random(len(x))-1) + >>> ax.plot(x,y) + >>> ax_zoom = plottools.zoom_axes(fig,ax,[0.1,0.2],[0.8,0.9],[0.6,0.9],[0.6,0.9]) + >>> ax_zoom.plot(x,y) + >>> plt.show() + """ plt.tight_layout() @@ -79,35 +277,356 @@ def zoom_axes(fig,ax,zoom_x,zoom_y,axes_x,axes_y,box=True,box_color='k',box_alph ax.plot([zoom_x[0],zoom_x[1],zoom_x[1],zoom_x[0],zoom_x[0]],[zoom_y[0],zoom_y[0],zoom_y[1],zoom_y[1],zoom_y[0]],color=box_color,alpha=box_alpha,linewidth=0.4) if connect: - # estimate the width of the ticks - spacing_zoom = (ax.transData.inverted()).transform_point((spacing_zoom,0))[0]-(ax.transData.inverted()).transform_point((0,0))[0] - spacing_axes = (ax.transData.inverted()).transform_point((spacing_axes,0))[0]-(ax.transData.inverted()).transform_point((0,0))[0] - - if zoom_x[1] < axes_x[0]: - # the zoom box is on the left - x_connect_bot = [zoom_x[1] + spacing_zoom , axes_x[0] - 0.3*spacing_axes] - x_connect_top = [zoom_x[1] + spacing_zoom , axes_x[0] - 0.3*spacing_axes] - y_connect_bot = np.interp(x_connect_bot,[zoom_x[1],axes_x[0]],[zoom_y[0],axes_y[0]]) - y_connect_top = np.interp(x_connect_top,[zoom_x[1],axes_x[0]],[zoom_y[1],axes_y[1]]) - - if y_connect_bot[1] > axes_y[0]: - x_connect_bot = [zoom_x[1] + spacing_zoom , axes_x[0] - spacing_axes] - y_connect_bot = np.interp(x_connect_bot,[zoom_x[1],axes_x[0]],[zoom_y[0],axes_y[0]]) - - if y_connect_top[1] < axes_y[1]: - x_connect_top = [zoom_x[1] + spacing_zoom , axes_x[0] - spacing_axes] - y_connect_top = np.interp(x_connect_top,[zoom_x[1],axes_x[0]],[zoom_y[1],axes_y[1]]) + + # define a box of points of the axes and the zoom + zoom_xx = [zoom_x[0],zoom_x[0],zoom_x[1],zoom_x[1]] + zoom_yy = [zoom_y[0],zoom_y[1],zoom_y[1],zoom_y[0]] + axes_xx = [axes_x[0],axes_x[0],axes_x[1],axes_x[1]] + axes_yy = [axes_y[0],axes_y[1],axes_y[1],axes_y[0]] + + # determine which points to connect + if axes_x[1] < zoom_x[1]: + # left + if axes_y[0] > zoom_y[0]: + # top + p1 = 0 + p2 = 2 + elif axes_y[1] < zoom_y[1]: + # bottom + p1 = 1 + p2 = 3 + else: + # center + p1 = 2 + p2 = 3 + + elif axes_x[0] > zoom_x[0]: + # right + if axes_y[0] > zoom_y[0]: + # top + p1 = 1 + p2 = 3 + elif axes_y[1] < zoom_y[1]: + # bottom + p1 = 0 + p2 = 2 + else: + # center + p1 = 0 + p2 = 1 else: - # the zoom box is on the right - # the zoom box is on the left - x_connect_bot = [axes_x[1] + 0.3*spacing_axes , zoom_x[0] - spacing_zoom] - x_connect_top = [axes_x[1] + 0.3*spacing_axes , zoom_x[0] - spacing_zoom] - y_connect_bot = np.interp(x_connect_bot,[axes_x[1],zoom_x[0]],[axes_y[0],zoom_y[0]]) - y_connect_top = np.interp(x_connect_top,[axes_x[1],zoom_x[0]],[axes_y[1],zoom_y[1]]) - - ax.plot(x_connect_bot,y_connect_bot,color=connect_color,alpha=connect_alpha,linewidth=0.4) - ax.plot(x_connect_top,y_connect_top,color=connect_color,alpha=connect_alpha,linewidth=0.4) + # center + if axes_y[0] > zoom_y[0]: + # top + p1 = 0 + p2 = 3 + elif axes_y[1] < zoom_y[1]: + # bottom + p1 = 1 + p2 = 2 + else: + # center, the axes is over the zoom + p1 = 0 + p2 = 0 + + + line1 = ([zoom_xx[p1],axes_xx[p1]],[zoom_yy[p1],axes_yy[p1]]) + line2 = ([zoom_xx[p2],axes_xx[p2]],[zoom_yy[p2],axes_yy[p2]]) + + + # estimate the width and height of the ticks + tick_width = (ax.transData.inverted()).transform_point((tick_width,0))[0]-(ax.transData.inverted()).transform_point((0,0))[0] + tick_height = (ax.transData.inverted()).transform_point((0,tick_height))[1]-(ax.transData.inverted()).transform_point((0,0))[1] + spacing = (ax.transData.inverted()).transform_point((spacing,0))[0]-(ax.transData.inverted()).transform_point((0,0))[0] + + # create fictional boxes around the axes where no lines should be + box_axes_x = [ axes_x[0]-tick_width , axes_x[1]+spacing] + box_axes_y = [ axes_y[0]-tick_height , axes_y[1]+spacing] + + box_zoom_x = [ zoom_x[0]-spacing , zoom_x[1]+spacing] + box_zoom_y = [ zoom_y[0]-spacing , zoom_y[1]+spacing] + + + + # cut the lines inside the boxes + t = np.linspace(0,1,100) + + line1_cut = line1 + line2_cut = line2 + for tt in t: + x = line1[0][0]*(1-tt) + line1[0][1]*tt + y = line1[1][0]*(1-tt) + line1[1][1]*tt + if x <= box_zoom_x[0] or x >= box_zoom_x[1] or y <= box_zoom_y[0] or y >= box_zoom_y[1]: + line1_cut[0][0] = x + line1_cut[1][0] = y + break + + for tt in t[::-1]: + x = line1[0][0]*(1-tt) + line1[0][1]*tt + y = line1[1][0]*(1-tt) + line1[1][1]*tt + if (x <= box_axes_x[0] or x >= box_axes_x[1]) or (y <= box_axes_y[0] or y >= box_axes_y[1]): + line1_cut[0][1] = x + line1_cut[1][1] = y + break + + for tt in t: + x = line2[0][0]*(1-tt) + line2[0][1]*tt + y = line2[1][0]*(1-tt) + line2[1][1]*tt + if (x <= box_zoom_x[0] or x >= box_zoom_x[1]) or (y <= box_zoom_y[0] or y >= box_zoom_y[1]): + line2_cut[0][0] = x + line2_cut[1][0] = y + break + + for tt in t[::-1]: + x = line2[0][0]*(1-tt) + line2[0][1]*tt + y = line2[1][0]*(1-tt) + line2[1][1]*tt + if (x <= box_axes_x[0] or x >= box_axes_x[1]) or (y <= box_axes_y[0] or y >= box_axes_y[1]): + line2_cut[0][1] = x + line2_cut[1][1] = y + break + + # draw the connecting lines + ax.plot(line1_cut[0],line1_cut[1],color=connect_color,alpha=connect_alpha,linewidth=0.4) + ax.plot(line2_cut[0],line2_cut[1],color=connect_color,alpha=connect_alpha,linewidth=0.4) + return ax1 + + + +def categorized_xticklabels(xticks,xticklabels,xticklabelnames=None,fmt=None,size=None,rotation=None,spacing=1.4): + """ + Creates categorized ticks on the x-axis + + Parameters + ---------- + xticks : array-like + The x-locations of the data points + + xticklabels : list of array-likes + A list of lists or arrays of which each must have the same length as + xticks. These are all used as x-tick labels, the first array is + displayed highest, the next arrays are printed below the previous + one. Results are the most appealing if the 1st array has the highest + variation and the last array the lowest. + + xticklabelnames: list of strings, optional + A list of names for the labels. It must have the same length as the + xticklabels list. + + fmt: list of fromat strings, optional + A list of fromatting strings as used by :code:`format` for the tick + labels. It must have the same length as the xticklabels list. + + size: list of numbers, optional + A list of numbers specifying the size of the ticklabels in points. + It must have the same length as the xticklabels list. + + rotation: list of numbers, optional + A list of numbers specifying the rotation of the ticklabels in + degrees. It must have the same length as the xticklabels list. + + spacing: number, optional + Controls the vertical spacing between the differnt ticklabels + + Examples + -------- + .. plot:: + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> import plottools + >>> + >>> plottools.set_publication_rc() + >>> + >>> # generate data + >>> C,B,A = np.meshgrid([10,20],[0.4,0.6,0.8],[1,2,3],indexing='ij') + >>> xticklabels = [ A.reshape((-1,)), B.reshape((-1,)), C.reshape((-1,)) ] + >>> values = [np.random.random(len(t)) for t in xticklabels] + >>> xticks = np.arange(len(values[0])) + >>> + >>> xticklabelnames = ['coord','$B_\mathrm{value}$','CCC'] + >>> labels = ['set1','set2','set3'] + >>> fmt = ['${:.2f}$','${}$ m','$10^{{{:.0f}}}$'] + >>> rotation = [70,0,0] + >>> + >>> # create the figure + >>> plt.figure() + >>> bottom = np.zeros_like(values[0]) + >>> for val,lab in zip(values,labels): + ... plt.bar(xticks+0.05,val,0.9,bottom=bottom,label=lab,color=plottools.color.default.next()) + ... bottom += val + ... + >>> + >>> plt.legend(framealpha=0.7,loc='upper right') + >>> plt.ylabel('y-label') + >>> + >>> # add categories on the x-axis + >>> plottools.categorized_xticklabels(xticks+0.5,xticklabels,xticklabelnames=xticklabelnames,fmt=fmt,rotation=rotation) + >>> plt.show() + + """ + + # input parsing + if xticklabelnames == None: + xticklabelnames = ['']*len(xticklabels) + + if fmt == None: + fmt = ['{}']*len(xticklabels) + + if size == None: + size = [plt.gca().xaxis.get_major_ticks()[0].label.get_fontsize()]*len(xticklabels) + + if rotation == None: + rotation = [0]*len(xticklabels) + + + dxticks = np.zeros_like(xticks) + for i in range(len(xticks)-1): + dxticks[i] = xticks[i+1]-xticks[i] + dxticks[-1] = dxticks[-2] + + # set the x limits + plt.xlim([xticks[0]-dxticks[0]/2,xticks[-1]+dxticks[-1]/2]) + + # get both axis limits + xlim = plt.xlim() + ylim = plt.ylim() + + + + # create a list of y positions in points + yp = [] + ypi = -1 + + for i in range(len(xticklabels)): + ypi += -spacing*size[i] - 1*size[i]*np.sin(1.*rotation[i]/180*np.pi) + yp.append( ypi ) + + # manual xticks and labels + plt.xticks([]) + + linepositions = [] + for i in range(len(xticklabels)-1,0,-1): + c = xticklabels[i] + xtick_old = None + xticklabel_old = None + + for j,xtl in enumerate(c): + if not xtl == xticklabel_old: + + if not j in linepositions: + # add the separator line + plt.annotate('',xy=(xticks[j]-0.5*dxticks[j], ylim[0]), xycoords='data',xytext=(0, yp[i]), textcoords='offset points',arrowprops={'arrowstyle':'-','color':(0.3,0.3,0.3)}) + linepositions.append(j) + + if not xticklabel_old==None: + # add the tick label + xtick_avg = 0.5*(xtick_old - 0.5*dxticks[j]) + 0.5*(xticks[j] - 0.5*dxticks[j]) + try: + lab = fmt[i].format(xticklabel_old) + except: + lab = xticklabel_old + plt.annotate(lab,xy=(xtick_avg, ylim[0]), xycoords='data',xytext=(0, yp[i]), textcoords='offset points',ha="center", va="bottom", size=size[i], rotation=rotation[i]) + + xtick_old = xticks[j] + xticklabel_old = xtl + + # add the final tick label + j = len(c)-1 + xtick_avg = 0.5*(xtick_old - 0.5*dxticks[j]) + 0.5*(xticks[j] + 0.5*dxticks[j]) + try: + lab = fmt[i].format(xticklabel_old) + except: + lab = xticklabel_old + plt.annotate(lab,xy=(xtick_avg, ylim[0]), xycoords='data',xytext=(0, yp[i]), textcoords='offset points',ha="center", va="bottom", size=size[i], rotation=rotation[i]) + + # add the deepest ticklabel + i = 0 + for j,xtl in enumerate(xticklabels[i]): + try: + lab = fmt[i].format(xtl) + except: + lab = xtl + plt.annotate(lab,xy=(xticks[j], ylim[0]), xycoords='data',xytext=(0, yp[i]), textcoords='offset points',ha="center", va="bottom", size=size[i], rotation=rotation[i]) + + # add the final separator line + i = len(xticklabels)-1 + j = len(xticklabels[-1])-1 + plt.annotate('',xy=(xticks[j]+0.5*dxticks[j], ylim[0]), xycoords='data',xytext=(0, yp[i]), textcoords='offset points',arrowprops={'arrowstyle':'-','color':(0.3,0.3,0.3)}) + linepositions.append(j) + + # add the ticklabelnames + xp = 3 + for i,l in enumerate(xticklabelnames): + plt.annotate(l,xy=(xlim[0], ylim[0]), xycoords='data',xytext=(-xp, yp[i]+0.1*size[i]), textcoords='offset points',ha="right", va="bottom", size=size[i]) + + + +def cmapval(v,vmin=0,vmax=1,cmap=None): + """ + Extracts the color belonging to a value or list of values from a colormap + + Parameters + ---------- + v : float, list, tuple + the value or values for which to return the colors + + vmin : float + the value corresponding to the first color in the colormap + + vmax : float + the value corresponding to the last color in the colormap + + cmap : colormap + a matplotlib colormap + + Returns + ------- + hexcolor : string or list of strings + a html representation of the colors correspoinding to the values + + Examples + -------- + >>> import matplotlib.pyplot as plt + >>> v = cmapval(0.6) + '#22a784' + + >>> v = cmapval([1000,2000,3000],vmin=100,vmax=3150,cmap=plt.cm.plasma) + ['#8f0da3', '#e46a5d', '#f7e024'] + + """ + + if cmap is None: + cmap = plt.cm.viridis + + extractvalue = False + if not isinstance(v,(list,tuple)): + v = [v] + extractvalue = True + + color = cmap( np.interp(v,[vmin,vmax],[0.01,0.99]) ) + hexcolor = map(lambda rgb:'#%02x%02x%02x' % (rgb[0]*255,rgb[1]*255,rgb[2]*255),tuple(color[:,0:-1])) + + if extractvalue: + hexcolor = hexcolor[0] + + return hexcolor + + + +def marker(i): + """ + Default cycle of markers + + Parameters + ---------- + i : int + an index when the supplied index is too big, the markers are cycled + + """ + + values = ['^','s','<','o','>','*','v','1'] + return values[np.mod(i,len(values))] \ No newline at end of file diff --git a/plottools/style.py b/plottools/style.py new file mode 100644 index 0000000..03ffe4a --- /dev/null +++ b/plottools/style.py @@ -0,0 +1,140 @@ +#!/usr/bin/env/ python +################################################################################ +# Copyright 2016 Brecht Baeten +# This file is part of plottools. +# +# plottools is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# plottools is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with plottools. If not, see . +################################################################################ + +import matplotlib.pyplot as plt + + +def horizontalgrid(axes=None): + """ + Sets a style with a horizontal grid to the axes + + Parameters + ---------- + axes : matplotlib axes object + the axes to which to apply the style, if omitted the current axis + obtained with plt.gca() it styled + + Examples + -------- + .. plot:: + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> import plottools + >>> plt.plot(np.arange(10),10*np.random.random(10)) + >>> plottools.style.horizontalgrid() + >>> plt.show() + + """ + + if axes == None: + axes = plt.gca() + + # hide the spines except the bottom one + axes.spines['top'].set_visible(False) + # axes.spines['bottom'].set_visible(False) + axes.spines['right'].set_visible(False) + axes.spines['left'].set_visible(False) + + # show ticks only on the left bottom + axes.get_xaxis().tick_bottom() + axes.get_yaxis().tick_left() + + # add horizontal lines + yticks = axes.get_yticks() + xlim = axes.get_xlim() + + for y in yticks: + axes.plot(xlim, [y,y], '-', linewidth=0.3, color='k', alpha=0.3, zorder=-10) + + axes.yaxis.set_tick_params(which='both', bottom='off', top='off', labelbottom='on', left='off', right='off', labelleft='on') + + +def noxticks(axes=None): + """ + Sets a style with no ticks on the x axis + + Parameters + ---------- + axes : matplotlib axes object + the axes to which to apply the style, if omitted the current axis + obtained with plt.gca() it styled + + Examples + -------- + .. plot:: + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> import plottools + >>> plt.plot(np.arange(10),10*np.random.random(10)) + >>> plottools.style.noxticks() + >>> plt.show() + + """ + if axes == None: + axes = plt.gca() + + axes.xaxis.set_tick_params(which='both', bottom='off', top='off', left='off', right='off', labelbottom='on', labelleft='on') + + +def set(*style,**kwargs): + """ + Sets styles of a single axes object + + Parameters + ---------- + style : string or list of strings + style string, if a list is supplied all styles in the list are applied + + axes : matplotlib axes object + the axes to which to apply the style, if omitted the current axis + obtained with plt.gca() it styled + + Examples + -------- + .. plot:: + + >>> import matplotlib.pyplot as plt + >>> import numpy as np + >>> import plottools + >>> plt.plot(np.arange(10),10*np.random.random(10)) + >>> plottools.style.set(['horizontalgrid','noxticks']) + >>> plt.show() + + """ + + if not 'axes' in kwargs: + axes = plt.gca() + else: + axes = kwargs['axes'] + + #if isinstance(style, basestring): + # style = [style] + + for s in style: + if s in ['horizontalgrid','horizontalgridwithoutticks']: + horizontalgrid(axes) + + if s in ['noxticks','horizontalgridwithoutticks']: + noxticks(axes) + + + + \ No newline at end of file diff --git a/setup.py b/setup.py index 2c8f99a..09c3822 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,30 @@ +#!/usr/bin/env/ python from setuptools import setup, find_packages +import os +# retrieve the version +try: + versionfile = os.path.join('plottools','__version__.py') + f = open( versionfile, 'r') + content = f.readline() + splitcontent = content.split('\'') + version = splitcontent[1] + f.close() +except: + raise Exception('Could not determine the version from plottools/__version__.py') + + +# run the setup command setup( name='plottools', - version='0.1.1', - license='GNU GENERAL PUBLIC LICENSE', - description='Plot functions that come in handy', - url='', - author='Brecht Baeten', - author_email='brecht.baeten@gmail.com', - packages=find_packages(), - install_requires=['matplotlib'], - classifiers = ['Programming Language :: Python :: 2.7'], -) \ No newline at end of file + version=version, + license='GPLv3', + description='', + long_description=open(os.path.join(os.path.dirname(__file__), 'README.rst')).read(), + url='https://github.com/BrechtBa/plottools', + author='Brecht Baeten', + author_email='brecht.baeten@gmail.com', + packages=find_packages(), + install_requires=['numpy','matplotlib','colorspacious'], + classifiers=['Programming Language :: Python :: 2.7'], +) diff --git a/tests/all.py b/tests/all.py new file mode 100644 index 0000000..46985ea --- /dev/null +++ b/tests/all.py @@ -0,0 +1,25 @@ +#!/usr/bin/env/ python +################################################################################ +# Copyright 2016 Brecht Baeten +# This file is part of plottools. +# +# plottools is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# plottools is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with plottools. If not, see . +################################################################################ + +import unittest + +from test_plottools import * + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_plottools.py b/tests/test_plottools.py new file mode 100644 index 0000000..5141f8b --- /dev/null +++ b/tests/test_plottools.py @@ -0,0 +1,30 @@ +#!/usr/bin/env/ python +################################################################################ +# Copyright 2016 Brecht Baeten +# This file is part of plottools. +# +# plottools is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# plottools is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with plottools. If not, see . +################################################################################ + +import unittest + +import plottools + +class TestPlottools(unittest.TestCase): + + def test_version(self): + self.assertGreater( len(plottools.__version__), 0 ) + +if __name__ == '__main__': + unittest.main()