From 35d76fd6b340fcf98c9bb548320509d72758a246 Mon Sep 17 00:00:00 2001 From: mattijn Date: Tue, 11 Apr 2023 23:17:45 +0200 Subject: [PATCH 01/17] start with outline --- doc/user_guide/color_scales.rst | 187 ++++++++++++++++++++++++++++++++ doc/user_guide/data.rst | 1 + 2 files changed, 188 insertions(+) create mode 100644 doc/user_guide/color_scales.rst diff --git a/doc/user_guide/color_scales.rst b/doc/user_guide/color_scales.rst new file mode 100644 index 000000000..0edd9e1fb --- /dev/null +++ b/doc/user_guide/color_scales.rst @@ -0,0 +1,187 @@ +.. currentmodule:: altair + +.. _user-guide-color-scales: + +Color Scales +============ + +Effective visualization of scientific data requires careful attention +to color selection. Choosing the right colors can be a complex task, +involving considerations such as color perception, accessibility, +and aesthetics. Fortunately, there are a number of resources available +to help simplify this process, including pre-designed color schemes +and tools for exploring and modifying color scales. In this guide, +we'll explore some of the basics of color selection for scientific +data visualization, including different types of color scales and +examples of their use. + +I. The Basics of Color Selection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Color selection is an essential aspect of creating clear and informative +visualizations of scientific data. To make the most effective choices, +it's important to understand the basics of color perception and theory. + +A. Color Perception and Theory +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. The importance of perceptually uniform color scales +------------------------------------------------------ + +Using colors that are perceptually uniform can help ensure that viewers +can accurately perceive the relative differences between data points. By +maintaining consistent differences in color perception across a scale, you +can create visualizations that are not only informative but also easy to +understand. + +.. code-block:: none + + A comparison of perceptually uniform color scales and non-uniform + scales, showing how the choice of color space can affect the perceived + brightness and contrast of colors. + +2. Different types of color spaces +---------------------------------- + +Different color spaces, such as RGB or HSL, offer different advantages and +can affect the perceived brightness and contrast of colors. By +understanding the nuances of color spaces, you can create visualizations +that are not only informative but also visually appealing. + +B. Accessibility and Aesthetics +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. Considerations for color-blind viewers +----------------------------------------- + +Around 8% of men and 0.5% of women have some form of color vision deficiency. +To make sure your visualizations are accessible to all viewers, it's +important to choose colors that are distinguishable even for those with +color vision deficiencies. By using colors that have a high degree of +contrast, you can create visualizations that are not only informative but +also inclusive. + +.. code-block:: none + + A comparison of color schemes that are accessible to color-blind + viewers versus those that are not. + +2. Guidelines for creating visually appealing presentations +----------------------------------------------------------- + +While accuracy is the primary goal of visualizations, aesthetics also play +an important role in engaging viewers and making data more memorable. +By using a limited color palette, choosing harmonious colors, and balancing +colors to create a sense of visual hierarchy, you can create visualizations +that are not only informative but also visually stunning. + +.. code-block:: none + + A visualization that uses color to create a sense of visual hierarchy, + such as a heatmap or a scatterplot. + + +II. Types of Color Scales +~~~~~~~~~~~~~~~~~~~~~~~~~ + +This sections presents the three primary color scales used in data +visualization: sequential, diverging, and categorical. The section +includes examples of data that can be visualized using each scale and +popular color schemes used in practice. + +A. Sequential Scales +^^^^^^^^^^^^^^^^^^^^ + +1. Examples of sequential data +------------------------------ + +Sequential scales are best suited for data that has an inherent ordering, +such as data that varies from low to high, or over time. Examples of +sequential data include temperature or population density, where the +range of values is continuous and unbroken. + +1. Example color schemes for sequential data +-------------------------------------------- + +.. code-block:: none + + Sequential color schemes. + +B. Diverging Scales +^^^^^^^^^^^^^^^^^^^ + +1. Examples of diverging data +----------------------------- + +Diverging scales are best suited for data that varies above and below a +center point, such as positive and negative deviations from a mean. +Examples of diverging data include measures of deviation, such as +temperature anomalies or changes in sea level. + +1. Example color schemes for diverging data +------------------------------------------- + +.. code-block:: none + + Diverging color schemes. + + +C. Categorical Scales +^^^^^^^^^^^^^^^^^^^^^ + +1. Examples of categorical data +------------------------------- + +Categorical scales are best suited for data that is unordered, such as +different species or categories. Examples of categorical data include +the types of flowers in a garden, or different political affiliations. + +1. Example color schemes for categorical data +--------------------------------------------- + +.. code-block:: none + + Categorical color schemes. + +III. Using Tools for Color Selection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A. Pre-Designed Color Palettes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. Example color palettes from the other packages +------------------------------------------------- + +This subsection focuses on pre-designed color palettes that can be +used in data visualization from other sources. + +B. Modifying Color Scales +^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. Tools for adjusting brightness, saturation, and hue +------------------------------------------------------ + +This subsection provides an overview of the different ways in which +brightness, saturation, and hue can be modified to create custom color +scales. It explains how changing these attributes can help to emphasize +or de-emphasize certain aspects of the data being visualized. + +2. Customizing color scales to match specific data requirements +--------------------------------------------------------------- + +This subsection explores the idea of creating custom color scales that +are tailored to the specific needs of the data being visualized. It +discusses how to use the tools introduced in III.B.1 to create color +scales that are better suited to representing the nuances of the data, +and provides examples of how this can be achieved in practice. + +Conclusion +~~~~~~~~~~ + +By utilizing the tools and resources available for color selection, +data visualizers can create effective and visually appealing +presentations that accurately convey scientific data. Whether using +pre-designed color schemes or customizing color scales, it is +essential to consider factors such as accessibility, aesthetics, +and perceptual relationships in order to create the most effective +visualizations possible. \ No newline at end of file diff --git a/doc/user_guide/data.rst b/doc/user_guide/data.rst index 382375434..4cfbb3f3d 100644 --- a/doc/user_guide/data.rst +++ b/doc/user_guide/data.rst @@ -620,6 +620,7 @@ data before usage in Altair using GeoPandas for example as such: encodings/index marks/index transform/index + color_scales interactions compound_charts scale_resolve From 230549c5392adc0f1e1a33e62f7c4cf6add8bef8 Mon Sep 17 00:00:00 2001 From: mattijn Date: Tue, 11 Apr 2023 23:32:35 +0200 Subject: [PATCH 02/17] naming --- doc/user_guide/data.rst | 2 +- doc/user_guide/{color_scales.rst => scale_color.rst} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename doc/user_guide/{color_scales.rst => scale_color.rst} (99%) diff --git a/doc/user_guide/data.rst b/doc/user_guide/data.rst index 4cfbb3f3d..c934aea70 100644 --- a/doc/user_guide/data.rst +++ b/doc/user_guide/data.rst @@ -620,7 +620,7 @@ data before usage in Altair using GeoPandas for example as such: encodings/index marks/index transform/index - color_scales + scale_color interactions compound_charts scale_resolve diff --git a/doc/user_guide/color_scales.rst b/doc/user_guide/scale_color.rst similarity index 99% rename from doc/user_guide/color_scales.rst rename to doc/user_guide/scale_color.rst index 0edd9e1fb..2aea961ad 100644 --- a/doc/user_guide/color_scales.rst +++ b/doc/user_guide/scale_color.rst @@ -1,6 +1,6 @@ .. currentmodule:: altair -.. _user-guide-color-scales: +.. _user-guide-color: Color Scales ============ From f2f7989984426b2b5cdcd96537859dfe02271422 Mon Sep 17 00:00:00 2001 From: mattijn Date: Fri, 14 Apr 2023 20:22:29 +0200 Subject: [PATCH 03/17] python code working --- doc/user_guide/scale_color.rst | 303 ++++++++++++++++++++++++++++++--- 1 file changed, 278 insertions(+), 25 deletions(-) diff --git a/doc/user_guide/scale_color.rst b/doc/user_guide/scale_color.rst index 2aea961ad..1695be723 100644 --- a/doc/user_guide/scale_color.rst +++ b/doc/user_guide/scale_color.rst @@ -15,15 +15,191 @@ we'll explore some of the basics of color selection for scientific data visualization, including different types of color scales and examples of their use. -I. The Basics of Color Selection -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. altair-plot:: + :remove-code: + :output: none + + import altair as alt + import pandas as pd + + def chart_settings(scheme_name, dict_schemes, continuous): + # predefined scheme handling + if type(dict_schemes[scheme_name]) is not list: + if continuous: + no_colors_in_scheme = 300 + else: + no_colors_in_scheme = dict_schemes[scheme_name] + df = pd.DataFrame({"i": range(no_colors_in_scheme)}) + custom_scheme = False + + # custom scheme handling + else: + if continuous: + no_colors_in_scheme = 300 + df = pd.DataFrame({"i": range(no_colors_in_scheme)}) + else: + no_colors_in_scheme = len(dict_schemes[scheme_name]) + colors_in_scheme = dict_schemes[scheme_name] + df = pd.DataFrame({"i": range(no_colors_in_scheme), "hex": colors_in_scheme}) + custom_scheme = True + + # static height + h = 50 + + # width is dependent on no. of colors in scheme + if continuous: + w = 2 + elif no_colors_in_scheme <= 5: + w = 50 + elif (no_colors_in_scheme > 5) and (no_colors_in_scheme <= 12): + w = 30 + else: + w = 15 + # cornerRadius dependent on height-width ratio + cr_l = 0 if continuous else int(w / 2.4) + cr_s = 0 if continuous else 5 + + return df, w, h, cr_l, cr_s, no_colors_in_scheme, custom_scheme + + + def plot_scheme(scheme_name, dict_schemes, continuous=False, cvd=False): + df, w, h, cr_l, cr_s, no, cs = chart_settings(scheme_name, dict_schemes, continuous) + + # define internal color scale definition + if cs and continuous: + expr_hex = "scale('color', datum.i)" + color_type = "quantitative" + elif cs and not continuous: + expr_hex = "datum.hex" + color_type = "ordinal" + elif not cs: + expr_hex = "scale('color', datum.i)" + color_type = "ordinal" + scale_scheme = alt.Scale(range=dict_schemes[scheme_name]) if cs else alt.Scale(scheme=scheme_name) + + color_scale = ( + alt.Chart(alt.sequence(0, no, as_="i")) + .mark_rect(height=alt.expr("0"), width=alt.expr("0")) + .encode(alt.Color("i", type=color_type).scale(scale_scheme).legend(None)) + ) + + # define styling axis + my_y_axis = alt.Axis( + grid=False, + ticks=False, + labels=False, + domain=False, + titleAngle=0, + titleAlign="right", + titleBaseline="middle", + ) + + normal_vision = ( + alt.Chart(df, height=h, width=alt.Step(w)) + .transform_calculate( + hex=expr_hex, + rgb="rgb(datum.hex)", + lum="luminance(datum.hex)", + ) + .mark_rect(cornerRadius=cr_l, discreteBandSize=w) + .encode( + x=alt.X("i:O").axis(None), + y=alt.Y("normal vision:N").axis(my_y_axis), + fill=alt.Fill("hex", type=color_type).scale(None).legend(None), + stroke=alt.Stroke("hex", type=color_type).scale(None).legend(None), + tooltip=[ + alt.Tooltip("hex:O"), + alt.Tooltip("rgb:O"), + alt.Tooltip("lum:O", format=".2"), + ], + ) + ) + + if not cvd: + chart_comb = ( + (normal_vision & color_scale) + .configure_concat(spacing=1) + .configure_view(stroke=None) + ) + return chart_comb + + + green_blindness = ( + alt.Chart(df, height=20, width=alt.Step(w)) + .transform_calculate( + hex=expr_hex, + rgb="rgb(datum.hex)", + gb_r="pow((4211+0.667*pow(datum.rgb['g'], 2.2)+0.2802*pow(datum.rgb['r'], 2.2)),1/2.2)", + gb_g="pow((4211+0.667*pow(datum.rgb['g'], 2.2)+0.2802*pow(datum.rgb['r'], 2.2)),1/2.2)", + gb_b="pow((4211+0.95724*pow(datum.rgb['b'], 2.2)+0.02138*pow(datum.rgb['b'], 2.2)-0.02138*pow(datum.rgb['r'], 2.2)),1/2.2)", + greenblind_rgb="rgb(datum.gb_r,datum.gb_g,datum.gb_b)", + ) + .mark_rect(cornerRadius=cr_s) + .encode( + x=alt.X("i:O").axis(None), + y=alt.Y("green-blindness:N").axis(my_y_axis), + fill=alt.Fill("greenblind_rgb", type=color_type).scale(None).legend(None), + stroke=alt.Stroke("greenblind_rgb", type=color_type).scale(None).legend(None), + ) + ) + + red_blindness = ( + alt.Chart(df, height=20, width=alt.Step(w)) + .transform_calculate( + hex=expr_hex, + rgb="rgb(datum.hex)", + rb_r="pow((782.7+0.8806*pow(datum.rgb['g'], 2.2)+0.1115*pow(datum.rgb['r'], 2.2)),1/2.2)", + rb_g="pow((782.7+0.8806*pow(datum.rgb['g'], 2.2)+0.1115*pow(datum.rgb['r'], 2.2)),1/2.2)", + rb_b="pow((782.7+0.992052*pow(datum.rgb['b'], 2.2)-0.03974*pow(datum.rgb['b'], 2.2)+0.003974*pow(datum.rgb['r'], 2.2)),1/2.2)", + redblind_rgb="rgb(datum.rb_r,datum.rb_g,datum.rb_b)", + ) + .mark_rect(cornerRadius=cr_s) + .encode( + x=alt.X("i:O").axis(None), + y=alt.Y("red-blindness:N").axis(my_y_axis), + fill=alt.Fill("redblind_rgb", type=color_type).scale(None).legend(None), + stroke=alt.Stroke("redblind_rgb", type=color_type).scale(None).legend(None), + ) + ) + + grayscale = ( + alt.Chart(df, height=20, width=alt.Step(w)) + .transform_calculate( + hex=expr_hex, + rgb="rgb(datum.hex)", + lumn_rgb="datum.rgb['r']*0.3+datum.rgb['g']*0.59+datum.rgb['b']*0.3", + grayscale_rgb="rgb(datum.lumn_rgb,datum.lumn_rgb,datum.lumn_rgb)", + ) + .mark_rect(cornerRadius=cr_s) + .encode( + x=alt.X("i:O").axis(None), + y=alt.Y("grayscale:N").axis(my_y_axis), + fill=alt.Fill("grayscale_rgb", type=color_type).scale(None).legend(None), + stroke=alt.Stroke("grayscale_rgb", type=color_type).scale(None).legend(None), + + ) + ) + + + + chart_comb = ( + (normal_vision & green_blindness & red_blindness & grayscale & color_scale) + .configure_concat(spacing=1) + .configure_view(stroke=None) + ) + return chart_comb + + + +The Basics of Color Selection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Color selection is an essential aspect of creating clear and informative visualizations of scientific data. To make the most effective choices, it's important to understand the basics of color perception and theory. -A. Color Perception and Theory -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Color Perception and Theory +^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1. The importance of perceptually uniform color scales ------------------------------------------------------ @@ -34,11 +210,33 @@ maintaining consistent differences in color perception across a scale, you can create visualizations that are not only informative but also easy to understand. -.. code-block:: none +.. altair-plot:: + :hide-code: + :output: none + + percep_schemes = { + "uniform": ["#542788", "#1f7abd", "#4dbd33", "#fdae61", "#d7191c"], + "non-uniform": ["#FF0000", "#FF6600", "#FFFF00", "#00FF00", "#0000FF"] + } + +.. list-table:: + :widths: 100 + :header-rows: 1 + + * - Example + * - Uniform color scheme + + .. altair-plot:: + :remove-code: + + plot_scheme("uniform", percep_schemes, cvd=True, continuous=True) - A comparison of perceptually uniform color scales and non-uniform - scales, showing how the choice of color space can affect the perceived - brightness and contrast of colors. + * - **Non-uniform** color scheme + + .. altair-plot:: + :remove-code: + + plot_scheme("non-uniform", percep_schemes, cvd=True, continuous=True) 2. Different types of color spaces ---------------------------------- @@ -48,8 +246,8 @@ can affect the perceived brightness and contrast of colors. By understanding the nuances of color spaces, you can create visualizations that are not only informative but also visually appealing. -B. Accessibility and Aesthetics -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Accessibility and Aesthetics +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1. Considerations for color-blind viewers ----------------------------------------- @@ -61,12 +259,35 @@ color vision deficiencies. By using colors that have a high degree of contrast, you can create visualizations that are not only informative but also inclusive. -.. code-block:: none +.. altair-plot:: + :hide-code: + :output: none + + color_vision_deficiency = { + 'tol_sunset_friendly': ['#364B9A', '#4A7BB7', '#6EA6CD', '#98CAE1', '#C2E4EF', '#EAECCC', '#FEDA8B', '#FDB366', '#F67E4B', '#DD3D2D', '#A50026'], + 'unfriendly' : ['#006400', '#228B22', '#00FF00', '#7FFF00', '#FFFF00', '#FFA500', '#FF0000'] + } + +.. list-table:: + :widths: 100 + :header-rows: 1 + + * - Example + * - Friendly diverging color scheme (`sunset` by Paul Ton, see section xx for more) - A comparison of color schemes that are accessible to color-blind - viewers versus those that are not. + .. altair-plot:: + :remove-code: -2. Guidelines for creating visually appealing presentations + plot_scheme("tol_sunset_friendly", color_vision_deficiency, cvd=True, continuous=True) + + * - **Unfriendly** diverging color scale + + .. altair-plot:: + :remove-code: + + plot_scheme("unfriendly", color_vision_deficiency, cvd=True, continuous=True) + +1. Guidelines for creating visually appealing presentations ----------------------------------------------------------- While accuracy is the primary goal of visualizations, aesthetics also play @@ -81,8 +302,8 @@ that are not only informative but also visually stunning. such as a heatmap or a scatterplot. -II. Types of Color Scales -~~~~~~~~~~~~~~~~~~~~~~~~~ +Types of Color Scales +~~~~~~~~~~~~~~~~~~~~~ This sections presents the three primary color scales used in data visualization: sequential, diverging, and categorical. The section @@ -92,20 +313,52 @@ popular color schemes used in practice. A. Sequential Scales ^^^^^^^^^^^^^^^^^^^^ -1. Examples of sequential data ------------------------------- - Sequential scales are best suited for data that has an inherent ordering, such as data that varies from low to high, or over time. Examples of sequential data include temperature or population density, where the range of values is continuous and unbroken. -1. Example color schemes for sequential data --------------------------------------------- +.. altair-plot:: + :hide-code: + :output: none + + seqs_schemes = { + "blues": 9, + "tealblues": 9, + "teals": 9, + "greens": 9, + "browns": 9, + "greys": 9, + "purples": 9, + "warmgreys": 9, + "reds": 9, + "oranges": 9, + } + +.. list-table:: + :widths: 25 75 + :header-rows: 1 + + * - Scheme + - Example + * - .. code-block:: none + + blues + + - .. altair-plot:: + :remove-code: -.. code-block:: none + plot_scheme("blues", seqs_schemes, cvd=False, continuous=True) + + * - .. code-block:: none - Sequential color schemes. + warmgreys + + - .. altair-plot:: + :remove-code: + + plot_scheme("warmgreys", seqs_schemes, cvd=False, continuous=False) + B. Diverging Scales ^^^^^^^^^^^^^^^^^^^ @@ -143,8 +396,8 @@ the types of flowers in a garden, or different political affiliations. Categorical color schemes. -III. Using Tools for Color Selection -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Using Tools for Color Selection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A. Pre-Designed Color Palettes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 06a8577fbfedaf190f9967e44dfc712ab622f62e Mon Sep 17 00:00:00 2001 From: mattijn Date: Sat, 15 Apr 2023 16:00:39 +0200 Subject: [PATCH 04/17] improve python code --- doc/user_guide/scale_color.rst | 263 ++++++++++++++++++++------------- 1 file changed, 161 insertions(+), 102 deletions(-) diff --git a/doc/user_guide/scale_color.rst b/doc/user_guide/scale_color.rst index 1695be723..526d6a407 100644 --- a/doc/user_guide/scale_color.rst +++ b/doc/user_guide/scale_color.rst @@ -23,59 +23,44 @@ examples of their use. import pandas as pd def chart_settings(scheme_name, dict_schemes, continuous): - # predefined scheme handling - if type(dict_schemes[scheme_name]) is not list: - if continuous: - no_colors_in_scheme = 300 - else: - no_colors_in_scheme = dict_schemes[scheme_name] - df = pd.DataFrame({"i": range(no_colors_in_scheme)}) - custom_scheme = False - - # custom scheme handling - else: - if continuous: - no_colors_in_scheme = 300 - df = pd.DataFrame({"i": range(no_colors_in_scheme)}) - else: - no_colors_in_scheme = len(dict_schemes[scheme_name]) - colors_in_scheme = dict_schemes[scheme_name] - df = pd.DataFrame({"i": range(no_colors_in_scheme), "hex": colors_in_scheme}) - custom_scheme = True - - # static height - h = 50 - - # width is dependent on no. of colors in scheme + # check if the input is a defined or custom scheme + custom_scheme = True if type(dict_schemes[scheme_name]) is list else False + + # define a data sequence of 300 for continuous color schemes if continuous: - w = 2 - elif no_colors_in_scheme <= 5: - w = 50 - elif (no_colors_in_scheme > 5) and (no_colors_in_scheme <= 12): - w = 30 + no_colors = 300 + data = alt.sequence(0, no_colors, as_="i") + + # define a data sequence using dict for defined schemes + elif type(dict_schemes[scheme_name]) is int: + no_colors = dict_schemes[scheme_name] + data = alt.sequence(0, no_colors, as_="i") + + # define a dataframe for custom schemes (possible with Altair only?) else: - w = 15 + no_colors = len(dict_schemes[scheme_name]) + colors_in_scheme = dict_schemes[scheme_name] + data = pd.DataFrame({"i": range(no_colors), "hex": colors_in_scheme}) + + # dynamic width + w = 1.5 if continuous else 50 if no_colors <= 5 else 30 if no_colors <= 12 else 15 + # cornerRadius dependent on height-width ratio cr_l = 0 if continuous else int(w / 2.4) cr_s = 0 if continuous else 5 - return df, w, h, cr_l, cr_s, no_colors_in_scheme, custom_scheme - - - def plot_scheme(scheme_name, dict_schemes, continuous=False, cvd=False): - df, w, h, cr_l, cr_s, no, cs = chart_settings(scheme_name, dict_schemes, continuous) + return { + "data": data, + "width": w, + "cornerRadius_large": cr_l, + "cornerRadius_small": cr_s, + "no_colors": no_colors, + "custom": custom_scheme + } - # define internal color scale definition - if cs and continuous: - expr_hex = "scale('color', datum.i)" - color_type = "quantitative" - elif cs and not continuous: - expr_hex = "datum.hex" - color_type = "ordinal" - elif not cs: - expr_hex = "scale('color', datum.i)" - color_type = "ordinal" - scale_scheme = alt.Scale(range=dict_schemes[scheme_name]) if cs else alt.Scale(scheme=scheme_name) + def chart_color_scale(scheme_name, dict_schemes, continuous, custom, no): + color_type = "quantitative" if custom and continuous else "ordinal" + scale_scheme = alt.Scale(range=dict_schemes[scheme_name]) if custom else alt.Scale(scheme=scheme_name) color_scale = ( alt.Chart(alt.sequence(0, no, as_="i")) @@ -83,19 +68,11 @@ examples of their use. .encode(alt.Color("i", type=color_type).scale(scale_scheme).legend(None)) ) - # define styling axis - my_y_axis = alt.Axis( - grid=False, - ticks=False, - labels=False, - domain=False, - titleAngle=0, - titleAlign="right", - titleBaseline="middle", - ) - + return color_scale + + def chart_normal_vision(data, h, w, expr_hex, my_y_axis, cr_l): normal_vision = ( - alt.Chart(df, height=h, width=alt.Step(w)) + alt.Chart(data, height=h, width=alt.Step(w)) .transform_calculate( hex=expr_hex, rgb="rgb(datum.hex)", @@ -104,9 +81,9 @@ examples of their use. .mark_rect(cornerRadius=cr_l, discreteBandSize=w) .encode( x=alt.X("i:O").axis(None), - y=alt.Y("normal vision:N").axis(my_y_axis), - fill=alt.Fill("hex", type=color_type).scale(None).legend(None), - stroke=alt.Stroke("hex", type=color_type).scale(None).legend(None), + y=alt.Y("normal vision:O").axis(my_y_axis), + fill=alt.Fill("hex:O").scale(None).legend(None), + stroke=alt.Stroke("hex:O").scale(None).legend(None), tooltip=[ alt.Tooltip("hex:O"), alt.Tooltip("rgb:O"), @@ -114,18 +91,11 @@ examples of their use. ], ) ) + return normal_vision - if not cvd: - chart_comb = ( - (normal_vision & color_scale) - .configure_concat(spacing=1) - .configure_view(stroke=None) - ) - return chart_comb - - + def chart_green_blindness(data, h, w, expr_hex, my_y_axis, cr_s): green_blindness = ( - alt.Chart(df, height=20, width=alt.Step(w)) + alt.Chart(data, height=h, width=alt.Step(w)) .transform_calculate( hex=expr_hex, rgb="rgb(datum.hex)", @@ -138,13 +108,15 @@ examples of their use. .encode( x=alt.X("i:O").axis(None), y=alt.Y("green-blindness:N").axis(my_y_axis), - fill=alt.Fill("greenblind_rgb", type=color_type).scale(None).legend(None), - stroke=alt.Stroke("greenblind_rgb", type=color_type).scale(None).legend(None), + fill=alt.Fill("greenblind_rgb:N").scale(None).legend(None), + stroke=alt.Stroke("greenblind_rgb:N").scale(None).legend(None), ) - ) + ) + return green_blindness + def chart_red_blindness(data, h, w, expr_hex, my_y_axis, cr_s): red_blindness = ( - alt.Chart(df, height=20, width=alt.Step(w)) + alt.Chart(data, height=h, width=alt.Step(w)) .transform_calculate( hex=expr_hex, rgb="rgb(datum.hex)", @@ -157,13 +129,15 @@ examples of their use. .encode( x=alt.X("i:O").axis(None), y=alt.Y("red-blindness:N").axis(my_y_axis), - fill=alt.Fill("redblind_rgb", type=color_type).scale(None).legend(None), - stroke=alt.Stroke("redblind_rgb", type=color_type).scale(None).legend(None), + fill=alt.Fill("redblind_rgb:N").scale(None).legend(None), + stroke=alt.Stroke("redblind_rgb:N").scale(None).legend(None), ) - ) + ) + return red_blindness + def chart_grayscale(data, h, w, expr_hex, my_y_axis, cr_s): grayscale = ( - alt.Chart(df, height=20, width=alt.Step(w)) + alt.Chart(data, height=h, width=alt.Step(w)) .transform_calculate( hex=expr_hex, rgb="rgb(datum.hex)", @@ -174,21 +148,61 @@ examples of their use. .encode( x=alt.X("i:O").axis(None), y=alt.Y("grayscale:N").axis(my_y_axis), - fill=alt.Fill("grayscale_rgb", type=color_type).scale(None).legend(None), - stroke=alt.Stroke("grayscale_rgb", type=color_type).scale(None).legend(None), + fill=alt.Fill("grayscale_rgb:N").scale(None).legend(None), + stroke=alt.Stroke("grayscale_rgb:N").scale(None).legend(None), ) ) + return grayscale + def plot_scheme(scheme_name, dict_schemes, continuous=False, cvd=False, grayscale=False): + # determine chart settings + chart_dict = chart_settings(scheme_name, dict_schemes, continuous) + # unpack chart_dict + custom = chart_dict['custom'] + data = chart_dict['data'] + w = chart_dict['width'] + h_l = 50 + h_s = 20 + cr_l = chart_dict['cornerRadius_large'] + cr_s = chart_dict['cornerRadius_small'] + no = chart_dict['no_colors'] - chart_comb = ( - (normal_vision & green_blindness & red_blindness & grayscale & color_scale) - .configure_concat(spacing=1) - .configure_view(stroke=None) - ) - return chart_comb + # define color scale + color_scale = chart_color_scale(scheme_name, dict_schemes, continuous, custom, no) + # define how hex-codes can be derived, from datasource or using color scale + expr_hex = "datum.hex" if custom and not continuous else "scale('color', datum.i)" + + # define styling for y-axis + my_y_axis = alt.Axis( + grid=False, + ticks=False, + labels=False, + domain=False, + titleAngle=0, + titleAlign="right", + titleBaseline="middle", + ) + + # determine color pallettes for different color deficiencies + normal_vision = chart_normal_vision(data, h_l, w, expr_hex, my_y_axis, cr_l) + green_blindness = chart_green_blindness(data, h_s, w, expr_hex, my_y_axis, cr_s) + red_blindness = chart_red_blindness(data, h_s, w, expr_hex, my_y_axis, cr_s) + monochrome = chart_grayscale(data, h_s, w, expr_hex, my_y_axis, cr_s) + + # determine which color pallettes to return + if cvd and grayscale: + chart_concat = (normal_vision & green_blindness & red_blindness & monochrome & color_scale) + elif cvd and not grayscale: + chart_concat = (normal_vision & green_blindness & red_blindness & color_scale) + else: + chart_concat = (normal_vision & color_scale) + + # set configuration on concatenated chart object + chart_comb = chart_concat.configure_concat(spacing=1).configure_view(stroke=None) + return chart_comb The Basics of Color Selection @@ -201,8 +215,8 @@ it's important to understand the basics of color perception and theory. Color Perception and Theory ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -1. The importance of perceptually uniform color scales ------------------------------------------------------- +The importance of perceptually uniform color scales +--------------------------------------------------- Using colors that are perceptually uniform can help ensure that viewers can accurately perceive the relative differences between data points. By @@ -238,8 +252,8 @@ understand. plot_scheme("non-uniform", percep_schemes, cvd=True, continuous=True) -2. Different types of color spaces ----------------------------------- +Different types of color spaces +------------------------------- Different color spaces, such as RGB or HSL, offer different advantages and can affect the perceived brightness and contrast of colors. By @@ -249,8 +263,8 @@ that are not only informative but also visually appealing. Accessibility and Aesthetics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -1. Considerations for color-blind viewers ------------------------------------------ +Considerations for color-blind viewers +-------------------------------------- Around 8% of men and 0.5% of women have some form of color vision deficiency. To make sure your visualizations are accessible to all viewers, it's @@ -287,8 +301,8 @@ also inclusive. plot_scheme("unfriendly", color_vision_deficiency, cvd=True, continuous=True) -1. Guidelines for creating visually appealing presentations ------------------------------------------------------------ +Guidelines for creating visually appealing presentations +-------------------------------------------------------- While accuracy is the primary goal of visualizations, aesthetics also play an important role in engaging viewers and making data more memorable. @@ -310,8 +324,8 @@ visualization: sequential, diverging, and categorical. The section includes examples of data that can be visualized using each scale and popular color schemes used in practice. -A. Sequential Scales -^^^^^^^^^^^^^^^^^^^^ +Sequential Scales +^^^^^^^^^^^^^^^^^ Sequential scales are best suited for data that has an inherent ordering, such as data that varies from low to high, or over time. Examples of @@ -348,20 +362,65 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("blues", seqs_schemes, cvd=False, continuous=True) + plot_scheme("blues", seqs_schemes, cvd=False, continuous=True) + + * - .. code-block:: none + + tealblues + + - .. altair-plot:: + :remove-code: + + plot_scheme("tealblues", seqs_schemes, cvd=False, continuous=True) + + * - .. code-block:: none + + teals + + - .. altair-plot:: + :remove-code: + + plot_scheme("teals", seqs_schemes, cvd=False, continuous=True) + + * - .. code-block:: none + + greens + + - .. altair-plot:: + :remove-code: + + plot_scheme("greens", seqs_schemes, cvd=False, continuous=True) + + * - .. code-block:: none + + browns + + - .. altair-plot:: + :remove-code: + + plot_scheme("browns", seqs_schemes, cvd=False, continuous=True) + + * - .. code-block:: none + + greys + + - .. altair-plot:: + :remove-code: + + plot_scheme("greys", seqs_schemes, cvd=False, continuous=True) * - .. code-block:: none - warmgreys + purples - .. altair-plot:: :remove-code: - plot_scheme("warmgreys", seqs_schemes, cvd=False, continuous=False) + plot_scheme("purples", seqs_schemes, cvd=False, continuous=True) -B. Diverging Scales -^^^^^^^^^^^^^^^^^^^ +Diverging Scales +^^^^^^^^^^^^^^^^ 1. Examples of diverging data ----------------------------- From 6234bee6e65ded49629fc68ec072a2b0f04ae9e4 Mon Sep 17 00:00:00 2001 From: mattijn Date: Sat, 15 Apr 2023 17:07:37 +0200 Subject: [PATCH 05/17] add colorscales --- doc/user_guide/scale_color.rst | 460 ++++++++++++++++++++++++++++++--- 1 file changed, 429 insertions(+), 31 deletions(-) diff --git a/doc/user_guide/scale_color.rst b/doc/user_guide/scale_color.rst index 526d6a407..968195f9f 100644 --- a/doc/user_guide/scale_color.rst +++ b/doc/user_guide/scale_color.rst @@ -362,7 +362,13 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("blues", seqs_schemes, cvd=False, continuous=True) + plot_scheme("blues", seqs_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("blues", seqs_schemes, cvd=False, continuous=False) * - .. code-block:: none @@ -371,7 +377,13 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("tealblues", seqs_schemes, cvd=False, continuous=True) + plot_scheme("tealblues", seqs_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("tealblues", seqs_schemes, cvd=False, continuous=False) * - .. code-block:: none @@ -380,7 +392,13 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("teals", seqs_schemes, cvd=False, continuous=True) + plot_scheme("teals", seqs_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("teals", seqs_schemes, cvd=False, continuous=False) * - .. code-block:: none @@ -389,7 +407,13 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("greens", seqs_schemes, cvd=False, continuous=True) + plot_scheme("greens", seqs_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("greens", seqs_schemes, cvd=False, continuous=False) * - .. code-block:: none @@ -398,7 +422,13 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("browns", seqs_schemes, cvd=False, continuous=True) + plot_scheme("browns", seqs_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("browns", seqs_schemes, cvd=False, continuous=False) * - .. code-block:: none @@ -407,7 +437,13 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("greys", seqs_schemes, cvd=False, continuous=True) + plot_scheme("greys", seqs_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("greys", seqs_schemes, cvd=False, continuous=False) * - .. code-block:: none @@ -416,70 +452,432 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("purples", seqs_schemes, cvd=False, continuous=True) + plot_scheme("purples", seqs_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("purples", seqs_schemes, cvd=False, continuous=False) + * - .. code-block:: none + + warmgreys + + - .. altair-plot:: + :remove-code: + + plot_scheme("warmgreys", seqs_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("warmgreys", seqs_schemes, cvd=False, continuous=False) + + * - .. code-block:: none + + reds + + - .. altair-plot:: + :remove-code: + + plot_scheme("reds", seqs_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("reds", seqs_schemes, cvd=False, continuous=False) + + * - .. code-block:: none + + oranges + + - .. altair-plot:: + :remove-code: + + plot_scheme("oranges", seqs_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("oranges", seqs_schemes, cvd=False, continuous=False) Diverging Scales ^^^^^^^^^^^^^^^^ -1. Examples of diverging data ------------------------------ +Examples of diverging data +-------------------------- Diverging scales are best suited for data that varies above and below a center point, such as positive and negative deviations from a mean. Examples of diverging data include measures of deviation, such as temperature anomalies or changes in sea level. -1. Example color schemes for diverging data -------------------------------------------- +.. altair-plot:: + :hide-code: + :output: none -.. code-block:: none + divg_schemes = { + "blueorange": 9, + "brownbluegreen": 9, + "purplegreen": 9, + "pinkyellowgreen": 9, + "purpleorange": 9, + "redblue": 9, + "redgrey": 9, + "redyellowblue": 9, + "redyellowgreen": 9, + "spectral": 9, + } + +.. list-table:: + :widths: 25 75 + :header-rows: 1 + + * - Scheme + - Example + * - .. code-block:: none - Diverging color schemes. + blueorange + - .. altair-plot:: + :remove-code: -C. Categorical Scales -^^^^^^^^^^^^^^^^^^^^^ + plot_scheme("blueorange", divg_schemes, cvd=True, continuous=True) -1. Examples of categorical data -------------------------------- + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("blueorange", divg_schemes, cvd=False, continuous=False) + + * - .. code-block:: none + + brownbluegreen + + - .. altair-plot:: + :remove-code: + + plot_scheme("brownbluegreen", divg_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("brownbluegreen", divg_schemes, cvd=False, continuous=False) + + * - .. code-block:: none + + purplegreen + + - .. altair-plot:: + :remove-code: + + plot_scheme("purplegreen", divg_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("purplegreen", divg_schemes, cvd=False, continuous=False) + + * - .. code-block:: none + + pinkyellowgreen + + - .. altair-plot:: + :remove-code: + + plot_scheme("pinkyellowgreen", divg_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("pinkyellowgreen", divg_schemes, cvd=False, continuous=False) + + * - .. code-block:: none + + purpleorange + + - .. altair-plot:: + :remove-code: + + plot_scheme("purpleorange", divg_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("purpleorange", divg_schemes, cvd=False, continuous=False) + + * - .. code-block:: none + + redblue + + - .. altair-plot:: + :remove-code: + + plot_scheme("redblue", divg_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("redblue", divg_schemes, cvd=False, continuous=False) + + * - .. code-block:: none + + redgrey + + - .. altair-plot:: + :remove-code: + + plot_scheme("redgrey", divg_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("redgrey", divg_schemes, cvd=False, continuous=False) + + * - .. code-block:: none + + redyellowblue + + - .. altair-plot:: + :remove-code: + + plot_scheme("redyellowblue", divg_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("redyellowblue", divg_schemes, cvd=False, continuous=False) + + * - .. code-block:: none + + redyellowgreen + + - .. altair-plot:: + :remove-code: + + plot_scheme("redyellowgreen", divg_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("redyellowgreen", divg_schemes, cvd=False, continuous=False) + + * - .. code-block:: none + + spectral + + - .. altair-plot:: + :remove-code: + + plot_scheme("spectral", divg_schemes, cvd=True, continuous=True) + + * - + - .. altair-plot:: + :remove-code: + + plot_scheme("spectral", divg_schemes, cvd=False, continuous=False) + +Categorical Scales +^^^^^^^^^^^^^^^^^^ + +Examples of categorical data +---------------------------- Categorical scales are best suited for data that is unordered, such as different species or categories. Examples of categorical data include the types of flowers in a garden, or different political affiliations. -1. Example color schemes for categorical data ---------------------------------------------- -.. code-block:: none +.. altair-plot:: + :hide-code: + :output: none + + catg_schemes = { + "accent": 8, + "category10": 10, + "category20": 20, + "category20b": 20, + "category20c": 20, + "dark2": 8, + "paired": 12, + "pastel1": 9, + "pastel2": 8, + "set1": 9, + "set2": 8, + "set3": 12, + "tableau10": 10, + "tableau20": 20, + } + +.. list-table:: + :widths: 25 75 + :header-rows: 1 + + * - Scheme + - Example + * - .. code-block:: none + + accent + + - .. altair-plot:: + :remove-code: + + plot_scheme("accent", catg_schemes, cvd=True, continuous=False) + + * - .. code-block:: none - Categorical color schemes. + category10 + + - .. altair-plot:: + :remove-code: + + plot_scheme("category10", catg_schemes, cvd=True, continuous=False) + + * - .. code-block:: none + + category20 + + - .. altair-plot:: + :remove-code: + + plot_scheme("category20", catg_schemes, cvd=True, continuous=False) + + * - .. code-block:: none + + category20b + + - .. altair-plot:: + :remove-code: + + plot_scheme("category20b", catg_schemes, cvd=True, continuous=False) + + * - .. code-block:: none + + category20c + + - .. altair-plot:: + :remove-code: + + plot_scheme("category20c", catg_schemes, cvd=True, continuous=False) + + * - .. code-block:: none + + dark2 + + - .. altair-plot:: + :remove-code: + + plot_scheme("dark2", catg_schemes, cvd=True, continuous=False) + + * - .. code-block:: none + + paired + + - .. altair-plot:: + :remove-code: + + plot_scheme("paired", catg_schemes, cvd=True, continuous=False) + + * - .. code-block:: none + + pastel1 + + - .. altair-plot:: + :remove-code: + + plot_scheme("pastel1", catg_schemes, cvd=True, continuous=False) + + * - .. code-block:: none + + pastel2 + + - .. altair-plot:: + :remove-code: + + plot_scheme("pastel2", catg_schemes, cvd=True, continuous=False) + + * - .. code-block:: none + + set1 + + - .. altair-plot:: + :remove-code: + + plot_scheme("set1", catg_schemes, cvd=True, continuous=False) + + * - .. code-block:: none + + set2 + + - .. altair-plot:: + :remove-code: + + plot_scheme("set2", catg_schemes, cvd=True, continuous=False) + + * - .. code-block:: none + + set3 + + - .. altair-plot:: + :remove-code: + + plot_scheme("set3", catg_schemes, cvd=True, continuous=False) + + * - .. code-block:: none + + tableau10 + + - .. altair-plot:: + :remove-code: + + plot_scheme("tableau10", catg_schemes, cvd=True, continuous=False) + + * - .. code-block:: none + + tableau20 + + - .. altair-plot:: + :remove-code: + + plot_scheme("tableau20", catg_schemes, cvd=True, continuous=False) Using Tools for Color Selection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A. Pre-Designed Color Palettes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Pre-Designed Color Palettes +^^^^^^^^^^^^^^^^^^^^^^^^^^^ -1. Example color palettes from the other packages -------------------------------------------------- +Example color palettes from the other packages +---------------------------------------------- This subsection focuses on pre-designed color palettes that can be used in data visualization from other sources. -B. Modifying Color Scales -^^^^^^^^^^^^^^^^^^^^^^^^^ +Modifying Color Scales +^^^^^^^^^^^^^^^^^^^^^^ -1. Tools for adjusting brightness, saturation, and hue ------------------------------------------------------- +Tools for adjusting brightness, saturation, and hue +--------------------------------------------------- This subsection provides an overview of the different ways in which brightness, saturation, and hue can be modified to create custom color scales. It explains how changing these attributes can help to emphasize or de-emphasize certain aspects of the data being visualized. -2. Customizing color scales to match specific data requirements ---------------------------------------------------------------- +Customizing color scales to match specific data requirements +------------------------------------------------------------ This subsection explores the idea of creating custom color scales that are tailored to the specific needs of the data being visualized. It From 82cc0780bafb40338132226dcb6b4ac961015fcc Mon Sep 17 00:00:00 2001 From: mattijn Date: Sat, 15 Apr 2023 19:47:09 +0200 Subject: [PATCH 06/17] add intuitive colors --- doc/user_guide/scale_color.rst | 209 +++++++++++++++++++++++++++++++-- 1 file changed, 202 insertions(+), 7 deletions(-) diff --git a/doc/user_guide/scale_color.rst b/doc/user_guide/scale_color.rst index 968195f9f..55eee773c 100644 --- a/doc/user_guide/scale_color.rst +++ b/doc/user_guide/scale_color.rst @@ -287,7 +287,7 @@ also inclusive. :header-rows: 1 * - Example - * - Friendly diverging color scheme (`sunset` by Paul Ton, see section xx for more) + * - **Friendly** diverging color scheme (`sunset` by Paul Ton, see section xx for more) .. altair-plot:: :remove-code: @@ -308,12 +308,65 @@ While accuracy is the primary goal of visualizations, aesthetics also play an important role in engaging viewers and making data more memorable. By using a limited color palette, choosing harmonious colors, and balancing colors to create a sense of visual hierarchy, you can create visualizations -that are not only informative but also visually stunning. +that are not only informative but also visually attractive. -.. code-block:: none +.. list-table:: + :widths: 100 + :header-rows: 0 + + * - Intuitive colors + + .. altair-plot:: + :hide-code: + + source = pd.DataFrame({ + 'land cover': ['Land', 'Water'], + 'value': [28, 55] + }) + + intuitive = alt.Chart(source, height=alt.Step(80), width=200, title='intuitive').mark_bar().encode( + x=alt.X('value').axis(None), + y=alt.Y('land cover').title(None), + color=alt.Color('land cover').scale(range=['#55AA22', '#5566AA'], domain=['Land', 'Water']) + ) + + non_intuitive = alt.Chart(source, height=alt.Step(80), width=200, title='non-intuitive').mark_bar().encode( + x=alt.X('value').axis(None), + y=alt.Y('land cover').title(None), + color=alt.Color('land cover').scale(range=['#5566AA', '#55AA22'], domain=['Land', 'Water']) + ) + + (intuitive | non_intuitive).configure_view(stroke=None).resolve_scale(color='independent') + + * - Colors in moderation + + .. code-block:: none + + example simple dataset/singe color + + * - Consistency in colors + + .. code-block:: none - A visualization that uses color to create a sense of visual hierarchy, - such as a heatmap or a scatterplot. + example compound + + * - Colors for clarity + + .. code-block:: none + + example distinguish + + * - Colors to classify + + .. code-block:: none + + example gradient for measurement vice versa + + * - Explain colors + + .. code-block:: none + + example use color key Types of Color Scales @@ -699,7 +752,6 @@ Categorical scales are best suited for data that is unordered, such as different species or categories. Examples of categorical data include the types of flowers in a garden, or different political affiliations. - .. altair-plot:: :hide-code: :output: none @@ -865,6 +917,149 @@ Example color palettes from the other packages This subsection focuses on pre-designed color palettes that can be used in data visualization from other sources. +.. altair-plot:: + :output: none + + tol_schemes = { + "tol_bright" : ['#4477AA', '#EE6677', '#228833', '#CCBB44', '#66CCEE', '#AA3377', '#BBBBBB'], + "tol_highcontrast" : ['#FFFFFF', '#DDAA33', '#BB5566', '#004488', '#000000'], + "tol_vibrant": ['#0077BB', '#33BBEE', '#009988', '#EE7733', '#CC3311', '#EE3377', '#BBBBBB'], + "tol_muted": ['#332288', '#88CCEE', '#44AA99', '#117733', '#999933', '#DDCC77' ,'#CC6677' , '#882255', '#AA4499', '#DDDDDD'], + "tol_mediumcontrast": ['#FFFFFF', '#EECC66', '#994455','#6699CC', '#997700', '#994455', '#004488', '#000000'], + "tol_pale": ['#BBCCEE', '#CCEEFF', '#CCDDAA', '#EEEEBB', '#FFCCCC', '#DDDDDD'], + "tol_dark": ['#222255', '#225555', '#225522', '#666633', '#663333', '#555555'], + "tol_light": ['#77AADD', '#99DDFF', '#44BB99', '#BBCC33', '#AAAA00', '#EEDD88', '#EE8866', '#FFAABB', '#DDDDDD'], + "tol_sunset": ['#364B9A', '#4A7BB7', '#6EA6CD', '#98CAE1', '#C2E4EF', '#EAECCC', '#FEDA8B', '#FDB366', '#F67E4B', '#DD3D2D', '#A50026'], + "tol_nightfall": ['#125A56', '#00767B', '#238F9D', '#42A7C6', '#60BCE9', '#9DCCEF', '#C6DBED', '#DEE6E7', '#ECEADA', '#F0E6B2', '#F9D576', '#FFB954', '#FD9A44', '#F57634', '#E94C1F', '#D11807', '#A01813'], + "tol_PRGn": ['#762A83', '#9970AB', '#C2A5CF', '#E7D4E8', '#F7F7F7', '#D9F0D3', '#ACD39E', '#5AAE61', '#1B7837'], + "tol_YlOrBr": ['#FFFFE5', '#FFF7BC', '#FEE391', '#FEC44F', '#FB9A29', '#EC7014', '#CC4C02', '#993404', '#662506'], + "tol_iridescent": ['#FEFBE9', '#FCF7D5', '#F5F3C1', '#EAF0B5', '#DDECBF', '#D0E7CA', '#C2E3D2', '#B5DDD8', '#A8D8DC', '#9BD2E1', '#8DCBE4', '#81C4E7', '#7BBCE7', '#7EB2E4', '#88A5DD', '#9398D2', '#9B8AC4', '#9D7DB2', '#9A709E', '#906388', '#805770', '#684957', '#46353A'], + "tol_incandescent": [ '#CEFFFF', '#C6F7D6', '#A2F49B', '#BBE453', '#D5CE04', '#E7B503', '#F19903', '#F6790B', '#F94902', '#E40515', '#A80003'] + } + +.. list-table:: + :widths: 25 75 + :header-rows: 1 + + * - Scheme + - Example + * - .. code-block:: none + + tol_bright + + - .. altair-plot:: + :remove-code: + + plot_scheme("tol_bright", tol_schemes, cvd=True, continuous=False, grayscale=True) + + * - .. code-block:: none + + tol_highcontrast + + - .. altair-plot:: + :remove-code: + + plot_scheme("tol_highcontrast", tol_schemes, cvd=True, continuous=False, grayscale=True) + + * - .. code-block:: none + + tol_vibrant + + - .. altair-plot:: + :remove-code: + + plot_scheme("tol_vibrant", tol_schemes, cvd=True, continuous=False, grayscale=True) + + * - .. code-block:: none + + tol_muted + + - .. altair-plot:: + :remove-code: + + plot_scheme("tol_muted", tol_schemes, cvd=True, continuous=False, grayscale=True) + + * - .. code-block:: none + + tol_mediumcontrast + + - .. altair-plot:: + :remove-code: + + plot_scheme("tol_mediumcontrast", tol_schemes, cvd=True, continuous=False, grayscale=True) + + * - .. code-block:: none + + tol_pale + + - .. altair-plot:: + :remove-code: + + plot_scheme("tol_pale", tol_schemes, cvd=True, continuous=False, grayscale=True) + + * - .. code-block:: none + + tol_dark + + - .. altair-plot:: + :remove-code: + + plot_scheme("tol_dark", tol_schemes, cvd=True, continuous=False, grayscale=True) + + * - .. code-block:: none + + tol_light + + - .. altair-plot:: + :remove-code: + + plot_scheme("tol_light", tol_schemes, cvd=True, continuous=False, grayscale=True) + + * - .. code-block:: none + + tol_light + + - .. altair-plot:: + :remove-code: + + plot_scheme("tol_light", tol_schemes, cvd=True, continuous=False, grayscale=True) + + * - .. code-block:: none + + tol_sunset + + - .. altair-plot:: + :remove-code: + + plot_scheme("tol_sunset", tol_schemes, cvd=True, continuous=True, grayscale=True) + + * - .. code-block:: none + + tol_nightfall + + - .. altair-plot:: + :remove-code: + + plot_scheme("tol_nightfall", tol_schemes, cvd=True, continuous=True, grayscale=True) + + * - .. code-block:: none + + tol_iridescent + + - .. altair-plot:: + :remove-code: + + plot_scheme("tol_iridescent", tol_schemes, cvd=True, continuous=True, grayscale=True) + + * - .. code-block:: none + + tol_incandescent + + - .. altair-plot:: + :remove-code: + + plot_scheme("tol_incandescent", tol_schemes, cvd=True, continuous=True, grayscale=True) + Modifying Color Scales ^^^^^^^^^^^^^^^^^^^^^^ @@ -881,7 +1076,7 @@ Customizing color scales to match specific data requirements This subsection explores the idea of creating custom color scales that are tailored to the specific needs of the data being visualized. It -discusses how to use the tools introduced in III.B.1 to create color +discusses how to use the tools introduced in xx to create color scales that are better suited to representing the nuances of the data, and provides examples of how this can be achieved in practice. From e62d6ad49d0917670f99f08223fa9270d397aea3 Mon Sep 17 00:00:00 2001 From: mattijn Date: Thu, 20 Apr 2023 22:40:08 +0200 Subject: [PATCH 07/17] add moderate/excess example --- doc/user_guide/scale_color.rst | 37 ++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/doc/user_guide/scale_color.rst b/doc/user_guide/scale_color.rst index 55eee773c..33d109ffe 100644 --- a/doc/user_guide/scale_color.rst +++ b/doc/user_guide/scale_color.rst @@ -20,6 +20,7 @@ examples of their use. :output: none import altair as alt + from vega_datasets import data import pandas as pd def chart_settings(scheme_name, dict_schemes, continuous): @@ -340,9 +341,41 @@ that are not only informative but also visually attractive. * - Colors in moderation - .. code-block:: none + .. altair-plot:: + :hide-code: - example simple dataset/singe color + import altair as alt + from vega_datasets import data + + source = data.barley.url + + labels = ["Crookston", "Grand Rapids"] + + cond_weight = alt.condition( + alt.FieldOneOfPredicate(field='value', oneOf=labels), + alt.value('bolder'), # predicate True + alt.value('normal') # predicate False + ) + + cond_color = alt.condition( + alt.FieldOneOfPredicate(field='site', oneOf=labels), + alt.value('#6A8AD5'), # predicate True + alt.value('#CCCCCC') # predicate False + ) + + moderate = alt.Chart(source, width=200).mark_bar().encode( + x='sum(yield):Q', + y=alt.Y('site:N').sort('-x').axis(labelFontWeight=cond_weight), + color=cond_color + ) + + excess = alt.Chart(source, width=200).mark_bar().encode( + x='sum(yield):Q', + y=alt.Y('site:N').sort('-x'), + color=alt.Color('site:N').scale(scheme='set1').legend(None) + ) + + (moderate | excess).resolve_scale(color='independent') * - Consistency in colors From ae266a76c98472f0306b61e6e680c1c09403d185 Mon Sep 17 00:00:00 2001 From: mattijn Date: Fri, 21 Apr 2023 00:55:29 +0200 Subject: [PATCH 08/17] add more examples --- doc/user_guide/scale_color.rst | 270 ++++++++++++++++++++++++++++----- 1 file changed, 235 insertions(+), 35 deletions(-) diff --git a/doc/user_guide/scale_color.rst b/doc/user_guide/scale_color.rst index 33d109ffe..acc9adf7e 100644 --- a/doc/user_guide/scale_color.rst +++ b/doc/user_guide/scale_color.rst @@ -312,12 +312,13 @@ colors to create a sense of visual hierarchy, you can create visualizations that are not only informative but also visually attractive. .. list-table:: - :widths: 100 - :header-rows: 0 + :widths: 50 50 + :header-rows: 1 * - Intuitive colors + - Non-intuitive colors - .. altair-plot:: + * - .. altair-plot:: :hide-code: source = pd.DataFrame({ @@ -331,18 +332,29 @@ that are not only informative but also visually attractive. color=alt.Color('land cover').scale(range=['#55AA22', '#5566AA'], domain=['Land', 'Water']) ) + intuitive.configure_view(stroke=None) + + - .. altair-plot:: + :hide-code: + non_intuitive = alt.Chart(source, height=alt.Step(80), width=200, title='non-intuitive').mark_bar().encode( x=alt.X('value').axis(None), y=alt.Y('land cover').title(None), color=alt.Color('land cover').scale(range=['#5566AA', '#55AA22'], domain=['Land', 'Water']) ) - (intuitive | non_intuitive).configure_view(stroke=None).resolve_scale(color='independent') + non_intuitive.configure_view(stroke=None) + + +.. list-table:: + :widths: 50 50 + :header-rows: 1 * - Colors in moderation + - Colors in excess - .. altair-plot:: - :hide-code: + * - .. altair-plot:: + :hide-code: import altair as alt from vega_datasets import data @@ -352,54 +364,242 @@ that are not only informative but also visually attractive. labels = ["Crookston", "Grand Rapids"] cond_weight = alt.condition( - alt.FieldOneOfPredicate(field='value', oneOf=labels), - alt.value('bolder'), # predicate True - alt.value('normal') # predicate False + alt.FieldOneOfPredicate( + field="value", oneOf=labels + ), + alt.value("bolder"), # predicate True + alt.value("normal"), # predicate False ) cond_color = alt.condition( - alt.FieldOneOfPredicate(field='site', oneOf=labels), - alt.value('#6A8AD5'), # predicate True - alt.value('#CCCCCC') # predicate False - ) + alt.FieldOneOfPredicate( + field="site", oneOf=labels + ), + alt.value("#6A8AD5"), # predicate True + alt.value("#CCCCCC"), # predicate False + ) - moderate = alt.Chart(source, width=200).mark_bar().encode( - x='sum(yield):Q', - y=alt.Y('site:N').sort('-x').axis(labelFontWeight=cond_weight), - color=cond_color + moderate = ( + alt.Chart(source, width=200) + .mark_bar() + .encode( + x="sum(yield):Q", + y=alt.Y("site:N") + .sort("-x") + .axis(labelFontWeight=cond_weight), + color=cond_color, + ) ) - excess = alt.Chart(source, width=200).mark_bar().encode( - x='sum(yield):Q', - y=alt.Y('site:N').sort('-x'), - color=alt.Color('site:N').scale(scheme='set1').legend(None) + moderate + + - .. altair-plot:: + :hide-code: + + excess = ( + alt.Chart(source, width=200) + .mark_bar() + .encode( + x="sum(yield):Q", + y=alt.Y("site:N").sort("-x"), + color=alt.Color("site:N") + .scale(scheme="set1") + .legend(None), + ) ) - (moderate | excess).resolve_scale(color='independent') + excess - * - Consistency in colors - .. code-block:: none - - example compound +.. list-table:: + :widths: 50 50 + :header-rows: 1 - * - Colors for clarity + * - Consistency color usage between plots + - In-consistent color usage between plots - .. code-block:: none + * - .. altair-plot:: + :hide-code: - example distinguish + import altair as alt + from vega_datasets import data + + source = data.movies() + + line = alt.Chart(source).mark_line().encode( + alt.X("IMDB_Rating").bin(True), + alt.Y(alt.repeat("layer")) + .aggregate("mean") + .title("Mean of US and Worldwide Gross"), + color=alt.datum(alt.repeat("layer")) + ).repeat( + layer=["US_Gross", "Worldwide_Gross"] + ) + + bar = alt.Chart(source).mark_bar().transform_fold( + fold = ["US_Gross", "Worldwide_Gross"] + ).encode( + alt.X('value:Q').aggregate("sum").title(None), + alt.Y('key:N').sort('-x'), + alt.Color('key:N', sort=['Worldwide_Gross']) + .scale(range=['#6A8AD5', '#CCCCCC']) + ) - * - Colors to classify + equal = (line & bar).resolve_scale(color="shared") + equal - .. code-block:: none + + - .. altair-plot:: + :hide-code: + + inequal = (line & bar).resolve_scale(color='independent') + inequal + + +.. list-table:: + :widths: 50 50 + :header-rows: 1 + + * - Clarity in colors + - Indistinct color usage + + * - .. altair-plot:: + :hide-code: - example gradient for measurement vice versa + import altair as alt + from vega_datasets import data - * - Explain colors + source = data.stocks() - .. code-block:: none + alt.Chart( + source, width=200, height=200 + ).mark_line().encode( + x="date:T", + y="price:Q", + color=alt.Color("symbol:N").scale( + scheme="reds" + ), + ) + + + - .. altair-plot:: + :hide-code: + + source = data.stocks() + + col_range = [ + "#E69F00", + "#57B4E9", + "#019E73", + "#F0E442", + "#0072B2", + ] + col_sort = [ + "GOOG", + "AAPL", + "IBM", + "AMZN", + "MSFT", + ] + + alt.Chart( + source, width=200, height=200 + ).mark_line().encode( + x="date:T", + y=alt.Y("price:Q"), + color=alt.Color( + "symbol:N", sort=col_sort + ).scale(range=col_range), + ) + +.. list-table:: + :widths: 50 50 + :header-rows: 1 + + * - Distinct classification of categories + - No gradient colors for categories + + * - .. altair-plot:: + :hide-code: - example use color key + import altair as alt + from vega_datasets import data + + source = data.stocks() + col_range = [ + "#E69F00", + "#57B4E9", + "#019E73", + "#F0E442", + "#0072B2", + ] + col_sort = [ + "GOOG", + "AAPL", + "IBM", + "AMZN", + "MSFT", + ] + + alt.Chart( + source, width=200, height=200 + ).mark_line().encode( + x=alt.X('symbol:N', sort='-y'), + y=alt.Y('price:Q').aggregate('sum'), + color=alt.Color( + "symbol:N", sort=col_sort + ).scale(range=col_range), + ) + + - .. altair-plot:: + :hide-code: + + source = data.stocks() + + alt.Chart( + source, width=200, height=200 + ).mark_bar().encode( + x=alt.X('symbol:N', sort=col_sort), + y=alt.Y('price:Q').aggregate('sum'), + color=alt.Color("symbol:N").scale( + scheme="reds" + ), + ) + +.. list-table:: + :widths: 50 50 + :header-rows: 1 + + * - Explain colors using a colorbar + - No understanding of used colors + + * - .. altair-plot:: + :hide-code: + + import altair as alt + from vega_datasets import data + + source = data.iowa_electricity() + + alt.Chart(source, width=200, height=200).mark_area().encode( + x="year:T", + y="net_generation:Q", + color=alt.Color("source:N").legend(None) + ) + + - .. altair-plot:: + :hide-code: + + import altair as alt + from vega_datasets import data + + source = data.iowa_electricity() + + alt.Chart(source, width=200, height=200).mark_area().encode( + x="year:T", + y="net_generation:Q", + color=alt.Color("source:N") + ) Types of Color Scales From 77f5a152de6639d45c1b56f2790086bb638e14db Mon Sep 17 00:00:00 2001 From: mattijn Date: Fri, 21 Apr 2023 01:09:17 +0200 Subject: [PATCH 09/17] swap chart colorbar --- doc/user_guide/scale_color.rst | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/doc/user_guide/scale_color.rst b/doc/user_guide/scale_color.rst index acc9adf7e..84f3bf7f5 100644 --- a/doc/user_guide/scale_color.rst +++ b/doc/user_guide/scale_color.rst @@ -581,10 +581,15 @@ that are not only informative but also visually attractive. source = data.iowa_electricity() - alt.Chart(source, width=200, height=200).mark_area().encode( + alt.Chart( + source, width=200, height=200 + ).mark_area().encode( x="year:T", y="net_generation:Q", - color=alt.Color("source:N").legend(None) + color=alt.Color("source:N").legend( + orient="top", + direction='horizontal' + ) ) - .. altair-plot:: @@ -595,10 +600,14 @@ that are not only informative but also visually attractive. source = data.iowa_electricity() - alt.Chart(source, width=200, height=200).mark_area().encode( + alt.Chart( + source, width=200, height=200 + ).mark_area().encode( x="year:T", y="net_generation:Q", - color=alt.Color("source:N") + color=alt.Color("source:N").legend( + None + ) ) From b74438ee57c6dff927d82768649e33036ec00420 Mon Sep 17 00:00:00 2001 From: mattijn Date: Fri, 21 Apr 2023 17:44:34 +0200 Subject: [PATCH 10/17] resolve color scale for vconcat --- doc/user_guide/scale_color.rst | 282 ++++++++++++++++----------------- 1 file changed, 139 insertions(+), 143 deletions(-) diff --git a/doc/user_guide/scale_color.rst b/doc/user_guide/scale_color.rst index 84f3bf7f5..b051db7a5 100644 --- a/doc/user_guide/scale_color.rst +++ b/doc/user_guide/scale_color.rst @@ -59,9 +59,10 @@ examples of their use. "custom": custom_scheme } - def chart_color_scale(scheme_name, dict_schemes, continuous, custom, no): + def chart_color_scale(scheme_name, dict_schemes, continuous, custom, no, method): color_type = "quantitative" if custom and continuous else "ordinal" scale_scheme = alt.Scale(range=dict_schemes[scheme_name]) if custom else alt.Scale(scheme=scheme_name) + scale_scheme.interpolate = method color_scale = ( alt.Chart(alt.sequence(0, no, as_="i")) @@ -156,7 +157,7 @@ examples of their use. ) return grayscale - def plot_scheme(scheme_name, dict_schemes, continuous=False, cvd=False, grayscale=False): + def plot_scheme(scheme_name, dict_schemes, continuous=False, cvd=False, grayscale=False, top=False, bottom=False, single=True, method='rgb'): # determine chart settings chart_dict = chart_settings(scheme_name, dict_schemes, continuous) @@ -171,10 +172,15 @@ examples of their use. no = chart_dict['no_colors'] # define color scale - color_scale = chart_color_scale(scheme_name, dict_schemes, continuous, custom, no) + color_scale = chart_color_scale(scheme_name, dict_schemes, continuous, custom, no, method) # define how hex-codes can be derived, from datasource or using color scale - expr_hex = "datum.hex" if custom and not continuous else "scale('color', datum.i)" + if single: + expr_hex = "datum.hex" if custom and not continuous else "scale('color', datum.i)" + elif top: + expr_hex = "datum.hex" if custom and not continuous else "scale('concat_0_color', datum.i)" + elif bottom: + expr_hex = "datum.hex" if custom and not continuous else "scale('concat_1_color', datum.i)" # define styling for y-axis my_y_axis = alt.Axis( @@ -202,8 +208,9 @@ examples of their use. chart_concat = (normal_vision & color_scale) # set configuration on concatenated chart object - chart_comb = chart_concat.configure_concat(spacing=1).configure_view(stroke=None) - return chart_comb + if single: + chart_concat = chart_concat.configure_concat(spacing=1).configure_view(stroke=None) + return chart_concat The Basics of Color Selection @@ -421,41 +428,70 @@ that are not only informative but also visually attractive. * - .. altair-plot:: :hide-code: - import altair as alt - from vega_datasets import data - source = data.movies() - - line = alt.Chart(source).mark_line().encode( - alt.X("IMDB_Rating").bin(True), - alt.Y(alt.repeat("layer")) - .aggregate("mean") - .title("Mean of US and Worldwide Gross"), - color=alt.datum(alt.repeat("layer")) - ).repeat( - layer=["US_Gross", "Worldwide_Gross"] + + line = ( + alt.Chart(source, width=200, height=200) + .transform_fold( + fold=["US_Gross", "Worldwide_Gross"] + ) + .mark_line() + .encode( + x=alt.X("IMDB_Rating:Q").bin(True), + y=alt.Y("value:Q") + .aggregate("mean") + .sort("-x"), + color=alt.Color( + "key:N", + sort=["Worldwide_Gross"], + ).legend( + orient="top", + direction="horizontal", + ), + ) ) - - bar = alt.Chart(source).mark_bar().transform_fold( - fold = ["US_Gross", "Worldwide_Gross"] - ).encode( - alt.X('value:Q').aggregate("sum").title(None), - alt.Y('key:N').sort('-x'), - alt.Color('key:N', sort=['Worldwide_Gross']) - .scale(range=['#6A8AD5', '#CCCCCC']) + + bar = ( + alt.Chart(source, width=200) + .transform_fold( + fold=["US_Gross", "Worldwide_Gross"] + ) + .mark_bar() + .encode( + x=alt.X("value:Q") + .aggregate("sum") + .title(None), + y=alt.Y("key:N").sort("-x"), + color=alt.Color( + "key:N", + sort=["Worldwide_Gross"], + ) + .scale(range=["#6A8AD5", "#CCCCCC"]) + .legend( + orient="top", + direction="horizontal", + ), + ) + ) + + equal = (line & bar).resolve_scale( + color="shared" ) - - equal = (line & bar).resolve_scale(color="shared") equal + + - .. altair-plot:: :hide-code: - inequal = (line & bar).resolve_scale(color='independent') + inequal = (line & bar).resolve_scale( + color="independent" + ) inequal + .. list-table:: :widths: 50 50 :header-rows: 1 @@ -466,25 +502,6 @@ that are not only informative but also visually attractive. * - .. altair-plot:: :hide-code: - import altair as alt - from vega_datasets import data - - source = data.stocks() - - alt.Chart( - source, width=200, height=200 - ).mark_line().encode( - x="date:T", - y="price:Q", - color=alt.Color("symbol:N").scale( - scheme="reds" - ), - ) - - - - .. altair-plot:: - :hide-code: - source = data.stocks() col_range = [ @@ -512,6 +529,24 @@ that are not only informative but also visually attractive. ).scale(range=col_range), ) + - .. altair-plot:: + :hide-code: + + import altair as alt + from vega_datasets import data + + source = data.stocks() + + alt.Chart( + source, width=200, height=200 + ).mark_line().encode( + x="date:T", + y="price:Q", + color=alt.Color("symbol:N").scale( + scheme="reds" + ), + ) + .. list-table:: :widths: 50 50 :header-rows: 1 @@ -521,7 +556,7 @@ that are not only informative but also visually attractive. * - .. altair-plot:: :hide-code: - + import altair as alt from vega_datasets import data @@ -543,7 +578,7 @@ that are not only informative but also visually attractive. alt.Chart( source, width=200, height=200 - ).mark_line().encode( + ).mark_bar().encode( x=alt.X('symbol:N', sort='-y'), y=alt.Y('price:Q').aggregate('sum'), color=alt.Color( @@ -562,7 +597,7 @@ that are not only informative but also visually attractive. x=alt.X('symbol:N', sort=col_sort), y=alt.Y('price:Q').aggregate('sum'), color=alt.Color("symbol:N").scale( - scheme="reds" + scheme="purples" ), ) @@ -587,8 +622,9 @@ that are not only informative but also visually attractive. x="year:T", y="net_generation:Q", color=alt.Color("source:N").legend( - orient="top", - direction='horizontal' + orient="bottom", + direction="horizontal", + columns=2 ) ) @@ -657,13 +693,10 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("blues", seqs_schemes, cvd=True, continuous=True) - - * - - - .. altair-plot:: - :remove-code: - - plot_scheme("blues", seqs_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("blues", seqs_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("blues", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -672,13 +705,10 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("tealblues", seqs_schemes, cvd=True, continuous=True) - - * - - - .. altair-plot:: - :remove-code: - - plot_scheme("tealblues", seqs_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("tealblues", seqs_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("tealblues", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -687,13 +717,10 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("teals", seqs_schemes, cvd=True, continuous=True) - - * - - - .. altair-plot:: - :remove-code: - - plot_scheme("teals", seqs_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("teals", seqs_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("teals", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -702,13 +729,10 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("greens", seqs_schemes, cvd=True, continuous=True) - - * - - - .. altair-plot:: - :remove-code: - - plot_scheme("greens", seqs_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("greens", seqs_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("greens", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -717,13 +741,10 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("browns", seqs_schemes, cvd=True, continuous=True) - - * - - - .. altair-plot:: - :remove-code: - - plot_scheme("browns", seqs_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("browns", seqs_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("browns", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -732,13 +753,10 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("greys", seqs_schemes, cvd=True, continuous=True) - - * - - - .. altair-plot:: - :remove-code: - - plot_scheme("greys", seqs_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("greys", seqs_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("greys", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -747,13 +765,10 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("purples", seqs_schemes, cvd=True, continuous=True) - - * - - - .. altair-plot:: - :remove-code: - - plot_scheme("purples", seqs_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("purples", seqs_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("purples", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -762,13 +777,10 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("warmgreys", seqs_schemes, cvd=True, continuous=True) - - * - - - .. altair-plot:: - :remove-code: - - plot_scheme("warmgreys", seqs_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("warmgreys", seqs_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("warmgreys", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -777,13 +789,10 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("reds", seqs_schemes, cvd=True, continuous=True) - - * - - - .. altair-plot:: - :remove-code: - - plot_scheme("reds", seqs_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("reds", seqs_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("reds", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -792,13 +801,10 @@ range of values is continuous and unbroken. - .. altair-plot:: :remove-code: - plot_scheme("oranges", seqs_schemes, cvd=True, continuous=True) - - * - - - .. altair-plot:: - :remove-code: - - plot_scheme("oranges", seqs_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("oranges", seqs_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("oranges", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') Diverging Scales ^^^^^^^^^^^^^^^^ @@ -843,8 +849,7 @@ temperature anomalies or changes in sea level. plot_scheme("blueorange", divg_schemes, cvd=True, continuous=True) - * - - - .. altair-plot:: + .. altair-plot:: :remove-code: plot_scheme("blueorange", divg_schemes, cvd=False, continuous=False) @@ -858,8 +863,7 @@ temperature anomalies or changes in sea level. plot_scheme("brownbluegreen", divg_schemes, cvd=True, continuous=True) - * - - - .. altair-plot:: + .. altair-plot:: :remove-code: plot_scheme("brownbluegreen", divg_schemes, cvd=False, continuous=False) @@ -873,8 +877,7 @@ temperature anomalies or changes in sea level. plot_scheme("purplegreen", divg_schemes, cvd=True, continuous=True) - * - - - .. altair-plot:: + .. altair-plot:: :remove-code: plot_scheme("purplegreen", divg_schemes, cvd=False, continuous=False) @@ -888,8 +891,7 @@ temperature anomalies or changes in sea level. plot_scheme("pinkyellowgreen", divg_schemes, cvd=True, continuous=True) - * - - - .. altair-plot:: + .. altair-plot:: :remove-code: plot_scheme("pinkyellowgreen", divg_schemes, cvd=False, continuous=False) @@ -903,8 +905,7 @@ temperature anomalies or changes in sea level. plot_scheme("purpleorange", divg_schemes, cvd=True, continuous=True) - * - - - .. altair-plot:: + .. altair-plot:: :remove-code: plot_scheme("purpleorange", divg_schemes, cvd=False, continuous=False) @@ -918,8 +919,7 @@ temperature anomalies or changes in sea level. plot_scheme("redblue", divg_schemes, cvd=True, continuous=True) - * - - - .. altair-plot:: + .. altair-plot:: :remove-code: plot_scheme("redblue", divg_schemes, cvd=False, continuous=False) @@ -933,8 +933,7 @@ temperature anomalies or changes in sea level. plot_scheme("redgrey", divg_schemes, cvd=True, continuous=True) - * - - - .. altair-plot:: + .. altair-plot:: :remove-code: plot_scheme("redgrey", divg_schemes, cvd=False, continuous=False) @@ -948,8 +947,7 @@ temperature anomalies or changes in sea level. plot_scheme("redyellowblue", divg_schemes, cvd=True, continuous=True) - * - - - .. altair-plot:: + .. altair-plot:: :remove-code: plot_scheme("redyellowblue", divg_schemes, cvd=False, continuous=False) @@ -963,8 +961,7 @@ temperature anomalies or changes in sea level. plot_scheme("redyellowgreen", divg_schemes, cvd=True, continuous=True) - * - - - .. altair-plot:: + .. altair-plot:: :remove-code: plot_scheme("redyellowgreen", divg_schemes, cvd=False, continuous=False) @@ -978,8 +975,7 @@ temperature anomalies or changes in sea level. plot_scheme("spectral", divg_schemes, cvd=True, continuous=True) - * - - - .. altair-plot:: + .. altair-plot:: :remove-code: plot_scheme("spectral", divg_schemes, cvd=False, continuous=False) From 818efb578187c9b57dc3c93504bc558a2debe68d Mon Sep 17 00:00:00 2001 From: mattijn Date: Fri, 21 Apr 2023 20:42:47 +0200 Subject: [PATCH 11/17] update diverging schemes --- doc/user_guide/scale_color.rst | 172 ++++++++++++++++++--------------- 1 file changed, 94 insertions(+), 78 deletions(-) diff --git a/doc/user_guide/scale_color.rst b/doc/user_guide/scale_color.rst index b051db7a5..b8f3af361 100644 --- a/doc/user_guide/scale_color.rst +++ b/doc/user_guide/scale_color.rst @@ -157,7 +157,7 @@ examples of their use. ) return grayscale - def plot_scheme(scheme_name, dict_schemes, continuous=False, cvd=False, grayscale=False, top=False, bottom=False, single=True, method='rgb'): + def plot_scheme(scheme_name, dict_schemes, continuous=False, cvd=False, grayscale=False, top=False, bottom=False, single=True, method='hcl'): # determine chart settings chart_dict = chart_settings(scheme_name, dict_schemes, continuous) @@ -268,11 +268,8 @@ can affect the perceived brightness and contrast of colors. By understanding the nuances of color spaces, you can create visualizations that are not only informative but also visually appealing. -Accessibility and Aesthetics -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Considerations for color-blind viewers --------------------------------------- +Color-blind viewers +^^^^^^^^^^^^^^^^^^^ Around 8% of men and 0.5% of women have some form of color vision deficiency. To make sure your visualizations are accessible to all viewers, it's @@ -286,8 +283,8 @@ also inclusive. :output: none color_vision_deficiency = { - 'tol_sunset_friendly': ['#364B9A', '#4A7BB7', '#6EA6CD', '#98CAE1', '#C2E4EF', '#EAECCC', '#FEDA8B', '#FDB366', '#F67E4B', '#DD3D2D', '#A50026'], - 'unfriendly' : ['#006400', '#228B22', '#00FF00', '#7FFF00', '#FFFF00', '#FFA500', '#FF0000'] + 'redyellowblue': 9, + 'redyellowgreen' : 9 } .. list-table:: @@ -300,17 +297,17 @@ also inclusive. .. altair-plot:: :remove-code: - plot_scheme("tol_sunset_friendly", color_vision_deficiency, cvd=True, continuous=True) + plot_scheme("redyellowblue", color_vision_deficiency, cvd=True, continuous=True) * - **Unfriendly** diverging color scale .. altair-plot:: :remove-code: - plot_scheme("unfriendly", color_vision_deficiency, cvd=True, continuous=True) + plot_scheme("redyellowgreen", color_vision_deficiency, cvd=True, continuous=True) -Guidelines for creating visually appealing presentations --------------------------------------------------------- +Guidelines for using colors +^^^^^^^^^^^^^^^^^^^^^^^^^^^ While accuracy is the primary goal of visualizations, aesthetics also play an important role in engaging viewers and making data more memorable. @@ -655,10 +652,10 @@ visualization: sequential, diverging, and categorical. The section includes examples of data that can be visualized using each scale and popular color schemes used in practice. -Sequential Scales -^^^^^^^^^^^^^^^^^ +Sequential Schemes +^^^^^^^^^^^^^^^^^^ -Sequential scales are best suited for data that has an inherent ordering, +Sequential schemes are best suited for data that has an inherent ordering, such as data that varies from low to high, or over time. Examples of sequential data include temperature or population density, where the range of values is continuous and unbroken. @@ -806,13 +803,10 @@ range of values is continuous and unbroken. plot_scheme("oranges", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') -Diverging Scales -^^^^^^^^^^^^^^^^ - -Examples of diverging data --------------------------- +Diverging Schemes +^^^^^^^^^^^^^^^^^ -Diverging scales are best suited for data that varies above and below a +Diverging schemes are best suited for data that varies above and below a center point, such as positive and negative deviations from a mean. Examples of diverging data include measures of deviation, such as temperature anomalies or changes in sea level. @@ -847,12 +841,10 @@ temperature anomalies or changes in sea level. - .. altair-plot:: :remove-code: - plot_scheme("blueorange", divg_schemes, cvd=True, continuous=True) - - .. altair-plot:: - :remove-code: - - plot_scheme("blueorange", divg_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("blueorange", divg_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("blueorange", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -861,12 +853,10 @@ temperature anomalies or changes in sea level. - .. altair-plot:: :remove-code: - plot_scheme("brownbluegreen", divg_schemes, cvd=True, continuous=True) - - .. altair-plot:: - :remove-code: - - plot_scheme("brownbluegreen", divg_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("brownbluegreen", divg_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("brownbluegreen", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -875,12 +865,10 @@ temperature anomalies or changes in sea level. - .. altair-plot:: :remove-code: - plot_scheme("purplegreen", divg_schemes, cvd=True, continuous=True) - - .. altair-plot:: - :remove-code: - - plot_scheme("purplegreen", divg_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("purplegreen", divg_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("purplegreen", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -889,12 +877,10 @@ temperature anomalies or changes in sea level. - .. altair-plot:: :remove-code: - plot_scheme("pinkyellowgreen", divg_schemes, cvd=True, continuous=True) - - .. altair-plot:: - :remove-code: - - plot_scheme("pinkyellowgreen", divg_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("pinkyellowgreen", divg_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("pinkyellowgreen", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -903,12 +889,10 @@ temperature anomalies or changes in sea level. - .. altair-plot:: :remove-code: - plot_scheme("purpleorange", divg_schemes, cvd=True, continuous=True) - - .. altair-plot:: - :remove-code: - - plot_scheme("purpleorange", divg_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("purpleorange", divg_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("purpleorange", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -917,12 +901,10 @@ temperature anomalies or changes in sea level. - .. altair-plot:: :remove-code: - plot_scheme("redblue", divg_schemes, cvd=True, continuous=True) - - .. altair-plot:: - :remove-code: - - plot_scheme("redblue", divg_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("redblue", divg_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("redblue", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -931,12 +913,10 @@ temperature anomalies or changes in sea level. - .. altair-plot:: :remove-code: - plot_scheme("redgrey", divg_schemes, cvd=True, continuous=True) - - .. altair-plot:: - :remove-code: - - plot_scheme("redgrey", divg_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("redgrey", divg_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("redgrey", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none @@ -945,47 +925,83 @@ temperature anomalies or changes in sea level. - .. altair-plot:: :remove-code: - plot_scheme("redyellowblue", divg_schemes, cvd=True, continuous=True) + alt.vconcat( + plot_scheme("redyellowblue", divg_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("redyellowblue", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') - .. altair-plot:: + * - .. code-block:: none + + redyellowgreen + + - .. altair-plot:: :remove-code: - plot_scheme("redyellowblue", divg_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("redyellowgreen", divg_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("redyellowgreen", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') * - .. code-block:: none - redyellowgreen + spectral - .. altair-plot:: :remove-code: - plot_scheme("redyellowgreen", divg_schemes, cvd=True, continuous=True) + alt.vconcat( + plot_scheme("spectral", divg_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("spectral", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') - .. altair-plot:: - :remove-code: +Cyclical Schemes +^^^^^^^^^^^^^^^^ + +Cyclical color schemes may be used to highlight periodic patterns in continuous data. +However, these schemes are not well suited to accurately convey value differences. - plot_scheme("redyellowgreen", divg_schemes, cvd=False, continuous=False) +.. altair-plot:: + :hide-code: + :output: none + cycl_schemes = { + "rainbow": 9, + "sinebow": 9 + } + +.. list-table:: + :widths: 25 75 + :header-rows: 1 + + * - Scheme + - Example * - .. code-block:: none - spectral + rainbow - .. altair-plot:: :remove-code: - plot_scheme("spectral", divg_schemes, cvd=True, continuous=True) + alt.vconcat( + plot_scheme("rainbow", cycl_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("rainbow", cycl_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') - .. altair-plot:: + * - .. code-block:: none + + sinebow + + - .. altair-plot:: :remove-code: - plot_scheme("spectral", divg_schemes, cvd=False, continuous=False) + alt.vconcat( + plot_scheme("sinebow", cycl_schemes, cvd=True, continuous=True, single=False, top=True), + plot_scheme("sinebow", cycl_schemes, cvd=False, continuous=False, single=False, bottom=True) + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') Categorical Scales ^^^^^^^^^^^^^^^^^^ -Examples of categorical data ----------------------------- - Categorical scales are best suited for data that is unordered, such as different species or categories. Examples of categorical data include the types of flowers in a garden, or different political affiliations. From 17ec53abc534cf717d66e11126c3f2ffbd1400f8 Mon Sep 17 00:00:00 2001 From: mattijn Date: Sat, 22 Apr 2023 00:12:07 +0200 Subject: [PATCH 12/17] format as hex --- doc/user_guide/scale_color.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/doc/user_guide/scale_color.rst b/doc/user_guide/scale_color.rst index b8f3af361..26e329863 100644 --- a/doc/user_guide/scale_color.rst +++ b/doc/user_guide/scale_color.rst @@ -76,16 +76,17 @@ examples of their use. normal_vision = ( alt.Chart(data, height=h, width=alt.Step(w)) .transform_calculate( - hex=expr_hex, - rgb="rgb(datum.hex)", - lum="luminance(datum.hex)", + hex_raw=expr_hex, + rgb="rgb(datum.hex_raw)", + lum="luminance(datum.hex_raw)", + hex="'#'+format(datum.rgb['r'], 'x')+format(datum.rgb['g'], 'x')+format(datum.rgb['b'], 'x')" ) .mark_rect(cornerRadius=cr_l, discreteBandSize=w) .encode( x=alt.X("i:O").axis(None), y=alt.Y("normal vision:O").axis(my_y_axis), - fill=alt.Fill("hex:O").scale(None).legend(None), - stroke=alt.Stroke("hex:O").scale(None).legend(None), + fill=alt.Fill("hex_raw:O").scale(None).legend(None), + stroke=alt.Stroke("hex_raw:O").scale(None).legend(None), tooltip=[ alt.Tooltip("hex:O"), alt.Tooltip("rgb:O"), @@ -157,7 +158,7 @@ examples of their use. ) return grayscale - def plot_scheme(scheme_name, dict_schemes, continuous=False, cvd=False, grayscale=False, top=False, bottom=False, single=True, method='hcl'): + def plot_scheme(scheme_name, dict_schemes, continuous=False, cvd=False, grayscale=False, top=False, bottom=False, single=True, method='rgb'): # determine chart settings chart_dict = chart_settings(scheme_name, dict_schemes, continuous) From b4e9dc4e37e754018ac9e860d6aa029d0c20dbfc Mon Sep 17 00:00:00 2001 From: mattijn Date: Sun, 23 Apr 2023 00:30:07 +0200 Subject: [PATCH 13/17] include color space --- doc/user_guide/scale_color.rst | 177 ++++++++++++++++++++++++--------- 1 file changed, 129 insertions(+), 48 deletions(-) diff --git a/doc/user_guide/scale_color.rst b/doc/user_guide/scale_color.rst index 26e329863..243e1097d 100644 --- a/doc/user_guide/scale_color.rst +++ b/doc/user_guide/scale_color.rst @@ -29,7 +29,7 @@ examples of their use. # define a data sequence of 300 for continuous color schemes if continuous: - no_colors = 300 + no_colors = continuous if type(continuous) is int else 300 data = alt.sequence(0, no_colors, as_="i") # define a data sequence using dict for defined schemes @@ -44,7 +44,8 @@ examples of their use. data = pd.DataFrame({"i": range(no_colors), "hex": colors_in_scheme}) # dynamic width - w = 1.5 if continuous else 50 if no_colors <= 5 else 30 if no_colors <= 12 else 15 + max_width = 450 + w = max_width / no_colors # cornerRadius dependent on height-width ratio cr_l = 0 if continuous else int(w / 2.4) @@ -79,9 +80,9 @@ examples of their use. hex_raw=expr_hex, rgb="rgb(datum.hex_raw)", lum="luminance(datum.hex_raw)", - hex="'#'+format(datum.rgb['r'], 'x')+format(datum.rgb['g'], 'x')+format(datum.rgb['b'], 'x')" + hex="'#'+format(datum.rgb['r'], '02x')+format(datum.rgb['g'], '02x')+format(datum.rgb['b'], '02x')" ) - .mark_rect(cornerRadius=cr_l, discreteBandSize=w) + .mark_rect(cornerRadius=cr_l) .encode( x=alt.X("i:O").axis(None), y=alt.Y("normal vision:O").axis(my_y_axis), @@ -224,50 +225,116 @@ it's important to understand the basics of color perception and theory. Color Perception and Theory ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The importance of perceptually uniform color scales ---------------------------------------------------- - -Using colors that are perceptually uniform can help ensure that viewers -can accurately perceive the relative differences between data points. By -maintaining consistent differences in color perception across a scale, you -can create visualizations that are not only informative but also easy to -understand. - +We will explain the basics using two colors, ``blue`` and ``orange``: + .. altair-plot:: - :hide-code: - :output: none + :remove-code: percep_schemes = { - "uniform": ["#542788", "#1f7abd", "#4dbd33", "#fdae61", "#d7191c"], - "non-uniform": ["#FF0000", "#FF6600", "#FFFF00", "#00FF00", "#0000FF"] + "two_color": ['blue','orange'] } + plot_scheme("two_color", percep_schemes, cvd=False, continuous=False, method='hcl').configure_scale(rectBandPaddingInner=0.1) -.. list-table:: - :widths: 100 - :header-rows: 1 +Now, lets say we want to define a gradient between ``blue`` and ``orange``. +That means we have to go gradually from ``blue`` to ``orange``. +The interpolation between colors can follow several methods. - * - Example - * - Uniform color scheme - - .. altair-plot:: - :remove-code: +It could be done like this (``interpolate='rgb'``): + +.. altair-plot:: + :remove-code: + + plot_scheme("two_color", percep_schemes, cvd=False, continuous=300, method='rgb') - plot_scheme("uniform", percep_schemes, cvd=True, continuous=True) +Or like this (``interpolate='hcl-long'``): + +.. altair-plot:: + :remove-code: + + plot_scheme("two_color", percep_schemes, cvd=False, continuous=300, method='hcl-long') - * - **Non-uniform** color scheme +Or like this (``interpolate='hcl'``): + +.. altair-plot:: + :remove-code: + + plot_scheme("two_color", percep_schemes, cvd=False, continuous=300, method='hcl') - .. altair-plot:: - :remove-code: +As you can observe, all three color ramps are different, +but all three color ramps start with ``blue`` and finsish with ``orange``. +So what is the correct interpolation method to go from ``blue`` to ``orange``? - plot_scheme("non-uniform", percep_schemes, cvd=True, continuous=True) +There is no single answer to this question. +It also based how we, as a human, see colors. +To better understand the perception of colors to the human-eye, we +can make use of a color space. A color space is a diagram to help +understand how we see perceive colors and how they are distributed as wavelengths. -Different types of color spaces -------------------------------- +Here we show the corresponding trajectories of the blue/orange color +gradient based on the three interpolation methods +(``rgb``, ``hcl``, ``hcl-long``) in a common used color space ('CIE 1931 color space'): -Different color spaces, such as RGB or HSL, offer different advantages and -can affect the perceived brightness and contrast of colors. By -understanding the nuances of color spaces, you can create visualizations -that are not only informative but also visually appealing. +.. altair-plot:: + :hide-code: + + import lzma + import io + + compressed_df_cie1931 = b'\xfd7zXZ\x00\x00\x04\xe6\xd6\xb4F\x02\x00!\x01\x16\x00\x00\x00t/\xe5\xa3\xe10\x9e\x1dp]\x00@\x01Nm\xa9\xb6E\x9b\x19_AS\x16\xb8\xb2s\xd2\x1b\xc9\x1e\xda\xbbR\x0b\x11\x9a\x98;K\xf9\xdaH\xf5\xb8K\x08\xe47\x8e\xd3/\x80\xfc\xad\xaeQ\xc7@\x98=\xcc\x04\xb6\xc0N\xd0\x17\x10\xdd\x85d9j\xd5=\x01\xff\xdf\xfcM\x8c\x1a;r\x02\x97\xd6O\xf0(\xe7\x82\x96\xfe4\x08\xf9\xc6U}\xff\xfa,\x14#4\x93\xd2a\xc5\x18\xcb\xd16\x00Qa\xd39mx\x90\x16\xec6SV&\xfe\x86\x18,\xe1X\xa72\xd3u\x92\xe9\x10\xb6@\x0c\x87\x94\x05"\xf4N\xaa^|\xec\x9co\xa5\xdccF|u\x89\xf0\x94m\x13%\xf9\xd5\xfc\x86\xe0\xf8\xe0\xd8\x85\x1a\x9e=\xb8\xcf\x81\x8a\xd5\x99\xf5\xc0\xfb\xb7=ft\xbasMXW\xe0\x8dU\x03\xa7\x87\xb8\x1e\x9a\x8a)\xf6\x0e\xfb\x812\xf0I\x05dW\xd6\xb8e\x1f!<\xc2\xd8\xfc)[\xf3\xf5\xa1\xac\xbf\x1b\xb5&\xd3\x16\xb8*[5n\xe0mW\x1d(#\xac\xa6\xea\xb3\xa4`u\xb5\xcak\x08\xaf\xb3\x02\xa4\xbf=\x9e\x8d`\xff\x80\xd9q\x98\xa6\'\xf9\x94\xed(\xc8\'\xad\xb9e\x01\xd9\xf2X\x99b\xbbp\xec\\\xb5\xadk#\x88~\xfe\x80 \xd8\x17|\x1buI\xc9\xb6\xae\xaeLS\x9d\x9f\x12\t\x82\xa9\xaf\xcfx\x87\t\xc9\xdb\xa43J\x9cD\xbed\x8c\x18\x8f\x1c\x15\x1a.\xfd\xc9*Z\x9b\x16\x95\xc9\x03ip\xbd\x87*\xa2I\xf4\xa3N\x9e\x1e\xd0\xf1w\x88\xe9#\x10\xe0\x86\xb1OQ \xe1\xd0Mj^\xf0\x89\xe5\xb5\x08\xd2\xabk\x84i\x1eA\xabg\x17_\x881\xfb:\x14\xc5\xfd[n\xden\xbdx\xca\xd7\xfb\x01\x82\xaf\xe6\'\xe6\x90v\xc3\x9eSq\xc9\x7ft\x80\x03X\xfa#3\n\x95+\xef\x9c+l\x1c\xe5\x9f\xcc\xe7\xdd\x85\xf5\xf5^:V\x81}\xb6(\x952\x0e)\x91\x94\xb8\x02js\x05y\x88\xc7m\x85_\x11*\x1b\xb5\xf4\xf2\xc36tWB\xe6\xc0p.H\xf4\x94\xdb\xd1\xe1m\xec9\xa3o\xd5I\xd8\x8d\x8f\xb6\xa0\x0e\xaf\x00\xa0b3= \xc9,\xa0\xa8b\x97`\x0e.<\xf2T\x9f\x1d7>\xc9y9v\xae^x\xca\xdd\xcaA\xf1K\xea\xb5)\xd7\xc5\x1d\xcd\xd4\xb6\xf7\x90V\x9a\xeaa\xaaX\xa1u\xc1\x17H\x84\xe5?\xf9\x8aPGB\r\xb0\xf29\xbf\x05^y{\xe4\xaf\x93\x08\x8dS\xd8\xd4\x91\xc3/\x1e:\xf8\xb4\xc8J\n\xe7\xf9ZB\xe2\x18\x0c\x1e\xa5G\xc9\x9et^\xed\xc1\x9d\xbc>A.\xa2\xee\x1b\xe70\x03\xeanD\xa2H\xb0\x98\'\x93\x1eA\x8bjOwz/\xf7\xb7l\r&\xa2\x11\xdfB\x88\xb4^<\x0b\xde_\xa3\x99?\x1dV#\x84i\xb2\xf1\xf2\xb6\x13FiT\xb3\xc6\x8e\x93\xc6qU{\xf6\x19\xe3R\xfa`\x0e%\xc7\x98\xe3[X\x04\x8f\xcd\x85\xee\xa7\xc1\x91j\xcb\xa9s\xcd\xd2\xe5\n#m\xaf\x9a\xe5\x7f\x84\x00\xafi\x19\x07K:\xbbw\xd4\r\xb1\x97\xee\xc7\x85JQ6=q\xf2\xe2IV|\x0c\x120\x1a:}\x0e\x10>\xf9\x82\xbc\xe3`\xd33~\xc3\xfca\x06\xfc\xbf\xb5u\xa6r%\x9faBk=]2QH;\xe6\x96:m\x15z\x84\xc3\xe8\xf9\xa5\x87Y\xf8\x9deVIm\x8c\xa1\xc8-\x0b\x8d\x15\xf1}\x8c{x\xce\x9c\\"\\!v\'\xcddL\x9c\xe2\x1e\xc8!X\x9f(\x0fn\xdf\x10>L\xa6\xab\xda\x15\xc6\xdbx\xc0v<\xf4\xab\x80T\xf4\x157\xd2\x97\x13#e-\x9c\x11\x87\x8b\xb4\xe9\'\x02\xa1U\x0f;Q\xf2Z\xd2/|:\x0b\xa8-#\x06,r\xfe\xea\x18\x8f%\xc5O\xc1\x92\xaflI$\x07\xd3\xba\xf45\xad\xaa\xe5\x18\xcc%/\xba\xef\xaa\xba8\xba\xab\xe1\x83:,\x95\xce\xf2\x8a\xc9N@`\x94+\xe6\xa53\x1dk\xc5\xc7kX7G\x00|\xa5\x1aL\xec\xf0\xe5p\xbaI\xd7\x97\xde\x1b\x15\xd5 \x888\'\xa3\x15i\x8b@\xef5\xe78lq\xa3\xe2j\x0b<\xb2~I[\xf6\xd0Xx\x86Dv\xae5\x81\xaa\xc2\x89r\xff\xa3B\xac\xf9\x07=\xc2I\xea\xbe\x03\x02\xc9f\xffJq\xd7\xbd%\x84\xee\xda\xec\x11\x00\xd5=-}\x98.u\xcd\xcbb`d\xc5\x87.h9\xb5\xdc\xd4nd\xd8\x06I\x9f\x81y\xeb\x07\xfb\xd4\x06Y\x97\xc5\x16\xdf\xd60\xdb\xfc\xa3L\x1cj\x0f\xe2\xc9\xd9;\xcbe\xc2\xfa\x94\xe8\x11\xb1\xc3\x95\xb3\x08\xae\x01s\x9bF\x86\x1e\xa3{\x89\x9b\xf2fx-\x9c\x0f\xe5X\xaaj\x85yP\x16\xe1\xd3Mg\xdd}\xc7\xe3h\xb5\xb0*\x85}\xf8\x15\x9a\x9b=\xed\xe4\x0e\x8do\x89\xca\xb5\xc4\xca\xc8\xd8\xecn\x83\xbf\x066}\xbb\xa98\xd6\x81\xfd]\xdcx\xbf\xceF\xd99\x02G\xcf\xdd\xed|\x18n\xb4\x0c]\xb5;\xb9\xcc\xb8\x9c\x88\xf9j!neq\x7f\xa2m\xf3\x05\x1e\x8dsL\x11kv\xdaO\xf5\xfc8^\x96>\xb0\xc8(:\xd9\xc1\xf5:>\x11\x1b \x1a2,\x9a\xc9~\x8c\xdd\x8a\xcb)\x80?:5\x8e\x85\xa3\xd3\x11g\xban35\xbf\xc3]\xf9\x94^p]\x87\xe3\xfdz\x9fd\x7f.\x80Q\xdc*\x07sP*%\xb2\x99\xaa;_\x1e\xee`\x91\xa5\xe8\xcb\x81\xa7g\x8a\xcd\rdd\xc7\xde]\xee\x05\x82\xe2s@{i\x04b\x92Q\x05nL\x0b;\x89>\xbex\xfc\x9d\xdb\xdc\x9e\xcd\xdahg:\xce\xb6Hm=,j^-~M\xedo\xca\x83w\xc5TK`K\xfa\xf8\x84\x17\xbd\xb2\xaf]#\x8bI\xdf \xbd\xee\x9f\xce\x19/<{\x82\xc4~\x1f\xc4P\x13j\xa3\xd0\x0b=\xb8\xe3y\xd9_\x8b@\xad&MF\xce\x03Z\'\xf7*%C\xd1g\xf1!\x93\x88\xbb\x01\x8e\xb4\xdbZd\xf3\x92\x1fF\x1b\xc6\xe1\x99\xbb\x8ec/g\xd9(\xd8%\xe4"\xce\xaf\x98\xb1\xf0\xa6\xe0\xd8\xcc\xebu\xd8\x8bT\x9e+x\x93\x0fw_\x82\x9d~U\xce\x8c(\x99L|y1\xd0\x0f\x9c\x1d7d=\x91\x17\x894A\xad\x88(\x94@aCi\xb2\xde\x1d\x1d\x97|\xb9l,\x9f\xbd\t\xce\xfd<\x98\x14\xf4X3\x0b\xef(g\xb2\xf8\xa4\x8c\xff\x1aj\xf4\xa0\x865B\xfaQ\xfa\x97\x81\'%\xf45\x19\xf52\x12\xd7\xd6\xd8\xdf2\x94kb\xd4\x8b\x07\xdd&X\xb2\x92e\xbd\xa0\xe6?\xfe_b]B]\x93P\xed\xcf.\x15}#Y\xa1\xe5\x90\xbe\xe7Lb_R?\x04I\x176d\xc68\xa0\xdc+\xcc|\xc0\xa5^\xb1X1f\xf2\x84\xea\xf1\r5Q\x0e\xa8\xae\xd2\x1fB\x04\xae\x04K\xfe\x08\xe6}\x84\x8d!n\x00\x0cPF!Z5\xab\x06\xdbxS\x85\xa8\xa8\x80\xa1\xfdq\xb1\x93\xdaCcn\x8d\xc0>\xe9\x91Ax\x81\x98G-\x0c\xa9\xd74\xa3\xaf\xf9\n`\xe4\xb9\xd9\xca\x02\xe2Vp\xa9E,Q\xbf\xc6\x8c\x0cDY\xd7\x01"\x82\x87\x80Pd\x9b18\xe1\xf0d\xad\xc6x\xa6\xd5\x8ba\xe4\x8b-\x08\x02"\rtlN\xd3]21yu\xf9 \x89\xc7r\x8e&\xc1\x06O\xf7n$\x99\xf0\xab(\xc4\x0f\xd6O\xe6\xfcJ\xc2:\xbc\xac\xa1.H\xa6\xd6\x04\r\x0b\xe3>M\'\x04\xb5\x05\xf6\x1e\x96!Y\xbd:\xe3\x03/,xri\r\xc0u\xe5\x0b\xb8\x0b<\xd9V\xf3C:\xd92\x0fK\xdf\xdd\x11R\x0e\x03u\x85v\xa2}\xd0f\xf4"\xde\xe6\xceC\xadrP\x140c\xf8\xd8\xd0\xd2\x00\xe4NSw\xf9\xd1\x15,\x9c\x1f\xda\xe4\x0cAt\xc8\x8b\x15\xefcV\x010\x10\xaa[\xddd\x81\xf4\xcaSgx\\\xa6\xb7\xb4\x88R5G=\xfd\x03\x0f@>\xbfW!\xee\xa6\xfc\x8d\xbc\xbb\xb7.Q5\x00\xc3\xaf\xfb\xcb\x8f\xf9\xdb[\x0b]lZ\x8fALcx\nvb\xdd\x00\xf75T\xb9\xa4\xfc\xfa\xafm\x10z%\nc[rj\x8d\xb9\xea2C}d\x8ee\xed\x0b\t\xc3T\xa0n\x91\x82\xd9\xa0\x14\xd5A\x10\xben\xfa\x0f\xf4\xce\x009\xde\xdf\x8b\x14,k5?\xef\x184u\x9c\x1b\xea\t\xef\x0c\xec\xb3}QA=\xe0K\n\xf7\x0fW\xc1R4&\x9a\xdc\xef\x1dz\x1c}\x89$\x02\xe0eWV\xc7\xfb\x98]9u.\xf6m\n\x8d3\x17\x89n\xb5\xe8\x14\x86]\xb6\xecM\x03*&Rt\xfe\xd3\tA\xd2\xa3\\+]\xf7W\xe5Z\x0b\x91\xc3$\xf3\x13\xbdf\x90\x9d\xe6\xba\xde\xe6\xc3\xa1\xf0\xdf;(\xc9v\x89\x93\x9e~\xf1\x00\xcd$$\xd5f\x03W\xed\xd6\xb9\x88\xb9\x7f\xd1J\x06\xb4\xe2<\xe9\xdb\xed_\x06\xb0T\x0c3C\xf2`\xa8\xa4${"6Z\xe8\x86\x8d*|\xec\x18\xa7\xc5\xbd\x8d\x19s9!\xdeI\xa7\xf1\x8a\xb2\x13g\xb2\x11\x9c\x13\xb4\x8c~O\x0c\xc0\xbf\xbfD\x07\xadm\xc2wz \xd5\x9f\x80\xe7\x0b\x1e\x86n\x1ef\xae\xfa\xa1\x84aF\x85\xf5@\x05\x97\x9a9\x15j\xf6\xc34vr\xc6\xbc/N\xb7\xa6\xae\x1f\xa0\xe6M\x83\xf5@/-j\x10dZM\x82\xbfnW\xe8\xb7oU\x98F\x1b\x93\xcfzj\xaa\x0fS\n\xdb\t?f\x80\x9e\xf7\xcd`\xf5\xb7\x1d(a.\xfb\xe1\n\xe7\xfcu{l\xc7\xc9\x08Y\x1f\x15\xfb\x82\x83\xa8\xc9\n\xf7;\x0f\xdb\xb0\x02\xda\xde\t\xd0\xeb\xa9K\x85(\x03\x86B!\xcd\xc2\x9e\xe9\x92\x9b\xb1\xca\xdd7b\x17\x13#\x9bz\xd7\xe8|\xd9\x0c\xd2\x14\x8d5\xf1\xef\x95;c\x7f\xfe.\xb2\xc5\xdc0\xbb\x18\xaa\xaa\xc6jG^\xd5\x92\xd4\xf5\xbc\x84\x0ftAp}\xcd\x16\xf4\x1a\xc6\xc0\x15\xb4\xd5\xcf`\x01\x05\xdc\x1d\xdd\xd8\xf6\x81\x8b\x15\xf5\xe1\xe3\xae\x17\xd1A\xda$\xc5\x9a\xd0\x03\xec\n\xb7\xd0\x1f\x82x\x7f\x87\x83\xa6\x06\xd0\xd9\x1a#>_nB\xc7\xa5\xddN\xc6\x1b?\xda\xda@\x1d\xbc\x1aM\xba\xa5\x1fB\xc0\x03\xc3x\x8fC\xe0]\xc4\x06\x0cE}9\xee\xa1\xf0\xf6\x91\xc0HH{{(0\x88\xd4mZ\xf7B\x91\x19\x8c~\xd9\x18\xf7\xdf\xab\x1e\x89$\xc9\xc8\xe9\xb2\xbaG:\xb1s\xa8U\xb5\xa8\xf3\xf7\xf7d\xe8\xb5\x97w5R4\x00\x85,\xd39-\x07\xfb\xcai\xdb\xce\xdb\xd4\xfd\x10\xca\x8d\xe6/\x03\rL\x85\x047\x10\xbeZ\xd6\x18O\x87lJ\xe2\xf7\xd4X\xcc\xd6z\x18w\x8c\x05\x97"\xca\x18\xf5$\xf4\x04\xda4\x90U\x99\xfe\x04\xbfO\x95\x8e\x8d\xac\xe3\x8c\n\xaa\xd4z\x14\x0c\x14i"\xea\x81#\xc5\x03?\xc3\xa5\x94\x19q\x16\xace1\xf1\x18\xc3?\xd8&\xe7\x82\xb2*\x06\xc3\x85,J#n\x9a\xe8\xc5\xb8>\x18\x8e(\xdc\xec\xa0em*\x0ei\x92\xb0\xc6JPE\xcc\xb1)\xc2\x86\x93P,\xad\xbd\xd5\xe12\xa5M\x0f#0\x0e|\x0e\xe1:\xd6\xf1\x13\x93 \xd64\xe6m\xbd\x88\xd2\xf1>\x07\xf4-\xc57!^)\xed\x05[\xeaa\x97x\x1a>\xf4\xe5\x19\xcfH\x9b$g\x15\x07\x9b\x98\xde\xa4:c\x8c\xaf*)I\x03r\xc1\xf5 \xdf\x88\x98k\xcc\xb8\x94S\x9fu9\xe0\x7fU>\xccc/0\xae}1\xfa\x94yy\x9d\xb8\xa63\x8cIm\xef\xd0\xe7\xa0\xe7\\\x987r\x8a\x96\xc8H6\x14\x15\x0c\x06\x99>Z,\xb7\xdd\xa0\t\xa9\xc4&\xb1\xda\x16y1\x91\xdb\xc6\xa1\xd9\x06\xfc\xa2\xec\'\xa2}\x0cS\x00\xa8\x86\x06\x14?,\xf5\x0f?CH\xc2,\x83\xef\x8a\x06eVL\x9b_|\xdbh$\xc2\xe4\xa0\x1d\x84\x17\xe2\xda\xbb\x9bY\xd0\xc9z\x91\xa76\xbb\x08\xb3\x17C\xd5\xd2\x01\xca\xf7\x8e\xc5\xa3%\xd4\xc8u\xbfW!\x06\x80\xda\xbc\x1b3T{\xa8\x85G@\x04\xe1f\xfb%\xb1w\x84\xd1\xf1\xc9x\xfa\xdb\x01`\x89\xd2w/\xf0\x96\xb1\xd5\x0e\x14x1\xd4\x06\xa4\x01\xe5\xe6\xa5\x04.\xf5\x05e>\xde\x98\xf0\x10\xff\xd2g^\x8e\xc1\xb5n_\xbc\xa4\x8e\xd6\xe7I\x0b\xf9@\xb5\xad,\xaf\x86\xfbW8\x13\xb4\x82\xde\xd5/\xdb\xdbD\xafH\x87\x12\xb1\x7f\xb3\xe5x\x07\xf1\xcd\xe3\xabh\x04U]\xb8?j1\x8d\xba\x81\xdbD\xa8\xce^\\\x86\xf2K\x1d\x82\xa6\xbc\xa5\x83,!\x8e+\x7f\x12\xe9\xb3\x92(31\x8d\xe6\xd3f|\x08t(\xb6(\x99\xfe\xc9\xcd\xe2\xcc\xa6\x02Vd\xae\x82\xe8\x9c\xa6\xc0y\xbc\x0e\x12\xd3\x9e\x94\xe7e/\xb2\xf1"c\x84y\xd8\xa1\x05\xeb\xabmR\xc9\xac\x07.\xd8\x12\xd9\xe2C\xba\tl\xa0\x0cY\xc4 \xda\xa4K\xdb\xf6\x9ax\xc1\xf9\x8c\x0b\x03 \xe7x\xf3\x05i\xd1w\'\xaa\x89~\x1b\xb1\x159p\xb7\x1e\x00\xa2\x05\xb7\xdd\xb5+L\x03\xf9\xde\x1f\x1e\x98\x81x;Z4\xac\x1e3M?\xcb\xa0\xb9h\xe0\x03\xfd\xd2\x1b\xf0P\xdf\xbe\xd5\r\xc9 \x14C&\\\xd8\xb3\xd8\xdc\xa9\xd6\x15\x19\xb2\xb6\x95\x17\x92]\xa4\xc0\x91T\xe0\xdb\xd1\xf0\x9c\xa6\x17\xd9g\xc0\xd9s\x1c\xf7\x11\x83\xc2\xbe\xf9\xda\x11.B\x8d\x96\xad\xd1\xe7\x91Qd:d@\x9e\xd8j\xf3oo\xe3VA.x\x1b\xe9\xd9\xc6\x14l\xf5\xc9\xa4\xe7\xb6%;\xe7@\x08&B+\x18\x0c\x1e\x1a\xd9ec\x93\xa4\x98\x19cv{\xe6\x08\x10\x1e\xaa\x03*\xdeQ,\xd9n5Yb\xdf\x04|Px\x85c\nN\xfai\\\xee\xc8K%@v\x14\x94\xf4\xce|\xf9\x195\xc3i\xe6\xde\x9a\xfef\xdd\xdb\x13\x1c\x19\xc6~\xb6\xf7\xf3B\xaf\x11@\xb1\xbd\xc4\xb8bc\xaeI\xf93\x8a*\x93\xe1SiW\xda\x98\xb0\xaa;J#\xcb\tW\xaa\x1fQ/w\xaf\xf1\x10\xbc\xadG\xe3&SV\xd9>\xb4\x15c[\xefMF\xb9\xe7\xce\x1e\xae\xcc\x85\x04\r\x92\x8b\xf7\xd2\x1f\xbb\xf5\xda\xb4\xb9\xca)\x94[q$\xdd0m\xd3\xb8+\xa8Y[\x14\xe5N\x84\x11\xb2\x8f)\xaf\x1e\xaa\xae\xb8\x01\x14\x9dNF\xcbn\xdc\xd7\x0f\x83zt\xa1\xb9\xf4A4\x7f\xb3b\xb4\x1eBp\xfb\xd9\xcc{gN\x9e}|\xb3\xdc\xdc\xda\xda-\x7f\x9aa\xd5Yv\xdc\'\xd3U\x19\xe1\xe6g\xb6\x08\xfct`hO\x80E\' \x9d5\xb4\xca\xfd\xa1!\xe0\xf8#C\x08\x11\xb4\xe5\x98\x10\xa6\x9e\xb7\xb6\xcb\xcc\xef\x80\xc7\xce\xde\xbd\xdeQ\x9f\x04\xf6\xf5f\x17A\xe9\xda\xb2qYtJ\xac\x95\xac\xe6\xd6#\xb5=\x94\xd5\x17R\xbd\xaf\x18\xb8\xe5\xaar\xd70\xaa/Z\xd8\x10zc\xb1\x1d<>M\xdb\'\xc7CR\xf2\xd1\x0bhF\xba\x91J\x81,j\x94}R\x0c83tO\x135Y\xddO:8\xda\xf6\xdd\x97\xbf\x986\x8c\xc0\x8aG+\xa3\x02\x12\xfa\xc6yq>\x02\xdf(\xbeITYk\x9d\x18\xa2\x89.K+\x87\x88\xc9`z7\x01a\x94\xe2\x1f$\xcbT;\xdcpI\xbb\x17\xdb1{\xcbK\x99\xea\xc5\xc5\xe1\xb9"\xf5\xe0@\x14\xa6\x8a\x1e\x85\x95\xe4\x89\xddec,\x80\xef(Q\xd4\xaf\x8fC`\xafX\xb5\x0eph)\xf9\xf0\xdc\x10aO`&\x92Li\xca\xdc\xe0\\\xb1\xf2\x8al\xb8p\xa9\xed\x99~;\xe6\xcf\x1a\xeef\xe2\xe3\xf9\x03\xe1\x07\x84O\xcf\xdbB.\x1dvP\xc5\x8c\x06s,\xc7+\xe9w\x9e\x9b1I\x08G\xb4\'\x95_\x0ct\xbfK\x9f\xf5\xa0\x8d>\xcc\x9apy\x9c{\xda\x92\xc1\xf7Q\x1d\xc6\\r\x9f\x10\x06\x1a\x8f\xce\xc4Ni\xbcdXC\xe7p\x16\nP\x07\xc2\x11\xf0\xe3A\xdckN\x85\x96\xebc!O\xcb\x8c\x0f\x07\x00\x89,RUB qmkp1\xed\x8b>\xd38\n<_\x13\x06\xa1\xf0\x97\xbbb\xf9\x8d\x82qv\x8d\xca\xe0\xa8\xbfbtwJY\xe6\xbd\xc9\x8c\x8f\x8cL\x98\xe5\x12\xc8X\xa85\x94_\x05\x95uO((lz\x00\x9f}5\xfcb\xe15\x9ah\x8bZ\x07\x8a\xd5\xd4\xfc\x13\x03r\xa4\xd6\x11\xdc\xae\x1d\x0f\x8aCB\x1a]\xc8\x93\x04A\xc9Z_\xc3\xb8\xacu\x1akl]d\x11w\xf3\xd7t\xa2M\x900\xd8y\xcd\xda\xc8\xb3i<\x82\x16H\xb4} :o\x8a\x97\x1a\x8c\xca8@\x82\xb1\x8c.\xb8~L\xb2}\xbc\xe3\x02\x03G\x10"\x9c\xc2D\x00\xedy\xf5J\x1fDj\x13S\xc8\xe0)\x83\xa1w\x1b\x0c^\x9fa\xed\xec\x8e;J\xee;\x95\xfd\xceo/tE"\xaf\x95\ne\xd6\xa1KZ\x7fJ\x15\x9e\x1f\x9a \xa5X\x81;$\xa6\xf7\x9b\xd8s\xd5\xc7\xba\xf7b{@h#\x7f[\x13\xba\x19\x9eW\xf5V\xe63\xd5o>o\x7f\xaa\rhG7\x0e\xa50v+\x8dG\xea\xad\x90\xe6W\xce\xc9\xbc\xdep\\\x1f\xabf\x99\xee\x06n\xac\xc5\xc5\xf3na\x8ap\xdb\x97\x99zG\xe3\x88\xd9U\xf1\x8f\x10\x81\x1a\xf36\x82\xccp\x04\x19\xe29\x88\x01\x1d\x07\xa1\xf07\xe7>\xbaOB\xd1\x81\xaf\xca\x00O\x80\xbe\x10\x8d\xb2]\x08u\r\xa4\xd6==\x0b\x91eR\x17\\Y\xd4\x1f\xbf\xee\x08\xe9}\xc0>\xa0\xd3\x9d\x85kED\x90-`s/\xbc^\xa0\xa6xZ\xcd\xa7$BgV\xd4\xe7\x0f\xdf\xbf\xee\x01s\xa5\xf0uU"\xcdu\x16\x03\xb2\x04O\xfb\x94\xaf\x08C\xc6\xba+\x06,^\r$J\xd7\xe9\xf8\x0e\xf4\xabb\x13\xda\xb3\xf5\xb92\xefV\xb0\x9b\xd9B^\x80>\xe7\xd1B\x87\xc3\xc2\xae\xe4\xc5\xf9\\*US\xbb\x03\xe3!T\x9a\xf7s\xdc\xec\xe2[\xa9\xdb@\xcf\xd0\x8a\xbd\xf8\xc8J\xc4Q/\xb0\x00(_\x1f\xf1Z(\x10\xbc\x91\xec\x95\xc0\xca8Erj>\x06\xb7<\x89Ei\x1fP\xd9s\xc4>6\x10\x01\xd4\x8aduK\x19\x80\xea\x1b>\xbc\xb0\xcc\xf6\x1d1\\W\xba\x0f6\xec\xb3A\x8276\x88\xd2\xaa*s\x1ao\xf0S\x7f\xd4kzo\x1f4\xfapQ\x16\x13\x00\xab6\x18\xd7\xcb\xf2qW\x88\x9b\'x\xe0\xf4\xf6&\xb2\xc6\x8b\xa8\xdf\xa2#\x10\x14s-\x96\xfa\x0fF\x9d\xc0\xb2\xd2\x07\x07;\xd77\'yY\x86Z9L\xd5k\xd0\x8c\x8f\xce$\x12\xec\xc8\xd3/\xb5\xf661\x1f\xd3X\xe1\r\xc1\x8d\x94I-\xb5aU\x9a\x8b\x82\xc7u\xad\x8b\xf8\x86\xd5\x1dbu\xbc\xcef\x14d1\xf7?\xb5\xb3c\x8f1\xe0\xa1\xaf\xf0\xa6j8O&>#\xc6\x08\x9f\xf67\x19\xf6\xcf\xea\x82[\x92\xe1\xa4\x8a\x01\xb3\xa0>\xdd\xae/DV\xa3h\x9c\xff(\xc0{\x00omP\xbd\x18\xdb\x86\x9c\x9b{\xbd\xeb\xca\xc5l\x86P\'\xf5\xbc\xc1\xe8\x15b\xe5lJ\x8c\x15p2\xa1\xcb\xceq\xba\x03#\xfc\x83\xf3hC\xce\xda\xf8\x11[\x03\x98\xff%\xc2\xce\xbf\xe2\xf9\xa9\xa0\xc8\xed\x1a\xcd\x93\xe1\xbc0T\x1eD\xc2\'\xc1o"f%\t\xf8i;\xd07\x01q\xea\xa2TR\x1e\x8bQ\xc2r\x86)QkNf\xc7\xf9e9\xe7\x8c\x8b\x9d\xb6\xe7\x0b\xf6\x86\xfa$js\xfc_\xc0J\x02/U|c\x06(\xb8H\xcf?O\x9f\x87\xc0\xffxa\xfc_\xe2\x1d\'i\x99\x88V\xef\xee\x92\x80S\xe4\xb4\xbeF\x81\xf4\x03+mg"=\x162TS\xd5^\xef\x9f~\x06\x99\x9d\x80@h`*w\xa2\xed\x84*\xcfpF{\x07\xaf\xca\xa4\x18k\xd1Y\xd2\x17\xbc\x07\x8e\x1c\x1b\xf7A\x0b\xc6\xbe\xa8\x03\x7f\xea\xd2\xe0\x13\x8c\xe0\xf2\x98\xa6\x1fJ\x01\x07+/b\xde-\x049\x9c\x83\xa5\xaa\xb6\x8dk]- \xc7\x1a\x91\xa6V\x1a\xc6\r\x08\x16\xb7\xb1\xda{\xe3}\xa6z\xe6`Y\x88\t\xe7\xebK\xb9\r\xf1\xb3\xd10\x10\rR\x1f$y%\xa6\x85\x8bw|&U\x95\xfa\xb8\x1d\xdb\xb2;\xee\xee\xa6\xd6\xab\x0bK\x9a\xed\x8b\xe6z\x97"\x15\xe0p-6\xd6K\xaa|\x00#o*\x076\x9d\r\xc2_*pcM2\xe8\xfd9\xaaY\xb0{d^\xaf\xb2]\x8d\x03#cQ\x04\x19\x8amo\x08\xe7e\xe0E\x11\xf9,\xe8\xb5k\xaa\x07\xe9\x82\xf4tw\x83<<\x92\xe4[\xbb\xd7\xb55\x19\xb7\xed\xe9\x7fd\xf0\xbe\x112\xc5\x186\xf0L%\xaa\x9d\x17\xe8\x94\xdd\xf1\x83\xd7\x89\x8e_\x07\x03\xa7\\\x1bz\xa71\x96\x84m\xda_[\xa1\x91o\x13\x0b\xca\xf3\x8e\xf2\x96J\xf3\x11k\x0b\xae\x86|\xb6[\xd40\xc6\x907\xf5\x92\x1bK3\xfejR^1"\xb5\xeb\x87\xf7\x06\xb3\xd0\xac\xdf\x8dq]\x84\xf0\xb7\x05\xf2,r\\\xa5]<\xc4\x95u_\x9c\xb8\x1b\xe3\x05\\\xbd\x18\xb5\xbe\xbc\xd4\xc3\x03#\x95R\x11\xa0\xe4[\x10@\xff\x9b\xa2\xb9{\xbe\x802\xbc\x84y\xa4h\xdduEs\xdcC\x0cV5\xd0\x97;\xd1\xcd:\x95~\xa3\x1fi\x86\x0f\x8aaP#*\xa5(mhS\x1d\x7f\x1e\x02`b.|\xef&\xeb\x84?&\x9c9\x01\xbc<9\x02\x19\x0eS\x1ce\x14\x10\xfe\x1c\xa9l!&\x04\x0bc<|5I\xc2\xce\xe3\x14>f\x8c\xe0a\x10\'\xa7\x91r\xe6J\x9d\x7f=\x14AjQ\xb6\x9d\x05\xa7#\x93\xed&\x1c\xcd\x123\xa2\x18;\xe8\xdess\xa5\x1c\x80\xb5\xadr\x95F\xdb\x85@%\xe5\xf9\xec@V\xcdM\xd6\xbf5\xfc\x90\xc9\xb3\x90\x99\x00\x1b\xec\xd3\xc4\xb71\xd0\x00\tb\x18\xbe\x17=\x1f<\xd0\xbd\xcc7\xb8_C\xb4XU\x01\x83\x1d\x08\x88\x17\xb0c\xa8\xb9\xb3\xf9O\xd1K\xddc\xda\xb2Q>\x1cU\xe9\xca(\x1aMN\xd2\xcd(\x81\xf6\x1a\xc5\xef\x0f\xcf\x04\x1f\xfbW/a\x85\xb7\xbb\xe6\x83\xeb\xb2N&\xaayh\x07\x14@C\xc1\x8b\xee\x047\x8fj\x13\xbf\x15\x8e\xa6\xd7B\xf2\xc4\r\xf1\xd9)\xd6\xa77\xcad\x81MC\xcaPM\x93b\xab7,\xe7V\xa4\xbc\x132\x9d\xd8\xc5\x16\x04\xbaD.#\xc3\x9d;,| \xa3,\x92\x99QT\x994t\xb5\xbb\xd3L\xaa\xb9\x00\xb22\xaeV\xeb^a.&O\xc0y\xab>\xc9\xa8\xfc\xa8\xb6\xd6\xd3V\xa8\xb6\x8er\xe1]\xde\x8eo\xd8@\xdd\xe2\xf9\xe5R\x88k_S\xc7\x08\x1e\x80\xd0\t\x11\xba\xa3]QP\x83\xceQ\x06\x1e{1\xa4\x0fM\xd8\xd3\\[\xb27\x82\xdd9*B\xb6b!\xd9\xe1\xee3\xca\xd0\xfe\x94\xa5`m\xb5\x19\xb0\x90\x17\xa77\xf4\xa98\xdc\xd0\xd7f\xd3o(\xd8#D\xcf\xa9\xed\x97\xb5\xfd\x88\xb17\xaa\x17SQX\x9eY<\x1fa\x8eO\xeb\x17\x99\x02\xe29\xdf\xb6\xd9\x7f\x1d$\x1c}J\xe0>\x83\xcdD\x14\x05p\xea\xf8N\xe3>\xf5R\xd3x\x86\xf6N0\xd5\xb7\r\xea7\xef\x05Q\xb1=\xc5\xc5\x01\rjA\x80kT\'\xe3B\xd7/uj\x87\xa1\x1cC\x17k:E\xed\xd5\x14\xd1\xc5\x89\x998\xeb\x80\tfTjox\x10A\x81\x0f\xc1=\xa8\xf4$\x86Y\r\x88\x96\xd0\xe5y\xe4s\xa9\x1a\xf1+\xe7\x87\xc6\xf5\xa2\xce8\xb3_\r\xe18\x87\xe3\xf00(\xa3w5y[\x19\xf6\x940\x95?M!\t\xfd\xd9\xc6&\xd8\x1dl"\xabf\x89\xd6A\x16$`\xed\x97\xaa\x16\x98\x9bT+\xb4\xb3)q\x12\xda?\x03\xdc\x83\xd8\x91\xba\xf0\xed5\x89\x8e!\x8c\xc9\xed\xe7\xc5\x00\x0f\xf2-\x8ef\x88\x0f6\xcb\xe6\xf1\xf3\xae\x85*\xc6\xe4<\t=\xb2\x8a\xafP3\x89\xed\xadM\x11\xb3\xdc\xe5r\x1f\xbd\x90\xc2\xd0\xb9O\x8b\x83\xd5-\x14\x8cm\xdd\x1a<\xc0\xf4\x85_\x9e\xab\xfa\x87\xb2N\x7f\xd8E\xc0\x98\xff9b\xbbn\xb5\x07F\x80\x05\xd3\xc5\x15\x86\xc6\x12\x17\x86\xc0(C\x85\xaa\x87i9\xdd5\xe4\x06[z\'\x8di\xa7n\x08)L\x9a\xa5r\xfb\x1de\x8a\\\x1b\x8b\x97Q\x91\xa6\x826Uz \xe5S>\xe3\xd3\xdd\x8e\xa6\xa3IU\xbfZ\x9eL%\xe4\xb4\x1a\x87\x942\x94\x9fp\xd4\xf0\x17\xe9\x118Q\xca\x1cva\xca\xbc\xc52\xb5\xdbk^U\x12^P\xa0\xe0DS=\xa0kR\xbe\xb5\xd3\xec\x13\x0f!E\xaf5\xc1\xfe5U\xf8\xcfj:\x88\x8dS\x93\x9b\xe9\xd6\x8e\xd6i\xa3\x97*o\x8a\xd2.\x0b\xf4\xfb\x86u\x9d\x08\x86\xe9\xb8\xbb_\xee\xa5RQ\xddQ\xed\xc4\x00\x93X\x88\x94X\xbe\xbf\x05\xa3L\xac(M\xa97\xce7\xe8*\xa2\xd2\xbc\xdd\xa4p\x8f)\xda\xe8\x04\xaa9\xc4f\\\x8a\xe88\x1b\xe6\xaf\xf4w\xb2t\xfb\x07\xd7\xda\x17\xb6\xfa\x01O\xc9l\x8a\xbb\x13\x11\xcf,\xf3\xf7\x84u4R\xe4X\xa0\xc7\xed\xefO\xe3\xed\xbf\xa2\xa9\xf2{\x02\xb0\x89~\x9bd\xfd\xdb\t\xeb\x9e\x0c\x12M\xb6\xd9\x8c6%\x1e\xf0\xb1"\xf9R\xa5\xf4\x93\xc9\x92\x91G\xdb\xd71 W\xcb\xfeTJ\x85\xdb_u\x87\x16Y\xf74\x1f\x11Ur6\x80\xa8\xe4oSG\xdc\xd7\x100\xcf\x11@\xb8\xba\xe8\x89\xd68a\x1c\x86I\x0e\x12#\x9c\t\xa2\xc2S*J\xce7\xfc\x85\xfa3\xcb\xb2Y\x8b\xfb\x89\xa5\xa6#\x9e\xb9\x83\xa2f\xa1q.\x16\xe7<\x92\xec:\xab\xc6G\x17\xae\x0f9\xab\xbc\xce\x97\xbf\x1f\xb9:\x94w\xe3\x9f\xe4";\xc7O\xa6\x02\x02\xd8\xc9\xc7\x9a\xca\x85\x9c\xea\x89\x0f)bD\xeb\xf7\x15\xd5h \x92\x00V\x05\\\\\xf3\n\x8e\xdbd\x9c\x08\xcb@%\x96-\xe2\xe1<\xc6~r\xb7\xf2\xd5\x1dH\x89\xc4\xcb\xcc\xa9\xa3\x98\xf4\xa6\xe0\xfc\xe1\xc7\xc5n\xf7\x1f\xba}\x17\t\xff\x07\\Iu\xb9z5u:\x18\x9c\xfc\xf6\x88\n\xb6?H\xb0\xf4\x94\xfa\x8dq\xcc9\x1d\xfcl7\xdc\xc0Bp|*\x14;R\xb1#\xc63\x03\xab\x81\x99\x9d\x82\x1f\xbd+u\xaf\xf9"\xd29\x84\xbc\xc1z\x07\xa0\x19\x05(\xc15\xb7\xe1\xe9\xda4\x03!\xdb\x1b\xfd\x87\xcd\x98\x91\xd7\xf0#>\xe6\xae\xfbe\x14\xec\xcd\x08\xc4\x87\xe6\xf8\xa5u\x05\xaa7*\xd5\xa77\xbe\x06\x80\x82\xe7v\x17\x8bG\xc7\xdd\xed\x88j>\x1fe\x14^\xffw\x15\xad6s\xb6\xec\x97\xc8\xe9\xe5m5+\xbd\x92\xb7\xa7\xcbe\xe4yt\x98F_\x10\x88\xf7_\x1f\xd3\xc0_D\xf4\xf8\x1b:z\xda\xe07;\xf5\x16\xc0?\xcc1P\x98\xfc\x0f\xe4\xbd\xb1T\xeaF\xab\xcd\x04&22\x14\xe8\x97\xd9\xb8\n\xe3\xf7\x00\xe7}\xc4\x89*\r\xd1M\x9e\xfb0\x92R\xa3\xc0<\xa3sB\xbb\xf7\x04\xf4h\x9d\x1b#\xa7T\x00;ulO\xc4P+\x8cJ|\xb4\x0fQ%\x07c\x8eDB\x92\xae/\xff\xb2\xc0\xb3\x92\xf1\xf3\x85\x87V\xaa\xcb\x1c\xcd\x13.#c\xde\xa4\x08+\xd5-\xa8\xa4\xf2\x06\xa7\xabTl-c\x05\x84\xf2\x0e\xa1\xa2\xafpQH\x015\x86A;\xa9\xd1\x93\xcf\xaaOJL\xbeez\xbb\xb2E\xcc\x14\x0e\xe0\xdd\x11\x0eJ\xd0X\x10G\xa0\x05\x96Y^$i\xcc\x7fi\xc4\xf8\x82\xa0\x99\n\xa5$W\x94\xea\xbdA\x06\xc0RL\xb6z\xe8\x1e\x8c\x99J\\\x00t\xf6\xdc\x8d\xd4\xeb\x9aa\xaf\xa4s7\xb9\x0e\xa9\x1b\x9e\x99 \xc1s\xed\x9d\x03\x1dj\x0e\x83\xd1\xe3\xe6\x81"\xb1+\x7f\xc1\xf9\xb8\x19\xf4\xcc\xc2\xe4\x15T\x8aM@\x87\x1ais\xb4n\x9b\xdc\x9f\x01"sN\xb4\xff\x17\xf4\t\x93mm\x1f"\xcaGKP\xa9B\x08\x17D=\x17\x0eTqv\xcc\x92S\x11YP\xcfN\xd4.\xac\xc0 \x8a\xaa&%\xde\x1e\x8b\x8e\xf2\x8e\xebw\xd9m1p\xbd\x89\xee\xf4\x8d\\\xc3\xc7\xd0\xa4:q}\x86\x8d8W-\xf3\xc0\x1b\xbd\xaf7\xb6C\xd0\x97\xa1`\x82\x16\xc4\x1d4.\xb1\xf1l1H%;\xcf\xc2\xe6\xa6H\x01\xf5c\x9b\xa7\xa0\x10\xa2\xa3\xb3\xc9I\xf5\x805F\xf3L\xa2\xa6e\x9e+\xc7XO\xbc\xc7\xf2\xad\xc9\xa3Wp\xb5\x0f\xaa\xf5\xa8)x\xb6\x0cPzz\xfb\x8a\x91\x98(\x8f\xda\xbd_Y\x17W\xb5\xf5\x96\x06\x92m&\xf1\xc3\xd6\xab~\xddv\xcd9\xdf\xb1\xbb\x9dM\x83\xe8\x8e\xa5\\C\xd7\xb1\xabMBi\x895dm\xc6\xebY\x8f\xbfi\x03\x94\\\xa7!W\x1e\xbd$t?\x83\xbe"i\xc7y\xc0\xe0i\x0eu\xb9\xe5\xc4_\x9bz\xaa\xc9\xe8\xca\xbbi\xfc8&\xe9\x96\xfc\xec\x1emb\xb0I\x9aCi{\xef@\xf0-\xc4%\xbb%\x1d\x05\x057,\xb5\x1cI\\~#\xfb\x1b\xb4y\'9A\x16\xb4\xf2o\xe5\th\x08)\x0c\xd77\xc0fy\xd7\xa0\xa9^*\'6\x13\xf2\n}\xf3\xc6cl!/\xb2-\xaf9\xdf[\x07\xdck\xd4d\x0c[\x9ciV\xd1\xb5\xa9\x14"\x114\xc6E>y\xdd\x05\x112\x98e\xe4\x90\xe5\xd0<\xcc\xac\x01\x16\xbek\xdb\x00\xc7\xbdm\xe7\xb7\xec\xbb1\x1fA\x15^4\xeaLi\x9a\x1e/\x9dB\x99#6Z\xc4b\xa4\x7f>\xb0\x13(L\xed\x0c\xa5&\x83\xd7\xeev\xfb\xa42?V\x13\xfd\xb7\xa4RQ\xdfG4\xd2\n\xa6\xfb\x97~\xb3\xed\x1d\x8bTfFFlL4~\x06\xa1\xd0\xa1(\x1f.\x1a\xc4\x8cH\xf0\xfd\xc0\xe5\xf4_BaC\x9a\xc2iHR\x9d+\x9a\xea\x1a\xf9\xab\xc7\xe8\xb5\x1e\x0e1M\x8b"\xce\xb9\xb3I\x972\xbe+\xec\xf1J\xc8\xef\xd1\xa1-\xa2\x1fV\xd8\x90\xf7{\xb6%\xa8\x81\xeaQZ\xea]T\x98 \xa4[\x1e\xb8\xca\x91s\x13i\x95Sto\x8b\xba\xa7\x91{H\xee>F\xcf\xa1\x03\xe5\xa8\xfb\x94\xa8\n\x05\xccO1\\\xcdH1\xb4\x87\xb9\xec\xea_\n;v\xbc Go\xc8~\xf7\x88\x16\x03]\xa7\xcdrx}[\xdc\xec\xa761\xba\xfb>\x8932\x94I\xc3\x97\xd0\t\xe8Z\xb1vo\xfe\xb9\xfc\x9b\xbeA\xde\xe8\xbb\x93\xc4\xb7\xb4\xb4\xa95\xbdET!4\xe0\x91S\xda8\xb3\x9b\xee!5\xf6c\x9e\x99\t\xd2pu\xe9\xfa\xda:,\xd0\xd06\x0c\xb6\xf5\x9a\xd2\xd4]\xea\xceW\x97(Z\x02\xa1\xad\xc0\xd6\x92\x8e\xb9\x1e\xd5Z+\xff\x95\x8f\x80\xd1.\x05\xef\x059\x9bD\x0e\x935\xd2\xb9\xf3\x0b\x16K)\xfdz\x8bT\xde\x91\xf9\xfe#@)7\x9b\x94\xbe1\xa5.G|\xf2d\x83\xd3\x19\xb9c\xc4\xbb\xda\xd1:Wy\x8c\xb8P\xc7\x97\xdb\xee\x15\xde\xb8\x9ckq\xc9\x7f\xc0\xeb\xe2\xa4Z\'\xeb{a\xcc4$\xf5T`\xaa\x89FB\xb4\x91\xde\x19\xf6q-\x0e\xf5\x80J\xf4\xd8\x91\x9a\x0e^p\x88lji\xd5\xd8\xcd<\xca\xb7\xdb\x14\xe8k\xa8|\xf2@\x94\x0b\xdf\x95Ja\xb4\xbe\xd6\x8fS\xac\x98I\xf7"\xd0\xfe\xdfQ\xa2g\xe7\xd4\xa3\xb1\xa4F\x1a"\xf4\xd2\xf5\xd8t\x07O\xf8\xca\xfdh5am@\xa4\x96\x9f\xaa#s\x98z\xe8\x8cW\xea\xf5\xa8\x91\xd0\t\xf6\xeb\xc5}tx\x87\xcb\xef\x86\x8d\x12P\xb7\x160\x92\xf9wO\xd5\xc6\xa1\x1aq\x8e\x18\x00\x00\x00\xd4o\xd5a\xa6\xa3\xb7]\x00\x01\x96\x06\xe1\r\x00\x00-a1\xe1\xb1\xc4g\xfb\x02\x00\x00\x00\x00\x04YZ' + + # read the compressed df_cie1931 dataframe + decompressed_df_cie1931 = io.BytesIO(lzma.decompress(compressed_df_cie1931)) + df_cie1931 = pd.read_pickle(decompressed_df_cie1931) + + # read the compressed df_intp_routes dataframe + decompressed_df_intp_routes = io.BytesIO(lzma.decompress(compressed_df_intp_routes)) + df_intp_routes = pd.read_pickle(decompressed_df_intp_routes) + + # Create the scatter plot using Altair + rect = alt.Chart( + df_cie1931, + width=alt.Step(3), + height=alt.Step(3) + ).transform_calculate( + rgb="rgb(datum.color)" + ).mark_rect(tooltip=True).encode( + x=alt.X('x:N').axis(None), + y=alt.Y('y:N').axis(None).scale(reverse=True), + fill=alt.Fill('color').scale(None), + stroke=alt.Stroke('color').scale(None), + tooltip=[alt.Tooltip('color:N'), alt.Tooltip('rgb:N')] + ) + + line = alt.Chart(df_intp_routes).mark_line( + tooltip=True, + interpolate='basis', + strokeWidth=3 + ).encode( + x='x:N', + y='y:N', + color=alt.Color('method') + .scale(range=['#44aa99', '#994455', '#117733']), + strokeDash=alt.StrokeDash('method'), + order=alt.Order('index'), + tooltip=[ + alt.Tooltip('color'), + alt.Tooltip('x'), + alt.Tooltip('y'), + alt.Tooltip('index') + ] + ) + + (rect + line).configure_view(stroke=None) + +You can see that all interpolation methods chose a different route +in the color space. We now also understand why there is a ``-long`` suffix +in the interpolation method ``hcl-long``. +The interpolation trajectory is indeed very ``long``. + +The default of Altair is ``hcl``, but dependening on your use-case +you can choose another interpolation method, +see :class:`ScaleInterpolateEnum` for a complete list of options. Color-blind viewers ^^^^^^^^^^^^^^^^^^^ @@ -293,14 +360,14 @@ also inclusive. :header-rows: 1 * - Example - * - **Friendly** diverging color scheme (`sunset` by Paul Ton, see section xx for more) + * - **Friendly** diverging color scheme (``redyellowblue``) .. altair-plot:: :remove-code: plot_scheme("redyellowblue", color_vision_deficiency, cvd=True, continuous=True) - * - **Unfriendly** diverging color scale + * - **Unfriendly** diverging color scale (``redyellowgreen``) .. altair-plot:: :remove-code: @@ -331,24 +398,38 @@ that are not only informative but also visually attractive. 'value': [28, 55] }) - intuitive = alt.Chart(source, height=alt.Step(80), width=200, title='intuitive').mark_bar().encode( - x=alt.X('value').axis(None), - y=alt.Y('land cover').title(None), - color=alt.Color('land cover').scale(range=['#55AA22', '#5566AA'], domain=['Land', 'Water']) + intuitive = alt.Chart( + source, + height=alt.Step(80), + width=200 + ).mark_bar().encode( + x=alt.X('value'), + y=alt.Y('land cover'), + color=alt.Color('land cover') + .scale(range=['#55AA22', '#5566AA'], + domain=['Land', 'Water']) + .legend(None) ) - intuitive.configure_view(stroke=None) + intuitive - .. altair-plot:: :hide-code: - non_intuitive = alt.Chart(source, height=alt.Step(80), width=200, title='non-intuitive').mark_bar().encode( - x=alt.X('value').axis(None), - y=alt.Y('land cover').title(None), - color=alt.Color('land cover').scale(range=['#5566AA', '#55AA22'], domain=['Land', 'Water']) + non_intuitive = alt.Chart( + source, + height=alt.Step(80), + width=200 + ).mark_bar().encode( + x=alt.X('value'), + y=alt.Y('land cover'), + color=alt.Color('land cover') + .scale(range=['#5566AA', '#55AA22'], + domain=['Land', 'Water']) + .legend(None) ) - non_intuitive.configure_view(stroke=None) + non_intuitive .. list-table:: From e66bdfd5d3e947182d9bda0eada9daf33f636033 Mon Sep 17 00:00:00 2001 From: mattijn Date: Wed, 26 Apr 2023 17:58:24 +0200 Subject: [PATCH 14/17] update text and styling --- doc/user_guide/scale_color.rst | 223 ++++++++++++++++++++------------- 1 file changed, 135 insertions(+), 88 deletions(-) diff --git a/doc/user_guide/scale_color.rst b/doc/user_guide/scale_color.rst index 243e1097d..f9e67a177 100644 --- a/doc/user_guide/scale_color.rst +++ b/doc/user_guide/scale_color.rst @@ -44,11 +44,11 @@ examples of their use. data = pd.DataFrame({"i": range(no_colors), "hex": colors_in_scheme}) # dynamic width - max_width = 450 + max_width = 400 w = max_width / no_colors # cornerRadius dependent on height-width ratio - cr_l = 0 if continuous else int(w / 2.4) + cr_l = 0 if continuous else 10 cr_s = 0 if continuous else 5 return { @@ -73,7 +73,8 @@ examples of their use. return color_scale - def chart_normal_vision(data, h, w, expr_hex, my_y_axis, cr_l): + def chart_normal_vision(data, h, w, expr_hex, my_y_axis, cr_l, no): + stroke_nv = alt.value('gray') if no < 25 else alt.Stroke("hex_raw:N").scale(None).legend(None) normal_vision = ( alt.Chart(data, height=h, width=alt.Step(w)) .transform_calculate( @@ -87,7 +88,7 @@ examples of their use. x=alt.X("i:O").axis(None), y=alt.Y("normal vision:O").axis(my_y_axis), fill=alt.Fill("hex_raw:O").scale(None).legend(None), - stroke=alt.Stroke("hex_raw:O").scale(None).legend(None), + stroke=stroke_nv, tooltip=[ alt.Tooltip("hex:O"), alt.Tooltip("rgb:O"), @@ -97,7 +98,8 @@ examples of their use. ) return normal_vision - def chart_green_blindness(data, h, w, expr_hex, my_y_axis, cr_s): + def chart_green_blindness(data, h, w, expr_hex, my_y_axis, cr_s, no): + stroke_gb = alt.value('gray') if no < 25 else alt.Stroke("greenblind_rgb:N").scale(None).legend(None) green_blindness = ( alt.Chart(data, height=h, width=alt.Step(w)) .transform_calculate( @@ -113,12 +115,13 @@ examples of their use. x=alt.X("i:O").axis(None), y=alt.Y("green-blindness:N").axis(my_y_axis), fill=alt.Fill("greenblind_rgb:N").scale(None).legend(None), - stroke=alt.Stroke("greenblind_rgb:N").scale(None).legend(None), + stroke=stroke_gb ) ) return green_blindness - def chart_red_blindness(data, h, w, expr_hex, my_y_axis, cr_s): + def chart_red_blindness(data, h, w, expr_hex, my_y_axis, cr_s, no): + stroke_rb = alt.value('gray') if no < 25 else alt.Stroke("redblind_rgb:N").scale(None).legend(None) red_blindness = ( alt.Chart(data, height=h, width=alt.Step(w)) .transform_calculate( @@ -134,12 +137,13 @@ examples of their use. x=alt.X("i:O").axis(None), y=alt.Y("red-blindness:N").axis(my_y_axis), fill=alt.Fill("redblind_rgb:N").scale(None).legend(None), - stroke=alt.Stroke("redblind_rgb:N").scale(None).legend(None), + stroke=stroke_rb ) ) return red_blindness - def chart_grayscale(data, h, w, expr_hex, my_y_axis, cr_s): + def chart_grayscale(data, h, w, expr_hex, my_y_axis, cr_s, no): + stroke_gs = alt.value('gray') if no < 25 else alt.Stroke("grayscale_rgb:N").scale(None).legend(None) grayscale = ( alt.Chart(data, height=h, width=alt.Step(w)) .transform_calculate( @@ -153,7 +157,7 @@ examples of their use. x=alt.X("i:O").axis(None), y=alt.Y("grayscale:N").axis(my_y_axis), fill=alt.Fill("grayscale_rgb:N").scale(None).legend(None), - stroke=alt.Stroke("grayscale_rgb:N").scale(None).legend(None), + stroke=stroke_gs, ) ) @@ -173,7 +177,7 @@ examples of their use. cr_s = chart_dict['cornerRadius_small'] no = chart_dict['no_colors'] - # define color scale + # define color scale and stroke color_scale = chart_color_scale(scheme_name, dict_schemes, continuous, custom, no, method) # define how hex-codes can be derived, from datasource or using color scale @@ -196,10 +200,10 @@ examples of their use. ) # determine color pallettes for different color deficiencies - normal_vision = chart_normal_vision(data, h_l, w, expr_hex, my_y_axis, cr_l) - green_blindness = chart_green_blindness(data, h_s, w, expr_hex, my_y_axis, cr_s) - red_blindness = chart_red_blindness(data, h_s, w, expr_hex, my_y_axis, cr_s) - monochrome = chart_grayscale(data, h_s, w, expr_hex, my_y_axis, cr_s) + normal_vision = chart_normal_vision(data, h_l, w, expr_hex, my_y_axis, cr_l, no) + green_blindness = chart_green_blindness(data, h_s, w, expr_hex, my_y_axis, cr_s, no) + red_blindness = chart_red_blindness(data, h_s, w, expr_hex, my_y_axis, cr_s, no) + monochrome = chart_grayscale(data, h_s, w, expr_hex, my_y_axis, cr_s, no) # determine which color pallettes to return if cvd and grayscale: @@ -261,18 +265,17 @@ Or like this (``interpolate='hcl'``): plot_scheme("two_color", percep_schemes, cvd=False, continuous=300, method='hcl') As you can observe, all three color ramps are different, -but all three color ramps start with ``blue`` and finsish with ``orange``. -So what is the correct interpolation method to go from ``blue`` to ``orange``? +but all three color ramps start with ``blue`` and finish with ``orange``. +So what is the correct interpolation method to go from ``blue`` to ``orange``? There is no single answer to this question. -It also based how we, as a human, see colors. +It also based how we see colors. To better understand the perception of colors to the human-eye, we can make use of a color space. A color space is a diagram to help -understand how we see perceive colors and how they are distributed as wavelengths. +understand how we see perceive colors and how they are distributed compare to each other. -Here we show the corresponding trajectories of the blue/orange color -gradient based on the three interpolation methods -(``rgb``, ``hcl``, ``hcl-long``) in a common used color space ('CIE 1931 color space'): +Here we show a common used color space ('CIE 1931 color space') from where we +can see that the human eye has more attenuation to the color green: .. altair-plot:: :hide-code: @@ -327,14 +330,19 @@ gradient based on the three interpolation methods (rect + line).configure_view(stroke=None) +In the color space are the trajectories of the blue/orange color +gradient overlayed based on the three interpolation methods +(``rgb``, ``hcl``, ``hcl-long``). You can see that all interpolation methods chose a different route -in the color space. We now also understand why there is a ``-long`` suffix +in the color space. The "``rgb``" trajectory a direct route. The +``hcl`` trajectory is choosing a route on the bottom part of the space. +We now also understand why there is a ``-long`` suffix in the interpolation method ``hcl-long``. The interpolation trajectory is indeed very ``long``. -The default of Altair is ``hcl``, but dependening on your use-case -you can choose another interpolation method, -see :class:`ScaleInterpolateEnum` for a complete list of options. +The default interpolation between colors within Altair is ``hcl``. +Dependening on your use-case you can choose another interpolation method, +for a complete list of interpolation options see :class:`ScaleInterpolateEnum`. Color-blind viewers ^^^^^^^^^^^^^^^^^^^ @@ -360,14 +368,18 @@ also inclusive. :header-rows: 1 * - Example - * - **Friendly** diverging color scheme (``redyellowblue``) + * - **More friendly** diverging color scheme (``redyellowblue``). + This color scheme avoids using green colors, and instead uses red, + yellow, and blue colors. .. altair-plot:: :remove-code: plot_scheme("redyellowblue", color_vision_deficiency, cvd=True, continuous=True) - * - **Unfriendly** diverging color scale (``redyellowgreen``) + * - **Unfriendly** diverging color scale (``redyellowgreen``). + The red and green colors are used to represent different ends of a spectrum, + but these two colors are not easy differentiated with some type of CVD. .. altair-plot:: :remove-code: @@ -390,7 +402,10 @@ that are not only informative but also visually attractive. * - Intuitive colors - Non-intuitive colors - * - .. altair-plot:: + * - Choose colors that are easy to understand by selecting ones that + naturally relate to your data. + + .. altair-plot:: :hide-code: source = pd.DataFrame({ @@ -413,7 +428,10 @@ that are not only informative but also visually attractive. intuitive - - .. altair-plot:: + - It is confusing and difficult to understand if you choose + colors which are non-intuive. Eg. green for water and blue for land. + + .. altair-plot:: :hide-code: non_intuitive = alt.Chart( @@ -439,7 +457,10 @@ that are not only informative but also visually attractive. * - Colors in moderation - Colors in excess - * - .. altair-plot:: + * - Try not to use too many colors. If you choose more colors, use them + thoughtfully to emphasize the most important parts of your visualization. + + .. altair-plot:: :hide-code: import altair as alt @@ -479,7 +500,11 @@ that are not only informative but also visually attractive. moderate - - .. altair-plot:: + - Using too many colors in graphs or charts can make them confusing + and hard to read, which can lead to misunderstandings. To make it + easy to understand, use colors carefully and don't use too many. + + .. altair-plot:: :hide-code: excess = ( @@ -504,7 +529,10 @@ that are not only informative but also visually attractive. * - Consistency color usage between plots - In-consistent color usage between plots - * - .. altair-plot:: + * - Using the same colors for the same data in charts or graphs helps + people understand the information better. + + .. altair-plot:: :hide-code: source = data.movies() @@ -558,10 +586,10 @@ that are not only informative but also visually attractive. ) equal - - - - - .. altair-plot:: + - Choosing different colors for the same data can lead + to misunderstandings or incorrect conclusions. + + .. altair-plot:: :hide-code: inequal = (line & bar).resolve_scale( @@ -578,7 +606,11 @@ that are not only informative but also visually attractive. * - Clarity in colors - Indistinct color usage - * - .. altair-plot:: + * - Use colors in order to make the data easier to read. If the items in the + visualiation can be easily distinguished leads to better understanding of + the data. + + .. altair-plot:: :hide-code: source = data.stocks() @@ -608,7 +640,10 @@ that are not only informative but also visually attractive. ).scale(range=col_range), ) - - .. altair-plot:: + - It is hard to understand the data correctly if you use colors + that make the items in your visualization unclear from each other. + + .. altair-plot:: :hide-code: import altair as alt @@ -633,7 +668,10 @@ that are not only informative but also visually attractive. * - Distinct classification of categories - No gradient colors for categories - * - .. altair-plot:: + * - Often categories are unique and distinct from each other. + Emphasize this by chosing colors that are clearly distinct from each other. + + .. altair-plot:: :hide-code: import altair as alt @@ -665,7 +703,10 @@ that are not only informative but also visually attractive. ).scale(range=col_range), ) - - .. altair-plot:: + - When unique categories are not distinguishable by their color usage, + it can cause confusion and misunderstandings. + + .. altair-plot:: :hide-code: source = data.stocks() @@ -687,7 +728,10 @@ that are not only informative but also visually attractive. * - Explain colors using a colorbar - No understanding of used colors - * - .. altair-plot:: + * - When you use colors in your visualization, it's important to explain + what each color means so that people can understand your message better. + + .. altair-plot:: :hide-code: import altair as alt @@ -707,7 +751,10 @@ that are not only informative but also visually attractive. ) ) - - .. altair-plot:: + - If you do not explain what your colors mean, people might be confused + and not understand your visualization properly. + + .. altair-plot:: :hide-code: import altair as alt @@ -775,7 +822,7 @@ range of values is continuous and unbroken. alt.vconcat( plot_scheme("blues", seqs_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("blues", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -787,7 +834,7 @@ range of values is continuous and unbroken. alt.vconcat( plot_scheme("tealblues", seqs_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("tealblues", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -799,7 +846,7 @@ range of values is continuous and unbroken. alt.vconcat( plot_scheme("teals", seqs_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("teals", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -811,7 +858,7 @@ range of values is continuous and unbroken. alt.vconcat( plot_scheme("greens", seqs_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("greens", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -823,7 +870,7 @@ range of values is continuous and unbroken. alt.vconcat( plot_scheme("browns", seqs_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("browns", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -835,7 +882,7 @@ range of values is continuous and unbroken. alt.vconcat( plot_scheme("greys", seqs_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("greys", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -847,7 +894,7 @@ range of values is continuous and unbroken. alt.vconcat( plot_scheme("purples", seqs_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("purples", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -859,7 +906,7 @@ range of values is continuous and unbroken. alt.vconcat( plot_scheme("warmgreys", seqs_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("warmgreys", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -871,7 +918,7 @@ range of values is continuous and unbroken. alt.vconcat( plot_scheme("reds", seqs_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("reds", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -883,7 +930,7 @@ range of values is continuous and unbroken. alt.vconcat( plot_scheme("oranges", seqs_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("oranges", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) Diverging Schemes ^^^^^^^^^^^^^^^^^ @@ -926,7 +973,7 @@ temperature anomalies or changes in sea level. alt.vconcat( plot_scheme("blueorange", divg_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("blueorange", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -938,7 +985,7 @@ temperature anomalies or changes in sea level. alt.vconcat( plot_scheme("brownbluegreen", divg_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("brownbluegreen", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -950,7 +997,7 @@ temperature anomalies or changes in sea level. alt.vconcat( plot_scheme("purplegreen", divg_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("purplegreen", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -962,7 +1009,7 @@ temperature anomalies or changes in sea level. alt.vconcat( plot_scheme("pinkyellowgreen", divg_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("pinkyellowgreen", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -974,7 +1021,7 @@ temperature anomalies or changes in sea level. alt.vconcat( plot_scheme("purpleorange", divg_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("purpleorange", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -986,7 +1033,7 @@ temperature anomalies or changes in sea level. alt.vconcat( plot_scheme("redblue", divg_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("redblue", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -998,7 +1045,7 @@ temperature anomalies or changes in sea level. alt.vconcat( plot_scheme("redgrey", divg_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("redgrey", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1010,7 +1057,7 @@ temperature anomalies or changes in sea level. alt.vconcat( plot_scheme("redyellowblue", divg_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("redyellowblue", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1022,7 +1069,7 @@ temperature anomalies or changes in sea level. alt.vconcat( plot_scheme("redyellowgreen", divg_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("redyellowgreen", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1034,7 +1081,7 @@ temperature anomalies or changes in sea level. alt.vconcat( plot_scheme("spectral", divg_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("spectral", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) Cyclical Schemes ^^^^^^^^^^^^^^^^ @@ -1067,7 +1114,7 @@ However, these schemes are not well suited to accurately convey value difference alt.vconcat( plot_scheme("rainbow", cycl_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("rainbow", cycl_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1079,7 +1126,7 @@ However, these schemes are not well suited to accurately convey value difference alt.vconcat( plot_scheme("sinebow", cycl_schemes, cvd=True, continuous=True, single=False, top=True), plot_scheme("sinebow", cycl_schemes, cvd=False, continuous=False, single=False, bottom=True) - ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent') + ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) Categorical Scales ^^^^^^^^^^^^^^^^^^ @@ -1122,7 +1169,7 @@ the types of flowers in a garden, or different political affiliations. - .. altair-plot:: :remove-code: - plot_scheme("accent", catg_schemes, cvd=True, continuous=False) + plot_scheme("accent", catg_schemes, cvd=True, continuous=False).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1131,7 +1178,7 @@ the types of flowers in a garden, or different political affiliations. - .. altair-plot:: :remove-code: - plot_scheme("category10", catg_schemes, cvd=True, continuous=False) + plot_scheme("category10", catg_schemes, cvd=True, continuous=False).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1140,7 +1187,7 @@ the types of flowers in a garden, or different political affiliations. - .. altair-plot:: :remove-code: - plot_scheme("category20", catg_schemes, cvd=True, continuous=False) + plot_scheme("category20", catg_schemes, cvd=True, continuous=False).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1149,7 +1196,7 @@ the types of flowers in a garden, or different political affiliations. - .. altair-plot:: :remove-code: - plot_scheme("category20b", catg_schemes, cvd=True, continuous=False) + plot_scheme("category20b", catg_schemes, cvd=True, continuous=False).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1158,7 +1205,7 @@ the types of flowers in a garden, or different political affiliations. - .. altair-plot:: :remove-code: - plot_scheme("category20c", catg_schemes, cvd=True, continuous=False) + plot_scheme("category20c", catg_schemes, cvd=True, continuous=False).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1167,7 +1214,7 @@ the types of flowers in a garden, or different political affiliations. - .. altair-plot:: :remove-code: - plot_scheme("dark2", catg_schemes, cvd=True, continuous=False) + plot_scheme("dark2", catg_schemes, cvd=True, continuous=False).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1176,7 +1223,7 @@ the types of flowers in a garden, or different political affiliations. - .. altair-plot:: :remove-code: - plot_scheme("paired", catg_schemes, cvd=True, continuous=False) + plot_scheme("paired", catg_schemes, cvd=True, continuous=False).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1185,7 +1232,7 @@ the types of flowers in a garden, or different political affiliations. - .. altair-plot:: :remove-code: - plot_scheme("pastel1", catg_schemes, cvd=True, continuous=False) + plot_scheme("pastel1", catg_schemes, cvd=True, continuous=False).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1194,7 +1241,7 @@ the types of flowers in a garden, or different political affiliations. - .. altair-plot:: :remove-code: - plot_scheme("pastel2", catg_schemes, cvd=True, continuous=False) + plot_scheme("pastel2", catg_schemes, cvd=True, continuous=False).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1203,7 +1250,7 @@ the types of flowers in a garden, or different political affiliations. - .. altair-plot:: :remove-code: - plot_scheme("set1", catg_schemes, cvd=True, continuous=False) + plot_scheme("set1", catg_schemes, cvd=True, continuous=False).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1212,7 +1259,7 @@ the types of flowers in a garden, or different political affiliations. - .. altair-plot:: :remove-code: - plot_scheme("set2", catg_schemes, cvd=True, continuous=False) + plot_scheme("set2", catg_schemes, cvd=True, continuous=False).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1221,7 +1268,7 @@ the types of flowers in a garden, or different political affiliations. - .. altair-plot:: :remove-code: - plot_scheme("set3", catg_schemes, cvd=True, continuous=False) + plot_scheme("set3", catg_schemes, cvd=True, continuous=False).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1230,7 +1277,7 @@ the types of flowers in a garden, or different political affiliations. - .. altair-plot:: :remove-code: - plot_scheme("tableau10", catg_schemes, cvd=True, continuous=False) + plot_scheme("tableau10", catg_schemes, cvd=True, continuous=False).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1239,7 +1286,7 @@ the types of flowers in a garden, or different political affiliations. - .. altair-plot:: :remove-code: - plot_scheme("tableau20", catg_schemes, cvd=True, continuous=False) + plot_scheme("tableau20", catg_schemes, cvd=True, continuous=False).configure_scale(rectBandPaddingInner=0.1) Using Tools for Color Selection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1286,7 +1333,7 @@ used in data visualization from other sources. - .. altair-plot:: :remove-code: - plot_scheme("tol_bright", tol_schemes, cvd=True, continuous=False, grayscale=True) + plot_scheme("tol_bright", tol_schemes, cvd=True, continuous=False, grayscale=True).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1295,7 +1342,7 @@ used in data visualization from other sources. - .. altair-plot:: :remove-code: - plot_scheme("tol_highcontrast", tol_schemes, cvd=True, continuous=False, grayscale=True) + plot_scheme("tol_highcontrast", tol_schemes, cvd=True, continuous=False, grayscale=True).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1304,7 +1351,7 @@ used in data visualization from other sources. - .. altair-plot:: :remove-code: - plot_scheme("tol_vibrant", tol_schemes, cvd=True, continuous=False, grayscale=True) + plot_scheme("tol_vibrant", tol_schemes, cvd=True, continuous=False, grayscale=True).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1313,7 +1360,7 @@ used in data visualization from other sources. - .. altair-plot:: :remove-code: - plot_scheme("tol_muted", tol_schemes, cvd=True, continuous=False, grayscale=True) + plot_scheme("tol_muted", tol_schemes, cvd=True, continuous=False, grayscale=True).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1322,7 +1369,7 @@ used in data visualization from other sources. - .. altair-plot:: :remove-code: - plot_scheme("tol_mediumcontrast", tol_schemes, cvd=True, continuous=False, grayscale=True) + plot_scheme("tol_mediumcontrast", tol_schemes, cvd=True, continuous=False, grayscale=True).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1331,7 +1378,7 @@ used in data visualization from other sources. - .. altair-plot:: :remove-code: - plot_scheme("tol_pale", tol_schemes, cvd=True, continuous=False, grayscale=True) + plot_scheme("tol_pale", tol_schemes, cvd=True, continuous=False, grayscale=True).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1340,7 +1387,7 @@ used in data visualization from other sources. - .. altair-plot:: :remove-code: - plot_scheme("tol_dark", tol_schemes, cvd=True, continuous=False, grayscale=True) + plot_scheme("tol_dark", tol_schemes, cvd=True, continuous=False, grayscale=True).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1349,7 +1396,7 @@ used in data visualization from other sources. - .. altair-plot:: :remove-code: - plot_scheme("tol_light", tol_schemes, cvd=True, continuous=False, grayscale=True) + plot_scheme("tol_light", tol_schemes, cvd=True, continuous=False, grayscale=True).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none @@ -1358,7 +1405,7 @@ used in data visualization from other sources. - .. altair-plot:: :remove-code: - plot_scheme("tol_light", tol_schemes, cvd=True, continuous=False, grayscale=True) + plot_scheme("tol_light", tol_schemes, cvd=True, continuous=False, grayscale=True).configure_scale(rectBandPaddingInner=0.1) * - .. code-block:: none From 180c9be14ad4b05f759257f9e61d87488a5de02a Mon Sep 17 00:00:00 2001 From: mattijn Date: Wed, 26 Apr 2023 21:10:20 +0200 Subject: [PATCH 15/17] textual changes --- doc/user_guide/scale_color.rst | 142 ++++++++++++++++++++++----------- 1 file changed, 96 insertions(+), 46 deletions(-) diff --git a/doc/user_guide/scale_color.rst b/doc/user_guide/scale_color.rst index f9e67a177..ab84037d8 100644 --- a/doc/user_guide/scale_color.rst +++ b/doc/user_guide/scale_color.rst @@ -2,8 +2,8 @@ .. _user-guide-color: -Color Scales -============ +Color usage +=========== Effective visualization of scientific data requires careful attention to color selection. Choosing the right colors can be a complex task, @@ -219,14 +219,14 @@ examples of their use. return chart_concat -The Basics of Color Selection +The basics of color selection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Color selection is an essential aspect of creating clear and informative visualizations of scientific data. To make the most effective choices, it's important to understand the basics of color perception and theory. -Color Perception and Theory +Color perception and theory ^^^^^^^^^^^^^^^^^^^^^^^^^^^ We will explain the basics using two colors, ``blue`` and ``orange``: @@ -773,15 +773,13 @@ that are not only informative but also visually attractive. ) -Types of Color Scales -~~~~~~~~~~~~~~~~~~~~~ +Color schemes +~~~~~~~~~~~~~ -This sections presents the three primary color scales used in data -visualization: sequential, diverging, and categorical. The section -includes examples of data that can be visualized using each scale and -popular color schemes used in practice. +This sections presents the three primary color schemes used in data +visualization: sequential, diverging, and categorical. -Sequential Schemes +Sequential schemes ^^^^^^^^^^^^^^^^^^ Sequential schemes are best suited for data that has an inherent ordering, @@ -932,7 +930,7 @@ range of values is continuous and unbroken. plot_scheme("oranges", seqs_schemes, cvd=False, continuous=False, single=False, bottom=True) ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) -Diverging Schemes +Diverging schemes ^^^^^^^^^^^^^^^^^ Diverging schemes are best suited for data that varies above and below a @@ -1083,7 +1081,7 @@ temperature anomalies or changes in sea level. plot_scheme("spectral", divg_schemes, cvd=False, continuous=False, single=False, bottom=True) ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) -Cyclical Schemes +Cyclical schemes ^^^^^^^^^^^^^^^^ Cyclical color schemes may be used to highlight periodic patterns in continuous data. @@ -1128,10 +1126,10 @@ However, these schemes are not well suited to accurately convey value difference plot_scheme("sinebow", cycl_schemes, cvd=False, continuous=False, single=False, bottom=True) ).configure_concat(spacing=1).configure_view(stroke=None).resolve_scale(color='independent').configure_scale(rectBandPaddingInner=0.1) -Categorical Scales -^^^^^^^^^^^^^^^^^^ +Categorical schemes +^^^^^^^^^^^^^^^^^^^ -Categorical scales are best suited for data that is unordered, such as +Categorical schemes are best suited for data that is unordered, such as different species or categories. Examples of categorical data include the types of flowers in a garden, or different political affiliations. @@ -1288,18 +1286,19 @@ the types of flowers in a garden, or different political affiliations. plot_scheme("tableau20", catg_schemes, cvd=True, continuous=False).configure_scale(rectBandPaddingInner=0.1) -Using Tools for Color Selection -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Pre-Designed Color Palettes -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Example color palettes from the other packages ----------------------------------------------- +Palettes from other sources +~~~~~~~~~~~~~~~~~~~~~~~~~~~ This subsection focuses on pre-designed color palettes that can be used in data visualization from other sources. +Here some color schemes from Paul Tol, https://personal.sron.nl/~pault/ which are picked as being: + +- distinct for all people, including colour-blind readers +- distinct from black and white +- distinct on screen and paper +- matching well together + .. altair-plot:: :output: none @@ -1443,33 +1442,84 @@ used in data visualization from other sources. plot_scheme("tol_incandescent", tol_schemes, cvd=True, continuous=True, grayscale=True) -Modifying Color Scales -^^^^^^^^^^^^^^^^^^^^^^ +Expressions and colors +~~~~~~~~~~~~~~~~~~~~~~ + +We can make use of the measured values regarding colors in a visualization. +In the following example we will make use of the ``lumniance`` to detect if +the color of a text overlay on top of a bar should be colored ``black`` or +``white``. The luminance describes the brightnes, normalized to 0 for darkest +black and 1 for lightest white. + +In the following chart we have a sorted bar chart where the text overlay describes +the sum of yield as is expressed on the ``x`` encoding channel. + +.. altair-plot:: + + import altair as alt + from vega_datasets import data + + source = data.barley() + + base = alt.Chart(source).encode( + x=alt.X('sum(yield):Q').stack('zero'), + y=alt.Y('site:O').sort('-x'), + text=alt.Text('sum(yield):Q', format='.0f') + ) -Tools for adjusting brightness, saturation, and hue ---------------------------------------------------- + bars = base.mark_bar().encode(color='sum(yield):Q') + text = base.mark_text(align='right', dx=-3, color='black') + + bars + text + +As you can see, when the bar becomes darker, the text overlay becomes less visible. + +At this moment we can make use of the measured luminance to decide if the text overlay +should be colored ``black`` or ``white``. + +.. altair-plot:: + + import altair as alt + from vega_datasets import data + + source = data.barley() + + base = alt.Chart(source).encode( + x=alt.X('sum(yield):Q').stack('zero'), + y=alt.Y('site:O').sort('-x'), + text=alt.Text('sum(yield):Q', format='.0f') + ) + + bars = base.mark_bar( + tooltip=alt.expr("luminance(scale('color', datum.sum_yield))") + ).encode( + color='sum(yield):Q' + ) + + text = base.mark_text( + align='right', + dx=-3, + color=alt.expr("luminance(scale('color', datum.sum_yield)) > 0.5 ? 'black' : 'white'") + ) -This subsection provides an overview of the different ways in which -brightness, saturation, and hue can be modified to create custom color -scales. It explains how changing these attributes can help to emphasize -or de-emphasize certain aspects of the data being visualized. + bars + text -Customizing color scales to match specific data requirements ------------------------------------------------------------- +The lighter the bar, the higher the luminance. If the bar is light +we like a text overlay that is black. The darker the bar, the lower +the luminance. If the bar is dark, we like a text overlay that is white. -This subsection explores the idea of creating custom color scales that -are tailored to the specific needs of the data being visualized. It -discusses how to use the tools introduced in xx to create color -scales that are better suited to representing the nuances of the data, -and provides examples of how this can be achieved in practice. +In the expression above we have written this as a predicate. The text +appear ``black`` if the luminance is above ``0.5`` and ``white`` when +the luminance is below ``0.5``. The luminance is computed using the +``'color'`` scale in combination with the interal computed data field +``datum.sum_yield``. You can inspect the luminance through the tooltip +by hovering the bars. Conclusion ~~~~~~~~~~ -By utilizing the tools and resources available for color selection, -data visualizers can create effective and visually appealing -presentations that accurately convey scientific data. Whether using -pre-designed color schemes or customizing color scales, it is -essential to consider factors such as accessibility, aesthetics, -and perceptual relationships in order to create the most effective -visualizations possible. \ No newline at end of file +If you use the right colors when making visualizations with scientific +data, you can make them look good and easy to understand. You can use +pre-designed color schemes or choose your own, but make sure they look +good, are easy to see and make sense with the information you're +presenting. \ No newline at end of file From 2f7d07b1ccfc266d2cde8b3b2f14bb1fe86c4f29 Mon Sep 17 00:00:00 2001 From: mattijn Date: Wed, 26 Apr 2023 21:11:08 +0200 Subject: [PATCH 16/17] rename file --- doc/user_guide/{scale_color.rst => color_usage.rst} | 0 doc/user_guide/data.rst | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename doc/user_guide/{scale_color.rst => color_usage.rst} (100%) diff --git a/doc/user_guide/scale_color.rst b/doc/user_guide/color_usage.rst similarity index 100% rename from doc/user_guide/scale_color.rst rename to doc/user_guide/color_usage.rst diff --git a/doc/user_guide/data.rst b/doc/user_guide/data.rst index 360bd91c1..7c258ef6f 100644 --- a/doc/user_guide/data.rst +++ b/doc/user_guide/data.rst @@ -620,7 +620,7 @@ data before usage in Altair using GeoPandas for example as such: encodings/index marks/index transform/index - scale_color + color_usage interactions compound_charts scale_resolve From 9817a4606d9a108be405ff9f5b99ebad5ef32199 Mon Sep 17 00:00:00 2001 From: mattijn Date: Wed, 26 Apr 2023 21:16:44 +0200 Subject: [PATCH 17/17] capitalize usage --- doc/user_guide/color_usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user_guide/color_usage.rst b/doc/user_guide/color_usage.rst index ab84037d8..b4763bcf2 100644 --- a/doc/user_guide/color_usage.rst +++ b/doc/user_guide/color_usage.rst @@ -2,7 +2,7 @@ .. _user-guide-color: -Color usage +Color Usage =========== Effective visualization of scientific data requires careful attention