Working with Color
import genstudio.plot as Plot
# ## Color Schemes
#
# ### Using a built-in color scheme
#
# You can use a [built-in color scheme](https://observablehq.com/@d3/color-schemes) from D3.js by specifying the [scheme name](bylight?match="Viridis") in the `color` option:
(
Plot.cell(range(20), {"x": Plot.identity, "fill": Plot.identity, "inset": -0.5})
+ {"color": {"type": "linear", "scheme": "Viridis"}}
+ Plot.height(50)
)
Here, the x
and fill
channels both use Plot.identity to use the corresponding value in the provided range.
Custom color interpolation¶
You can also create custom color scales by specifying a range and an interpolation function:
(
Plot.cell(range(20), {"x": Plot.identity, "fill": Plot.identity, "inset": -0.5})
+ {
"color": {
"range": ["blue", "red"],
"interpolate": Plot.js("(start, end) => d3.interpolateHsl(start, end)"),
}
}
+ Plot.height(50)
)
In this example:
"range"
specifies the start and end colors for the scale (blue to red)."interpolate"
defines how to transition between these colors, using D3's HSL interpolation. This results in a smooth color gradient from blue to red across the cells.
Using D3 color scales directly¶
GenStudio allows you to use D3 color scales directly in your plots:
(
Plot.cell(
range(10),
{"x": Plot.identity, "fill": Plot.js("(d) => d3.interpolateSpectral(d/10)")},
)
+ Plot.height(50)
)
Using colorMap and colorLegend¶
Plot.colorMap(...) assigns specific colors to categories, while Plot.colorLegend() adds a color legend to your plot. In the following example, we create a dot plot with categorical data. The fill channel determines the color of each dot based on its category.
categorical_data = [
{"category": "A", "value": 10},
{"category": "B", "value": 20},
{"category": "C", "value": 15},
{"category": "D", "value": 25},
]
(
Plot.dot(
categorical_data, {"x": "value", "y": "category", "fill": "category"}, r=10
)
+ Plot.colorMap({"A": "red", "B": "blue", "C": "green", "D": "orange"})
+ Plot.colorLegend()
)
Applying a constant color to an entire mark¶
When specifying colors for marks, there's an important distinction to be aware of:
Direct color specification: Use a string to specify a color directly. This will set the color for all points in the mark, but it's not possible to label the color in a legend. Example:
{"fill": "red"}
or{"fill": "#FF0000"}
Categorical color assignment: For automatic color assignment based on categories, use a function (or string, for property access) that returns a value for each data point. For a constant category across all data points, use Plot.constantly(...). Example:
{"fill": Plot.constantly("Category A")}
.
Plot.constantly
returns a function that always returns the same value, regardless of its input. When used as a channel specifier (like for fill
or stroke
), it assigns a single categorical value to the entire mark.
Categorical color assignment has the advantage that we can use it with Plot.colorMap(...) to assign specific colors to categories, and Plot.colorLegend() to display the color mappings.
import random
(
Plot.line(
[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]],
{"stroke": Plot.constantly("Walls")},
)
+ Plot.ellipse([[5, 5, 1]], {"fill": Plot.constantly("Target")})
+ Plot.ellipse(
[(random.uniform(0, 10), random.uniform(0, 10)) for _ in range(20)],
{"fill": Plot.constantly("Guesses"), "r": 0.5, "opacity": 0.2},
)
+ Plot.colorMap({"Walls": "black", "Target": "blue", "Guesses": "purple"})
+ Plot.colorLegend()
+ {"width": 400, "height": 400, "aspectRatio": 1}
)
Using RGB(A) Colors¶
You can specify colors using RGB or RGBA values by specifying color channel value as CSS rgb/rgba syntax: "rgb(255, 0, 0)"
or "rgba(255, 0, 0, 0.5)"
. For example:
x = [0, 1, 2, 3, 4]
y = [0, 1, 2, 3, 4]
colors = [
(255, 0, 0), # Red
(0, 255, 0), # Green
(0, 0, 255), # Blue
(255, 255, 0), # Yellow
(255, 0, 255), # Magenta
]
Plot.dot({"x": x, "y": y, "fill": [f"rgb({r}, {g}, {b})" for (r, g, b) in colors]})