import os
|
|
import sys
|
|
import re
|
|
from glob import glob
|
|
import matplotlib as mpl
|
|
from jupyter_core.paths import jupyter_config_dir
|
|
|
|
# path to install (~/.jupyter/custom/)
|
|
jupyter_custom = os.path.join(jupyter_config_dir(), 'custom')
|
|
# path to local site-packages/jupyterthemes
|
|
package_dir = os.path.dirname(os.path.realpath(__file__))
|
|
# theme colors, layout, and font directories
|
|
styles_dir = os.path.join(package_dir, 'styles')
|
|
# text file containing name of currently installed theme
|
|
theme_name_file = os.path.join(jupyter_custom, 'current_theme.txt')
|
|
|
|
|
|
# base style params
|
|
base_style = {
|
|
'axes.axisbelow': True,
|
|
'figure.autolayout': True,
|
|
'grid.linestyle': u'-',
|
|
'lines.solid_capstyle': u'round',
|
|
'legend.frameon': False,
|
|
"legend.numpoints": 1,
|
|
"legend.scatterpoints": 1}
|
|
|
|
# base context params
|
|
base_context = {
|
|
'axes.linewidth': 1.4,
|
|
"grid.linewidth": 1.4,
|
|
"lines.linewidth": 1.5,
|
|
"patch.linewidth": .2,
|
|
"lines.markersize": 7,
|
|
"lines.markeredgewidth": 0,
|
|
"xtick.major.width": 1,
|
|
"ytick.major.width": 1,
|
|
"xtick.minor.width": .5,
|
|
"ytick.minor.width": .5,
|
|
"xtick.major.pad": 7,
|
|
"ytick.major.pad": 7,
|
|
"xtick.major.size": 0,
|
|
"ytick.major.size": 0,
|
|
"xtick.minor.size": 0,
|
|
"ytick.minor.size": 0}
|
|
|
|
# base font params
|
|
base_font = {
|
|
"font.size": 11,
|
|
"axes.labelsize": 12,
|
|
"axes.titlesize": 12,
|
|
"xtick.labelsize": 10.5,
|
|
"ytick.labelsize": 10.5,
|
|
"legend.fontsize": 10.5}
|
|
|
|
|
|
def remove_non_colors(clist):
|
|
checkHex = r'^#(?:[0-9a-fA-F]{3}){1,2}$'
|
|
return [clr for clr in clist if re.search(checkHex, clr)]
|
|
|
|
|
|
def infer_theme():
|
|
""" checks jupyter_config_dir() for text file containing theme name
|
|
(updated whenever user installs a new theme)
|
|
"""
|
|
themes = [os.path.basename(theme).replace('.less', '')
|
|
for theme in glob('{0}/*.less'.format(styles_dir))]
|
|
if os.path.exists(theme_name_file):
|
|
with open(theme_name_file) as f:
|
|
theme = f.readlines()[0]
|
|
if theme not in themes:
|
|
theme = 'default'
|
|
else:
|
|
theme = 'default'
|
|
|
|
return theme
|
|
|
|
|
|
def style(theme=None, context='paper', grid=True, gridlines=u'-', ticks=False, spines=True, fscale=1.2, figsize=(8., 7.)):
|
|
"""
|
|
main function for styling matplotlib according to theme
|
|
|
|
::Arguments::
|
|
theme (str): 'oceans16', 'grade3', 'chesterish', 'onedork', 'monokai', 'solarizedl', 'solarizedd'. If no theme name supplied the currently installed notebook theme will be used.
|
|
|
|
context (str): 'paper' (Default), 'notebook', 'talk', or 'poster'
|
|
|
|
grid (bool): removes axis grid lines if False
|
|
|
|
gridlines (str): set grid linestyle (e.g., '--' for dashed grid)
|
|
|
|
ticks (bool): make major x and y ticks visible
|
|
|
|
spines (bool): removes x (bottom) and y (left) axis spines if False
|
|
|
|
fscale (float): scale font size for axes labels, legend, etc.
|
|
|
|
figsize (tuple): default figure size of matplotlib figures
|
|
"""
|
|
|
|
# set context and font rc parameters, return rcdict
|
|
rcdict = set_context(context=context, fscale=fscale, figsize=figsize)
|
|
|
|
# read in theme name from ~/.jupyter/custom/current_theme.txt
|
|
if theme is None:
|
|
theme = infer_theme()
|
|
|
|
# combine context & font rcparams with theme style
|
|
set_style(rcdict, theme=theme, grid=grid, gridlines=gridlines, ticks=ticks, spines=spines)
|
|
|
|
|
|
|
|
def set_style(rcdict, theme=None, grid=True, gridlines=u'-', ticks=False, spines=True):
|
|
"""
|
|
This code has been modified from seaborn.rcmod.set_style()
|
|
::Arguments::
|
|
rcdict (str): dict of "context" properties (filled by set_context())
|
|
theme (str): name of theme to use when setting color properties
|
|
grid (bool): turns off axis grid if False (default: True)
|
|
ticks (bool): removes x,y axis ticks if True (default: False)
|
|
spines (bool): removes axis spines if False (default: True)
|
|
"""
|
|
|
|
# extract style and color info for theme
|
|
styleMap, clist = get_theme_style(theme)
|
|
|
|
# extract style variables
|
|
figureFace = styleMap['figureFace']
|
|
axisFace = styleMap['axisFace']
|
|
textColor = styleMap['textColor']
|
|
edgeColor = styleMap['edgeColor']
|
|
gridColor = styleMap['gridColor']
|
|
|
|
if not spines:
|
|
edgeColor = 'none'
|
|
|
|
style_dict = {
|
|
'figure.edgecolor': figureFace,
|
|
'figure.facecolor': figureFace,
|
|
'axes.facecolor': axisFace,
|
|
'axes.edgecolor': edgeColor,
|
|
'axes.labelcolor': textColor,
|
|
'axes.grid': grid,
|
|
'grid.linestyle': gridlines,
|
|
'grid.color': gridColor,
|
|
'text.color': textColor,
|
|
'xtick.color': textColor,
|
|
'ytick.color': textColor,
|
|
'patch.edgecolor': axisFace,
|
|
'patch.facecolor': gridColor,
|
|
'savefig.facecolor': figureFace,
|
|
'savefig.edgecolor': figureFace}
|
|
|
|
# update rcdict with style params
|
|
rcdict.update(style_dict)
|
|
|
|
# Show or hide the axes ticks
|
|
if ticks:
|
|
rcdict.update({
|
|
"xtick.major.size": 6,
|
|
"ytick.major.size": 6,
|
|
"xtick.minor.size": 3,
|
|
"ytick.minor.size": 3})
|
|
|
|
base_style.update(rcdict)
|
|
|
|
# update matplotlib with rcdict (incl. context, font, & style)
|
|
mpl.rcParams.update(rcdict)
|
|
|
|
# update seaborn with rcdict (incl. context, font, & style)
|
|
try:
|
|
import seaborn as sns
|
|
sns.set_style(rc=rcdict)
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
from cycler import cycler
|
|
# set color cycle to jt-style color list
|
|
mpl.rcParams['axes.prop_cycle'] = cycler(color=clist)
|
|
except Exception:
|
|
pass
|
|
|
|
# replace default blue, green, etc. with jt colors
|
|
for code, color in zip("bgrmyck", clist[:7]):
|
|
rgb = mpl.colors.colorConverter.to_rgb(color)
|
|
mpl.colors.colorConverter.colors[code] = rgb
|
|
mpl.colors.colorConverter.cache[code] = rgb
|
|
|
|
|
|
def set_context(context='paper', fscale=1., figsize=(8., 7.)):
|
|
"""
|
|
Most of this code has been copied/modified from seaborn.rcmod.plotting_context()
|
|
::Arguments::
|
|
context (str): 'paper', 'notebook', 'talk', or 'poster'
|
|
fscale (float): font-size scalar applied to axes ticks, legend, labels, etc.
|
|
"""
|
|
# scale all the parameters by the same factor depending on the context
|
|
scaling = dict(paper=.8, notebook=1, talk=1.3, poster=1.6)[context]
|
|
context_dict = {k: v * scaling for k, v in base_context.items()}
|
|
|
|
# scale default figsize
|
|
figX, figY = figsize
|
|
context_dict["figure.figsize"] = (figX*scaling, figY*scaling)
|
|
|
|
# independently scale the fonts
|
|
font_dict = {k: v * fscale for k, v in base_font.items()}
|
|
font_dict["font.family"] = ["sans-serif"]
|
|
font_dict["font.sans-serif"] = ["Helvetica", "Helvetica Neue", "Arial",
|
|
"DejaVu Sans", "Liberation Sans", "sans-serif"]
|
|
context_dict.update(font_dict)
|
|
return context_dict
|
|
|
|
|
|
def figsize(x=8, y=7., aspect=1.):
|
|
""" manually set the default figure size of plots
|
|
::Arguments::
|
|
x (float): x-axis size
|
|
y (float): y-axis size
|
|
aspect (float): aspect ratio scalar
|
|
"""
|
|
# update rcparams with adjusted figsize params
|
|
mpl.rcParams.update({'figure.figsize': (x*aspect, y)})
|
|
|
|
|
|
def get_theme_style(theme):
|
|
"""
|
|
read-in theme style info and populate styleMap (dict of with mpl.rcParams)
|
|
and clist (list of hex codes passed to color cylcler)
|
|
::Arguments::
|
|
theme (str): theme name
|
|
::Returns::
|
|
styleMap (dict): dict containing theme-specific colors for figure properties
|
|
clist (list): list of colors to replace mpl's default color_cycle
|
|
"""
|
|
styleMap, clist = get_default_jtstyle()
|
|
if theme == 'default':
|
|
return styleMap, clist
|
|
|
|
syntaxVars = ['@yellow:', '@orange:', '@red:', '@magenta:', '@violet:', '@blue:', '@cyan:', '@green:']
|
|
|
|
get_hex_code = lambda line: line.split(':')[-1].split(';')[0][-7:]
|
|
|
|
themeFile = os.path.join(styles_dir, theme+'.less')
|
|
with open(themeFile) as f:
|
|
for line in f:
|
|
for k, v in styleMap.items():
|
|
if k in line.strip():
|
|
styleMap[k] = get_hex_code(line)
|
|
for c in syntaxVars:
|
|
if c in line.strip():
|
|
syntaxVars[syntaxVars.index(c)] = get_hex_code(line)
|
|
|
|
# remove duplicate hexcolors
|
|
syntaxVars = list(set(syntaxVars))
|
|
clist.extend(syntaxVars)
|
|
clist = remove_non_colors(clist)
|
|
return styleMap, clist
|
|
|
|
|
|
def get_default_jtstyle():
|
|
styleMap = {'axisFace': 'white',
|
|
'figureFace': 'white',
|
|
'textColor': '.15',
|
|
'edgeColor': '.8',
|
|
'gridColor': '.8'}
|
|
return styleMap, get_color_list()
|
|
|
|
|
|
def get_color_list():
|
|
return ['#3572C6', '#83a83b', '#c44e52', '#8172b2', "#ff914d",
|
|
"#77BEDB", "#222222", "#4168B7", "#27ae60", "#e74c3c",'#bc89e0',
|
|
"#ff711a", "#3498db", '#6C7A89']
|
|
|
|
|
|
def reset():
|
|
""" full reset of matplotlib default style and colors
|
|
"""
|
|
colors = [(0., 0., 1.), (0., .5, 0.), (1., 0., 0.), (.75, .75, 0.),
|
|
(.75, .75, 0.), (0., .75, .75), (0., 0., 0.)]
|
|
for code, color in zip("bgrmyck", colors):
|
|
rgb = mpl.colors.colorConverter.to_rgb(color)
|
|
mpl.colors.colorConverter.colors[code] = rgb
|
|
mpl.colors.colorConverter.cache[code] = rgb
|
|
mpl.rcParams.update(mpl.rcParamsDefault)
|
|
mpl.rcParams['figure.facecolor'] = 'white'
|
|
mpl.rcParams['axes.facecolor'] = 'white'
|