Skip to content

Commit fb09dc9

Browse files
feat(series): set custom series colors through spec prop (#95)
1 parent 0b60cbb commit fb09dc9

File tree

8 files changed

+149
-18
lines changed

8 files changed

+149
-18
lines changed

src/lib/series/series.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,15 +265,15 @@ describe('Series', () => {
265265

266266
const emptyCustomColors = new Map();
267267

268-
const defaultColorMap = getSeriesColorMap(seriesColors, chartColors, emptyCustomColors, specs);
268+
const defaultColorMap = getSeriesColorMap(seriesColors, chartColors, emptyCustomColors);
269269
const expectedDefaultColorMap = new Map();
270270
expectedDefaultColorMap.set('spec1', 'elastic_charts_c1');
271271
expect(defaultColorMap).toEqual(expectedDefaultColorMap);
272272

273273
const customColors: Map<string, string> = new Map();
274274
customColors.set('spec1', 'custom_color');
275275

276-
const customizedColorMap = getSeriesColorMap(seriesColors, chartColors, customColors, specs);
276+
const customizedColorMap = getSeriesColorMap(seriesColors, chartColors, customColors);
277277
const expectedCustomizedColorMap = new Map();
278278
expectedCustomizedColorMap.set('spec1', 'custom_color');
279279
expect(customizedColorMap).toEqual(expectedCustomizedColorMap);

src/lib/series/series.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { ColorConfig } from '../themes/theme';
33
import { Accessor } from '../utils/accessor';
44
import { GroupId, SpecId } from '../utils/ids';
55
import { splitSpecsByGroupId, YBasicSeriesSpec } from './domains/y_domain';
6-
import { getSeriesColorLabel } from './legend';
76
import { BasicSeriesSpec, Datum, SeriesAccessors } from './specs';
87

98
export interface RawDataSeriesDatum {
@@ -148,7 +147,7 @@ function getColorValues(
148147
/**
149148
* Get the array of values that forms a series key
150149
*/
151-
function getColorValuesAsString(colorValues: any[], specId: SpecId): string {
150+
export function getColorValuesAsString(colorValues: any[], specId: SpecId): string {
152151
return `specId:{${specId}},colors:{${colorValues}}`;
153152
}
154153

@@ -392,17 +391,13 @@ export function getSeriesColorMap(
392391
seriesColors: Map<string, DataSeriesColorsValues>,
393392
chartColors: ColorConfig,
394393
customColors: Map<string, string>,
395-
specs: Map<SpecId, BasicSeriesSpec>,
396394
): Map<string, string> {
397395
const seriesColorMap = new Map<string, string>();
398396
let counter = 0;
399397

400-
seriesColors.forEach((value, seriesColorKey) => {
401-
const spec = specs.get(value.specId);
402-
const hasSingleSeries = seriesColors.size === 1;
403-
const seriesLabel = getSeriesColorLabel(value, hasSingleSeries, spec);
404-
405-
const color = (seriesLabel && customColors.get(seriesLabel)) ||
398+
seriesColors.forEach((value: DataSeriesColorsValues, seriesColorKey: string) => {
399+
const customSeriesColor: string | undefined = customColors.get(seriesColorKey);
400+
const color = customSeriesColor ||
406401
chartColors.vizColors[counter % chartColors.vizColors.length];
407402

408403
seriesColorMap.set(

src/lib/series/specs.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Domain } from '../utils/domain';
44
import { AxisId, GroupId, SpecId } from '../utils/ids';
55
import { ScaleContinuousType, ScaleType } from '../utils/scales/scales';
66
import { CurveType } from './curves';
7+
import { DataSeriesColorsValues } from './series';
78
import { TooltipPosition } from './tooltip';
89

910
export type Datum = any;
@@ -37,8 +38,12 @@ export interface SeriesSpec {
3738
yDomain?: Domain;
3839
/** The type of series you are looking to render */
3940
seriesType: 'bar' | 'line' | 'area' | 'basic';
41+
/** Custom colors for series */
42+
customSeriesColors?: CustomSeriesColorsMap;
4043
}
4144

45+
export type CustomSeriesColorsMap = Map<DataSeriesColorsValues, string>;
46+
4247
export interface SeriesAccessors {
4348
/** The field name of the x value on Datum object */
4449
xAccessor: Accessor;

src/state/chart_state.test.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -483,16 +483,23 @@ describe('Chart Store', () => {
483483
store.computeChart = computeChart;
484484
store.legendItems = [firstLegendItem, secondLegendItem];
485485

486-
const expectedCustomColors = new Map();
487-
expectedCustomColors.set(firstLegendItem.label, 'foo');
488-
489486
store.setSeriesColor(-1, 'foo');
490487
expect(computeChart).not.toBeCalled();
491488
expect(store.customSeriesColors).toEqual(new Map());
492489

493490
store.setSeriesColor(0, 'foo');
494491
expect(computeChart).toBeCalled();
495-
expect(store.customSeriesColors).toEqual(expectedCustomColors);
492+
expect(store.seriesSpecs.get(firstLegendItem.value.specId)).toBeUndefined();
493+
494+
store.addSeriesSpec(spec);
495+
store.setSeriesColor(0, 'foo');
496+
const expectedSpecCustomColorSeries = new Map();
497+
expectedSpecCustomColorSeries.set(firstLegendItem.value, 'foo');
498+
expect(spec.customSeriesColors).toEqual(expectedSpecCustomColorSeries);
499+
500+
store.setSeriesColor(1, 'bar');
501+
expectedSpecCustomColorSeries.set(secondLegendItem.value, 'bar');
502+
expect(spec.customSeriesColors).toEqual(expectedSpecCustomColorSeries);
496503
});
497504

498505
test('can reset selectedDataSeries', () => {

src/state/chart_state.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
getAllDataSeriesColorValues,
5151
getAxesSpecForSpecId,
5252
getLegendItemByIndex,
53+
getUpdatedCustomSeriesColors,
5354
Transform,
5455
updateSelectedDataSeries,
5556
} from './utils';
@@ -293,8 +294,19 @@ export class ChartStore {
293294
const legendItem = getLegendItemByIndex(this.legendItems, legendItemIndex);
294295

295296
if (legendItem) {
296-
const key = legendItem.label;
297-
this.customSeriesColors.set(key, color);
297+
const { specId } = legendItem.value;
298+
299+
const spec = this.seriesSpecs.get(specId);
300+
if (spec) {
301+
if (spec.customSeriesColors) {
302+
spec.customSeriesColors.set(legendItem.value, color);
303+
} else {
304+
const specCustomSeriesColors = new Map();
305+
spec.customSeriesColors = specCustomSeriesColors;
306+
spec.customSeriesColors.set(legendItem.value, color);
307+
}
308+
}
309+
298310
this.computeChart();
299311
}
300312
});
@@ -438,6 +450,10 @@ export class ChartStore {
438450
this.selectedDataSeries = getAllDataSeriesColorValues(seriesDomains.seriesColors);
439451
}
440452

453+
// Merge all series spec custom colors with state custom colors map
454+
const updatedCustomSeriesColors = getUpdatedCustomSeriesColors(this.seriesSpecs);
455+
this.customSeriesColors = new Map([...this.customSeriesColors, ...updatedCustomSeriesColors]);
456+
441457
// tslint:disable-next-line:no-console
442458
// console.log({colors: seriesDomains.seriesColors});
443459

@@ -447,7 +463,6 @@ export class ChartStore {
447463
seriesDomains.seriesColors,
448464
this.chartTheme.colors,
449465
this.customSeriesColors,
450-
this.seriesSpecs,
451466
);
452467

453468
this.legendItems = computeLegend(

src/state/utils.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
findSelectedDataSeries,
1212
getAllDataSeriesColorValues,
1313
getLegendItemByIndex,
14+
getUpdatedCustomSeriesColors,
1415
updateSelectedDataSeries,
1516
} from './utils';
1617

@@ -214,4 +215,36 @@ describe('Chart State utils', () => {
214215

215216
expect(getAllDataSeriesColorValues(colorMap)).toEqual(expected);
216217
});
218+
it('should get an updated customSeriesColor based on specs', () => {
219+
const spec1: BasicSeriesSpec = {
220+
id: getSpecId('spec1'),
221+
groupId: getGroupId('group1'),
222+
seriesType: 'line',
223+
yScaleType: ScaleType.Log,
224+
xScaleType: ScaleType.Linear,
225+
xAccessor: 'x',
226+
yAccessors: ['y'],
227+
yScaleToDataExtent: false,
228+
data: BARCHART_1Y0G,
229+
};
230+
231+
const specs = new Map<SpecId, BasicSeriesSpec>();
232+
specs.set(spec1.id, spec1);
233+
234+
const emptyCustomSeriesColors = getUpdatedCustomSeriesColors(specs);
235+
expect(emptyCustomSeriesColors).toEqual(new Map());
236+
237+
const dataSeriesColorValues = {
238+
specId: spec1.id,
239+
colorValues: ['bar'],
240+
};
241+
spec1.customSeriesColors = new Map();
242+
spec1.customSeriesColors.set(dataSeriesColorValues, 'custom_color');
243+
244+
const updatedCustomSeriesColors = getUpdatedCustomSeriesColors(specs);
245+
const expectedCustomSeriesColors = new Map();
246+
expectedCustomSeriesColors.set('specId:{spec1},colors:{bar}', 'custom_color');
247+
248+
expect(updatedCustomSeriesColors).toEqual(expectedCustomSeriesColors);
249+
});
217250
});

src/state/utils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
DataSeries,
1919
DataSeriesColorsValues,
2020
FormattedDataSeries,
21+
getColorValuesAsString,
2122
getFormattedDataseries,
2223
getSplittedSeries,
2324
RawDataSeries,
@@ -89,6 +90,20 @@ export function updateSelectedDataSeries(
8990
return updatedSeries;
9091
}
9192

93+
export function getUpdatedCustomSeriesColors(seriesSpecs: Map<SpecId, BasicSeriesSpec>): Map<string, string> {
94+
const updatedCustomSeriesColors = new Map();
95+
seriesSpecs.forEach((spec: BasicSeriesSpec, id: SpecId) => {
96+
if (spec.customSeriesColors) {
97+
spec.customSeriesColors.forEach((color: string, seriesColorValues: DataSeriesColorsValues) => {
98+
const { colorValues, specId } = seriesColorValues;
99+
const seriesLabel = getColorValuesAsString(colorValues, specId);
100+
updatedCustomSeriesColors.set(seriesLabel, color);
101+
});
102+
}
103+
});
104+
return updatedCustomSeriesColors;
105+
}
106+
92107
export function computeSeriesDomains(
93108
seriesSpecs: Map<SpecId, BasicSeriesSpec>,
94109
selectedDataSeries?: DataSeriesColorsValues[] | null,

stories/styling.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import {
2121
ScaleType,
2222
Settings,
2323
} from '../src/';
24+
import { DataSeriesColorsValues } from '../src/lib/series/series';
25+
import { CustomSeriesColorsMap } from '../src/lib/series/specs';
26+
import * as TestDatasets from '../src/lib/series/utils/test_dataset';
2427
import { DEFAULT_MISSING_COLOR } from '../src/lib/themes/theme_commons';
2528

2629
function range(
@@ -348,4 +351,62 @@ storiesOf('Stylings', module)
348351
/>
349352
</Chart>
350353
);
354+
})
355+
.add('custom series colors through spec props', () => {
356+
const barCustomSeriesColors: CustomSeriesColorsMap = new Map();
357+
const barDataSeriesColorValues: DataSeriesColorsValues = {
358+
colorValues: ['cloudflare.com', 'direct-cdn', 'y2'],
359+
specId: getSpecId('bars'),
360+
};
361+
362+
const lineCustomSeriesColors: CustomSeriesColorsMap = new Map();
363+
const lineDataSeriesColorValues: DataSeriesColorsValues = {
364+
colorValues: [],
365+
specId: getSpecId('lines'),
366+
};
367+
368+
const customBarColorKnob = color('barDataSeriesColor', '#000');
369+
const customLineColorKnob = color('lineDataSeriesColor', '#ff0');
370+
barCustomSeriesColors.set(barDataSeriesColorValues, customBarColorKnob);
371+
lineCustomSeriesColors.set(lineDataSeriesColorValues, customLineColorKnob);
372+
373+
return (
374+
<Chart renderer="canvas" className={'story-chart'}>
375+
<Settings showLegend={true} legendPosition={Position.Right} />
376+
<Axis
377+
id={getAxisId('bottom')}
378+
position={Position.Bottom}
379+
title={'Bottom axis'}
380+
showOverlappingTicks={true}
381+
/>
382+
<Axis
383+
id={getAxisId('left2')}
384+
title={'Left axis'}
385+
position={Position.Left}
386+
tickFormat={(d) => Number(d).toFixed(2)}
387+
/>
388+
389+
<BarSeries
390+
id={getSpecId('bars')}
391+
xScaleType={ScaleType.Linear}
392+
yScaleType={ScaleType.Linear}
393+
xAccessor="x"
394+
yAccessors={['y1', 'y2']}
395+
splitSeriesAccessors={['g1', 'g2']}
396+
customSeriesColors={barCustomSeriesColors}
397+
data={TestDatasets.BARCHART_2Y2G}
398+
yScaleToDataExtent={false}
399+
/>
400+
<LineSeries
401+
id={getSpecId('lines')}
402+
xScaleType={ScaleType.Linear}
403+
yScaleType={ScaleType.Linear}
404+
xAccessor="x"
405+
yAccessors={['y']}
406+
customSeriesColors={lineCustomSeriesColors}
407+
data={[{ x: 0, y: 3 }, { x: 1, y: 2 }, { x: 2, y: 4 }, { x: 3, y: 10 }]}
408+
yScaleToDataExtent={false}
409+
/>
410+
</Chart>
411+
);
351412
});

0 commit comments

Comments
 (0)