Edit Page

Chromatic Scales

Chromatic Scales are scales that output colors. The ScalesChromatic object offers somes factories for creating pre-configured color scales:

Scales factory
ScalesChromatic.*
TypeSubtypeExample of use
Continuous.*
linearRGB...
Continuous Create your own linear color scale
Discrete.*
dark8, pale12...
Discrete Display distinct categories with no specific order
Sequential.SingleHue.*
blues, greens, reds...
SequentialSingle hueShow continuous data on a single-color scheme
Sequential.MultiHue.*
viridis, plasma...
SequentialMulti hueShow continuous data on multi-color scheme (better for large domains)
Sequential.Diverging.*
spectral, red_blue...
SequentialDivergingHighlight divergence of continuous data (temperatures...)
Sequential.Cyclical.*
rainbow...
SequentialCyclicalGood for radial visuals as
start color == end color

Scales are managed in there own module. You have to import the dependency inside your project (io.data2viz.scale) and in the import directive in your code.

Continuous scales

The ScalesChromatic.Continuous.* object contains factories to create linear color scales. These scales already contains a color interpolator so you just need to set the domain and the range, as you would do with a numeric linear scale.

There are several color interpolators available:

  • defaultRGB: simple RGB interpolator
  • linearRGB: better results on bright colors than the previous one
  • linearLAB: interpolator in the LAB color space
  • linearHSL, linearHCL: interpolators in HSL and HCL color spaces
  • linearHSLLong, linearHCLLong: same as above, but if your colors are separated with more than 180° on the chromatic wheel these interpolators will take the "longest way" from one color to another

Learn more about the bias of "default RGB interpolation" in this video.

import io.data2viz.color.* import io.data2viz.scale.* import io.data2viz.math.* import io.data2viz.geom.* import io.data2viz.viz.* fun main() { //sampleStart val valuesDomain = listOf(.0, 100.0) val colorRange = listOf("#33A7D8".col, "#FECE3E".col) // scale with linear interpolation in 2 color spaces : RGB & HCL val scaleRGB = ScalesChromatic.Continuous.linearRGB { domain = valuesDomain range = colorRange } val scaleHCL = ScalesChromatic.Continuous.linearHCL { domain = valuesDomain range = colorRange } viz { size = size(800, 50) (0..100).forEach { rect { x = 10 + it * 5.0 size = size(5, 19) fill = scaleRGB(it) } rect { x = 10 + it * 5.0 y = 31.0 size = size(5, 19) fill = scaleHCL(it) } if (it % 10 == 0) { text { x = 10 + it * 5.0 y = 26.0 textAlign = textAlign(TextHAlign.MIDDLE, TextVAlign.MIDDLE) textContent = "$it" } } } }.bindRendererOnNewCanvas() //sampleEnd }

Discrete scales

Discrete scales are pre-configured color scales map colors to different categories. To mark the difference between comparable objects (where we would use gradients of colors) these scales propose some color schemes with very distinct colors.

You can create your own category color scale using the Scales.Discrete.* object, but the ScalesChromatic.Discrete factory will offer some ready to use color scales.

The factories names finish with a number indicating the total colors in the range of the scale, ScalesChromatic.Discrete.accent8 for example contains 8 distinct colors.

The colors in these scales are mostly derived from Cynthia A. Brewer’s ColorBrewer.

import io.data2viz.color.* import io.data2viz.scale.* import io.data2viz.math.* import io.data2viz.geom.* import io.data2viz.viz.* fun main() { //sampleStart val days = listOf("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday") // this scale map names of the days (as String) to colors val scale = ScalesChromatic.Discrete.category10<String> { domain = days } viz { size = size(800, 50) days.forEachIndexed { index, dayName -> text { x = 30 + index * 70.0 y = 25.0 textColor = scale(dayName) textAlign = textAlign(TextHAlign.MIDDLE, TextVAlign.MIDDLE) textContent = "$dayName" } } }.bindRendererOnNewCanvas() //sampleEnd }

Sequential scales

Sequential scales are pre-configured scales, the color scheme is already defined so the range is fixed.

These scales are distributed among 4 categories in ScalesChromatic.Sequential.*

Single hue scales

Use ScalesChromatic.Sequential.SingleHue.* to create a new single hue scale.

import io.data2viz.color.* import io.data2viz.scale.* import io.data2viz.math.* import io.data2viz.geom.* import io.data2viz.viz.* import io.data2viz.color.Colors.Web.white import io.data2viz.color.Colors.Web.black fun main() { //sampleStart // for a sequential scale, the range is already defined val scale = ScalesChromatic.Sequential.SingleHue.purples { domain = StrictlyContinuous(0.0, 40.0) } viz { size = size(800, 50) (0..40).forEach { val color = scale(it.toDouble()) rect { x = it * 17.0 size = size(16, 50) fill = color } text { x = 8 + it * 17.0 y = 25.0 textColor = if (color.luminance() > 50.pct) black else white textAlign = textAlign(TextHAlign.MIDDLE, TextVAlign.MIDDLE) textContent = "$it" } } }.bindRendererOnNewCanvas() //sampleEnd }

Multi hue scales

Use ScalesChromatic.Sequential.MultiHue.* to create a new multi hue scale.

import io.data2viz.color.* import io.data2viz.scale.* import io.data2viz.math.* import io.data2viz.geom.* import io.data2viz.viz.* import io.data2viz.color.Colors.Web.white import io.data2viz.color.Colors.Web.black fun main() { //sampleStart // for a sequential scale, the range is already defined val scale = ScalesChromatic.Sequential.MultiHue.viridis { domain = StrictlyContinuous(0.0, 40.0) } viz { size = size(800, 50) (0..40).forEach { val color = scale(it.toDouble()) rect { x = it * 17.0 size = size(16, 50) fill = color } text { x = 8 + it * 17.0 y = 25.0 textColor = if (color.luminance() > 50.pct) black else white textAlign = textAlign(TextHAlign.MIDDLE, TextVAlign.MIDDLE) textContent = "$it" } } }.bindRendererOnNewCanvas() //sampleEnd }

Diverging scales

Use ScalesChromatic.Sequential.Diverging.* to create a new diverging scale.

import io.data2viz.color.* import io.data2viz.scale.* import io.data2viz.math.* import io.data2viz.geom.* import io.data2viz.viz.* import io.data2viz.color.Colors.Web.white import io.data2viz.color.Colors.Web.black fun main() { //sampleStart // note the inverted domain to bind blue to -15 and red to +15 val scale = ScalesChromatic.Sequential.Diverging.red_yelow_blue() { domain = StrictlyContinuous(15.0, -15.0) } viz { size = Size(800.0, 50.0) (-15..15).forEach { val color = scale(it.toDouble()) rect { x = (it + 15) * 21.0 size = size(20, 50) fill = color } text { x = 10 + (it + 15) * 21.0 y = 25.0 textColor = if (color.luminance() > 50.pct) black else white textAlign = textAlign(TextHAlign.MIDDLE, TextVAlign.MIDDLE) textContent = "$it" } } }.bindRendererOnNewCanvas() //sampleEnd }

Cyclical scales

Use ScalesChromatic.Sequential.Cyclical.* to create a new diverging scale.

Cyclical color scales obviously do not clamp data, they just cycle over when you scale a value which is outside the domain.

import io.data2viz.color.* import io.data2viz.math.* import io.data2viz.scale.* import io.data2viz.geom.* import io.data2viz.viz.* fun main() { //sampleStart // on this cyclical scale: scale(0) == scale(360) == scale(720)... val scale = ScalesChromatic.Sequential.Cyclical.sineBow { domain = StrictlyContinuous(.0, 360.0) } viz { size = size(800, 200) (0..360).forEach { line { val angle = it.deg x1 = 300.0 + 60 * angle.cos x2 = 300.0 + 100 * angle.cos y1 = 100.0 + 60 * angle.sin y2 = 100.0 + 100 * angle.sin stroke = scale(it.toDouble()) strokeWidth = 3.0 } } }.bindRendererOnNewCanvas() //sampleEnd }