[Canvas] Expression shape (#103219)

* expression_reveal_image skeleton.

* expression_functions added.

* expression_renderers added.

* Backup of daily work.

* Fixed errors.

* Added legacy support. Added button for legacy.

* Added storybook.

* Removed revealImage from canvas.

* Types fixed.

* Fixed test suite error.

* Fixed eslint error.

* Moved UI and elements, related to expressionRevealImage from canvas.

* Fixed unused translations errors.

* Moved type of element to types.

* Fixed types and added service for representing elements, ui and supported renderers to canvas.

* Added expression registration to canvas.

* Fixed

* Fixed mutiple call of the function.

* Removed support of a legacy lib for revealImage chart.

* Removed legacy presentation_utils plugin import.

* Removed useless translations and tried to fix error.

* One more fix.

* Small imports fix.

* Fixed translations.

* Made fixes based on nits.

* Removed useless params.

* fix.

* Fixed errors, related to jest and __mocks__.

* Removed useless type definition.

* Replaced RendererHandlers with IInterpreterRendererHandlers.

* fixed supported_shareable.

* Moved elements back to canvas.

* Moved views to canvas, removed expression service and imported renderer to canvas.

* Fixed translations.

* Moved libs to presentation utils.

* Fixed types and removed function_wrapper.ts

* Fixed types of test helpers.

* Fixed imports.

* One more fix.

* Fixed public API.

* Moved css to component.

* Fixed spaces at element.

* Removed unused plugin.

* Basic setup of error plugin.

* Removed not used `function` files at `error` expression.

* Moved related components from canvas.

* Changed imports of components.

* Removed useless translations and fixed .i18nrc.json

* More fixes of i18nrc.

* Fixed async functions.

Written current code, based on https://github.com/storybookjs/storybook/issues/7745

* Fixed one test with Expression input.

After changing the way of rendering in stories, all elements are mounting and componentDidMount is involved. The previous snapshot was without
mounted `monaco` editor.

* generated plugin and copied code from expression_reveal_image

* fixed double import after merge.

* Changed all names from reveal_image to shape.

* moved shape to plugin and added all necessary configs

* Fixed translations, fixed all imports and debug of svg.

* `function` moved to `server`.

* One shape is rewritten to `React` and rendering is written with passing necessary props.

* changed default width and heigth.

* Added `ShapeHOC`.

* Shapes changed.

* small refactor.

* Removed useless import.

* one more refactor.

* Refactor + fix errors + updated limits.

* Changed ShapePreview from pure js to react and removed `dangerouslySetInnerHTML`

* Fixed types of viewbox.

* Changed types source for Shape components.

* small refactor.

* Fixed imports.

* Removed `shape` from `canvas`

* Updated docs.

* Basic setup of error plugin.

* Removed not used `function` files at `error` expression.

* Changed imports of components.

* Fixed errors, related to shape and autosuggestions.

* Fixed i18n for shape.

* Moved function from public to common and registered at server.

* Fixed types error.

* Fixed snapshots and shape mocks.

* Moved some libs from `presentations_util` to `expression_shape`

* Shape refactored.

* Shape picker fixed.

* Moved `Popover` back to `canvas`

* Removed `Popover` export from presentation_utils components.

* Moved error_component and debug_component from presentation_util to expression_error.

* Removed `.i18nrc.json`.

* Removed `.i18nrc.json`.

* Removed useless scss.

* Fixed color of `error`.

* added fixes of rebase.

* More fixes of rebase error .

* Removed useless .i18nrc.json file.

* More fixes.

* More fixes of rebase.

* One more fix.

* More fixes.

* Fixed limits and translations.

* Added.

* Fixed i18nrc.

* Fixed error..

* Moved shapes to async chunks.

* One more fix.

* Some fixes.

* Trying to fix the typecheck error.

* Added temp of drawer.

* Moved shapes to the async chunk in a less complex way.

* Made `ShapeDrawer` reusable among different `expressions`.

* Changed type of `shapes` from `any` and `Shape` to `string`.

* Made changes, based on nits.

* Removed not necessary changes.

* Moved all reusable libs to `expression_shapes`.

* Reduced the size of the bundle.

* Hope, fixed type check errors.

* Removed getDefaultShapeData.

* Removed `getViewBox` from bundle.
This commit is contained in:
Yaroslav Kuznietsov 2021-07-21 17:46:19 +03:00 committed by GitHub
parent 8460035eca
commit 473b6aad0f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
100 changed files with 1851 additions and 489 deletions

View file

@ -446,6 +446,7 @@ module.exports = {
'!(src|x-pack)/plugins/**/(public|server)/mocks/index.{js,mjs,ts}',
'!(src|x-pack)/plugins/**/(public|server)/(index|mocks).{js,mjs,ts,tsx}',
'!(src|x-pack)/plugins/**/__stories__/index.{js,mjs,ts,tsx}',
'!(src|x-pack)/plugins/**/__fixtures__/index.{js,mjs,ts,tsx}',
],
allowSameFolder: true,
errorMessage: 'Plugins may only import from top-level public and server modules.',

View file

@ -18,6 +18,7 @@
"expressions": "src/plugins/expressions",
"expressionError": "src/plugins/expression_error",
"expressionRevealImage": "src/plugins/expression_reveal_image",
"expressionShape": "src/plugins/expression_shape",
"inputControl": "src/plugins/input_control_vis",
"inspector": "src/plugins/inspector",
"inspectorViews": "src/legacy/core_plugins/inspector_views",

View file

@ -99,6 +99,10 @@ want to incorporate their own functions, types, and renderers into the service
for use in their own application.
|{kib-repo}blob/{branch}/src/plugins/expression_shape/README.md[expressionShape]
|Expression Shape plugin adds a shape function to the expression plugin and an associated renderer. The renderer will display the given shape with selected decorations.
|{kib-repo}blob/{branch}/src/plugins/home/README.md[home]
|Moves the legacy ui/registry/feature_catalogue module for registering "features" that should be shown in the home page's feature catalogue to a service within a "home" plugin. The feature catalogue refered to here should not be confused with the "feature" plugin for registering features used to derive UI capabilities for feature controls.

View file

@ -114,3 +114,4 @@ pageLoadAssetSize:
cases: 144442
expressionError: 22127
userSetup: 18532
expressionShape: 30033

View file

@ -19,6 +19,7 @@ export const storybookAliases = {
embeddable: 'src/plugins/embeddable/.storybook',
expression_error: 'src/plugins/expression_error/.storybook',
expression_reveal_image: 'src/plugins/expression_reveal_image/.storybook',
expression_shape: 'src/plugins/expression_shape/.storybook',
infra: 'x-pack/plugins/infra/.storybook',
security_solution: 'x-pack/plugins/security_solution/.storybook',
ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/.storybook',

View file

@ -0,0 +1,10 @@
/*
* 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.
*/
// eslint-disable-next-line import/no-commonjs
module.exports = require('@kbn/storybook').defaultConfig;

View file

@ -0,0 +1,9 @@
# expressionShape
Expression Shape plugin adds a `shape` function to the expression plugin and an associated renderer. The renderer will display the given shape with selected decorations.
---
## Development
See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment.

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 { shapeFunction } from '../common/expression_functions';
import { ExpressionFunction } from '../../../../src/plugins/expressions';
export const functionSpecs = [shapeFunction].map((fn) => new ExpressionFunction(fn()));

View file

@ -0,0 +1,9 @@
/*
* 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.
*/
export * from './function_specs';

View file

@ -0,0 +1,11 @@
/*
* 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.
*/
export const PLUGIN_ID = 'expressionShape';
export const PLUGIN_NAME = 'expressionShape';
export const SVG = 'SVG';

View file

@ -0,0 +1,9 @@
/*
* 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.
*/
export { shapeFunction } from './shape_function';

View file

@ -0,0 +1,106 @@
/*
* 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 { ExpressionShapeFunction, Shape } from '../types';
import { SVG } from '../constants';
import { getAvailableShapes } from '../lib';
export const strings = {
help: i18n.translate('expressionShape.functions.shapeHelpText', {
defaultMessage: 'Creates a shape.',
}),
args: {
shape: i18n.translate('expressionShape.functions.shape.args.shapeHelpText', {
defaultMessage: 'Pick a shape.',
}),
border: i18n.translate('expressionShape.functions.shape.args.borderHelpText', {
defaultMessage: 'An {SVG} color for the border outlining the shape.',
values: {
SVG,
},
}),
borderWidth: i18n.translate('expressionShape.functions.shape.args.borderWidthHelpText', {
defaultMessage: 'The thickness of the border.',
}),
fill: i18n.translate('expressionShape.functions.shape.args.fillHelpText', {
defaultMessage: 'An {SVG} color to fill the shape.',
values: {
SVG,
},
}),
maintainAspect: i18n.translate('expressionShape.functions.shape.args.maintainAspectHelpText', {
defaultMessage: `Maintain the shape's original aspect ratio?`,
}),
},
};
export const errors = {
invalidShape: (shape: string) =>
new Error(
i18n.translate('expressionShape.functions.shape.invalidShapeErrorMessage', {
defaultMessage: "Invalid value: '{shape}'. Such a shape doesn't exist.",
values: {
shape,
},
})
),
};
export const shapeFunction: ExpressionShapeFunction = () => {
const { help, args: argHelp } = strings;
return {
name: 'shape',
aliases: [],
inputTypes: ['null'],
help,
args: {
shape: {
types: ['string'],
help: argHelp.shape,
aliases: ['_'],
default: 'square',
options: Object.values(Shape),
},
border: {
types: ['string'],
aliases: ['stroke'],
help: argHelp.border,
},
borderWidth: {
types: ['number'],
aliases: ['strokeWidth'],
help: argHelp.borderWidth,
default: 0,
},
fill: {
types: ['string'],
help: argHelp.fill,
default: 'black',
},
maintainAspect: {
types: ['boolean'],
help: argHelp.maintainAspect,
default: false,
options: [true, false],
},
},
fn: (input, args) => {
const avaliableShapes = getAvailableShapes();
if (!avaliableShapes.includes(args.shape)) {
throw errors.invalidShape(args.shape);
}
return {
type: 'shape',
...args,
};
},
};
};

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.
*/
export * from './constants';
export * from './types';
export { getAvailableShapes } from './lib/available_shapes';

View file

@ -0,0 +1,11 @@
/*
* 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 { Shape } from '../types';
export const getAvailableShapes = () => Object.values(Shape);

View file

@ -0,0 +1,10 @@
/*
* 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.
*/
export * from './view_box';
export * from './available_shapes';

View file

@ -0,0 +1,52 @@
/*
* 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 { ParentNodeParams, ViewBoxParams } from '../types';
export function viewBoxToString(viewBox?: ViewBoxParams): undefined | string {
if (!viewBox) return;
return `${viewBox?.minX} ${viewBox?.minY} ${viewBox?.width} ${viewBox?.height}`;
}
function getMinxAndWidth(viewBoxParams: ViewBoxParams, { borderOffset, width }: ParentNodeParams) {
let { minX, width: shapeWidth } = viewBoxParams;
if (width) {
const xOffset = (shapeWidth / width) * borderOffset;
minX -= xOffset;
shapeWidth += xOffset * 2;
} else {
shapeWidth = 0;
}
return [minX, shapeWidth];
}
function getMinyAndHeight(
viewBoxParams: ViewBoxParams,
{ borderOffset, height }: ParentNodeParams
) {
let { minY, height: shapeHeight } = viewBoxParams;
if (height) {
const yOffset = (shapeHeight / height) * borderOffset;
minY -= yOffset;
shapeHeight += yOffset * 2;
} else {
shapeHeight = 0;
}
return [minY, shapeHeight];
}
export function getViewBox(
viewBoxParams: ViewBoxParams,
parentNodeParams: ParentNodeParams
): ViewBoxParams {
const [minX, width] = getMinxAndWidth(viewBoxParams, parentNodeParams);
const [minY, height] = getMinyAndHeight(viewBoxParams, parentNodeParams);
return { minX, minY, width, height };
}

View file

@ -0,0 +1,46 @@
/*
* 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 { ExpressionFunctionDefinition } from 'src/plugins/expressions';
export enum Shape {
ARROW = 'arrow',
ARROW_MULTI = 'arrowMulti',
BOOKMARK = 'bookmark',
CIRCLE = 'circle',
CROSS = 'cross',
HEXAGON = 'hexagon',
KITE = 'kite',
PENTAGON = 'pentagon',
RHOMBUS = 'rhombus',
SEMICIRCLE = 'semicircle',
SPEECH_BUBBLE = 'speechBubble',
SQUARE = 'square',
STAR = 'star',
TAG = 'tag',
TRIANGLE = 'triangle',
TRIANGLE_RIGHT = 'triangleRight',
}
interface Arguments {
border: string;
borderWidth: number;
shape: Shape;
fill: string;
maintainAspect: boolean;
}
export interface Output extends Arguments {
type: 'shape';
}
export type ExpressionShapeFunction = () => ExpressionFunctionDefinition<
'shape',
number | null,
Arguments,
Output
>;

View file

@ -0,0 +1,35 @@
/*
* 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 { Shape } from './expression_functions';
export type OriginString = 'bottom' | 'left' | 'top' | 'right';
export interface ShapeRendererConfig {
border: string;
borderWidth: number;
shape: Shape;
fill: string;
maintainAspect: boolean;
}
export interface NodeDimensions {
width: number;
height: number;
}
export interface ParentNodeParams {
borderOffset: number;
width: number;
height: number;
}
export interface ViewBoxParams {
minX: number;
minY: number;
width: number;
height: number;
}

View file

@ -0,0 +1,9 @@
/*
* 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.
*/
export * from './expression_functions';
export * from './expression_renderers';

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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../..',
roots: ['<rootDir>/src/plugins/expression_shape'],
};

View file

@ -0,0 +1,13 @@
{
"id": "expressionShape",
"version": "1.0.0",
"kibanaVersion": "kibana",
"server": true,
"ui": true,
"extraPublicDirs": [
"common"
],
"requiredPlugins": ["expressions", "presentationUtil"],
"optionalPlugins": [],
"requiredBundles": []
}

View file

@ -0,0 +1,11 @@
/*
* 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.
*/
export * from './shape_drawer';
export * from './utils';
export * from './types';

View file

@ -0,0 +1,23 @@
/*
* 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, { forwardRef, Ref, useImperativeHandle } from 'react';
import { ShapeDrawerProps, ShapeRef } from './types';
function ShapeDrawerComponent(props: ShapeDrawerProps, ref: Ref<ShapeRef>) {
const { shapeType, getShape } = props;
const Shape = getShape(shapeType);
if (!Shape) throw new Error("Shape doesn't exist.");
useImperativeHandle(ref, () => ({ getData: () => Shape.data }), [Shape]);
return <Shape.Component {...props} />;
}
export const ShapeDrawer = forwardRef(ShapeDrawerComponent);

View file

@ -0,0 +1,49 @@
/*
* 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 { 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;
const viewBox = shapeAttributes?.viewBox
? viewBoxToString(shapeAttributes?.viewBox)
: viewBoxToString(initialViewBox);
const SvgContentElement = getShapeContentElement(shapeType);
return (
<svg xmlns="http://www.w3.org/2000/svg" {...{ ...(shapeAttributes || {}), viewBox }}>
<SvgContentElement {...{ ...(shapeContentAttributes || {}), ...shapeProps }} />
</svg>
);
};
function getShapeContentElement(type?: SvgElementTypes) {
switch (type) {
case SvgElementTypes.circle:
return (props: SvgConfig['shapeProps']) => <circle {...props} />;
case SvgElementTypes.rect:
return (props: SvgConfig['shapeProps']) => <rect {...props} />;
case SvgElementTypes.path:
return (props: SvgConfig['shapeProps']) => <path {...props} />;
default:
return (props: SvgConfig['shapeProps']) => <polygon {...props} />;
}
}
export const createShape = (props: SvgConfig) => {
return {
Component: getShapeComponent(props),
data: props,
};
};
export type ShapeType = ReturnType<typeof createShape>;

View file

@ -0,0 +1,83 @@
/*
* 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 { Ref, SVGProps } from 'react';
import { ViewBoxParams } from '../../../common/types';
import type { ShapeType } from './shape_factory';
export interface ShapeProps {
shapeAttributes?: ShapeAttributes;
shapeContentAttributes?: ShapeContentAttributes;
}
export enum SvgElementTypes {
polygon,
circle,
rect,
path,
}
export interface ShapeAttributes {
fill?: SVGProps<SVGElement>['fill'];
stroke?: SVGProps<SVGElement>['stroke'];
width?: SVGProps<SVGElement>['width'];
height?: SVGProps<SVGElement>['height'];
viewBox?: ViewBoxParams;
overflow?: SVGProps<SVGElement>['overflow'];
preserveAspectRatio?: SVGProps<SVGElement>['preserveAspectRatio'];
}
export interface ShapeContentAttributes {
strokeWidth?: SVGProps<SVGElement>['strokeWidth'];
stroke?: SVGProps<SVGElement>['stroke'];
fill?: SVGProps<SVGElement>['fill'];
vectorEffect?: SVGProps<SVGElement>['vectorEffect'];
strokeMiterlimit?: SVGProps<SVGElement>['strokeMiterlimit'];
}
interface CircleParams {
r: SVGProps<SVGCircleElement>['r'];
cx: SVGProps<SVGCircleElement>['cx'];
cy: SVGProps<SVGCircleElement>['cy'];
}
interface RectParams {
x: SVGProps<SVGRectElement>['x'];
y: SVGProps<SVGRectElement>['y'];
width: SVGProps<SVGRectElement>['width'];
height: SVGProps<SVGRectElement>['height'];
}
interface PathParams {
d: SVGProps<SVGPathElement>['d'];
}
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 ShapeDrawerProps = {
shapeType: string;
getShape: (shapeType: string) => ShapeType | undefined;
ref: Ref<ShapeRef>;
} & ShapeProps;
export interface ShapeRef {
getData: () => SvgConfig;
}
export type { ShapeType };

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 { SvgConfig } from './types';
export const getDefaultShapeData = (): SvgConfig => ({
viewBox: {
minX: 0,
minY: 0,
width: 0,
height: 0,
},
shapeProps: {},
});

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 LazyShapeComponent = lazy(() => import('./shape_component'));
export const LazyShapeDrawer = lazy(() => import('./shape_drawer'));

View file

@ -0,0 +1,93 @@
/*
* 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, { useState, useEffect, useCallback, RefCallback } from 'react';
import { useResizeObserver } from '@elastic/eui';
import { withSuspense } from '../../../../presentation_util/public';
import {
ShapeRef,
ShapeAttributes,
ShapeContentAttributes,
SvgConfig,
getDefaultShapeData,
} from '../reusable';
import { Dimensions, ShapeComponentProps } from './types';
import { getViewBox } from '../../../common/lib';
import { LazyShapeDrawer } from '../..';
const ShapeDrawer = withSuspense(LazyShapeDrawer);
function ShapeComponent({
onLoaded,
parentNode,
shape: shapeType,
fill,
border,
borderWidth,
maintainAspect,
}: ShapeComponentProps) {
const parentNodeDimensions = useResizeObserver(parentNode);
const [dimensions, setDimensions] = useState<Dimensions>({
width: parentNode.offsetWidth,
height: parentNode.offsetHeight,
});
const [shapeData, setShapeData] = useState<SvgConfig>(getDefaultShapeData());
useEffect(() => {
setDimensions({
width: parentNode.offsetWidth,
height: parentNode.offsetHeight,
});
onLoaded();
}, [parentNode, parentNodeDimensions, onLoaded]);
const shapeRef = useCallback<RefCallback<ShapeRef>>((node) => {
if (node !== null) setShapeData(node.getData());
}, []);
const strokeWidth = Math.max(borderWidth, 0);
const shapeContentAttributes: ShapeContentAttributes = {
strokeWidth: String(strokeWidth),
vectorEffect: 'non-scaling-stroke',
strokeMiterlimit: '999',
};
if (fill) shapeContentAttributes.fill = fill;
if (border) shapeContentAttributes.stroke = border;
const { width, height } = dimensions;
const shapeAttributes: ShapeAttributes = {
width,
height,
overflow: 'visible',
preserveAspectRatio: maintainAspect ? 'xMidYMid meet' : 'none',
viewBox: getViewBox(shapeData.viewBox, {
borderOffset: strokeWidth,
width,
height,
}),
};
parentNode.style.lineHeight = '0';
return (
<div className="shapeAligner">
<ShapeDrawer
shapeType={shapeType}
shapeContentAttributes={shapeContentAttributes}
shapeAttributes={shapeAttributes}
ref={shapeRef}
/>
</div>
);
}
// default export required for React.Lazy
// eslint-disable-next-line import/no-default-export
export { ShapeComponent as default };

View file

@ -0,0 +1,20 @@
/*
* 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 } from '../reusable';
import { getShape } from './shapes';
import { ShapeDrawerComponentProps } from './types';
const ShapeDrawerComponent = React.forwardRef(
(props: ShapeDrawerComponentProps, ref: Ref<ShapeRef>) => (
<ShapeDrawer {...props} ref={ref} getShape={getShape} />
)
);
// eslint-disable-next-line import/no-default-export
export { ShapeDrawerComponent as default };

View file

@ -0,0 +1,20 @@
/*
* 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';
export const Arrow = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 100,
height: 100,
},
shapeProps: {
points: '0,40 60,40 60,20 95,50 60,80 60,60 0,60',
},
});

View file

@ -0,0 +1,20 @@
/*
* 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';
export const ArrowMulti = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 100,
height: 60,
},
shapeProps: {
points: '5,30 25,10 25,20 75,20 75,10 95,30 75,50 75,40 25,40 25,50',
},
});

View file

@ -0,0 +1,21 @@
/*
* 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';
export const Bookmark = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 60,
height: 100,
},
shapeProps: {
points: '0,0 60,0 60,95 30,75 0,95 0,0',
},
});

View file

@ -0,0 +1,25 @@
/*
* 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/types';
export const Circle = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 100,
height: 100,
},
shapeProps: {
r: '45',
cx: '50',
cy: '50',
},
shapeType: SvgElementTypes.circle,
});

View file

@ -0,0 +1,21 @@
/*
* 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';
export const Cross = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 100,
height: 100,
},
shapeProps: {
points: '30,0 70,0 70,30 100,30 100,70 70,70 70,100 30,100 30,70 0,70 0,30 30,30',
},
});

View file

@ -0,0 +1,22 @@
/*
* 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';
export const Hexagon = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 100,
height: 100,
},
shapeProps: {
points:
'70.000, 15.359 30.000, 15.359 10.000, 50.000 30.000, 84.641 70.000, 84.641 90.000, 50.000',
},
});

View file

@ -0,0 +1,46 @@
/*
* 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 { Arrow as arrow } from './arrow';
import { ArrowMulti as arrowMulti } from './arrow_multi';
import { Bookmark as bookmark } from './bookmark';
import { Cross as cross } from './cross';
import { Circle as circle } from './circle';
import { Hexagon as hexagon } from './hexagon';
import { Kite as kite } from './kite';
import { Pentagon as pentagon } from './pentagon';
import { Rhombus as rhombus } from './rhombus';
import { Semicircle as semicircle } from './semicircle';
import { SpeechBubble as speechBubble } from './speech_bubble';
import { Square as square } from './square';
import { Star as star } from './star';
import { Tag as tag } from './tag';
import { Triangle as triangle } from './triangle';
import { TriangleRight as triangleRight } from './triangle_right';
import { ShapeType } from '../../reusable';
const shapes: { [key: string]: ShapeType } = {
arrow,
arrowMulti,
bookmark,
cross,
circle,
hexagon,
kite,
pentagon,
rhombus,
semicircle,
speechBubble,
square,
star,
tag,
triangle,
triangleRight,
};
export const getShape = (shapeType: string) => shapes[shapeType];

View file

@ -0,0 +1,21 @@
/*
* 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';
export const Kite = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 100,
height: 150,
},
shapeProps: {
points: '50,10 10,50 50,140 90,50',
},
});

View file

@ -0,0 +1,21 @@
/*
* 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';
export const Pentagon = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 100,
height: 100,
},
shapeProps: {
points: '50.0000, 14.0000 11.9577, 41.6393 26.4886, 86.3607 73.5114, 86.3607 88.0423, 41.6393',
},
});

View file

@ -0,0 +1,21 @@
/*
* 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';
export const Rhombus = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 100,
height: 100,
},
shapeProps: {
points: '50,10 10,50 50,90 90,50',
},
});

View file

@ -0,0 +1,23 @@
/*
* 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/types';
export const Semicircle = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 100,
height: 100,
},
shapeProps: {
d: 'M 5,50 h 90 A 45 45 180 1 0 5,50 Z',
},
shapeType: SvgElementTypes.path,
});

View file

@ -0,0 +1,22 @@
/*
* 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';
export const SpeechBubble = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 100,
height: 100,
},
shapeProps: {
points: '0,0 100,0 100,70 40,70 20,85 25,70 0,70',
strokeLinejoin: 'round',
},
});

View file

@ -0,0 +1,26 @@
/*
* 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/types';
export const Square = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 100,
height: 100,
},
shapeProps: {
x: '0',
y: '0',
width: '100',
height: '100',
},
shapeType: SvgElementTypes.rect,
});

View file

@ -0,0 +1,22 @@
/*
* 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';
export const Star = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 100,
height: 100,
},
shapeProps: {
points:
'41.183, 37.865 12.652, 37.865 35.734, 54.635 26.917, 81.771 50.000, 65.000 73.265, 81.904 64.266, 54.635 87.348, 37.865 58.817, 37.865 50.07, 10.515',
},
});

View file

@ -0,0 +1,21 @@
/*
* 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';
export const Tag = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 100,
height: 60,
},
shapeProps: {
points: '0,0 75,0 90,30 75,60 0,60',
},
});

View file

@ -0,0 +1,21 @@
/*
* 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';
export const Triangle = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 100,
height: 100,
},
shapeProps: {
points: '50.000, 20.000 15.359, 80.000 84.641, 80.000',
},
});

View file

@ -0,0 +1,21 @@
/*
* 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';
export const TriangleRight = createShape({
viewBox: {
minX: 0,
minY: 0,
width: 100,
height: 100,
},
shapeProps: {
points: '0, 10 0, 100 90, 100',
},
});

View file

@ -0,0 +1,22 @@
/*
* 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 { IInterpreterRenderHandlers } from '../../../../../../src/plugins/expressions';
import { ShapeRendererConfig } from '../../../common/types';
import { ShapeDrawerProps } from '../reusable/types';
export interface ShapeComponentProps extends ShapeRendererConfig {
onLoaded: IInterpreterRenderHandlers['done'];
parentNode: HTMLElement;
}
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 { shape } from '../';
import { Render } from '../../__stories__/render';
import { Shape } from '../../../functions/common/shape';
import { shapeRenderer as shape } from '../';
import { Render } from '../../../../presentation_util/public/__stories__';
import { Shape } from '../../../common/types';
storiesOf('renderers/shape', module).add('default', () => {
const config = {

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 { shapeRenderer } from './shape_renderer';
export const renderers = [shapeRenderer];
export { shapeRenderer };

View file

@ -0,0 +1,51 @@
/*
* 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 { I18nProvider } from '@kbn/i18n/react';
import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { i18n } from '@kbn/i18n';
import { withSuspense } from '../../../presentation_util/public';
import { ShapeRendererConfig } from '../../common/types';
import { LazyShapeComponent } from '../components/shape';
const strings = {
getDisplayName: () =>
i18n.translate('expressionShape.renderer.shape.displayName', {
defaultMessage: 'Shape',
}),
getHelpDescription: () =>
i18n.translate('expressionShape.renderer.shape.helpDescription', {
defaultMessage: 'Render a basic shape',
}),
};
const ShapeComponent = withSuspense(LazyShapeComponent);
export const shapeRenderer = (): ExpressionRenderDefinition<ShapeRendererConfig> => ({
name: 'shape',
displayName: strings.getDisplayName(),
help: strings.getHelpDescription(),
reuseDomNode: true,
render: async (
domNode: HTMLElement,
config: ShapeRendererConfig,
handlers: IInterpreterRenderHandlers
) => {
handlers.onDestroy(() => {
unmountComponentAtNode(domNode);
});
render(
<I18nProvider>
<ShapeComponent onLoaded={handlers.done} {...config} parentNode={domNode} />
</I18nProvider>,
domNode
);
},
});

View file

@ -0,0 +1,22 @@
/*
* 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 { ExpressionShapePlugin } from './plugin';
export type { ExpressionShapePluginSetup, ExpressionShapePluginStart } from './plugin';
export function plugin() {
return new ExpressionShapePlugin();
}
export * from './expression_renderers';
export { LazyShapeDrawer } from './components/shape';
export { getDefaultShapeData } from './components/reusable';
export * from './components/shape/types';
export * from './components/reusable/types';
export * from '../common/types';

View file

@ -0,0 +1,33 @@
/*
* 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 { CoreSetup, CoreStart, Plugin } from '../../../core/public';
import { ExpressionsStart, ExpressionsSetup } from '../../expressions/public';
import { shapeRenderer } from './expression_renderers';
interface SetupDeps {
expressions: ExpressionsSetup;
}
interface StartDeps {
expression: ExpressionsStart;
}
export type ExpressionShapePluginSetup = void;
export type ExpressionShapePluginStart = void;
export class ExpressionShapePlugin
implements Plugin<ExpressionShapePluginSetup, ExpressionShapePluginStart, SetupDeps, StartDeps> {
public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionShapePluginSetup {
expressions.registerRenderer(shapeRenderer);
}
public start(core: CoreStart): ExpressionShapePluginStart {}
public stop() {}
}

View file

@ -0,0 +1,15 @@
/*
* 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 { ExpressionShapePlugin } from './plugin';
export type { ExpressionShapePluginSetup, ExpressionShapePluginStart } from './plugin';
export function plugin() {
return new ExpressionShapePlugin();
}

View file

@ -0,0 +1,33 @@
/*
* 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 { CoreSetup, CoreStart, Plugin } from '../../../core/public';
import { ExpressionsServerStart, ExpressionsServerSetup } from '../../expressions/server';
import { shapeFunction } from '../common/expression_functions';
interface SetupDeps {
expressions: ExpressionsServerSetup;
}
interface StartDeps {
expression: ExpressionsServerStart;
}
export type ExpressionShapePluginSetup = void;
export type ExpressionShapePluginStart = void;
export class ExpressionShapePlugin
implements Plugin<ExpressionShapePluginSetup, ExpressionShapePluginStart, SetupDeps, StartDeps> {
public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionShapePluginSetup {
expressions.registerFunction(shapeFunction);
}
public start(core: CoreStart): ExpressionShapePluginStart {}
public stop() {}
}

View file

@ -0,0 +1,22 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"outDir": "./target/types",
"emitDeclarationOnly": true,
"declaration": true,
"declarationMap": true,
"isolatedModules": true
},
"include": [
"common/**/*",
"public/**/*",
"server/**/*",
"__fixtures__/**/*",
],
"references": [
{ "path": "../../core/tsconfig.json" },
{ "path": "../presentation_util/tsconfig.json" },
{ "path": "../expressions/tsconfig.json" },
]
}

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import React, { Suspense, ComponentType, ReactElement } from 'react';
import React, { Suspense, ComponentType, ReactElement, Ref } from 'react';
import { EuiLoadingSpinner, EuiErrorBoundary } from '@elastic/eui';
/**
@ -14,16 +14,19 @@ import { EuiLoadingSpinner, EuiErrorBoundary } from '@elastic/eui';
* @param Component A component deferred by `React.lazy`
* @param fallback A fallback component to render while things load; default is `EuiLoadingSpinner`
*/
export const withSuspense = <P extends {}>(
export const withSuspense = <P extends {}, R = {}>(
Component: ComponentType<P>,
fallback: ReactElement | null = <EuiLoadingSpinner />
) => (props: P) => (
<EuiErrorBoundary>
<Suspense fallback={fallback}>
<Component {...props} />
</Suspense>
</EuiErrorBoundary>
);
) =>
React.forwardRef((props: P, ref: Ref<R>) => {
return (
<EuiErrorBoundary>
<Suspense fallback={fallback}>
<Component {...props} ref={ref} />
</Suspense>
</EuiErrorBoundary>
);
});
export const LazyLabsBeakerButton = React.lazy(() => import('./labs/labs_beaker_button'));
@ -34,3 +37,5 @@ export const LazyDashboardPicker = React.lazy(() => import('./dashboard_picker')
export const LazySavedObjectSaveModalDashboard = React.lazy(
() => import('./saved_object_save_modal_dashboard')
);
export * from './types';

View file

@ -38,6 +38,8 @@ export {
withSuspense,
} from './components';
export * from './components/types';
export {
AddFromLibraryButton,
PrimaryActionButton,

View file

@ -8,7 +8,9 @@
import { functions as browserFns } from '../canvas_plugin_src/functions/browser';
import { ExpressionFunction } from '../../../../src/plugins/expressions';
import { initFunctions } from '../public/functions';
import { functionSpecs as shapeFunctionSpecs } from '../../../../src/plugins/expression_shape/__fixtures__';
export const functionSpecs = browserFns
.concat(...(initFunctions({} as any) as any))
.map((fn) => new ExpressionFunction(fn()));
.map((fn) => new ExpressionFunction(fn()))
.concat(...shapeFunctionSpecs);

View file

@ -44,7 +44,6 @@ import { rounddate } from './rounddate';
import { rowCount } from './rowCount';
import { repeatImage } from './repeat_image';
import { seriesStyle } from './seriesStyle';
import { shape } from './shape';
import { sort } from './sort';
import { staticColumn } from './staticColumn';
import { string } from './string';
@ -96,7 +95,6 @@ export const functions = [
rounddate,
rowCount,
seriesStyle,
shape,
sort,
staticColumn,
string,

View file

@ -1,87 +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 { ExpressionFunctionDefinition } from 'src/plugins/expressions';
import { getFunctionHelp } from '../../../i18n';
export enum Shape {
ARROW = 'arrow',
ARROW_MULTI = 'arrowMulti',
BOOKMARK = 'bookmark',
CIRCLE = 'circle',
CROSS = 'cross',
HEXAGON = 'hexagon',
KITE = 'kite',
PENTAGON = 'pentagon',
RHOMBUS = 'rhombus',
SEMICIRCLE = 'semicircle',
SPEECH_BUBBLE = 'speechBubble',
SQUARE = 'square',
STAR = 'star',
TAG = 'tag',
TRIANGLE = 'triangle',
TRIANGLE_RIGHT = 'triangleRight',
}
interface Arguments {
border: string;
borderWidth: number;
shape: Shape;
fill: string;
maintainAspect: boolean;
}
export interface Output extends Arguments {
type: 'shape';
}
export function shape(): ExpressionFunctionDefinition<'shape', null, Arguments, Output> {
const { help, args: argHelp } = getFunctionHelp().shape;
return {
name: 'shape',
aliases: [],
type: 'shape',
inputTypes: ['null'],
help,
args: {
shape: {
types: ['string'],
help: argHelp.shape,
aliases: ['_'],
default: 'square',
options: Object.values(Shape),
},
border: {
types: ['string'],
aliases: ['stroke'],
help: argHelp.border,
},
borderWidth: {
types: ['number'],
aliases: ['strokeWidth'],
help: argHelp.borderWidth,
default: 0,
},
fill: {
types: ['string'],
help: argHelp.fill,
default: 'black',
},
maintainAspect: {
types: ['boolean'],
help: argHelp.maintainAspect,
default: false,
options: [true, false],
},
},
fn: (input, args) => ({
type: 'shape',
...args,
}),
};
}

View file

@ -12,7 +12,6 @@ import { pie } from './pie';
import { plot } from './plot';
import { progress } from './progress';
import { repeatImage } from './repeat_image';
import { shape } from './shape';
import { table } from './table';
import { text } from './text';
@ -24,7 +23,6 @@ export const renderFunctions = [
plot,
progress,
repeatImage,
shape,
table,
text,
];

View file

@ -7,6 +7,7 @@
import { revealImageRenderer } from '../../../../../src/plugins/expression_reveal_image/public';
import { errorRenderer, debugRenderer } from '../../../../../src/plugins/expression_error/public';
import { shapeRenderer } from '../../../../../src/plugins/expression_shape/public';
export const renderFunctions = [revealImageRenderer, errorRenderer, debugRenderer];
export const renderFunctions = [revealImageRenderer, debugRenderer, errorRenderer, shapeRenderer];
export const renderFunctionFactories = [];

View file

@ -1,91 +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 { RendererStrings } from '../../../i18n';
import { shapes } from './shapes';
import { RendererFactory } from '../../../types';
import { Output } from '../../functions/common/shape';
const { shape: strings } = RendererStrings;
export const shape: RendererFactory<Output> = () => ({
name: 'shape',
displayName: strings.getDisplayName(),
help: strings.getHelpDescription(),
reuseDomNode: true,
render(domNode, config, handlers) {
const { shape: shapeType, fill, border, borderWidth, maintainAspect } = config;
const parser = new DOMParser();
const shapeSvg = parser
.parseFromString(shapes[shapeType], 'image/svg+xml')
.getElementsByTagName('svg')
.item(0)!;
const shapeContent = shapeSvg.firstElementChild!;
if (fill) {
shapeContent.setAttribute('fill', fill);
}
if (border) {
shapeContent.setAttribute('stroke', border);
}
const strokeWidth = Math.max(borderWidth, 0);
shapeContent.setAttribute('stroke-width', String(strokeWidth));
shapeContent.setAttribute('stroke-miterlimit', '999');
shapeContent.setAttribute('vector-effect', 'non-scaling-stroke');
shapeSvg.setAttribute('preserveAspectRatio', maintainAspect ? 'xMidYMid meet' : 'none');
shapeSvg.setAttribute('overflow', 'visible');
const initialViewBox = shapeSvg
.getAttribute('viewBox')!
.split(' ')
.map((v) => parseInt(v, 10));
const draw = () => {
const width = domNode.offsetWidth;
const height = domNode.offsetHeight;
// adjust viewBox based on border width
let [minX, minY, shapeWidth, shapeHeight] = initialViewBox;
const borderOffset = strokeWidth;
if (width) {
const xOffset = (shapeWidth / width) * borderOffset;
minX -= xOffset;
shapeWidth += xOffset * 2;
} else {
shapeWidth = 0;
}
if (height) {
const yOffset = (shapeHeight / height) * borderOffset;
minY -= yOffset;
shapeHeight += yOffset * 2;
} else {
shapeHeight = 0;
}
shapeSvg.setAttribute('width', String(width));
shapeSvg.setAttribute('height', String(height));
shapeSvg.setAttribute('viewBox', [minX, minY, shapeWidth, shapeHeight].join(' '));
const oldShape = domNode.firstElementChild;
if (oldShape) {
domNode.removeChild(oldShape);
}
domNode.style.lineHeight = '0';
domNode.appendChild(shapeSvg);
};
draw();
handlers.done();
handlers.onResize(draw); // debouncing avoided for fluidity
},
});

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<polygon points="0,40 60,40 60,20 95,50 60,80 60,60 0,60" />
</svg>

Before

Width:  |  Height:  |  Size: 132 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 60">
<polygon points="5,30 25,10 25,20 75,20 75,10 95,30 75,50 75,40 25,40 25,50" />
</svg>

Before

Width:  |  Height:  |  Size: 150 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 100">
<polygon points="0,0 60,0 60,95 30,75 0,95 0,0"/>
</svg>

Before

Width:  |  Height:  |  Size: 121 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle r="45" cx="50" cy="50" />
</svg>

Before

Width:  |  Height:  |  Size: 105 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<polygon points="30,0 70,0 70,30 100,30 100,70 70,70 70,100 30,100 30,70 0,70 0,30 30,30"/>
</svg>

Before

Width:  |  Height:  |  Size: 163 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<polygon points="70.000, 15.359 30.000, 15.359 10.000, 50.000 30.000, 84.641 70.000, 84.641 90.000, 50.000"/>
</svg>

Before

Width:  |  Height:  |  Size: 183 B

View file

@ -1,42 +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 arrow from '!!raw-loader!./arrow.svg';
import arrowMulti from '!!raw-loader!./arrow_multi.svg';
import bookmark from '!!raw-loader!./bookmark.svg';
import cross from '!!raw-loader!./cross.svg';
import circle from '!!raw-loader!./circle.svg';
import hexagon from '!!raw-loader!./hexagon.svg';
import kite from '!!raw-loader!./kite.svg';
import pentagon from '!!raw-loader!./pentagon.svg';
import rhombus from '!!raw-loader!./rhombus.svg';
import semicircle from '!!raw-loader!./semicircle.svg';
import speechBubble from '!!raw-loader!./speech_bubble.svg';
import square from '!!raw-loader!./square.svg';
import star from '!!raw-loader!./star.svg';
import tag from '!!raw-loader!./tag.svg';
import triangle from '!!raw-loader!./triangle.svg';
import triangleRight from '!!raw-loader!./triangle_right.svg';
export const shapes = {
arrow,
arrowMulti,
bookmark,
cross,
circle,
hexagon,
kite,
pentagon,
rhombus,
semicircle,
speechBubble,
square,
star,
tag,
triangle,
triangleRight,
};

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 150">
<polygon points="50,10 10,50 50,140 90,50"/>
</svg>

Before

Width:  |  Height:  |  Size: 116 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<polygon points="50.0000, 14.0000 11.9577, 41.6393 26.4886, 86.3607 73.5114, 86.3607 88.0423, 41.6393"/>
</svg>

Before

Width:  |  Height:  |  Size: 176 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<polygon points="50,10 10,50 50,90 90,50"/>
</svg>

Before

Width:  |  Height:  |  Size: 115 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 50">
<path d="M 5,50 h 90 A 45 45 180 1 0 5,50 Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 117 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<polygon points="0,0 100,0 100,70 40,70 20,85 25,70 0,70" stroke-linejoin="round" />
</svg>

Before

Width:  |  Height:  |  Size: 156 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect x="0" y="0" width="100" height="100" />
</svg>

Before

Width:  |  Height:  |  Size: 118 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<polygon points="41.183, 37.865 12.652, 37.865 35.734, 54.635 26.917, 81.771 50.000, 65.000 73.265, 81.904 64.266, 54.635 87.348, 37.865 58.817, 37.865 50.07, 10.515" />
</svg>

Before

Width:  |  Height:  |  Size: 241 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 60">
<polygon points="0,0 75,0 90,30 75,60 0,60"/>
</svg>

Before

Width:  |  Height:  |  Size: 116 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" >
<polygon points="50.000, 20.000 15.359, 80.000 84.641, 80.000"/>
</svg>

Before

Width:  |  Height:  |  Size: 137 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" >
<polygon points="0, 10 0, 100 90, 100"/>
</svg>

Before

Width:  |  Height:  |  Size: 113 B

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { shapes } from '../../renderers/shape/shapes';
import { getAvailableShapes } from '../../../../../../src/plugins/expression_shape/common';
import { ViewStrings } from '../../../i18n';
const { Shape: strings } = ViewStrings;
@ -21,7 +21,7 @@ export const shape = () => ({
displayName: strings.getShapeDisplayName(),
argType: 'shape',
options: {
shapes,
shapes: getAvailableShapes(),
},
},
{

View file

@ -1,41 +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 { shape } from '../../../canvas_plugin_src/functions/common/shape';
import { FunctionHelp } from '../function_help';
import { FunctionFactory } from '../../../types';
import { SVG } from '../../constants';
export const help: FunctionHelp<FunctionFactory<typeof shape>> = {
help: i18n.translate('xpack.canvas.functions.shapeHelpText', {
defaultMessage: 'Creates a shape.',
}),
args: {
shape: i18n.translate('xpack.canvas.functions.shape.args.shapeHelpText', {
defaultMessage: 'Pick a shape.',
}),
border: i18n.translate('xpack.canvas.functions.shape.args.borderHelpText', {
defaultMessage: 'An {SVG} color for the border outlining the shape.',
values: {
SVG,
},
}),
borderWidth: i18n.translate('xpack.canvas.functions.shape.args.borderWidthHelpText', {
defaultMessage: 'The thickness of the border.',
}),
fill: i18n.translate('xpack.canvas.functions.shape.args.fillHelpText', {
defaultMessage: 'An {SVG} color to fill the shape.',
values: {
SVG,
},
}),
maintainAspect: i18n.translate('xpack.canvas.functions.shape.args.maintainAspectHelpText', {
defaultMessage: `Maintain the shape's original aspect ratio?`,
}),
},
};

View file

@ -64,7 +64,6 @@ import { help as savedMap } from './dict/saved_map';
import { help as savedSearch } from './dict/saved_search';
import { help as savedVisualization } from './dict/saved_visualization';
import { help as seriesStyle } from './dict/series_style';
import { help as shape } from './dict/shape';
import { help as sort } from './dict/sort';
import { help as staticColumn } from './dict/static_column';
import { help as string } from './dict/string';
@ -224,7 +223,6 @@ export const getFunctionHelp = (): FunctionHelpDict => ({
savedSearch,
savedVisualization,
seriesStyle,
shape,
sort,
staticColumn,
string,

View file

@ -129,16 +129,6 @@ export const RendererStrings = {
defaultMessage: 'Repeat an image a given number of times',
}),
},
shape: {
getDisplayName: () =>
i18n.translate('xpack.canvas.renderer.shape.displayName', {
defaultMessage: 'Shape',
}),
getHelpDescription: () =>
i18n.translate('xpack.canvas.renderer.shape.helpDescription', {
defaultMessage: 'Render a basic shape',
}),
},
table: {
getDisplayName: () =>
i18n.translate('xpack.canvas.renderer.table.displayName', {

View file

@ -13,6 +13,7 @@
"expressionError",
"expressionRevealImage",
"expressions",
"expressionShape",
"features",
"inspector",
"presentationUtil",

View file

@ -15,14 +15,18 @@ exports[`Storyshots components/Shapes/ShapePicker default 1`] = `
>
<div
className="canvasShapePreview"
dangerouslySetInnerHTML={
Object {
"__html": "<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2.5 -2.5 105 105\\" fill=\\"none\\" stroke=\\"black\\">
<polygon points=\\"0,40 60,40 60,20 95,50 60,80 60,60 0,60\\"/>
</svg>",
}
}
/>
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
points="0,40 60,40 60,20 95,50 60,80 60,60 0,60"
/>
</svg>
</div>
</button>
</div>
<div
@ -36,14 +40,374 @@ exports[`Storyshots components/Shapes/ShapePicker default 1`] = `
>
<div
className="canvasShapePreview"
dangerouslySetInnerHTML={
Object {
"__html": "<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2.5 -2.5 105 105\\" fill=\\"none\\" stroke=\\"black\\">
<rect x=\\"0\\" y=\\"0\\" width=\\"100\\" height=\\"100\\"/>
</svg>",
}
}
/>
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 65"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
points="5,30 25,10 25,20 75,20 75,10 95,30 75,50 75,40 25,40 25,50"
/>
</svg>
</div>
</button>
</div>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
disabled={false}
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 65 105"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
points="0,0 60,0 60,95 30,75 0,95 0,0"
/>
</svg>
</div>
</button>
</div>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
disabled={false}
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="50"
cy="50"
r="45"
/>
</svg>
</div>
</button>
</div>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
disabled={false}
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
points="30,0 70,0 70,30 100,30 100,70 70,70 70,100 30,100 30,70 0,70 0,30 30,30"
/>
</svg>
</div>
</button>
</div>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
disabled={false}
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
points="70.000, 15.359 30.000, 15.359 10.000, 50.000 30.000, 84.641 70.000, 84.641 90.000, 50.000"
/>
</svg>
</div>
</button>
</div>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
disabled={false}
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 155"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
points="50,10 10,50 50,140 90,50"
/>
</svg>
</div>
</button>
</div>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
disabled={false}
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
points="50.0000, 14.0000 11.9577, 41.6393 26.4886, 86.3607 73.5114, 86.3607 88.0423, 41.6393"
/>
</svg>
</div>
</button>
</div>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
disabled={false}
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
points="50,10 10,50 50,90 90,50"
/>
</svg>
</div>
</button>
</div>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
disabled={false}
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M 5,50 h 90 A 45 45 180 1 0 5,50 Z"
/>
</svg>
</div>
</button>
</div>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
disabled={false}
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
points="0,0 100,0 100,70 40,70 20,85 25,70 0,70"
strokeLinejoin="round"
/>
</svg>
</div>
</button>
</div>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
disabled={false}
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<rect
height="100"
width="100"
x="0"
y="0"
/>
</svg>
</div>
</button>
</div>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
disabled={false}
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
points="41.183, 37.865 12.652, 37.865 35.734, 54.635 26.917, 81.771 50.000, 65.000 73.265, 81.904 64.266, 54.635 87.348, 37.865 58.817, 37.865 50.07, 10.515"
/>
</svg>
</div>
</button>
</div>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
disabled={false}
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 65"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
points="0,0 75,0 90,30 75,60 0,60"
/>
</svg>
</div>
</button>
</div>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
disabled={false}
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
points="50.000, 20.000 15.359, 80.000 84.641, 80.000"
/>
</svg>
</div>
</button>
</div>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
disabled={false}
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
points="0, 10 0, 100 90, 100"
/>
</svg>
</div>
</button>
</div>
</div>

View file

@ -9,9 +9,8 @@ import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import React from 'react';
import { ShapePicker } from '../shape_picker';
import { shapes } from '../../../../canvas_plugin_src/renderers/shape/shapes';
import { getAvailableShapes } from '../../../../../../../src/plugins/expression_shape/common';
storiesOf('components/Shapes/ShapePicker', module).add('default', () => (
<ShapePicker shapes={shapes} onChange={action('onChange')} />
<ShapePicker shapes={getAvailableShapes()} onChange={action('onChange')} />
));

View file

@ -9,29 +9,24 @@ import React, { FC } from 'react';
import PropTypes from 'prop-types';
import { EuiFlexGrid, EuiFlexItem, EuiLink } from '@elastic/eui';
import { ShapePreview } from '../shape_preview';
import { Shape } from '../../../../../../src/plugins/expression_shape/common';
interface Props {
shapes: {
[key: string]: string;
};
shapes: Shape[];
onChange?: (key: string) => void;
}
export const ShapePicker: FC<Props> = ({ shapes, onChange = () => {} }) => {
return (
<EuiFlexGrid gutterSize="s" columns={4} className="canvasShapePicker">
{Object.keys(shapes)
.sort()
.map((shapeKey) => (
<EuiFlexItem key={shapeKey}>
<EuiLink onClick={() => onChange(shapeKey)}>
<ShapePreview shape={shapes[shapeKey]} />
</EuiLink>
</EuiFlexItem>
))}
</EuiFlexGrid>
);
};
export const ShapePicker: FC<Props> = ({ shapes, onChange = () => {} }) => (
<EuiFlexGrid gutterSize="s" columns={4} className="canvasShapePicker">
{shapes.sort().map((shapeKey) => (
<EuiFlexItem key={shapeKey}>
<EuiLink onClick={() => onChange(shapeKey)}>
<ShapePreview shape={shapeKey} />
</EuiLink>
</EuiFlexItem>
))}
</EuiFlexGrid>
);
ShapePicker.propTypes = {
onChange: PropTypes.func,

View file

@ -53,14 +53,21 @@ exports[`Storyshots components/Shapes/ShapePickerPopover interactive 1`] = `
>
<div
className="canvasShapePreview"
dangerouslySetInnerHTML={
Object {
"__html": "<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2.5 -2.5 105 105\\" fill=\\"none\\" stroke=\\"black\\">
<rect x=\\"0\\" y=\\"0\\" width=\\"100\\" height=\\"100\\"/>
</svg>",
}
}
/>
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<rect
height="100"
width="100"
x="0"
y="0"
/>
</svg>
</div>
</button>
</div>
</div>
@ -90,14 +97,21 @@ exports[`Storyshots components/Shapes/ShapePickerPopover shape selected 1`] = `
>
<div
className="canvasShapePreview"
dangerouslySetInnerHTML={
Object {
"__html": "<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2.5 -2.5 105 105\\" fill=\\"none\\" stroke=\\"black\\">
<rect x=\\"0\\" y=\\"0\\" width=\\"100\\" height=\\"100\\"/>
</svg>",
}
}
/>
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<rect
height="100"
width="100"
x="0"
y="0"
/>
</svg>
</div>
</button>
</div>
</div>

View file

@ -9,18 +9,20 @@ import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import React from 'react';
import { ShapePickerPopover } from '../shape_picker_popover';
import { shapes } from '../../../../canvas_plugin_src/renderers/shape/shapes';
import {
getAvailableShapes,
Shape,
} from '../../../../../../../src/plugins/expression_shape/common';
class Interactive extends React.Component<{}, { value: string }> {
public state = {
value: 'square',
value: Shape.SQUARE,
};
public render() {
return (
<ShapePickerPopover
shapes={shapes}
shapes={getAvailableShapes()}
onChange={(value) => this.setState({ value })}
value={this.state.value}
/>
@ -29,9 +31,15 @@ class Interactive extends React.Component<{}, { value: string }> {
}
storiesOf('components/Shapes/ShapePickerPopover', module)
.add('default', () => <ShapePickerPopover shapes={shapes} onChange={action('onChange')} />)
.add('default', () => (
<ShapePickerPopover shapes={getAvailableShapes()} onChange={action('onChange')} />
))
.add('shape selected', () => (
<ShapePickerPopover shapes={shapes} onChange={action('onChange')} value="square" />
<ShapePickerPopover
shapes={getAvailableShapes()}
onChange={action('onChange')}
value={Shape.SQUARE}
/>
))
.add('interactive', () => <Interactive />, {
info: {

View file

@ -11,13 +11,12 @@ import { EuiLink, EuiPanel } from '@elastic/eui';
import { Popover } from '../popover';
import { ShapePicker } from '../shape_picker';
import { ShapePreview } from '../shape_preview';
import { Shape } from '../../../../../../src/plugins/expression_shape/common';
interface Props {
shapes: {
[key: string]: string;
};
shapes: Shape[];
onChange?: (key: string) => void;
value?: string;
value?: Shape;
ariaLabel?: string;
}
@ -25,7 +24,7 @@ export const ShapePickerPopover: FC<Props> = ({ shapes, onChange, value, ariaLab
const button = (handleClick: React.MouseEventHandler<any>) => (
<EuiPanel paddingSize="s" hasShadow={false}>
<EuiLink aria-label={ariaLabel} style={{ fontSize: 0 }} onClick={handleClick}>
<ShapePreview shape={value ? shapes[value] : undefined} />
<ShapePreview shape={value} />
</EuiLink>
</EuiPanel>
);

View file

@ -3,25 +3,36 @@
exports[`Storyshots components/Shapes/ShapePreview arrow 1`] = `
<div
className="canvasShapePreview"
dangerouslySetInnerHTML={
Object {
"__html": "<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2.5 -2.5 105 105\\" fill=\\"none\\" stroke=\\"black\\">
<polygon points=\\"0,40 60,40 60,20 95,50 60,80 60,60 0,60\\"/>
</svg>",
}
}
/>
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
points="0,40 60,40 60,20 95,50 60,80 60,60 0,60"
/>
</svg>
</div>
`;
exports[`Storyshots components/Shapes/ShapePreview square 1`] = `
<div
className="canvasShapePreview"
dangerouslySetInnerHTML={
Object {
"__html": "<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2.5 -2.5 105 105\\" fill=\\"none\\" stroke=\\"black\\">
<rect x=\\"0\\" y=\\"0\\" width=\\"100\\" height=\\"100\\"/>
</svg>",
}
}
/>
>
<svg
fill="none"
stroke="black"
viewBox="-2.5 -2.5 105 105"
xmlns="http://www.w3.org/2000/svg"
>
<rect
height="100"
width="100"
x="0"
y="0"
/>
</svg>
</div>
`;

View file

@ -8,9 +8,8 @@
import { storiesOf } from '@storybook/react';
import React from 'react';
import { ShapePreview } from '../shape_preview';
import { shapes } from '../../../../canvas_plugin_src/renderers/shape/shapes';
import { Shape } from '../../../../../../../src/plugins/expression_shape/public';
storiesOf('components/Shapes/ShapePreview', module)
.add('arrow', () => <ShapePreview shape={shapes.arrow} />)
.add('square', () => <ShapePreview shape={shapes.square} />);
.add('arrow', () => <ShapePreview shape={Shape.ARROW} />)
.add('square', () => <ShapePreview shape={Shape.SQUARE} />);

View file

@ -5,45 +5,55 @@
* 2.0.
*/
import React, { FC } from 'react';
import React, { FC, RefCallback, useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import {
LazyShapeDrawer,
Shape,
ShapeDrawerComponentProps,
getDefaultShapeData,
SvgConfig,
ShapeRef,
ViewBoxParams,
} from '../../../../../../src/plugins/expression_shape/public';
import { withSuspense } from '../../../../../../src/plugins/presentation_util/public';
interface Props {
shape?: string;
shape?: Shape;
}
const ShapeDrawer = withSuspense<ShapeDrawerComponentProps, ShapeRef>(LazyShapeDrawer);
function getViewBox(defaultWidth: number, defaultViewBox: ViewBoxParams): ViewBoxParams {
const { minX, minY, width, height } = defaultViewBox;
return {
minX: minX - defaultWidth / 2,
minY: minY - defaultWidth / 2,
width: width + defaultWidth,
height: height + defaultWidth,
};
}
export const ShapePreview: FC<Props> = ({ shape }) => {
if (!shape) {
return <div className="canvasShapePreview" />;
}
const [shapeData, setShapeData] = useState<SvgConfig>(getDefaultShapeData());
const weight = 5;
const parser = new DOMParser();
const shapeSvg = parser
.parseFromString(shape, 'image/svg+xml')
.getElementsByTagName('svg')
.item(0);
if (!shapeSvg) {
throw new Error('An unexpected error occurred: the SVG was not parseable');
}
shapeSvg.setAttribute('fill', 'none');
shapeSvg.setAttribute('stroke', 'black');
const viewBox = shapeSvg.getAttribute('viewBox') || '0 0 0 0';
const initialViewBox = viewBox.split(' ').map((v: string) => parseInt(v, 10));
let [minX, minY, width, height] = initialViewBox;
minX -= weight / 2;
minY -= weight / 2;
width += weight;
height += weight;
shapeSvg.setAttribute('viewBox', [minX, minY, width, height].join(' '));
const shapeRef = useCallback<RefCallback<ShapeRef>>((node) => {
if (node !== null) setShapeData(node.getData());
}, []);
if (!shape) return <div className="canvasShapePreview" />;
return (
// eslint-disable-next-line react/no-danger
<div className="canvasShapePreview" dangerouslySetInnerHTML={{ __html: shapeSvg.outerHTML }} />
<div className="canvasShapePreview">
<ShapeDrawer
ref={shapeRef}
shapeType={shape}
shapeAttributes={{
fill: 'none',
stroke: 'black',
viewBox: getViewBox(5, shapeData.viewBox),
}}
/>
</div>
);
};

View file

@ -12,7 +12,6 @@ import { metric } from '../canvas_plugin_src/renderers/metric';
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 { shape } from '../canvas_plugin_src/renderers/shape';
import { table } from '../canvas_plugin_src/renderers/table';
import { text } from '../canvas_plugin_src/renderers/text';
import { revealImageRenderer as revealImage } from '../../../../src/plugins/expression_reveal_image/public';
@ -20,6 +19,7 @@ import {
errorRenderer as error,
debugRenderer as debug,
} from '../../../../src/plugins/expression_error/public';
import { shapeRenderer as shape } from '../../../../src/plugins/expression_shape/public';
/**
* This is a collection of renderers which are bundled with the runtime. If

View file

@ -39,19 +39,6 @@ jest.mock('../public/lib/ui_metric', () => ({ trackCanvasUiMetric: () => {} }));
// Mock EUI generated ids to be consistently predictable for snapshots.
jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`);
// Jest automatically mocks SVGs to be a plain-text string that isn't an SVG. Canvas uses
// them in examples, so let's mock a few for tests.
jest.mock('../canvas_plugin_src/renderers/shape/shapes', () => ({
shapes: {
arrow: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<polygon points="0,40 60,40 60,20 95,50 60,80 60,60 0,60" />
</svg>`,
square: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect x="0" y="0" width="100" height="100" />
</svg>`,
},
}));
// Mock react-datepicker dep used by eui to avoid rendering the entire large component
jest.mock('@elastic/eui/packages/react-datepicker', () => {
return {

View file

@ -33,6 +33,7 @@
{ "path": "../../../src/plugins/expressions/tsconfig.json" },
{ "path": "../../../src/plugins/expression_error/tsconfig.json" },
{ "path": "../../../src/plugins/expression_reveal_image/tsconfig.json" },
{ "path": "../../../src/plugins/expression_shape/tsconfig.json" },
{ "path": "../../../src/plugins/home/tsconfig.json" },
{ "path": "../../../src/plugins/inspector/tsconfig.json" },
{ "path": "../../../src/plugins/kibana_legacy/tsconfig.json" },

View file

@ -6347,12 +6347,12 @@
"xpack.canvas.functions.seriesStyle.args.pointsHelpText": "線上の点のサイズです。",
"xpack.canvas.functions.seriesStyle.args.stackHelpText": "数列をスタックするかを指定します。数字はスタック ID です。同じスタック ID の数列は一緒にスタックされます。",
"xpack.canvas.functions.seriesStyleHelpText": "チャートの数列のプロパティの説明に使用されるオブジェクトを作成します。{plotFn} や {pieFn} のように、チャート関数内で {seriesStyleFn} を使用します。",
"xpack.canvas.functions.shape.args.borderHelpText": "図形の外郭の {SVG} カラーです。",
"xpack.canvas.functions.shape.args.borderWidthHelpText": "境界の太さです。",
"xpack.canvas.functions.shape.args.fillHelpText": "図形を塗りつぶす {SVG} カラーです。",
"xpack.canvas.functions.shape.args.maintainAspectHelpText": "図形の元の横縦比を維持しますか?",
"xpack.canvas.functions.shape.args.shapeHelpText": "図形を選択します。",
"xpack.canvas.functions.shapeHelpText": "図形を作成します。",
"expressionShape.functions.shape.args.borderHelpText": "図形の外郭の {SVG} カラーです。",
"expressionShape.functions.shape.args.borderWidthHelpText": "境界の太さです。",
"expressionShape.functions.shape.args.fillHelpText": "図形を塗りつぶす {SVG} カラーです。",
"expressionShape.functions.shape.args.maintainAspectHelpText": "図形の元の横縦比を維持しますか?",
"expressionShape.functions.shape.args.shapeHelpText": "図形を選択します。",
"expressionShape.functions.shapeHelpText": "図形を作成します。",
"xpack.canvas.functions.sort.args.byHelpText": "並べ替えの基準となる列です。指定されていない場合、{DATATABLE}は初めの列で並べられます。",
"xpack.canvas.functions.sort.args.reverseHelpText": "並び順を反転させます。指定されていない場合、{DATATABLE}は昇順で並べられます。",
"xpack.canvas.functions.sortHelpText": "{DATATABLE}を指定された列で並べ替えます。",
@ -6509,8 +6509,8 @@
"xpack.canvas.renderer.progress.helpDescription": "エレメントのパーセンテージを示す進捗インジケーターをレンダリングします",
"xpack.canvas.renderer.repeatImage.displayName": "画像の繰り返し",
"xpack.canvas.renderer.repeatImage.helpDescription": "画像を指定回数繰り返し表示します",
"xpack.canvas.renderer.shape.displayName": "形状",
"xpack.canvas.renderer.shape.helpDescription": "基本的な図形をレンダリングします",
"expressionShape.renderer.shape.displayName": "形状",
"expressionShape.renderer.shape.helpDescription": "基本的な図形をレンダリングします",
"xpack.canvas.renderer.table.displayName": "データテーブル",
"xpack.canvas.renderer.table.helpDescription": "表形式データを {HTML} としてレンダリングします",
"xpack.canvas.renderer.text.displayName": "プレインテキスト",

View file

@ -6071,8 +6071,6 @@
"xpack.canvas.elements.progressWheelHelpText": "将进度显示为轮盘的一部分",
"xpack.canvas.elements.repeatImageDisplayName": "图像重复",
"xpack.canvas.elements.repeatImageHelpText": "使图像重复 N 次",
"xpack.canvas.elements.revealImageDisplayName": "图像显示",
"xpack.canvas.elements.revealImageHelpText": "显示图像特定百分比",
"xpack.canvas.elements.shapeDisplayName": "形状",
"xpack.canvas.elements.shapeHelpText": "可定制的形状",
"xpack.canvas.elements.tableDisplayName": "数据表",
@ -6386,12 +6384,12 @@
"xpack.canvas.functions.seriesStyle.args.pointsHelpText": "折线图上的点大小。",
"xpack.canvas.functions.seriesStyle.args.stackHelpText": "指定是否应堆叠序列。数字为堆叠 ID。具有相同堆叠 ID 的序列将堆叠在一起。",
"xpack.canvas.functions.seriesStyleHelpText": "创建用于在图表上描述序列属性的对象。在绘图函数 (如 {plotFn} 或 {pieFn} ) 内使用 {seriesStyleFn}。",
"xpack.canvas.functions.shape.args.borderHelpText": "形状轮廓边框的 {SVG} 颜色。",
"xpack.canvas.functions.shape.args.borderWidthHelpText": "边框的粗细。",
"xpack.canvas.functions.shape.args.fillHelpText": "填充形状的 {SVG} 颜色。",
"xpack.canvas.functions.shape.args.maintainAspectHelpText": "维持形状的原始纵横比?",
"xpack.canvas.functions.shape.args.shapeHelpText": "选取形状。",
"xpack.canvas.functions.shapeHelpText": "创建形状。",
"expressionShape.functions.shape.args.borderHelpText": "形状轮廓边框的 {SVG} 颜色。",
"expressionShape.functions.shape.args.borderWidthHelpText": "边框的粗细。",
"expressionShape.functions.shape.args.fillHelpText": "填充形状的 {SVG} 颜色。",
"expressionShape.functions.shape.args.maintainAspectHelpText": "维持形状的原始纵横比?",
"expressionShape.functions.shape.args.shapeHelpText": "选取形状。",
"expressionShape.functions.shapeHelpText": "创建形状。",
"xpack.canvas.functions.sort.args.byHelpText": "排序依据的列。如果未指定,则 {DATATABLE} 按第一列排序。",
"xpack.canvas.functions.sort.args.reverseHelpText": "反转排序顺序。如果未指定,则 {DATATABLE} 按升序排序。",
"xpack.canvas.functions.sortHelpText": "按指定列对 {DATATABLE} 进行排序。",
@ -6548,8 +6546,8 @@
"xpack.canvas.renderer.progress.helpDescription": "呈现显示元素百分比的进度指示",
"xpack.canvas.renderer.repeatImage.displayName": "图像重复",
"xpack.canvas.renderer.repeatImage.helpDescription": "重复图像给定次数",
"xpack.canvas.renderer.shape.displayName": "形状",
"xpack.canvas.renderer.shape.helpDescription": "呈现基本形状",
"expressionShape.renderer.shape.displayName": "形状",
"expressionShape.renderer.shape.helpDescription": "呈现基本形状",
"xpack.canvas.renderer.table.displayName": "数据表",
"xpack.canvas.renderer.table.helpDescription": "将表格数据呈现为 {HTML}",
"xpack.canvas.renderer.text.displayName": "纯文本",