From 1dd6ea2c08bfd1400781ed6d35ffee78cdd20e58 Mon Sep 17 00:00:00 2001 From: Chris Larsen Date: Fri, 13 Jan 2023 04:39:03 -0800 Subject: [PATCH] update Palette to allow all colors to be accessed (#1509) Convert the Palette to require an ArraySeq of colors. Rename the parameter and implement colors() to avoid code changing in other places. This allows the full set of colors to be accessed directly for use with things like heatmaps. --- .../netflix/atlas/chart/model/Palette.scala | 65 +++++++++++-------- .../atlas/chart/model/PaletteSuite.scala | 7 ++ 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/atlas-chart/src/main/scala/com/netflix/atlas/chart/model/Palette.scala b/atlas-chart/src/main/scala/com/netflix/atlas/chart/model/Palette.scala index 4b179a463..7af016768 100644 --- a/atlas-chart/src/main/scala/com/netflix/atlas/chart/model/Palette.scala +++ b/atlas-chart/src/main/scala/com/netflix/atlas/chart/model/Palette.scala @@ -18,43 +18,39 @@ package com.netflix.atlas.chart.model import java.awt.Color import java.io.FileNotFoundException import java.util.concurrent.ConcurrentHashMap - import com.netflix.atlas.chart.Colors import com.netflix.atlas.core.util.Strings -case class Palette(name: String, colors: Int => Color) { +import scala.collection.immutable.ArraySeq - def withAlpha(alpha: Int): Palette = { +case class Palette(name: String, colorArray: ArraySeq[Color]) { - def f(i: Int): Color = { - val c = colors(i) - new Color(c.getRed, c.getGreen, c.getBlue, alpha) - } - Palette(name, f) + override def equals(obj: Any): Boolean = { + if (obj == null) return false + if (!obj.isInstanceOf[Palette]) return false + val other = obj.asInstanceOf[Palette] + if (!name.equals(other.name)) return false + colorArray.equals(other.colorArray) } - def withVisionType(vision: VisionType): Palette = { + def withAlpha(alpha: Int): Palette = + Palette(name, colorArray.map(c => new Color(c.getRed, c.getGreen, c.getBlue, alpha))) - def f(i: Int): Color = { - val c = colors(i) - vision.convert(c) - } - Palette(s"${vision.name}_$name", f) - } + def withVisionType(vision: VisionType): Palette = + Palette(s"${vision.name}_$name", colorArray.map(vision.convert(_))) /** * Convert colors from another palette into grayscale. For information about the conversion * see: http://www.johndcook.com/blog/2009/08/24/algorithms-convert-color-grayscale/. */ - def asGrayscale: Palette = { - - def f(i: Int): Color = { - val c = colors(i) - val v = (0.21 * c.getRed + 0.72 * c.getGreen + 0.07 * c.getBlue).toInt - new Color(v, v, v, c.getAlpha) - } - Palette(s"grayscale_$name", f) - } + def asGrayscale: Palette = + Palette( + s"grayscale_$name", + colorArray.map { c => + val v = (0.21 * c.getRed + 0.72 * c.getGreen + 0.07 * c.getBlue).toInt + new Color(v, v, v, c.getAlpha) + } + ) def iterator: Iterator[Color] = new Iterator[Color] { @@ -64,9 +60,24 @@ case class Palette(name: String, colors: Int => Color) { override def next(): Color = { pos += 1 - colors(pos) + if (pos >= colorArray.length) pos = 0 + colorArray(pos) } } + + /** + * Rotates through the colors in the palette based on the index, returning a + * deterministic color. + * + * @param i + * A positive integer value. + * @return + * A deterministic color in the palette. + */ + def colors(i: Int): Color = { + val index = math.abs(i) % colorArray.length + colorArray(index) + } } object Palette { @@ -121,7 +132,7 @@ object Palette { def fromArray(name: String, colors: Array[Color]): Palette = { require(colors.nonEmpty, "palette must contain at least one color") - Palette(name, i => colors(math.abs(i) % colors.length)) + Palette(name, ArraySeq.from(colors)) } /** @@ -143,7 +154,7 @@ object Palette { } def singleColor(c: Color): Palette = { - Palette("%08X".format(c.getRGB), _ => c) + Palette("%08X".format(c.getRGB), ArraySeq(c)) } def brighter(c: Color, n: Int): Palette = { diff --git a/atlas-chart/src/test/scala/com/netflix/atlas/chart/model/PaletteSuite.scala b/atlas-chart/src/test/scala/com/netflix/atlas/chart/model/PaletteSuite.scala index 37a03d082..a07ca21ac 100644 --- a/atlas-chart/src/test/scala/com/netflix/atlas/chart/model/PaletteSuite.scala +++ b/atlas-chart/src/test/scala/com/netflix/atlas/chart/model/PaletteSuite.scala @@ -36,12 +36,14 @@ class PaletteSuite extends FunSuite { test("colors:f00") { val p = Palette.create("colors:f00") assertEquals(p.colors(0), Color.RED) + assertEquals(1, p.colorArray.size) } test("colors:f00,00ff00") { val p = Palette.create("colors:f00,00ff00") assertEquals(p.colors(0), Color.RED) assertEquals(p.colors(1), Color.GREEN) + assertEquals(2, p.colorArray.size) } test("colors:f00,00ff00,ff0000ff") { @@ -49,6 +51,7 @@ class PaletteSuite extends FunSuite { assertEquals(p.colors(0), Color.RED) assertEquals(p.colors(1), Color.GREEN) assertEquals(p.colors(2), Color.BLUE) + assertEquals(3, p.colorArray.size) } test("(,)") { @@ -66,12 +69,14 @@ class PaletteSuite extends FunSuite { test("(,f00,)") { val p = Palette.create("(,f00,)") assertEquals(p.colors(0), Color.RED) + assertEquals(1, p.colorArray.size) } test("(,f00,00ff00,)") { val p = Palette.create("(,f00,00ff00,)") assertEquals(p.colors(0), Color.RED) assertEquals(p.colors(1), Color.GREEN) + assertEquals(2, p.colorArray.size) } test("(,f00,00ff00,ff0000ff,)") { @@ -79,11 +84,13 @@ class PaletteSuite extends FunSuite { assertEquals(p.colors(0), Color.RED) assertEquals(p.colors(1), Color.GREEN) assertEquals(p.colors(2), Color.BLUE) + assertEquals(3, p.colorArray.size) } test("armytage") { val p = Palette.create("armytage") assertEquals(p.colors(0), new Color(0, 117, 220)) + assertEquals(24, p.colorArray.size) } test("foo") {