Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add color option to use series color #630

Merged
merged 12 commits into from
Apr 21, 2020
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions src/chart_types/partition_chart/layout/utils/__mocks__/d3_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License. */

const module = jest.requireActual('../d3_utils.ts');

export const stringToRGB = jest.fn(module.stringToRGB);
export const validateColor = jest.fn(module.validateColor);
export const argsToRGB = jest.fn(module.argsToRGB);
export const argsToRGBString = jest.fn(module.argsToRGBString);
export const RGBtoString = jest.fn(module.RGBtoString);
219 changes: 219 additions & 0 deletions src/chart_types/partition_chart/layout/utils/d3_utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License. */

import {
stringToRGB,
validateColor,
defaultD3Color,
argsToRGB,
RgbObject,
argsToRGBString,
RGBtoString,
} from './d3_utils';

describe('d3 Utils', () => {
describe('stringToRGB', () => {
describe('bad colors or undefined', () => {
it('should return default color for undefined color string', () => {
expect(stringToRGB()).toMatchObject({
r: 255,
g: 0,
b: 0,
opacity: 1,
});
});

it('should return default RgbObject', () => {
expect(stringToRGB('not a color')).toMatchObject({
r: 255,
g: 0,
b: 0,
opacity: 1,
});
});

it('should return default color if bad opacity', () => {
expect(stringToRGB('rgba(50,50,50,x)')).toMatchObject({
r: 255,
g: 0,
b: 0,
opacity: 1,
});
});
});

describe('hex colors', () => {
it('should return RgbObject', () => {
expect(stringToRGB('#ef713d')).toMatchObject({
r: 239,
g: 113,
b: 61,
});
});

it('should return RgbObject from shorthand', () => {
expect(stringToRGB('#ccc')).toMatchObject({
r: 204,
g: 204,
b: 204,
});
});

it('should return RgbObject with correct opacity', () => {
// https://gist.github.com/lopspower/03fb1cc0ac9f32ef38f4
expect(stringToRGB('#ef713d80').opacity).toBeCloseTo(0.5, 1);
});

it('should return correct RgbObject for alpha value of 0', () => {
expect(stringToRGB('#00000000')).toMatchObject({
r: 0,
g: 0,
b: 0,
opacity: 0,
});
});
});

describe('rgb colors', () => {
it('should return RgbObject', () => {
expect(stringToRGB('rgb(50,50,50)')).toMatchObject({
r: 50,
g: 50,
b: 50,
});
});

it('should return RgbObject with correct opacity', () => {
expect(stringToRGB('rgba(50,50,50,0.25)').opacity).toBe(0.25);
});

it('should return correct RgbObject for alpha value of 0', () => {
expect(stringToRGB('rgba(50,50,50,0)')).toMatchObject({
r: 50,
g: 50,
b: 50,
opacity: 0,
});
});
});

describe('hsl colors', () => {
it('should return RgbObject', () => {
expect(stringToRGB('hsl(0,0%,50%)')).toMatchObject({
r: 127.5,
g: 127.5,
b: 127.5,
});
});

it('should return RgbObject with correct opacity', () => {
expect(stringToRGB('hsla(0,0%,50%,0.25)').opacity).toBe(0.25);
});

it('should return correct RgbObject for alpha value of 0', () => {
expect(stringToRGB('hsla(0,0%,50%,0)')).toEqual({
r: 127.5,
g: 127.5,
b: 127.5,
opacity: 0,
});
});
});

describe('named colors', () => {
it('should return RgbObject', () => {
expect(stringToRGB('aquamarine')).toMatchObject({
r: 127,
g: 255,
b: 212,
});
});

it('should return default RgbObject with 0 opacity', () => {
expect(stringToRGB('transparent')).toMatchObject({
r: 0,
g: 0,
b: 0,
opacity: 0,
});
});

it('should return default RgbObject with 0 opacity even with override', () => {
expect(stringToRGB('transparent', 0.5)).toMatchObject({
r: 0,
g: 0,
b: 0,
opacity: 0,
});
});
});

describe('Optional opactiy override', () => {
it('should override opacity from color', () => {
expect(stringToRGB('rgba(50,50,50,0.25)', 0.75).opacity).toBe(0.75);
});

it('should use OpacityFn to compute opacity override', () => {
expect(stringToRGB('rgba(50,50,50,0.25)', (o) => o * 2).opacity).toBe(0.5);
});
});
});

describe('validateColor', () => {
it.each<string>(['r', 'g', 'b', 'opacity'])('should return null if %s is NaN', (value) => {
expect(
validateColor({
...defaultD3Color,
[value]: NaN,
}),
).toBeNull();
});

it('should return valid colors', () => {
expect(validateColor(defaultD3Color)).toBe(defaultD3Color);
});
});

describe('argsToRGB', () => {
it.each<keyof RgbObject>(['r', 'g', 'b', 'opacity'])('should return defaultD3Color if %s is NaN', (value) => {
const { r, g, b, opacity }: RgbObject = {
...defaultD3Color,
[value]: NaN,
};
expect(argsToRGB(r, g, b, opacity)).toEqual(defaultD3Color);
});

it('should return valid colors', () => {
const { r, g, b, opacity } = defaultD3Color;
expect(argsToRGB(r, g, b, opacity)).toEqual(defaultD3Color);
});
});

describe('argsToRGBString', () => {
it('should return valid colors', () => {
const { r, g, b, opacity } = defaultD3Color;
expect(argsToRGBString(r, g, b, opacity)).toBe('rgb(255, 0, 0)');
});
});

describe('RGBtoString', () => {
it('should return valid colors', () => {
expect(RGBtoString(defaultD3Color)).toBe('rgb(255, 0, 0)');
});
});
});
72 changes: 66 additions & 6 deletions src/chart_types/partition_chart/layout/utils/d3_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,76 @@ type A = number;
export type RgbTuple = [RGB, RGB, RGB, RGB?];
export type RgbObject = { r: RGB; g: RGB; b: RGB; opacity: A };

const defaultColor: RgbObject = { r: 255, g: 0, b: 0, opacity: 1 };
const defaultD3Color: D3RGBColor = d3Rgb(defaultColor.r, defaultColor.g, defaultColor.b, defaultColor.opacity);
/** @internal */
export const defaultColor: RgbObject = { r: 255, g: 0, b: 0, opacity: 1 };
/** @internal */
export const transparentColor: RgbObject = { r: 0, g: 0, b: 0, opacity: 0 };
/** @internal */
export const defaultD3Color: D3RGBColor = d3Rgb(defaultColor.r, defaultColor.g, defaultColor.b, defaultColor.opacity);

/** @internal */
export type OpacityFn = (colorOpacity: number) => number;

/** @internal */
export function stringToRGB(cssColorSpecifier?: string, opacity?: number | OpacityFn): RgbObject {
if (cssColorSpecifier === 'transparent') {
return transparentColor;
}
const color = getColor(cssColorSpecifier);

if (opacity === undefined) {
return color;
}

const opacityOverride = typeof opacity === 'number' ? opacity : opacity(color.opacity);

if (isNaN(opacityOverride)) {
return color;
}

return {
...color,
opacity: opacityOverride,
};
}

/**
* Returns color as RgbObject or default fallback.
*
* Handles issue in d3-color for hsla and rgba colors with alpha value of `0`
*
* @param cssColorSpecifier
*/
function getColor(cssColorSpecifier: string = ''): RgbObject {
let color: D3RGBColor;
const endRegEx = /,\s*0+(\.0*)?\s*\)$/;
// TODO: make this check more robust
if (/^(rgba|hsla)\(/i.test(cssColorSpecifier) && endRegEx.test(cssColorSpecifier)) {
color = {
...d3Rgb(cssColorSpecifier.replace(endRegEx, ',1)')),
opacity: 0,
};
} else {
color = d3Rgb(cssColorSpecifier);
}

return validateColor(color) ?? defaultColor;
}

/** @internal */
export function stringToRGB(cssColorSpecifier: string): RgbObject {
return d3Rgb(cssColorSpecifier) || defaultColor;
export function validateColor(color: D3RGBColor): D3RGBColor | null {
const { r, g, b, opacity } = color;

if (isNaN(r) || isNaN(g) || isNaN(b) || isNaN(opacity)) {
return null;
}

return color;
}

function argsToRGB(r: number, g: number, b: number, opacity: number): D3RGBColor {
return d3Rgb(r, g, b, opacity) || defaultD3Color;
/** @internal */
export function argsToRGB(r: number, g: number, b: number, opacity: number): D3RGBColor {
return validateColor(d3Rgb(r, g, b, opacity)) ?? defaultD3Color;
}

/** @internal */
Expand Down
13 changes: 11 additions & 2 deletions src/chart_types/xy_chart/renderer/canvas/primitives/rect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ export function renderRect(
return;
}

// fill

if (fill) {
const borderOffset = !disableBoardOffset && stroke && stroke.width > 0.001 ? stroke.width : 0;
// console.log(stroke, borderOffset);
Expand All @@ -58,12 +56,16 @@ export function renderRect(
drawRect(ctx, { x, y, width, height });
if (stroke.dash) {
ctx.setLineDash(stroke.dash);
} else {
// Setting linecap with dash causes solid line
ctx.lineCap = 'square';
}

ctx.stroke();
}
}

/** @internal */
function drawRect(ctx: CanvasRenderingContext2D, rect: Rect) {
const { x, y, width, height } = rect;
ctx.beginPath();
Expand Down Expand Up @@ -100,5 +102,12 @@ export function renderMultiRect(ctx: CanvasRenderingContext2D, rects: Rect[], fi
ctx.strokeStyle = RGBtoString(stroke.color);
ctx.lineWidth = stroke.width;
ctx.stroke();

if (stroke.dash) {
ctx.setLineDash(stroke.dash);
} else {
// Setting linecap with dash causes solid line
ctx.lineCap = 'square';
}
}
}
Loading