From 6cdf265b7c5ed27137b70cc1cd544d7194f80831 Mon Sep 17 00:00:00 2001 From: genrichez <2.2434764e+07+genrichez@users.noreply.github.com> Date: Thu, 16 Jul 2026 21:52:32 +0000 Subject: [PATCH 1/3] fix: handle 3-digit shorthand hex in hex_to_rgb Expand 3-char hex codes (e.g. #FFF) to 6-char before parsing. Fixes both _plotly_utils and matplotlylib copies. Closes #5661 --- _plotly_utils/colors/__init__.py | 2 ++ plotly/matplotlylib/mpltools.py | 2 ++ tests/test_plotly_utils/colors/test_color_conversions.py | 9 +++++++++ 3 files changed, 13 insertions(+) diff --git a/_plotly_utils/colors/__init__.py b/_plotly_utils/colors/__init__.py index 254c8ce264f..193cb378e40 100644 --- a/_plotly_utils/colors/__init__.py +++ b/_plotly_utils/colors/__init__.py @@ -757,6 +757,8 @@ def hex_to_rgb(value): :rtype (tuple) (r_value, g_value, b_value): tuple of rgb values """ value = value.lstrip("#") + if len(value) == 3: + value = "".join(c * 2 for c in value) hex_total_length = len(value) rgb_section_length = hex_total_length // 3 return tuple( diff --git a/plotly/matplotlylib/mpltools.py b/plotly/matplotlylib/mpltools.py index 42681360030..0865a7df37a 100644 --- a/plotly/matplotlylib/mpltools.py +++ b/plotly/matplotlylib/mpltools.py @@ -117,6 +117,8 @@ def hex_to_rgb(value): """ value = value.lstrip("#") + if len(value) == 3: + value = "".join(c * 2 for c in value) lv = len(value) return tuple(int(value[i : i + lv // 3], 16) for i in range(0, lv, lv // 3)) diff --git a/tests/test_plotly_utils/colors/test_color_conversions.py b/tests/test_plotly_utils/colors/test_color_conversions.py index 282a81ce562..6513b7c3233 100644 --- a/tests/test_plotly_utils/colors/test_color_conversions.py +++ b/tests/test_plotly_utils/colors/test_color_conversions.py @@ -12,6 +12,15 @@ def test_hex_to_rgb_basic_values(): assert hex_to_rgb("#aabbcc") == (170, 187, 204) +def test_hex_to_rgb_shorthand_3_digit(): + assert hex_to_rgb("#fff") == (255, 255, 255) + assert hex_to_rgb("#000") == (0, 0, 0) + assert hex_to_rgb("#abc") == (170, 187, 204) + assert hex_to_rgb("#f00") == (255, 0, 0) + assert hex_to_rgb("#0f0") == (0, 255, 0) + assert hex_to_rgb("#00f") == (0, 0, 255) + + def test_label_rgb_formats_tuple(): assert label_rgb((255, 0, 0)) == "rgb(255, 0, 0)" assert label_rgb((1, 2, 3)) == "rgb(1, 2, 3)" From 7f3b58cabc2e57175bbf94ed4b030b42284dcf12 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Mon, 27 Jul 2026 15:13:02 -0600 Subject: [PATCH 2/3] Refactor to remove duplicate function --- _plotly_utils/colors/__init__.py | 9 ++++++++- plotly/matplotlylib/mpltools.py | 21 ++------------------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/_plotly_utils/colors/__init__.py b/_plotly_utils/colors/__init__.py index 193cb378e40..e3b5cfa3205 100644 --- a/_plotly_utils/colors/__init__.py +++ b/_plotly_utils/colors/__init__.py @@ -752,9 +752,16 @@ def hex_to_rgb(value): """ Calculates rgb values from a hex color code. - :param (string) value: Hex color string + :param (string) value: Hex color string. May be a full 6-character code + or a 3-character shorthand code. :rtype (tuple) (r_value, g_value, b_value): tuple of rgb values + + Example: + + '#FFFFFF' --> (255, 255, 255) + '#FFF' --> (255, 255, 255) + """ value = value.lstrip("#") if len(value) == 3: diff --git a/plotly/matplotlylib/mpltools.py b/plotly/matplotlylib/mpltools.py index 0865a7df37a..0a3206998ba 100644 --- a/plotly/matplotlylib/mpltools.py +++ b/plotly/matplotlylib/mpltools.py @@ -10,6 +10,8 @@ import warnings import matplotlib.dates +from _plotly_utils.colors import hex_to_rgb + def check_bar_match(old_bar, new_bar): """Check if two bars belong in the same collection (bar chart). @@ -104,25 +106,6 @@ def convert_symbol(mpl_symbol): return "circle" # default -def hex_to_rgb(value): - """ - Change a hex color to an rgb tuple - - :param (str|unicode) value: The hex string we want to convert. - :return: (int, int, int) The red, green, blue int-tuple. - - Example: - - '#FFFFFF' --> (255, 255, 255) - - """ - value = value.lstrip("#") - if len(value) == 3: - value = "".join(c * 2 for c in value) - lv = len(value) - return tuple(int(value[i : i + lv // 3], 16) for i in range(0, lv, lv // 3)) - - def merge_color_and_opacity(color, opacity): """ Merge hex color with an alpha (opacity) to get an rgba tuple. From 82fe3cf69a1e4ba38c288593952dd638904e82e9 Mon Sep 17 00:00:00 2001 From: genrichez <2.2434764e+07+genrichez@users.noreply.github.com> Date: Mon, 27 Jul 2026 21:59:00 +0000 Subject: [PATCH 3/3] Add changelog entry for hex_to_rgb shorthand --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e52c392dc79..9e39b670b5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Fixed +- Fix `hex_to_rgb` parsing of 3-digit shorthand hexadecimal colors such as `#FFF` [[#5662](https://github.com/plotly/plotly.py/pull/5662)], with thanks to @genrichez for the contribution! + ## [6.9.0] - 2026-07-09