From f7ee40349f8e39cc4babce7bce04c3f58a968265 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Thu, 13 Jun 2019 13:49:06 -0400 Subject: [PATCH 1/4] Integrate plotly_express as plotly.express --- packages/python/plotly/plotly/__init__.py | 2 + .../python/plotly/plotly/colors/__init__.py | 13 + .../python/plotly/plotly/colors/_swatches.py | 38 + packages/python/plotly/plotly/colors/carto.py | 384 ++++++ .../python/plotly/plotly/colors/cmocean.py | 269 ++++ .../plotly/plotly/colors/colorbrewer.py | 458 +++++++ .../python/plotly/plotly/colors/cyclical.py | 127 ++ .../python/plotly/plotly/colors/diverging.py | 31 + .../plotly/plotly/colors/qualitative.py | 147 +++ .../python/plotly/plotly/colors/sequential.py | 154 +++ .../python/plotly/plotly/data/__init__.py | 78 ++ .../python/plotly/plotly/express/__init__.py | 74 ++ .../plotly/plotly/express/_chart_types.py | 1062 ++++++++++++++++ .../python/plotly/plotly/express/_core.py | 1116 +++++++++++++++++ packages/python/plotly/plotly/express/_doc.py | 362 ++++++ .../python/plotly/plotly/express/colors.py | 2 + packages/python/plotly/plotly/express/data.py | 2 + packages/python/plotly/setup.py | 3 + 18 files changed, 4322 insertions(+) create mode 100644 packages/python/plotly/plotly/colors/__init__.py create mode 100644 packages/python/plotly/plotly/colors/_swatches.py create mode 100644 packages/python/plotly/plotly/colors/carto.py create mode 100644 packages/python/plotly/plotly/colors/cmocean.py create mode 100644 packages/python/plotly/plotly/colors/colorbrewer.py create mode 100644 packages/python/plotly/plotly/colors/cyclical.py create mode 100644 packages/python/plotly/plotly/colors/diverging.py create mode 100644 packages/python/plotly/plotly/colors/qualitative.py create mode 100644 packages/python/plotly/plotly/colors/sequential.py create mode 100644 packages/python/plotly/plotly/data/__init__.py create mode 100644 packages/python/plotly/plotly/express/__init__.py create mode 100644 packages/python/plotly/plotly/express/_chart_types.py create mode 100644 packages/python/plotly/plotly/express/_core.py create mode 100644 packages/python/plotly/plotly/express/_doc.py create mode 100644 packages/python/plotly/plotly/express/colors.py create mode 100644 packages/python/plotly/plotly/express/data.py diff --git a/packages/python/plotly/plotly/__init__.py b/packages/python/plotly/plotly/__init__.py index a8fdee9158d..3782be146b9 100644 --- a/packages/python/plotly/plotly/__init__.py +++ b/packages/python/plotly/plotly/__init__.py @@ -38,6 +38,8 @@ offline, colors, io, + data, + colors, _docstring_gen ) diff --git a/packages/python/plotly/plotly/colors/__init__.py b/packages/python/plotly/plotly/colors/__init__.py new file mode 100644 index 00000000000..d9482ee6220 --- /dev/null +++ b/packages/python/plotly/plotly/colors/__init__.py @@ -0,0 +1,13 @@ +""" +Built-in qualitative color sequences and sequential, diverging and cyclical color scales. +""" + +from . import ( # noqa: F401 + qualitative, + sequential, + diverging, + cyclical, + cmocean, + colorbrewer, + carto, +) diff --git a/packages/python/plotly/plotly/colors/_swatches.py b/packages/python/plotly/plotly/colors/_swatches.py new file mode 100644 index 00000000000..a2417dfcb5a --- /dev/null +++ b/packages/python/plotly/plotly/colors/_swatches.py @@ -0,0 +1,38 @@ +def _swatches(module_names, module_contents): + """ + Returns: + A `plotly_express.ExpressFigure` object. This figure demonstrates the color scales and + sequences in this module, as stacked bar charts. + """ + from .. import _core + import plotly.graph_objs as go + + sequences = [ + (k, v) + for k, v in module_contents.items() + if not (k.startswith("_") or k == "swatches") + ] + + return go.Figure( + data=[ + go.Bar( + orientation="h", + y=[name] * len(colors), + x=[1] * len(colors), + customdata=list(range(len(colors))), + marker=dict(color=colors), + hovertemplate="%{y}[%{customdata}] = %{marker.color}", + ) + for name, colors in reversed(sequences) + ], + layout=dict( + title=module_names, + barmode="stack", + barnorm="fraction", + template="plotly", + bargap=0.5, + showlegend=False, + xaxis=dict(range=[-0.02, 1.02], showticklabels=False, showgrid=False), + height=max(600, 40 * len(sequences)), + ), + ) diff --git a/packages/python/plotly/plotly/colors/carto.py b/packages/python/plotly/plotly/colors/carto.py new file mode 100644 index 00000000000..c20d6b5d259 --- /dev/null +++ b/packages/python/plotly/plotly/colors/carto.py @@ -0,0 +1,384 @@ +""" +Color sequences and scales from CARTO's CartoColors + +Learn more at https://github.com/CartoDB/CartoColor + +CARTOColors are made available under a Creative Commons Attribution license: https://creativecommons.org/licenses/by/3.0/us/ +""" + +from ._swatches import _swatches + + +def swatches(): + return _swatches(__name__, globals()) + + +swatches.__doc__ = _swatches.__doc__ + +Burg = [ + "rgb(255, 198, 196)", + "rgb(244, 163, 168)", + "rgb(227, 129, 145)", + "rgb(204, 96, 125)", + "rgb(173, 70, 108)", + "rgb(139, 48, 88)", + "rgb(103, 32, 68)", +] + +Burgyl = [ + "rgb(251, 230, 197)", + "rgb(245, 186, 152)", + "rgb(238, 138, 130)", + "rgb(220, 113, 118)", + "rgb(200, 88, 108)", + "rgb(156, 63, 93)", + "rgb(112, 40, 74)", +] + +Redor = [ + "rgb(246, 210, 169)", + "rgb(245, 183, 142)", + "rgb(241, 156, 124)", + "rgb(234, 129, 113)", + "rgb(221, 104, 108)", + "rgb(202, 82, 104)", + "rgb(177, 63, 100)", +] + +Oryel = [ + "rgb(236, 218, 154)", + "rgb(239, 196, 126)", + "rgb(243, 173, 106)", + "rgb(247, 148, 93)", + "rgb(249, 123, 87)", + "rgb(246, 99, 86)", + "rgb(238, 77, 90)", +] + +Peach = [ + "rgb(253, 224, 197)", + "rgb(250, 203, 166)", + "rgb(248, 181, 139)", + "rgb(245, 158, 114)", + "rgb(242, 133, 93)", + "rgb(239, 106, 76)", + "rgb(235, 74, 64)", +] + +Pinkyl = [ + "rgb(254, 246, 181)", + "rgb(255, 221, 154)", + "rgb(255, 194, 133)", + "rgb(255, 166, 121)", + "rgb(250, 138, 118)", + "rgb(241, 109, 122)", + "rgb(225, 83, 131)", +] + +Mint = [ + "rgb(228, 241, 225)", + "rgb(180, 217, 204)", + "rgb(137, 192, 182)", + "rgb(99, 166, 160)", + "rgb(68, 140, 138)", + "rgb(40, 114, 116)", + "rgb(13, 88, 95)", +] + +Blugrn = [ + "rgb(196, 230, 195)", + "rgb(150, 210, 164)", + "rgb(109, 188, 144)", + "rgb(77, 162, 132)", + "rgb(54, 135, 122)", + "rgb(38, 107, 110)", + "rgb(29, 79, 96)", +] + +Darkmint = [ + "rgb(210, 251, 212)", + "rgb(165, 219, 194)", + "rgb(123, 188, 176)", + "rgb(85, 156, 158)", + "rgb(58, 124, 137)", + "rgb(35, 93, 114)", + "rgb(18, 63, 90)", +] + +Emrld = [ + "rgb(211, 242, 163)", + "rgb(151, 225, 150)", + "rgb(108, 192, 139)", + "rgb(76, 155, 130)", + "rgb(33, 122, 121)", + "rgb(16, 89, 101)", + "rgb(7, 64, 80)", +] + +Aggrnyl = [ + "rgb(36, 86, 104)", + "rgb(15, 114, 121)", + "rgb(13, 143, 129)", + "rgb(57, 171, 126)", + "rgb(110, 197, 116)", + "rgb(169, 220, 103)", + "rgb(237, 239, 93)", +] + +Bluyl = [ + "rgb(247, 254, 174)", + "rgb(183, 230, 165)", + "rgb(124, 203, 162)", + "rgb(70, 174, 160)", + "rgb(8, 144, 153)", + "rgb(0, 113, 139)", + "rgb(4, 82, 117)", +] + +Teal = [ + "rgb(209, 238, 234)", + "rgb(168, 219, 217)", + "rgb(133, 196, 201)", + "rgb(104, 171, 184)", + "rgb(79, 144, 166)", + "rgb(59, 115, 143)", + "rgb(42, 86, 116)", +] + +Tealgrn = [ + "rgb(176, 242, 188)", + "rgb(137, 232, 172)", + "rgb(103, 219, 165)", + "rgb(76, 200, 163)", + "rgb(56, 178, 163)", + "rgb(44, 152, 160)", + "rgb(37, 125, 152)", +] + +Purp = [ + "rgb(243, 224, 247)", + "rgb(228, 199, 241)", + "rgb(209, 175, 232)", + "rgb(185, 152, 221)", + "rgb(159, 130, 206)", + "rgb(130, 109, 186)", + "rgb(99, 88, 159)", +] + +Purpor = [ + "rgb(249, 221, 218)", + "rgb(242, 185, 196)", + "rgb(229, 151, 185)", + "rgb(206, 120, 179)", + "rgb(173, 95, 173)", + "rgb(131, 75, 160)", + "rgb(87, 59, 136)", +] + +Sunset = [ + "rgb(243, 231, 155)", + "rgb(250, 196, 132)", + "rgb(248, 160, 126)", + "rgb(235, 127, 134)", + "rgb(206, 102, 147)", + "rgb(160, 89, 160)", + "rgb(92, 83, 165)", +] + +Magenta = [ + "rgb(243, 203, 211)", + "rgb(234, 169, 189)", + "rgb(221, 136, 172)", + "rgb(202, 105, 157)", + "rgb(177, 77, 142)", + "rgb(145, 53, 125)", + "rgb(108, 33, 103)", +] + +Sunsetdark = [ + "rgb(252, 222, 156)", + "rgb(250, 164, 118)", + "rgb(240, 116, 110)", + "rgb(227, 79, 111)", + "rgb(220, 57, 119)", + "rgb(185, 37, 122)", + "rgb(124, 29, 111)", +] + +Agsunset = [ + "rgb(75, 41, 145)", + "rgb(135, 44, 162)", + "rgb(192, 54, 157)", + "rgb(234, 79, 136)", + "rgb(250, 120, 118)", + "rgb(246, 169, 122)", + "rgb(237, 217, 163)", +] + +Brwnyl = [ + "rgb(237, 229, 207)", + "rgb(224, 194, 162)", + "rgb(211, 156, 131)", + "rgb(193, 118, 111)", + "rgb(166, 84, 97)", + "rgb(129, 55, 83)", + "rgb(84, 31, 63)", +] + +# Diverging schemes + +Armyrose = [ + "rgb(121, 130, 52)", + "rgb(163, 173, 98)", + "rgb(208, 211, 162)", + "rgb(253, 251, 228)", + "rgb(240, 198, 195)", + "rgb(223, 145, 163)", + "rgb(212, 103, 128)", +] + +Fall = [ + "rgb(61, 89, 65)", + "rgb(119, 136, 104)", + "rgb(181, 185, 145)", + "rgb(246, 237, 189)", + "rgb(237, 187, 138)", + "rgb(222, 138, 90)", + "rgb(202, 86, 44)", +] + +Geyser = [ + "rgb(0, 128, 128)", + "rgb(112, 164, 148)", + "rgb(180, 200, 168)", + "rgb(246, 237, 189)", + "rgb(237, 187, 138)", + "rgb(222, 138, 90)", + "rgb(202, 86, 44)", +] + +Temps = [ + "rgb(0, 147, 146)", + "rgb(57, 177, 133)", + "rgb(156, 203, 134)", + "rgb(233, 226, 156)", + "rgb(238, 180, 121)", + "rgb(232, 132, 113)", + "rgb(207, 89, 126)", +] + +Tealrose = [ + "rgb(0, 147, 146)", + "rgb(114, 170, 161)", + "rgb(177, 199, 179)", + "rgb(241, 234, 200)", + "rgb(229, 185, 173)", + "rgb(217, 137, 148)", + "rgb(208, 88, 126)", +] + +Tropic = [ + "rgb(0, 155, 158)", + "rgb(66, 183, 185)", + "rgb(167, 211, 212)", + "rgb(241, 241, 241)", + "rgb(228, 193, 217)", + "rgb(214, 145, 193)", + "rgb(199, 93, 171)", +] + +Earth = [ + "rgb(161, 105, 40)", + "rgb(189, 146, 90)", + "rgb(214, 189, 141)", + "rgb(237, 234, 194)", + "rgb(181, 200, 184)", + "rgb(121, 167, 172)", + "rgb(40, 135, 161)", +] + +# Qualitative palettes + +Antique = [ + "rgb(133, 92, 117)", + "rgb(217, 175, 107)", + "rgb(175, 100, 88)", + "rgb(115, 111, 76)", + "rgb(82, 106, 131)", + "rgb(98, 83, 119)", + "rgb(104, 133, 92)", + "rgb(156, 156, 94)", + "rgb(160, 97, 119)", + "rgb(140, 120, 93)", + "rgb(124, 124, 124)", +] + +Bold = [ + "rgb(127, 60, 141)", + "rgb(17, 165, 121)", + "rgb(57, 105, 172)", + "rgb(242, 183, 1)", + "rgb(231, 63, 116)", + "rgb(128, 186, 90)", + "rgb(230, 131, 16)", + "rgb(0, 134, 149)", + "rgb(207, 28, 144)", + "rgb(249, 123, 114)", + "rgb(165, 170, 153)", +] + +Pastel = [ + "rgb(102, 197, 204)", + "rgb(246, 207, 113)", + "rgb(248, 156, 116)", + "rgb(220, 176, 242)", + "rgb(135, 197, 95)", + "rgb(158, 185, 243)", + "rgb(254, 136, 177)", + "rgb(201, 219, 116)", + "rgb(139, 224, 164)", + "rgb(180, 151, 231)", + "rgb(179, 179, 179)", +] + +Prism = [ + "rgb(95, 70, 144)", + "rgb(29, 105, 150)", + "rgb(56, 166, 165)", + "rgb(15, 133, 84)", + "rgb(115, 175, 72)", + "rgb(237, 173, 8)", + "rgb(225, 124, 5)", + "rgb(204, 80, 62)", + "rgb(148, 52, 110)", + "rgb(111, 64, 112)", + "rgb(102, 102, 102)", +] + +Safe = [ + "rgb(136, 204, 238)", + "rgb(204, 102, 119)", + "rgb(221, 204, 119)", + "rgb(17, 119, 51)", + "rgb(51, 34, 136)", + "rgb(170, 68, 153)", + "rgb(68, 170, 153)", + "rgb(153, 153, 51)", + "rgb(136, 34, 85)", + "rgb(102, 17, 0)", + "rgb(136, 136, 136)", +] + +Vivid = [ + "rgb(229, 134, 6)", + "rgb(93, 105, 177)", + "rgb(82, 188, 163)", + "rgb(153, 201, 69)", + "rgb(204, 97, 176)", + "rgb(36, 121, 108)", + "rgb(218, 165, 27)", + "rgb(47, 138, 196)", + "rgb(118, 78, 159)", + "rgb(237, 100, 90)", + "rgb(165, 170, 153)", +] diff --git a/packages/python/plotly/plotly/colors/cmocean.py b/packages/python/plotly/plotly/colors/cmocean.py new file mode 100644 index 00000000000..c7174fd60c0 --- /dev/null +++ b/packages/python/plotly/plotly/colors/cmocean.py @@ -0,0 +1,269 @@ +""" +Color scales from the cmocean project + +Learn more at https://matplotlib.org/cmocean/ + +cmocean is made available under an MIT license: https://github.com/matplotlib/cmocean/blob/master/LICENSE.txt +""" + +from ._swatches import _swatches + + +def swatches(): + return _swatches(__name__, globals()) + + +swatches.__doc__ = _swatches.__doc__ + +turbid = [ + "rgb(232, 245, 171)", + "rgb(220, 219, 137)", + "rgb(209, 193, 107)", + "rgb(199, 168, 83)", + "rgb(186, 143, 66)", + "rgb(170, 121, 60)", + "rgb(151, 103, 58)", + "rgb(129, 87, 56)", + "rgb(104, 72, 53)", + "rgb(80, 59, 46)", + "rgb(57, 45, 37)", + "rgb(34, 30, 27)", +] +thermal = [ + "rgb(3, 35, 51)", + "rgb(13, 48, 100)", + "rgb(53, 50, 155)", + "rgb(93, 62, 153)", + "rgb(126, 77, 143)", + "rgb(158, 89, 135)", + "rgb(193, 100, 121)", + "rgb(225, 113, 97)", + "rgb(246, 139, 69)", + "rgb(251, 173, 60)", + "rgb(246, 211, 70)", + "rgb(231, 250, 90)", +] +haline = [ + "rgb(41, 24, 107)", + "rgb(42, 35, 160)", + "rgb(15, 71, 153)", + "rgb(18, 95, 142)", + "rgb(38, 116, 137)", + "rgb(53, 136, 136)", + "rgb(65, 157, 133)", + "rgb(81, 178, 124)", + "rgb(111, 198, 107)", + "rgb(160, 214, 91)", + "rgb(212, 225, 112)", + "rgb(253, 238, 153)", +] +solar = [ + "rgb(51, 19, 23)", + "rgb(79, 28, 33)", + "rgb(108, 36, 36)", + "rgb(135, 47, 32)", + "rgb(157, 66, 25)", + "rgb(174, 88, 20)", + "rgb(188, 111, 19)", + "rgb(199, 137, 22)", + "rgb(209, 164, 32)", + "rgb(217, 192, 44)", + "rgb(222, 222, 59)", + "rgb(224, 253, 74)", +] +ice = [ + "rgb(3, 5, 18)", + "rgb(25, 25, 51)", + "rgb(44, 42, 87)", + "rgb(58, 60, 125)", + "rgb(62, 83, 160)", + "rgb(62, 109, 178)", + "rgb(72, 134, 187)", + "rgb(89, 159, 196)", + "rgb(114, 184, 205)", + "rgb(149, 207, 216)", + "rgb(192, 229, 232)", + "rgb(234, 252, 253)", +] +gray = [ + "rgb(0, 0, 0)", + "rgb(16, 16, 16)", + "rgb(38, 38, 38)", + "rgb(59, 59, 59)", + "rgb(81, 80, 80)", + "rgb(102, 101, 101)", + "rgb(124, 123, 122)", + "rgb(146, 146, 145)", + "rgb(171, 171, 170)", + "rgb(197, 197, 195)", + "rgb(224, 224, 223)", + "rgb(254, 254, 253)", +] +oxy = [ + "rgb(63, 5, 5)", + "rgb(101, 6, 13)", + "rgb(138, 17, 9)", + "rgb(96, 95, 95)", + "rgb(119, 118, 118)", + "rgb(142, 141, 141)", + "rgb(166, 166, 165)", + "rgb(193, 192, 191)", + "rgb(222, 222, 220)", + "rgb(239, 248, 90)", + "rgb(230, 210, 41)", + "rgb(220, 174, 25)", +] +deep = [ + "rgb(253, 253, 204)", + "rgb(206, 236, 179)", + "rgb(156, 219, 165)", + "rgb(111, 201, 163)", + "rgb(86, 177, 163)", + "rgb(76, 153, 160)", + "rgb(68, 130, 155)", + "rgb(62, 108, 150)", + "rgb(62, 82, 143)", + "rgb(64, 60, 115)", + "rgb(54, 43, 77)", + "rgb(39, 26, 44)", +] +dense = [ + "rgb(230, 240, 240)", + "rgb(191, 221, 229)", + "rgb(156, 201, 226)", + "rgb(129, 180, 227)", + "rgb(115, 154, 228)", + "rgb(117, 127, 221)", + "rgb(120, 100, 202)", + "rgb(119, 74, 175)", + "rgb(113, 50, 141)", + "rgb(100, 31, 104)", + "rgb(80, 20, 66)", + "rgb(54, 14, 36)", +] +algae = [ + "rgb(214, 249, 207)", + "rgb(186, 228, 174)", + "rgb(156, 209, 143)", + "rgb(124, 191, 115)", + "rgb(85, 174, 91)", + "rgb(37, 157, 81)", + "rgb(7, 138, 78)", + "rgb(13, 117, 71)", + "rgb(23, 95, 61)", + "rgb(25, 75, 49)", + "rgb(23, 55, 35)", + "rgb(17, 36, 20)", +] +matter = [ + "rgb(253, 237, 176)", + "rgb(250, 205, 145)", + "rgb(246, 173, 119)", + "rgb(240, 142, 98)", + "rgb(231, 109, 84)", + "rgb(216, 80, 83)", + "rgb(195, 56, 90)", + "rgb(168, 40, 96)", + "rgb(138, 29, 99)", + "rgb(107, 24, 93)", + "rgb(76, 21, 80)", + "rgb(47, 15, 61)", +] +speed = [ + "rgb(254, 252, 205)", + "rgb(239, 225, 156)", + "rgb(221, 201, 106)", + "rgb(194, 182, 59)", + "rgb(157, 167, 21)", + "rgb(116, 153, 5)", + "rgb(75, 138, 20)", + "rgb(35, 121, 36)", + "rgb(11, 100, 44)", + "rgb(18, 78, 43)", + "rgb(25, 56, 34)", + "rgb(23, 35, 18)", +] +amp = [ + "rgb(241, 236, 236)", + "rgb(230, 209, 203)", + "rgb(221, 182, 170)", + "rgb(213, 156, 137)", + "rgb(205, 129, 103)", + "rgb(196, 102, 73)", + "rgb(186, 74, 47)", + "rgb(172, 44, 36)", + "rgb(149, 19, 39)", + "rgb(120, 14, 40)", + "rgb(89, 13, 31)", + "rgb(60, 9, 17)", +] +tempo = [ + "rgb(254, 245, 244)", + "rgb(222, 224, 210)", + "rgb(189, 206, 181)", + "rgb(153, 189, 156)", + "rgb(110, 173, 138)", + "rgb(65, 157, 129)", + "rgb(25, 137, 125)", + "rgb(18, 116, 117)", + "rgb(25, 94, 106)", + "rgb(28, 72, 93)", + "rgb(25, 51, 80)", + "rgb(20, 29, 67)", +] +phase = [ + "rgb(167, 119, 12)", + "rgb(197, 96, 51)", + "rgb(217, 67, 96)", + "rgb(221, 38, 163)", + "rgb(196, 59, 224)", + "rgb(153, 97, 244)", + "rgb(95, 127, 228)", + "rgb(40, 144, 183)", + "rgb(15, 151, 136)", + "rgb(39, 153, 79)", + "rgb(119, 141, 17)", + "rgb(167, 119, 12)", +] +balance = [ + "rgb(23, 28, 66)", + "rgb(41, 58, 143)", + "rgb(11, 102, 189)", + "rgb(69, 144, 185)", + "rgb(142, 181, 194)", + "rgb(210, 216, 219)", + "rgb(230, 210, 204)", + "rgb(213, 157, 137)", + "rgb(196, 101, 72)", + "rgb(172, 43, 36)", + "rgb(120, 14, 40)", + "rgb(60, 9, 17)", +] +delta = [ + "rgb(16, 31, 63)", + "rgb(38, 62, 144)", + "rgb(30, 110, 161)", + "rgb(60, 154, 171)", + "rgb(140, 193, 186)", + "rgb(217, 229, 218)", + "rgb(239, 226, 156)", + "rgb(195, 182, 59)", + "rgb(115, 152, 5)", + "rgb(34, 120, 36)", + "rgb(18, 78, 43)", + "rgb(23, 35, 18)", +] +curl = [ + "rgb(20, 29, 67)", + "rgb(28, 72, 93)", + "rgb(18, 115, 117)", + "rgb(63, 156, 129)", + "rgb(153, 189, 156)", + "rgb(223, 225, 211)", + "rgb(241, 218, 206)", + "rgb(224, 160, 137)", + "rgb(203, 101, 99)", + "rgb(164, 54, 96)", + "rgb(111, 23, 91)", + "rgb(51, 13, 53)", +] diff --git a/packages/python/plotly/plotly/colors/colorbrewer.py b/packages/python/plotly/plotly/colors/colorbrewer.py new file mode 100644 index 00000000000..fa2e2b9a981 --- /dev/null +++ b/packages/python/plotly/plotly/colors/colorbrewer.py @@ -0,0 +1,458 @@ +""" +Color scales and sequences from the colorbrewer 2 project + +Learn more at http://colorbrewer2.org + +colorbrewer is made available under an Apache license: http://colorbrewer2.org/export/LICENSE.txt +""" + +from ._swatches import _swatches + + +def swatches(): + return _swatches(__name__, globals()) + + +swatches.__doc__ = _swatches.__doc__ + +BrBG = [ + "rgb(84,48,5)", + "rgb(140,81,10)", + "rgb(191,129,45)", + "rgb(223,194,125)", + "rgb(246,232,195)", + "rgb(245,245,245)", + "rgb(199,234,229)", + "rgb(128,205,193)", + "rgb(53,151,143)", + "rgb(1,102,94)", + "rgb(0,60,48)", +] + +PRGn = [ + "rgb(64,0,75)", + "rgb(118,42,131)", + "rgb(153,112,171)", + "rgb(194,165,207)", + "rgb(231,212,232)", + "rgb(247,247,247)", + "rgb(217,240,211)", + "rgb(166,219,160)", + "rgb(90,174,97)", + "rgb(27,120,55)", + "rgb(0,68,27)", +] + +PiYG = [ + "rgb(142,1,82)", + "rgb(197,27,125)", + "rgb(222,119,174)", + "rgb(241,182,218)", + "rgb(253,224,239)", + "rgb(247,247,247)", + "rgb(230,245,208)", + "rgb(184,225,134)", + "rgb(127,188,65)", + "rgb(77,146,33)", + "rgb(39,100,25)", +] + +PuOr = [ + "rgb(127,59,8)", + "rgb(179,88,6)", + "rgb(224,130,20)", + "rgb(253,184,99)", + "rgb(254,224,182)", + "rgb(247,247,247)", + "rgb(216,218,235)", + "rgb(178,171,210)", + "rgb(128,115,172)", + "rgb(84,39,136)", + "rgb(45,0,75)", +] + +RdBu = [ + "rgb(103,0,31)", + "rgb(178,24,43)", + "rgb(214,96,77)", + "rgb(244,165,130)", + "rgb(253,219,199)", + "rgb(247,247,247)", + "rgb(209,229,240)", + "rgb(146,197,222)", + "rgb(67,147,195)", + "rgb(33,102,172)", + "rgb(5,48,97)", +] + +RdGy = [ + "rgb(103,0,31)", + "rgb(178,24,43)", + "rgb(214,96,77)", + "rgb(244,165,130)", + "rgb(253,219,199)", + "rgb(255,255,255)", + "rgb(224,224,224)", + "rgb(186,186,186)", + "rgb(135,135,135)", + "rgb(77,77,77)", + "rgb(26,26,26)", +] + +RdYlBu = [ + "rgb(165,0,38)", + "rgb(215,48,39)", + "rgb(244,109,67)", + "rgb(253,174,97)", + "rgb(254,224,144)", + "rgb(255,255,191)", + "rgb(224,243,248)", + "rgb(171,217,233)", + "rgb(116,173,209)", + "rgb(69,117,180)", + "rgb(49,54,149)", +] + +RdYlGn = [ + "rgb(165,0,38)", + "rgb(215,48,39)", + "rgb(244,109,67)", + "rgb(253,174,97)", + "rgb(254,224,139)", + "rgb(255,255,191)", + "rgb(217,239,139)", + "rgb(166,217,106)", + "rgb(102,189,99)", + "rgb(26,152,80)", + "rgb(0,104,55)", +] + +Spectral = [ + "rgb(158,1,66)", + "rgb(213,62,79)", + "rgb(244,109,67)", + "rgb(253,174,97)", + "rgb(254,224,139)", + "rgb(255,255,191)", + "rgb(230,245,152)", + "rgb(171,221,164)", + "rgb(102,194,165)", + "rgb(50,136,189)", + "rgb(94,79,162)", +] + +Set1 = [ + "rgb(228,26,28)", + "rgb(55,126,184)", + "rgb(77,175,74)", + "rgb(152,78,163)", + "rgb(255,127,0)", + "rgb(255,255,51)", + "rgb(166,86,40)", + "rgb(247,129,191)", + "rgb(153,153,153)", +] + + +Pastel1 = [ + "rgb(251,180,174)", + "rgb(179,205,227)", + "rgb(204,235,197)", + "rgb(222,203,228)", + "rgb(254,217,166)", + "rgb(255,255,204)", + "rgb(229,216,189)", + "rgb(253,218,236)", + "rgb(242,242,242)", +] +Dark2 = [ + "rgb(27,158,119)", + "rgb(217,95,2)", + "rgb(117,112,179)", + "rgb(231,41,138)", + "rgb(102,166,30)", + "rgb(230,171,2)", + "rgb(166,118,29)", + "rgb(102,102,102)", +] +Set2 = [ + "rgb(102,194,165)", + "rgb(252,141,98)", + "rgb(141,160,203)", + "rgb(231,138,195)", + "rgb(166,216,84)", + "rgb(255,217,47)", + "rgb(229,196,148)", + "rgb(179,179,179)", +] + + +Pastel2 = [ + "rgb(179,226,205)", + "rgb(253,205,172)", + "rgb(203,213,232)", + "rgb(244,202,228)", + "rgb(230,245,201)", + "rgb(255,242,174)", + "rgb(241,226,204)", + "rgb(204,204,204)", +] + +Set3 = [ + "rgb(141,211,199)", + "rgb(255,255,179)", + "rgb(190,186,218)", + "rgb(251,128,114)", + "rgb(128,177,211)", + "rgb(253,180,98)", + "rgb(179,222,105)", + "rgb(252,205,229)", + "rgb(217,217,217)", + "rgb(188,128,189)", + "rgb(204,235,197)", + "rgb(255,237,111)", +] + +Accent = [ + "rgb(127,201,127)", + "rgb(190,174,212)", + "rgb(253,192,134)", + "rgb(255,255,153)", + "rgb(56,108,176)", + "rgb(240,2,127)", + "rgb(191,91,23)", + "rgb(102,102,102)", +] + + +Paired = [ + "rgb(166,206,227)", + "rgb(31,120,180)", + "rgb(178,223,138)", + "rgb(51,160,44)", + "rgb(251,154,153)", + "rgb(227,26,28)", + "rgb(253,191,111)", + "rgb(255,127,0)", + "rgb(202,178,214)", + "rgb(106,61,154)", + "rgb(255,255,153)", + "rgb(177,89,40)", +] + + +Blues = [ + "rgb(247,251,255)", + "rgb(222,235,247)", + "rgb(198,219,239)", + "rgb(158,202,225)", + "rgb(107,174,214)", + "rgb(66,146,198)", + "rgb(33,113,181)", + "rgb(8,81,156)", + "rgb(8,48,107)", +] + +BuGn = [ + "rgb(247,252,253)", + "rgb(229,245,249)", + "rgb(204,236,230)", + "rgb(153,216,201)", + "rgb(102,194,164)", + "rgb(65,174,118)", + "rgb(35,139,69)", + "rgb(0,109,44)", + "rgb(0,68,27)", +] + +BuPu = [ + "rgb(247,252,253)", + "rgb(224,236,244)", + "rgb(191,211,230)", + "rgb(158,188,218)", + "rgb(140,150,198)", + "rgb(140,107,177)", + "rgb(136,65,157)", + "rgb(129,15,124)", + "rgb(77,0,75)", +] + +GnBu = [ + "rgb(247,252,240)", + "rgb(224,243,219)", + "rgb(204,235,197)", + "rgb(168,221,181)", + "rgb(123,204,196)", + "rgb(78,179,211)", + "rgb(43,140,190)", + "rgb(8,104,172)", + "rgb(8,64,129)", +] + +Greens = [ + "rgb(247,252,245)", + "rgb(229,245,224)", + "rgb(199,233,192)", + "rgb(161,217,155)", + "rgb(116,196,118)", + "rgb(65,171,93)", + "rgb(35,139,69)", + "rgb(0,109,44)", + "rgb(0,68,27)", +] + +Greys = [ + "rgb(255,255,255)", + "rgb(240,240,240)", + "rgb(217,217,217)", + "rgb(189,189,189)", + "rgb(150,150,150)", + "rgb(115,115,115)", + "rgb(82,82,82)", + "rgb(37,37,37)", + "rgb(0,0,0)", +] + +OrRd = [ + "rgb(255,247,236)", + "rgb(254,232,200)", + "rgb(253,212,158)", + "rgb(253,187,132)", + "rgb(252,141,89)", + "rgb(239,101,72)", + "rgb(215,48,31)", + "rgb(179,0,0)", + "rgb(127,0,0)", +] + +Oranges = [ + "rgb(255,245,235)", + "rgb(254,230,206)", + "rgb(253,208,162)", + "rgb(253,174,107)", + "rgb(253,141,60)", + "rgb(241,105,19)", + "rgb(217,72,1)", + "rgb(166,54,3)", + "rgb(127,39,4)", +] + +PuBu = [ + "rgb(255,247,251)", + "rgb(236,231,242)", + "rgb(208,209,230)", + "rgb(166,189,219)", + "rgb(116,169,207)", + "rgb(54,144,192)", + "rgb(5,112,176)", + "rgb(4,90,141)", + "rgb(2,56,88)", +] + +PuBuGn = [ + "rgb(255,247,251)", + "rgb(236,226,240)", + "rgb(208,209,230)", + "rgb(166,189,219)", + "rgb(103,169,207)", + "rgb(54,144,192)", + "rgb(2,129,138)", + "rgb(1,108,89)", + "rgb(1,70,54)", +] + +PuRd = [ + "rgb(247,244,249)", + "rgb(231,225,239)", + "rgb(212,185,218)", + "rgb(201,148,199)", + "rgb(223,101,176)", + "rgb(231,41,138)", + "rgb(206,18,86)", + "rgb(152,0,67)", + "rgb(103,0,31)", +] + +Purples = [ + "rgb(252,251,253)", + "rgb(239,237,245)", + "rgb(218,218,235)", + "rgb(188,189,220)", + "rgb(158,154,200)", + "rgb(128,125,186)", + "rgb(106,81,163)", + "rgb(84,39,143)", + "rgb(63,0,125)", +] + +RdPu = [ + "rgb(255,247,243)", + "rgb(253,224,221)", + "rgb(252,197,192)", + "rgb(250,159,181)", + "rgb(247,104,161)", + "rgb(221,52,151)", + "rgb(174,1,126)", + "rgb(122,1,119)", + "rgb(73,0,106)", +] + +Reds = [ + "rgb(255,245,240)", + "rgb(254,224,210)", + "rgb(252,187,161)", + "rgb(252,146,114)", + "rgb(251,106,74)", + "rgb(239,59,44)", + "rgb(203,24,29)", + "rgb(165,15,21)", + "rgb(103,0,13)", +] + +YlGn = [ + "rgb(255,255,229)", + "rgb(247,252,185)", + "rgb(217,240,163)", + "rgb(173,221,142)", + "rgb(120,198,121)", + "rgb(65,171,93)", + "rgb(35,132,67)", + "rgb(0,104,55)", + "rgb(0,69,41)", +] + +YlGnBu = [ + "rgb(255,255,217)", + "rgb(237,248,177)", + "rgb(199,233,180)", + "rgb(127,205,187)", + "rgb(65,182,196)", + "rgb(29,145,192)", + "rgb(34,94,168)", + "rgb(37,52,148)", + "rgb(8,29,88)", +] + +YlOrBr = [ + "rgb(255,255,229)", + "rgb(255,247,188)", + "rgb(254,227,145)", + "rgb(254,196,79)", + "rgb(254,153,41)", + "rgb(236,112,20)", + "rgb(204,76,2)", + "rgb(153,52,4)", + "rgb(102,37,6)", +] + +YlOrRd = [ + "rgb(255,255,204)", + "rgb(255,237,160)", + "rgb(254,217,118)", + "rgb(254,178,76)", + "rgb(253,141,60)", + "rgb(252,78,42)", + "rgb(227,26,28)", + "rgb(189,0,38)", + "rgb(128,0,38)", +] diff --git a/packages/python/plotly/plotly/colors/cyclical.py b/packages/python/plotly/plotly/colors/cyclical.py new file mode 100644 index 00000000000..ec024865df6 --- /dev/null +++ b/packages/python/plotly/plotly/colors/cyclical.py @@ -0,0 +1,127 @@ +""" +Cyclical color scales are appropriate for continuous data that has a natural cyclical \ +structure, such as temporal data (hour of day, day of week, day of year, seasons) or +complex numbers or other phase data. +""" + +from ._swatches import _swatches + + +def swatches(): + return _swatches(__name__, globals()) + + +swatches.__doc__ = _swatches.__doc__ + +Twilight = [ + "#e2d9e2", + "#9ebbc9", + "#6785be", + "#5e43a5", + "#421257", + "#471340", + "#8e2c50", + "#ba6657", + "#ceac94", + "#e2d9e2", +] +IceFire = [ + "#000000", + "#001f4d", + "#003786", + "#0e58a8", + "#217eb8", + "#30a4ca", + "#54c8df", + "#9be4ef", + "#e1e9d1", + "#f3d573", + "#e7b000", + "#da8200", + "#c65400", + "#ac2301", + "#820000", + "#4c0000", + "#040100", +] +Edge = [ + "#313131", + "#3d019d", + "#3810dc", + "#2d47f9", + "#2593ff", + "#2adef6", + "#60fdfa", + "#aefdff", + "#f3f3f1", + "#fffda9", + "#fafd5b", + "#f7da29", + "#ff8e25", + "#f8432d", + "#d90d39", + "#97023d", + "#313131", +] +Phase = [ + "rgb(167, 119, 12)", + "rgb(197, 96, 51)", + "rgb(217, 67, 96)", + "rgb(221, 38, 163)", + "rgb(196, 59, 224)", + "rgb(153, 97, 244)", + "rgb(95, 127, 228)", + "rgb(40, 144, 183)", + "rgb(15, 151, 136)", + "rgb(39, 153, 79)", + "rgb(119, 141, 17)", + "rgb(167, 119, 12)", +] +HSV = [ + "#ff0000", + "#ffa700", + "#afff00", + "#08ff00", + "#00ff9f", + "#00b7ff", + "#0010ff", + "#9700ff", + "#ff00bf", + "#ff0018", +] +mrybm = [ + "#f884f7", + "#f968c4", + "#ea4388", + "#cf244b", + "#b51a15", + "#bd4304", + "#cc6904", + "#d58f04", + "#cfaa27", + "#a19f62", + "#588a93", + "#2269c4", + "#3e3ef0", + "#6b4ef9", + "#956bfa", + "#cd7dfe", +] +mygbm = [ + "#ef55f1", + "#fb84ce", + "#fbafa1", + "#fcd471", + "#f0ed35", + "#c6e516", + "#96d310", + "#61c10b", + "#31ac28", + "#439064", + "#3d719a", + "#284ec8", + "#2e21ea", + "#6324f5", + "#9139fa", + "#c543fa", +] diff --git a/packages/python/plotly/plotly/colors/diverging.py b/packages/python/plotly/plotly/colors/diverging.py new file mode 100644 index 00000000000..66caef7e921 --- /dev/null +++ b/packages/python/plotly/plotly/colors/diverging.py @@ -0,0 +1,31 @@ +""" +Diverging color scales are appropriate for continuous data that has a natural midpoint \ +other otherwise informative special value, such as 0 altitude, or the boiling point +of a liquid. The color scales in this module are \ +mostly meant to be passed in as the `color_continuous_scale` argument to various \ +functions, and to be used with the `color_continuous_midpoint` argument. +""" + +from .colorbrewer import ( # noqa: F401 + BrBG, + PRGn, + PiYG, + PuOr, + RdBu, + RdGy, + RdYlBu, + RdYlGn, + Spectral, +) +from .cmocean import balance, delta, curl # noqa: F401 +from .carto import Armyrose, Fall, Geyser, Temps, Tealrose, Tropic, Earth # noqa: F401 + + +from ._swatches import _swatches + + +def swatches(): + return _swatches(__name__, globals()) + + +swatches.__doc__ = _swatches.__doc__ diff --git a/packages/python/plotly/plotly/colors/qualitative.py b/packages/python/plotly/plotly/colors/qualitative.py new file mode 100644 index 00000000000..ca934f0d2e2 --- /dev/null +++ b/packages/python/plotly/plotly/colors/qualitative.py @@ -0,0 +1,147 @@ +""" +Qualitative color sequences are appropriate for data that has no natural ordering, such \ +as categories, colors, names, countries etc. The color sequences in this module are \ +mostly meant to be passed in as the `color_discrete_sequence` argument to various functions. +""" + +from ._swatches import _swatches + + +def swatches(): + return _swatches(__name__, globals()) + + +swatches.__doc__ = _swatches.__doc__ + +Plotly = [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#19d3f3", + "#e763fa", + "#fecb52", + "#ffa15a", + "#ff6692", + "#b6e880", +] + +D3 = [ + "#1f77b4", + "#ff7f0e", + "#2ca02c", + "#d62728", + "#9467bd", + "#8c564b", + "#e377c2", + "#7f7f7f", + "#bcbd22", + "#17becf", +] +G10 = [ + "#3366cc", + "#dc3912", + "#ff9900", + "#109618", + "#990099", + "#0099c6", + "#dd4477", + "#66aa00", + "#b82e2e", + "#316395", +] +T10 = [ + "#4c78a8", + "#f58518", + "#e45756", + "#72b7b2", + "#54a24b", + "#eeca3b", + "#b279a2", + "#ff9da6", + "#9d755d", + "#bab0ac", +] +Alphabet = [ + "#AA0DFE", + "#3283FE", + "#85660D", + "#782AB6", + "#565656", + "#1C8356", + "#16FF32", + "#F7E1A0", + "#E2E2E2", + "#1CBE4F", + "#C4451C", + "#DEA0FD", + "#FE00FA", + "#325A9B", + "#FEAF16", + "#F8A19F", + "#90AD1C", + "#F6222E", + "#1CFFCE", + "#2ED9FF", + "#B10DA1", + "#C075A6", + "#FC1CBF", + "#B00068", + "#FBE426", + "#FA0087", +] +Dark24 = [ + "#2E91E5", + "#E15F99", + "#1CA71C", + "#FB0D0D", + "#DA16FF", + "#222A2A", + "#B68100", + "#750D86", + "#EB663B", + "#511CFB", + "#00A08B", + "#FB00D1", + "#FC0080", + "#B2828D", + "#6C7C32", + "#778AAE", + "#862A16", + "#A777F1", + "#620042", + "#1616A7", + "#DA60CA", + "#6C4516", + "#0D2A63", + "#AF0038", +] +Light24 = [ + "#FD3216", + "#00FE35", + "#6A76FC", + "#FED4C4", + "#FE00CE", + "#0DF9FF", + "#F6F926", + "#FF9616", + "#479B55", + "#EEA6FB", + "#DC587D", + "#D626FF", + "#6E899C", + "#00B5F7", + "#B68E00", + "#C9FBE5", + "#FF0092", + "#22FFA7", + "#E3EE9E", + "#86CE00", + "#BC7196", + "#7E7DCD", + "#FC6955", + "#E48F72", +] + +from .colorbrewer import Set1, Pastel1, Dark2, Set2, Pastel2, Set3 # noqa: F401 +from .carto import Antique, Bold, Pastel, Prism, Safe, Vivid # noqa: F401 diff --git a/packages/python/plotly/plotly/colors/sequential.py b/packages/python/plotly/plotly/colors/sequential.py new file mode 100644 index 00000000000..a860f3f37b4 --- /dev/null +++ b/packages/python/plotly/plotly/colors/sequential.py @@ -0,0 +1,154 @@ +""" +Sequential color scales are appropriate for most continuous data, but in some cases it \ +can be helpful to use a `plotly_express.colors.diverging` or \ +`plotly_express.colors.cyclical` scale instead. The color scales in this module are \ +mostly meant to be passed in as the `color_continuous_scale` argument to various functions. +""" + +from ._swatches import _swatches + + +def swatches(): + return _swatches(__name__, globals()) + + +swatches.__doc__ = _swatches.__doc__ + +Plotly = [ + "#0508b8", + "#1910d8", + "#3c19f0", + "#6b1cfb", + "#981cfd", + "#bf1cfd", + "#dd2bfd", + "#f246fe", + "#fc67fd", + "#fe88fc", + "#fea5fd", + "#febefe", + "#fec3fe", +] + +Viridis = [ + "#440154", + "#482878", + "#3e4989", + "#31688e", + "#26828e", + "#1f9e89", + "#35b779", + "#6ece58", + "#b5de2b", + "#fde725", +] +Cividis = [ + "#00224e", + "#123570", + "#3b496c", + "#575d6d", + "#707173", + "#8a8678", + "#a59c74", + "#c3b369", + "#e1cc55", + "#fee838", +] + +Inferno = [ + "#000004", + "#1b0c41", + "#4a0c6b", + "#781c6d", + "#a52c60", + "#cf4446", + "#ed6925", + "#fb9b06", + "#f7d13d", + "#fcffa4", +] +Magma = [ + "#000004", + "#180f3d", + "#440f76", + "#721f81", + "#9e2f7f", + "#cd4071", + "#f1605d", + "#fd9668", + "#feca8d", + "#fcfdbf", +] +Plasma = [ + "#0d0887", + "#46039f", + "#7201a8", + "#9c179e", + "#bd3786", + "#d8576b", + "#ed7953", + "#fb9f3a", + "#fdca26", + "#f0f921", +] + +from .colorbrewer import ( # noqa: F401 + Blues, + BuGn, + BuPu, + GnBu, + Greens, + Greys, + OrRd, + Oranges, + PuBu, + PuBuGn, + PuRd, + Purples, + RdPu, + Reds, + YlGn, + YlGnBu, + YlOrBr, + YlOrRd, +) + +from .cmocean import ( # noqa: F401 + turbid, + thermal, + haline, + solar, + ice, + gray, + deep, + dense, + algae, + matter, + speed, + amp, + tempo, +) + +from .carto import ( # noqa: F401 + Burg, + Burgyl, + Redor, + Oryel, + Peach, + Pinkyl, + Mint, + Blugrn, + Darkmint, + Emrld, + Aggrnyl, + Bluyl, + Teal, + Tealgrn, + Purp, + Purpor, + Sunset, + Magenta, + Sunsetdark, + Agsunset, + Brwnyl, +) diff --git a/packages/python/plotly/plotly/data/__init__.py b/packages/python/plotly/plotly/data/__init__.py new file mode 100644 index 00000000000..2a83f8719ff --- /dev/null +++ b/packages/python/plotly/plotly/data/__init__.py @@ -0,0 +1,78 @@ +""" +Built-in datasets for demonstration, educational and test purposes. +""" + + +def gapminder(): + """ + Each row represents a country on a given year. + + https://www.gapminder.org/data/ + + Returns: + A `pandas.DataFrame` with 1704 rows and the following columns: `['country', 'continent', 'year', 'lifeExp', 'pop', 'gdpPercap', + 'iso_alpha', 'iso_num']`. + """ + return _get_dataset("gapminder") + + +def tips(): + """ + Each row represents a restaurant bill. + + https://vincentarelbundock.github.io/Rdatasets/doc/reshape2/tips.html + + Returns: + A `pandas.DataFrame` with 244 rows and the following columns: `['total_bill', 'tip', 'sex', 'smoker', 'day', 'time', 'size']`. + """ + return _get_dataset("tips") + + +def iris(): + """ + Each row represents a flower. + + https://en.wikipedia.org/wiki/Iris_flower_data_set + + Returns: + A `pandas.DataFrame` with 150 rows and the following columns: `['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species', + 'species_id']`. + """ + return _get_dataset("iris") + + +def wind(): + """ + Each row represents a level of wind intensity in a cardinal direction. + + Returns: + A `pandas.DataFrame` with 128 rows and the following columns: `['direction', 'strength', 'value']`. + """ + return _get_dataset("wind") + + +def election(): + """ + Each row represents voting results for an electoral district in the 2013 Montreal mayoral election. + + Returns: + A `pandas.DataFrame` with 58 rows and the following columns: `['district', 'Coderre', 'Bergeron', 'Joly', 'total', 'winner', 'result']`. + """ + return _get_dataset("election") + + +def carshare(): + """ + Each row represents the availability of car-sharing services near the centroid of a zone in Montreal. + + Returns: + A `pandas.DataFrame` with 249 rows and the following columns: `['centroid_lat', 'centroid_lon', 'car_hours', 'peak_hour']`. + """ + return _get_dataset("carshare") + + +def _get_dataset(d): + import pandas + import os + + return pandas.read_csv(os.path.join(os.path.dirname(__file__), d + ".csv.gz")) diff --git a/packages/python/plotly/plotly/express/__init__.py b/packages/python/plotly/plotly/express/__init__.py new file mode 100644 index 00000000000..01d899fb1ab --- /dev/null +++ b/packages/python/plotly/plotly/express/__init__.py @@ -0,0 +1,74 @@ +""" +`plotly_express` is a terse, consistent, high-level wrapper around `plotly` for rapid \ +data exploration and figure generation. See the gallery at https://plotly.github.io/plotly_express +""" + +__version__ = "0.3.0" + +from ._chart_types import ( # noqa: F401 + scatter, + scatter_3d, + scatter_polar, + scatter_ternary, + scatter_mapbox, + scatter_geo, + line, + line_3d, + line_polar, + line_ternary, + line_mapbox, + line_geo, + area, + bar, + bar_polar, + violin, + box, + strip, + histogram, + scatter_matrix, + parallel_coordinates, + parallel_categories, + choropleth, + density_contour, + density_heatmap, +) + +from ._core import ( # noqa: F401 + set_mapbox_access_token, + defaults, + get_trendline_results, +) + +from . import data, colors # noqa: F401 + +__all__ = [ + "scatter", + "scatter_3d", + "scatter_polar", + "scatter_ternary", + "scatter_mapbox", + "scatter_geo", + "scatter_matrix", + "density_contour", + "density_heatmap", + "line", + "line_polar", + "line_ternary", + "line_mapbox", + "line_geo", + "parallel_coordinates", + "parallel_categories", + "area", + "bar", + "bar_polar", + "violin", + "box", + "strip", + "histogram", + "choropleth", + "data", + "colors", + "set_mapbox_access_token", + "get_trendline_results", + "ExpressFigure", +] diff --git a/packages/python/plotly/plotly/express/_chart_types.py b/packages/python/plotly/plotly/express/_chart_types.py new file mode 100644 index 00000000000..c6f2fc099a0 --- /dev/null +++ b/packages/python/plotly/plotly/express/_chart_types.py @@ -0,0 +1,1062 @@ +from ._core import make_figure +from ._doc import make_docstring +import plotly.graph_objs as go + + +def scatter( + data_frame, + x=None, + y=None, + color=None, + symbol=None, + size=None, + hover_name=None, + hover_data=None, + text=None, + facet_row=None, + facet_col=None, + error_x=None, + error_x_minus=None, + error_y=None, + error_y_minus=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, + symbol_sequence=None, + symbol_map={}, + opacity=None, + size_max=None, + marginal_x=None, + marginal_y=None, + trendline=None, + trendline_color_override=None, + log_x=False, + log_y=False, + range_x=None, + range_y=None, + render_mode="auto", + title=None, + template=None, + width=None, + height=None, +): + """ + In a scatter plot, each row of `data_frame` is represented by a symbol mark in 2D space. + """ + return make_figure(args=locals(), constructor=go.Scatter) + + +scatter.__doc__ = make_docstring(scatter) + + +def density_contour( + data_frame, + x=None, + y=None, + z=None, + color=None, + facet_row=None, + facet_col=None, + hover_name=None, + hover_data=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + marginal_x=None, + marginal_y=None, + trendline=None, + trendline_color_override=None, + log_x=False, + log_y=False, + range_x=None, + range_y=None, + histfunc=None, + histnorm=None, + nbinsx=None, + nbinsy=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a density contour plot, rows of `data_frame` are grouped together into contour marks to \ + visualize the 2D distribution of an aggregate function `histfunc` (e.g. the count or sum) \ + of the value `z`. + """ + return make_figure( + args=locals(), + constructor=go.Histogram2dContour, + trace_patch=dict( + contours=dict(coloring="none"), + histfunc=histfunc, + histnorm=histnorm, + nbinsx=nbinsx, + nbinsy=nbinsy, + xbingroup="x", + ybingroup="y", + ), + ) + + +density_contour.__doc__ = make_docstring(density_contour) + + +def density_heatmap( + data_frame, + x=None, + y=None, + z=None, + facet_row=None, + facet_col=None, + hover_name=None, + hover_data=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, + marginal_x=None, + marginal_y=None, + opacity=None, + log_x=False, + log_y=False, + range_x=None, + range_y=None, + histfunc=None, + histnorm=None, + nbinsx=None, + nbinsy=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a density heatmap, rows of `data_frame` are grouped together into colored \ + rectangular tiles to visualize the 2D distribution of an aggregate function \ + `histfunc` (e.g. the count or sum) of the value `z`. + """ + return make_figure( + args=locals(), + constructor=go.Histogram2d, + trace_patch=dict( + histfunc=histfunc, + histnorm=histnorm, + nbinsx=nbinsx, + nbinsy=nbinsy, + xbingroup="x", + ybingroup="y", + ), + ) + + +density_heatmap.__doc__ = make_docstring(density_heatmap) + + +def line( + data_frame, + x=None, + y=None, + line_group=None, + color=None, + line_dash=None, + hover_name=None, + hover_data=None, + text=None, + facet_row=None, + facet_col=None, + error_x=None, + error_x_minus=None, + error_y=None, + error_y_minus=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + line_dash_sequence=None, + line_dash_map={}, + log_x=False, + log_y=False, + range_x=None, + range_y=None, + line_shape=None, + render_mode="auto", + title=None, + template=None, + width=None, + height=None, +): + """ + In a 2D line plot, each row of `data_frame` is represented as vertex of a polyline mark in 2D space. + """ + return make_figure(args=locals(), constructor=go.Scatter) + + +line.__doc__ = make_docstring(line) + + +def area( + data_frame, + x=None, + y=None, + line_group=None, + color=None, + hover_name=None, + hover_data=None, + text=None, + facet_row=None, + facet_col=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + orientation="v", + groupnorm=None, + log_x=False, + log_y=False, + range_x=None, + range_y=None, + line_shape=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a stacked area plot, each row of `data_frame` is represented as vertex of a polyline mark in 2D space. The area between successive polylines is filled. + """ + return make_figure( + args=locals(), + constructor=go.Scatter, + trace_patch=dict( + stackgroup=1, mode="lines", orientation=orientation, groupnorm=groupnorm + ), + ) + + +area.__doc__ = make_docstring(area) + + +def bar( + data_frame, + x=None, + y=None, + color=None, + facet_row=None, + facet_col=None, + hover_name=None, + hover_data=None, + text=None, + error_x=None, + error_x_minus=None, + error_y=None, + error_y_minus=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, + opacity=None, + orientation="v", + barmode="relative", + log_x=False, + log_y=False, + range_x=None, + range_y=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a bar plot, each row of `data_frame` is represented as a rectangular mark. + """ + return make_figure( + args=locals(), + constructor=go.Bar, + trace_patch=dict(orientation=orientation, textposition="auto"), + layout_patch=dict(barmode=barmode), + ) + + +bar.__doc__ = make_docstring(bar) + + +def histogram( + data_frame, + x=None, + y=None, + color=None, + facet_row=None, + facet_col=None, + hover_name=None, + hover_data=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + marginal=None, + opacity=None, + orientation="v", + barmode="relative", + barnorm=None, + histnorm=None, + log_x=False, + log_y=False, + range_x=None, + range_y=None, + histfunc=None, + cumulative=None, + nbins=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a histogram, rows of `data_frame` are grouped together into a rectangular mark to \ + visualize the 1D distribution of an aggregate function `histfunc` (e.g. the count or sum) \ + of the value `y` (or `x` if `orientation` is `'h'`). + """ + return make_figure( + args=locals(), + constructor=go.Histogram, + trace_patch=dict( + orientation=orientation, + histnorm=histnorm, + histfunc=histfunc, + nbinsx=nbins if orientation == "v" else None, + nbinsy=None if orientation == "v" else nbins, + cumulative=dict(enabled=cumulative), + bingroup="x" if orientation == "v" else "y", + ), + layout_patch=dict(barmode=barmode, barnorm=barnorm), + ) + + +histogram.__doc__ = make_docstring(histogram) + + +def violin( + data_frame, + x=None, + y=None, + color=None, + facet_row=None, + facet_col=None, + hover_name=None, + hover_data=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + orientation="v", + violinmode="group", + log_x=False, + log_y=False, + range_x=None, + range_y=None, + points=None, + box=False, + title=None, + template=None, + width=None, + height=None, +): + """ + In a violin plot, rows of `data_frame` are grouped together into a curved mark to \ + visualize their distribution. + """ + return make_figure( + args=locals(), + constructor=go.Violin, + trace_patch=dict( + orientation=orientation, + points=points, + box=dict(visible=box), + scalegroup=True, + x0=" ", + y0=" ", + ), + layout_patch=dict(violinmode=violinmode), + ) + + +violin.__doc__ = make_docstring(violin) + + +def box( + data_frame, + x=None, + y=None, + color=None, + facet_row=None, + facet_col=None, + hover_name=None, + hover_data=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + orientation="v", + boxmode="group", + log_x=False, + log_y=False, + range_x=None, + range_y=None, + points=None, + notched=False, + title=None, + template=None, + width=None, + height=None, +): + """ + In a box plot, rows of `data_frame` are grouped together into a box-and-whisker mark to \ + visualize their distribution. + """ + return make_figure( + args=locals(), + constructor=go.Box, + trace_patch=dict( + orientation=orientation, boxpoints=points, notched=notched, x0=" ", y0=" " + ), + layout_patch=dict(boxmode=boxmode), + ) + + +box.__doc__ = make_docstring(box) + + +def strip( + data_frame, + x=None, + y=None, + color=None, + facet_row=None, + facet_col=None, + hover_name=None, + hover_data=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + orientation="v", + stripmode="group", + log_x=False, + log_y=False, + range_x=None, + range_y=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a strip plot each row of `data_frame` is represented as a jittered mark within categories. + """ + return make_figure( + args=locals(), + constructor=go.Box, + trace_patch=dict( + orientation=orientation, + boxpoints="all", + pointpos=0, + hoveron="points", + fillcolor="rgba(255,255,255,0)", + line={"color": "rgba(255,255,255,0)"}, + x0=" ", + y0=" ", + ), + layout_patch=dict(boxmode=stripmode), + ) + + +strip.__doc__ = make_docstring(strip) + + +def scatter_3d( + data_frame, + x=None, + y=None, + z=None, + color=None, + symbol=None, + size=None, + text=None, + hover_name=None, + hover_data=None, + error_x=None, + error_x_minus=None, + error_y=None, + error_y_minus=None, + error_z=None, + error_z_minus=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + size_max=None, + color_discrete_sequence=None, + color_discrete_map={}, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, + symbol_sequence=None, + symbol_map={}, + opacity=None, + log_x=False, + log_y=False, + log_z=False, + range_x=None, + range_y=None, + range_z=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a 3D scatter plot, each row of `data_frame` is represented by a symbol mark in 3D space. + """ + return make_figure(args=locals(), constructor=go.Scatter3d) + + +scatter_3d.__doc__ = make_docstring(scatter_3d) + + +def line_3d( + data_frame, + x=None, + y=None, + z=None, + color=None, + line_dash=None, + text=None, + line_group=None, + hover_name=None, + hover_data=None, + error_x=None, + error_x_minus=None, + error_y=None, + error_y_minus=None, + error_z=None, + error_z_minus=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + line_dash_sequence=None, + line_dash_map={}, + log_x=False, + log_y=False, + log_z=False, + range_x=None, + range_y=None, + range_z=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a 3D line plot, each row of `data_frame` is represented as vertex of a polyline mark in 3D space. + """ + return make_figure(args=locals(), constructor=go.Scatter3d) + + +line_3d.__doc__ = make_docstring(line_3d) + + +def scatter_ternary( + data_frame, + a=None, + b=None, + c=None, + color=None, + symbol=None, + size=None, + text=None, + hover_name=None, + hover_data=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, + symbol_sequence=None, + symbol_map={}, + opacity=None, + size_max=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a ternary scatter plot, each row of `data_frame` is represented by a symbol mark in ternary coordinates. + """ + return make_figure(args=locals(), constructor=go.Scatterternary) + + +scatter_ternary.__doc__ = make_docstring(scatter_ternary) + + +def line_ternary( + data_frame, + a=None, + b=None, + c=None, + color=None, + line_dash=None, + line_group=None, + hover_name=None, + hover_data=None, + text=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + line_dash_sequence=None, + line_dash_map={}, + line_shape=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a ternary line plot, each row of `data_frame` is represented as vertex of a polyline mark in ternary coordinates. + """ + return make_figure(args=locals(), constructor=go.Scatterternary) + + +line_ternary.__doc__ = make_docstring(line_ternary) + + +def scatter_polar( + data_frame, + r=None, + theta=None, + color=None, + symbol=None, + size=None, + hover_name=None, + hover_data=None, + text=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, + symbol_sequence=None, + symbol_map={}, + opacity=None, + direction="clockwise", + start_angle=90, + size_max=None, + range_r=None, + log_r=False, + render_mode="auto", + title=None, + template=None, + width=None, + height=None, +): + """ + In a polar scatter plot, each row of `data_frame` is represented by a symbol mark in + polar coordinates. + """ + return make_figure(args=locals(), constructor=go.Scatterpolar) + + +scatter_polar.__doc__ = make_docstring(scatter_polar) + + +def line_polar( + data_frame, + r=None, + theta=None, + color=None, + line_dash=None, + hover_name=None, + hover_data=None, + line_group=None, + text=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + line_dash_sequence=None, + line_dash_map={}, + direction="clockwise", + start_angle=90, + line_close=False, + line_shape=None, + render_mode="auto", + range_r=None, + log_r=False, + title=None, + template=None, + width=None, + height=None, +): + """ + In a polar line plot, each row of `data_frame` is represented as vertex of a polyline mark in polar coordinates. + """ + return make_figure(args=locals(), constructor=go.Scatterpolar) + + +line_polar.__doc__ = make_docstring(line_polar) + + +def bar_polar( + data_frame, + r=None, + theta=None, + color=None, + hover_name=None, + hover_data=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + barnorm="", + barmode="relative", + direction="clockwise", + start_angle=90, + range_r=None, + log_r=False, + title=None, + template=None, + width=None, + height=None, +): + """ + In a polar bar plot, each row of `data_frame` is represented as a wedge mark in polar coordinates. + """ + return make_figure( + args=locals(), + constructor=go.Barpolar, + layout_patch=dict(barnorm=barnorm, barmode=barmode), + ) + + +bar_polar.__doc__ = make_docstring(bar_polar) + + +def choropleth( + data_frame, + lat=None, + lon=None, + locations=None, + locationmode=None, + color=None, + hover_name=None, + hover_data=None, + size=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, + size_max=None, + projection=None, + scope=None, + center=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a choropleth map, each row of `data_frame` is represented by a colored region mark on a map. + """ + return make_figure( + args=locals(), + constructor=go.Choropleth, + trace_patch=dict(locationmode=locationmode), + ) + + +choropleth.__doc__ = make_docstring(choropleth) + + +def scatter_geo( + data_frame, + lat=None, + lon=None, + locations=None, + locationmode=None, + color=None, + text=None, + hover_name=None, + hover_data=None, + size=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, + opacity=None, + size_max=None, + projection=None, + scope=None, + center=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a geographic scatter plot, each row of `data_frame` is represented by a symbol mark on a map. + """ + return make_figure( + args=locals(), + constructor=go.Scattergeo, + trace_patch=dict(locationmode=locationmode), + ) + + +scatter_geo.__doc__ = make_docstring(scatter_geo) + + +def line_geo( + data_frame, + lat=None, + lon=None, + locations=None, + locationmode=None, + color=None, + line_dash=None, + text=None, + hover_name=None, + hover_data=None, + line_group=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + line_dash_sequence=None, + line_dash_map={}, + projection=None, + scope=None, + center=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a geographic line plot, each row of `data_frame` is represented as vertex of a polyline mark on a map. + """ + return make_figure( + args=locals(), + constructor=go.Scattergeo, + trace_patch=dict(locationmode=locationmode), + ) + + +line_geo.__doc__ = make_docstring(line_geo) + + +def scatter_mapbox( + data_frame, + lat=None, + lon=None, + color=None, + text=None, + hover_name=None, + hover_data=None, + size=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, + opacity=None, + size_max=None, + zoom=8, + title=None, + template=None, + width=None, + height=None, +): + """ + In a Mapbox scatter plot, each row of `data_frame` is represented by a symbol mark on a Mapbox map. + """ + return make_figure(args=locals(), constructor=go.Scattermapbox) + + +scatter_mapbox.__doc__ = make_docstring(scatter_mapbox) + + +def line_mapbox( + data_frame, + lat=None, + lon=None, + color=None, + text=None, + hover_name=None, + hover_data=None, + line_group=None, + animation_frame=None, + animation_group=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + zoom=8, + title=None, + template=None, + width=None, + height=None, +): + """ + In a Mapbox line plot, each row of `data_frame` is represented as vertex of a polyline mark on a Mapbox map. + """ + return make_figure(args=locals(), constructor=go.Scattermapbox) + + +line_mapbox.__doc__ = make_docstring(line_mapbox) + + +def scatter_matrix( + data_frame, + dimensions=None, + color=None, + symbol=None, + size=None, + hover_name=None, + hover_data=None, + category_orders={}, + labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, + symbol_sequence=None, + symbol_map={}, + opacity=None, + size_max=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a scatter plot matrix (or SPLOM), each row of `data_frame` is represented \ + by a multiple symbol marks, one in each cell of a grid of 2D scatter plots, which \ + plot each pair of `dimensions` against each other. + """ + return make_figure( + args=locals(), constructor=go.Splom, layout_patch=dict(dragmode="select") + ) + + +scatter_matrix.__doc__ = make_docstring(scatter_matrix) + + +def parallel_coordinates( + data_frame, + dimensions=None, + color=None, + labels={}, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a parallel coordinates plot, each row of `data_frame` is represented \ + by a polyline mark which traverses a set of parallel axes, one for each of the \ + `dimensions`. + """ + return make_figure(args=locals(), constructor=go.Parcoords) + + +parallel_coordinates.__doc__ = make_docstring(parallel_coordinates) + + +def parallel_categories( + data_frame, + dimensions=None, + color=None, + labels={}, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, + title=None, + template=None, + width=None, + height=None, +): + """ + In a parallel categories (or parallel sets) plot, each row of `data_frame` is \ + grouped with other rows that share the same values of `dimensions` and then plotted \ + as a polyline mark through a set of parallel axes, one for each of the `dimensions`. + """ + return make_figure(args=locals(), constructor=go.Parcats) + + +parallel_categories.__doc__ = make_docstring(parallel_categories) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py new file mode 100644 index 00000000000..0659fa0d696 --- /dev/null +++ b/packages/python/plotly/plotly/express/_core.py @@ -0,0 +1,1116 @@ +import plotly.graph_objs as go +import plotly.io as pio +from collections import namedtuple, OrderedDict +from .colors import qualitative, sequential +import math +import pandas + +from plotly.subplots import ( + make_subplots, + _set_trace_grid_reference, + _subplot_type_for_trace_type, +) + + +class PxDefaults(object): + def __init__(self): + self.template = None + self.width = None + self.height = 600 + self.color_discrete_sequence = None + self.color_continuous_scale = None + self.symbol_sequence = ["circle", "diamond", "square", "x", "cross"] + self.line_dash_sequence = ["solid", "dot", "dash", "longdash", "dashdot"] + [ + "longdashdot" + ] + self.size_max = 20 + + +defaults = PxDefaults() +del PxDefaults + +MAPBOX_TOKEN = "" + + +def set_mapbox_access_token(token): + """ + Arguments: + token: A Mapbox token to be used in `plotly_express.scatter_mapbox` and \ + `plotly_express.line_mapbox` figures. See \ + https://docs.mapbox.com/help/how-mapbox-works/access-tokens/ for more details + """ + global MAPBOX_TOKEN + MAPBOX_TOKEN = token + + +def get_trendline_results(fig): + """ + Extracts fit statistics for trendlines (when applied to figures generated with + the `trendline` argument set to `"ols"`). + + Arguments: + fig: the output of a `plotly_express` charting call + Returns: + A `pandas.DataFrame` with a column "px_fit_results" containing the `statsmodels` + results objects, along with columns identifying the subset of the data the + trendline was fit on. + """ + return fig._px_trendlines + + +Mapping = namedtuple( + "Mapping", + [ + "show_in_trace_name", + "grouper", + "val_map", + "sequence", + "updater", + "variable", + "facet", + ], +) +TraceSpec = namedtuple("TraceSpec", ["constructor", "attrs", "trace_patch", "marginal"]) + + +def get_label(args, column): + try: + return args["labels"][column] + except Exception: + return column + + +def get_decorated_label(args, column, role): + label = get_label(args, column) + if "histfunc" in args and ( + (role == "x" and "orientation" in args and args["orientation"] == "h") + or (role == "y" and "orientation" in args and args["orientation"] == "v") + or (role == "z") + ): + if label: + return "%s of %s" % (args["histfunc"] or "count", label) + else: + return "count" + else: + return label + + +def make_mapping(args, variable): + if variable == "line_group" or variable == "animation_frame": + return Mapping( + show_in_trace_name=False, + grouper=args[variable], + val_map={}, + sequence=[""], + variable=variable, + updater=(lambda trace, v: v), + facet=None, + ) + if variable == "facet_row" or variable == "facet_col": + letter = "x" if variable == "facet_col" else "y" + return Mapping( + show_in_trace_name=False, + variable=letter, + grouper=args[variable], + val_map={}, + sequence=[i for i in range(1, 1000)], + updater=(lambda trace, v: v), + facet="row" if variable == "facet_row" else "col", + ) + (parent, variable) = variable.split(".") + vprefix = variable + arg_name = variable + if variable == "color": + vprefix = "color_discrete" + if variable == "dash": + arg_name = "line_dash" + vprefix = "line_dash" + return Mapping( + show_in_trace_name=True, + variable=variable, + grouper=args[arg_name], + val_map=args[vprefix + "_map"].copy(), + sequence=args[vprefix + "_sequence"], + updater=lambda trace, v: trace.update({parent: {variable: v}}), + facet=None, + ) + + +def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): + + if "line_close" in args and args["line_close"]: + g = g.append(g.iloc[0]) + result = trace_spec.trace_patch.copy() or {} + fit_results = None + hover_header = "" + for k in trace_spec.attrs: + v = args[k] + v_label = get_decorated_label(args, v, k) + if k == "dimensions": + dims = [ + (name, column) + for (name, column) in g.iteritems() + if ((not v) or (name in v)) + and ( + trace_spec.constructor != go.Parcoords + or args["data_frame"][name].dtype.kind in "bifc" + ) + and ( + trace_spec.constructor != go.Parcats + or len(args["data_frame"][name].unique()) <= 20 + ) + ] + result["dimensions"] = [ + dict(label=get_label(args, name), values=column.values) + for (name, column) in dims + ] + if trace_spec.constructor == go.Splom: + for d in result["dimensions"]: + d["axis"] = dict(matches=True) + mapping_labels["%{xaxis.title.text}"] = "%{x}" + mapping_labels["%{yaxis.title.text}"] = "%{y}" + + elif ( + v is not None + or (trace_spec.constructor == go.Histogram and k in ["x", "y"]) + or ( + trace_spec.constructor in [go.Histogram2d, go.Histogram2dContour] + and k == "z" + ) + ): + if k == "size": + if "marker" not in result: + result["marker"] = dict() + result["marker"]["size"] = g[v] + result["marker"]["sizemode"] = "area" + result["marker"]["sizeref"] = sizeref + mapping_labels[v_label] = "%{marker.size}" + elif k == "marginal_x": + if trace_spec.constructor == go.Histogram: + mapping_labels["count"] = "%{y}" + elif k == "marginal_y": + if trace_spec.constructor == go.Histogram: + mapping_labels["count"] = "%{x}" + elif k == "trendline": + if v in ["ols", "lowess"] and args["x"] and args["y"] and len(g) > 1: + import statsmodels.api as sm + import numpy as np + + # sorting is bad but trace_specs with "trendline" have no other attrs + g2 = g.sort_values(by=args["x"]) + y = g2[args["y"]] + x = g2[args["x"]] + result["x"] = x + + if x.dtype.type == np.datetime64: + x = x.astype(int) / 10 ** 9 # convert to unix epoch seconds + + if v == "lowess": + trendline = sm.nonparametric.lowess(y, x) + result["y"] = trendline[:, 1] + hover_header = "LOWESS trendline

" + elif v == "ols": + fit_results = sm.OLS(y, sm.add_constant(x)).fit() + result["y"] = fit_results.predict() + hover_header = "OLS trendline
" + hover_header += "%s = %f * %s + %f
" % ( + args["y"], + fit_results.params[1], + args["x"], + fit_results.params[0], + ) + hover_header += ( + "R2=%f

" % fit_results.rsquared + ) + mapping_labels[get_label(args, args["x"])] = "%{x}" + mapping_labels[get_label(args, args["y"])] = "%{y} (trend)" + + elif k.startswith("error"): + error_xy = k[:7] + arr = "arrayminus" if k.endswith("minus") else "array" + if error_xy not in result: + result[error_xy] = {} + result[error_xy][arr] = g[v] + elif k == "hover_name": + if trace_spec.constructor not in [ + go.Histogram, + go.Histogram2d, + go.Histogram2dContour, + ]: + result["hovertext"] = g[v] + if hover_header == "": + hover_header = "%{hovertext}

" + elif k == "hover_data": + if trace_spec.constructor not in [ + go.Histogram, + go.Histogram2d, + go.Histogram2dContour, + ]: + result["customdata"] = g[v].values + for i, col in enumerate(v): + v_label_col = get_decorated_label(args, col, None) + mapping_labels[v_label_col] = "%%{customdata[%d]}" % i + elif k == "color": + if trace_spec.constructor == go.Choropleth: + result["z"] = g[v] + result["coloraxis"] = "coloraxis1" + mapping_labels[v_label] = "%{z}" + else: + colorable = "marker" + if trace_spec.constructor in [go.Parcats, go.Parcoords]: + colorable = "line" + if colorable not in result: + result[colorable] = dict() + result[colorable]["color"] = g[v] + result[colorable]["coloraxis"] = "coloraxis1" + mapping_labels[v_label] = "%%{%s.color}" % colorable + elif k == "animation_group": + result["ids"] = g[v] + elif k == "locations": + result[k] = g[v] + mapping_labels[v_label] = "%{location}" + else: + if v: + result[k] = g[v] + mapping_labels[v_label] = "%%{%s}" % k + if trace_spec.constructor not in [go.Parcoords, go.Parcats]: + hover_lines = [k + "=" + v for k, v in mapping_labels.items()] + result["hovertemplate"] = hover_header + "
".join(hover_lines) + return result, fit_results + + +def configure_axes(args, constructor, fig, orders): + configurators = { + go.Scatter: configure_cartesian_axes, + go.Scattergl: configure_cartesian_axes, + go.Bar: configure_cartesian_axes, + go.Box: configure_cartesian_axes, + go.Violin: configure_cartesian_axes, + go.Histogram: configure_cartesian_axes, + go.Histogram2dContour: configure_cartesian_axes, + go.Histogram2d: configure_cartesian_axes, + go.Scatter3d: configure_3d_axes, + go.Scatterternary: configure_ternary_axes, + go.Scatterpolar: configure_polar_axes, + go.Scatterpolargl: configure_polar_axes, + go.Barpolar: configure_polar_axes, + go.Scattermapbox: configure_mapbox, + go.Scattergeo: configure_geo, + go.Choropleth: configure_geo, + } + if constructor in configurators: + configurators[constructor](args, fig, orders) + + +def set_cartesian_axis_opts(args, axis, letter, orders): + log_key = "log_" + letter + range_key = "range_" + letter + if log_key in args and args[log_key]: + axis["type"] = "log" + if range_key in args and args[range_key]: + axis["range"] = [math.log(r, 10) for r in args[range_key]] + elif range_key in args and args[range_key]: + axis["range"] = args[range_key] + + if args[letter] in orders: + axis["categoryorder"] = "array" + axis["categoryarray"] = ( + orders[args[letter]] + if isinstance(axis, go.layout.XAxis) + else list(reversed(orders[args[letter]])) + ) + + +def configure_cartesian_marginal_axes(args, fig, orders): + + if "histogram" in [args["marginal_x"], args["marginal_y"]]: + fig.layout["barmode"] = "overlay" + + nrows = len(fig._grid_ref) + ncols = len(fig._grid_ref[0]) + + # Set y-axis titles and axis options in the left-most column + for yaxis in fig.select_yaxes(col=1): + set_cartesian_axis_opts(args, yaxis, "y", orders) + + # Set x-axis titles and axis options in the bottom-most row + for xaxis in fig.select_xaxes(row=1): + set_cartesian_axis_opts(args, xaxis, "x", orders) + + # Configure axis ticks on marginal subplots + if args["marginal_x"]: + fig.update_yaxes( + showticklabels=False, + showgrid=args["marginal_x"] == "histogram", + row=nrows, + ) + fig.update_xaxes(showgrid=True, row=nrows) + + if args["marginal_y"]: + fig.update_xaxes( + showticklabels=False, + showgrid=args["marginal_y"] == "histogram", + col=ncols, + ) + fig.update_yaxes(showgrid=True, col=ncols) + + # Add axis titles to non-marginal subplots + y_title = get_decorated_label(args, args["y"], "y") + for row in range(1, nrows): + fig.update_yaxes(title_text=y_title, row=row, col=1) + + x_title = get_decorated_label(args, args["x"], "x") + for col in range(1, ncols): + fig.update_xaxes(title_text=x_title, row=1, col=col) + + # Configure axis type across all x-axes + if "log_x" in args and args["log_x"]: + fig.update_xaxes(type="log") + + # Configure axis type across all y-axes + if "log_y" in args and args["log_y"]: + fig.update_yaxes(type="log") + + # Configure matching and axis type for marginal y-axes + matches_y = "y" + str(ncols + 1) + if args["marginal_x"]: + for row in range(2, nrows + 1, 2): + fig.update_yaxes(matches=matches_y, type=None, row=row) + + if args["marginal_y"]: + for col in range(2, ncols + 1, 2): + fig.update_xaxes(matches="x2", type=None, col=col) + + +def configure_cartesian_axes(args, fig, orders): + if ("marginal_x" in args and args["marginal_x"]) or ( + "marginal_y" in args and args["marginal_y"] + ): + configure_cartesian_marginal_axes(args, fig, orders) + return + + # Set y-axis titles and axis options in the left-most column + y_title = get_decorated_label(args, args["y"], "y") + for yaxis in fig.select_yaxes(col=1): + yaxis.update(title_text=y_title) + set_cartesian_axis_opts(args, yaxis, "y", orders) + + # Set x-axis titles and axis options in the bottom-most row + x_title = get_decorated_label(args, args["x"], "x") + for xaxis in fig.select_xaxes(row=1): + xaxis.update(title_text=x_title) + set_cartesian_axis_opts(args, xaxis, "x", orders) + + # Configure axis type across all x-axes + if "log_x" in args and args["log_x"]: + fig.update_xaxes(type="log") + + # Configure axis type across all y-axes + if "log_y" in args and args["log_y"]: + fig.update_yaxes(type="log") + + return fig.layout + + +def configure_ternary_axes(args, fig, orders): + fig.update( + layout=dict( + ternary=dict( + aaxis=dict(title=get_label(args, args["a"])), + baxis=dict(title=get_label(args, args["b"])), + caxis=dict(title=get_label(args, args["c"])), + ) + ) + ) + + +def configure_polar_axes(args, fig, orders): + layout = dict( + polar=dict( + angularaxis=dict(direction=args["direction"], rotation=args["start_angle"]), + radialaxis=dict(), + ) + ) + + for var, axis in [("r", "radialaxis"), ("theta", "angularaxis")]: + if args[var] in orders: + layout["polar"][axis]["categoryorder"] = "array" + layout["polar"][axis]["categoryarray"] = orders[args[var]] + + radialaxis = layout["polar"]["radialaxis"] + if args["log_r"]: + radialaxis["type"] = "log" + if args["range_r"]: + radialaxis["range"] = [math.log(x, 10) for x in args["range_r"]] + else: + if args["range_r"]: + radialaxis["range"] = args["range_r"] + fig.update(layout=layout) + + +def configure_3d_axes(args, fig, orders): + layout = dict( + scene=dict( + xaxis=dict(title=get_label(args, args["x"])), + yaxis=dict(title=get_label(args, args["y"])), + zaxis=dict(title=get_label(args, args["z"])), + ) + ) + + for letter in ["x", "y", "z"]: + axis = layout["scene"][letter + "axis"] + if args["log_" + letter]: + axis["type"] = "log" + if args["range_" + letter]: + axis["range"] = [math.log(x, 10) for x in args["range_" + letter]] + else: + if args["range_" + letter]: + axis["range"] = args["range_" + letter] + if args[letter] in orders: + axis["categoryorder"] = "array" + axis["categoryarray"] = orders[args[letter]] + fig.update(layout=layout) + + +def configure_mapbox(args, fig, orders): + fig.update( + layout=dict( + mapbox=dict( + accesstoken=MAPBOX_TOKEN, + center=dict( + lat=args["data_frame"][args["lat"]].mean(), + lon=args["data_frame"][args["lon"]].mean(), + ), + zoom=args["zoom"], + ) + ) + ) + + +def configure_geo(args, fig, orders): + fig.update( + layout=dict( + geo=dict( + center=args["center"], + scope=args["scope"], + projection=dict(type=args["projection"]), + ) + ) + ) + + +def configure_animation_controls(args, constructor, fig): + def frame_args(duration): + return { + "frame": {"duration": duration, "redraw": constructor != go.Scatter}, + "mode": "immediate", + "fromcurrent": True, + "transition": {"duration": duration, "easing": "linear"}, + } + + if "animation_frame" in args and args["animation_frame"] and len(fig.frames) > 1: + fig.layout.updatemenus = [ + { + "buttons": [ + { + "args": [None, frame_args(500)], + "label": "▶", + "method": "animate", + }, + { + "args": [[None], frame_args(0)], + "label": "◼", + "method": "animate", + }, + ], + "direction": "left", + "pad": {"r": 10, "t": 70}, + "showactive": False, + "type": "buttons", + "x": 0.1, + "xanchor": "right", + "y": 0, + "yanchor": "top", + } + ] + fig.layout.sliders = [ + { + "active": 0, + "yanchor": "top", + "xanchor": "left", + "currentvalue": { + "prefix": get_label(args, args["animation_frame"]) + "=" + }, + "pad": {"b": 10, "t": 60}, + "len": 0.9, + "x": 0.1, + "y": 0, + "steps": [ + { + "args": [[f.name], frame_args(0)], + "label": f.name, + "method": "animate", + } + for f in fig.frames + ], + } + ] + + +def make_trace_spec(args, constructor, attrs, trace_patch): + # Create base trace specification + result = [TraceSpec(constructor, attrs, trace_patch, None)] + + # Add marginal trace specifications + for letter in ["x", "y"]: + if "marginal_" + letter in args and args["marginal_" + letter]: + trace_spec = None + axis_map = dict( + xaxis="x1" if letter == "x" else "x2", + yaxis="y1" if letter == "y" else "y2", + ) + if args["marginal_" + letter] == "histogram": + trace_spec = TraceSpec( + constructor=go.Histogram, + attrs=[letter, "marginal_" + letter], + trace_patch=dict(opacity=0.5, bingroup=letter, **axis_map), + marginal=letter, + ) + elif args["marginal_" + letter] == "violin": + trace_spec = TraceSpec( + constructor=go.Violin, + attrs=[letter, "hover_name", "hover_data"], + trace_patch=dict(scalegroup=letter), + marginal=letter, + ) + elif args["marginal_" + letter] == "box": + trace_spec = TraceSpec( + constructor=go.Box, + attrs=[letter, "hover_name", "hover_data"], + trace_patch=dict(notched=True), + marginal=letter, + ) + elif args["marginal_" + letter] == "rug": + symbols = {"x": "line-ns-open", "y": "line-ew-open"} + trace_spec = TraceSpec( + constructor=go.Box, + attrs=[letter, "hover_name", "hover_data"], + trace_patch=dict( + fillcolor="rgba(255,255,255,0)", + line={"color": "rgba(255,255,255,0)"}, + boxpoints="all", + jitter=0, + hoveron="points", + marker={"symbol": symbols[letter]}, + ), + marginal=letter, + ) + if "color" in attrs or "color" not in args: + if "marker" not in trace_spec.trace_patch: + trace_spec.trace_patch["marker"] = dict() + first_default_color = args["color_continuous_scale"][0] + trace_spec.trace_patch["marker"]["color"] = first_default_color + result.append(trace_spec) + + # Add trendline trace specifications + if "trendline" in args and args["trendline"]: + trace_spec = TraceSpec( + constructor=go.Scatter, + attrs=["trendline"], + trace_patch=dict(mode="lines"), + marginal=None, + ) + if args["trendline_color_override"]: + trace_spec.trace_patch["line"] = dict( + color=args["trendline_color_override"] + ) + result.append(trace_spec) + return result + + +def one_group(x): + return "" + + +def apply_default_cascade(args): + # first we apply px.defaults to unspecified args + for param in ( + ["color_discrete_sequence", "color_continuous_scale"] + + ["symbol_sequence", "line_dash_sequence", "template"] + + ["width", "height", "size_max"] + ): + if param in args and args[param] is None: + args[param] = getattr(defaults, param) + + # load the default template if set, otherwise "plotly" + if args["template"] is None: + if pio.templates.default is not None: + args["template"] = pio.templates.default + else: + args["template"] = "plotly" + + # retrieve the actual template if we were given a name + try: + template = pio.templates[args["template"]] + except Exception: + template = args["template"] + + # if colors not set explicitly or in px.defaults, defer to a template + # if the template doesn't have one, we set some final fallback defaults + if "color_continuous_scale" in args: + if args["color_continuous_scale"] is None: + try: + args["color_continuous_scale"] = [ + x[1] for x in template.layout.colorscale.sequential + ] + except AttributeError: + pass + if args["color_continuous_scale"] is None: + args["color_continuous_scale"] = sequential.Plasma + + if "color_discrete_sequence" in args: + if args["color_discrete_sequence"] is None: + try: + args["color_discrete_sequence"] = template.layout.colorway + except AttributeError: + pass + if args["color_discrete_sequence"] is None: + args["color_discrete_sequence"] = qualitative.Plotly + + # If both marginals and faceting are specified, faceting wins + if args.get('facet_col', None) and args.get('marginal_y', None): + args['marginal_y'] = None + + if args.get('facet_row', None) and args.get('marginal_x', None): + args['marginal_x'] = None + + +def infer_config(args, constructor, trace_patch): + # Declare all supported attributes, across all plot types + attrables = ( + ["x", "y", "z", "a", "b", "c", "r", "theta", "size"] + + ["dimensions", "hover_name", "hover_data", "text", "error_x", "error_x_minus"] + + ["error_y", "error_y_minus", "error_z", "error_z_minus"] + + ["lat", "lon", "locations", "animation_group"] + ) + array_attrables = ["dimensions", "hover_data"] + group_attrables = ["animation_frame", "facet_row", "facet_col", "line_group"] + + # Validate that the strings provided as attribute values reference columns + # in the provided data_frame + df_columns = args["data_frame"].columns + + for attr in attrables + group_attrables + ["color"]: + if attr in args and args[attr] is not None: + maybe_col_list = [args[attr]] if attr not in array_attrables else args[attr] + for maybe_col in maybe_col_list: + try: + in_cols = maybe_col in df_columns + except TypeError: + in_cols = False + if not in_cols: + value_str = ( + "Element of value" if attr in array_attrables else "Value" + ) + raise ValueError( + "%s of '%s' is not the name of a column in 'data_frame'. " + "Expected one of %s but received: %s" + % (value_str, attr, str(list(df_columns)), str(maybe_col)) + ) + + attrs = [k for k in attrables if k in args] + grouped_attrs = [] + + # Compute sizeref + sizeref = 0 + if "size" in args and args["size"]: + sizeref = args["data_frame"][args["size"]].max() / args["size_max"] ** 2 + + # Compute color attributes and grouping attributes + if "color" in args: + if "color_continuous_scale" in args: + if "color_discrete_sequence" not in args: + attrs.append("color") + else: + if ( + args["color"] + and args["data_frame"][args["color"]].dtype.kind in "bifc" + ): + attrs.append("color") + else: + grouped_attrs.append("marker.color") + elif "line_group" in args or constructor == go.Histogram2dContour: + grouped_attrs.append("line.color") + else: + grouped_attrs.append("marker.color") + + show_colorbar = bool("color" in attrs and args["color"]) + + # Compute line_dash grouping attribute + if "line_dash" in args: + grouped_attrs.append("line.dash") + + # Compute symbol grouping attribute + if "symbol" in args: + grouped_attrs.append("marker.symbol") + + # Compute final trace patch + trace_patch = trace_patch.copy() + + if constructor == go.Histogram2d: + show_colorbar = True + trace_patch["coloraxis"] = "coloraxis1" + + if "opacity" in args: + if args["opacity"] is None: + if "barmode" in args and args["barmode"] == "overlay": + trace_patch["marker"] = dict(opacity=0.5) + else: + trace_patch["marker"] = dict(opacity=args["opacity"]) + if "line_group" in args: + trace_patch["mode"] = "lines" + ("+markers+text" if args["text"] else "") + elif constructor != go.Splom and ( + "symbol" in args or constructor == go.Scattermapbox + ): + trace_patch["mode"] = "markers" + ("+text" if args["text"] else "") + + if "line_shape" in args: + trace_patch["line"] = dict(shape=args["line_shape"]) + + # Compute marginal attribute + if "marginal" in args: + position = "marginal_x" if args["orientation"] == "v" else "marginal_y" + other_position = "marginal_x" if args["orientation"] == "h" else "marginal_y" + args[position] = args["marginal"] + args[other_position] = None + + # Compute applicable grouping attributes + for k in group_attrables: + if k in args: + grouped_attrs.append(k) + + # Create grouped mappings + grouped_mappings = [make_mapping(args, a) for a in grouped_attrs] + + # Create trace specs + trace_specs = make_trace_spec(args, constructor, attrs, trace_patch) + return trace_specs, grouped_mappings, sizeref, show_colorbar + + +def get_orderings(args, grouper, grouped): + """ + `orders` is the user-supplied ordering (with the remaining data-frame-supplied + ordering appended if the column is used for grouping) + `group_names` is the set of groups, ordered by the order above + """ + orders = {} if "category_orders" not in args else args["category_orders"].copy() + group_names = [] + for group_name in grouped.groups: + if len(grouper) == 1: + group_name = (group_name,) + group_names.append(group_name) + for col in grouper: + if col != one_group: + uniques = args["data_frame"][col].unique() + if col not in orders: + orders[col] = list(uniques) + else: + for val in uniques: + if val not in orders[col]: + orders[col].append(val) + + for i, col in reversed(list(enumerate(grouper))): + if col != one_group: + group_names = sorted( + group_names, + key=lambda g: orders[col].index(g[i]) if g[i] in orders[col] else -1, + ) + + return orders, group_names + + +def make_figure(args, constructor, trace_patch={}, layout_patch={}): + apply_default_cascade(args) + + trace_specs, grouped_mappings, sizeref, show_colorbar = infer_config( + args, constructor, trace_patch + ) + grouper = [x.grouper or one_group for x in grouped_mappings] or [one_group] + grouped = args["data_frame"].groupby(grouper, sort=False) + + orders, sorted_group_names = get_orderings(args, grouper, grouped) + + has_marginal_x = bool(args.get("marginal_x", False)) + has_marginal_y = bool(args.get("marginal_y", False)) + + subplot_type = _subplot_type_for_trace_type(constructor().type) + + trace_names_by_frame = {} + frames = OrderedDict() + trendline_rows = [] + nrows = ncols = 1 + for group_name in sorted_group_names: + group = grouped.get_group(group_name if len(group_name) > 1 else group_name[0]) + mapping_labels = OrderedDict() + trace_name_labels = OrderedDict() + frame_name = "" + for col, val, m in zip(grouper, group_name, grouped_mappings): + if col != one_group: + key = get_label(args, col) + mapping_labels[key] = str(val) + if m.show_in_trace_name: + trace_name_labels[key] = str(val) + if m.variable == "animation_frame": + frame_name = val + trace_name = ", ".join(k + "=" + v for k, v in trace_name_labels.items()) + if frame_name not in trace_names_by_frame: + trace_names_by_frame[frame_name] = set() + trace_names = trace_names_by_frame[frame_name] + + for trace_spec in trace_specs: + constructor_to_use = trace_spec.constructor + if constructor_to_use in [go.Scatter, go.Scatterpolar]: + if "render_mode" in args and ( + args["render_mode"] == "webgl" + or ( + args["render_mode"] == "auto" + and len(args["data_frame"]) > 1000 + and args["animation_frame"] is None + ) + ): + constructor_to_use = ( + go.Scattergl + if constructor_to_use == go.Scatter + else go.Scatterpolargl + ) + trace = constructor_to_use(name=trace_name) + if trace_spec.constructor not in [ + go.Parcats, + go.Parcoords, + go.Choropleth, + go.Histogram2d, + ]: + trace.update( + legendgroup=trace_name, + showlegend=(trace_name != "" and trace_name not in trace_names), + ) + if trace_spec.constructor in [go.Bar, go.Violin, go.Box, go.Histogram]: + trace.update(alignmentgroup=True, offsetgroup=trace_name) + if trace_spec.constructor not in [go.Parcats, go.Parcoords]: + trace.update(hoverlabel=dict(namelength=0)) + trace_names.add(trace_name) + + # Init subplot row/col + trace._subplot_row = 1 + trace._subplot_col = 1 + + for i, m in enumerate(grouped_mappings): + val = group_name[i] + if val not in m.val_map: + m.val_map[val] = m.sequence[len(m.val_map) % len(m.sequence)] + try: + m.updater(trace, m.val_map[val]) + except ValueError: + if ( + trace_spec != trace_specs[0] + and trace_spec.constructor in [go.Violin, go.Box, go.Histogram] + and m.variable == "symbol" + ): + pass + elif ( + trace_spec != trace_specs[0] + and trace_spec.constructor in [go.Histogram] + and m.variable == "color" + ): + trace.update(marker=dict(color=m.val_map[val])) + else: + raise + + # Find row for trace, handling facet_row and marginal_x + if m.facet == "row": + row = m.val_map[val] + trace._subplot_row_val = val + else: + if trace_spec.marginal == "x": + row = 2 + else: + row = 1 + + nrows = max(nrows, row) + if row > 1: + trace._subplot_row = row + + # Find col for trace, handling facet_col and marginal_y + if m.facet == "col": + col = m.val_map[val] + trace._subplot_col_val = val + else: + if trace_spec.marginal == "y": + col = 2 + else: + col = 1 + + ncols = max(ncols, col) + if col > 1: + trace._subplot_col = col + if ( + trace_specs[0].constructor == go.Histogram2dContour + and trace_spec.constructor == go.Box + and trace.line.color + ): + trace.update(marker=dict(color=trace.line.color)) + + patch, fit_results = make_trace_kwargs( + args, trace_spec, group, mapping_labels.copy(), sizeref + ) + trace.update(patch) + if fit_results is not None: + trendline_rows.append(mapping_labels.copy()) + trendline_rows[-1]["px_fit_results"] = fit_results + if frame_name not in frames: + frames[frame_name] = dict(data=[], name=frame_name) + frames[frame_name]["data"].append(trace) + frame_list = [f for f in frames.values()] + if len(frame_list) > 1: + frame_list = sorted( + frame_list, key=lambda f: orders[args["animation_frame"]].index(f["name"]) + ) + layout_patch = layout_patch.copy() + if show_colorbar: + colorvar = "z" if constructor == go.Histogram2d else "color" + range_color = args["range_color"] or [None, None] + d = len(args["color_continuous_scale"]) - 1 + layout_patch["coloraxis1"] = dict( + colorscale=[ + [(1.0 * i) / (1.0 * d), x] + for i, x in enumerate(args["color_continuous_scale"]) + ], + cmid=args["color_continuous_midpoint"], + cmin=range_color[0], + cmax=range_color[1], + colorbar=dict(title=get_decorated_label(args, args[colorvar], colorvar)), + ) + for v in ["title", "height", "width", "template"]: + if args[v]: + layout_patch[v] = args[v] + layout_patch["legend"] = {"tracegroupgap": 0} + if "title" not in layout_patch: + layout_patch["margin"] = {"t": 60} + if "size" in args and args["size"]: + layout_patch["legend"]["itemsizing"] = "constant" + + fig = init_figure( + args, subplot_type, frame_list, ncols, nrows, has_marginal_x, has_marginal_y + ) + + # Position traces in subplots + for frame in frame_list: + for trace in frame["data"]: + if isinstance(trace, go.Splom): + # Special case that is not compatible with make_subplots + continue + + _set_trace_grid_reference( + trace, + fig.layout, + fig._grid_ref, + trace._subplot_row, + trace._subplot_col, + ) + + # Add traces, layout and frames to figure + fig.add_traces(frame_list[0]["data"] if len(frame_list) > 0 else []) + fig.layout.update(layout_patch) + fig.frames = frame_list if len(frames) > 1 else [] + + fig._px_trendlines = pandas.DataFrame(trendline_rows) + + configure_axes(args, constructor, fig, orders) + configure_animation_controls(args, constructor, fig) + return fig + + +def init_figure( + args, subplot_type, frame_list, ncols, nrows, has_marginal_x, has_marginal_y +): + # Build subplot specs + specs = [[{}] * ncols for _ in range(nrows)] + column_titles = [None] * ncols + row_titles = [None] * nrows + for frame in frame_list: + for trace in frame["data"]: + row0 = nrows - trace._subplot_row + col0 = trace._subplot_col - 1 + + if isinstance(trace, go.Splom): + # Splom not compatible with make_subplots, treat as domain + specs[row0][col0] = {"type": "domain"} + else: + specs[row0][col0] = {"type": trace.type} + if args.get("facet_row", None) and hasattr(trace, "_subplot_row_val"): + row_titles[row0] = ( + args["facet_row"] + "=" + str(trace._subplot_row_val) + ) + + if args.get("facet_col", None) and hasattr(trace, "_subplot_col_val"): + column_titles[col0] = ( + args["facet_col"] + "=" + str(trace._subplot_col_val) + ) + + # Default row/column widths uniform + column_widths = [1.0] * ncols + row_heights = [1.0] * nrows + + # Build column_widths/row_heights + if subplot_type == "xy": + if has_marginal_x: + if args["marginal_x"] == "histogram" or ("color" in args and args["color"]): + main_size = 0.74 + else: + main_size = 0.84 + + row_heights = [main_size] * (nrows - 1) + [1 - main_size] + vertical_spacing = 0.01 + else: + vertical_spacing = 0.03 + + if has_marginal_y: + if args["marginal_y"] == "histogram" or ("color" in args and args["color"]): + main_size = 0.74 + else: + main_size = 0.84 + + column_widths = [main_size] * (ncols - 1) + [1 - main_size] + horizontal_spacing = 0.005 + else: + horizontal_spacing = 0.02 + else: + # Other subplot types: + # 'scene', 'geo', 'polar', 'ternary', 'mapbox', 'domain', None + # + # We can customize subplot spacing per type once we enable faceting + # for all plot types + vertical_spacing = 0.1 + horizontal_spacing = 0.1 + + # Create figure with subplots + fig = make_subplots( + rows=nrows, + cols=ncols, + specs=specs, + shared_xaxes="all", + shared_yaxes="all", + row_titles=row_titles, + column_titles=column_titles, + horizontal_spacing=horizontal_spacing, + vertical_spacing=vertical_spacing, + row_heights=row_heights, + column_widths=column_widths, + start_cell="bottom-left", + ) + + # Remove explicit font size of row/col titles so template can take over + for annot in fig.layout.annotations: + annot.update(font=None) + + return fig diff --git a/packages/python/plotly/plotly/express/_doc.py b/packages/python/plotly/plotly/express/_doc.py new file mode 100644 index 00000000000..688f0938d4d --- /dev/null +++ b/packages/python/plotly/plotly/express/_doc.py @@ -0,0 +1,362 @@ +import inspect + +colref = "(string: name of column in `data_frame`)" +colref_list = "(list of string: names of columns in `data_frame`)" + +# TODO contents of columns +# TODO explain categorical +# TODO handle color +# TODO handle details of box/violin/histogram +# TODO handle details of column selection with `dimensions` +# TODO document "or `None`, default `None`" in various places +# TODO standardize positioning and casing of 'default' + +docs = dict( + data_frame=["A 'tidy' `pandas.DataFrame`"], + x=[ + colref, + "Values from this column are used to position marks along the x axis in cartesian coordinates.", + "For horizontal `histogram`s, these values are used as inputs to `histfunc`.", + ], + y=[ + colref, + "Values from this column are used to position marks along the y axis in cartesian coordinates.", + "For vertical `histogram`s, these values are used as inputs to `histfunc`.", + ], + z=[ + colref, + "Values from this column are used to position marks along the z axis in cartesian coordinates.", + "For `density_heatmap` and `density_contour` these values are used as the inputs to `histfunc`.", + ], + a=[ + colref, + "Values from this column are used to position marks along the a axis in ternary coordinates.", + ], + b=[ + colref, + "Values from this column are used to position marks along the b axis in ternary coordinates.", + ], + c=[ + colref, + "Values from this column are used to position marks along the c axis in ternary coordinates.", + ], + r=[ + colref, + "Values from this column are used to position marks along the radial axis in polar coordinates.", + ], + theta=[ + colref, + "Values from this column are used to position marks along the angular axis in polar coordinates.", + ], + lat=[ + colref, + "Values from this column are used to position marks according to latitude on a map.", + ], + lon=[ + colref, + "Values from this column are used to position marks according to longitude on a map.", + ], + locations=[ + colref, + "Values from this column are be interpreted according to `locationmode` and mapped to longitude/latitude.", + ], + dimensions=[ + "(list of strings, names of columns in `data_frame`)", + "Columns to be used in multidimensional visualization.", + ], + error_x=[ + colref, + "Values from this column are used to size x-axis error bars.", + "If `error_x_minus` is `None`, error bars will be symmetrical, otherwise `error_x` is used for the positive direction only.", + ], + error_x_minus=[ + colref, + "Values from this column are used to size x-axis error bars in the negative direction.", + "Ignored if `error_x` is `None`.", + ], + error_y=[ + colref, + "Values from this column are used to size y-axis error bars.", + "If `error_y_minus` is `None`, error bars will be symmetrical, otherwise `error_y` is used for the positive direction only.", + ], + error_y_minus=[ + colref, + "Values from this column are used to size y-axis error bars in the negative direction.", + "Ignored if `error_y` is `None`.", + ], + error_z=[ + colref, + "Values from this column are used to size z-axis error bars.", + "If `error_z_minus` is `None`, error bars will be symmetrical, otherwise `error_z` is used for the positive direction only.", + ], + error_z_minus=[ + colref, + "Values from this column are used to size z-axis error bars in the negative direction.", + "Ignored if `error_z` is `None`.", + ], + color=[colref, "Values from this column are used to assign color to marks."], + opacity=["(number, between 0 and 1) Sets the opacity for markers."], + line_dash=[ + colref, + "Values from this column are used to assign dash-patterns to lines.", + ], + line_group=[ + colref, + "Values from this column are used to group rows of `data_frame` into lines.", + ], + symbol=[colref, "Values from this column are used to assign symbols to marks."], + size=[colref, "Values from this column are used to assign mark sizes."], + hover_name=[colref, "Values from this column appear in bold in the hover tooltip."], + hover_data=[ + colref_list, + "Values from these columns appear as extra data in the hover tooltip.", + ], + text=[colref, "Values from this column appear in the figure as text labels."], + locationmode=[ + "(string, one of 'ISO-3', 'USA-states', 'country names')", + "Determines the set of locations used to match entries in `locations` to regions on the map.", + ], + facet_row=[ + colref, + "Values from this column are used to assign marks to facetted subplots in the vertical direction.", + ], + facet_col=[ + colref, + "Values from this column are used to assign marks to facetted subplots in the horizontal direction.", + ], + animation_frame=[ + colref, + "Values from this column are used to assign marks to animation frames.", + ], + animation_group=[ + colref, + "Values from this column are used to provide object-constancy across animation frames: rows with matching `animation_group`s will be treated as if they describe the same object in each frame.", + ], + symbol_sequence=[ + "(list of strings defining plotly.js symbols)", + "When `symbol` is set, values in that column are assigned symbols by cycling through `symbol_sequence` in the order described in `category_orders`, unless the value of `symbol` is a key in `symbol_map`.", + ], + symbol_map=[ + "(dict with string keys and values that are strings defining plotly.js symbols, default `{}`)", + "Used to override `symbol_sequence` to assign a specific symbols to marks corresponding with specific values.", + "Keys in `symbol_map` should be values in the column denoted by `symbol`.", + ], + line_dash_map=[ + "(dict with string keys and values that are strings defining plotly.js dash-patterns, default `{}`)" + "Used to override `line_dash_sequences` to assign a specific dash-patterns to lines corresponding with specific values.", + "Keys in `line_dash_map` should be values in the column denoted by `line_dash`.", + ], + line_dash_sequence=[ + "(list of strings defining plotly.js dash-patterns)", + "When `line_dash` is set, values in that column are assigned dash-patterns by cycling through `line_dash_sequence` in the order described in `category_orders`, unless the value of `line_dash` is a key in `line_dash_map`.", + ], + color_discrete_sequence=[ + "(list of valid CSS-color strings)", + "When `color` is set and the values in the corresponding column are not numeric, values in that column are assigned colors by cycling through `color_discrete_sequence` in the order described in `category_orders`, unless the value of `color` is a key in `color_discrete_map`.", + "Various useful color sequences are available in the `plotly_express.colors` submodules, specifically `plotly_express.colors.qualitative`.", + ], + color_discrete_map=[ + "(dict with string keys and values that are valid CSS-color strings, default `{}`)", + "Used to override `color_discrete_sequence` to assign a specific colors to marks corresponding with specific values.", + "Keys in `color_discrete_map` should be values in the column denoted by `color`.", + ], + color_continuous_scale=[ + "(list of valid CSS-color strings)", + "This list is used to build a continuous color scale when the column denoted by `color` contains numeric data.", + "Various useful color scales are available in the `plotly_express.colors` submodules, specifically `plotly_express.colors.sequential`, `plotly_express.colors.diverging` and `plotly_express.colors.cyclical`.", + ], + color_continuous_midpoint=[ + "(number, defaults to `None`)", + "If set, computes the bounds of the continuous color scale to have the desired midpoint.", + "Setting this value is recommended when using `plotly_express.colors.diverging` color scales as the inputs to `color_continuous_scale`.", + ], + size_max=["(integer, default 20)", "Set the maximum mark size when using `size`."], + log_x=[ + "(boolean, default `False`)", + "If `True`, the x-axis is log-scaled in cartesian coordinates.", + ], + log_y=[ + "(boolean, default `False`)", + "If `True`, the y-axis is log-scaled in cartesian coordinates.", + ], + log_z=[ + "(boolean, default `False`)", + "If `True`, the z-axis is log-scaled in cartesian coordinates.", + ], + log_r=[ + "(boolean, default `False`)", + "If `True`, the radial axis is log-scaled in polar coordinates.", + ], + range_x=[ + "(2-element list of numbers)", + "If provided, overrides auto-scaling on the x-axis in cartesian coordinates.", + ], + range_y=[ + "(2-element list of numbers)", + "If provided, overrides auto-scaling on the y-axis in cartesian coordinates.", + ], + range_z=[ + "(2-element list of numbers)", + "If provided, overrides auto-scaling on the z-axis in cartesian coordinates.", + ], + range_color=[ + "(2-element list of numbers)", + "If provided, overrides auto-scaling on the continuous color scale.", + ], + range_r=[ + "(2-element list of numbers)", + "If provided, overrides auto-scaling on the radial axis in polar coordinates.", + ], + title=["(string)", "The figure title."], + template=[ + "(string or Plotly.py template object)", + "The figure template name or definition.", + ], + width=["(integer, default `None`)", "The figure width in pixels."], + height=["(integer, default `600`)", "The figure height in pixels."], + labels=[ + "(dict with string keys and string values, default `{}`)", + "By default, column names are used in the figure for axis titles, legend entries and hovers.", + "This parameter allows this to be overridden.", + "The keys of this dict should correspond to column names, and the values should correspond to the desired label to be displayed.", + ], + category_orders=[ + "(dict with string keys and list-of-string values, default `{}`)", + "By default, in Python 3.6+, the order of categorical values in axes, legends and facets depends on the order in which these values are first encountered in `data_frame` (and no order is guaranteed by default in Python below 3.6).", + "This parameter is used to force a specific ordering of values per column.", + "The keys of this dict should correspond to column names, and the values should be lists of strings corresponding to the specific display order desired.", + ], + marginal=[ + "(string, one of `'rug'`, `'box'`, `'violin'`, `'histogram'`)", + "If set, a subplot is drawn alongside the main plot, visulizing the distribution.", + ], + marginal_x=[ + "(string, one of `'rug'`, `'box'`, `'violin'`, `'histogram'`)", + "If set, a horizontal subplot is drawn above the main plot, visulizing the x-distribution.", + ], + marginal_y=[ + "(string, one of `'rug'`, `'box'`, `'violin'`, `'histogram'`)", + "If set, a vertical subplot is drawn to the right of the main plot, visulizing the y-distribution.", + ], + trendline=[ + "(string, one of `'ols'` or `'lowess'`, default `None`)", + "If `'ols'`, an Ordinary Least Squares regression line will be drawn for each discrete-color/symbol group.", + "If `'lowess`', a Locally Weighted Scatterplot Smoothing line will be drawn for each discrete-color/symbol group.", + ], + trendline_color_override=[ + "(string, valid CSS color)", + "If provided, and if `trendline` is set, all trendlines will be drawn in this color.", + ], + render_mode=[ + "(string, one of `'auto'`, `'svg'` or `'webgl'`, default `'auto'`)", + "Controls the browser API used to draw marks.", + "`'svg`' is appropriate for figures of less than 1000 data points, and will allow for fully-vectorized output.", + "`'webgl'` is likely necessary for acceptable performance above 1000 points but rasterizes part of the output. ", + "`'auto'` uses heuristics to choose the mode.", + ], + direction=[ + "(string, one of '`counterclockwise'`, `'clockwise'`. Default is `'clockwise'`)", + "Sets the direction in which increasing values of the angular axis are drawn.", + ], + start_angle=[ + "(integer, default is 90)", + "Sets start angle for the angular axis, with 0 being due east and 90 being due north.", + ], + histfunc=[ + "(string, one of `'count'`, `'sum'`, `'avg'`, `'min'`, `'max'`. Default is `'count'`)" + "Function used to aggregate values for summarization (note: can be normalized with `histnorm`).", + "The arguments to this function for `histogram` are the values of `y` if `orientation` is `'v'`,", + "otherwise the arguements are the values of `x`.", + "The arguments to this function for `density_heatmap` and `density_contour` are the values of `z`.", + ], + histnorm=[ + "(string, one of `'percent'`, `'probability'`, `'density'`, `'probability density'`, default `None`)", + "If `None`, the output of `histfunc` is used as is.", + "If `'probability'`, the output of `histfunc` for a given bin is divided by the sum of the output of `histfunc` for all bins.", + "If `'percent'`, the output of `histfunc` for a given bin is divided by the sum of the output of `histfunc` for all bins and multiplied by 100.", + "If `'density'`, the output of `histfunc` for a given bin is divided by the size of the bin.", + "If `'probability density'`, the output of `histfunc` for a given bin is normalized such that it corresponds to the probability that a random event whose distribution is described by the output of `histfunc` will fall into that bin.", + ], + barnorm=[ + "(string, one of `'fraction'` or `'percent'`, default is `None`)", + "If set to `'fraction'`, the value of each bar is divided by the sum of all values at that location coordinate.", + "`'percent'` is the same but multiplied by 100 to show percentages.", + ], + groupnorm=[ + "(string, one of `'fraction'` or `'percent'`, default is `None`)", + "If set to `'fraction'`, the value of each point is divided by the sum of all values at that location coordinate.", + "`'percent'` is the same but multiplied by 100 to show percentages.", + ], + barmode=[ + "(string, one of `'group'`, `'overlay'` or `'relative'`. Default is `'relative'`)", + "In `'relative'` mode, bars are stacked above zero for positive values and below zero for negative values.", + "In `'overlay'` mode, bars are on drawn top of one another.", + "In `'group'` mode, bars are placed beside each other.", + ], + boxmode=[ + "(string, one of `'group'` or `'overlay'`. Default is `'group'`)", + "In `'overlay'` mode, boxes are on drawn top of one another.", + "In `'group'` mode, baxes are placed beside each other.", + ], + violinmode=[ + "(string, one of `'group'` or `'overlay'`. Default is `'group'`)", + "In `'overlay'` mode, violins are on drawn top of one another.", + "In `'group'` mode, violins are placed beside each other.", + ], + stripmode=[ + "(string, one of `'group'` or `'overlay'`. Default is `'group'`)", + "In `'overlay'` mode, strips are on drawn top of one another.", + "In `'group'` mode, strips are placed beside each other.", + ], + zoom=["(integer between 0 and 20, default is 8)", "Sets map zoom level."], + orientation=[ + "(string, one of `'h'` for horizontal or `'v'` for vertical)", + "Default is `'v'`.", + ], + line_close=[ + "(boolean, default `False`)", + "If `True`, an extra line segment is drawn between the first and last point.", + ], + line_shape=["(string, one of `'linear'` or `'spline'`)", "Default is `'linear'`."], + scope=[ + "(string, one of `'world'`, `'usa'`, `'europe'`, `'asia'`, `'africa'`, `'north america'`, `'south america'`)" + "Default is `'world'` unless `projection` is set to `'albers usa'`, which forces `'usa'`." + ], + projection=[ + "(string, one of `'equirectangular'`, `'mercator'`, `'orthographic'`, `'natural earth'`, `'kavrayskiy7'`, `'miller'`, `'robinson'`, `'eckert4'`, `'azimuthal equal area'`, `'azimuthal equidistant'`, `'conic equal area'`, `'conic conformal'`, `'conic equidistant'`, `'gnomonic'`, `'stereographic'`, `'mollweide'`, `'hammer'`, `'transverse mercator'`, `'albers usa'`, `'winkel tripel'`, `'aitoff'`, `'sinusoidal'`)" + "Default depends on `scope`." + ], + center=["(dict with `lat` and `lon` keys)", "Sets the center point of the map."], + points=[ + "(string or boolean, one of `'all'`, `'outliers'`, or `False`. Default is `'outliers'`)", + "If `'outliers'`, only the sample points lying outside the whiskers are shown.", + "If `'all'`, all sample points are shown.", + "If `False`, no sample points are shown", + ], + box=[ + "(boolean, default `False`)", + "If `True`, boxes are drawn inside the violins.", + ], + notched=["(boolean, default `False`)", "If `True`, boxes are drawn with notches."], + cumulative=[ + "(boolean, default `False`)", + "If `True`, histogram values are cumulative.", + ], + nbins=["(positive integer)", "Sets the number of bins."], + nbinsx=["(positive integer)", "Sets the number of bins along the x axis."], + nbinsy=["(positive integer)", "Sets the number of bins along the y axis."], +) + + +def make_docstring(fn): + result = (fn.__doc__ or "") + "\nArguments:\n" + for arg in inspect.getargspec(fn)[0]: + d = ( + " ".join(docs[arg] or "") + if arg in docs + else "(documentation missing from map)" + ) + result += " %s: %s\n" % (arg, d) + result += "Returns:\n" + result += " A `plotly_express.ExpressFigure` object." + return result diff --git a/packages/python/plotly/plotly/express/colors.py b/packages/python/plotly/plotly/express/colors.py new file mode 100644 index 00000000000..134f1707e9a --- /dev/null +++ b/packages/python/plotly/plotly/express/colors.py @@ -0,0 +1,2 @@ +from __future__ import absolute_import +from plotly.colors import * diff --git a/packages/python/plotly/plotly/express/data.py b/packages/python/plotly/plotly/express/data.py new file mode 100644 index 00000000000..375de95c4fd --- /dev/null +++ b/packages/python/plotly/plotly/express/data.py @@ -0,0 +1,2 @@ +from __future__ import absolute_import +from plotly.data import * diff --git a/packages/python/plotly/setup.py b/packages/python/plotly/setup.py index 69048c63071..271e7d6035c 100644 --- a/packages/python/plotly/setup.py +++ b/packages/python/plotly/setup.py @@ -431,6 +431,9 @@ def run(self): 'plotly.matplotlylib.mplexporter', 'plotly.matplotlylib.mplexporter.renderers', 'plotly.figure_factory', + 'plotly.data', + 'plotly.colors', + 'plotly.express', '_plotly_utils', '_plotly_future_', ] + graph_objs_packages + validator_packages, From e3390a02b49d193bf941c0312294bda2c37cb3b6 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Thu, 13 Jun 2019 15:09:44 -0400 Subject: [PATCH 2/4] Remove namespace package path extension This was added back when figure_factory was a separate package. --- packages/python/plotly/plotly/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/python/plotly/plotly/__init__.py b/packages/python/plotly/plotly/__init__.py index 3782be146b9..c75cac14006 100644 --- a/packages/python/plotly/plotly/__init__.py +++ b/packages/python/plotly/plotly/__init__.py @@ -27,10 +27,6 @@ """ from __future__ import absolute_import -# https://packaging.python.org/guides/packaging-namespace-packages/ -# pkgutil-style-namespace-packages -__path__ = __import__('pkgutil').extend_path(__path__, __name__) - from plotly import ( graph_objs, tools, From 6f102fa4fdf2f5b5f9d3422aa49e32eb78b21a5e Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Fri, 14 Jun 2019 11:03:39 -0400 Subject: [PATCH 3/4] Remove doc references to ExpressFigure --- packages/python/plotly/plotly/colors/_swatches.py | 3 +-- packages/python/plotly/plotly/express/__init__.py | 1 - packages/python/plotly/plotly/express/_doc.py | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/python/plotly/plotly/colors/_swatches.py b/packages/python/plotly/plotly/colors/_swatches.py index a2417dfcb5a..ff07ff42907 100644 --- a/packages/python/plotly/plotly/colors/_swatches.py +++ b/packages/python/plotly/plotly/colors/_swatches.py @@ -1,10 +1,9 @@ def _swatches(module_names, module_contents): """ Returns: - A `plotly_express.ExpressFigure` object. This figure demonstrates the color scales and + A `Figure` object. This figure demonstrates the color scales and sequences in this module, as stacked bar charts. """ - from .. import _core import plotly.graph_objs as go sequences = [ diff --git a/packages/python/plotly/plotly/express/__init__.py b/packages/python/plotly/plotly/express/__init__.py index 01d899fb1ab..6a956e64a54 100644 --- a/packages/python/plotly/plotly/express/__init__.py +++ b/packages/python/plotly/plotly/express/__init__.py @@ -70,5 +70,4 @@ "colors", "set_mapbox_access_token", "get_trendline_results", - "ExpressFigure", ] diff --git a/packages/python/plotly/plotly/express/_doc.py b/packages/python/plotly/plotly/express/_doc.py index 688f0938d4d..4b8a5e949d9 100644 --- a/packages/python/plotly/plotly/express/_doc.py +++ b/packages/python/plotly/plotly/express/_doc.py @@ -358,5 +358,5 @@ def make_docstring(fn): ) result += " %s: %s\n" % (arg, d) result += "Returns:\n" - result += " A `plotly_express.ExpressFigure` object." + result += " A `Figure` object." return result From 6abb3758a880410b7ecce2881c347417d58f74ad Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Fri, 14 Jun 2019 11:52:15 -0400 Subject: [PATCH 4/4] move legacy plotly/colors.py to plotly/colors/__init__.py --- packages/python/plotly/plotly/colors.py | 748 ----------------- .../python/plotly/plotly/colors/__init__.py | 752 +++++++++++++++++- 2 files changed, 751 insertions(+), 749 deletions(-) delete mode 100644 packages/python/plotly/plotly/colors.py diff --git a/packages/python/plotly/plotly/colors.py b/packages/python/plotly/plotly/colors.py deleted file mode 100644 index 8ec66f78cb8..00000000000 --- a/packages/python/plotly/plotly/colors.py +++ /dev/null @@ -1,748 +0,0 @@ -""" -colors -===== - -Functions that manipulate colors and arrays of colors. - ------ -There are three basic types of color types: rgb, hex and tuple: - -rgb - An rgb color is a string of the form 'rgb(a,b,c)' where a, b and c are -integers between 0 and 255 inclusive. - -hex - A hex color is a string of the form '#xxxxxx' where each x is a -character that belongs to the set [0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f]. This is -just the set of characters used in the hexadecimal numeric system. - -tuple - A tuple color is a 3-tuple of the form (a,b,c) where a, b and c are -floats between 0 and 1 inclusive. - ------ -Colormaps and Colorscales: -A colormap or a colorscale is a correspondence between values - Pythonic -objects such as strings and floats - to colors. - -There are typically two main types of colormaps that exist: numerical and -categorical colormaps. - -Numerical: ----------- -Numerical colormaps are used when the coloring column being used takes a -spectrum of values or numbers. - -A classic example from the Plotly library: -``` -rainbow_colorscale = [ - [0, 'rgb(150,0,90)'], [0.125, 'rgb(0,0,200)'], - [0.25, 'rgb(0,25,255)'], [0.375, 'rgb(0,152,255)'], - [0.5, 'rgb(44,255,150)'], [0.625, 'rgb(151,255,0)'], - [0.75, 'rgb(255,234,0)'], [0.875, 'rgb(255,111,0)'], - [1, 'rgb(255,0,0)'] -] -``` - -Notice that this colorscale is a list of lists with each inner list containing -a number and a color. These left hand numbers in the nested lists go from 0 to -1, and they are like pointers tell you when a number is mapped to a specific -color. - -If you have a column of numbers `col_num` that you want to plot, and you know - -``` -min(col_num) = 0 -max(col_num) = 100 -``` - -then if you pull out the number `12.5` in the list and want to figure out what -color the corresponding chart element (bar, scatter plot, etc) is going to be, -you'll figure out that proportionally 12.5 to 100 is the same as 0.125 to 1. -So, the point will be mapped to 'rgb(0,0,200)'. - -All other colors between the pinned values in a colorscale are linearly -interpolated. - -Categorical: ------------- -Alternatively, a categorical colormap is used to assign a specific value in a -color column to a specific color everytime it appears in the dataset. - -A column of strings in a panadas.dataframe that is chosen to serve as the -color index would naturally use a categorical colormap. However, you can -choose to use a categorical colormap with a column of numbers. - -Be careful! If you have a lot of unique numbers in your color column you will -end up with a colormap that is massive and may slow down graphing performance. -""" -from __future__ import absolute_import - -import decimal -from numbers import Number -import six - -from plotly import exceptions - -DEFAULT_PLOTLY_COLORS = ['rgb(31, 119, 180)', 'rgb(255, 127, 14)', - 'rgb(44, 160, 44)', 'rgb(214, 39, 40)', - 'rgb(148, 103, 189)', 'rgb(140, 86, 75)', - 'rgb(227, 119, 194)', 'rgb(127, 127, 127)', - 'rgb(188, 189, 34)', 'rgb(23, 190, 207)'] - -PLOTLY_SCALES = { - 'Greys': [ - [0, 'rgb(0,0,0)'], [1, 'rgb(255,255,255)'] - ], - - 'YlGnBu': [ - [0, 'rgb(8,29,88)'], [0.125, 'rgb(37,52,148)'], - [0.25, 'rgb(34,94,168)'], [0.375, 'rgb(29,145,192)'], - [0.5, 'rgb(65,182,196)'], [0.625, 'rgb(127,205,187)'], - [0.75, 'rgb(199,233,180)'], [0.875, 'rgb(237,248,217)'], - [1, 'rgb(255,255,217)'] - ], - - 'Greens': [ - [0, 'rgb(0,68,27)'], [0.125, 'rgb(0,109,44)'], - [0.25, 'rgb(35,139,69)'], [0.375, 'rgb(65,171,93)'], - [0.5, 'rgb(116,196,118)'], [0.625, 'rgb(161,217,155)'], - [0.75, 'rgb(199,233,192)'], [0.875, 'rgb(229,245,224)'], - [1, 'rgb(247,252,245)'] - ], - - 'YlOrRd': [ - [0, 'rgb(128,0,38)'], [0.125, 'rgb(189,0,38)'], - [0.25, 'rgb(227,26,28)'], [0.375, 'rgb(252,78,42)'], - [0.5, 'rgb(253,141,60)'], [0.625, 'rgb(254,178,76)'], - [0.75, 'rgb(254,217,118)'], [0.875, 'rgb(255,237,160)'], - [1, 'rgb(255,255,204)'] - ], - - 'Bluered': [ - [0, 'rgb(0,0,255)'], [1, 'rgb(255,0,0)'] - ], - - # modified RdBu based on - # www.sandia.gov/~kmorel/documents/ColorMaps/ColorMapsExpanded.pdf - 'RdBu': [ - [0, 'rgb(5,10,172)'], [0.35, 'rgb(106,137,247)'], - [0.5, 'rgb(190,190,190)'], [0.6, 'rgb(220,170,132)'], - [0.7, 'rgb(230,145,90)'], [1, 'rgb(178,10,28)'] - ], - - # Scale for non-negative numeric values - 'Reds': [ - [0, 'rgb(220,220,220)'], [0.2, 'rgb(245,195,157)'], - [0.4, 'rgb(245,160,105)'], [1, 'rgb(178,10,28)'] - ], - - # Scale for non-positive numeric values - 'Blues': [ - [0, 'rgb(5,10,172)'], [0.35, 'rgb(40,60,190)'], - [0.5, 'rgb(70,100,245)'], [0.6, 'rgb(90,120,245)'], - [0.7, 'rgb(106,137,247)'], [1, 'rgb(220,220,220)'] - ], - - 'Picnic': [ - [0, 'rgb(0,0,255)'], [0.1, 'rgb(51,153,255)'], - [0.2, 'rgb(102,204,255)'], [0.3, 'rgb(153,204,255)'], - [0.4, 'rgb(204,204,255)'], [0.5, 'rgb(255,255,255)'], - [0.6, 'rgb(255,204,255)'], [0.7, 'rgb(255,153,255)'], - [0.8, 'rgb(255,102,204)'], [0.9, 'rgb(255,102,102)'], - [1, 'rgb(255,0,0)'] - ], - - 'Rainbow': [ - [0, 'rgb(150,0,90)'], [0.125, 'rgb(0,0,200)'], - [0.25, 'rgb(0,25,255)'], [0.375, 'rgb(0,152,255)'], - [0.5, 'rgb(44,255,150)'], [0.625, 'rgb(151,255,0)'], - [0.75, 'rgb(255,234,0)'], [0.875, 'rgb(255,111,0)'], - [1, 'rgb(255,0,0)'] - ], - - 'Portland': [ - [0, 'rgb(12,51,131)'], [0.25, 'rgb(10,136,186)'], - [0.5, 'rgb(242,211,56)'], [0.75, 'rgb(242,143,56)'], - [1, 'rgb(217,30,30)'] - ], - - 'Jet': [ - [0, 'rgb(0,0,131)'], [0.125, 'rgb(0,60,170)'], - [0.375, 'rgb(5,255,255)'], [0.625, 'rgb(255,255,0)'], - [0.875, 'rgb(250,0,0)'], [1, 'rgb(128,0,0)'] - ], - - 'Hot': [ - [0, 'rgb(0,0,0)'], [0.3, 'rgb(230,0,0)'], - [0.6, 'rgb(255,210,0)'], [1, 'rgb(255,255,255)'] - ], - - 'Blackbody': [ - [0, 'rgb(0,0,0)'], [0.2, 'rgb(230,0,0)'], - [0.4, 'rgb(230,210,0)'], [0.7, 'rgb(255,255,255)'], - [1, 'rgb(160,200,255)'] - ], - - 'Earth': [ - [0, 'rgb(0,0,130)'], [0.1, 'rgb(0,180,180)'], - [0.2, 'rgb(40,210,40)'], [0.4, 'rgb(230,230,50)'], - [0.6, 'rgb(120,70,20)'], [1, 'rgb(255,255,255)'] - ], - - 'Electric': [ - [0, 'rgb(0,0,0)'], [0.15, 'rgb(30,0,100)'], - [0.4, 'rgb(120,0,100)'], [0.6, 'rgb(160,90,0)'], - [0.8, 'rgb(230,200,0)'], [1, 'rgb(255,250,220)'] - ], - - 'Viridis': [ - [0, '#440154'], [0.06274509803921569, '#48186a'], - [0.12549019607843137, '#472d7b'], [0.18823529411764706, '#424086'], - [0.25098039215686274, '#3b528b'], [0.3137254901960784, '#33638d'], - [0.3764705882352941, '#2c728e'], [0.4392156862745098, '#26828e'], - [0.5019607843137255, '#21918c'], [0.5647058823529412, '#1fa088'], - [0.6274509803921569, '#28ae80'], [0.6901960784313725, '#3fbc73'], - [0.7529411764705882, '#5ec962'], [0.8156862745098039, '#84d44b'], - [0.8784313725490196, '#addc30'], [0.9411764705882353, '#d8e219'], - [1, '#fde725'] - ], - 'Cividis': [ - [0.000000, 'rgb(0,32,76)'], [0.058824, 'rgb(0,42,102)'], - [0.117647, 'rgb(0,52,110)'], [0.176471, 'rgb(39,63,108)'], - [0.235294, 'rgb(60,74,107)'], [0.294118, 'rgb(76,85,107)'], - [0.352941, 'rgb(91,95,109)'], [0.411765, 'rgb(104,106,112)'], - [0.470588, 'rgb(117,117,117)'], [0.529412, 'rgb(131,129,120)'], - [0.588235, 'rgb(146,140,120)'], [0.647059, 'rgb(161,152,118)'], - [0.705882, 'rgb(176,165,114)'], [0.764706, 'rgb(192,177,109)'], - [0.823529, 'rgb(209,191,102)'], [0.882353, 'rgb(225,204,92)'], - [0.941176, 'rgb(243,219,79)'], [1.000000, 'rgb(255,233,69)'] - ] -} - - -def color_parser(colors, function): - """ - Takes color(s) and a function and applies the function on the color(s) - - In particular, this function identifies whether the given color object - is an iterable or not and applies the given color-parsing function to - the color or iterable of colors. If given an iterable, it will only be - able to work with it if all items in the iterable are of the same type - - rgb string, hex string or tuple - """ - if isinstance(colors, str): - return function(colors) - - if isinstance(colors, tuple) and isinstance(colors[0], Number): - return function(colors) - - if hasattr(colors, '__iter__'): - if isinstance(colors, tuple): - new_color_tuple = tuple(function(item) for item in colors) - return new_color_tuple - - else: - new_color_list = [function(item) for item in colors] - return new_color_list - - -def validate_colors(colors, colortype='tuple'): - """ - Validates color(s) and returns a list of color(s) of a specified type - """ - from numbers import Number - if colors is None: - colors = DEFAULT_PLOTLY_COLORS - - if isinstance(colors, str): - if colors in PLOTLY_SCALES: - colors_list = colorscale_to_colors(PLOTLY_SCALES[colors]) - # TODO: fix _gantt.py/_scatter.py so that they can accept the - # actual colorscale and not just a list of the first and last - # color in the plotly colorscale. In resolving this issue we - # will be removing the immediate line below - colors = [colors_list[0]] + [colors_list[-1]] - elif 'rgb' in colors or '#' in colors: - colors = [colors] - else: - raise exceptions.PlotlyError( - "If your colors variable is a string, it must be a " - "Plotly scale, an rgb color or a hex color.") - - elif isinstance(colors, tuple): - if isinstance(colors[0], Number): - colors = [colors] - else: - colors = list(colors) - - # convert color elements in list to tuple color - for j, each_color in enumerate(colors): - if 'rgb' in each_color: - each_color = color_parser(each_color, unlabel_rgb) - for value in each_color: - if value > 255.0: - raise exceptions.PlotlyError( - "Whoops! The elements in your rgb colors " - "tuples cannot exceed 255.0." - ) - each_color = color_parser(each_color, unconvert_from_RGB_255) - colors[j] = each_color - - if '#' in each_color: - each_color = color_parser(each_color, hex_to_rgb) - each_color = color_parser(each_color, unconvert_from_RGB_255) - - colors[j] = each_color - - if isinstance(each_color, tuple): - for value in each_color: - if value > 1.0: - raise exceptions.PlotlyError( - "Whoops! The elements in your colors tuples " - "cannot exceed 1.0." - ) - colors[j] = each_color - - if colortype == 'rgb' and not isinstance(colors, six.string_types): - for j, each_color in enumerate(colors): - rgb_color = color_parser(each_color, convert_to_RGB_255) - colors[j] = color_parser(rgb_color, label_rgb) - - return colors - - -def validate_colors_dict(colors, colortype='tuple'): - """ - Validates dictioanry of color(s) - """ - # validate each color element in the dictionary - for key in colors: - if 'rgb' in colors[key]: - colors[key] = color_parser(colors[key], unlabel_rgb) - for value in colors[key]: - if value > 255.0: - raise exceptions.PlotlyError( - "Whoops! The elements in your rgb colors " - "tuples cannot exceed 255.0." - ) - colors[key] = color_parser(colors[key], unconvert_from_RGB_255) - - if '#' in colors[key]: - colors[key] = color_parser(colors[key], hex_to_rgb) - colors[key] = color_parser(colors[key], unconvert_from_RGB_255) - - if isinstance(colors[key], tuple): - for value in colors[key]: - if value > 1.0: - raise exceptions.PlotlyError( - "Whoops! The elements in your colors tuples " - "cannot exceed 1.0." - ) - - if colortype == 'rgb': - for key in colors: - colors[key] = color_parser(colors[key], convert_to_RGB_255) - colors[key] = color_parser(colors[key], label_rgb) - - return colors - - -def convert_colors_to_same_type(colors, colortype='rgb', scale=None, - return_default_colors=False, - num_of_defualt_colors=2): - """ - Converts color(s) to the specified color type - - Takes a single color or an iterable of colors, as well as a list of scale - values, and outputs a 2-pair of the list of color(s) converted all to an - rgb or tuple color type, aswell as the scale as the second element. If - colors is a Plotly Scale name, then 'scale' will be forced to the scale - from the respective colorscale and the colors in that colorscale will also - be coverted to the selected colortype. If colors is None, then there is an - option to return portion of the DEFAULT_PLOTLY_COLORS - - :param (str|tuple|list) colors: either a plotly scale name, an rgb or hex - color, a color tuple or a list/tuple of colors - :param (list) scale: see docs for validate_scale_values() - - :rtype (tuple) (colors_list, scale) if scale is None in the function call, - then scale will remain None in the returned tuple - """ - colors_list = [] - - if colors is None and return_default_colors is True: - colors_list = DEFAULT_PLOTLY_COLORS[0:num_of_defualt_colors] - - if isinstance(colors, str): - if colors in PLOTLY_SCALES: - colors_list = colorscale_to_colors(PLOTLY_SCALES[colors]) - if scale is None: - scale = colorscale_to_scale(PLOTLY_SCALES[colors]) - - elif 'rgb' in colors or '#' in colors: - colors_list = [colors] - - elif isinstance(colors, tuple): - if isinstance(colors[0], Number): - colors_list = [colors] - else: - colors_list = list(colors) - - elif isinstance(colors, list): - colors_list = colors - - # validate scale - if scale is not None: - validate_scale_values(scale) - - if len(colors_list) != len(scale): - raise exceptions.PlotlyError( - 'Make sure that the length of your scale matches the length ' - 'of your list of colors which is {}.'.format(len(colors_list)) - ) - - # convert all colors to rgb - for j, each_color in enumerate(colors_list): - if '#' in each_color: - each_color = color_parser( - each_color, hex_to_rgb - ) - each_color = color_parser( - each_color, label_rgb - ) - colors_list[j] = each_color - - elif isinstance(each_color, tuple): - each_color = color_parser( - each_color, convert_to_RGB_255 - ) - each_color = color_parser( - each_color, label_rgb - ) - colors_list[j] = each_color - - if colortype == 'rgb': - return (colors_list, scale) - elif colortype == 'tuple': - for j, each_color in enumerate(colors_list): - each_color = color_parser( - each_color, unlabel_rgb - ) - each_color = color_parser( - each_color, unconvert_from_RGB_255 - ) - colors_list[j] = each_color - return (colors_list, scale) - else: - raise exceptions.PlotlyError('You must select either rgb or tuple ' - 'for your colortype variable.') - - -def convert_dict_colors_to_same_type(colors_dict, colortype='rgb'): - """ - Converts a colors in a dictioanry of colors to the specified color type - - :param (dict) colors_dict: a dictioanry whose values are single colors - """ - for key in colors_dict: - if '#' in colors_dict[key]: - colors_dict[key] = color_parser( - colors_dict[key], hex_to_rgb - ) - colors_dict[key] = color_parser( - colors_dict[key], label_rgb - ) - - elif isinstance(colors_dict[key], tuple): - colors_dict[key] = color_parser( - colors_dict[key], convert_to_RGB_255 - ) - colors_dict[key] = color_parser( - colors_dict[key], label_rgb - ) - - if colortype == 'rgb': - return colors_dict - elif colortype == 'tuple': - for key in colors_dict: - colors_dict[key] = color_parser( - colors_dict[key], unlabel_rgb - ) - colors_dict[key] = color_parser( - colors_dict[key], unconvert_from_RGB_255 - ) - return colors_dict - else: - raise exceptions.PlotlyError('You must select either rgb or tuple ' - 'for your colortype variable.') - - -def validate_scale_values(scale): - """ - Validates scale values from a colorscale - - :param (list) scale: a strictly increasing list of floats that begins - with 0 and ends with 1. Its usage derives from a colorscale which is - a list of two-lists (a list with two elements) of the form - [value, color] which are used to determine how interpolation weighting - works between the colors in the colorscale. Therefore scale is just - the extraction of these values from the two-lists in order - """ - if len(scale) < 2: - raise exceptions.PlotlyError('You must input a list of scale values ' - 'that has at least two values.') - - if (scale[0] != 0) or (scale[-1] != 1): - raise exceptions.PlotlyError( - 'The first and last number in your scale must be 0.0 and 1.0 ' - 'respectively.' - ) - - if not all(x < y for x, y in zip(scale, scale[1:])): - raise exceptions.PlotlyError( - "'scale' must be a list that contains a strictly increasing " - "sequence of numbers." - ) - - -def validate_colorscale(colorscale): - """Validate the structure, scale values and colors of colorscale.""" - if not isinstance(colorscale, list): - # TODO Write tests for these exceptions - raise exceptions.PlotlyError("A valid colorscale must be a list.") - if not all(isinstance(innerlist, list) for innerlist in colorscale): - raise exceptions.PlotlyError( - "A valid colorscale must be a list of lists." - ) - colorscale_colors = colorscale_to_colors(colorscale) - scale_values = colorscale_to_scale(colorscale) - - validate_scale_values(scale_values) - validate_colors(colorscale_colors) - - -def make_colorscale(colors, scale=None): - """ - Makes a colorscale from a list of colors and a scale - - Takes a list of colors and scales and constructs a colorscale based - on the colors in sequential order. If 'scale' is left empty, a linear- - interpolated colorscale will be generated. If 'scale' is a specificed - list, it must be the same legnth as colors and must contain all floats - For documentation regarding to the form of the output, see - https://plot.ly/python/reference/#mesh3d-colorscale - - :param (list) colors: a list of single colors - """ - colorscale = [] - - # validate minimum colors length of 2 - if len(colors) < 2: - raise exceptions.PlotlyError('You must input a list of colors that ' - 'has at least two colors.') - - if scale is None: - scale_incr = 1./(len(colors) - 1) - return [[i * scale_incr, color] for i, color in enumerate(colors)] - - else: - if len(colors) != len(scale): - raise exceptions.PlotlyError('The length of colors and scale ' - 'must be the same.') - - validate_scale_values(scale) - - colorscale = [list(tup) for tup in zip(scale, colors)] - return colorscale - - -def find_intermediate_color(lowcolor, highcolor, intermed, colortype='tuple'): - """ - Returns the color at a given distance between two colors - - This function takes two color tuples, where each element is between 0 - and 1, along with a value 0 < intermed < 1 and returns a color that is - intermed-percent from lowcolor to highcolor. If colortype is set to 'rgb', - the function will automatically convert the rgb type to a tuple, find the - intermediate color and return it as an rgb color. - """ - if colortype == 'rgb': - # convert to tuple color, eg. (1, 0.45, 0.7) - lowcolor = unlabel_rgb(lowcolor) - highcolor = unlabel_rgb(highcolor) - - diff_0 = float(highcolor[0] - lowcolor[0]) - diff_1 = float(highcolor[1] - lowcolor[1]) - diff_2 = float(highcolor[2] - lowcolor[2]) - - inter_med_tuple = ( - lowcolor[0] + intermed * diff_0, - lowcolor[1] + intermed * diff_1, - lowcolor[2] + intermed * diff_2 - ) - - if colortype == 'rgb': - # back to an rgb string, e.g. rgb(30, 20, 10) - inter_med_rgb = label_rgb(inter_med_tuple) - return inter_med_rgb - - return inter_med_tuple - - -def unconvert_from_RGB_255(colors): - """ - Return a tuple where each element gets divided by 255 - - Takes a (list of) color tuple(s) where each element is between 0 and - 255. Returns the same tuples where each tuple element is normalized to - a value between 0 and 1 - """ - return (colors[0]/(255.0), - colors[1]/(255.0), - colors[2]/(255.0)) - - -def convert_to_RGB_255(colors): - """ - Multiplies each element of a triplet by 255 - - Each coordinate of the color tuple is rounded to the nearest float and - then is turned into an integer. If a number is of the form x.5, then - if x is odd, the number rounds up to (x+1). Otherwise, it rounds down - to just x. This is the way rounding works in Python 3 and in current - statistical analysis to avoid rounding bias - - :param (list) rgb_components: grabs the three R, G and B values to be - returned as computed in the function - """ - rgb_components = [] - - for component in colors: - rounded_num = decimal.Decimal(str(component*255.0)).quantize( - decimal.Decimal('1'), rounding=decimal.ROUND_HALF_EVEN - ) - # convert rounded number to an integer from 'Decimal' form - rounded_num = int(rounded_num) - rgb_components.append(rounded_num) - - return (rgb_components[0], rgb_components[1], rgb_components[2]) - - -def n_colors(lowcolor, highcolor, n_colors, colortype='tuple'): - """ - Splits a low and high color into a list of n_colors colors in it - - Accepts two color tuples and returns a list of n_colors colors - which form the intermediate colors between lowcolor and highcolor - from linearly interpolating through RGB space. If colortype is 'rgb' - the function will return a list of colors in the same form. - """ - if colortype == 'rgb': - # convert to tuple - lowcolor = unlabel_rgb(lowcolor) - highcolor = unlabel_rgb(highcolor) - - diff_0 = float(highcolor[0] - lowcolor[0]) - incr_0 = diff_0/(n_colors - 1) - diff_1 = float(highcolor[1] - lowcolor[1]) - incr_1 = diff_1/(n_colors - 1) - diff_2 = float(highcolor[2] - lowcolor[2]) - incr_2 = diff_2/(n_colors - 1) - list_of_colors = [] - - for index in range(n_colors): - new_tuple = (lowcolor[0] + (index * incr_0), - lowcolor[1] + (index * incr_1), - lowcolor[2] + (index * incr_2)) - list_of_colors.append(new_tuple) - - if colortype == 'rgb': - # back to an rgb string - list_of_colors = color_parser(list_of_colors, label_rgb) - - return list_of_colors - - -def label_rgb(colors): - """ - Takes tuple (a, b, c) and returns an rgb color 'rgb(a, b, c)' - """ - return ('rgb(%s, %s, %s)' % (colors[0], colors[1], colors[2])) - - -def unlabel_rgb(colors): - """ - Takes rgb color(s) 'rgb(a, b, c)' and returns tuple(s) (a, b, c) - - This function takes either an 'rgb(a, b, c)' color or a list of - such colors and returns the color tuples in tuple(s) (a, b, c) - """ - str_vals = '' - for index in range(len(colors)): - try: - float(colors[index]) - str_vals = str_vals + colors[index] - except ValueError: - if colors[index] == ',' or colors[index] == '.': - str_vals = str_vals + colors[index] - - str_vals = str_vals + ',' - numbers = [] - str_num = '' - for char in str_vals: - if char != ',': - str_num = str_num + char - else: - numbers.append(float(str_num)) - str_num = '' - return (numbers[0], numbers[1], numbers[2]) - - -def hex_to_rgb(value): - """ - Calculates rgb values from a hex color code. - - :param (string) value: Hex color string - - :rtype (tuple) (r_value, g_value, b_value): tuple of rgb values - """ - value = value.lstrip('#') - hex_total_length = len(value) - rgb_section_length = hex_total_length // 3 - return tuple(int(value[i:i + rgb_section_length], 16) - for i in range(0, hex_total_length, rgb_section_length)) - - -def colorscale_to_colors(colorscale): - """ - Extracts the colors from colorscale as a list - """ - color_list = [] - for item in colorscale: - color_list.append(item[1]) - return color_list - - -def colorscale_to_scale(colorscale): - """ - Extracts the interpolation scale values from colorscale as a list - """ - scale_list = [] - for item in colorscale: - scale_list.append(item[0]) - return scale_list - - -def convert_colorscale_to_rgb(colorscale): - """ - Converts the colors in a colorscale to rgb colors - - A colorscale is an array of arrays, each with a numeric value as the - first item and a color as the second. This function specifically is - converting a colorscale with tuple colors (each coordinate between 0 - and 1) into a colorscale with the colors transformed into rgb colors - """ - for color in colorscale: - color[1] = convert_to_RGB_255(color[1]) - - for color in colorscale: - color[1] = label_rgb(color[1]) - return colorscale diff --git a/packages/python/plotly/plotly/colors/__init__.py b/packages/python/plotly/plotly/colors/__init__.py index d9482ee6220..205771fd5ee 100644 --- a/packages/python/plotly/plotly/colors/__init__.py +++ b/packages/python/plotly/plotly/colors/__init__.py @@ -1,7 +1,91 @@ """ -Built-in qualitative color sequences and sequential, diverging and cyclical color scales. +colors +===== + +Functions that manipulate colors and arrays of colors. + +----- +There are three basic types of color types: rgb, hex and tuple: + +rgb - An rgb color is a string of the form 'rgb(a,b,c)' where a, b and c are +integers between 0 and 255 inclusive. + +hex - A hex color is a string of the form '#xxxxxx' where each x is a +character that belongs to the set [0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f]. This is +just the set of characters used in the hexadecimal numeric system. + +tuple - A tuple color is a 3-tuple of the form (a,b,c) where a, b and c are +floats between 0 and 1 inclusive. + +----- +Colormaps and Colorscales: +A colormap or a colorscale is a correspondence between values - Pythonic +objects such as strings and floats - to colors. + +There are typically two main types of colormaps that exist: numerical and +categorical colormaps. + +Numerical: +---------- +Numerical colormaps are used when the coloring column being used takes a +spectrum of values or numbers. + +A classic example from the Plotly library: +``` +rainbow_colorscale = [ + [0, 'rgb(150,0,90)'], [0.125, 'rgb(0,0,200)'], + [0.25, 'rgb(0,25,255)'], [0.375, 'rgb(0,152,255)'], + [0.5, 'rgb(44,255,150)'], [0.625, 'rgb(151,255,0)'], + [0.75, 'rgb(255,234,0)'], [0.875, 'rgb(255,111,0)'], + [1, 'rgb(255,0,0)'] +] +``` + +Notice that this colorscale is a list of lists with each inner list containing +a number and a color. These left hand numbers in the nested lists go from 0 to +1, and they are like pointers tell you when a number is mapped to a specific +color. + +If you have a column of numbers `col_num` that you want to plot, and you know + +``` +min(col_num) = 0 +max(col_num) = 100 +``` + +then if you pull out the number `12.5` in the list and want to figure out what +color the corresponding chart element (bar, scatter plot, etc) is going to be, +you'll figure out that proportionally 12.5 to 100 is the same as 0.125 to 1. +So, the point will be mapped to 'rgb(0,0,200)'. + +All other colors between the pinned values in a colorscale are linearly +interpolated. + +Categorical: +------------ +Alternatively, a categorical colormap is used to assign a specific value in a +color column to a specific color everytime it appears in the dataset. + +A column of strings in a panadas.dataframe that is chosen to serve as the +color index would naturally use a categorical colormap. However, you can +choose to use a categorical colormap with a column of numbers. + +Be careful! If you have a lot of unique numbers in your color column you will +end up with a colormap that is massive and may slow down graphing performance. """ +from __future__ import absolute_import + +import decimal +from numbers import Number +import six +from plotly import exceptions + + +# Built-in qualitative color sequences and sequential, +# diverging and cyclical color scales. +# +# Initially ported over from plotly_express from . import ( # noqa: F401 qualitative, sequential, @@ -11,3 +95,669 @@ colorbrewer, carto, ) + +DEFAULT_PLOTLY_COLORS = ['rgb(31, 119, 180)', 'rgb(255, 127, 14)', + 'rgb(44, 160, 44)', 'rgb(214, 39, 40)', + 'rgb(148, 103, 189)', 'rgb(140, 86, 75)', + 'rgb(227, 119, 194)', 'rgb(127, 127, 127)', + 'rgb(188, 189, 34)', 'rgb(23, 190, 207)'] + +PLOTLY_SCALES = { + 'Greys': [ + [0, 'rgb(0,0,0)'], [1, 'rgb(255,255,255)'] + ], + + 'YlGnBu': [ + [0, 'rgb(8,29,88)'], [0.125, 'rgb(37,52,148)'], + [0.25, 'rgb(34,94,168)'], [0.375, 'rgb(29,145,192)'], + [0.5, 'rgb(65,182,196)'], [0.625, 'rgb(127,205,187)'], + [0.75, 'rgb(199,233,180)'], [0.875, 'rgb(237,248,217)'], + [1, 'rgb(255,255,217)'] + ], + + 'Greens': [ + [0, 'rgb(0,68,27)'], [0.125, 'rgb(0,109,44)'], + [0.25, 'rgb(35,139,69)'], [0.375, 'rgb(65,171,93)'], + [0.5, 'rgb(116,196,118)'], [0.625, 'rgb(161,217,155)'], + [0.75, 'rgb(199,233,192)'], [0.875, 'rgb(229,245,224)'], + [1, 'rgb(247,252,245)'] + ], + + 'YlOrRd': [ + [0, 'rgb(128,0,38)'], [0.125, 'rgb(189,0,38)'], + [0.25, 'rgb(227,26,28)'], [0.375, 'rgb(252,78,42)'], + [0.5, 'rgb(253,141,60)'], [0.625, 'rgb(254,178,76)'], + [0.75, 'rgb(254,217,118)'], [0.875, 'rgb(255,237,160)'], + [1, 'rgb(255,255,204)'] + ], + + 'Bluered': [ + [0, 'rgb(0,0,255)'], [1, 'rgb(255,0,0)'] + ], + + # modified RdBu based on + # www.sandia.gov/~kmorel/documents/ColorMaps/ColorMapsExpanded.pdf + 'RdBu': [ + [0, 'rgb(5,10,172)'], [0.35, 'rgb(106,137,247)'], + [0.5, 'rgb(190,190,190)'], [0.6, 'rgb(220,170,132)'], + [0.7, 'rgb(230,145,90)'], [1, 'rgb(178,10,28)'] + ], + + # Scale for non-negative numeric values + 'Reds': [ + [0, 'rgb(220,220,220)'], [0.2, 'rgb(245,195,157)'], + [0.4, 'rgb(245,160,105)'], [1, 'rgb(178,10,28)'] + ], + + # Scale for non-positive numeric values + 'Blues': [ + [0, 'rgb(5,10,172)'], [0.35, 'rgb(40,60,190)'], + [0.5, 'rgb(70,100,245)'], [0.6, 'rgb(90,120,245)'], + [0.7, 'rgb(106,137,247)'], [1, 'rgb(220,220,220)'] + ], + + 'Picnic': [ + [0, 'rgb(0,0,255)'], [0.1, 'rgb(51,153,255)'], + [0.2, 'rgb(102,204,255)'], [0.3, 'rgb(153,204,255)'], + [0.4, 'rgb(204,204,255)'], [0.5, 'rgb(255,255,255)'], + [0.6, 'rgb(255,204,255)'], [0.7, 'rgb(255,153,255)'], + [0.8, 'rgb(255,102,204)'], [0.9, 'rgb(255,102,102)'], + [1, 'rgb(255,0,0)'] + ], + + 'Rainbow': [ + [0, 'rgb(150,0,90)'], [0.125, 'rgb(0,0,200)'], + [0.25, 'rgb(0,25,255)'], [0.375, 'rgb(0,152,255)'], + [0.5, 'rgb(44,255,150)'], [0.625, 'rgb(151,255,0)'], + [0.75, 'rgb(255,234,0)'], [0.875, 'rgb(255,111,0)'], + [1, 'rgb(255,0,0)'] + ], + + 'Portland': [ + [0, 'rgb(12,51,131)'], [0.25, 'rgb(10,136,186)'], + [0.5, 'rgb(242,211,56)'], [0.75, 'rgb(242,143,56)'], + [1, 'rgb(217,30,30)'] + ], + + 'Jet': [ + [0, 'rgb(0,0,131)'], [0.125, 'rgb(0,60,170)'], + [0.375, 'rgb(5,255,255)'], [0.625, 'rgb(255,255,0)'], + [0.875, 'rgb(250,0,0)'], [1, 'rgb(128,0,0)'] + ], + + 'Hot': [ + [0, 'rgb(0,0,0)'], [0.3, 'rgb(230,0,0)'], + [0.6, 'rgb(255,210,0)'], [1, 'rgb(255,255,255)'] + ], + + 'Blackbody': [ + [0, 'rgb(0,0,0)'], [0.2, 'rgb(230,0,0)'], + [0.4, 'rgb(230,210,0)'], [0.7, 'rgb(255,255,255)'], + [1, 'rgb(160,200,255)'] + ], + + 'Earth': [ + [0, 'rgb(0,0,130)'], [0.1, 'rgb(0,180,180)'], + [0.2, 'rgb(40,210,40)'], [0.4, 'rgb(230,230,50)'], + [0.6, 'rgb(120,70,20)'], [1, 'rgb(255,255,255)'] + ], + + 'Electric': [ + [0, 'rgb(0,0,0)'], [0.15, 'rgb(30,0,100)'], + [0.4, 'rgb(120,0,100)'], [0.6, 'rgb(160,90,0)'], + [0.8, 'rgb(230,200,0)'], [1, 'rgb(255,250,220)'] + ], + + 'Viridis': [ + [0, '#440154'], [0.06274509803921569, '#48186a'], + [0.12549019607843137, '#472d7b'], [0.18823529411764706, '#424086'], + [0.25098039215686274, '#3b528b'], [0.3137254901960784, '#33638d'], + [0.3764705882352941, '#2c728e'], [0.4392156862745098, '#26828e'], + [0.5019607843137255, '#21918c'], [0.5647058823529412, '#1fa088'], + [0.6274509803921569, '#28ae80'], [0.6901960784313725, '#3fbc73'], + [0.7529411764705882, '#5ec962'], [0.8156862745098039, '#84d44b'], + [0.8784313725490196, '#addc30'], [0.9411764705882353, '#d8e219'], + [1, '#fde725'] + ], + 'Cividis': [ + [0.000000, 'rgb(0,32,76)'], [0.058824, 'rgb(0,42,102)'], + [0.117647, 'rgb(0,52,110)'], [0.176471, 'rgb(39,63,108)'], + [0.235294, 'rgb(60,74,107)'], [0.294118, 'rgb(76,85,107)'], + [0.352941, 'rgb(91,95,109)'], [0.411765, 'rgb(104,106,112)'], + [0.470588, 'rgb(117,117,117)'], [0.529412, 'rgb(131,129,120)'], + [0.588235, 'rgb(146,140,120)'], [0.647059, 'rgb(161,152,118)'], + [0.705882, 'rgb(176,165,114)'], [0.764706, 'rgb(192,177,109)'], + [0.823529, 'rgb(209,191,102)'], [0.882353, 'rgb(225,204,92)'], + [0.941176, 'rgb(243,219,79)'], [1.000000, 'rgb(255,233,69)'] + ] +} + + +def color_parser(colors, function): + """ + Takes color(s) and a function and applies the function on the color(s) + + In particular, this function identifies whether the given color object + is an iterable or not and applies the given color-parsing function to + the color or iterable of colors. If given an iterable, it will only be + able to work with it if all items in the iterable are of the same type + - rgb string, hex string or tuple + """ + if isinstance(colors, str): + return function(colors) + + if isinstance(colors, tuple) and isinstance(colors[0], Number): + return function(colors) + + if hasattr(colors, '__iter__'): + if isinstance(colors, tuple): + new_color_tuple = tuple(function(item) for item in colors) + return new_color_tuple + + else: + new_color_list = [function(item) for item in colors] + return new_color_list + + +def validate_colors(colors, colortype='tuple'): + """ + Validates color(s) and returns a list of color(s) of a specified type + """ + from numbers import Number + if colors is None: + colors = DEFAULT_PLOTLY_COLORS + + if isinstance(colors, str): + if colors in PLOTLY_SCALES: + colors_list = colorscale_to_colors(PLOTLY_SCALES[colors]) + # TODO: fix _gantt.py/_scatter.py so that they can accept the + # actual colorscale and not just a list of the first and last + # color in the plotly colorscale. In resolving this issue we + # will be removing the immediate line below + colors = [colors_list[0]] + [colors_list[-1]] + elif 'rgb' in colors or '#' in colors: + colors = [colors] + else: + raise exceptions.PlotlyError( + "If your colors variable is a string, it must be a " + "Plotly scale, an rgb color or a hex color.") + + elif isinstance(colors, tuple): + if isinstance(colors[0], Number): + colors = [colors] + else: + colors = list(colors) + + # convert color elements in list to tuple color + for j, each_color in enumerate(colors): + if 'rgb' in each_color: + each_color = color_parser(each_color, unlabel_rgb) + for value in each_color: + if value > 255.0: + raise exceptions.PlotlyError( + "Whoops! The elements in your rgb colors " + "tuples cannot exceed 255.0." + ) + each_color = color_parser(each_color, unconvert_from_RGB_255) + colors[j] = each_color + + if '#' in each_color: + each_color = color_parser(each_color, hex_to_rgb) + each_color = color_parser(each_color, unconvert_from_RGB_255) + + colors[j] = each_color + + if isinstance(each_color, tuple): + for value in each_color: + if value > 1.0: + raise exceptions.PlotlyError( + "Whoops! The elements in your colors tuples " + "cannot exceed 1.0." + ) + colors[j] = each_color + + if colortype == 'rgb' and not isinstance(colors, six.string_types): + for j, each_color in enumerate(colors): + rgb_color = color_parser(each_color, convert_to_RGB_255) + colors[j] = color_parser(rgb_color, label_rgb) + + return colors + + +def validate_colors_dict(colors, colortype='tuple'): + """ + Validates dictioanry of color(s) + """ + # validate each color element in the dictionary + for key in colors: + if 'rgb' in colors[key]: + colors[key] = color_parser(colors[key], unlabel_rgb) + for value in colors[key]: + if value > 255.0: + raise exceptions.PlotlyError( + "Whoops! The elements in your rgb colors " + "tuples cannot exceed 255.0." + ) + colors[key] = color_parser(colors[key], unconvert_from_RGB_255) + + if '#' in colors[key]: + colors[key] = color_parser(colors[key], hex_to_rgb) + colors[key] = color_parser(colors[key], unconvert_from_RGB_255) + + if isinstance(colors[key], tuple): + for value in colors[key]: + if value > 1.0: + raise exceptions.PlotlyError( + "Whoops! The elements in your colors tuples " + "cannot exceed 1.0." + ) + + if colortype == 'rgb': + for key in colors: + colors[key] = color_parser(colors[key], convert_to_RGB_255) + colors[key] = color_parser(colors[key], label_rgb) + + return colors + + +def convert_colors_to_same_type(colors, colortype='rgb', scale=None, + return_default_colors=False, + num_of_defualt_colors=2): + """ + Converts color(s) to the specified color type + + Takes a single color or an iterable of colors, as well as a list of scale + values, and outputs a 2-pair of the list of color(s) converted all to an + rgb or tuple color type, aswell as the scale as the second element. If + colors is a Plotly Scale name, then 'scale' will be forced to the scale + from the respective colorscale and the colors in that colorscale will also + be coverted to the selected colortype. If colors is None, then there is an + option to return portion of the DEFAULT_PLOTLY_COLORS + + :param (str|tuple|list) colors: either a plotly scale name, an rgb or hex + color, a color tuple or a list/tuple of colors + :param (list) scale: see docs for validate_scale_values() + + :rtype (tuple) (colors_list, scale) if scale is None in the function call, + then scale will remain None in the returned tuple + """ + colors_list = [] + + if colors is None and return_default_colors is True: + colors_list = DEFAULT_PLOTLY_COLORS[0:num_of_defualt_colors] + + if isinstance(colors, str): + if colors in PLOTLY_SCALES: + colors_list = colorscale_to_colors(PLOTLY_SCALES[colors]) + if scale is None: + scale = colorscale_to_scale(PLOTLY_SCALES[colors]) + + elif 'rgb' in colors or '#' in colors: + colors_list = [colors] + + elif isinstance(colors, tuple): + if isinstance(colors[0], Number): + colors_list = [colors] + else: + colors_list = list(colors) + + elif isinstance(colors, list): + colors_list = colors + + # validate scale + if scale is not None: + validate_scale_values(scale) + + if len(colors_list) != len(scale): + raise exceptions.PlotlyError( + 'Make sure that the length of your scale matches the length ' + 'of your list of colors which is {}.'.format(len(colors_list)) + ) + + # convert all colors to rgb + for j, each_color in enumerate(colors_list): + if '#' in each_color: + each_color = color_parser( + each_color, hex_to_rgb + ) + each_color = color_parser( + each_color, label_rgb + ) + colors_list[j] = each_color + + elif isinstance(each_color, tuple): + each_color = color_parser( + each_color, convert_to_RGB_255 + ) + each_color = color_parser( + each_color, label_rgb + ) + colors_list[j] = each_color + + if colortype == 'rgb': + return (colors_list, scale) + elif colortype == 'tuple': + for j, each_color in enumerate(colors_list): + each_color = color_parser( + each_color, unlabel_rgb + ) + each_color = color_parser( + each_color, unconvert_from_RGB_255 + ) + colors_list[j] = each_color + return (colors_list, scale) + else: + raise exceptions.PlotlyError('You must select either rgb or tuple ' + 'for your colortype variable.') + + +def convert_dict_colors_to_same_type(colors_dict, colortype='rgb'): + """ + Converts a colors in a dictioanry of colors to the specified color type + + :param (dict) colors_dict: a dictioanry whose values are single colors + """ + for key in colors_dict: + if '#' in colors_dict[key]: + colors_dict[key] = color_parser( + colors_dict[key], hex_to_rgb + ) + colors_dict[key] = color_parser( + colors_dict[key], label_rgb + ) + + elif isinstance(colors_dict[key], tuple): + colors_dict[key] = color_parser( + colors_dict[key], convert_to_RGB_255 + ) + colors_dict[key] = color_parser( + colors_dict[key], label_rgb + ) + + if colortype == 'rgb': + return colors_dict + elif colortype == 'tuple': + for key in colors_dict: + colors_dict[key] = color_parser( + colors_dict[key], unlabel_rgb + ) + colors_dict[key] = color_parser( + colors_dict[key], unconvert_from_RGB_255 + ) + return colors_dict + else: + raise exceptions.PlotlyError('You must select either rgb or tuple ' + 'for your colortype variable.') + + +def validate_scale_values(scale): + """ + Validates scale values from a colorscale + + :param (list) scale: a strictly increasing list of floats that begins + with 0 and ends with 1. Its usage derives from a colorscale which is + a list of two-lists (a list with two elements) of the form + [value, color] which are used to determine how interpolation weighting + works between the colors in the colorscale. Therefore scale is just + the extraction of these values from the two-lists in order + """ + if len(scale) < 2: + raise exceptions.PlotlyError('You must input a list of scale values ' + 'that has at least two values.') + + if (scale[0] != 0) or (scale[-1] != 1): + raise exceptions.PlotlyError( + 'The first and last number in your scale must be 0.0 and 1.0 ' + 'respectively.' + ) + + if not all(x < y for x, y in zip(scale, scale[1:])): + raise exceptions.PlotlyError( + "'scale' must be a list that contains a strictly increasing " + "sequence of numbers." + ) + + +def validate_colorscale(colorscale): + """Validate the structure, scale values and colors of colorscale.""" + if not isinstance(colorscale, list): + # TODO Write tests for these exceptions + raise exceptions.PlotlyError("A valid colorscale must be a list.") + if not all(isinstance(innerlist, list) for innerlist in colorscale): + raise exceptions.PlotlyError( + "A valid colorscale must be a list of lists." + ) + colorscale_colors = colorscale_to_colors(colorscale) + scale_values = colorscale_to_scale(colorscale) + + validate_scale_values(scale_values) + validate_colors(colorscale_colors) + + +def make_colorscale(colors, scale=None): + """ + Makes a colorscale from a list of colors and a scale + + Takes a list of colors and scales and constructs a colorscale based + on the colors in sequential order. If 'scale' is left empty, a linear- + interpolated colorscale will be generated. If 'scale' is a specificed + list, it must be the same legnth as colors and must contain all floats + For documentation regarding to the form of the output, see + https://plot.ly/python/reference/#mesh3d-colorscale + + :param (list) colors: a list of single colors + """ + colorscale = [] + + # validate minimum colors length of 2 + if len(colors) < 2: + raise exceptions.PlotlyError('You must input a list of colors that ' + 'has at least two colors.') + + if scale is None: + scale_incr = 1./(len(colors) - 1) + return [[i * scale_incr, color] for i, color in enumerate(colors)] + + else: + if len(colors) != len(scale): + raise exceptions.PlotlyError('The length of colors and scale ' + 'must be the same.') + + validate_scale_values(scale) + + colorscale = [list(tup) for tup in zip(scale, colors)] + return colorscale + + +def find_intermediate_color(lowcolor, highcolor, intermed, colortype='tuple'): + """ + Returns the color at a given distance between two colors + + This function takes two color tuples, where each element is between 0 + and 1, along with a value 0 < intermed < 1 and returns a color that is + intermed-percent from lowcolor to highcolor. If colortype is set to 'rgb', + the function will automatically convert the rgb type to a tuple, find the + intermediate color and return it as an rgb color. + """ + if colortype == 'rgb': + # convert to tuple color, eg. (1, 0.45, 0.7) + lowcolor = unlabel_rgb(lowcolor) + highcolor = unlabel_rgb(highcolor) + + diff_0 = float(highcolor[0] - lowcolor[0]) + diff_1 = float(highcolor[1] - lowcolor[1]) + diff_2 = float(highcolor[2] - lowcolor[2]) + + inter_med_tuple = ( + lowcolor[0] + intermed * diff_0, + lowcolor[1] + intermed * diff_1, + lowcolor[2] + intermed * diff_2 + ) + + if colortype == 'rgb': + # back to an rgb string, e.g. rgb(30, 20, 10) + inter_med_rgb = label_rgb(inter_med_tuple) + return inter_med_rgb + + return inter_med_tuple + + +def unconvert_from_RGB_255(colors): + """ + Return a tuple where each element gets divided by 255 + + Takes a (list of) color tuple(s) where each element is between 0 and + 255. Returns the same tuples where each tuple element is normalized to + a value between 0 and 1 + """ + return (colors[0]/(255.0), + colors[1]/(255.0), + colors[2]/(255.0)) + + +def convert_to_RGB_255(colors): + """ + Multiplies each element of a triplet by 255 + + Each coordinate of the color tuple is rounded to the nearest float and + then is turned into an integer. If a number is of the form x.5, then + if x is odd, the number rounds up to (x+1). Otherwise, it rounds down + to just x. This is the way rounding works in Python 3 and in current + statistical analysis to avoid rounding bias + + :param (list) rgb_components: grabs the three R, G and B values to be + returned as computed in the function + """ + rgb_components = [] + + for component in colors: + rounded_num = decimal.Decimal(str(component*255.0)).quantize( + decimal.Decimal('1'), rounding=decimal.ROUND_HALF_EVEN + ) + # convert rounded number to an integer from 'Decimal' form + rounded_num = int(rounded_num) + rgb_components.append(rounded_num) + + return (rgb_components[0], rgb_components[1], rgb_components[2]) + + +def n_colors(lowcolor, highcolor, n_colors, colortype='tuple'): + """ + Splits a low and high color into a list of n_colors colors in it + + Accepts two color tuples and returns a list of n_colors colors + which form the intermediate colors between lowcolor and highcolor + from linearly interpolating through RGB space. If colortype is 'rgb' + the function will return a list of colors in the same form. + """ + if colortype == 'rgb': + # convert to tuple + lowcolor = unlabel_rgb(lowcolor) + highcolor = unlabel_rgb(highcolor) + + diff_0 = float(highcolor[0] - lowcolor[0]) + incr_0 = diff_0/(n_colors - 1) + diff_1 = float(highcolor[1] - lowcolor[1]) + incr_1 = diff_1/(n_colors - 1) + diff_2 = float(highcolor[2] - lowcolor[2]) + incr_2 = diff_2/(n_colors - 1) + list_of_colors = [] + + for index in range(n_colors): + new_tuple = (lowcolor[0] + (index * incr_0), + lowcolor[1] + (index * incr_1), + lowcolor[2] + (index * incr_2)) + list_of_colors.append(new_tuple) + + if colortype == 'rgb': + # back to an rgb string + list_of_colors = color_parser(list_of_colors, label_rgb) + + return list_of_colors + + +def label_rgb(colors): + """ + Takes tuple (a, b, c) and returns an rgb color 'rgb(a, b, c)' + """ + return ('rgb(%s, %s, %s)' % (colors[0], colors[1], colors[2])) + + +def unlabel_rgb(colors): + """ + Takes rgb color(s) 'rgb(a, b, c)' and returns tuple(s) (a, b, c) + + This function takes either an 'rgb(a, b, c)' color or a list of + such colors and returns the color tuples in tuple(s) (a, b, c) + """ + str_vals = '' + for index in range(len(colors)): + try: + float(colors[index]) + str_vals = str_vals + colors[index] + except ValueError: + if colors[index] == ',' or colors[index] == '.': + str_vals = str_vals + colors[index] + + str_vals = str_vals + ',' + numbers = [] + str_num = '' + for char in str_vals: + if char != ',': + str_num = str_num + char + else: + numbers.append(float(str_num)) + str_num = '' + return (numbers[0], numbers[1], numbers[2]) + + +def hex_to_rgb(value): + """ + Calculates rgb values from a hex color code. + + :param (string) value: Hex color string + + :rtype (tuple) (r_value, g_value, b_value): tuple of rgb values + """ + value = value.lstrip('#') + hex_total_length = len(value) + rgb_section_length = hex_total_length // 3 + return tuple(int(value[i:i + rgb_section_length], 16) + for i in range(0, hex_total_length, rgb_section_length)) + + +def colorscale_to_colors(colorscale): + """ + Extracts the colors from colorscale as a list + """ + color_list = [] + for item in colorscale: + color_list.append(item[1]) + return color_list + + +def colorscale_to_scale(colorscale): + """ + Extracts the interpolation scale values from colorscale as a list + """ + scale_list = [] + for item in colorscale: + scale_list.append(item[0]) + return scale_list + + +def convert_colorscale_to_rgb(colorscale): + """ + Converts the colors in a colorscale to rgb colors + + A colorscale is an array of arrays, each with a numeric value as the + first item and a color as the second. This function specifically is + converting a colorscale with tuple colors (each coordinate between 0 + and 1) into a colorscale with the colors transformed into rgb colors + """ + for color in colorscale: + color[1] = convert_to_RGB_255(color[1]) + + for color in colorscale: + color[1] = label_rgb(color[1]) + return colorscale