* Adds progress function and elements Added progress elements Added progress view Added unit tests for progress common function Fixed prop type in toggle arg Renamed vert -> vertical and horiz -> horizontal Adjusted progress element dimensions Removed check for null context in progress function Refactored progress shapes Added unicorn shape Adds labelPosition arg Added tests for labelPosition * Added percentage column to demodata * Updated elements to use percent_uptime in demodata * Updated demodata percent values * Refactored progress to use SVGs instead of shape defs * Added barWeight arg to progress function * Removed labelPosition arg. Set static label position for each progress shape * Added label to unicorn shape * Fixed element images
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { openSans } from '../../../common/lib/fonts';
|
||||
import header from './header.png';
|
||||
|
||||
export const horizontalProgressBar = () => ({
|
||||
name: 'horizontalProgressBar',
|
||||
displayName: 'Horizontal Progress Bar',
|
||||
help: 'Displays progress as a portion of a horizontal bar',
|
||||
width: 400,
|
||||
height: 30,
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="horizontalBar" label={formatnumber 0%} font={font size=24 family="${
|
||||
openSans.value
|
||||
}" color="#000000" align=center}
|
||||
| render`,
|
||||
});
|
After Width: | Height: | Size: 19 KiB |
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { openSans } from '../../../common/lib/fonts';
|
||||
import header from './header.png';
|
||||
|
||||
export const horizontalProgressPill = () => ({
|
||||
name: 'horizontalProgressPill',
|
||||
displayName: 'Horizontal Progress Pill',
|
||||
help: 'Displays progress as a portion of a horizontal pill',
|
||||
width: 400,
|
||||
height: 30,
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="horizontalPill" label={formatnumber 0%} font={font size=24 family="${
|
||||
openSans.value
|
||||
}" color="#000000" align=center}
|
||||
| render`,
|
||||
});
|
|
@ -11,11 +11,16 @@ import { donut } from './donut';
|
|||
import { dropdownFilter } from './dropdown_filter';
|
||||
import { image } from './image';
|
||||
import { horizontalBarChart } from './horiz_bar_chart';
|
||||
import { horizontalProgressBar } from './horizontal_progress_bar';
|
||||
import { horizontalProgressPill } from './horizontal_progress_pill';
|
||||
import { lineChart } from './line_chart';
|
||||
import { markdown } from './markdown';
|
||||
import { metric } from './metric';
|
||||
import { pie } from './pie';
|
||||
import { plot } from './plot';
|
||||
import { progressGauge } from './progress_gauge';
|
||||
import { progressSemicircle } from './progress_semicircle';
|
||||
import { progressWheel } from './progress_wheel';
|
||||
import { repeatImage } from './repeatImage';
|
||||
import { revealImage } from './revealImage';
|
||||
import { shape } from './shape';
|
||||
|
@ -23,6 +28,8 @@ import { table } from './table';
|
|||
import { tiltedPie } from './tilted_pie';
|
||||
import { timeFilter } from './time_filter';
|
||||
import { verticalBarChart } from './vert_bar_chart';
|
||||
import { verticalProgressBar } from './vertical_progress_bar';
|
||||
import { verticalProgressPill } from './vertical_progress_pill';
|
||||
|
||||
export const elementSpecs = [
|
||||
areaChart,
|
||||
|
@ -32,11 +39,16 @@ export const elementSpecs = [
|
|||
dropdownFilter,
|
||||
image,
|
||||
horizontalBarChart,
|
||||
horizontalProgressBar,
|
||||
horizontalProgressPill,
|
||||
lineChart,
|
||||
markdown,
|
||||
metric,
|
||||
pie,
|
||||
plot,
|
||||
progressGauge,
|
||||
progressSemicircle,
|
||||
progressWheel,
|
||||
repeatImage,
|
||||
revealImage,
|
||||
shape,
|
||||
|
@ -44,4 +56,6 @@ export const elementSpecs = [
|
|||
tiltedPie,
|
||||
timeFilter,
|
||||
verticalBarChart,
|
||||
verticalProgressBar,
|
||||
verticalProgressPill,
|
||||
];
|
||||
|
|
After Width: | Height: | Size: 22 KiB |
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { openSans } from '../../../common/lib/fonts';
|
||||
import header from './header.png';
|
||||
|
||||
export const progressGauge = () => ({
|
||||
name: 'progressGauge',
|
||||
displayName: 'Progress Gauge',
|
||||
help: 'Displays progress as a portion of a gauge',
|
||||
width: 200,
|
||||
height: 200,
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="gauge" label={formatnumber 0%} font={font size=24 family="${
|
||||
openSans.value
|
||||
}" color="#000000" align=center}
|
||||
| render`,
|
||||
});
|
After Width: | Height: | Size: 17 KiB |
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { openSans } from '../../../common/lib/fonts';
|
||||
import header from './header.png';
|
||||
|
||||
export const progressSemicircle = () => ({
|
||||
name: 'progressSemicircle',
|
||||
displayName: 'Progress Semicircle',
|
||||
help: 'Displays progress as a portion of a semicircle',
|
||||
width: 200,
|
||||
height: 100,
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="semicircle" label={formatnumber 0%} font={font size=24 family="${
|
||||
openSans.value
|
||||
}" color="#000000" align=center}
|
||||
| render`,
|
||||
});
|
After Width: | Height: | Size: 26 KiB |
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { openSans } from '../../../common/lib/fonts';
|
||||
import header from './header.png';
|
||||
|
||||
export const progressWheel = () => ({
|
||||
name: 'progressWheel',
|
||||
displayName: 'Progress Wheel',
|
||||
help: 'Displays progress as a portion of a wheel',
|
||||
width: 200,
|
||||
height: 200,
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="wheel" label={formatnumber 0%} font={font size=24 family="${
|
||||
openSans.value
|
||||
}" color="#000000" align=center}
|
||||
| render`,
|
||||
});
|
|
@ -13,7 +13,7 @@ export const revealImage = () => ({
|
|||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| math "sum(min(cost) / max(cost))"
|
||||
| math "mean(percent_uptime)"
|
||||
| revealImage origin=bottom image=null
|
||||
| render`,
|
||||
});
|
||||
|
|
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { openSans } from '../../../common/lib/fonts';
|
||||
import header from './header.png';
|
||||
|
||||
export const verticalProgressBar = () => ({
|
||||
name: 'verticalProgressBar',
|
||||
displayName: 'Vertical Progress Bar',
|
||||
help: 'Displays progress as a portion of a vertical bar',
|
||||
width: 80,
|
||||
height: 400,
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="verticalBar" label={formatnumber 0%} font={font size=24 family="${
|
||||
openSans.value
|
||||
}" color="#000000" align=center}
|
||||
| render`,
|
||||
});
|
After Width: | Height: | Size: 20 KiB |
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { openSans } from '../../../common/lib/fonts';
|
||||
import header from './header.png';
|
||||
|
||||
export const verticalProgressPill = () => ({
|
||||
name: 'verticalProgressPill',
|
||||
displayName: 'Vertical Progress Pill',
|
||||
help: 'Displays progress as a portion of a vertical pill',
|
||||
width: 80,
|
||||
height: 400,
|
||||
image: header,
|
||||
expression: `filters
|
||||
| demodata
|
||||
| math "mean(percent_uptime)"
|
||||
| progress shape="verticalPill" label={formatnumber 0%} font={font size=24 family="${
|
||||
openSans.value
|
||||
}" color="#000000" align=center}
|
||||
| render`,
|
||||
});
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from 'expect.js';
|
||||
import { progress } from '../progress';
|
||||
import { functionWrapper } from '../../../../__tests__/helpers/function_wrapper';
|
||||
import { fontStyle } from './fixtures/test_styles';
|
||||
|
||||
describe('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(e => {
|
||||
expect(e.message).to.be('Context must be between 0 and 1');
|
||||
});
|
||||
});
|
||||
|
||||
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(e => {
|
||||
expect(e.message).to.be(`'max' must be greater than 0`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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", () => {});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -40,6 +40,7 @@ import { palette } from './palette';
|
|||
import { pie } from './pie';
|
||||
import { plot } from './plot';
|
||||
import { ply } from './ply';
|
||||
import { progress } from './progress';
|
||||
import { render } from './render';
|
||||
import { replace } from './replace';
|
||||
import { rounddate } from './rounddate';
|
||||
|
@ -95,6 +96,7 @@ export const functions = [
|
|||
pie,
|
||||
plot,
|
||||
ply,
|
||||
progress,
|
||||
render,
|
||||
repeatImage,
|
||||
replace,
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import { openSans } from '../../../common/lib/fonts';
|
||||
import { shapes } from '../../renderers/progress/shapes';
|
||||
|
||||
export const progress = () => ({
|
||||
name: 'progress',
|
||||
aliases: [],
|
||||
type: 'render',
|
||||
help: 'Configure a progress element',
|
||||
context: {
|
||||
types: ['number'],
|
||||
},
|
||||
args: {
|
||||
shape: {
|
||||
type: ['string'],
|
||||
alias: ['_'],
|
||||
help: `Select ${Object.keys(shapes)
|
||||
.map((key, i, src) => (i === src.length - 1 ? `or ${shapes[key].name}` : shapes[key].name))
|
||||
.join(', ')}`,
|
||||
default: 'gauge',
|
||||
},
|
||||
max: {
|
||||
type: ['number'],
|
||||
help: 'Maximum value of the progress element',
|
||||
default: 1,
|
||||
},
|
||||
valueColor: {
|
||||
type: ['string'],
|
||||
help: 'Color of the progress bar',
|
||||
default: `#1785b0`,
|
||||
},
|
||||
barColor: {
|
||||
type: ['string'],
|
||||
help: 'Color of the background bar',
|
||||
default: `#f0f0f0`,
|
||||
},
|
||||
valueWeight: {
|
||||
type: ['number'],
|
||||
help: 'Thickness of the progress bar',
|
||||
default: 20,
|
||||
},
|
||||
barWeight: {
|
||||
type: ['number'],
|
||||
help: 'Thickness of the background bar',
|
||||
default: 20,
|
||||
},
|
||||
label: {
|
||||
type: ['boolean', 'string'],
|
||||
help: `Set true/false to show/hide label or provide a string to display as the label`,
|
||||
default: true,
|
||||
},
|
||||
font: {
|
||||
types: ['style'],
|
||||
help: 'Font settings for the label. Technically you can stick other styles in here too!',
|
||||
default: `{font size=24 family="${openSans.value}" color="#000000" align=center}`,
|
||||
},
|
||||
},
|
||||
fn: (value, args) => {
|
||||
if (args.max <= 0) throw new Error(`'max' must be greater than 0`);
|
||||
if (value > args.max || value < 0) throw new Error(`Context must be between 0 and ${args.max}`);
|
||||
|
||||
let label = '';
|
||||
if (args.label) label = typeof args.label === 'string' ? args.label : `${value}`;
|
||||
|
||||
let font = {};
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
|
@ -26,6 +26,7 @@ export const demodata = () => ({
|
|||
},
|
||||
fn: (context, args) => {
|
||||
const demoRows = getDemoRows(args.type);
|
||||
|
||||
let set = {};
|
||||
if (args.type === 'ci') {
|
||||
set = {
|
||||
|
@ -38,6 +39,7 @@ export const demodata = () => ({
|
|||
{ name: 'country', type: 'string' },
|
||||
{ name: 'state', type: 'string' },
|
||||
{ name: 'project', type: 'string' },
|
||||
{ name: 'percent_uptime', type: 'number' },
|
||||
],
|
||||
rows: sortBy(demoRows, 'time'),
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ import { markdown } from './markdown';
|
|||
import { metric } from './metric';
|
||||
import { pie } from './pie';
|
||||
import { plot } from './plot';
|
||||
import { progress } from './progress';
|
||||
import { shape } from './shape';
|
||||
import { table } from './table';
|
||||
import { timeFilter } from './time_filter';
|
||||
|
@ -32,6 +33,7 @@ export const renderFunctions = [
|
|||
metric,
|
||||
pie,
|
||||
plot,
|
||||
progress,
|
||||
shape,
|
||||
table,
|
||||
timeFilter,
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { getId } from '../../../public/lib/get_id';
|
||||
import { shapes } from './shapes';
|
||||
|
||||
export const progress = () => ({
|
||||
name: 'progress',
|
||||
displayName: 'Progress',
|
||||
help: 'Reveal a percentage of an element',
|
||||
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(shapes[shape], 'image/svg+xml')
|
||||
.getElementsByTagName('svg');
|
||||
|
||||
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');
|
||||
bar.setAttribute('className', 'canvasProgress__background');
|
||||
bar.setAttribute('fill', 'none');
|
||||
bar.setAttribute('stroke', barColor);
|
||||
bar.setAttribute('stroke-width', `${barWeight}px`);
|
||||
|
||||
const value = bar.cloneNode(true);
|
||||
value.setAttribute('className', 'canvasProgress__value');
|
||||
value.setAttribute('stroke', valueColor);
|
||||
value.setAttribute('stroke-width', `${valueWeight}px`);
|
||||
|
||||
const length = value.getTotalLength();
|
||||
const to = length * (1 - percent);
|
||||
value.setAttribute('stroke-dasharray', length);
|
||||
value.setAttribute('stroke-dashoffset', Math.max(0, to));
|
||||
|
||||
shapeSvg.appendChild(value);
|
||||
|
||||
const [text] = shapeSvg.getElementsByTagName('text');
|
||||
|
||||
if (label && text) {
|
||||
text.textContent = label;
|
||||
text.setAttribute('className', 'canvasProgress__label');
|
||||
|
||||
if (shape === 'horizontalPill')
|
||||
text.setAttribute('x', parseInt(text.getAttribute('x'), 10) + offset / 2);
|
||||
if (shape === 'verticalPill')
|
||||
text.setAttribute('y', 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', 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', domNode.offsetWidth);
|
||||
shapeSvg.setAttribute('height', domNode.offsetHeight);
|
||||
|
||||
if (domNode.firstChild) domNode.removeChild(domNode.firstChild);
|
||||
domNode.appendChild(shapeSvg);
|
||||
|
||||
handlers.onResize(() => {
|
||||
shapeSvg.setAttribute('width', domNode.offsetWidth);
|
||||
shapeSvg.setAttribute('height', domNode.offsetHeight);
|
||||
});
|
||||
}
|
||||
|
||||
handlers.done();
|
||||
},
|
||||
});
|
|
@ -0,0 +1,4 @@
|
|||
<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>
|
After Width: | Height: | Size: 190 B |
|
@ -0,0 +1,4 @@
|
|||
<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>
|
After Width: | Height: | Size: 170 B |
|
@ -0,0 +1,4 @@
|
|||
<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>
|
After Width: | Height: | Size: 193 B |
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
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,
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
<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' dominant-baseline='text-after-edge' />
|
||||
</svg>
|
After Width: | Height: | Size: 194 B |
|
@ -0,0 +1,5 @@
|
|||
<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>
|
||||
|
After Width: | Height: | Size: 368 B |
|
@ -0,0 +1,5 @@
|
|||
<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' dominant-baseline='text-after-edge' />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 180 B |
|
@ -0,0 +1,5 @@
|
|||
<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' dominant-baseline='text-after-edge' />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 203 B |
|
@ -0,0 +1,4 @@
|
|||
<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>
|
After Width: | Height: | Size: 208 B |
|
@ -7,14 +7,17 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { shapes } from '../../renderers/shape/shapes';
|
||||
import { templateFromReactComponent } from '../../../public/lib/template_from_react_component';
|
||||
import { ShapePickerMini } from '../../../public/components/shape_picker_mini/';
|
||||
|
||||
const ShapeArgInput = ({ onValueChange, argValue }) => (
|
||||
const ShapeArgInput = ({ onValueChange, argValue, typeInstance }) => (
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<ShapePickerMini value={argValue} onChange={onValueChange} shapes={shapes} />
|
||||
<ShapePickerMini
|
||||
value={argValue}
|
||||
onChange={onValueChange}
|
||||
shapes={typeInstance.options.shapes}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
@ -22,6 +25,9 @@ const ShapeArgInput = ({ onValueChange, argValue }) => (
|
|||
ShapeArgInput.propTypes = {
|
||||
argValue: PropTypes.any.isRequired,
|
||||
onValueChange: PropTypes.func.isRequired,
|
||||
typeInstance: PropTypes.shape({
|
||||
options: PropTypes.shape({ shapes: PropTypes.object.isRequired }).isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export const shape = () => ({
|
||||
|
|
|
@ -9,16 +9,17 @@ import PropTypes from 'prop-types';
|
|||
import { EuiSwitch } from '@elastic/eui';
|
||||
import { templateFromReactComponent } from '../../../public/lib/template_from_react_component';
|
||||
|
||||
const ToggleArgInput = ({ onValueChange, argValue, argId }) => {
|
||||
const ToggleArgInput = ({ onValueChange, argValue, argId, renderError }) => {
|
||||
const handleChange = () => onValueChange(!argValue);
|
||||
|
||||
if (typeof argValue !== 'boolean') renderError();
|
||||
return <EuiSwitch id={argId} checked={argValue} onChange={handleChange} />;
|
||||
};
|
||||
|
||||
ToggleArgInput.propTypes = {
|
||||
onValueChange: PropTypes.func.isRequired,
|
||||
argValue: PropTypes.bool.isRequired,
|
||||
argValue: PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.object]).isRequired,
|
||||
argId: PropTypes.string.isRequired,
|
||||
renderError: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export const toggle = () => ({
|
||||
|
|
|
@ -5,12 +5,13 @@
|
|||
*/
|
||||
|
||||
import { dropdownControl } from './dropdownControl';
|
||||
import { getCell } from './getCell';
|
||||
import { image } from './image';
|
||||
import { markdown } from './markdown';
|
||||
import { metric } from './metric';
|
||||
import { pie } from './pie';
|
||||
import { plot } from './plot';
|
||||
import { getCell } from './getCell';
|
||||
import { progress } from './progress';
|
||||
import { repeatImage } from './repeatImage';
|
||||
import { revealImage } from './revealImage';
|
||||
import { render } from './render';
|
||||
|
@ -20,12 +21,13 @@ import { timefilterControl } from './timefilterControl';
|
|||
|
||||
export const viewSpecs = [
|
||||
dropdownControl,
|
||||
getCell,
|
||||
image,
|
||||
markdown,
|
||||
metric,
|
||||
pie,
|
||||
plot,
|
||||
getCell,
|
||||
progress,
|
||||
repeatImage,
|
||||
revealImage,
|
||||
render,
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { openSans } from '../../../common/lib/fonts';
|
||||
import { shapes } from '../../renderers/progress/shapes';
|
||||
|
||||
export const progress = () => ({
|
||||
name: 'progress',
|
||||
displayName: 'Progress',
|
||||
modelArgs: [['_', { label: 'Value' }]],
|
||||
requiresContext: false,
|
||||
args: [
|
||||
{
|
||||
name: 'shape',
|
||||
argType: 'select',
|
||||
options: {
|
||||
choices: Object.keys(shapes).map(key => ({
|
||||
value: key,
|
||||
//turns camel into title case
|
||||
name: key[0].toUpperCase() + key.slice(1).replace(/([A-Z])/g, ' $1'),
|
||||
})),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'max',
|
||||
displayName: 'Maximum value',
|
||||
help: 'Maximum value of the progress element',
|
||||
argType: 'number',
|
||||
default: '1',
|
||||
},
|
||||
{
|
||||
name: 'valueColor',
|
||||
displayName: 'Progress Color',
|
||||
help: 'Color of the progress bar',
|
||||
argType: 'color',
|
||||
default: `#1785b0`,
|
||||
},
|
||||
{
|
||||
name: 'valueWeight',
|
||||
displayName: 'Progress Weight',
|
||||
help: 'Thickness of the progress bar',
|
||||
argType: 'number',
|
||||
default: '20',
|
||||
},
|
||||
{
|
||||
name: 'barColor',
|
||||
displayName: 'Background Color',
|
||||
help: 'Color of the background bar',
|
||||
argType: 'color',
|
||||
default: `#f0f0f0`,
|
||||
},
|
||||
{
|
||||
name: 'barWeight',
|
||||
displayName: 'Background Weight',
|
||||
help: 'Thickness of the background bar',
|
||||
argType: 'number',
|
||||
default: '20',
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
displayName: 'Label',
|
||||
help: `Set true/false to show/hide label or provide a string to display as the label`,
|
||||
argType: 'toggle',
|
||||
default: 'true',
|
||||
},
|
||||
{
|
||||
name: 'font',
|
||||
displayName: 'Label Settings',
|
||||
help: 'Font settings for the label. Technically you can stick other styles in here too!',
|
||||
argType: 'font',
|
||||
default: `{font size=24 family="${openSans.value}" color="#000000" align=center}`,
|
||||
},
|
||||
],
|
||||
});
|
|
@ -16,12 +16,8 @@ export const shape = () => ({
|
|||
name: '_',
|
||||
displayName: 'Select a Shape',
|
||||
argType: 'shape',
|
||||
help: 'A basic shape',
|
||||
options: {
|
||||
choices: Object.keys(shapes).map(shape => ({
|
||||
value: shape,
|
||||
name: shape,
|
||||
})),
|
||||
shapes,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -7,10 +7,9 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiFlexGrid, EuiFlexItem, EuiLink } from '@elastic/eui';
|
||||
import { shapes } from '../../../canvas_plugin_src/renderers/shape/shapes';
|
||||
import { ShapePreview } from '../shape_preview';
|
||||
|
||||
export const ShapePicker = ({ onChange }) => {
|
||||
export const ShapePicker = ({ shapes, onChange }) => {
|
||||
return (
|
||||
<EuiFlexGrid gutterSize="s" columns={4}>
|
||||
{Object.keys(shapes)
|
||||
|
@ -18,7 +17,7 @@ export const ShapePicker = ({ onChange }) => {
|
|||
.map(shapeKey => (
|
||||
<EuiFlexItem key={shapeKey}>
|
||||
<EuiLink onClick={() => onChange(shapeKey)}>
|
||||
<ShapePreview value={shapeKey} />
|
||||
<ShapePreview shape={shapes[shapeKey]} />
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
|
@ -27,6 +26,6 @@ export const ShapePicker = ({ onChange }) => {
|
|||
};
|
||||
|
||||
ShapePicker.propTypes = {
|
||||
value: PropTypes.string,
|
||||
shapes: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
|
|
@ -11,10 +11,10 @@ import { Popover } from '../popover';
|
|||
import { ShapePicker } from '../shape_picker/';
|
||||
import { ShapePreview } from '../shape_preview';
|
||||
|
||||
export const ShapePickerMini = ({ onChange, value, anchorPosition }) => {
|
||||
export const ShapePickerMini = ({ shapes, onChange, value, anchorPosition }) => {
|
||||
const button = handleClick => (
|
||||
<EuiLink style={{ fontSize: 0 }} onClick={handleClick}>
|
||||
<ShapePreview value={value} />
|
||||
<ShapePreview shape={shapes[value]} />
|
||||
</EuiLink>
|
||||
);
|
||||
|
||||
|
@ -24,12 +24,13 @@ export const ShapePickerMini = ({ onChange, value, anchorPosition }) => {
|
|||
button={button}
|
||||
anchorPosition={anchorPosition}
|
||||
>
|
||||
{() => <ShapePicker onChange={onChange} value={value} />}
|
||||
{() => <ShapePicker onChange={onChange} shapes={shapes} />}
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
ShapePickerMini.propTypes = {
|
||||
shapes: PropTypes.object.isRequired,
|
||||
value: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
anchorPosition: PropTypes.string,
|
||||
|
|
|
@ -6,13 +6,32 @@
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { shapes } from '../../../canvas_plugin_src/renderers/shape/shapes';
|
||||
|
||||
export const ShapePreview = ({ value }) => {
|
||||
// eslint-disable-next-line react/no-danger
|
||||
return <div className="canvasShapePreview" dangerouslySetInnerHTML={{ __html: shapes[value] }} />;
|
||||
export const ShapePreview = ({ shape }) => {
|
||||
const weight = 5;
|
||||
const parser = new DOMParser();
|
||||
const [shapeSvg] = parser.parseFromString(shape, 'image/svg+xml').getElementsByTagName('svg');
|
||||
shapeSvg.setAttribute('fill', 'none');
|
||||
shapeSvg.setAttribute('stroke', 'black');
|
||||
|
||||
const initialViewBox = shapeSvg
|
||||
.getAttribute('viewBox')
|
||||
.split(' ')
|
||||
.map(v => 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(' '));
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line react/no-danger
|
||||
<div className="canvasShapePreview" dangerouslySetInnerHTML={{ __html: shapeSvg.outerHTML }} />
|
||||
);
|
||||
};
|
||||
|
||||
ShapePreview.propTypes = {
|
||||
value: PropTypes.string,
|
||||
shape: PropTypes.string,
|
||||
};
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
height: $euiSizeXXL;
|
||||
|
||||
svg {
|
||||
fill: black;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
|