afcharts 1.0.0a0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2025 Crown Copyright
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: afcharts
3
+ Version: 1.0.0a0
4
+ Summary: A python package to produce charts following UK Government Analysis Function guidance
5
+ Author: Crown Copyright
6
+ Maintainer-email: Nick Henden <Nick.Henden@ukhsa.gov.uk>, Tom Farley <tom.farley@ukhsa.gov.uk>, Stelios Georgiou <Stelios.Georgiou@ukhsa.gov.uk>, Frederico Rodrigues <fred.rodrigues@defra.gov.uk>, Peter Brohan <peter.brohan@communities.gov.uk>, Fahim Hossain <fahim.hossain@ukhsa.gov.uk>
7
+ Project-URL: Homepage, https://github.com/best-practice-and-impact/afcharts-py
8
+ Project-URL: Documentation, https://best-practice-and-impact.github.io/afcharts-py/
9
+ Project-URL: Source, https://github.com/best-practice-and-impact/afcharts-py
10
+ Project-URL: Issue Tracker, https://github.com/best-practice-and-impact/afcharts-py/issues
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE.md
18
+ Requires-Dist: matplotlib>=3.10.3
19
+ Requires-Dist: plotly>=6.1.2
20
+ Requires-Dist: typeguard>=4.4.4
21
+ Requires-Dist: PyYAML>=5.0
22
+ Dynamic: license-file
23
+
24
+
25
+ # afcharts-py <img src="docs/images/logo.svg" alt="afcharts logo" align="right" height="150"/>
26
+
27
+ ## Overview
28
+
29
+ The afcharts python package helps make accessible Matplotlib and Plotly charts following [Government Analysis Function Data Visualisation guidance](https://analysisfunction.civilservice.gov.uk/policy-store/data-visualisation-charts/):
30
+
31
+ - **Automatic chart formatting** with pre-built style sheets for Matplotlib and Plotly
32
+ - **Chart colours** from the Analysis Function [accessible colour palettes](https://analysisfunction.civilservice.gov.uk/policy-store/data-visualisation-colours-in-charts/#section-4)
33
+ - **Example code** for common chart types in the [cookbook](https://best-practice-and-impact.github.io/afcharts-py/)
34
+
35
+ Looking for the R version? Check out the [afcharts R package](https://best-practice-and-impact.github.io/afcharts/).
36
+
37
+ <img src="docs/images/example_charts/bar_chart-matplotlib_afcharts.png" width="28.1%" alt="A grouped bar chart in afcharts style showing life expectancy in 1967 and 2007 for four countries. Bars use Analysis Function palette: dark blue for 1967, orange for 2007."/> <img src="docs/images/example_charts/scatterplot-matplotlib_afcharts.png" width="30%" alt="A scatterplot in afcharts style showing life expectancy against GDP per capita for 142 countries in 2007."/> <img src="docs/images/example_charts/line_chart-matplotlib_afcharts.png" width="37.5%" alt="A line chart in afcharts style showing life expectancy in China and the UK from 1952 to 2007"/>
38
+
39
+ ## Installation
40
+
41
+ afcharts is available at the Python Package Index (PyPI):
42
+ ```bash
43
+ pip install afcharts
44
+ ```
45
+ or see the [alternative installation](https://best-practice-and-impact.github.io/afcharts-py/getting-started.html) instructions.
46
+
47
+ ## Usage
48
+
49
+ Format any Matplotlib or Plotly chart in the Analysis Function style using the built-in style sheets.
50
+
51
+ See the [Getting Started](https://best-practice-and-impact.github.io/afcharts-py/getting-started.html) guide for more options and the [cookbook](https://best-practice-and-impact.github.io/afcharts-py/) for extensive examples.
52
+
53
+ ### Matplotlib
54
+
55
+ ```python
56
+ import matplotlib.pyplot as plt
57
+
58
+ # Apply the afcharts style to all Matplotlib plots
59
+ plt.style.use('afcharts.afcharts')
60
+ ```
61
+
62
+ Example: A [Matplotlib bar chart](https://best-practice-and-impact.github.io/afcharts-py/01-matplotlib-usage.html#grouped-bar-chart) with afcharts (left) and without (right)
63
+
64
+ <img src="docs/images/example_charts/bar_chart-matplotlib_afcharts.png" width="35%" alt="Grouped bar chart (afcharts style) showing life expectancy in 1967 and 2007 for four countries. Bars use Analysis Function palette: dark blue for 1967, orange for 2007."/> <img src="docs/images/example_charts/bar_chart-matplotlib_default.png" width="34.7%" alt="Grouped bar chart (default Matplotlib) showing life expectancy in 1967 and 2007 for four countries. Bars: blue for 1967, orange for 2007."/>
65
+
66
+ ### Plotly
67
+
68
+ ```python
69
+ from afcharts.pio_template import pio
70
+
71
+ # Apply the afcharts style to all Plotly plots
72
+ pio.templates.default = "afcharts"
73
+ ```
74
+
75
+ Example: A [Plotly bar chart](https://best-practice-and-impact.github.io/afcharts-py/03-plotly-usage.html#grouped-bar-chart) with afcharts (left) and without (right)
76
+
77
+ <img src="docs/images/example_charts/bar_chart-plotly_afcharts.png" width="35%" alt="Grouped bar chart (afcharts style) showing life expectancy in 1967 and 2007 for four countries. Bars use Analysis Function palette: dark blue for 1967, orange for 2007."/> <img src="docs/images/example_charts/bar_chart-plotly_default.png" width="35%" alt="Grouped bar chart (default Plotly) showing life expectancy in 1967 and 2007 for four countries. Bars: blue for 1967, red for 2007."/>
78
+
79
+ ### Colours
80
+
81
+ Easily return a list of colours from any of the Analysis Function [accessible colour palettes](https://analysisfunction.civilservice.gov.uk/policy-store/data-visualisation-colours-in-charts/#section-4) with the `get_af_colours()` function:
82
+
83
+ ```python
84
+ from afcharts.af_colours import get_af_colours
85
+
86
+ # Get the duo colour palette hex codes
87
+ duo = get_af_colours("duo")
88
+ ```
89
+
90
+ ## Getting help
91
+ If you encounter a bug, please file a minimal reproducible example on [Github Issues](https://github.com/best-practice-and-impact/afcharts-py/issues). For questions and other discussion, please start a [discussion](https://github.com/best-practice-and-impact/afcharts-py/discussions).
92
+
93
+ ## Contributing
94
+ Interested in contributing? Check out the [contributing guidelines](CONTRIBUTING.md).
95
+
96
+ ## Acknowledgments
97
+ The afcharts python package is based on the
98
+ [afcharts](https://github.com/best-practice-and-impact/afcharts.git) R package and the [py-af-colours](https://github.com/best-practice-and-impact/py-af-colours) package.
99
+
100
+ ## License
101
+ Unless stated otherwise, the codebase is released under [the MIT License](LICENSE.md). This covers both the codebase and any sample code in the documentation.
102
+
103
+ The documentation is [© Crown copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/) and available under the terms of the [Open Government 3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/) licence.
@@ -0,0 +1,80 @@
1
+
2
+ # afcharts-py <img src="docs/images/logo.svg" alt="afcharts logo" align="right" height="150"/>
3
+
4
+ ## Overview
5
+
6
+ The afcharts python package helps make accessible Matplotlib and Plotly charts following [Government Analysis Function Data Visualisation guidance](https://analysisfunction.civilservice.gov.uk/policy-store/data-visualisation-charts/):
7
+
8
+ - **Automatic chart formatting** with pre-built style sheets for Matplotlib and Plotly
9
+ - **Chart colours** from the Analysis Function [accessible colour palettes](https://analysisfunction.civilservice.gov.uk/policy-store/data-visualisation-colours-in-charts/#section-4)
10
+ - **Example code** for common chart types in the [cookbook](https://best-practice-and-impact.github.io/afcharts-py/)
11
+
12
+ Looking for the R version? Check out the [afcharts R package](https://best-practice-and-impact.github.io/afcharts/).
13
+
14
+ <img src="docs/images/example_charts/bar_chart-matplotlib_afcharts.png" width="28.1%" alt="A grouped bar chart in afcharts style showing life expectancy in 1967 and 2007 for four countries. Bars use Analysis Function palette: dark blue for 1967, orange for 2007."/> <img src="docs/images/example_charts/scatterplot-matplotlib_afcharts.png" width="30%" alt="A scatterplot in afcharts style showing life expectancy against GDP per capita for 142 countries in 2007."/> <img src="docs/images/example_charts/line_chart-matplotlib_afcharts.png" width="37.5%" alt="A line chart in afcharts style showing life expectancy in China and the UK from 1952 to 2007"/>
15
+
16
+ ## Installation
17
+
18
+ afcharts is available at the Python Package Index (PyPI):
19
+ ```bash
20
+ pip install afcharts
21
+ ```
22
+ or see the [alternative installation](https://best-practice-and-impact.github.io/afcharts-py/getting-started.html) instructions.
23
+
24
+ ## Usage
25
+
26
+ Format any Matplotlib or Plotly chart in the Analysis Function style using the built-in style sheets.
27
+
28
+ See the [Getting Started](https://best-practice-and-impact.github.io/afcharts-py/getting-started.html) guide for more options and the [cookbook](https://best-practice-and-impact.github.io/afcharts-py/) for extensive examples.
29
+
30
+ ### Matplotlib
31
+
32
+ ```python
33
+ import matplotlib.pyplot as plt
34
+
35
+ # Apply the afcharts style to all Matplotlib plots
36
+ plt.style.use('afcharts.afcharts')
37
+ ```
38
+
39
+ Example: A [Matplotlib bar chart](https://best-practice-and-impact.github.io/afcharts-py/01-matplotlib-usage.html#grouped-bar-chart) with afcharts (left) and without (right)
40
+
41
+ <img src="docs/images/example_charts/bar_chart-matplotlib_afcharts.png" width="35%" alt="Grouped bar chart (afcharts style) showing life expectancy in 1967 and 2007 for four countries. Bars use Analysis Function palette: dark blue for 1967, orange for 2007."/> <img src="docs/images/example_charts/bar_chart-matplotlib_default.png" width="34.7%" alt="Grouped bar chart (default Matplotlib) showing life expectancy in 1967 and 2007 for four countries. Bars: blue for 1967, orange for 2007."/>
42
+
43
+ ### Plotly
44
+
45
+ ```python
46
+ from afcharts.pio_template import pio
47
+
48
+ # Apply the afcharts style to all Plotly plots
49
+ pio.templates.default = "afcharts"
50
+ ```
51
+
52
+ Example: A [Plotly bar chart](https://best-practice-and-impact.github.io/afcharts-py/03-plotly-usage.html#grouped-bar-chart) with afcharts (left) and without (right)
53
+
54
+ <img src="docs/images/example_charts/bar_chart-plotly_afcharts.png" width="35%" alt="Grouped bar chart (afcharts style) showing life expectancy in 1967 and 2007 for four countries. Bars use Analysis Function palette: dark blue for 1967, orange for 2007."/> <img src="docs/images/example_charts/bar_chart-plotly_default.png" width="35%" alt="Grouped bar chart (default Plotly) showing life expectancy in 1967 and 2007 for four countries. Bars: blue for 1967, red for 2007."/>
55
+
56
+ ### Colours
57
+
58
+ Easily return a list of colours from any of the Analysis Function [accessible colour palettes](https://analysisfunction.civilservice.gov.uk/policy-store/data-visualisation-colours-in-charts/#section-4) with the `get_af_colours()` function:
59
+
60
+ ```python
61
+ from afcharts.af_colours import get_af_colours
62
+
63
+ # Get the duo colour palette hex codes
64
+ duo = get_af_colours("duo")
65
+ ```
66
+
67
+ ## Getting help
68
+ If you encounter a bug, please file a minimal reproducible example on [Github Issues](https://github.com/best-practice-and-impact/afcharts-py/issues). For questions and other discussion, please start a [discussion](https://github.com/best-practice-and-impact/afcharts-py/discussions).
69
+
70
+ ## Contributing
71
+ Interested in contributing? Check out the [contributing guidelines](CONTRIBUTING.md).
72
+
73
+ ## Acknowledgments
74
+ The afcharts python package is based on the
75
+ [afcharts](https://github.com/best-practice-and-impact/afcharts.git) R package and the [py-af-colours](https://github.com/best-practice-and-impact/py-af-colours) package.
76
+
77
+ ## License
78
+ Unless stated otherwise, the codebase is released under [the MIT License](LICENSE.md). This covers both the codebase and any sample code in the documentation.
79
+
80
+ The documentation is [© Crown copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/) and available under the terms of the [Open Government 3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/) licence.
@@ -0,0 +1,103 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "afcharts"
7
+ version = "1.0.0-alpha"
8
+ description = "A python package to produce charts following UK Government Analysis Function guidance"
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ authors = [{ name = "Crown Copyright" }]
12
+ maintainers = [
13
+ { name = "Nick Henden", email = "Nick.Henden@ukhsa.gov.uk" },
14
+ { name = "Tom Farley", email = "tom.farley@ukhsa.gov.uk" },
15
+ { name = "Stelios Georgiou", email = "Stelios.Georgiou@ukhsa.gov.uk" },
16
+ { name = "Frederico Rodrigues", email = "fred.rodrigues@defra.gov.uk" },
17
+ { name = "Peter Brohan", email = "peter.brohan@communities.gov.uk"},
18
+ { name = "Fahim Hossain", email = "fahim.hossain@ukhsa.gov.uk"}
19
+ # Other contributors, please add your details here
20
+ ]
21
+ classifiers = [
22
+ "Programming Language :: Python :: 3",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Operating System :: OS Independent",
25
+ "Development Status :: 3 - Alpha",
26
+ ]
27
+ requires-python = ">=3.10"
28
+
29
+ dependencies = [ # install package dependencies with $ uv sync
30
+ "matplotlib>=3.10.3",
31
+ "plotly>=6.1.2",
32
+ "typeguard>=4.4.4",
33
+ 'PyYAML >= 5.0',
34
+ ]
35
+
36
+ [dependency-groups]
37
+ dev = [ # package developer dependencies are installed by default with $ uv sync
38
+ "ipython>=8.26.0", # Installing ipython will enable colourised console output/tracebacks
39
+ "mypy>=1.15.0", # Mypy static type checker/linter
40
+ "types-PyYAML", # type stub package used by mypy to check code that uses PyYAML
41
+ "pre-commit>=3.8.0", # Used to run pre-commit hook linting and formatting checks
42
+ "ruff>=0.6.2", # Python linting/formatting tool combines black, flake8 etc. Run `ruff check --fix`/`ruff format`
43
+ "pandas>=2.2.2", # Used to manipulate tabular data
44
+ "afcharts", # Install current package in developer mode as workspace package
45
+ "ipykernel", # For rendering Quarto
46
+ "nbclient", # For rendering Quarto
47
+ ]
48
+ test = [ # package testing dependencies are installed by default with $ uv sync
49
+ "pandas>=2.2.2", # Used to manipulate tabular data
50
+ "pytest>=8.2.2", # Unit/regression testing etc.
51
+ "pytest-cov>=5.0.0", # Test code coverage
52
+ "pytest-mock", # Mock wrapper for unit tests
53
+ "pytest-sugar>=1.0.0", # Prettier pytest terminal output
54
+ ]
55
+
56
+ [project.urls]
57
+ "Homepage" = "https://github.com/best-practice-and-impact/afcharts-py"
58
+ "Documentation" = "https://best-practice-and-impact.github.io/afcharts-py/"
59
+ "Source" = "https://github.com/best-practice-and-impact/afcharts-py"
60
+ "Issue Tracker" = "https://github.com/best-practice-and-impact/afcharts-py/issues"
61
+
62
+ [tool.uv]
63
+ default-groups = [
64
+ "dev",
65
+ "test",
66
+ ] # `uv sync` will also install dev and test dependencies by default
67
+
68
+ [tool.uv.sources]
69
+ afcharts = { workspace = true } # Install as editable package (developer mode like pip install -e .)
70
+
71
+ [tool.setuptools]
72
+ package-dir = { "" = "src" } # Maps package names to their actual file paths.
73
+
74
+ [tool.setuptools.packages.find]
75
+ where = ["src"] # Packages are located in src/ top level directory
76
+ exclude = ["docs*", "Documentation*", "build"]
77
+
78
+ [tool.setuptools.package-data]
79
+ afcharts = ["*.mplstyle", "config/af_colours.yaml"]
80
+
81
+ [tool.ruff]
82
+ line-length = 120
83
+ target-version = "py313"
84
+ lint.select = [
85
+ "E", # pycodestyle
86
+ "W", # pycodestyle
87
+ "F", # pyflakes
88
+ "I", # isort
89
+ "B", # flake8-bugbear
90
+ "C90", # mccabe cyclomatic complexity
91
+ "G", # flake8-logging-format
92
+ ]
93
+ exclude = ["migrations/versions/"]
94
+ lint.ignore = []
95
+
96
+ [tool.ruff.lint.pydocstyle]
97
+ convention = "google" # Accepts one of {google,numpy,pep257}
98
+
99
+ [tool.mypy] # Config for mypy static type checking
100
+ python_version = "3.13"
101
+ check_untyped_defs = true
102
+ ignore_missing_imports = true
103
+ warn_unused_ignores = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,259 @@
1
+ # Government Analysis Function (AF) colours and palettes
2
+ # Source: https://analysisfunction.civilservice.gov.uk/policy-store/
3
+ # data-visualisation-colours-in-charts/
4
+ # Py-af-colours source: https://github.com/best-practice-and-impact/py-af-colours
5
+
6
+ from pathlib import Path
7
+
8
+ import yaml
9
+
10
+
11
+ def get_af_colours(palette: str, colour_format="hex", number_of_colours=6, config_path=None):
12
+ """
13
+ get_af_colours() is the top level function in af_colours. This returns
14
+ the chosen Analysis Function colour palette in hex or rgb format.
15
+ For the categorical palette, this can be a chosen number of colours
16
+ up to 6.
17
+
18
+ Parameters
19
+ ----------
20
+ palette : string
21
+ Type of palette required, with accepted values of "duo",
22
+ "focus", "categorical", and "sequential".
23
+
24
+ colour_format : string, optional
25
+ Colour format required, with accepted values of "hex" or "rgb".
26
+
27
+ number_of_colours : int, optional
28
+ Number of colours required (categorical palette only). Takes
29
+ values between 2 and 6. Returns 2 colours by default. If a
30
+ palette other than categorical is chosen, any value passed
31
+ is ignored.
32
+
33
+ config_path : NoneType, optional
34
+ Takes the default value None, inside the function this is
35
+ mapped to the relative path independent of operating system.
36
+ Should not require changing.
37
+
38
+ Raises
39
+ ------
40
+ ValueError
41
+ If palette is not "categorical", "duo", "sequential", or "focus".
42
+ Or if colour_format is not "hex" or "rgb".
43
+
44
+ Returns
45
+ -------
46
+ list
47
+ chosen_colours_list
48
+
49
+ """
50
+ if config_path is None:
51
+ parent_dir = Path(__file__).parent
52
+ config_path = parent_dir.joinpath("config", "af_colours.yaml")
53
+
54
+ with open(config_path) as file:
55
+ config = yaml.load(file, Loader=yaml.BaseLoader)
56
+
57
+ categorical_hex_list = config["categorical_hex_list"]
58
+ duo_hex_list = config["duo_hex_list"]
59
+ sequential_hex_list = config["sequential_hex_list"]
60
+ focus_hex_list = config["focus_hex_list"]
61
+
62
+ if palette not in ["categorical", "duo", "sequential", "focus"]:
63
+ raise ValueError("palette must be one of 'categorical', 'duo', 'sequential' " + f"or 'focus', not {palette}.")
64
+ if colour_format not in ["hex", "rgb"]:
65
+ raise ValueError(f"colour_format must be 'hex' or 'rgb', not {colour_format}.")
66
+
67
+ if number_of_colours < 1:
68
+ raise ValueError("number_of_colours must be greater than 0.")
69
+
70
+ elif palette == "sequential":
71
+ chosen_colours_list = sequential_colours(sequential_hex_list, colour_format)
72
+
73
+ elif palette == "focus":
74
+ chosen_colours_list = focus_colours(focus_hex_list, colour_format)
75
+
76
+ elif palette == "duo":
77
+ chosen_colours_list = duo_colours(duo_hex_list, colour_format)
78
+
79
+ elif palette == "categorical":
80
+ chosen_colours_list = categorical_colours(categorical_hex_list, duo_hex_list, colour_format, number_of_colours)
81
+
82
+ return chosen_colours_list
83
+
84
+
85
+ def categorical_colours(categorical_hex_list, duo_hex_list, colour_format="hex", number_of_colours=2):
86
+ """
87
+ Return the Analysis Function categorical colour palette as a list
88
+ in hex or rgb format for up to 6 colours. If number_of_colours is
89
+ 2, the function returns the duo palette.
90
+
91
+ Parameters
92
+ ----------
93
+ categorical_hex_list : list
94
+ List of categorical colours as a hex list, stored in the config.
95
+
96
+ duo_hex_list : list
97
+ List of duo hex codes, stored in the config. This is needed for the
98
+ case of number_of_colours = 2.
99
+
100
+ colour_format : string
101
+ Colour format required, with accepted values of "hex" or "rgb".
102
+
103
+ number_of_colours : int
104
+ Number of colours required, with accepted values between 2 and 6
105
+ inclusive. Returns 2 colours if no value given.
106
+
107
+ Raises
108
+ ------
109
+ ValueError
110
+ If number_of_colours is greater than 6.
111
+
112
+ Returns
113
+ -------
114
+ list
115
+ categorical_colours_list
116
+
117
+ """
118
+
119
+ if number_of_colours > 6:
120
+ raise ValueError("number_of_colours must not be more than 6 for the categorical palette.")
121
+
122
+ if number_of_colours == 2:
123
+ categorical_colours_list = duo_colours(duo_hex_list, colour_format)
124
+ return categorical_colours_list
125
+
126
+ elif colour_format == "hex":
127
+ full_categorical_colours_list = categorical_hex_list
128
+
129
+ elif colour_format == "rgb":
130
+ full_categorical_colours_list = hex_to_rgb(categorical_hex_list)
131
+
132
+ else:
133
+ raise ValueError(f"colour_format must be 'hex' or 'rgb', not {colour_format}.")
134
+
135
+ categorical_colours_list = full_categorical_colours_list[0:number_of_colours]
136
+
137
+ return categorical_colours_list
138
+
139
+
140
+ def duo_colours(duo_hex_list, colour_format="hex"):
141
+ """
142
+ Return the Analysis Function duo colour palette as a list of 2
143
+ colours in hex or rgb format. This function is also called by
144
+ sequential_colours() if number_of_colours is equal to 2.
145
+
146
+ Parameters
147
+ ----------
148
+ duo_hex_list : list
149
+ List of duo colours hex codes, stored in the config. This is needed for the
150
+ case of number_of_colours = 2.
151
+
152
+ colour_format : string
153
+ Colour format required, with accepted values of "hex" or "rgb".
154
+
155
+ Returns
156
+ -------
157
+ list
158
+ duo_colours_list
159
+
160
+ """
161
+
162
+ if colour_format == "hex":
163
+ duo_colours_list = duo_hex_list
164
+ elif colour_format == "rgb":
165
+ duo_colours_list = hex_to_rgb(duo_hex_list)
166
+ else:
167
+ raise ValueError(f"colour_format must be 'hex' or 'rgb', not {colour_format}.")
168
+
169
+ return duo_colours_list
170
+
171
+
172
+ def sequential_colours(sequential_hex_list, colour_format="hex"):
173
+ """
174
+ Return the Analysis Function sequential colour palette as a list
175
+ of 3 colours in hex or rgb format.
176
+
177
+ Parameters
178
+ ----------
179
+ sequential_hex_list : list
180
+ List of sequential colours hex codes, stored in the config.
181
+
182
+ colour_format : string
183
+ Colour format required, with accepted values of "hex" or "rgb".
184
+
185
+ Returns
186
+ -------
187
+ list
188
+ sequential_colours_list
189
+
190
+ """
191
+
192
+ if colour_format == "hex":
193
+ sequential_colours_list = sequential_hex_list
194
+ elif colour_format == "rgb":
195
+ sequential_colours_list = hex_to_rgb(sequential_hex_list)
196
+ else:
197
+ raise ValueError(f"colour_format must be 'hex' or 'rgb', not {colour_format}.")
198
+
199
+ return sequential_colours_list
200
+
201
+
202
+ def focus_colours(focus_hex_list, colour_format="hex"):
203
+ """
204
+ Return the Analysis Function focus colour palette as a list of 2
205
+ colours in hex or rgb format.
206
+
207
+ Parameters
208
+ ----------
209
+ focus_hex_list : list
210
+ List of focus colours hex codes, stored in the config.
211
+
212
+ colour_format : string
213
+ Colour format required, with accepted values of "hex" or "rgb".
214
+
215
+ Returns
216
+ -------
217
+ list
218
+ focus_colours_list
219
+
220
+ """
221
+
222
+ if colour_format == "hex":
223
+ focus_colours_list = focus_hex_list
224
+ elif colour_format == "rgb":
225
+ focus_colours_list = hex_to_rgb(focus_hex_list)
226
+ else:
227
+ raise ValueError(f"colour_format must be 'hex' or 'rgb', not {colour_format}.")
228
+
229
+ return focus_colours_list
230
+
231
+
232
+ def hex_to_rgb(hex_colours):
233
+ """
234
+ Convert a list of hex codes to a list of rgb colours.
235
+
236
+ Parameters
237
+ ----------
238
+ hex_colours : list
239
+ The hex colours to be converted as a list of strings, with or
240
+ without # at the beginning.
241
+
242
+ Raises
243
+ ------
244
+ TypeError
245
+ If hex_colours is not a list.
246
+
247
+ Returns
248
+ -------
249
+ list
250
+ converted_list
251
+
252
+ """
253
+ if type(hex_colours) is not list:
254
+ raise TypeError("hex_colours must be a list.")
255
+
256
+ hex_colours_new = [i.lstrip("#") for i in hex_colours]
257
+
258
+ converted_list = [(tuple(int(value[i : i + 2], 16) for i in (0, 2, 4))) for value in hex_colours_new]
259
+ return converted_list
@@ -0,0 +1,42 @@
1
+ # TEXT
2
+ font.size : 14
3
+ font.family : sans-serif
4
+ font.sans-serif : Arial, Helvetica, DejaVu Sans, Bitstream Vera Sans, Computer Modern Sans Serif, Lucida Grande, Verdana, Geneva, Lucid, Avant Garde, sans-serif
5
+ text.color : black
6
+ axes.titlelocation: left
7
+
8
+ # AXES
9
+ axes.edgecolor : (0.8,0.8,0.8)
10
+ axes.labelcolor : (0.1,0.1,0.1)
11
+ axes.linewidth : 1
12
+ axes.spines.top : False
13
+ axes.spines.right : False
14
+ axes.spines.bottom : True
15
+ axes.spines.left : False
16
+ axes.axisbelow : True
17
+
18
+ # GRID
19
+ axes.grid : True
20
+ axes.grid.axis : y
21
+ axes.grid.which : major
22
+ grid.color : '0.8'
23
+ grid.linestyle : -
24
+ grid.linewidth : 0.6
25
+
26
+ # LINES
27
+ axes.prop_cycle: cycler('color', ["#12436D","#28A197","#801650", "#F46A25","#3D3D3D","#A285D1"])
28
+ lines.linewidth : 2
29
+
30
+ # TICKS
31
+ xtick.color : (0.8,0.8,0.8)
32
+ xtick.labelcolor : (0.1,0.1,0.1)
33
+ xtick.direction : out
34
+ ytick.color : (0.8,0.8,0.8)
35
+ ytick.labelcolor : (0.1,0.1,0.1)
36
+ ytick.direction : out
37
+
38
+ # LEGEND
39
+ legend.frameon : False
40
+
41
+ # FIGURE OUTPUT
42
+ figure.figsize : 6.4, 4.8
File without changes
@@ -0,0 +1,4 @@
1
+ categorical_hex_list: ["#12436D","#28A197","#801650", "#F46A25","#3D3D3D","#A285D1"]
2
+ duo_hex_list: ["#12436D","#F46A25"]
3
+ sequential_hex_list: ["#12436D", "#2073BC", "#6BACE6"]
4
+ focus_hex_list: ["#12436D","#BFBFBF"]
@@ -0,0 +1,142 @@
1
+ import plotly.graph_objects as go
2
+ import plotly.io as pio
3
+
4
+ from afcharts.af_colours import get_af_colours
5
+
6
+ # References:
7
+ # https://plotly.com/python/templates/
8
+ # https://plotly.com/python-api-reference/generated/plotly.graph_objects.layout.template.html
9
+
10
+ # Use built in sans font
11
+ afcharts_font = "Sans-serif" # consider using a different font?
12
+
13
+ # R package ggplot uses pt as dimmentions whereas plotly uses px
14
+ # 1 pt ≈ 1.33 px
15
+ # So a 14 pt font ≈ 18.62 px in Plotly
16
+ base_size = 14 * 1.33
17
+ base_line_size = base_size / 24
18
+ base_rect_size = base_size / 24
19
+
20
+ # The half-line (base_size / 2) sets up the basic vertical
21
+ # rhythm of the theme. Most margins will be set to this value.
22
+ # However, when we work with relative sizes, we may want to multiply
23
+ # `half_line` with the appropriate relative size. This applies in
24
+ # particular for axis tick sizes. And also, for axis ticks and
25
+ # axis titles, `half_size` is too large a distance, and we use `half_size/2`
26
+ # instead.
27
+ half_line = base_size / 2
28
+
29
+ af_chart_feature_colour = "#D6D6D6"
30
+
31
+ pio.templates["afcharts"] = go.layout.Template(
32
+ layout={
33
+ "autosize": True, # Automatically adjusts the scale of the plot based on it's content
34
+ "annotationdefaults": {
35
+ "font": {"size": base_size},
36
+ "showarrow": False,
37
+ }, # Sets default font size for annotation
38
+ "bargap": 0.15,
39
+ "bargroupgap": 0.1,
40
+ "coloraxis": {
41
+ "colorbar": { # Bar chart colours
42
+ "outlinewidth": 0, # Width of the outline around the color bar
43
+ "tickcolor": af_chart_feature_colour, # Bar chart tick colour
44
+ "ticklen": half_line / 2, # Bar chart tick length
45
+ "ticks": "outside", # Bar chart tick position
46
+ }
47
+ },
48
+ "colorscale": {
49
+ "sequential": get_af_colours("sequential")[
50
+ ::-1
51
+ ] # reverse to make high = dark # Sequential colour scale for low to high ranges
52
+ },
53
+ "colorway": get_af_colours("categorical"), # Sequence of colours to be used in plots
54
+ "font": {
55
+ "color": "black", # Text colour
56
+ "family": afcharts_font, # Font
57
+ "size": base_size,
58
+ }, # Text size
59
+ "legend_title": None, # Removes legend title
60
+ "legend": {
61
+ "borderwidth": 0,
62
+ "title": {"text": None}, # Removes legend title
63
+ "font": {"size": base_size}, # Legend font size
64
+ "bgcolor": "rgba(0,0,0,0)", # Makes legend background transparent
65
+ "orientation": "v", # Legend orientation
66
+ "x": 1, # Positions legend (0,0 is the bottom left)
67
+ "y": 0.5,
68
+ "indentation": 0,
69
+ "itemclick": "toggleothers", # Change behaviour from hiding trace to showing only this trace
70
+ "itemwidth": 30,
71
+ "traceorder": "normal",
72
+ },
73
+ "hoverlabel": {
74
+ "align": "left", # Align hover label text to the left
75
+ "font_size": base_size * 0.9, # Text size of hover
76
+ "bgcolor": "white", # Hover box background
77
+ },
78
+ "hovermode": "x unified", # How hovering affects the display
79
+ # x unified shows info for all the data at that point in the x-axis
80
+ "margin": { # Set margins around the plot area in pixels
81
+ "l": half_line, # Left margin
82
+ "r": half_line, # Right margin
83
+ "t": half_line, # Top margin
84
+ "b": half_line, # Bottom margin
85
+ "pad": 0, # Padding between grid lines and the tick labels
86
+ },
87
+ "uniformtext_minsize": 8, # Minimum font size for text elements in the plot
88
+ "uniformtext_mode": "hide", # Controls visibility of text based on size then the
89
+ # text will be hidden - hide means that if a text element's size falls below the "uniformtext_minsize"
90
+ "title": {
91
+ "text": None,
92
+ "font": {
93
+ "size": base_size * 1.6,
94
+ }, # Title font size and colour
95
+ "x": 0,
96
+ "xref": "paper", # Title alignment
97
+ "pad": {
98
+ "t": half_line,
99
+ "l": 0,
100
+ "r": half_line,
101
+ "b": half_line,
102
+ }, # Padding above and below title
103
+ },
104
+ "xaxis": { # Configures the x-axis
105
+ "automargin": True, # Automatically adjust margins on axes to fit the content
106
+ "gridcolor": af_chart_feature_colour, # Grid lines colours
107
+ "linecolor": af_chart_feature_colour, # Axes line colour
108
+ "linewidth": 1,
109
+ "tickcolor": af_chart_feature_colour, # Tick mark colours
110
+ "tickfont": {
111
+ "size": base_size,
112
+ }, # Tick label font size
113
+ "tickwidth": 1,
114
+ "ticks": "outside", # Removes tick marks
115
+ "title": { # Axes title
116
+ "text": None, # Removes axes title
117
+ "standoff": half_line / 2, # Position from axes
118
+ },
119
+ "fixedrange": True, # Disables zoom and pan, keeps range fixed
120
+ "zeroline": True, # Makes zeroline visible
121
+ "zerolinecolor": af_chart_feature_colour, # Zero line colour
122
+ },
123
+ "yaxis": { # Configures the y-axis (as with the x-axis above)
124
+ "automargin": True,
125
+ "gridcolor": af_chart_feature_colour,
126
+ "linecolor": af_chart_feature_colour,
127
+ "linewidth": 1,
128
+ "tickcolor": af_chart_feature_colour,
129
+ "tickfont": {"size": base_size},
130
+ "tickwidth": 1,
131
+ "ticks": "outside",
132
+ "title": {
133
+ "text": None,
134
+ "standoff": half_line / 2, # Position from axes
135
+ },
136
+ "fixedrange": True,
137
+ "zeroline": True,
138
+ "zerolinecolor": af_chart_feature_colour,
139
+ },
140
+ },
141
+ data={"scatter": [{"marker": {"size": 12}, "line": {"width": 2.5}}]},
142
+ )
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: afcharts
3
+ Version: 1.0.0a0
4
+ Summary: A python package to produce charts following UK Government Analysis Function guidance
5
+ Author: Crown Copyright
6
+ Maintainer-email: Nick Henden <Nick.Henden@ukhsa.gov.uk>, Tom Farley <tom.farley@ukhsa.gov.uk>, Stelios Georgiou <Stelios.Georgiou@ukhsa.gov.uk>, Frederico Rodrigues <fred.rodrigues@defra.gov.uk>, Peter Brohan <peter.brohan@communities.gov.uk>, Fahim Hossain <fahim.hossain@ukhsa.gov.uk>
7
+ Project-URL: Homepage, https://github.com/best-practice-and-impact/afcharts-py
8
+ Project-URL: Documentation, https://best-practice-and-impact.github.io/afcharts-py/
9
+ Project-URL: Source, https://github.com/best-practice-and-impact/afcharts-py
10
+ Project-URL: Issue Tracker, https://github.com/best-practice-and-impact/afcharts-py/issues
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE.md
18
+ Requires-Dist: matplotlib>=3.10.3
19
+ Requires-Dist: plotly>=6.1.2
20
+ Requires-Dist: typeguard>=4.4.4
21
+ Requires-Dist: PyYAML>=5.0
22
+ Dynamic: license-file
23
+
24
+
25
+ # afcharts-py <img src="docs/images/logo.svg" alt="afcharts logo" align="right" height="150"/>
26
+
27
+ ## Overview
28
+
29
+ The afcharts python package helps make accessible Matplotlib and Plotly charts following [Government Analysis Function Data Visualisation guidance](https://analysisfunction.civilservice.gov.uk/policy-store/data-visualisation-charts/):
30
+
31
+ - **Automatic chart formatting** with pre-built style sheets for Matplotlib and Plotly
32
+ - **Chart colours** from the Analysis Function [accessible colour palettes](https://analysisfunction.civilservice.gov.uk/policy-store/data-visualisation-colours-in-charts/#section-4)
33
+ - **Example code** for common chart types in the [cookbook](https://best-practice-and-impact.github.io/afcharts-py/)
34
+
35
+ Looking for the R version? Check out the [afcharts R package](https://best-practice-and-impact.github.io/afcharts/).
36
+
37
+ <img src="docs/images/example_charts/bar_chart-matplotlib_afcharts.png" width="28.1%" alt="A grouped bar chart in afcharts style showing life expectancy in 1967 and 2007 for four countries. Bars use Analysis Function palette: dark blue for 1967, orange for 2007."/> <img src="docs/images/example_charts/scatterplot-matplotlib_afcharts.png" width="30%" alt="A scatterplot in afcharts style showing life expectancy against GDP per capita for 142 countries in 2007."/> <img src="docs/images/example_charts/line_chart-matplotlib_afcharts.png" width="37.5%" alt="A line chart in afcharts style showing life expectancy in China and the UK from 1952 to 2007"/>
38
+
39
+ ## Installation
40
+
41
+ afcharts is available at the Python Package Index (PyPI):
42
+ ```bash
43
+ pip install afcharts
44
+ ```
45
+ or see the [alternative installation](https://best-practice-and-impact.github.io/afcharts-py/getting-started.html) instructions.
46
+
47
+ ## Usage
48
+
49
+ Format any Matplotlib or Plotly chart in the Analysis Function style using the built-in style sheets.
50
+
51
+ See the [Getting Started](https://best-practice-and-impact.github.io/afcharts-py/getting-started.html) guide for more options and the [cookbook](https://best-practice-and-impact.github.io/afcharts-py/) for extensive examples.
52
+
53
+ ### Matplotlib
54
+
55
+ ```python
56
+ import matplotlib.pyplot as plt
57
+
58
+ # Apply the afcharts style to all Matplotlib plots
59
+ plt.style.use('afcharts.afcharts')
60
+ ```
61
+
62
+ Example: A [Matplotlib bar chart](https://best-practice-and-impact.github.io/afcharts-py/01-matplotlib-usage.html#grouped-bar-chart) with afcharts (left) and without (right)
63
+
64
+ <img src="docs/images/example_charts/bar_chart-matplotlib_afcharts.png" width="35%" alt="Grouped bar chart (afcharts style) showing life expectancy in 1967 and 2007 for four countries. Bars use Analysis Function palette: dark blue for 1967, orange for 2007."/> <img src="docs/images/example_charts/bar_chart-matplotlib_default.png" width="34.7%" alt="Grouped bar chart (default Matplotlib) showing life expectancy in 1967 and 2007 for four countries. Bars: blue for 1967, orange for 2007."/>
65
+
66
+ ### Plotly
67
+
68
+ ```python
69
+ from afcharts.pio_template import pio
70
+
71
+ # Apply the afcharts style to all Plotly plots
72
+ pio.templates.default = "afcharts"
73
+ ```
74
+
75
+ Example: A [Plotly bar chart](https://best-practice-and-impact.github.io/afcharts-py/03-plotly-usage.html#grouped-bar-chart) with afcharts (left) and without (right)
76
+
77
+ <img src="docs/images/example_charts/bar_chart-plotly_afcharts.png" width="35%" alt="Grouped bar chart (afcharts style) showing life expectancy in 1967 and 2007 for four countries. Bars use Analysis Function palette: dark blue for 1967, orange for 2007."/> <img src="docs/images/example_charts/bar_chart-plotly_default.png" width="35%" alt="Grouped bar chart (default Plotly) showing life expectancy in 1967 and 2007 for four countries. Bars: blue for 1967, red for 2007."/>
78
+
79
+ ### Colours
80
+
81
+ Easily return a list of colours from any of the Analysis Function [accessible colour palettes](https://analysisfunction.civilservice.gov.uk/policy-store/data-visualisation-colours-in-charts/#section-4) with the `get_af_colours()` function:
82
+
83
+ ```python
84
+ from afcharts.af_colours import get_af_colours
85
+
86
+ # Get the duo colour palette hex codes
87
+ duo = get_af_colours("duo")
88
+ ```
89
+
90
+ ## Getting help
91
+ If you encounter a bug, please file a minimal reproducible example on [Github Issues](https://github.com/best-practice-and-impact/afcharts-py/issues). For questions and other discussion, please start a [discussion](https://github.com/best-practice-and-impact/afcharts-py/discussions).
92
+
93
+ ## Contributing
94
+ Interested in contributing? Check out the [contributing guidelines](CONTRIBUTING.md).
95
+
96
+ ## Acknowledgments
97
+ The afcharts python package is based on the
98
+ [afcharts](https://github.com/best-practice-and-impact/afcharts.git) R package and the [py-af-colours](https://github.com/best-practice-and-impact/py-af-colours) package.
99
+
100
+ ## License
101
+ Unless stated otherwise, the codebase is released under [the MIT License](LICENSE.md). This covers both the codebase and any sample code in the documentation.
102
+
103
+ The documentation is [© Crown copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/) and available under the terms of the [Open Government 3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/) licence.
@@ -0,0 +1,14 @@
1
+ LICENSE.md
2
+ README.md
3
+ pyproject.toml
4
+ src/afcharts/__init__.py
5
+ src/afcharts/af_colours.py
6
+ src/afcharts/afcharts.mplstyle
7
+ src/afcharts/pio_template.py
8
+ src/afcharts.egg-info/PKG-INFO
9
+ src/afcharts.egg-info/SOURCES.txt
10
+ src/afcharts.egg-info/dependency_links.txt
11
+ src/afcharts.egg-info/requires.txt
12
+ src/afcharts.egg-info/top_level.txt
13
+ src/afcharts/assets/__init__.py
14
+ src/afcharts/config/af_colours.yaml
@@ -0,0 +1,4 @@
1
+ matplotlib>=3.10.3
2
+ plotly>=6.1.2
3
+ typeguard>=4.4.4
4
+ PyYAML>=5.0
@@ -0,0 +1 @@
1
+ afcharts