palettes.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import * as d3 from 'd3';
  2. import * as d3ScaleChromatic from 'd3-scale-chromatic';
  3. import tinycolor from 'tinycolor2';
  4. import { GrafanaTheme2 } from '@grafana/data';
  5. import { HeatmapColorOptions, defaultPanelOptions, HeatmapColorMode, HeatmapColorScale } from './models.gen';
  6. // https://observablehq.com/@d3/color-schemes?collection=@d3/d3-scale-chromatic
  7. // the previous heatmap panel used d3 deps and some code to interpolate to static 9-color palettes. here we just hard-code them for clarity.
  8. // if the need arises for configurable-sized palettes, we can bring back the deps & variable interpolation (see simplified code at end)
  9. // Schemes from d3-scale-chromatic
  10. // https://github.com/d3/d3-scale-chromatic
  11. export const colorSchemes = [
  12. // Diverging
  13. { name: 'BrBG', invert: 'always' },
  14. { name: 'PiYG', invert: 'always' },
  15. { name: 'PRGn', invert: 'always' },
  16. { name: 'PuOr', invert: 'always' },
  17. { name: 'RdBu', invert: 'always' },
  18. { name: 'RdGy', invert: 'always' },
  19. { name: 'RdYlBu', invert: 'always' },
  20. { name: 'RdYlGn', invert: 'always' },
  21. { name: 'Spectral', invert: 'always' },
  22. // Sequential (Single Hue)
  23. { name: 'Blues', invert: 'dark' },
  24. { name: 'Greens', invert: 'dark' },
  25. { name: 'Greys', invert: 'dark' },
  26. { name: 'Oranges', invert: 'dark' },
  27. { name: 'Purples', invert: 'dark' },
  28. { name: 'Reds', invert: 'dark' },
  29. // Sequential (Multi-Hue)
  30. { name: 'Turbo', invert: 'light' },
  31. { name: 'Cividis', invert: 'light' },
  32. { name: 'Viridis', invert: 'light' },
  33. { name: 'Magma', invert: 'light' },
  34. { name: 'Inferno', invert: 'light' },
  35. { name: 'Plasma', invert: 'light' },
  36. { name: 'Warm', invert: 'light' },
  37. { name: 'Cool', invert: 'light' },
  38. { name: 'Cubehelix', invert: 'light', name2: 'CubehelixDefault' },
  39. { name: 'BuGn', invert: 'dark' },
  40. { name: 'BuPu', invert: 'dark' },
  41. { name: 'GnBu', invert: 'dark' },
  42. { name: 'OrRd', invert: 'dark' },
  43. { name: 'PuBuGn', invert: 'dark' },
  44. { name: 'PuBu', invert: 'dark' },
  45. { name: 'PuRd', invert: 'dark' },
  46. { name: 'RdPu', invert: 'dark' },
  47. { name: 'YlGnBu', invert: 'dark' },
  48. { name: 'YlGn', invert: 'dark' },
  49. { name: 'YlOrBr', invert: 'dark' },
  50. { name: 'YlOrRd', invert: 'dark' },
  51. // Cyclical
  52. { name: 'Rainbow', invert: 'always' },
  53. { name: 'Sinebow', invert: 'always' },
  54. ];
  55. type Interpolator = (t: number) => string;
  56. const DEFAULT_SCHEME = colorSchemes.find((scheme) => scheme.name === 'Spectral');
  57. export function quantizeScheme(opts: HeatmapColorOptions, theme: GrafanaTheme2): string[] {
  58. const options = { ...defaultPanelOptions.color, ...opts };
  59. const palette = [];
  60. const steps = (options.steps ?? 128) - 1;
  61. if (opts.mode === HeatmapColorMode.Opacity) {
  62. const fill = tinycolor(theme.visualization.getColorByName(opts.fill)).toPercentageRgb();
  63. const scale =
  64. options.scale === HeatmapColorScale.Exponential
  65. ? d3.scalePow().exponent(options.exponent).domain([0, 1]).range([0, 1])
  66. : d3.scaleLinear().domain([0, 1]).range([0, 1]);
  67. for (let i = 0; i <= steps; i++) {
  68. fill.a = scale(i / steps);
  69. palette.push(tinycolor(fill).toString('hex8'));
  70. }
  71. } else {
  72. const scheme = colorSchemes.find((scheme) => scheme.name === options.scheme) ?? DEFAULT_SCHEME!;
  73. let fnName = 'interpolate' + (scheme.name2 ?? scheme.name);
  74. const interpolate: Interpolator = (d3ScaleChromatic as any)[fnName];
  75. for (let i = 0; i <= steps; i++) {
  76. let rgbStr = interpolate(i / steps);
  77. let rgb =
  78. rgbStr.indexOf('rgb') === 0
  79. ? '#' + [...rgbStr.matchAll(/\d+/g)].map((v) => (+v[0]).toString(16).padStart(2, '0')).join('')
  80. : rgbStr;
  81. palette.push(rgb);
  82. }
  83. if (
  84. scheme.invert === 'always' ||
  85. (scheme.invert === 'dark' && theme.isDark) ||
  86. (scheme.invert === 'light' && theme.isLight)
  87. ) {
  88. palette.reverse();
  89. }
  90. }
  91. return palette;
  92. }