[Canvas] Expression progress (#104457)

* Added `expression_progress` plugin.

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Yaroslav Kuznietsov 2021-08-04 11:33:01 +03:00 committed by GitHub
parent 1186b7d7ce
commit dbab7d9f70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 1199 additions and 696 deletions

View file

@ -116,5 +116,6 @@ pageLoadAssetSize:
expressionRepeatImage: 22341
expressionImage: 19288
expressionMetric: 22238
expressionShape: 30033
expressionShape: 34008
interactiveSetup: 18532

View file

@ -8,4 +8,10 @@
export const PLUGIN_ID = 'expressionShape';
export const PLUGIN_NAME = 'expressionShape';
export const SVG = 'SVG';
export const CSS = 'CSS';
export const FONT_FAMILY = '`font-family`';
export const FONT_WEIGHT = '`font-weight`';
export const BOOLEAN_TRUE = '`true`';
export const BOOLEAN_FALSE = '`false`';

View file

@ -7,3 +7,4 @@
*/
export { shapeFunction } from './shape_function';
export { progressFunction } from './progress_function';

View file

@ -0,0 +1,213 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { ExecutionContext } from '../../../expressions';
import { functionWrapper, fontStyle } from '../../../presentation_util/common/lib';
import { progressFunction, errors } from './progress_function';
describe('progress', () => {
const fn = functionWrapper(progressFunction);
const value = 0.33;
it('returns a render as progress', () => {
const result = fn(0.2, {}, {} as ExecutionContext);
expect(result).toHaveProperty('type', 'render');
expect(result).toHaveProperty('as', 'progress');
});
it('sets the progress to context', () => {
const result = fn(0.58, {}, {} as ExecutionContext);
expect(result.value).toHaveProperty('value', 0.58);
});
it(`throws when context is outside of the valid range`, async () => {
expect.assertions(1);
try {
await fn(3, {}, {} as ExecutionContext);
} catch (e: any) {
expect(e.message).toEqual(errors.invalidValue(3).message);
}
});
describe('args', () => {
describe('shape', () => {
it('sets the progress element shape', () => {
const result = fn(
value,
{
shape: 'wheel',
},
{} as ExecutionContext
);
expect(result.value).toHaveProperty('shape', 'wheel');
});
it(`defaults to 'gauge'`, () => {
const result = fn(value, {}, {} as ExecutionContext);
expect(result.value).toHaveProperty('shape', 'gauge');
});
});
describe('max', () => {
it('sets the maximum value', () => {
const result = fn(
value,
{
max: 2,
},
{} as ExecutionContext
);
expect(result.value).toHaveProperty('max', 2);
});
it('defaults to 1', () => {
const result = fn(value, {}, {} as ExecutionContext);
expect(result.value).toHaveProperty('max', 1);
});
it('throws if max <= 0', async () => {
expect.assertions(1);
try {
await fn(value, { max: -0.5 }, {} as ExecutionContext);
} catch (e: any) {
expect(e.message).toEqual(errors.invalidMaxValue(-0.5).message);
}
});
});
describe('valueColor', () => {
it('sets the color of the progress bar', () => {
const result = fn(
value,
{
valueColor: '#000000',
},
{} as ExecutionContext
);
expect(result.value).toHaveProperty('valueColor', '#000000');
});
it(`defaults to '#1785b0'`, () => {
const result = fn(value, {}, {} as ExecutionContext);
expect(result.value).toHaveProperty('valueColor', '#1785b0');
});
});
describe('barColor', () => {
it('sets the color of the background bar', () => {
const result = fn(
value,
{
barColor: '#FFFFFF',
},
{} as ExecutionContext
);
expect(result.value).toHaveProperty('barColor', '#FFFFFF');
});
it(`defaults to '#f0f0f0'`, () => {
const result = fn(value, {}, {} as ExecutionContext);
expect(result.value).toHaveProperty('barColor', '#f0f0f0');
});
});
describe('valueWeight', () => {
it('sets the thickness of the bars', () => {
const result = fn(
value,
{
valuWeight: 100,
},
{} as ExecutionContext
);
expect(result.value).toHaveProperty('valuWeight', 100);
});
it(`defaults to 20`, () => {
const result = fn(value, {}, {} as ExecutionContext);
expect(result.value).toHaveProperty('barWeight', 20);
});
});
describe('barWeight', () => {
it('sets the thickness of the bars', () => {
const result = fn(
value,
{
barWeight: 50,
},
{} as ExecutionContext
);
expect(result.value).toHaveProperty('barWeight', 50);
});
it(`defaults to 20`, () => {
const result = fn(value, {}, {} as ExecutionContext);
expect(result.value).toHaveProperty('barWeight', 20);
});
});
describe('label', () => {
it('sets the label of the progress', () => {
const result = fn(value, { label: 'foo' }, {} as ExecutionContext);
expect(result.value).toHaveProperty('label', 'foo');
});
it('hides the label if false', () => {
const result = fn(
value,
{
label: false,
},
{} as ExecutionContext
);
expect(result.value).toHaveProperty('label', '');
});
it('defaults to true which sets the context as the label', () => {
const result = fn(value, {}, {} as ExecutionContext);
expect(result.value).toHaveProperty('label', '0.33');
});
});
describe('font', () => {
it('sets the font style for the label', () => {
const result = fn(
value,
{
font: fontStyle,
},
{} as ExecutionContext
);
expect(result.value).toHaveProperty('font');
expect(Object.keys(result.value.font).sort()).toEqual(Object.keys(fontStyle).sort());
expect(Object.keys(result.value.font.spec).sort()).toEqual(
Object.keys(fontStyle.spec).sort()
);
});
it('sets fill to color', () => {
const result = fn(
value,
{
font: fontStyle,
},
{} as ExecutionContext
);
expect(result.value.font.spec).toHaveProperty('fill', fontStyle.spec.color);
});
// TODO: write test when using an instance of the interpreter
// it("sets a default style for the label when not provided", () => {});
});
});
});

View file

@ -0,0 +1,174 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import { get } from 'lodash';
import { Style, openSans } from '../../../expressions/common';
import { CSS, FONT_FAMILY, FONT_WEIGHT, BOOLEAN_TRUE, BOOLEAN_FALSE } from '../constants';
import { ExpressionProgressFunction, Progress } from '../types';
export const strings = {
help: i18n.translate('expressionShape.functions.progressHelpText', {
defaultMessage: 'Configures a progress element.',
}),
args: {
barColor: i18n.translate('expressionShape.functions.progress.args.barColorHelpText', {
defaultMessage: 'The color of the background bar.',
}),
barWeight: i18n.translate('expressionShape.functions.progress.args.barWeightHelpText', {
defaultMessage: 'The thickness of the background bar.',
}),
font: i18n.translate('expressionShape.functions.progress.args.fontHelpText', {
defaultMessage:
'The {CSS} font properties for the label. For example, {FONT_FAMILY} or {FONT_WEIGHT}.',
values: {
CSS,
FONT_FAMILY,
FONT_WEIGHT,
},
}),
label: i18n.translate('expressionShape.functions.progress.args.labelHelpText', {
defaultMessage:
'To show or hide the label, use {BOOLEAN_TRUE} or {BOOLEAN_FALSE}. Alternatively, provide a string to display as a label.',
values: {
BOOLEAN_TRUE,
BOOLEAN_FALSE,
},
}),
max: i18n.translate('expressionShape.functions.progress.args.maxHelpText', {
defaultMessage: 'The maximum value of the progress element.',
}),
shape: i18n.translate('expressionShape.functions.progress.args.shapeHelpText', {
defaultMessage: `Select {list}, or {end}.`,
values: {
list: Object.values(Progress)
.slice(0, -1)
.map((shape) => `\`"${shape}"\``)
.join(', '),
end: `\`"${Object.values(Progress).slice(-1)[0]}"\``,
},
}),
valueColor: i18n.translate('expressionShape.functions.progress.args.valueColorHelpText', {
defaultMessage: 'The color of the progress bar.',
}),
valueWeight: i18n.translate('expressionShape.functions.progress.args.valueWeightHelpText', {
defaultMessage: 'The thickness of the progress bar.',
}),
},
};
export const errors = {
invalidMaxValue: (max: number) =>
new Error(
i18n.translate('expressionShape.functions.progress.invalidMaxValueErrorMessage', {
defaultMessage: "Invalid {arg} value: '{max, number}'. '{arg}' must be greater than 0",
values: {
arg: 'max',
max,
},
})
),
invalidValue: (value: number, max: number = 1) =>
new Error(
i18n.translate('expressionShape.functions.progress.invalidValueErrorMessage', {
defaultMessage:
"Invalid value: '{value, number}'. Value must be between 0 and {max, number}",
values: {
value,
max,
},
})
),
};
export const progressFunction: ExpressionProgressFunction = () => {
const { help, args: argHelp } = strings;
return {
name: 'progress',
aliases: [],
type: 'render',
inputTypes: ['number'],
help,
args: {
shape: {
aliases: ['_'],
types: ['string'],
help: argHelp.shape,
options: Object.values(Progress),
default: 'gauge',
},
barColor: {
types: ['string'],
help: argHelp.barColor,
default: `#f0f0f0`,
},
barWeight: {
types: ['number'],
help: argHelp.barWeight,
default: 20,
},
font: {
types: ['style'],
help: argHelp.font,
default: `{font size=24 family="${openSans.value}" color="#000000" align=center}`,
},
label: {
types: ['boolean', 'string'],
help: argHelp.label,
default: true,
},
max: {
types: ['number'],
help: argHelp.max,
default: 1,
},
valueColor: {
types: ['string'],
help: argHelp.valueColor,
default: `#1785b0`,
},
valueWeight: {
types: ['number'],
help: argHelp.valueWeight,
default: 20,
},
},
fn: (value, args) => {
if (args.max <= 0) {
throw errors.invalidMaxValue(args.max);
}
if (value > args.max || value < 0) {
throw errors.invalidValue(value, args.max);
}
let label = '';
if (args.label) {
label = typeof args.label === 'string' ? args.label : `${value}`;
}
let font: Style = {} as Style;
if (get(args, 'font.spec')) {
font = { ...args.font };
font.spec.fill = args.font.spec.color; // SVG <text> uses fill for font color
}
return {
type: 'render',
as: 'progress',
value: {
value,
...args,
label,
font,
},
};
},
};
};

View file

@ -9,4 +9,4 @@
export * from './constants';
export * from './types';
export { getAvailableShapes } from './lib/available_shapes';
export { getAvailableShapes, getAvailableProgressShapes } from './lib/available_shapes';

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
import { Shape } from '../types';
import { Shape, Progress } from '../types';
export const getAvailableShapes = () => Object.values(Shape);
export const getAvailableProgressShapes = () => Object.values(Progress);

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import uuid from 'uuid/v4';
export function getId(type: string): string {
return `${type}-${uuid()}`;
}

View file

@ -8,3 +8,4 @@
export * from './view_box';
export * from './available_shapes';
export * from './get_id';

View file

@ -9,7 +9,10 @@
import { ParentNodeParams, ViewBoxParams } from '../types';
export function viewBoxToString(viewBox?: ViewBoxParams): undefined | string {
if (!viewBox) return;
if (!viewBox) {
return;
}
return `${viewBox?.minX} ${viewBox?.minY} ${viewBox?.width} ${viewBox?.height}`;
}

View file

@ -5,7 +5,8 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { ExpressionFunctionDefinition } from 'src/plugins/expressions';
import { ExpressionFunctionDefinition, ExpressionValueRender, Style } from '../../../expressions';
export enum Shape {
ARROW = 'arrow',
@ -44,3 +45,36 @@ export type ExpressionShapeFunction = () => ExpressionFunctionDefinition<
Arguments,
Output
>;
export enum Progress {
GAUGE = 'gauge',
HORIZONTAL_BAR = 'horizontalBar',
HORIZONTAL_PILL = 'horizontalPill',
SEMICIRCLE = 'semicircle',
UNICORN = 'unicorn',
VERTICAL_BAR = 'verticalBar',
VERTICAL_PILL = 'verticalPill',
WHEEL = 'wheel',
}
export interface ProgressArguments {
barColor: string;
barWeight: number;
font: Style;
label: boolean | string;
max: number;
shape: Progress;
valueColor: string;
valueWeight: number;
}
export type ProgressOutput = ProgressArguments & {
value: number;
};
export type ExpressionProgressFunction = () => ExpressionFunctionDefinition<
'progress',
number,
ProgressArguments,
ExpressionValueRender<ProgressArguments>
>;

View file

@ -5,7 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { Shape } from './expression_functions';
import { Shape, ProgressOutput as Arguments } from './expression_functions';
export type OriginString = 'bottom' | 'left' | 'top' | 'right';
export interface ShapeRendererConfig {
@ -33,3 +33,5 @@ export interface ViewBoxParams {
width: number;
height: number;
}
export type ProgressRendererConfig = Arguments;

View file

@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { lazy } from 'react';
export const LazyProgressComponent = lazy(() => import('./progress_component'));
export const LazyProgressDrawer = lazy(() => import('./progress_drawer'));

View file

@ -0,0 +1,131 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { CSSProperties, RefCallback, useCallback, useEffect, useRef, useState } from 'react';
import { useResizeObserver } from '@elastic/eui';
import { IInterpreterRenderHandlers } from '../../../../expressions';
import { NodeDimensions, ProgressRendererConfig } from '../../../common/types';
import { ShapeRef, SvgConfig, SvgTextAttributes } from '../reusable/types';
import { getShapeContentElement } from '../reusable/shape_factory';
import { withSuspense } from '../../../../presentation_util/public';
import { getTextAttributes, getViewBox } from './utils';
import { getId } from '../../../common/lib';
import { getDefaultShapeData } from '../reusable';
import { LazyProgressDrawer } from '../..';
const ProgressDrawer = withSuspense(LazyProgressDrawer);
interface ProgressComponentProps extends ProgressRendererConfig {
onLoaded: IInterpreterRenderHandlers['done'];
parentNode: HTMLElement;
}
function ProgressComponent({
onLoaded,
parentNode,
shape: shapeType,
value,
max,
valueColor,
barColor,
valueWeight,
barWeight,
label,
font,
}: ProgressComponentProps) {
const parentNodeDimensions = useResizeObserver(parentNode);
const [dimensions, setDimensions] = useState<NodeDimensions>({
width: parentNode.offsetWidth,
height: parentNode.offsetHeight,
});
const [shapeData, setShapeData] = useState<SvgConfig>(getDefaultShapeData());
const shapeRef = useCallback<RefCallback<ShapeRef>>((node) => {
if (node !== null) {
setShapeData(node.getData());
}
}, []);
const [totalLength, setTotalLength] = useState<number>(0);
useEffect(() => {
setDimensions({
width: parentNode.offsetWidth,
height: parentNode.offsetHeight,
});
onLoaded();
}, [onLoaded, parentNode, parentNodeDimensions]);
const progressRef = useRef<
SVGCircleElement & SVGPathElement & SVGPolygonElement & SVGRectElement
>(null);
const textRef = useRef<SVGTextElement>(null);
useEffect(() => {
setTotalLength(progressRef.current ? progressRef.current.getTotalLength() : 0);
}, [shapeType, shapeData, progressRef]);
const BarProgress = shapeData.shapeType ? getShapeContentElement(shapeData.shapeType) : null;
const shapeContentAttributes = {
fill: 'none',
stroke: barColor,
strokeWidth: `${barWeight}px`,
};
const percent = value / max;
const to = totalLength * (1 - percent);
const barProgressAttributes = {
...shapeData.shapeProps,
fill: 'none',
stroke: valueColor,
strokeWidth: `${valueWeight}px`,
strokeDasharray: totalLength,
strokeDashoffset: Math.max(0, to),
};
const { width: labelWidth, height: labelHeight } = textRef.current
? textRef.current.getBBox()
: { width: 0, height: 0 };
const offset = Math.max(valueWeight, barWeight);
const updatedTextAttributes = shapeData.textAttributes
? getTextAttributes(shapeType, shapeData.textAttributes, offset, label)
: {};
const textAttributes: SvgTextAttributes = {
style: font.spec as CSSProperties,
...updatedTextAttributes,
};
const updatedViewBox = getViewBox(shapeType, shapeData.viewBox, offset, labelWidth, labelHeight);
const shapeAttributes = {
id: getId('svg'),
...(dimensions || {}),
viewBox: updatedViewBox,
};
return (
<div className="shapeAligner">
<ProgressDrawer
shapeType={shapeType}
shapeContentAttributes={{ ...shapeContentAttributes, ref: progressRef }}
shapeAttributes={shapeAttributes}
textAttributes={{ ...textAttributes, ref: textRef }}
ref={shapeRef}
>
{BarProgress && <BarProgress {...barProgressAttributes} />}
</ProgressDrawer>
</div>
);
}
// default export required for React.Lazy
// eslint-disable-next-line import/no-default-export
export { ProgressComponent as default };

View file

@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { Ref } from 'react';
import { ShapeDrawer, ShapeRef, ShapeDrawerComponentProps } from '../reusable';
import { getShape } from './shapes';
const ProgressDrawerComponent = React.forwardRef(
(props: ShapeDrawerComponentProps, ref: Ref<ShapeRef>) => (
<ShapeDrawer {...props} ref={ref} getShape={getShape} />
)
);
// eslint-disable-next-line import/no-default-export
export { ProgressDrawerComponent as default };

View file

@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { createShape } from '../../reusable/shape_factory';
import { SvgElementTypes } from '../../reusable';
export const Gauge = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 120,
height: 120,
},
shapeProps: {
d: 'M 15 100 A 60 60 0 1 1 105 100',
},
shapeType: SvgElementTypes.path,
textAttributes: {
x: '60',
y: '60',
textAnchor: 'middle',
dominantBaseline: 'central',
},
});

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { createShape } from '../../reusable/shape_factory';
import { SvgElementTypes } from '../../reusable';
export const HorizontalBar = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 208,
height: 1,
},
shapeType: SvgElementTypes.path,
shapeProps: {
d: 'M 0 1 L 200 1',
},
textAttributes: {
x: 208,
y: 0,
textAnchor: 'start',
dominantBaseline: 'central',
},
});

View file

@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { createShape } from '../../reusable/shape_factory';
import { SvgElementTypes } from '../../reusable';
export const HorizontalPill = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 208,
height: 1,
},
shapeType: SvgElementTypes.path,
shapeProps: {
d: 'M 0 1 L 200 1',
strokeLinecap: 'round',
},
textAttributes: {
x: 208,
y: 0,
textAnchor: 'start',
dominantBaseline: 'central',
},
});

View file

@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { ShapeType } from '../../reusable';
import { Gauge as gauge } from './gauge';
import { HorizontalBar as horizontalBar } from './horizontal_bar';
import { HorizontalPill as horizontalPill } from './horizontal_pill';
import { Semicircle as semicircle } from './semicircle';
import { Unicorn as unicorn } from './unicorn';
import { VerticalBar as verticalBar } from './vertical_bar';
import { VerticalPill as verticalPill } from './vertical_pill';
import { Wheel as wheel } from './wheel';
const shapes: { [key: string]: ShapeType } = {
gauge,
horizontalBar,
horizontalPill,
semicircle,
unicorn,
verticalBar,
verticalPill,
wheel,
};
export const getShape = (shapeType: string) => shapes[shapeType];

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { createShape } from '../../reusable/shape_factory';
import { SvgElementTypes } from '../../reusable';
export const Semicircle = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 120,
height: 60,
},
shapeType: SvgElementTypes.path,
shapeProps: {
d: 'M 0 60 A 60 60 0 1 1 120 60',
},
textAttributes: {
x: 60,
y: 60,
textAnchor: 'middle',
dy: '-1',
},
});

View file

@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { createShape } from '../../reusable/shape_factory';
import { SvgElementTypes } from '../../reusable';
export const Unicorn = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 200,
height: 200,
},
shapeType: SvgElementTypes.path,
shapeProps: {
d:
'M 123 189 C 93 141 129 126 102 96 L 78 102 L 48 117 L 42 129 Q 30 132 21 126 L 18 114 L 27 90 L 42 72 L 48 57 L 3 6 L 57 42 L 63 33 L 60 15 L 69 27 L 69 15 L 84 27 Q 162 36 195 108 Q 174 159 123 189 Z',
},
textAttributes: {
x: '0',
y: '200',
textAnchor: 'start',
dominantBaseline: 'text-after-edge',
},
});

View file

@ -0,0 +1,27 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { createShape } from '../../reusable/shape_factory';
import { SvgElementTypes } from '../../reusable';
export const VerticalBar = createShape({
viewBox: {
minX: 0,
minY: -8,
width: 1,
height: 208,
},
shapeType: SvgElementTypes.path,
shapeProps: {
d: 'M 1 200 L 1 0',
},
textAttributes: {
x: '0',
y: '-8',
textAnchor: 'middle',
},
});

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { createShape } from '../../reusable/shape_factory';
import { SvgElementTypes } from '../../reusable';
export const VerticalPill = createShape({
viewBox: {
minX: 0,
minY: -8,
width: 1,
height: 208,
},
shapeType: SvgElementTypes.path,
shapeProps: {
d: 'M 1 200 L 1 0',
strokeLinecap: 'round',
},
textAttributes: {
x: '0',
y: '-8',
textAnchor: 'middle',
},
});

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { createShape } from '../../reusable/shape_factory';
import { SvgElementTypes } from '../../reusable';
export const Wheel = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 120,
height: 120,
},
shapeType: SvgElementTypes.path,
shapeProps: {
d: 'M 60 0 A 60 60 0 1 1 60 120 A 60 60 0 1 1 60 0 Z',
},
textAttributes: {
x: '60',
y: '60',
textAnchor: 'middle',
dominantBaseline: 'central',
},
});

View file

@ -0,0 +1,146 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { Progress, ViewBoxParams } from '../../../common';
import { SvgTextAttributes } from '../reusable';
type GetViewBox = (
shapeType: Progress,
initialViewBox: ViewBoxParams,
offset: number,
labelWidth: number,
labelHeight: number
) => ViewBoxParams;
type GetViewBoxArguments = Parameters<GetViewBox>;
type GetViewBoxParam = (...args: GetViewBoxArguments) => number;
const getMinX: GetViewBoxParam = (shapeType, viewBox, offset = 0) => {
let { minX } = viewBox;
if (shapeType !== Progress.HORIZONTAL_BAR) {
minX -= offset / 2;
}
return minX;
};
const getMinY: GetViewBoxParam = (shapeType, viewBox, offset = 0, labelWidth, labelHeight = 0) => {
let { minY } = viewBox;
if (shapeType === Progress.SEMICIRCLE) {
minY -= offset / 2;
}
if (shapeType !== Progress.SEMICIRCLE && shapeType !== Progress.VERTICAL_BAR) {
minY -= offset / 2;
}
if (shapeType === Progress.VERTICAL_BAR || shapeType === Progress.VERTICAL_PILL) {
minY -= labelHeight;
}
return minY;
};
const getWidth: GetViewBoxParam = (shapeType, viewBox, offset = 0, labelWidth = 0) => {
let { width } = viewBox;
if (shapeType !== Progress.HORIZONTAL_BAR) {
width += offset;
}
if (shapeType === Progress.HORIZONTAL_BAR || shapeType === Progress.HORIZONTAL_PILL) {
width += labelWidth;
}
return width;
};
const getHeight: GetViewBoxParam = (
shapeType,
viewBox,
offset = 0,
labelWidth = 0,
labelHeight = 0
) => {
let { height } = viewBox;
if (shapeType === Progress.SEMICIRCLE) {
height += offset / 2;
}
if (shapeType !== Progress.SEMICIRCLE && shapeType !== Progress.VERTICAL_BAR) {
height += offset;
}
if (shapeType === Progress.VERTICAL_BAR || shapeType === Progress.VERTICAL_PILL) {
height += labelHeight;
}
return height;
};
const updateMinxAndWidthIfNecessary = (
shapeType: Progress,
labelWidth: number,
minX: number,
width: number
) => {
if (
(shapeType === Progress.VERTICAL_BAR || shapeType === Progress.VERTICAL_PILL) &&
labelWidth > width
) {
minX = -labelWidth / 2;
width = labelWidth;
}
return [minX, width];
};
export const getViewBox: GetViewBox = function (
shapeType,
viewBox,
offset = 0,
labelWidth = 0,
labelHeight = 0
): ViewBoxParams {
const args: GetViewBoxArguments = [shapeType, viewBox, offset, labelWidth, labelHeight];
const minX = getMinX(...args);
const minY = getMinY(...args);
const width = getWidth(...args);
const height = getHeight(...args);
const [updatedMinX, updatedWidth] = updateMinxAndWidthIfNecessary(
shapeType,
labelWidth,
minX,
width
);
return { minX: updatedMinX, minY, width: updatedWidth, height };
};
export function getTextAttributes(
shapeType: Progress,
textAttributes: SvgTextAttributes,
offset: number = 0,
label: string | boolean = ''
) {
if (!label) {
return textAttributes;
}
let { x, y, textContent } = textAttributes;
textContent = label ? label.toString() : '';
if (shapeType === Progress.HORIZONTAL_PILL) {
x = parseInt(String(x)!, 10) + offset / 2;
}
if (shapeType === Progress.VERTICAL_PILL) {
y = parseInt(String(y)!, 10) - offset / 2;
}
if (shapeType === Progress.HORIZONTAL_BAR || shapeType === Progress.HORIZONTAL_PILL) {
x = parseInt(String(x)!, 10);
}
return { x, y, textContent };
}

View file

@ -10,32 +10,59 @@ import React from 'react';
import { viewBoxToString } from '../../../common/lib';
import { ShapeProps, SvgConfig, SvgElementTypes } from './types';
const getShapeComponent = (svgParams: SvgConfig) =>
function Shape({ shapeAttributes, shapeContentAttributes }: ShapeProps) {
const { viewBox: initialViewBox, shapeProps, shapeType } = svgParams;
export const getShapeComponent = (svgParams: SvgConfig) =>
function Shape({
shapeAttributes,
shapeContentAttributes,
children,
textAttributes,
}: ShapeProps) {
const {
viewBox: initialViewBox,
shapeProps: defaultShapeContentAttributes,
textAttributes: defaultTextAttributes,
shapeType,
} = svgParams;
const viewBox = shapeAttributes?.viewBox
? viewBoxToString(shapeAttributes?.viewBox)
: viewBoxToString(initialViewBox);
const SvgContentElement = getShapeContentElement(shapeType);
const TextElement = textAttributes
? React.forwardRef<SVGTextElement>((props, ref) => <text {...props} ref={ref} />)
: null;
return (
<svg xmlns="http://www.w3.org/2000/svg" {...{ ...(shapeAttributes || {}), viewBox }}>
<SvgContentElement {...{ ...(shapeContentAttributes || {}), ...shapeProps }} />
<SvgContentElement
{...{ ...defaultShapeContentAttributes, ...(shapeContentAttributes || {}) }}
/>
{children}
{TextElement && (
<TextElement {...{ ...(defaultTextAttributes || {}), ...(textAttributes || {}) }}>
{textAttributes?.textContent}
</TextElement>
)}
</svg>
);
};
function getShapeContentElement(type?: SvgElementTypes) {
export function getShapeContentElement(type?: SvgElementTypes) {
switch (type) {
case SvgElementTypes.circle:
return (props: SvgConfig['shapeProps']) => <circle {...props} />;
return React.forwardRef<SVGCircleElement | null>((props, ref) => (
<circle {...props} ref={ref} />
));
case SvgElementTypes.rect:
return (props: SvgConfig['shapeProps']) => <rect {...props} />;
return React.forwardRef<SVGRectElement | null>((props, ref) => <rect {...props} ref={ref} />);
case SvgElementTypes.path:
return (props: SvgConfig['shapeProps']) => <path {...props} />;
return React.forwardRef<SVGPathElement | null>((props, ref) => <path {...props} ref={ref} />);
default:
return (props: SvgConfig['shapeProps']) => <polygon {...props} />;
return React.forwardRef<SVGPolygonElement | null>((props, ref) => (
<polygon {...props} ref={ref} />
));
}
}

View file

@ -6,14 +6,16 @@
* Side Public License, v 1.
*/
import { Ref, SVGProps } from 'react';
import { Component, CSSProperties, Ref, SVGProps } from 'react';
import { ViewBoxParams } from '../../../common/types';
import type { ShapeType } from './shape_factory';
export interface ShapeProps {
export type ShapeProps = {
shapeAttributes?: ShapeAttributes;
shapeContentAttributes?: ShapeContentAttributes;
}
shapeContentAttributes?: ShapeContentAttributes &
SpecificShapeContentAttributes & { ref?: React.RefObject<any> };
textAttributes?: SvgTextAttributes;
} & Component['props'] & { ref?: React.RefObject<any> };
export enum SvgElementTypes {
polygon,
@ -39,36 +41,48 @@ export interface ShapeContentAttributes {
vectorEffect?: SVGProps<SVGElement>['vectorEffect'];
strokeMiterlimit?: SVGProps<SVGElement>['strokeMiterlimit'];
}
export interface SvgConfig {
shapeType?: SvgElementTypes;
viewBox: ViewBoxParams;
shapeProps: ShapeContentAttributes &
SpecificShapeContentAttributes &
Component['props'] & { ref?: React.RefObject<any> };
textAttributes?: SvgTextAttributes;
}
interface CircleParams {
export type SvgTextAttributes = Partial<Element> & {
x?: SVGProps<SVGTextElement>['x'];
y?: SVGProps<SVGTextElement>['y'];
textAnchor?: SVGProps<SVGTextElement>['textAnchor'];
dominantBaseline?: SVGProps<SVGTextElement>['dominantBaseline'];
dx?: SVGProps<SVGTextElement>['dx'];
dy?: SVGProps<SVGTextElement>['dy'];
} & { style?: CSSProperties } & { ref?: React.RefObject<SVGTextElement> };
export interface CircleParams {
r: SVGProps<SVGCircleElement>['r'];
cx: SVGProps<SVGCircleElement>['cx'];
cy: SVGProps<SVGCircleElement>['cy'];
}
interface RectParams {
export interface RectParams {
x: SVGProps<SVGRectElement>['x'];
y: SVGProps<SVGRectElement>['y'];
width: SVGProps<SVGRectElement>['width'];
height: SVGProps<SVGRectElement>['height'];
}
interface PathParams {
export interface PathParams {
d: SVGProps<SVGPathElement>['d'];
strokeLinecap?: SVGProps<SVGPathElement>['strokeLinecap'];
}
interface PolygonParams {
export interface PolygonParams {
points?: SVGProps<SVGPolygonElement>['points'];
strokeLinejoin?: SVGProps<SVGPolygonElement>['strokeLinejoin'];
}
type SpecificShapeContentAttributes = CircleParams | RectParams | PathParams | PolygonParams;
export interface SvgConfig {
shapeType?: SvgElementTypes;
viewBox: ViewBoxParams;
shapeProps: ShapeContentAttributes & SpecificShapeContentAttributes;
}
export type SpecificShapeContentAttributes = CircleParams | RectParams | PathParams | PolygonParams;
export type ShapeDrawerProps = {
shapeType: string;
@ -80,4 +94,6 @@ export interface ShapeRef {
getData: () => SvgConfig;
}
export type ShapeDrawerComponentProps = Omit<ShapeDrawerProps, 'getShape'>;
export type { ShapeType };

View file

@ -6,9 +6,8 @@
* Side Public License, v 1.
*/
import React, { Ref } from 'react';
import { ShapeDrawer, ShapeRef } from '../reusable';
import { ShapeDrawer, ShapeRef, ShapeDrawerComponentProps } from '../reusable';
import { getShape } from './shapes';
import { ShapeDrawerComponentProps } from './types';
const ShapeDrawerComponent = React.forwardRef(
(props: ShapeDrawerComponentProps, ref: Ref<ShapeRef>) => (

View file

@ -8,7 +8,6 @@
import { IInterpreterRenderHandlers } from '../../../../../../src/plugins/expressions';
import { ShapeRendererConfig } from '../../../common/types';
import { ShapeDrawerProps } from '../reusable/types';
export interface ShapeComponentProps extends ShapeRendererConfig {
onLoaded: IInterpreterRenderHandlers['done'];
@ -19,4 +18,3 @@ export interface Dimensions {
width: number;
height: number;
}
export type ShapeDrawerComponentProps = Omit<ShapeDrawerProps, 'getShape'>;

View file

@ -1,15 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { storiesOf } from '@storybook/react';
import { progress } from '../';
import { Render } from '../../__stories__/render';
import { Shape } from '../../../functions/common/progress';
import { Render } from '../../../../presentation_util/public/__stories__';
import { progressRenderer } from '../progress_renderer';
import { Progress } from '../../../common';
storiesOf('renderers/progress', module).add('default', () => {
const config = {
@ -22,11 +23,11 @@ storiesOf('renderers/progress', module).add('default', () => {
},
label: '66%',
max: 1,
shape: Shape.UNICORN,
shape: Progress.UNICORN,
value: 0.66,
valueColor: '#000',
valueWeight: 15,
};
return <Render renderer={progress} config={config} />;
return <Render renderer={progressRenderer} config={config} />;
});

View file

@ -7,7 +7,8 @@
*/
import { shapeRenderer } from './shape_renderer';
import { progressRenderer } from './progress_renderer';
export const renderers = [shapeRenderer];
export const renderers = [shapeRenderer, progressRenderer];
export { shapeRenderer };
export { shapeRenderer, progressRenderer };

View file

@ -0,0 +1,48 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { i18n } from '@kbn/i18n';
import { ProgressRendererConfig } from '../../common/types';
import { LazyProgressComponent } from '../components/progress';
import { withSuspense } from '../../../presentation_util/public';
const ProgressComponent = withSuspense(LazyProgressComponent);
const strings = {
getDisplayName: () =>
i18n.translate('expressionShape.renderer.progress.displayName', {
defaultMessage: 'Progress',
}),
getHelpDescription: () =>
i18n.translate('expressionShape.renderer.progress.helpDescription', {
defaultMessage: 'Render a basic progress',
}),
};
export const progressRenderer = (): ExpressionRenderDefinition<ProgressRendererConfig> => ({
name: 'progress',
displayName: strings.getDisplayName(),
help: strings.getHelpDescription(),
reuseDomNode: true,
render: async (
domNode: HTMLElement,
config: ProgressRendererConfig,
handlers: IInterpreterRenderHandlers
) => {
handlers.onDestroy(() => {
unmountComponentAtNode(domNode);
});
render(
<ProgressComponent {...config} parentNode={domNode} onLoaded={handlers.done} />,
domNode
);
},
});

View file

@ -16,6 +16,7 @@ export function plugin() {
export * from './expression_renderers';
export { LazyShapeDrawer } from './components/shape';
export { LazyProgressDrawer } from './components/progress';
export { getDefaultShapeData } from './components/reusable';
export * from './components/shape/types';
export * from './components/reusable/types';

View file

@ -8,8 +8,8 @@
import { CoreSetup, CoreStart, Plugin } from '../../../core/public';
import { ExpressionsStart, ExpressionsSetup } from '../../expressions/public';
import { shapeRenderer } from './expression_renderers';
import { shapeFunction } from '../common/expression_functions';
import { shapeRenderer, progressRenderer } from './expression_renderers';
import { shapeFunction, progressFunction } from '../common/expression_functions';
interface SetupDeps {
expressions: ExpressionsSetup;
@ -26,7 +26,9 @@ export class ExpressionShapePlugin
implements Plugin<ExpressionShapePluginSetup, ExpressionShapePluginStart, SetupDeps, StartDeps> {
public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionShapePluginSetup {
expressions.registerFunction(shapeFunction);
expressions.registerFunction(progressFunction);
expressions.registerRenderer(shapeRenderer);
expressions.registerRenderer(progressRenderer);
}
public start(core: CoreStart): ExpressionShapePluginStart {}

View file

@ -8,7 +8,7 @@
import { CoreSetup, CoreStart, Plugin } from '../../../core/public';
import { ExpressionsServerStart, ExpressionsServerSetup } from '../../expressions/server';
import { shapeFunction } from '../common/expression_functions';
import { shapeFunction, progressFunction } from '../common/expression_functions';
interface SetupDeps {
expressions: ExpressionsServerSetup;
@ -25,6 +25,7 @@ export class ExpressionShapePlugin
implements Plugin<ExpressionShapePluginSetup, ExpressionShapePluginStart, SetupDeps, StartDeps> {
public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionShapePluginSetup {
expressions.registerFunction(shapeFunction);
expressions.registerFunction(progressFunction);
}
public start(core: CoreStart): ExpressionShapePluginStart {}

View file

@ -35,7 +35,6 @@ import { lte } from './lte';
import { mapCenter } from './map_center';
import { neq } from './neq';
import { ply } from './ply';
import { progress } from './progress';
import { render } from './render';
import { replace } from './replace';
import { rounddate } from './rounddate';
@ -83,7 +82,6 @@ export const functions = [
mapCenter,
neq,
ply,
progress,
render,
replace,
rounddate,

View file

@ -1,175 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import {
functionWrapper,
fontStyle,
} from '../../../../../../src/plugins/presentation_util/common/lib';
import { getFunctionErrors } from '../../../i18n';
import { progress } from './progress';
const errors = getFunctionErrors().progress;
// TODO: this test was not running and is not up to date
describe.skip('progress', () => {
const fn = functionWrapper(progress);
const value = 0.33;
it('returns a render as progress', () => {
const result = fn(0.2);
expect(result).to.have.property('type', 'render').and.to.have.property('as', 'progress');
});
it('sets the progress to context', () => {
const result = fn(0.58);
expect(result.value).to.have.property('value', 0.58);
});
it(`throws when context is outside of the valid range`, () => {
expect(fn)
.withArgs(3)
.to.throwException(new RegExp(errors.invalidValue(3).message));
});
describe('args', () => {
describe('shape', () => {
it('sets the progress element shape', () => {
const result = fn(value, {
shape: 'wheel',
});
expect(result.value).to.have.property('shape', 'wheel');
});
it(`defaults to 'gauge'`, () => {
const result = fn(value);
expect(result.value).to.have.property('shape', 'gauge');
});
});
describe('max', () => {
it('sets the maximum value', () => {
const result = fn(value, {
max: 2,
});
expect(result.value).to.have.property('max', 2);
});
it('defaults to 1', () => {
const result = fn(value);
expect(result.value).to.have.property('max', 1);
});
it('throws if max <= 0', () => {
expect(fn)
.withArgs(value, { max: -0.5 })
.to.throwException(new RegExp(errors.invalidMaxValue(-0.5).message));
});
});
describe('valueColor', () => {
it('sets the color of the progress bar', () => {
const result = fn(value, {
valueColor: '#000000',
});
expect(result.value).to.have.property('valueColor', '#000000');
});
it(`defaults to '#1785b0'`, () => {
const result = fn(value);
expect(result.value).to.have.property('valueColor', '#1785b0');
});
});
describe('barColor', () => {
it('sets the color of the background bar', () => {
const result = fn(value, {
barColor: '#FFFFFF',
});
expect(result.value).to.have.property('barColor', '#FFFFFF');
});
it(`defaults to '#f0f0f0'`, () => {
const result = fn(value);
expect(result.value).to.have.property('barColor', '#f0f0f0');
});
});
describe('valueWeight', () => {
it('sets the thickness of the bars', () => {
const result = fn(value, {
valuWeight: 100,
});
expect(result.value).to.have.property('valuWeight', 100);
});
it(`defaults to 20`, () => {
const result = fn(value);
expect(result.value).to.have.property('barWeight', 20);
});
});
describe('barWeight', () => {
it('sets the thickness of the bars', () => {
const result = fn(value, {
barWeight: 50,
});
expect(result.value).to.have.property('barWeight', 50);
});
it(`defaults to 20`, () => {
const result = fn(value);
expect(result.value).to.have.property('barWeight', 20);
});
});
describe('label', () => {
it('sets the label of the progress', () => {
const result = fn(value, { label: 'foo' });
expect(result.value).to.have.property('label', 'foo');
});
it('hides the label if false', () => {
const result = fn(value, {
label: false,
});
expect(result.value).to.have.property('label', '');
});
it('defaults to true which sets the context as the label', () => {
const result = fn(value);
expect(result.value).to.have.property('label', '0.33');
});
});
describe('font', () => {
it('sets the font style for the label', () => {
const result = fn(value, {
font: fontStyle,
});
expect(result.value).to.have.property('font');
expect(result.value.font).to.have.keys(Object.keys(fontStyle));
expect(result.value.font.spec).to.have.keys(Object.keys(fontStyle.spec));
});
it('sets fill to color', () => {
const result = fn(value, {
font: fontStyle,
});
expect(result.value.font.spec).to.have.property('fill', fontStyle.spec.color);
});
// TODO: write test when using an instance of the interpreter
// it("sets a default style for the label when not provided", () => {});
});
});
});

View file

@ -1,130 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { get } from 'lodash';
import { openSans } from '../../../common/lib/fonts';
import { Render, Style, ExpressionFunctionDefinition } from '../../../types';
import { getFunctionHelp, getFunctionErrors } from '../../../i18n';
export enum Shape {
GAUGE = 'gauge',
HORIZONTAL_BAR = 'horizontalBar',
HORIZONTAL_PILL = 'horizontalPill',
SEMICIRCLE = 'semicircle',
UNICORN = 'unicorn',
VERTICAL_BAR = 'verticalBar',
VERTICAL_PILL = 'verticalPill',
WHEEL = 'wheel',
}
export interface Arguments {
barColor: string;
barWeight: number;
font: Style;
label: boolean | string;
max: number;
shape: Shape;
valueColor: string;
valueWeight: number;
}
export type Output = Arguments & {
value: number;
};
export function progress(): ExpressionFunctionDefinition<
'progress',
number,
Arguments,
Render<Arguments>
> {
const { help, args: argHelp } = getFunctionHelp().progress;
const errors = getFunctionErrors().progress;
return {
name: 'progress',
aliases: [],
type: 'render',
inputTypes: ['number'],
help,
args: {
shape: {
aliases: ['_'],
types: ['string'],
help: argHelp.shape,
options: Object.values(Shape),
default: 'gauge',
},
barColor: {
types: ['string'],
help: argHelp.barColor,
default: `#f0f0f0`,
},
barWeight: {
types: ['number'],
help: argHelp.barWeight,
default: 20,
},
font: {
types: ['style'],
help: argHelp.font,
default: `{font size=24 family="${openSans.value}" color="#000000" align=center}`,
},
label: {
types: ['boolean', 'string'],
help: argHelp.label,
default: true,
},
max: {
types: ['number'],
help: argHelp.max,
default: 1,
},
valueColor: {
types: ['string'],
help: argHelp.valueColor,
default: `#1785b0`,
},
valueWeight: {
types: ['number'],
help: argHelp.valueWeight,
default: 20,
},
},
fn: (value, args) => {
if (args.max <= 0) {
throw errors.invalidMaxValue(args.max);
}
if (value > args.max || value < 0) {
throw errors.invalidValue(value, args.max);
}
let label = '';
if (args.label) {
label = typeof args.label === 'string' ? args.label : `${value}`;
}
let font: Style = {} as Style;
if (get(args, 'font.spec')) {
font = { ...args.font };
font.spec.fill = args.font.spec.color; // SVG <text> uses fill for font color
}
return {
type: 'render',
as: 'progress',
value: {
value,
...args,
label,
font,
},
};
},
};
}

View file

@ -8,10 +8,9 @@
import { markdown } from './markdown';
import { pie } from './pie';
import { plot } from './plot';
import { progress } from './progress';
import { text } from './text';
import { table } from './table';
export const renderFunctions = [markdown, pie, plot, progress, table, text];
export const renderFunctions = [markdown, pie, plot, table, text];
export const renderFunctionFactories = [];

View file

@ -10,7 +10,10 @@ import { metricRenderer } from '../../../../../src/plugins/expression_metric/pub
import { errorRenderer, debugRenderer } from '../../../../../src/plugins/expression_error/public';
import { repeatImageRenderer } from '../../../../../src/plugins/expression_repeat_image/public';
import { revealImageRenderer } from '../../../../../src/plugins/expression_reveal_image/public';
import { shapeRenderer } from '../../../../../src/plugins/expression_shape/public';
import {
shapeRenderer,
progressRenderer,
} from '../../../../../src/plugins/expression_shape/public';
export const renderFunctions = [
debugRenderer,
@ -20,6 +23,7 @@ export const renderFunctions = [
revealImageRenderer,
shapeRenderer,
repeatImageRenderer,
progressRenderer,
];
export const renderFunctionFactories = [];

View file

@ -1,14 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots renderers/progress default 1`] = `
<div
style={
Object {
"height": "200px",
"width": "200px",
}
}
>
</div>
`;

View file

@ -1,126 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { getId } from '../../../public/lib/get_id';
import { RendererStrings } from '../../../i18n';
import { shapes } from './shapes';
import { Output as Arguments } from '../../functions/common/progress';
import { RendererFactory } from '../../../types';
const { progress: strings } = RendererStrings;
export const progress: RendererFactory<Arguments> = () => ({
name: 'progress',
displayName: strings.getDisplayName(),
help: strings.getHelpDescription(),
reuseDomNode: true,
render(domNode, config, handlers) {
const { shape, value, max, valueColor, barColor, valueWeight, barWeight, label, font } = config;
const percent = value / max;
const shapeDef = shapes[shape];
const offset = Math.max(valueWeight, barWeight);
if (shapeDef) {
const parser = new DOMParser();
const shapeSvg = parser
.parseFromString(shapeDef, 'image/svg+xml')
.getElementsByTagName('svg')
.item(0)!;
const initialViewBox = shapeSvg
.getAttribute('viewBox')!
.split(' ')
.map((v) => parseInt(v, 10));
let [minX, minY, width, height] = initialViewBox;
if (shape !== 'horizontalBar') {
minX -= offset / 2;
width += offset;
}
if (shape === 'semicircle') {
minY -= offset / 2;
height += offset / 2;
} else if (shape !== 'verticalBar') {
minY -= offset / 2;
height += offset;
}
shapeSvg.setAttribute('className', 'canvasProgress');
const svgId = getId('svg');
shapeSvg.id = svgId;
const bar = shapeSvg.getElementsByTagName('path').item(0)!;
bar.setAttribute('className', 'canvasProgress__background');
bar.setAttribute('fill', 'none');
bar.setAttribute('stroke', barColor);
bar.setAttribute('stroke-width', `${barWeight}px`);
const valueSvg = bar.cloneNode(true) as SVGPathElement;
valueSvg.setAttribute('className', 'canvasProgress__value');
valueSvg.setAttribute('stroke', valueColor);
valueSvg.setAttribute('stroke-width', `${valueWeight}px`);
const length = valueSvg.getTotalLength();
const to = length * (1 - percent);
valueSvg.setAttribute('stroke-dasharray', String(length));
valueSvg.setAttribute('stroke-dashoffset', String(Math.max(0, to)));
shapeSvg.appendChild(valueSvg);
const text = shapeSvg.getElementsByTagName('text').item(0);
if (label && text) {
text.textContent = String(label);
text.setAttribute('className', 'canvasProgress__label');
if (shape === 'horizontalPill') {
text.setAttribute('x', String(parseInt(text.getAttribute('x')!, 10) + offset / 2));
}
if (shape === 'verticalPill') {
text.setAttribute('y', String(parseInt(text.getAttribute('y')!, 10) - offset / 2));
}
Object.assign(text.style, font.spec);
shapeSvg.appendChild(text);
domNode.appendChild(shapeSvg);
const { width: labelWidth, height: labelHeight } = text.getBBox();
if (shape === 'horizontalBar' || shape === 'horizontalPill') {
text.setAttribute('x', String(parseInt(text.getAttribute('x')!, 10)));
width += labelWidth;
}
if (shape === 'verticalBar' || shape === 'verticalPill') {
if (labelWidth > width) {
minX = -labelWidth / 2;
width = labelWidth;
}
minY -= labelHeight;
height += labelHeight;
}
}
shapeSvg.setAttribute('viewBox', [minX, minY, width, height].join(' '));
shapeSvg.setAttribute('width', String(domNode.offsetWidth));
shapeSvg.setAttribute('height', String(domNode.offsetHeight));
if (domNode.firstChild) {
domNode.removeChild(domNode.firstChild);
}
domNode.appendChild(shapeSvg);
handlers.onResize(() => {
shapeSvg.setAttribute('width', String(domNode.offsetWidth));
shapeSvg.setAttribute('height', String(domNode.offsetHeight));
});
}
handlers.done();
},
});

View file

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120">
<path d="M 15 100 A 60 60 0 1 1 105 100" />
<text x="60" y="60" text-anchor='middle' dominant-baseline='central' />
</svg>

Before

Width:  |  Height:  |  Size: 190 B

View file

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 208 1">
<path d="M 0 1 L 200 1" />
<text x="208" y="0" text-anchor='start' dominant-baseline='central' />
</svg>

Before

Width:  |  Height:  |  Size: 170 B

View file

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 208 1">
<path d="M 0 1 L 200 1" stroke-linecap="round" />
<text x="208" y="0" text-anchor='start' dominant-baseline='central' />
</svg>

Before

Width:  |  Height:  |  Size: 193 B

View file

@ -1,26 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import gauge from '!!raw-loader!./gauge.svg';
import horizontalBar from '!!raw-loader!./horizontal_bar.svg';
import horizontalPill from '!!raw-loader!./horizontal_pill.svg';
import semicircle from '!!raw-loader!./semicircle.svg';
import unicorn from '!!raw-loader!./unicorn.svg';
import verticalBar from '!!raw-loader!./vertical_bar.svg';
import verticalPill from '!!raw-loader!./vertical_pill.svg';
import wheel from '!!raw-loader!./wheel.svg';
export const shapes = {
gauge,
horizontalBar,
horizontalPill,
semicircle,
unicorn,
verticalBar,
verticalPill,
wheel,
};

View file

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 60">
<path d="M 0 60 A 60 60 0 1 1 120 60" />
<text x="60" y="60" text-anchor='middle' dy="-1" />
</svg>

Before

Width:  |  Height:  |  Size: 166 B

View file

@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
<path d="M 123 189 C 93 141 129 126 102 96 L 78 102 L 48 117 L 42 129 Q 30 132 21 126 L 18 114 L 27 90 L 42 72 L 48 57 L 3 6 L 57 42 L 63 33 L 60 15 L 69 27 L 69 15 L 84 27 Q 162 36 195 108 Q 174 159 123 189 Z" />
<text x="0" y="200" text-anchor='start' dominant-baseline='text-after-edge' />
</svg>

Before

Width:  |  Height:  |  Size: 368 B

View file

@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -8 1 208">
<path d="M 1 200 L 1 0" />
<text x="0" y="-8" text-anchor='middle'/>
</svg>

Before

Width:  |  Height:  |  Size: 143 B

View file

@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -8 1 208">
<path d="M 1 200 L 1 0" stroke-linecap="round" />
<text x="0" y="-8" text-anchor='middle' />
</svg>

Before

Width:  |  Height:  |  Size: 167 B

View file

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120">
<path d="M 60 0 A 60 60 0 1 1 60 120 A 60 60 0 1 1 60 0 Z" />
<text x="60" y="60" text-anchor='middle' dominant-baseline='central' />
</svg>

Before

Width:  |  Height:  |  Size: 208 B

View file

@ -6,7 +6,7 @@
*/
import { openSans } from '../../../common/lib/fonts';
import { shapes } from '../../renderers/progress/shapes';
import { getAvailableProgressShapes } from '../../../../../../src/plugins/expression_shape/common';
import { ViewStrings } from '../../../i18n';
const { Progress: strings } = ViewStrings;
@ -23,7 +23,7 @@ export const progress = () => ({
help: strings.getShapeHelp(),
argType: 'select',
options: {
choices: Object.keys(shapes).map((key) => ({
choices: getAvailableProgressShapes().map((key) => ({
value: key,
//turns camel into title case
name: key[0].toUpperCase() + key.slice(1).replace(/([A-Z])/g, ' $1'),

View file

@ -1,88 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import { progress } from '../../../canvas_plugin_src/functions/common/progress';
import { FunctionHelp } from '../function_help';
import { FunctionFactory } from '../../../types';
import { Shape } from '../../../canvas_plugin_src/functions/common/progress';
import { CSS, FONT_FAMILY, FONT_WEIGHT, BOOLEAN_TRUE, BOOLEAN_FALSE } from '../../constants';
export const help: FunctionHelp<FunctionFactory<typeof progress>> = {
help: i18n.translate('xpack.canvas.functions.progressHelpText', {
defaultMessage: 'Configures a progress element.',
}),
args: {
barColor: i18n.translate('xpack.canvas.functions.progress.args.barColorHelpText', {
defaultMessage: 'The color of the background bar.',
}),
barWeight: i18n.translate('xpack.canvas.functions.progress.args.barWeightHelpText', {
defaultMessage: 'The thickness of the background bar.',
}),
font: i18n.translate('xpack.canvas.functions.progress.args.fontHelpText', {
defaultMessage:
'The {CSS} font properties for the label. For example, {FONT_FAMILY} or {FONT_WEIGHT}.',
values: {
CSS,
FONT_FAMILY,
FONT_WEIGHT,
},
}),
label: i18n.translate('xpack.canvas.functions.progress.args.labelHelpText', {
defaultMessage:
'To show or hide the label, use {BOOLEAN_TRUE} or {BOOLEAN_FALSE}. Alternatively, provide a string to display as a label.',
values: {
BOOLEAN_TRUE,
BOOLEAN_FALSE,
},
}),
max: i18n.translate('xpack.canvas.functions.progress.args.maxHelpText', {
defaultMessage: 'The maximum value of the progress element.',
}),
shape: i18n.translate('xpack.canvas.functions.progress.args.shapeHelpText', {
defaultMessage: `Select {list}, or {end}.`,
values: {
list: Object.values(Shape)
.slice(0, -1)
.map((shape) => `\`"${shape}"\``)
.join(', '),
end: `\`"${Object.values(Shape).slice(-1)[0]}"\``,
},
}),
valueColor: i18n.translate('xpack.canvas.functions.progress.args.valueColorHelpText', {
defaultMessage: 'The color of the progress bar.',
}),
valueWeight: i18n.translate('xpack.canvas.functions.progress.args.valueWeightHelpText', {
defaultMessage: 'The thickness of the progress bar.',
}),
},
};
export const errors = {
invalidMaxValue: (max: number) =>
new Error(
i18n.translate('xpack.canvas.functions.progress.invalidMaxValueErrorMessage', {
defaultMessage: "Invalid {arg} value: '{max, number}'. '{arg}' must be greater than 0",
values: {
arg: 'max',
max,
},
})
),
invalidValue: (value: number, max: number = 1) =>
new Error(
i18n.translate('xpack.canvas.functions.progress.invalidValueErrorMessage', {
defaultMessage:
"Invalid value: '{value, number}'. Value must be between 0 and {max, number}",
values: {
value,
max,
},
})
),
};

View file

@ -17,7 +17,6 @@ import { errors as getCell } from './dict/get_cell';
import { errors as joinRows } from './dict/join_rows';
import { errors as ply } from './dict/ply';
import { errors as pointseries } from './dict/pointseries';
import { errors as progress } from './dict/progress';
import { errors as timefilter } from './dict/timefilter';
import { errors as to } from './dict/to';
@ -34,7 +33,6 @@ export const getFunctionErrors = () => ({
joinRows,
ply,
pointseries,
progress,
timefilter,
to,
});

View file

@ -51,7 +51,6 @@ import { help as pie } from './dict/pie';
import { help as plot } from './dict/plot';
import { help as ply } from './dict/ply';
import { help as pointseries } from './dict/pointseries';
import { help as progress } from './dict/progress';
import { help as render } from './dict/render';
import { help as replace } from './dict/replace';
import { help as rounddate } from './dict/rounddate';
@ -207,7 +206,6 @@ export const getFunctionHelp = (): FunctionHelpDict => ({
plot,
ply,
pointseries,
progress,
render,
replace,
rounddate,

View file

@ -89,16 +89,6 @@ export const RendererStrings = {
defaultMessage: 'Render an XY plot from your data',
}),
},
progress: {
getDisplayName: () =>
i18n.translate('xpack.canvas.renderer.progress.displayName', {
defaultMessage: 'Progress indicator',
}),
getHelpDescription: () =>
i18n.translate('xpack.canvas.renderer.progress.helpDescription', {
defaultMessage: 'Render a progress indicator that reveals a percentage of an element',
}),
},
table: {
getDisplayName: () =>
i18n.translate('xpack.canvas.renderer.table.displayName', {

View file

@ -8,12 +8,11 @@
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import { get } from 'lodash';
import { getId } from '../../lib/get_id';
// @ts-expect-error untyped local
import { findExistingAsset } from '../../lib/find_existing_asset';
import { VALID_IMAGE_TYPES } from '../../../common/lib/constants';
import { encode } from '../../../../../../src/plugins/presentation_util/public';
import { getId } from '../../lib/get_id';
// @ts-expect-error untyped local
import { elementsRegistry } from '../../lib/elements_registry';
// @ts-expect-error untyped local

View file

@ -12,6 +12,7 @@ import { i18n } from '@kbn/i18n';
import { CANVAS, JSON as JSONString } from '../../../../i18n/constants';
import { useNotifyService } from '../../../services';
import { getId } from '../../../lib/get_id';
import { useCreateWorkpad } from './use_create_workpad';
import type { CanvasWorkpad } from '../../../../types';

View file

@ -37,7 +37,9 @@ export const ShapePreview: FC<Props> = ({ shape }) => {
const [shapeData, setShapeData] = useState<SvgConfig>(getDefaultShapeData());
const shapeRef = useCallback<RefCallback<ShapeRef>>((node) => {
if (node !== null) setShapeData(node.getData());
if (node !== null) {
setShapeData(node.getData());
}
}, []);
if (!shape) return <div className="canvasShapePreview" />;

View file

@ -15,11 +15,11 @@ import {
EuiContextMenuPanelItemDescriptor,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { getId } from '../../../lib/get_id';
import { Popover, ClosePopoverFn } from '../../popover';
import { CONTEXT_MENU_TOP_BORDER_CLASSNAME } from '../../../../common/lib';
import { ElementSpec } from '../../../../types';
import { flattenPanelTree } from '../../../lib/flatten_panel_tree';
import { getId } from '../../../lib/get_id';
import { AssetManager } from '../../asset_manager';
import { SavedElementsModal } from '../../saved_elements_modal';

View file

@ -5,6 +5,8 @@
* 2.0.
*/
import { getId as rawGetId } from '../get_id';
import { insideAABB, landmarkPoint, shapesAt } from './geometry';
import {
@ -40,8 +42,6 @@ import {
removeDuplicates,
} from './functional';
import { getId as rawGetId } from './../../lib/get_id';
const idMap = {};
const getId = (name, extension) => {
// ensures that `axisAlignedBoundingBoxShape` is pure-ish - a new call with the same input will not yield a new id

View file

@ -9,7 +9,6 @@ import moment from 'moment';
import { action } from '@storybook/addon-actions';
import { PluginServiceFactory } from '../../../../../../src/plugins/presentation_util/public';
import { getId } from '../../lib/get_id';
// @ts-expect-error
import { getDefaultWorkpad } from '../../state/defaults';

View file

@ -8,7 +8,6 @@
import { markdown } from '../canvas_plugin_src/renderers/markdown';
import { pie } from '../canvas_plugin_src/renderers/pie';
import { plot } from '../canvas_plugin_src/renderers/plot';
import { progress } from '../canvas_plugin_src/renderers/progress';
import { table } from '../canvas_plugin_src/renderers/table';
import { text } from '../canvas_plugin_src/renderers/text';
import { imageRenderer as image } from '../../../../src/plugins/expression_image/public';
@ -18,7 +17,10 @@ import {
} from '../../../../src/plugins/expression_error/public';
import { repeatImageRenderer as repeatImage } from '../../../../src/plugins/expression_repeat_image/public';
import { revealImageRenderer as revealImage } from '../../../../src/plugins/expression_reveal_image/public';
import { shapeRenderer as shape } from '../../../../src/plugins/expression_shape/public';
import {
shapeRenderer as shape,
progressRenderer as progress,
} from '../../../../src/plugins/expression_shape/public';
import { metricRenderer as metric } from '../../../../src/plugins/expression_metric/public';
/**

View file

@ -6727,17 +6727,17 @@
"xpack.canvas.functions.pointseries.args.yHelpText": "Y軸の値です。",
"xpack.canvas.functions.pointseries.unwrappedExpressionErrorMessage": "表現は {fn} などの関数で囲む必要があります",
"xpack.canvas.functions.pointseriesHelpText": "{DATATABLE} を点の配列モデルに変換します。現在 {TINYMATH} 式でディメンションのメジャーを区別します。{TINYMATH_URL} をご覧ください。引数に {TINYMATH} 式が入力された場合、その引数をメジャーとして使用し、そうでない場合はディメンションになります。ディメンションを組み合わせて固有のキーを作成します。その後メジャーはそれらのキーで、指定された {TINYMATH} 関数を使用して複製されます。",
"xpack.canvas.functions.progress.args.barColorHelpText": "背景バーの色です。",
"xpack.canvas.functions.progress.args.barWeightHelpText": "背景バーの太さです。",
"xpack.canvas.functions.progress.args.fontHelpText": "ラベルの {CSS} フォントプロパティです。例:{FONT_FAMILY} または {FONT_WEIGHT}。",
"xpack.canvas.functions.progress.args.labelHelpText": "ラベルの表示・非表示を切り替えるには、{BOOLEAN_TRUE}または{BOOLEAN_FALSE}を使用します。また、ラベルとして表示する文字列を入力することもできます。",
"xpack.canvas.functions.progress.args.maxHelpText": "進捗エレメントの最高値です。",
"xpack.canvas.functions.progress.args.shapeHelpText": "{list} または {end} を選択します。",
"xpack.canvas.functions.progress.args.valueColorHelpText": "進捗バーの色です。",
"xpack.canvas.functions.progress.args.valueWeightHelpText": "進捗バーの太さです。",
"xpack.canvas.functions.progress.invalidMaxValueErrorMessage": "無効な {arg} 値:「{max, number}」。「{arg}」は 0 より大きい必要があります",
"xpack.canvas.functions.progress.invalidValueErrorMessage": "無効な値:「{value, number}」。値は 0 と {max, number} の間でなければなりません",
"xpack.canvas.functions.progressHelpText": "進捗エレメントを構成します。",
"expressionShape.functions.progress.args.barColorHelpText": "背景バーの色です。",
"expressionShape.functions.progress.args.barWeightHelpText": "背景バーの太さです。",
"expressionShape.functions.progress.args.fontHelpText": "ラベルの {CSS} フォントプロパティです。例:{FONT_FAMILY} または {FONT_WEIGHT}。",
"expressionShape.functions.progress.args.labelHelpText": "ラベルの表示・非表示を切り替えるには、{BOOLEAN_TRUE}または{BOOLEAN_FALSE}を使用します。また、ラベルとして表示する文字列を入力することもできます。",
"expressionShape.functions.progress.args.maxHelpText": "進捗エレメントの最高値です。",
"expressionShape.functions.progress.args.shapeHelpText": "{list} または {end} を選択します。",
"expressionShape.functions.progress.args.valueColorHelpText": "進捗バーの色です。",
"expressionShape.functions.progress.args.valueWeightHelpText": "進捗バーの太さです。",
"expressionShape.functions.progress.invalidMaxValueErrorMessage": "無効な {arg} 値:「{max, number}」。「{arg}」は 0 より大きい必要があります",
"expressionShape.functions.progress.invalidValueErrorMessage": "無効な値:「{value, number}」。値は 0 と {max, number} の間でなければなりません",
"expressionShape.functions.progressHelpText": "進捗エレメントを構成します。",
"xpack.canvas.functions.render.args.asHelpText": "レンダリングに使用するエレメントタイプです。代わりに {plotFn} や {shapeFn} などの特殊な関数を使用するほうがいいでしょう。",
"xpack.canvas.functions.render.args.containerStyleHelpText": "背景、境界、透明度を含む、コンテナーのスタイルです。",
"xpack.canvas.functions.render.args.cssHelpText": "このエレメントの対象となるカスタム {CSS} のブロックです。",
@ -6926,8 +6926,8 @@
"xpack.canvas.renderer.pie.helpDescription": "データから円グラフをレンダリングします",
"xpack.canvas.renderer.plot.displayName": "座標プロット",
"xpack.canvas.renderer.plot.helpDescription": "データから XY プロットをレンダリングします",
"xpack.canvas.renderer.progress.displayName": "進捗インジケーター",
"xpack.canvas.renderer.progress.helpDescription": "エレメントのパーセンテージを示す進捗インジケーターをレンダリングします",
"expressionShape.renderer.progress.displayName": "進捗インジケーター",
"expressionShape.renderer.progress.helpDescription": "エレメントのパーセンテージを示す進捗インジケーターをレンダリングします",
"xpack.canvas.renderer.table.displayName": "データテーブル",
"xpack.canvas.renderer.table.helpDescription": "表形式データを {HTML} としてレンダリングします",
"xpack.canvas.renderer.text.displayName": "プレインテキスト",

View file

@ -6767,17 +6767,17 @@
"xpack.canvas.functions.pointseries.args.yHelpText": "Y 轴上的值。",
"xpack.canvas.functions.pointseries.unwrappedExpressionErrorMessage": "表达式必须包装在函数中,例如 {fn}",
"xpack.canvas.functions.pointseriesHelpText": "将 {DATATABLE} 转成点序列模型。当前我们通过寻找 {TINYMATH} 表达式来区分度量和维度。请参阅 {TINYMATH_URL}。如果在参数中输入 {TINYMATH} 表达式,我们将该参数视为度量,否则该参数为维度。维度将进行组合以创建唯一键。然后,这些键使用指定的 {TINYMATH} 函数消除重复的度量",
"xpack.canvas.functions.progress.args.barColorHelpText": "背景条形的颜色。",
"xpack.canvas.functions.progress.args.barWeightHelpText": "背景条形的粗细。",
"xpack.canvas.functions.progress.args.fontHelpText": "标签的 {CSS} 字体属性。例如 {FONT_FAMILY} 或 {FONT_WEIGHT}。",
"xpack.canvas.functions.progress.args.labelHelpText": "要显示或隐藏标签,请使用 {BOOLEAN_TRUE} 或 {BOOLEAN_FALSE}。或者,提供字符串以显示为标签。",
"xpack.canvas.functions.progress.args.maxHelpText": "进度元素的最大值。",
"xpack.canvas.functions.progress.args.shapeHelpText": "选择 {list} 或 {end}。",
"xpack.canvas.functions.progress.args.valueColorHelpText": "进度条的颜色。",
"xpack.canvas.functions.progress.args.valueWeightHelpText": "进度条的粗细。",
"xpack.canvas.functions.progress.invalidMaxValueErrorMessage": "无效的 {arg} 值:“{max, number}”。“{arg}”必须大于 0",
"xpack.canvas.functions.progress.invalidValueErrorMessage": "无效的值:“{value, number}”。值必须介于 0 和 {max, number} 之间",
"xpack.canvas.functions.progressHelpText": "配置进度元素。",
"expressionShape.functions.progress.args.barColorHelpText": "背景条形的颜色。",
"expressionShape.functions.progress.args.barWeightHelpText": "背景条形的粗细。",
"expressionShape.functions.progress.args.fontHelpText": "标签的 {CSS} 字体属性。例如 {FONT_FAMILY} 或 {FONT_WEIGHT}。",
"expressionShape.functions.progress.args.labelHelpText": "要显示或隐藏标签,请使用 {BOOLEAN_TRUE} 或 {BOOLEAN_FALSE}。或者,提供字符串以显示为标签。",
"expressionShape.functions.progress.args.maxHelpText": "进度元素的最大值。",
"expressionShape.functions.progress.args.shapeHelpText": "选择 {list} 或 {end}。",
"expressionShape.functions.progress.args.valueColorHelpText": "进度条的颜色。",
"expressionShape.functions.progress.args.valueWeightHelpText": "进度条的粗细。",
"expressionShape.functions.progress.invalidMaxValueErrorMessage": "无效的 {arg} 值:“{max, number}”。“{arg}”必须大于 0",
"expressionShape.functions.progress.invalidValueErrorMessage": "无效的值:“{value, number}”。值必须介于 0 和 {max, number} 之间",
"expressionShape.functions.progressHelpText": "配置进度元素。",
"xpack.canvas.functions.render.args.asHelpText": "要渲染的元素类型。您可能需要专门的函数,例如 {plotFn} 或 {shapeFn}。",
"xpack.canvas.functions.render.args.containerStyleHelpText": "容器的样式,包括背景、边框和透明度。",
"xpack.canvas.functions.render.args.cssHelpText": "要限定于元素的任何定制 {CSS} 块。",
@ -6966,8 +6966,8 @@
"xpack.canvas.renderer.pie.helpDescription": "根据您的数据呈现饼图",
"xpack.canvas.renderer.plot.displayName": "坐标图",
"xpack.canvas.renderer.plot.helpDescription": "根据您的数据呈现 XY 坐标图",
"xpack.canvas.renderer.progress.displayName": "进度指示",
"xpack.canvas.renderer.progress.helpDescription": "呈现显示元素百分比的进度指示",
"expressionShape.renderer.progress.displayName": "进度指示",
"expressionShape.renderer.progress.helpDescription": "呈现显示元素百分比的进度指示",
"xpack.canvas.renderer.table.displayName": "数据表",
"xpack.canvas.renderer.table.helpDescription": "将表格数据呈现为 {HTML}",
"xpack.canvas.renderer.text.displayName": "纯文本",