Move color mapping logic to mapped_colors and protect against duplicate colors
This commit is contained in:
parent
7b14699991
commit
ad07d651de
|
@ -1,13 +1,16 @@
|
|||
var angular = require('angular');
|
||||
var expect = require('expect.js');
|
||||
var ngMock = require('ngMock');
|
||||
const _ = require('lodash');
|
||||
const d3 = require('d3');
|
||||
|
||||
describe('Vislib Color Module Test Suite', function () {
|
||||
var seedColors;
|
||||
var MappedColors;
|
||||
var mappedColors;
|
||||
let config;
|
||||
|
||||
describe('Color (main)', function () {
|
||||
let previousConfig;
|
||||
var getColors;
|
||||
var arr = ['good', 'better', 'best', 'never', 'let', 'it', 'rest'];
|
||||
var arrayOfNumbers = [1, 2, 3, 4, 5];
|
||||
|
@ -21,14 +24,19 @@ describe('Vislib Color Module Test Suite', function () {
|
|||
var color;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private) {
|
||||
beforeEach(ngMock.inject(function (Private, config) {
|
||||
previousConfig = config.get('visualization:colorMapping');
|
||||
config.set('visualization:colorMapping', {});
|
||||
seedColors = Private(require('ui/vislib/components/color/seed_colors'));
|
||||
getColors = Private(require('ui/vislib/components/color/color'));
|
||||
MappedColors = Private(require('ui/vislib/components/color/mapped_colors'));
|
||||
mappedColors = new MappedColors();
|
||||
mappedColors = Private(require('ui/vislib/components/color/mapped_colors'));
|
||||
color = getColors(arr);
|
||||
}));
|
||||
|
||||
afterEach(ngMock.inject(function (config) {
|
||||
config.set('visualization:colorMapping', previousConfig);
|
||||
}));
|
||||
|
||||
it('should throw an error if input is not an array', function () {
|
||||
expect(function () {
|
||||
getColors(200);
|
||||
|
@ -108,38 +116,66 @@ describe('Vislib Color Module Test Suite', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Mapped Colors', function () {
|
||||
describe('Mapped Colors', () => {
|
||||
let previousConfig;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private) {
|
||||
MappedColors = Private(require('ui/vislib/components/color/mapped_colors'));
|
||||
mappedColors = new MappedColors();
|
||||
beforeEach(ngMock.inject((Private, config) => {
|
||||
previousConfig = config.get('visualization:colorMapping');
|
||||
mappedColors = Private(require('ui/vislib/components/color/mapped_colors'));
|
||||
mappedColors.mapping = {};
|
||||
}));
|
||||
|
||||
it('should clear all the keys from the map table', function () {
|
||||
mappedColors.reset();
|
||||
expect(mappedColors.count()).to.be(0);
|
||||
});
|
||||
afterEach(ngMock.inject((config) => {
|
||||
config.set('visualization:colorMapping', previousConfig);
|
||||
}));
|
||||
|
||||
it('should return the color for the added value', function () {
|
||||
mappedColors.reset();
|
||||
mappedColors.add('value1', '#somecolor');
|
||||
expect(mappedColors.get('value1')).to.be('#somecolor');
|
||||
});
|
||||
it('should properly map keys to unique colors', ngMock.inject((config) => {
|
||||
config.set('visualization:colorMapping', {});
|
||||
|
||||
it('should return the count of mapped keys', function () {
|
||||
mappedColors.reset();
|
||||
mappedColors.add('value1', '#color1');
|
||||
mappedColors.add('value2', '#color2');
|
||||
expect(mappedColors.count()).to.be(2);
|
||||
});
|
||||
const arr = [1, 2, 3, 4, 5];
|
||||
mappedColors.mapKeys(arr);
|
||||
expect(_(mappedColors.mapping).values().uniq().size()).to.be(arr.length);
|
||||
}));
|
||||
|
||||
it('should return all the colors in the map', function () {
|
||||
mappedColors.reset();
|
||||
mappedColors.add('value1', '#colors1');
|
||||
mappedColors.add('value3', '#newColor');
|
||||
expect(mappedColors.all()).to.eql(['#colors1', '#newColor']);
|
||||
});
|
||||
it('should not include colors used by the config', ngMock.inject((config) => {
|
||||
const newConfig = {bar: seedColors[0]};
|
||||
config.set('visualization:colorMapping', newConfig);
|
||||
|
||||
const arr = ['foo', 'baz', 'qux'];
|
||||
mappedColors.mapKeys(arr);
|
||||
|
||||
const colorValues = _(mappedColors.mapping).values();
|
||||
expect(colorValues.contains(seedColors[0])).to.be(false);
|
||||
expect(colorValues.uniq().size()).to.be(arr.length);
|
||||
}));
|
||||
|
||||
it('should create a unique array of colors even when config is set', ngMock.inject((config) => {
|
||||
const newConfig = {bar: seedColors[0]};
|
||||
config.set('visualization:colorMapping', newConfig);
|
||||
|
||||
const arr = ['foo', 'bar', 'baz', 'qux'];
|
||||
mappedColors.mapKeys(arr);
|
||||
|
||||
const expectedSize = _(arr).difference(_.keys(newConfig)).size();
|
||||
expect(_(mappedColors.mapping).values().uniq().size()).to.be(expectedSize);
|
||||
expect(mappedColors.get(arr[0])).to.not.be(seedColors[0]);
|
||||
}));
|
||||
|
||||
it('should treat different formats of colors as equal', ngMock.inject((config) => {
|
||||
const color = d3.rgb(seedColors[0]);
|
||||
const rgb = `rgb(${color.r}, ${color.g}, ${color.b})`;
|
||||
const newConfig = {bar: rgb};
|
||||
config.set('visualization:colorMapping', newConfig);
|
||||
|
||||
const arr = ['foo', 'bar', 'baz', 'qux'];
|
||||
mappedColors.mapKeys(arr);
|
||||
|
||||
const expectedSize = _(arr).difference(_.keys(newConfig)).size();
|
||||
expect(_(mappedColors.mapping).values().uniq().size()).to.be(expectedSize);
|
||||
expect(mappedColors.get(arr[0])).to.not.be(seedColors[0]);
|
||||
expect(mappedColors.get('bar')).to.be(seedColors[0]);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Color Palette', function () {
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
define(function (require) {
|
||||
return function ColorUtilService(Private, config) {
|
||||
var _ = require('lodash');
|
||||
|
||||
var createColorPalette = Private(require('ui/vislib/components/color/color_palette'));
|
||||
var MappedColors = Private(require('ui/vislib/components/color/mapped_colors'));
|
||||
var mappedColors = new MappedColors();
|
||||
var mappedColors = Private(require('ui/vislib/components/color/mapped_colors'));
|
||||
|
||||
/*
|
||||
* Accepts an array of strings or numbers that are used to create a
|
||||
|
@ -24,16 +21,10 @@ define(function (require) {
|
|||
}
|
||||
});
|
||||
|
||||
var configColorMapping = config.get('visualization:colorMapping');
|
||||
var arrayLength = arrayOfStringsOrNumbers.length;
|
||||
var colors = createColorPalette(arrayLength + mappedColors.count());
|
||||
var uniqueColors = _.difference(colors, mappedColors.all()).slice(0, arrayLength + 1);
|
||||
var colorObj = _.zipObject(arrayOfStringsOrNumbers, uniqueColors);
|
||||
mappedColors.mapKeys(arrayOfStringsOrNumbers);
|
||||
|
||||
return function (value) {
|
||||
var mappedColor = configColorMapping[value] || mappedColors.get(value);
|
||||
if (!mappedColor) mappedColors.add(value, mappedColor = colorObj[value]);
|
||||
return mappedColor;
|
||||
return mappedColors.get(value);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,63 +1,50 @@
|
|||
define(function () {
|
||||
return function MappedColorFactory() {
|
||||
define((require) => (Private, config) => {
|
||||
const _ = require('lodash');
|
||||
const d3 = require('d3');
|
||||
const createColorPalette = Private(require('ui/vislib/components/color/color_palette'));
|
||||
|
||||
var _ = require('lodash');
|
||||
/*
|
||||
* Maintains a lookup table that associates the value (key) with a hex color (value)
|
||||
* across the visualizations.
|
||||
* Provides functions to interact with the lookup table
|
||||
*/
|
||||
const standardizeColor = (color) => d3.rgb(color).toString();
|
||||
function getConfigColorMapping() {
|
||||
return _.mapValues(config.get('visualization:colorMapping'), standardizeColor);
|
||||
}
|
||||
|
||||
var MappedColorService = function () {
|
||||
};
|
||||
/*
|
||||
* Maintains a lookup table that associates the value (key) with a hex color (value)
|
||||
* across the visualizations.
|
||||
* Provides functions to interact with the lookup table
|
||||
*/
|
||||
class MappedColors {
|
||||
constructor() {
|
||||
this.mapping = {};
|
||||
}
|
||||
|
||||
MappedColorService.colors = {};
|
||||
/**
|
||||
* Allows to add value (key) and color (value) to the lookup table
|
||||
*
|
||||
* @method add
|
||||
* @param {String} key - the value in a visualization
|
||||
* @param {String} value - the color associated with the value
|
||||
*/
|
||||
MappedColorService.prototype.add = function (key, value) {
|
||||
MappedColorService.colors[key] = value;
|
||||
};
|
||||
get(key) {
|
||||
return getConfigColorMapping()[key] || this.mapping[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the color (value) associated with the value (key)
|
||||
*
|
||||
* @method get
|
||||
* @param {String} key - the value for which color is required
|
||||
* @returns the color associated with the value
|
||||
*/
|
||||
MappedColorService.prototype.get = function (key) {
|
||||
return MappedColorService.colors[key];
|
||||
};
|
||||
mapKeys(keys) {
|
||||
const configMapping = getConfigColorMapping();
|
||||
const configColors = _.values(configMapping);
|
||||
|
||||
/**
|
||||
* Size of the mapped colors
|
||||
*
|
||||
* @method count
|
||||
* @returns the number of values (keys) stored in the lookup table
|
||||
*/
|
||||
MappedColorService.prototype.count = function () {
|
||||
return _.keys(MappedColorService.colors).length;
|
||||
};
|
||||
const keysToMap = [];
|
||||
_.each(keys, (key) => {
|
||||
// If this key is mapped in the config, it's unnecessary to have it mapped here
|
||||
if (configMapping[key]) delete this.mapping[key];
|
||||
|
||||
/**
|
||||
* All the colors of in the lookup table
|
||||
*
|
||||
* @method all
|
||||
* @returns all the colors used in the lookup table
|
||||
*/
|
||||
MappedColorService.prototype.all = function () {
|
||||
return _.values(MappedColorService.colors);
|
||||
};
|
||||
// If this key is mapped to a color used by the config color mapping, we need to remap it
|
||||
if (_.contains(configColors, this.mapping[key])) keysToMap.push(key);
|
||||
|
||||
MappedColorService.prototype.reset = function () {
|
||||
MappedColorService.colors = {};
|
||||
};
|
||||
// If this key isn't mapped, we need to map it
|
||||
if (this.get(key) == null) keysToMap.push(key);
|
||||
});
|
||||
|
||||
return MappedColorService;
|
||||
};
|
||||
// Generate a color palette big enough that all new keys can have unique color values
|
||||
const allColors = _(this.mapping).values().union(configColors).value();
|
||||
const colorPalette = createColorPalette(allColors.length + keysToMap.length);
|
||||
const newColors = _.difference(colorPalette, allColors);
|
||||
_.merge(this.mapping, _.zipObject(keysToMap, newColors));
|
||||
}
|
||||
}
|
||||
|
||||
return new MappedColors();
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue