Source code for geoml.plots.style

# geoML - machine learning models for geospatial data
# Copyright (C) 2021  Ítalo Gomes Gonçalves
#
# This program 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.
#
# This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
"""
One place defining what a geoML figure looks like.

Every function in this sub-package draws inside `context()`, a
`matplotlib.rc_context`, so the settings below apply to the figure being drawn
and to nothing else: importing geoML does not change how anyone else's plots
look. Call `use()` to take them globally, if that is what you want.

`PALETTE` is what tells one category from another, and it is the same list
wherever a category is drawn, so a rock type keeps its colour from a histogram
to a map to the dashboard. Change it here and every figure follows.
"""
import matplotlib as _mpl


PALETTE = [
    "#1f6f8b",   # teal blue
    "#e0761f",   # orange
    "#1e7b45",   # green
    "#9b2226",   # dark red
    "#5f5aa2",   # violet
    "#b58b00",   # ochre
    "#00a5a8",   # cyan
    "#6c757d",   # slate
]

# For a continuous value, where the order of the colours has to mean something.
# `cividis` reads the same to colour-vision-deficient eyes, and both its ends
# stay visible against a white background.
SEQUENTIAL = "cividis"

# what a measured but unassigned location looks like
MISSING_COLOR = "#c9c9c9"

# behind a label that has to be read over the data it sits on, such as the
# loading arrows in a biplot: enough to lift the text off the points without
# hiding them
LABEL_BOX = {
    "boxstyle": "round,pad=0.25",
    "facecolor": "white",
    "edgecolor": "black",
    "linewidth": 0.6,
    "alpha": 0.5,
}

RC = {
    "figure.dpi": 110,
    "figure.facecolor": "white",
    "savefig.bbox": "tight",
    "savefig.facecolor": "white",

    "font.family": "sans-serif",
    "font.size": 9,

    "axes.prop_cycle": _mpl.cycler(color=PALETTE),
    "axes.facecolor": "white",
    "axes.edgecolor": "#4a4a4a",
    "axes.linewidth": 0.8,
    "axes.spines.top": False,
    "axes.spines.right": False,
    "axes.labelsize": 9,
    "axes.labelcolor": "#2b2b2b",
    "axes.titlesize": 10,
    "axes.titleweight": "semibold",
    "axes.titlelocation": "left",
    "axes.titlepad": 6,
    "axes.grid": True,
    "axes.axisbelow": True,          # data over the grid, never under it

    "grid.color": "#d9d9d9",
    "grid.linewidth": 0.6,

    "xtick.color": "#4a4a4a",
    "ytick.color": "#4a4a4a",
    "xtick.labelsize": 8,
    "ytick.labelsize": 8,
    "xtick.direction": "out",
    "ytick.direction": "out",

    "legend.frameon": False,
    "legend.fontsize": 8,

    "lines.linewidth": 1.6,
    "lines.solid_capstyle": "round",

    "image.cmap": SEQUENTIAL,
}


# The same figure, for plotly. A plain dict rather than a `go.layout.Template`,
# so that reading the package's colours never costs an import of plotly -- the
# same reason `geoml.plotly` builds dicts and imports nothing. Plotly accepts a
# dict wherever it accepts a template.
TEMPLATE = {
    "layout": {
        "colorway": PALETTE,
        "paper_bgcolor": "white",
        "plot_bgcolor": "white",
        "font": {"family": "sans-serif", "size": 11, "color": "#2b2b2b"},
        "title": {"x": 0.0, "xanchor": "left",
                  "font": {"size": 14, "color": "#2b2b2b"}},
        "xaxis": {"gridcolor": "#d9d9d9", "gridwidth": 0.6,
                  "linecolor": "#4a4a4a", "linewidth": 0.8,
                  "showline": True, "zeroline": False,
                  "ticks": "outside", "tickcolor": "#4a4a4a",
                  "tickfont": {"size": 9}, "automargin": True},
        "yaxis": {"gridcolor": "#d9d9d9", "gridwidth": 0.6,
                  "linecolor": "#4a4a4a", "linewidth": 0.8,
                  "showline": True, "zeroline": False,
                  "ticks": "outside", "tickcolor": "#4a4a4a",
                  "tickfont": {"size": 9}, "automargin": True},
        "colorscale": {"sequential": SEQUENTIAL},
        "legend": {"font": {"size": 10}, "bgcolor": "rgba(255,255,255,0.7)",
                   "borderwidth": 0},
        "hoverlabel": {"font": {"size": 11}},
        "margin": {"l": 60, "r": 30, "t": 60, "b": 50},
    }
}


[docs] def context(): """The geoML settings, for the figure being drawn and nothing else.""" return _mpl.rc_context(RC)
[docs] def use(): """Takes the geoML settings globally, for every figure from here on.""" _mpl.rcParams.update(RC)
[docs] def color(index): """The colour of the `index`-th category, cycling when there are many.""" return PALETTE[index % len(PALETTE)]