diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-goal-alpha-auto-linear-ticks-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-goal-alpha-auto-linear-ticks-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..4d4efc5f2a Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-goal-alpha-auto-linear-ticks-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/goal-stories-test-ts-goal-stories-auto-ticks-goal-reverse-false-1-snap.png b/integration/tests/__image_snapshots__/goal-stories-test-ts-goal-stories-auto-ticks-goal-reverse-false-1-snap.png new file mode 100644 index 0000000000..3028c84dd4 Binary files /dev/null and b/integration/tests/__image_snapshots__/goal-stories-test-ts-goal-stories-auto-ticks-goal-reverse-false-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/goal-stories-test-ts-goal-stories-auto-ticks-goal-reverse-true-1-snap.png b/integration/tests/__image_snapshots__/goal-stories-test-ts-goal-stories-auto-ticks-goal-reverse-true-1-snap.png new file mode 100644 index 0000000000..626f9457d8 Binary files /dev/null and b/integration/tests/__image_snapshots__/goal-stories-test-ts-goal-stories-auto-ticks-goal-reverse-true-1-snap.png differ diff --git a/integration/tests/goal_stories.test.ts b/integration/tests/goal_stories.test.ts index 54341020e0..40bcce9625 100644 --- a/integration/tests/goal_stories.test.ts +++ b/integration/tests/goal_stories.test.ts @@ -83,6 +83,14 @@ describe('Goal stories', () => { ); }); + describe('auto ticks', () => { + it.each([true, false])('goal - reverse %p', async (reverse) => { + await common.expectChartAtUrlToMatchScreenshot( + `http://localhost:9001/?path=/story/goal-alpha--auto-linear-ticks&knob-subtype=goal&knob-reverse=${reverse}`, + ); + }); + }); + describe('sagitta shifted goal charts', () => { it.each<[title: string, startAngle: number, endAngle: number]>([ // top openings diff --git a/packages/charts/api/charts.api.md b/packages/charts/api/charts.api.md index 8b393be5f6..b4f106fcd6 100644 --- a/packages/charts/api/charts.api.md +++ b/packages/charts/api/charts.api.md @@ -958,6 +958,12 @@ export function getNodeName(node: ArrayNode): string; // @alpha export const Goal: (props: SFProps) => null; +// @alpha (undocumented) +export interface GoalDomainRange { + max: number; + min: number; +} + // @alpha (undocumented) export type GoalLabelAccessor = LabelAccessor; @@ -978,8 +984,7 @@ export interface GoalSpec extends Spec { bandFillColor: BandFillColorAccessor; // (undocumented) bandLabels: string[]; - // (undocumented) - bands: number[]; + bands?: number | number[]; // (undocumented) base: number; // (undocumented) @@ -988,6 +993,7 @@ export interface GoalSpec extends Spec { centralMinor: string | GoalLabelAccessor; // (undocumented) chartType: typeof ChartType.Goal; + domain: GoalDomainRange; // (undocumented) labelMajor: string | GoalLabelAccessor; // (undocumented) @@ -1000,8 +1006,7 @@ export interface GoalSpec extends Spec { subtype: GoalSubtype; // (undocumented) target?: number; - // (undocumented) - ticks: number[]; + ticks?: number | number[]; // (undocumented) tickValueFormatter: GoalLabelAccessor; // (undocumented) diff --git a/packages/charts/src/chart_types/goal_chart/layout/types/viewmodel_types.ts b/packages/charts/src/chart_types/goal_chart/layout/types/viewmodel_types.ts index 4180eb7dfb..bc9ecb2b6a 100644 --- a/packages/charts/src/chart_types/goal_chart/layout/types/viewmodel_types.ts +++ b/packages/charts/src/chart_types/goal_chart/layout/types/viewmodel_types.ts @@ -62,13 +62,11 @@ export type ShapeViewModel = { const commonDefaults = { base: 0, actual: 50, - ticks: [0, 25, 50, 75, 100], }; /** @internal */ export const defaultGoalSpec = { ...commonDefaults, - bands: [50, 75, 100], bandFillColor: ({ value, highestValue, lowestValue }: BandFillColorAccessorInput) => { return getGreensColorScale(0.5, [highestValue, lowestValue])(value); }, diff --git a/packages/charts/src/chart_types/goal_chart/layout/viewmodel/viewmodel.ts b/packages/charts/src/chart_types/goal_chart/layout/viewmodel/viewmodel.ts index 68ad0462f1..941f7c91ad 100644 --- a/packages/charts/src/chart_types/goal_chart/layout/viewmodel/viewmodel.ts +++ b/packages/charts/src/chart_types/goal_chart/layout/viewmodel/viewmodel.ts @@ -6,10 +6,14 @@ * Side Public License, v 1. */ +import { Radian } from '../../../../common/geometry'; +import { ScaleContinuous } from '../../../../scales'; import { Dimensions } from '../../../../utils/dimensions'; import { Theme } from '../../../../utils/themes/theme'; import { GoalSpec } from '../../specs'; +import { GoalSubtype } from '../../specs/constants'; import { BulletViewModel, PickFunction, ShapeViewModel } from '../types/viewmodel_types'; +import { clamp, clampAll, isBetween, isFiniteNumber, isNil } from './../../../../utils/common'; /** @internal */ export function shapeViewModel(spec: GoalSpec, theme: Theme, chartDimensions: Dimensions): ShapeViewModel { @@ -24,11 +28,9 @@ export function shapeViewModel(spec: GoalSpec, theme: Theme, chartDimensions: Di const { subtype, - base, - target, - actual, - bands, ticks, + bands, + domain, bandFillColor, tickValueFormatter, labelMajor, @@ -39,13 +41,40 @@ export function shapeViewModel(spec: GoalSpec, theme: Theme, chartDimensions: Di angleStart, angleEnd, } = spec; - const [lowestValue, highestValue] = [base, ...(target ? [target] : []), actual, ...bands, ...ticks].reduce( - ([min, max], value) => [Math.min(min, value), Math.max(max, value)], - [Infinity, -Infinity], - ); + const lowestValue = isFiniteNumber(domain.min) ? domain.min : 0; + const highestValue = isFiniteNumber(domain.max) ? domain.max : 1; + const base = clamp(spec.base, lowestValue, highestValue); + const target = + !isNil(spec.target) && spec.target <= highestValue && spec.target >= lowestValue ? spec.target : undefined; + const actual = clamp(spec.actual, lowestValue, highestValue); + const finalTicks = Array.isArray(ticks) + ? ticks.filter(isBetween(lowestValue, highestValue)) + : new ScaleContinuous( + { + type: 'linear', + domain: [lowestValue, highestValue], + range: [0, 1], + }, + { + desiredTickCount: ticks ?? getDesiredTicks(subtype, angleStart, angleEnd), + }, + ).ticks(); + + const finalBands = Array.isArray(bands) + ? bands.reduce(...clampAll(lowestValue, highestValue)) + : new ScaleContinuous( + { + type: 'linear', + domain: [lowestValue, highestValue], + range: [0, 1], + }, + { + desiredTickCount: bands ?? getDesiredTicks(subtype, angleStart, angleEnd), + }, + ).ticks(); - const aboveBaseCount = bands.filter((b: number) => b > base).length; - const belowBaseCount = bands.filter((b: number) => b <= base).length; + const aboveBaseCount = finalBands.filter((b: number) => b > base).length; + const belowBaseCount = finalBands.filter((b: number) => b <= base).length; const callbackArgs = { base, @@ -62,12 +91,12 @@ export function shapeViewModel(spec: GoalSpec, theme: Theme, chartDimensions: Di base, target, actual, - bands: bands.map((value: number, index: number) => ({ + bands: finalBands.map((value: number, index: number) => ({ value, fillColor: bandFillColor({ value, index, ...callbackArgs }), text: bandLabels, })), - ticks: ticks.map((value: number, index: number) => ({ + ticks: finalTicks.map((value: number, index: number) => ({ value, text: tickValueFormatter({ value, index, ...callbackArgs }), })), @@ -98,3 +127,9 @@ export function shapeViewModel(spec: GoalSpec, theme: Theme, chartDimensions: Di pickQuads, }; } + +function getDesiredTicks(subtype: GoalSubtype, angleStart: Radian, angleEnd: Radian) { + if (subtype !== GoalSubtype.Goal) return 5; + const arc = Math.abs(angleStart - angleEnd); + return Math.ceil(arc / (Math.PI / 4)); +} diff --git a/packages/charts/src/chart_types/goal_chart/specs/index.ts b/packages/charts/src/chart_types/goal_chart/specs/index.ts index 6febeb9af4..e131dc099c 100644 --- a/packages/charts/src/chart_types/goal_chart/specs/index.ts +++ b/packages/charts/src/chart_types/goal_chart/specs/index.ts @@ -37,6 +37,18 @@ export type BandFillColorAccessor = (input: BandFillColorAccessorInput) => Color /** @alpha */ export type GoalLabelAccessor = LabelAccessor; +/** @alpha */ +export interface GoalDomainRange { + /** + * A finite number to defined the lower bound of the domain. Defaults to 0 if _not_ finite. + */ + min: number; + /** + * A finite number to defined the upper bound of the domain. Defaults to 1 if _not_ finite. + */ + max: number; +} + /** @alpha */ export interface GoalSpec extends Spec { specType: typeof SpecType.Series; @@ -45,8 +57,18 @@ export interface GoalSpec extends Spec { base: number; target?: number; actual: number; - bands: number[]; - ticks: number[]; + /** + * array of discrete band intervals or approximate number of desired bands + */ + bands?: number | number[]; + /** + * Array of discrete tick values or approximate number of desired ticks + */ + ticks?: number | number[]; + /** + * Domain of goal charts. Limits every value to within domain. + */ + domain: GoalDomainRange; bandFillColor: BandFillColorAccessor; tickValueFormatter: GoalLabelAccessor; labelMajor: string | GoalLabelAccessor; diff --git a/packages/charts/src/components/accessibility/accessibility.test.tsx b/packages/charts/src/components/accessibility/accessibility.test.tsx index 0eedeb9042..3445b67762 100644 --- a/packages/charts/src/components/accessibility/accessibility.test.tsx +++ b/packages/charts/src/components/accessibility/accessibility.test.tsx @@ -132,6 +132,7 @@ describe('Accessibility', () => { target={260} actual={170} bands={[200, 250, 300]} + domain={{ min: 0, max: 300 }} ticks={[0, 50, 100, 150, 200, 250, 300]} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " @@ -155,6 +156,7 @@ describe('Accessibility', () => { target={260} actual={170} bands={bandsAscending} + domain={{ min: 0, max: 300 }} ticks={[0, 50, 100, 150, 200, 250, 300]} labelMajor="Revenue 2020 YTD " labelMinor="(thousand USD) " diff --git a/packages/charts/src/utils/common.test.ts b/packages/charts/src/utils/common.test.ts index c67e4321ad..c7e36bcf28 100644 --- a/packages/charts/src/utils/common.test.ts +++ b/packages/charts/src/utils/common.test.ts @@ -20,6 +20,8 @@ import { isUniqueArray, isDefined, isDefinedFrom, + isBetween, + clampAll, } from './common'; describe('common utilities', () => { @@ -1025,4 +1027,22 @@ describe('#isDefinedFrom', () => { ); expect(result).toEqual(values.slice(0, 3)); }); + + describe('#isBetween', () => { + it('should filter array values between min and max inclusive', () => { + expect([1, 2, 3, 4, 5, 6].filter(isBetween(2, 5, false))).toEqual([2, 3, 4, 5]); + }); + it('should filter array values between min and max exclusive', () => { + expect([1, 2, 3, 4, 5, 6].filter(isBetween(2, 5, true))).toEqual([3, 4]); + }); + }); + + describe('#clampAll', () => { + it('should clamp each value in array between min and max', () => { + expect([0, 200, 400].reduce(...clampAll(100, 300))).toEqual([100, 200, 300]); + }); + it('should clamp array values and remove duplicates', () => { + expect([0, 100, 200, 300, 400].reduce(...clampAll(100, 300))).toEqual([100, 200, 300]); + }); + }); }); diff --git a/packages/charts/src/utils/common.ts b/packages/charts/src/utils/common.ts index 816d461c85..bc1b3a48be 100644 --- a/packages/charts/src/utils/common.ts +++ b/packages/charts/src/utils/common.ts @@ -645,3 +645,30 @@ export function stripUndefined>(source: R): R return acc; }, {} as R); } + +/** + * Returns `Array.filter` callback for values between a min and max + * @internal + */ +export const isBetween = (min: number, max: number, exclusive = false): ((n: number) => boolean) => + exclusive ? (n) => n < max && n > min : (n) => n <= max && n >= min; + +/** + * Returns `Array.reduce` callback to clamp values and remove duplicates + * @internal + */ +export const clampAll = ( + min: number, + max: number, +): [callbackfn: (acc: number[], value: number) => number[], initialAcc: number[]] => { + const seen = new Set(); + return [ + (acc: number[], n: number) => { + const clampValue = clamp(n, min, max); + if (!seen.has(clampValue)) acc.push(clampValue); + seen.add(clampValue); + return acc; + }, + [], + ]; +}; diff --git a/storybook/stories/goal/10_band_in_band.story.tsx b/storybook/stories/goal/10_band_in_band.story.tsx index 5aad312fa8..443455055b 100644 --- a/storybook/stories/goal/10_band_in_band.story.tsx +++ b/storybook/stories/goal/10_band_in_band.story.tsx @@ -34,6 +34,7 @@ export const Example = () => ( target={0} actual={0} bands={[225, 300]} + domain={{ min: 0, max: 300 }} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} diff --git a/storybook/stories/goal/11_gaps.story.tsx b/storybook/stories/goal/11_gaps.story.tsx index 6d5a9570d9..a2d1f9a8b9 100644 --- a/storybook/stories/goal/11_gaps.story.tsx +++ b/storybook/stories/goal/11_gaps.story.tsx @@ -41,6 +41,7 @@ export const Example = () => { base={0} target={showTarget ? target : undefined} actual={280} + domain={{ min: 0, max: 300 }} bands={[199, 201, 249, 251, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/12_range.story.tsx b/storybook/stories/goal/12_range.story.tsx index 87bcd2bccd..b432133d7c 100644 --- a/storybook/stories/goal/12_range.story.tsx +++ b/storybook/stories/goal/12_range.story.tsx @@ -35,6 +35,7 @@ export const Example = () => ( target={0} actual={0} bands={[215, 235, 300]} + domain={{ min: 0, max: 300 }} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} bandFillColor={({ value }: BandFillColorAccessorInput) => bandFillColor(value)} diff --git a/storybook/stories/goal/13_confidence_level.story.tsx b/storybook/stories/goal/13_confidence_level.story.tsx index ba0924da07..92909a0f11 100644 --- a/storybook/stories/goal/13_confidence_level.story.tsx +++ b/storybook/stories/goal/13_confidence_level.story.tsx @@ -38,6 +38,7 @@ export const Example = () => ( base={0} target={226.5} actual={0} + domain={{ min: 0, max: 300 }} bands={[210, 218, 224, 229, 235, 243, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/14_one_third.story.tsx b/storybook/stories/goal/14_one_third.story.tsx index cc28b62ffd..4be3905283 100644 --- a/storybook/stories/goal/14_one_third.story.tsx +++ b/storybook/stories/goal/14_one_third.story.tsx @@ -34,6 +34,7 @@ export const Example = () => ( base={0} target={260} actual={280} + domain={{ min: 0, max: 300 }} bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/15_half_circle.story.tsx b/storybook/stories/goal/15_half_circle.story.tsx index 4799b373eb..6fe7ff39dd 100644 --- a/storybook/stories/goal/15_half_circle.story.tsx +++ b/storybook/stories/goal/15_half_circle.story.tsx @@ -34,6 +34,7 @@ export const Example = () => ( base={0} target={260} actual={280} + domain={{ min: 0, max: 300 }} bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/16_two_thirds.story.tsx b/storybook/stories/goal/16_two_thirds.story.tsx index a1050859c2..642fdc2363 100644 --- a/storybook/stories/goal/16_two_thirds.story.tsx +++ b/storybook/stories/goal/16_two_thirds.story.tsx @@ -34,6 +34,7 @@ export const Example = () => ( base={0} target={260} actual={280} + domain={{ min: 0, max: 300 }} bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/17_three_quarters.story.tsx b/storybook/stories/goal/17_three_quarters.story.tsx index b856e74731..7f477704a6 100644 --- a/storybook/stories/goal/17_three_quarters.story.tsx +++ b/storybook/stories/goal/17_three_quarters.story.tsx @@ -34,6 +34,7 @@ export const Example = () => ( base={0} target={260} actual={280} + domain={{ min: 0, max: 300 }} bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/17_total_circle.story.tsx b/storybook/stories/goal/17_total_circle.story.tsx index 564631bcc0..5c0f883176 100644 --- a/storybook/stories/goal/17_total_circle.story.tsx +++ b/storybook/stories/goal/17_total_circle.story.tsx @@ -35,6 +35,7 @@ export const Example = () => { base={0} target={260} actual={280} + domain={{ min: 0, max: 300 }} bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 265, 280]} tickValueFormatter={({ value }) => String(value)} diff --git a/storybook/stories/goal/17_very_small_gap.story.tsx b/storybook/stories/goal/17_very_small_gap.story.tsx index 26e27cf419..caf5402f34 100644 --- a/storybook/stories/goal/17_very_small_gap.story.tsx +++ b/storybook/stories/goal/17_very_small_gap.story.tsx @@ -34,6 +34,7 @@ export const Example = () => ( base={0} target={260} actual={280} + domain={{ min: 0, max: 300 }} bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 265, 280]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/18_side_gauge.story.tsx b/storybook/stories/goal/18_side_gauge.story.tsx index 8a478720fd..b4a95abe1f 100644 --- a/storybook/stories/goal/18_side_gauge.story.tsx +++ b/storybook/stories/goal/18_side_gauge.story.tsx @@ -32,6 +32,7 @@ export const Example = () => ( base={0} target={260} actual={280} + domain={{ min: 0, max: 300 }} bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/18_side_gauge_inverted_angle_relation.story.tsx b/storybook/stories/goal/18_side_gauge_inverted_angle_relation.story.tsx index 6fdd5e4500..7bda47aeab 100644 --- a/storybook/stories/goal/18_side_gauge_inverted_angle_relation.story.tsx +++ b/storybook/stories/goal/18_side_gauge_inverted_angle_relation.story.tsx @@ -32,6 +32,7 @@ export const Example = () => ( base={0} target={260} actual={280} + domain={{ min: 0, max: 300 }} bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/19_horizontal_negative.story.tsx b/storybook/stories/goal/19_horizontal_negative.story.tsx index 4279b0bade..41a4e1c881 100644 --- a/storybook/stories/goal/19_horizontal_negative.story.tsx +++ b/storybook/stories/goal/19_horizontal_negative.story.tsx @@ -38,6 +38,7 @@ export const Example = () => ( base={0} target={-260} actual={-280} + domain={{ min: -300, max: 0 }} bands={[-200, -250, -300]} ticks={[0, -50, -100, -150, -200, -250, -300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/20_vertical_negative.story.tsx b/storybook/stories/goal/20_vertical_negative.story.tsx index e87e4cb35c..98a57e0d27 100644 --- a/storybook/stories/goal/20_vertical_negative.story.tsx +++ b/storybook/stories/goal/20_vertical_negative.story.tsx @@ -38,6 +38,7 @@ export const Example = () => ( base={0} target={-260} actual={-280} + domain={{ min: -300, max: 0 }} bands={[-200, -250, -300]} ticks={[0, -50, -100, -150, -200, -250, -300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/21_goal_negative.story.tsx b/storybook/stories/goal/21_goal_negative.story.tsx index 68e1f49b20..f30db1a373 100644 --- a/storybook/stories/goal/21_goal_negative.story.tsx +++ b/storybook/stories/goal/21_goal_negative.story.tsx @@ -38,6 +38,7 @@ export const Example = () => ( base={0} target={-260} actual={-280} + domain={{ min: -300, max: 0 }} bands={[-200, -250, -300]} ticks={[0, -50, -100, -150, -200, -250, -300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/22_horizontal_plusminus.story.tsx b/storybook/stories/goal/22_horizontal_plusminus.story.tsx index 305108a944..511d34e687 100644 --- a/storybook/stories/goal/22_horizontal_plusminus.story.tsx +++ b/storybook/stories/goal/22_horizontal_plusminus.story.tsx @@ -40,6 +40,7 @@ export const Example = () => ( base={0} target={260} actual={-80} + domain={{ min: -200, max: 300 }} bands={[-200, -100, 0, 200, 250, 300]} ticks={[-200, -100, 0, 100, 200, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/23_vertical_plusminus.story.tsx b/storybook/stories/goal/23_vertical_plusminus.story.tsx index bafc02e92e..f8257d342c 100644 --- a/storybook/stories/goal/23_vertical_plusminus.story.tsx +++ b/storybook/stories/goal/23_vertical_plusminus.story.tsx @@ -40,6 +40,7 @@ export const Example = () => ( base={0} target={260} actual={-80} + domain={{ min: -200, max: 300 }} bands={[-200, -100, 0, 200, 250, 300]} ticks={[-200, -100, 0, 100, 200, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/24_goal_plusminus.story.tsx b/storybook/stories/goal/24_goal_plusminus.story.tsx index 4e26971db8..b2b4d6f650 100644 --- a/storybook/stories/goal/24_goal_plusminus.story.tsx +++ b/storybook/stories/goal/24_goal_plusminus.story.tsx @@ -40,6 +40,7 @@ export const Example = () => ( base={0} target={260} actual={-80} + domain={{ min: -100, max: 300 }} bands={[-100, -50, 0, 200, 250, 300]} ticks={[-100, 0, 100, 200, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/25_goal_semantic.story.tsx b/storybook/stories/goal/25_goal_semantic.story.tsx index 673ab45816..fec2b5c01e 100644 --- a/storybook/stories/goal/25_goal_semantic.story.tsx +++ b/storybook/stories/goal/25_goal_semantic.story.tsx @@ -39,7 +39,7 @@ export const Example = () => { base={0} target={260} actual={170} - // doesn't mess with canvas_renderers.ts + domain={{ min: 0, max: 300 }} bands={bands} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/26_auto_linear_ticks.story.tsx b/storybook/stories/goal/26_auto_linear_ticks.story.tsx new file mode 100644 index 0000000000..b7b25339f7 --- /dev/null +++ b/storybook/stories/goal/26_auto_linear_ticks.story.tsx @@ -0,0 +1,65 @@ +/* + * 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 { array, boolean, number } from '@storybook/addon-knobs'; +import React from 'react'; + +import { Chart, Goal, Settings } from '@elastic/charts'; +import { BandFillColorAccessorInput } from '@elastic/charts/src/chart_types/goal_chart/specs'; +import { GoalSubtype } from '@elastic/charts/src/chart_types/goal_chart/specs/constants'; + +import { useBaseTheme } from '../../use_base_theme'; +import { getKnobsFromEnum } from '../utils/knobs'; + +export const Example = () => { + const subtype = + getKnobsFromEnum('subtype', GoalSubtype, GoalSubtype.VerticalBullet as GoalSubtype) ?? GoalSubtype.VerticalBullet; + const reverse = boolean('reverse', false); + const start = number('angleStart (π)', 5 / 4, { min: -2, max: 2, step: 1 / 8 }); + const end = number('angleEnd (π)', -1 / 4, { min: -2, max: 2, step: 1 / 8 }); + const base = number('base', 0); + const target = number('target', 260); + const actual = number('actual', 280); + const min = number('domain min', 0, { min: 0, step: 50 }); + const max = number('domain max', 300, { min, step: 50 }); + const autoTicks = boolean('auto generate ticks', true); + const ticks = autoTicks ? undefined : array('ticks', ['0', '100', '200', '300']).map(Number); + const autoBands = boolean('auto generate bands', true); + const bands = autoBands ? undefined : array('bands', ['200', '250', '300']).map(Number); + + const angleStart = start * Math.PI; + const angleEnd = end * Math.PI; + + return ( + + + String(value)} + labelMajor="Speed average" + labelMinor={subtype === GoalSubtype.Goal ? '' : `${actual} MB/s`} + centralMajor={`${actual} MB/s`} + centralMinor="" + /> + + ); +}; + +Example.parameters = { + markdown: `Leaving \`ticks\` and/or \`bands\` as \`undefined\` will automatically generate linear values given the specified domain. +If \`ticks\` and/or \`bands\` is set to \`[]\` (empty array), no ticks or bands will be displayed, respectively`, +}; diff --git a/storybook/stories/goal/2_gauge_with_target.story.tsx b/storybook/stories/goal/2_gauge_with_target.story.tsx index 745d304171..16d30d98e5 100644 --- a/storybook/stories/goal/2_gauge_with_target.story.tsx +++ b/storybook/stories/goal/2_gauge_with_target.story.tsx @@ -66,6 +66,7 @@ export const Example = () => { actual={actual} bands={bands} ticks={ticks} + domain={{ min: 0, max: 300 }} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} bandFillColor={useColors ? ({ value }: BandFillColorAccessorInput) => bandFillColor(value) : undefined} labelMajor="Revenue 2020 YTD " diff --git a/storybook/stories/goal/3_horizontal_bullet.story.tsx b/storybook/stories/goal/3_horizontal_bullet.story.tsx index fc11ef1329..69382f66ed 100644 --- a/storybook/stories/goal/3_horizontal_bullet.story.tsx +++ b/storybook/stories/goal/3_horizontal_bullet.story.tsx @@ -38,6 +38,7 @@ export const Example = () => ( base={0} target={260} actual={280} + domain={{ min: 0, max: 300 }} bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/4_vertical_bullet.story.tsx b/storybook/stories/goal/4_vertical_bullet.story.tsx index e10a245089..ec633f6177 100644 --- a/storybook/stories/goal/4_vertical_bullet.story.tsx +++ b/storybook/stories/goal/4_vertical_bullet.story.tsx @@ -38,6 +38,7 @@ export const Example = () => ( base={0} target={260} actual={280} + domain={{ min: 0, max: 300 }} bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/5_minimal.story.tsx b/storybook/stories/goal/5_minimal.story.tsx index 0e4a67d189..bc64cc0a2d 100644 --- a/storybook/stories/goal/5_minimal.story.tsx +++ b/storybook/stories/goal/5_minimal.story.tsx @@ -39,6 +39,7 @@ export const Example = () => ( base={0} target={260} actual={280} + domain={{ min: 0, max: 300 }} bands={[]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/6_minimal_horizontal.story.tsx b/storybook/stories/goal/6_minimal_horizontal.story.tsx index 50cd4ce1e7..3a9ec2df68 100644 --- a/storybook/stories/goal/6_minimal_horizontal.story.tsx +++ b/storybook/stories/goal/6_minimal_horizontal.story.tsx @@ -38,6 +38,7 @@ export const Example = () => ( base={0} target={260} actual={280} + domain={{ min: 0, max: 300 }} bands={[300]} ticks={[0, 100, 200, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/7_horizontal_bar.story.tsx b/storybook/stories/goal/7_horizontal_bar.story.tsx index 34df6e1a0d..febbacebf9 100644 --- a/storybook/stories/goal/7_horizontal_bar.story.tsx +++ b/storybook/stories/goal/7_horizontal_bar.story.tsx @@ -38,6 +38,7 @@ export const Example = () => ( base={0} target={260} actual={280} + domain={{ min: 0, max: 300 }} bands={[]} ticks={[0, 100, 200, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/8_irregular_ticks.story.tsx b/storybook/stories/goal/8_irregular_ticks.story.tsx index 48b6ae224e..da4511e1c8 100644 --- a/storybook/stories/goal/8_irregular_ticks.story.tsx +++ b/storybook/stories/goal/8_irregular_ticks.story.tsx @@ -34,6 +34,7 @@ export const Example = () => ( base={0} target={260} actual={280} + domain={{ min: 0, max: 300 }} bands={[200, 250, 300]} ticks={[0, 100, 200, 250, 260, 270, 280, 290, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/9_minimal_band.story.tsx b/storybook/stories/goal/9_minimal_band.story.tsx index a2867d18b0..adda20be2d 100644 --- a/storybook/stories/goal/9_minimal_band.story.tsx +++ b/storybook/stories/goal/9_minimal_band.story.tsx @@ -32,6 +32,7 @@ export const Example = () => ( base={0} target={225} actual={0} + domain={{ min: 0, max: 300 }} bands={[300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)} diff --git a/storybook/stories/goal/goal.stories.tsx b/storybook/stories/goal/goal.stories.tsx index 8489beca87..6b60179f27 100644 --- a/storybook/stories/goal/goal.stories.tsx +++ b/storybook/stories/goal/goal.stories.tsx @@ -37,3 +37,4 @@ export { Example as horizontalPlusMinus } from './22_horizontal_plusminus.story' export { Example as verticalPlusMinus } from './23_vertical_plusminus.story'; export { Example as goalPlusMinus } from './24_goal_plusminus.story'; export { Example as goalSemantics } from './25_goal_semantic.story'; +export { Example as autoLinearTicks } from './26_auto_linear_ticks.story'; diff --git a/storybook/stories/interactions/17_png_export.story.tsx b/storybook/stories/interactions/17_png_export.story.tsx index ad25170f23..245aa2ef7c 100644 --- a/storybook/stories/interactions/17_png_export.story.tsx +++ b/storybook/stories/interactions/17_png_export.story.tsx @@ -145,6 +145,7 @@ function renderGoalchart() { base={0} target={260} actual={280} + domain={{ min: 0, max: 300 }} bands={[200, 250, 300]} ticks={[0, 50, 100, 150, 200, 250, 300]} tickValueFormatter={({ value }: BandFillColorAccessorInput) => String(value)}