Colors and gradients
Colors are used everywhere in data visualizations and -as a way of displaying data- they need to
be chosen with care to ensure that the perception of the color carry the right information.
Data2viz provides a lot of helpful functions to create several colors and gradients and still maintain
a high level of readability and accessibility in visuals.
In data2viz, colors are managed in their own module:
- import the dependency inside your project (
io.data2viz.color
)
- add the import directive in your code (
import io.data2viz.color.*
)
Color creation
The Colors
object provides several ways to retrieve colors and gradients.
Web colors
The first option is to use a named color. All CSS colors are accessible as references through
the Colors.Web
object.
For example, Colors.Web.darkturquoise
returns a reference
on the dark turquoise html color. As colors are immutable objects, it's then possible to
reuse and pass references of these named colors.
import io.data2viz.color.*
import io.data2viz.geom.*
import io.data2viz.math.*
import io.data2viz.viz.*
fun main() {
//sampleStart
viz {
rect {
size = size(50, 50)
fill = Colors.Web.blueviolet
}
}.bindRendererOnNewCanvas() //sampleEnd
}
Hex colors
Another usual option to create a color is through its hexadecimal code. There are
2 extension vals that simplify the creation of a color from Int and String.
import io.data2viz.color.*
import io.data2viz.geom.*
import io.data2viz.math.*
import io.data2viz.viz.*
fun main() {
//sampleStart
viz {
rect {
size = size(50, 50)
fill = 0x87ceeb.col // <- Int extension val
}
rect {
x = 50.0
size = size(50, 50)
fill = "#800080".col // <- String extension val
}
}.bindRendererOnNewCanvas() //sampleEnd
}
Color spaces
You can also use the values from 0 to 255 of the RGB channels to create a color calling
Colors.rgb
.
Beside RGB, data2viz allows the use of different color spaces to create colors:
- HSL (Hue, Saturation, Lightness),
- HCL (Hue, Chroma, Lightness) and
- LAB (also known as CIE Lab).
For each of them a factory function is available in Colors
, taking
the transparency alpha as a last parameter with a d
import io.data2viz.color.*
import io.data2viz.geom.*
import io.data2viz.math.*
import io.data2viz.viz.*
fun main() {
//sampleStart
viz {
size = size(600, 50)
rect { // pure red fill
size = size(50, 50)
fill = Colors.rgb(255, 0, 0)
}
rect { // filled with 50% transparency
x = 50.0
size = size(50, 50)
fill = Colors.rgb(255, 0, 0, 50.pct)
}
rect { // fill defined in HSL color space
x = 100.0
size = size(50, 50)
fill = Colors.hsl(38.82.deg, 100.pct, 50.pct)
}
rect { // fill defined in HCL color space
x = 150.0
size = size(50, 50)
fill = Colors.hcl(167.95.deg, 46.55, 92.03.pct)
}
rect { // fill defined in LAB color space
x = 200.0
size = size(50, 50)
fill = Colors.lab(30.83.pct, 26.05, -42.08)
}
}.bindRendererOnNewCanvas() //sampleEnd
}
Color manipulation
Data2viz provides several functions to manipulate colors based on color perception.
Luminance & Contrast
Some color spaces like LAB or LCH use a parameter to determine the "lightness" of a color, but the
hue impacts the lightness we perceived from it (the luminance).
For example, blue and yellow seems to have very different brightness even if these 2 colors are created using
the same "lightness" parameter in HSL.
The luminance()
function returns the perceived lightness of a given color.
The contrast we perceive is tightly bound to the luminance of 2 given colors. The contrast()
computes the perceived contrast ratio of 2 colors.
Check the WCAG for more info about contrast and readability.
import io.data2viz.viz.*
import io.data2viz.color.*
import io.data2viz.geom.*
import io.data2viz.math.*
import io.data2viz.color.Colors.Web.white
import io.data2viz.color.Colors.Web.black
fun main() {
viz {
size = size(800, 250)
//sampleStart
(0 until 360 step 30).forEach {
val angle = it.deg
val position = point(250 + angle.cos * 100, 125 + angle.sin * 100)
val color = Colors.hsl(angle, 100.pct, 50.pct)
circle { // draw a circle with "pure-color"
fill = color
radius = 25.0
x = position.x
y = position.y
}
circle { // draw a circle with the desaturated color
fill = color.desaturate(10.0)
radius = 25.0
x = position.x + 270
y = position.y
}
text { // indicate the perceived lightness of the color
x = position.x
y = position.y
textColor = if (color.luminance() > 50.pct) black else white
textContent = "${(color.luminance().value*100).toInt()}%"
textAlign = textAlign(TextHAlign.MIDDLE, TextVAlign.MIDDLE)
}
} //sampleEnd
text {
x = 250.0
y = 125.0
textAlign = textAlign(TextHAlign.MIDDLE, TextVAlign.MIDDLE)
textContent = "LUMINANCE"
}
text {
x = 520.0
y = 125.0
textAlign = textAlign(TextHAlign.MIDDLE, TextVAlign.MIDDLE)
textContent = "DESATURATED COLORS"
}
}.bindRendererOnNewCanvas()
}
Change brightness
brighten()
and darken()
functions allow to easily change the brightness of a given color.
brighten(x)
is equivalent to darken(-x)
import io.data2viz.viz.*
import io.data2viz.color.*
import io.data2viz.geom.*
import io.data2viz.math.*
fun main() {
//sampleStart
viz {
size = size(600, 50)
val myColor = Colors.hsl(260.deg, 100.pct, 10.pct)
(0..10).forEach {
rect {
x = it * 50.0
size = size(50, 50)
fill = myColor.brighten(it / 2.0)
}
}
}.bindRendererOnNewCanvas() //sampleEnd
}
Change saturation
The saturate()
and desaturate()
functions change the saturation of a given color.
saturate(x)
is equivalent to desaturate(-x)
import io.data2viz.viz.*
import io.data2viz.color.*
import io.data2viz.geom.*
import io.data2viz.math.*
fun main() {
//sampleStart
viz {
size = size(600, 50)
val myColor = Colors.hsl(240.deg, 100.pct, 50.pct)
(0..7).forEach {
rect {
x = it * 50.0
size = size(50, 50)
fill = myColor.desaturate(it.toDouble())
}
}
}.bindRendererOnNewCanvas() //sampleEnd
}
Gradients
Most of the data2viz visual elements accepts a ColorOrGradient
object for its fill color.
A gradient is defined by giving at least 2 ColorStop
, each corresponding to a color and its position along
the gradient (in percentage).
Gradient positioning (as defined by its ColorStop
) is absolute, not relative to the positions of the shapes using it.
Linear gradient
A Linear gradient is created using the Colors.Gradient.linear()
builder.
start
: starting point of the gradient
end
: ending point of the gradient
Next you call withColor()
given a Color and a percentage to set the base color then add any number of ColorStop
using andColor()
.
import io.data2viz.color.*
import io.data2viz.geom.*
import io.data2viz.math.*
import io.data2viz.viz.*
fun main() {
//sampleStart
viz {
size = size(800, 100)
val linearGradient = Colors.Gradient.linear(point(0, 0), point(800, 0))
.withColor(Colors.Web.hotpink, 20.pct) // under 20% color is "hot pink"
.andColor(Colors.Web.blueviolet, 50.pct) // middle (50%) is "blue violet"
.andColor(Colors.Web.skyblue, 80.pct) // from 80% color is "sky blue"
// we set stroke & strokewidth here, and use style delegation to pass it down
stroke = linearGradient
strokeWidth = 30.0
line {
x1 = 100.0
y1 = 18.0
x2 = 500.0
y2 = 18.0
}
line {
x1 = 300.0
y1 = 50.0
x2 = 700.0
y2 = 50.0
}
line {
x1 = .0
y1 = 82.0
x2 = 800.0
y2 = 82.0
}
}.bindRendererOnNewCanvas() //sampleEnd
}
Radial gradient
If you want to paint a shape using a radial gradient, use the Colors.Gradient.radial()
builder.
center
: starting point of the gradient
radius
: radius of the gradient
Next you call withColor()
given a Color and a percentage to set the base color then add any number of ColorStop
using andColor()
.
import io.data2viz.color.*
import io.data2viz.geom.*
import io.data2viz.math.*
import io.data2viz.viz.*
fun main() {
//sampleStart
val radialGradient = Colors.Gradient.radial(point(400, 100), 100.0)
.withColor(Colors.Web.hotpink, 0.pct) // gradient center is "hot pink"
.andColor(Colors.Web.skyblue, 100.pct) // gradient end "is sky blue"
viz {
size = size(800, 200)
circle {
x = 400.0
y = 100.0
radius = 100.0
fill = radialGradient
}
}.bindRendererOnNewCanvas() //sampleEnd
}