From 0dfa6d3dda67551a78aa5ad7cb1e2430cccfdb61 Mon Sep 17 00:00:00 2001 From: Max Woolf Date: Sat, 9 Nov 2019 17:04:02 -0800 Subject: [PATCH 1/7] Working color input (#9) --- stylecloud/stylecloud.py | 55 +++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/stylecloud/stylecloud.py b/stylecloud/stylecloud.py index eb0fefa..de3289e 100644 --- a/stylecloud/stylecloud.py +++ b/stylecloud/stylecloud.py @@ -3,7 +3,7 @@ import csv import os from PIL import Image -from matplotlib.colors import makeMappingArray +from matplotlib.colors import makeMappingArray, to_rgb import numpy as np import fire from shutil import rmtree @@ -111,11 +111,23 @@ def gen_gradient_mask(size, palette, icon_dir='.temp', return image_colors, np.uint8(mask_array) +def color_to_rgb(color): + """Converts a color to a RGB tuple from (0-255).""" + if isinstance(color, tuple): + # if a RGB tuple already + return color + else: + # to_rgb() returns colors from (0-1) + color = tuple(int(x * 255) for x in to_rgb(color)) + return color + + def gen_stylecloud(text=None, file_path=None, size=512, icon_name='fas fa-flag', palette='cartocolors.qualitative.Bold_6', + colors=None, background_color="white", max_font_size=200, max_words=2000, @@ -134,7 +146,8 @@ def gen_stylecloud(text=None, :param size: Size (length and width in pixels) of the stylecloud. :param icon_name: Icon Name for the stylecloud shape. (e.g. 'fas fa-grin') :param palette: Color palette (via palettable) - :param background_color: Background color (name or hex) + :param colors: Custom color(s) for text (name or hex). Overrides palette. + :param background_color: Background color (name or hex). :param max_font_size: Maximum font size in the stylecloud. :param max_words: Maximum number of words to include in the stylecloud. :param stopwords: Boolean to filter out common stopwords. @@ -155,20 +168,32 @@ def gen_stylecloud(text=None, gen_fa_mask(icon_name, size, icon_dir) - if gradient: - colors, mask_array = gen_gradient_mask(size, palette, icon_dir, - gradient) + if gradient and colors is None: + pal_colors, mask_array = gen_gradient_mask(size, palette, icon_dir, + gradient) else: # Color each word randomly from the palette mask_array = gen_mask_array(icon_dir, 'uint8') - palette_func = gen_palette(palette) - - # See also: - # https://amueller.github.io/word_cloud/auto_examples/a_new_hope.html - def colors(word, font_size, position, - orientation, random_state, - **kwargs): - rand_color = np.random.randint(0, palette_func.number - 1) - return tuple(palette_func.colors[rand_color]) + if colors: + # if specifying a single color string + if isinstance(colors, str): + colors = [colors] + + # iterate through each color to ensure correct RGB format. + # see matplotlib docs on how colors are decoded: + # https://matplotlib.org/3.1.1/api/colors_api.html + colors = [color_to_rgb(color) for color in colors] + + else: + palette_func = gen_palette(palette) + colors = palette_func.colors + + def pal_colors(word, font_size, position, + orientation, random_state, + **kwargs): + if len(colors) == 1: + return tuple(colors[0]) + rand_color = np.random.randint(0, len(colors) - 1) + return tuple(colors[rand_color]) # cleanup icon folder rmtree(icon_dir) @@ -185,7 +210,7 @@ def colors(word, font_size, position, wc.generate_from_text(text) else: # i.e. a dict of word:value from a CSV wc.generate_from_frequencies(text) - wc.recolor(color_func=colors, random_state=random_state) + wc.recolor(color_func=pal_colors, random_state=random_state) wc.to_file(output_name) From 789978f0d1c8cd05ef82ecbe519626e3dd058994 Mon Sep 17 00:00:00 2001 From: Max Woolf Date: Sat, 9 Nov 2019 17:14:41 -0800 Subject: [PATCH 2/7] Fix bug where last color in palette was ignored --- README.md | 2 +- stylecloud/stylecloud.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e9c0ddf..297bfd7 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ These parameters are valid for both the Python function and the CLI (you can use * gradient: Direction of gradient. (if not None, the stylecloud will use a directional gradient) [default: `None`] * size: Size (length and width in pixels) of the stylecloud. [default: `512`] * icon_name: Icon Name for the stylecloud shape. (e.g. 'fas fa-grin') [default: `fas fa-flag`] -* palette: Color palette (via palettable) [default: `cartocolors.qualitative.Bold_6`] +* palette: Color palette (via palettable) [default: `cartocolors.qualitative.Bold_5`] * background_color: Background color (name or hex) [default: `white`] * max_font_size: Maximum font size in the stylecloud. [default: `200`] * max_words: Maximum number of words to include in the stylecloud. [default: `2000`] diff --git a/stylecloud/stylecloud.py b/stylecloud/stylecloud.py index de3289e..ebbb5bc 100644 --- a/stylecloud/stylecloud.py +++ b/stylecloud/stylecloud.py @@ -126,7 +126,7 @@ def gen_stylecloud(text=None, file_path=None, size=512, icon_name='fas fa-flag', - palette='cartocolors.qualitative.Bold_6', + palette='cartocolors.qualitative.Bold_5', colors=None, background_color="white", max_font_size=200, @@ -192,7 +192,7 @@ def pal_colors(word, font_size, position, **kwargs): if len(colors) == 1: return tuple(colors[0]) - rand_color = np.random.randint(0, len(colors) - 1) + rand_color = np.random.randint(0, len(colors)) return tuple(colors[rand_color]) # cleanup icon folder From 8e3b506384774665dfcf0d8e43f40dd55e5d1c54 Mon Sep 17 00:00:00 2001 From: Max Woolf Date: Sat, 9 Nov 2019 17:23:59 -0800 Subject: [PATCH 3/7] Allow stopwords filter on dict input --- stylecloud/stylecloud.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stylecloud/stylecloud.py b/stylecloud/stylecloud.py index ebbb5bc..62d1714 100644 --- a/stylecloud/stylecloud.py +++ b/stylecloud/stylecloud.py @@ -209,6 +209,8 @@ def pal_colors(word, font_size, position, if isinstance(text, str): wc.generate_from_text(text) else: # i.e. a dict of word:value from a CSV + if stopwords: # manually remove stopwords since otherwise ignored + text = {k: v for k, v in text.items() if k not in custom_stopwords} wc.generate_from_frequencies(text) wc.recolor(color_func=pal_colors, random_state=random_state) wc.to_file(output_name) From 5d8ff407eefa918c01d6011f9b34fdb9ae2a7ace Mon Sep 17 00:00:00 2001 From: Max Woolf Date: Sat, 9 Nov 2019 17:43:23 -0800 Subject: [PATCH 4/7] remove unnecessary early return --- stylecloud/stylecloud.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/stylecloud/stylecloud.py b/stylecloud/stylecloud.py index 62d1714..3956b21 100644 --- a/stylecloud/stylecloud.py +++ b/stylecloud/stylecloud.py @@ -190,8 +190,6 @@ def gen_stylecloud(text=None, def pal_colors(word, font_size, position, orientation, random_state, **kwargs): - if len(colors) == 1: - return tuple(colors[0]) rand_color = np.random.randint(0, len(colors)) return tuple(colors[rand_color]) From e2978cd30601bd3c4b042caa048145c1caebbab7 Mon Sep 17 00:00:00 2001 From: Max Woolf Date: Sat, 9 Nov 2019 18:00:36 -0800 Subject: [PATCH 5/7] README revamp w/ new features --- README.md | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 297bfd7..bf5b5b6 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,9 @@ stylecloud is a Python package that leverages the popular [word_cloud](https://g * Icon shapes (of any size!) for wordclouds (via [Font Awesome](https://fontawesome.com) 5.11.2) * Support for advanced color palettes (via [palettable](https://jiffyclub.github.io/palettable/)) +* Manual color selection for text and backgrounds, * Directional gradients w/ the aforementioned palettes. -* Supports reading a file of text, or reading a pre-generated CSV with words and counts. +* Supports reading text files and CSVs (either one-column w/ texts, or two columns w/ words+weights). * Command Line Interface! This package is a more formal implementation of my [stylistic word cloud project](https://minimaxir.com/2016/05/wordclouds/) from 2016. @@ -64,21 +65,41 @@ stylecloud --file_path constitution.txt --icon_name 'fas fa-dog' --palette color You can find more examples of styleclouds, including how to make styleclouds from Twitter and Reddit data, in the [stylecloud-examples](https://github.com/minimaxir/stylecloud-examples) repo. -In order to deal with different languages or simply add list of custom stopwords it is possible to pass a list contained in a string as parameter like so : +### Custom Colors for stylecloud Text + +You can manually specify the color(s) of the text with the `colors` parameter, overriding the palettes. This can be useful for specific branding, or high-contrast visualizations. However, manual color selection not work with gradients. + +```python +import stylecloud + +stylecloud.gen_stylecloud(file_path='constitution.txt', + colors=['#ecf0f1', '#3498db', '#e74c3c'], + background_color='#1A1A1A') +``` ```sh -stylecloud --file_path constitution.txt --custom_words "[thereof, may, state, united states]" +stylecloud --file_path constitution.txt --colors "['#ecf0f1', '#3498db', '#e74c3c']" --background_color '#1A1A1A' ``` -For more control it would of course be most ideal to define the list in code since if one is defining stopwords for another language these lists can get long. In that case simply pass in the list as argument to the function +![](https://github.com/minimaxir/stylecloud-examples/raw/master/hello-world/stylecloud5.png) + +### Stopwords + +In order to filter out stopwords in non-English languages or use custom stopwords, you can pass a list of words to the `custom_stopwords` parameter: ```python import stylecloud my_long_list = ["thereof", "may", "state", "united states"] -stylecloud.gen_stylecloud(file_path=constitution.txt, custom_words=my_long_list) +stylecloud.gen_stylecloud(file_path=constitution.txt, + custom_stopwords=my_long_list) +``` + +```sh +stylecloud --file_path constitution.txt --custom_stopwords "[thereof, may, state, united states]" ``` -Good resources for stopwords in other languages are the [stop-words python package](https://github.com/Alir3z4/python-stop-words) which gives you python lists directly. Or as JSON arrays this list of [iso stopword collections](https://github.com/stopwords-iso/stopwords-iso). + +Good resources for stopwords in other languages are the [stop-words Python package](https://github.com/Alir3z4/python-stop-words) and the [ISO stopword collections](https://github.com/stopwords-iso/stopwords-iso). ### Helpful Parameters @@ -90,6 +111,7 @@ These parameters are valid for both the Python function and the CLI (you can use * size: Size (length and width in pixels) of the stylecloud. [default: `512`] * icon_name: Icon Name for the stylecloud shape. (e.g. 'fas fa-grin') [default: `fas fa-flag`] * palette: Color palette (via palettable) [default: `cartocolors.qualitative.Bold_5`] +* colors: Color(s) to use as the text colors. Overrides both gradient and palette if specified [default: `None`] * background_color: Background color (name or hex) [default: `white`] * max_font_size: Maximum font size in the stylecloud. [default: `200`] * max_words: Maximum number of words to include in the stylecloud. [default: `2000`] @@ -119,7 +141,7 @@ These parameters are valid for both the Python function and the CLI (you can use Max Woolf ([@minimaxir](https://minimaxir.com)) -*Max's open-source projects are supported by his [Patreon](https://www.patreon.com/minimaxir) and GitHub Sponsors. If you found this project helpful, any monetary contributions to the Patreon are appreciated and will be put to good creative use.* +*Max's open-source projects are supported by his [Patreon](https://www.patreon.com/minimaxir) and [GitHub Sponsors](https://github.com/sponsors/minimaxir). If you found this project helpful, any monetary contributions to the Patreon are appreciated and will be put to good creative use.* ## License From ffda9c75fbaea284785fa21222e1e029451f155e Mon Sep 17 00:00:00 2001 From: Max Woolf Date: Sat, 9 Nov 2019 18:07:18 -0800 Subject: [PATCH 6/7] collocations + minor edits --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bf5b5b6..3191e71 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ pip3 install stylecloud ## Usage -You can use stylecloud in a Python script or as a standalone CLI app. For example, let's say you have a text of the U.S. Constitution `constitution.txt`. +You can use stylecloud in a Python script or as a standalone CLI app. For example, let's say you have a [text](https://github.com/amueller/word_cloud/blob/master/examples/constitution.txt) of the U.S. Constitution `constitution.txt`. Python script: @@ -116,10 +116,11 @@ These parameters are valid for both the Python function and the CLI (you can use * max_font_size: Maximum font size in the stylecloud. [default: `200`] * max_words: Maximum number of words to include in the stylecloud. [default: `2000`] * stopwords: Boolean to filter out common stopwords. [default: `True`] -* custom_stopwords: list of custom stopwords. e.g: For other languages than english [default: `STOPWORDS`] +* custom_stopwords: list of custom stopwords. e.g: For other languages than english [default: `STOPWORDS`, via `word_cloud`] * output_name: Output file name of the stylecloud. [default: `stylecloud.png`] * font_path: Path to .ttf file for font to use in stylecloud. [default: uses included Staatliches font] -* random_state: Controls random state of words and colors. +* random_state: Controls random state of words and colors. [default: `None`] +* collocations: Whether to include collocations (bigrams) of two words. Same behavior as base `word_cloud` package. [default: `True`] ## Helpful Notes From 688e115061aa7de676e706cd74e24160f4456a6d Mon Sep 17 00:00:00 2001 From: Max Woolf Date: Sat, 9 Nov 2019 18:11:08 -0800 Subject: [PATCH 7/7] Version bump --- setup.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index e3fb4c5..2b921bb 100644 --- a/setup.py +++ b/setup.py @@ -3,12 +3,13 @@ long_description = ''' Generate stylistic wordclouds, including gradients and icon shapes! -stylecloud is a Python package leverages the popular [word_cloud](https://github.com/amueller/word_cloud) package, adding useful features to create truly unique word clouds! +stylecloud is a Python package that leverages the popular [word_cloud](https://github.com/amueller/word_cloud) package, adding useful features to create truly unique word clouds! -* Icon shapes for wordclouds (via [Font Awesome](https://fontawesome.com) 5.11.2) +* Icon shapes (of any size!) for wordclouds (via [Font Awesome](https://fontawesome.com) 5.11.2) * Support for advanced color palettes (via [palettable](https://jiffyclub.github.io/palettable/)) +* Manual color selection for text and backgrounds, * Directional gradients w/ the aforementioned palettes. -* Supports reading a file of text, or reading a pre-generated CSV with words and counts. +* Supports reading text files and CSVs (either one-column w/ texts, or two columns w/ words+weights). * Command Line Interface! This package is a more formal implementation of my [stylistic word cloud project](https://minimaxir.com/2016/05/wordclouds/) from 2016. @@ -18,7 +19,7 @@ setup( name='stylecloud', packages=['stylecloud'], # this must be the same as the name above - version='0.2', + version='0.3', description="Python package + CLI to generate stylistic wordclouds, " \ "including gradients and icon shapes!", long_description=long_description,