Skip to content

Commit 5669178

Browse files
authored
feat: gauge, goal and bullet graph (alpha) (#614)
1 parent de7df75 commit 5669178

File tree

72 files changed

+2957
-5
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+2957
-5
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License. */
18+
19+
import { Config } from '../types/config_types';
20+
import { TAU } from '../../../partition_chart/layout/utils/math';
21+
import { configMap } from '../../../partition_chart/layout/config/config';
22+
23+
export const configMetadata = {
24+
angleStart: { dflt: Math.PI + Math.PI / 4, min: -TAU, max: TAU, type: 'number' },
25+
angleEnd: { dflt: -Math.PI / 4, min: -TAU, max: TAU, type: 'number' },
26+
27+
// shape geometry
28+
width: { dflt: 300, min: 0, max: 1024, type: 'number', reconfigurable: false },
29+
height: { dflt: 150, min: 0, max: 1024, type: 'number', reconfigurable: false },
30+
margin: {
31+
type: 'group',
32+
values: {
33+
left: { dflt: 0, min: -0.25, max: 0.25, type: 'number' },
34+
right: { dflt: 0, min: -0.25, max: 0.25, type: 'number' },
35+
top: { dflt: 0, min: -0.25, max: 0.25, type: 'number' },
36+
bottom: { dflt: 0, min: -0.25, max: 0.25, type: 'number' },
37+
},
38+
},
39+
40+
// general text config
41+
fontFamily: {
42+
dflt: 'Sans-Serif',
43+
type: 'string',
44+
},
45+
46+
// fill text config
47+
minFontSize: { dflt: 8, min: 0.1, max: 8, type: 'number', reconfigurable: true },
48+
maxFontSize: { dflt: 64, min: 0.1, max: 64, type: 'number' },
49+
50+
backgroundColor: { dflt: '#ffffff', type: 'color' },
51+
sectorLineWidth: { dflt: 1, min: 0, max: 4, type: 'number' },
52+
};
53+
54+
export const config: Config = configMap<Config>((item: any) => item.dflt, configMetadata);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License. */
18+
19+
import { Pixels, SizeRatio } from '../../../partition_chart/layout/types/geometry_types';
20+
import { FontFamily } from '../../../partition_chart/layout/types/types';
21+
import { Color } from '../../../../utils/commons';
22+
23+
// todo switch to `io-ts` style, generic way of combining static and runtime type info
24+
export interface Config {
25+
angleStart: number;
26+
angleEnd: number;
27+
28+
// shape geometry
29+
width: number;
30+
height: number;
31+
margin: { left: SizeRatio; right: SizeRatio; top: SizeRatio; bottom: SizeRatio };
32+
33+
// general text config
34+
fontFamily: FontFamily;
35+
36+
// fill text config
37+
minFontSize: Pixels;
38+
maxFontSize: Pixels;
39+
40+
// other
41+
backgroundColor: Color;
42+
sectorLineWidth: Pixels;
43+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License. */
18+
19+
import { Config } from './config_types';
20+
21+
import { Pixels, PointObject } from '../../../partition_chart/layout/types/geometry_types';
22+
import { config } from '../config/config';
23+
import { SpecTypes } from '../../../../specs/settings';
24+
import { BandFillColorAccessorInput, GOAL_SUBTYPES } from '../../specs/index';
25+
26+
interface BandViewModel {
27+
value: number;
28+
fillColor: string;
29+
}
30+
31+
interface TickViewModel {
32+
value: number;
33+
text: string;
34+
}
35+
36+
/** @internal */
37+
export interface BulletViewModel {
38+
subtype: string;
39+
base: number;
40+
target: number;
41+
actual: number;
42+
bands: Array<BandViewModel>;
43+
ticks: Array<TickViewModel>;
44+
labelMajor: string;
45+
labelMinor: string;
46+
centralMajor: string;
47+
centralMinor: string;
48+
highestValue: number;
49+
lowestValue: number;
50+
aboveBaseCount: number;
51+
belowBaseCount: number;
52+
}
53+
54+
/** @internal */
55+
export type PickFunction = (x: Pixels, y: Pixels) => Array<BulletViewModel>;
56+
57+
/** @internal */
58+
export type ShapeViewModel = {
59+
config: Config;
60+
bulletViewModel: BulletViewModel;
61+
chartCenter: PointObject;
62+
pickQuads: PickFunction;
63+
};
64+
65+
const commonDefaults = {
66+
specType: SpecTypes.Series,
67+
subtype: GOAL_SUBTYPES[0],
68+
base: 0,
69+
target: 100,
70+
actual: 50,
71+
ticks: [0, 25, 50, 75, 100],
72+
};
73+
74+
/** @internal */
75+
export const defaultGoalSpec = {
76+
...commonDefaults,
77+
bands: [50, 75, 100],
78+
bandFillColor: ({ value, base, highestValue, lowestValue }: BandFillColorAccessorInput) => {
79+
const aboveBase = value > base;
80+
const ratio = aboveBase
81+
? (value - base) / (Math.max(base, highestValue) - base)
82+
: (value - base) / (Math.min(base, lowestValue) - base);
83+
const level = Math.round(255 * ratio);
84+
return aboveBase ? `rgb(0, ${level}, 0)` : `rgb( ${level}, 0, 0)`;
85+
},
86+
tickValueFormatter: ({ value }: BandFillColorAccessorInput) => String(value),
87+
labelMajor: ({ base }: BandFillColorAccessorInput) => String(base),
88+
labelMinor: ({}: BandFillColorAccessorInput) => 'unit',
89+
centralMajor: ({ base }: BandFillColorAccessorInput) => String(base),
90+
centralMinor: ({ target }: BandFillColorAccessorInput) => String(target),
91+
};
92+
93+
/** @internal */
94+
export const nullGoalViewModel = {
95+
...commonDefaults,
96+
bands: [],
97+
ticks: [],
98+
labelMajor: '',
99+
labelMinor: '',
100+
centralMajor: '',
101+
centralMinor: '',
102+
highestValue: 100,
103+
lowestValue: 0,
104+
aboveBaseCount: 0,
105+
belowBaseCount: 0,
106+
};
107+
108+
/** @internal */
109+
export const nullShapeViewModel = (specifiedConfig?: Config, chartCenter?: PointObject): ShapeViewModel => ({
110+
config: specifiedConfig || config,
111+
bulletViewModel: nullGoalViewModel,
112+
chartCenter: chartCenter || { x: 0, y: 0 },
113+
pickQuads: () => [],
114+
});
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License. */
18+
19+
import { TextMeasure } from '../../../partition_chart/layout/types/types';
20+
import { Config } from '../types/config_types';
21+
import { BulletViewModel, PickFunction, ShapeViewModel } from '../types/viewmodel_types';
22+
import { GoalSpec } from '../../specs/index';
23+
24+
/** @internal */
25+
export function shapeViewModel(textMeasure: TextMeasure, spec: GoalSpec, config: Config): ShapeViewModel {
26+
const { width, height, margin } = config;
27+
28+
const innerWidth = width * (1 - Math.min(1, margin.left + margin.right));
29+
const innerHeight = height * (1 - Math.min(1, margin.top + margin.bottom));
30+
31+
const chartCenter = {
32+
x: width * margin.left + innerWidth / 2,
33+
y: height * margin.top + innerHeight / 2,
34+
};
35+
36+
const pickQuads: PickFunction = (x, y) => {
37+
return -innerWidth / 2 <= x && x <= innerWidth / 2 && -innerHeight / 2 <= y && y <= innerHeight / 2
38+
? [bulletViewModel]
39+
: [];
40+
};
41+
42+
const {
43+
subtype,
44+
base,
45+
target,
46+
actual,
47+
bands,
48+
ticks,
49+
bandFillColor,
50+
tickValueFormatter,
51+
labelMajor,
52+
labelMinor,
53+
centralMajor,
54+
centralMinor,
55+
} = spec;
56+
57+
const [lowestValue, highestValue] = [base, target, actual, ...bands, ...ticks].reduce(
58+
([min, max], value) => [Math.min(min, value), Math.max(max, value)],
59+
[Infinity, -Infinity],
60+
);
61+
62+
const aboveBaseCount = bands.filter((b: number) => b > base).length;
63+
const belowBaseCount = bands.filter((b: number) => b <= base).length;
64+
65+
const callbackArgs = {
66+
base,
67+
target,
68+
actual,
69+
highestValue,
70+
lowestValue,
71+
aboveBaseCount,
72+
belowBaseCount,
73+
};
74+
75+
const bulletViewModel: BulletViewModel = {
76+
subtype,
77+
base,
78+
target,
79+
actual,
80+
bands: bands.map((value: number, index: number) => ({
81+
value,
82+
fillColor: bandFillColor({ value, index, ...callbackArgs }),
83+
})),
84+
ticks: ticks.map((value: number, index: number) => ({
85+
value,
86+
text: tickValueFormatter({ value, index, ...callbackArgs }),
87+
})),
88+
labelMajor: typeof labelMajor === 'string' ? labelMajor : labelMajor({ value: NaN, index: 0, ...callbackArgs }),
89+
labelMinor: typeof labelMinor === 'string' ? labelMinor : labelMinor({ value: NaN, index: 0, ...callbackArgs }),
90+
centralMajor:
91+
typeof centralMajor === 'string' ? centralMajor : centralMajor({ value: NaN, index: 0, ...callbackArgs }),
92+
centralMinor:
93+
typeof centralMinor === 'string' ? centralMinor : centralMinor({ value: NaN, index: 0, ...callbackArgs }),
94+
highestValue,
95+
lowestValue,
96+
aboveBaseCount,
97+
belowBaseCount,
98+
};
99+
100+
// combined viewModel
101+
return {
102+
config,
103+
chartCenter,
104+
bulletViewModel,
105+
pickQuads,
106+
};
107+
}

0 commit comments

Comments
 (0)