diff --git a/api/charts.api.md b/api/charts.api.md index 38f5b209d0..f4e708d85f 100644 --- a/api/charts.api.md +++ b/api/charts.api.md @@ -6,7 +6,7 @@ import { $Values } from 'utility-types'; import { ComponentType } from 'react'; -import React from 'react'; +import { default as React_2 } from 'react'; import { ReactChild } from 'react'; // @public @@ -103,7 +103,7 @@ export interface ArcStyle { // Warning: (ae-missing-release-tag) "AreaSeries" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const AreaSeries: React.FunctionComponent; +export const AreaSeries: React_2.FunctionComponent; // @public export type AreaSeriesSpec = BasicSeriesSpec & HistogramConfig & Postfixes & { @@ -160,7 +160,7 @@ export interface ArrayNode extends NodeDescriptor { // Warning: (ae-missing-release-tag) "Axis" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const Axis: React.FunctionComponent; +export const Axis: React_2.FunctionComponent; // @public (undocumented) export type AxisId = string; @@ -262,7 +262,7 @@ export interface BandFillColorAccessorInput { // Warning: (ae-missing-release-tag) "BarSeries" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const BarSeries: React.FunctionComponent; +export const BarSeries: React_2.FunctionComponent; // @public export type BarSeriesSpec = BasicSeriesSpec & Postfixes & { @@ -360,7 +360,7 @@ export type BrushEndListener = (brushArea: XYBrushArea) => void; // Warning: (ae-forgotten-export) The symbol "SpecOptionalProps" needs to be exported by the entry point index.d.ts // // @alpha -export const BubbleSeries: React.FunctionComponent; +export const BubbleSeries: React_2.FunctionComponent; // @alpha export type BubbleSeriesSpec = BasicSeriesSpec & { @@ -419,7 +419,7 @@ export interface Cell { // Warning: (ae-missing-release-tag) "Chart" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class Chart extends React.Component { +export class Chart extends React_2.Component { constructor(props: ChartProps); // (undocumented) componentDidMount(): void; @@ -428,9 +428,9 @@ export class Chart extends React.Component { // (undocumented) static defaultProps: ChartProps; // (undocumented) - dispatchExternalPointerEvent(event: PointerEvent): void; + dispatchExternalPointerEvent(event: PointerEvent_2): void; // (undocumented) - getChartContainerRef: () => React.RefObject; + getChartContainerRef: () => React_2.RefObject; // (undocumented) getPNGSnapshot(options?: { backgroundColor: string; @@ -461,7 +461,7 @@ export interface ChartSizeObject { // Warning: (ae-missing-release-tag) "ChartTypes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public export const ChartTypes: Readonly<{ Global: "global"; Goal: "goal"; @@ -517,6 +517,11 @@ export type ColorVariant = $Values; // @public (undocumented) export type CompleteBoundedDomain = DomainBase & LowerBound & UpperBound; +// Warning: (ae-missing-release-tag) "ContinuousDomain" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ContinuousDomain = [min: number, max: number]; + // Warning: (ae-missing-release-tag) "CrosshairStyle" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -858,7 +863,7 @@ export function getNodeName(node: ArrayNode): string; // Warning: (ae-forgotten-export) The symbol "SpecOptionalProps" needs to be exported by the entry point index.d.ts // // @alpha (undocumented) -export const Goal: React.FunctionComponent; +export const Goal: React_2.FunctionComponent; // @alpha (undocumented) export interface GoalSpec extends Spec { @@ -923,7 +928,7 @@ export interface GroupBrushExtent { } // @alpha (undocumented) -export const GroupBy: React.FunctionComponent; +export const GroupBy: React_2.FunctionComponent; // @alpha (undocumented) export type GroupByAccessor = (spec: Spec, datum: any) => string | number; @@ -952,7 +957,7 @@ export interface GroupBySpec extends Spec { export type GroupId = string; // @alpha (undocumented) -export const Heatmap: React.FunctionComponent & Partial>>; +export const Heatmap: React_2.FunctionComponent & Partial>>; // Warning: (ae-missing-release-tag) "HeatmapBrushEvent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1131,7 +1136,7 @@ export type HierarchyOfArrays = Array; // Warning: (ae-missing-release-tag) "HistogramBarSeries" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const HistogramBarSeries: React.FunctionComponent; +export const HistogramBarSeries: React_2.FunctionComponent; // @public export type HistogramBarSeriesSpec = Omit & { @@ -1273,7 +1278,7 @@ export const LIGHT_THEME: Theme; // Warning: (ae-missing-release-tag) "LineAnnotation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const LineAnnotation: React.FunctionComponent; +export const LineAnnotation: React_2.FunctionComponent; // @public export interface LineAnnotationDatum { @@ -1308,7 +1313,7 @@ export interface LineAnnotationStyle { // Warning: (ae-missing-release-tag) "LineSeries" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const LineSeries: React.FunctionComponent; +export const LineSeries: React_2.FunctionComponent; // @public export type LineSeriesSpec = BasicSeriesSpec & HistogramConfig & { @@ -1340,6 +1345,19 @@ export interface LineStyle { visible: boolean; } +// Warning: (ae-missing-release-tag) "LogBase" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "LogBase" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const LogBase: Readonly<{ + Common: "common"; + Binary: "binary"; + Natural: "natural"; +}>; + +// @public +export type LogBase = $Values; + // @public (undocumented) export type LowerBoundedDomain = DomainBase & LowerBound; @@ -1410,6 +1428,11 @@ export interface OrderBy { direction?: Direction; } +// Warning: (ae-missing-release-tag) "OrdinalDomain" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type OrdinalDomain = (number | string)[]; + // Warning: (ae-missing-release-tag) "PARENT_KEY" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1428,7 +1451,7 @@ export type PartialTheme = RecursivePartial; // Warning: (ae-missing-release-tag) "Partition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const Partition: React.FunctionComponent; +export const Partition: React_2.FunctionComponent; // Warning: (ae-forgotten-export) The symbol "StaticConfig" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "Config" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1520,7 +1543,9 @@ export type Placement = $Values; // Warning: (ae-missing-release-tag) "PointerEvent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type PointerEvent = PointerOverEvent | PointerOutEvent; +type PointerEvent_2 = PointerOverEvent | PointerOutEvent; + +export { PointerEvent_2 as PointerEvent } // Warning: (ae-missing-release-tag) "PointerEventType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1558,7 +1583,7 @@ export interface PointerOverEvent extends BasePointerEvent { // Warning: (ae-missing-release-tag) "PointerUpdateListener" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type PointerUpdateListener = (event: PointerEvent) => void; +export type PointerUpdateListener = (event: PointerEvent_2) => void; // Warning: (ae-missing-release-tag) "PointShape" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1640,7 +1665,7 @@ export type RawTextGetter = (node: ShapeTreeNode) => string; // Warning: (ae-missing-release-tag) "RectAnnotation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const RectAnnotation: React.FunctionComponent & Partial>>; +export const RectAnnotation: React_2.FunctionComponent & Partial>>; // Warning: (ae-missing-release-tag) "RectAnnotationDatum" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1711,6 +1736,16 @@ export type ScaleBandType = ScaleOrdinalType; // @public (undocumented) export type ScaleContinuousType = typeof ScaleType.Linear | typeof ScaleType.Time | typeof ScaleType.Log | typeof ScaleType.Sqrt; +// Warning: (ae-missing-release-tag) "ScaleLogOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ScaleLogOptions { + xLogBase?: LogBase; + xLogMinLimit?: number; + yLogBase?: LogBase; + yLogMinLimit?: number; +} + // Warning: (ae-missing-release-tag) "ScaleOrdinalType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1724,7 +1759,9 @@ export interface ScalesConfig { histogramPadding: number; } -// @public +// Warning: (ae-missing-release-tag) "ScaleType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) export const ScaleType: Readonly<{ Linear: "linear"; Ordinal: "ordinal"; @@ -1736,7 +1773,7 @@ export const ScaleType: Readonly<{ Threshold: "threshold"; }>; -// @public (undocumented) +// @public export type ScaleType = $Values; // Warning: (ae-missing-release-tag) "SectorGeomSpecY" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1852,6 +1889,8 @@ export interface SeriesSpec extends Spec { // @public (undocumented) export type SeriesSpecs = Array; +// Warning: (ae-missing-release-tag) "SeriesTypes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// // @public (undocumented) export const SeriesTypes: Readonly<{ Area: "area"; @@ -1860,13 +1899,13 @@ export const SeriesTypes: Readonly<{ Bubble: "bubble"; }>; -// @public (undocumented) +// @public export type SeriesTypes = $Values; // Warning: (ae-missing-release-tag) "Settings" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const Settings: React.FunctionComponent; +export const Settings: React_2.FunctionComponent; // @public export interface SettingsSpec extends Spec { @@ -1923,15 +1962,14 @@ export interface SettingsSpec extends Spec { // (undocumented) rotation: Rotation; roundHistogramBrushValues?: boolean; + scaleLogOptions?: ScaleLogOptions; // (undocumented) showLegend: boolean; showLegendExtra: boolean; theme?: PartialTheme | PartialTheme[]; tooltip: TooltipSettings; - // Warning: (ae-forgotten-export) The symbol "Domain" needs to be exported by the entry point index.d.ts - // // (undocumented) - xDomain?: Domain | DomainRange; + xDomain?: ContinuousDomain | OrdinalDomain | DomainRange; } // Warning: (ae-missing-release-tag) "SettingsSpecProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1985,7 +2023,7 @@ export interface SimplePadding { } // @alpha (undocumented) -export const SmallMultiples: React.FunctionComponent; +export const SmallMultiples: React_2.FunctionComponent; // @alpha (undocumented) export type SmallMultiplesProps = Partial>; diff --git a/docs/charts.tsx b/docs/charts.tsx index 58153b9fa8..fe4fe38b99 100644 --- a/docs/charts.tsx +++ b/docs/charts.tsx @@ -100,7 +100,7 @@ function generateAnnotationData(values: any[]): LineAnnotationDatum[] { return values.map((value, index) => ({ dataValue: value, details: `detail-${index}` })); } -export const lineBasicXDomainContinous = () => { +export const lineBasicXDomainContinuous = () => { const data = arrayKnobs('data values', [2.5, 7.2]); const dataValues = generateAnnotationData(data); @@ -149,7 +149,7 @@ export const lineBasicXDomainContinous = () => { ); }; -lineBasicXDomainContinous.story = { +lineBasicXDomainContinuous.story = { name: '[line] basic xDomain continuous', }; diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-scales-log-scale-options-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-scales-log-scale-options-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000..b53713482d Binary files /dev/null and b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-scales-log-scale-options-visually-looks-correct-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/area-stories-test-ts-area-series-stories-negative-log-areas-shows-negative-values-with-log-scale-1-snap.png b/integration/tests/__image_snapshots__/area-stories-test-ts-area-series-stories-negative-log-areas-shows-negative-values-with-log-scale-1-snap.png index cb29697295..26cbef6748 100644 Binary files a/integration/tests/__image_snapshots__/area-stories-test-ts-area-series-stories-negative-log-areas-shows-negative-values-with-log-scale-1-snap.png and b/integration/tests/__image_snapshots__/area-stories-test-ts-area-series-stories-negative-log-areas-shows-negative-values-with-log-scale-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/area-stories-test-ts-area-series-stories-negative-log-areas-shows-only-positive-domain-mixed-polarity-domain-with-limit-1-snap.png b/integration/tests/__image_snapshots__/area-stories-test-ts-area-series-stories-negative-log-areas-shows-only-positive-domain-mixed-polarity-domain-with-limit-1-snap.png new file mode 100644 index 0000000000..d555330e5a Binary files /dev/null and b/integration/tests/__image_snapshots__/area-stories-test-ts-area-series-stories-negative-log-areas-shows-only-positive-domain-mixed-polarity-domain-with-limit-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/area-stories-test-ts-area-series-stories-negative-log-areas-shows-only-positive-domain-mixed-polarity-domain-with-limit-of-0-1-snap.png b/integration/tests/__image_snapshots__/area-stories-test-ts-area-series-stories-negative-log-areas-shows-only-positive-domain-mixed-polarity-domain-with-limit-of-0-1-snap.png new file mode 100644 index 0000000000..5a5f41d12a Binary files /dev/null and b/integration/tests/__image_snapshots__/area-stories-test-ts-area-series-stories-negative-log-areas-shows-only-positive-domain-mixed-polarity-domain-with-limit-of-0-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/scales-stories-test-ts-scales-stories-should-render-proper-tick-count-1-snap.png b/integration/tests/__image_snapshots__/scales-stories-test-ts-scales-stories-should-render-proper-tick-count-1-snap.png new file mode 100644 index 0000000000..b53713482d Binary files /dev/null and b/integration/tests/__image_snapshots__/scales-stories-test-ts-scales-stories-should-render-proper-tick-count-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/scales-stories-test-ts-scales-stories-should-render-proper-tick-count-2-snap.png b/integration/tests/__image_snapshots__/scales-stories-test-ts-scales-stories-should-render-proper-tick-count-2-snap.png new file mode 100644 index 0000000000..85f53bd905 Binary files /dev/null and b/integration/tests/__image_snapshots__/scales-stories-test-ts-scales-stories-should-render-proper-tick-count-2-snap.png differ diff --git a/integration/tests/__image_snapshots__/scales-stories-test-ts-scales-stories-should-render-proper-tick-count-3-snap.png b/integration/tests/__image_snapshots__/scales-stories-test-ts-scales-stories-should-render-proper-tick-count-3-snap.png new file mode 100644 index 0000000000..994f760b85 Binary files /dev/null and b/integration/tests/__image_snapshots__/scales-stories-test-ts-scales-stories-should-render-proper-tick-count-3-snap.png differ diff --git a/integration/tests/area_stories.test.ts b/integration/tests/area_stories.test.ts index 60fd1dad3d..66a9eada27 100644 --- a/integration/tests/area_stories.test.ts +++ b/integration/tests/area_stories.test.ts @@ -75,6 +75,18 @@ describe('Area series stories', () => { ); }); + it('shows only positive domain mixed polarity domain with limit', async () => { + await common.expectChartAtUrlToMatchScreenshot( + 'http://localhost:9001/?path=/story/area-chart--with-negative-and-positive&knob-Y scale=log&knob-Y log limit=0.01', + ); + }); + + it('shows only positive domain mixed polarity domain with limit of 0', async () => { + await common.expectChartAtUrlToMatchScreenshot( + 'http://localhost:9001/?path=/story/area-chart--with-negative-and-positive&knob-Y scale=log&knob-Y log limit=0', + ); + }); + it('shows only positive values when hiding negative one', async () => { const action = async () => { await common.disableAnimations(); diff --git a/integration/tests/scales_stories.test.ts b/integration/tests/scales_stories.test.ts new file mode 100644 index 0000000000..1cc2ab6025 --- /dev/null +++ b/integration/tests/scales_stories.test.ts @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { LogBase } from '../../src/scales/scale_continuous'; +import { common } from '../page_objects'; + +describe('Scales stories', () => { + it.each(Object.values(LogBase))('should render proper tick count', async (base) => { + await common.expectChartAtUrlToMatchScreenshot( + `http://localhost:9001/?path=/story/scales--log-scale-options&knob-Log base_Y - Axis=${base}`, + ); + }); +}); diff --git a/package.json b/package.json index 5e7529e727..d39dfca3c8 100644 --- a/package.json +++ b/package.json @@ -87,8 +87,8 @@ "@elastic/eui": "^27.0.0", "@elastic/github-checks-reporter": "^0.0.20-b3", "@mdx-js/loader": "^1.6.6", - "@microsoft/api-documenter": "^7.7.20", - "@microsoft/api-extractor": "^7.7.9", + "@microsoft/api-documenter": "^7.12.7", + "@microsoft/api-extractor": "^7.13.1", "@semantic-release/changelog": "^5.0.1", "@semantic-release/commit-analyzer": "^8.0.1", "@semantic-release/git": "^9.0.0", diff --git a/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts b/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts index dcf5e99b43..20302ab943 100644 --- a/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts +++ b/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts @@ -104,7 +104,8 @@ export function shapeViewModel( const yInvertedScale = scaleQuantize().domain([0, height]).range(yValues); - let xValues = xDomain.domain; + // TODO: Fix domain type to be `Array` + let xValues = xDomain.domain as any[]; const timeScale = xDomain.scaleType === ScaleType.Time @@ -123,8 +124,8 @@ export function shapeViewModel( if (timeScale) { const result = []; - let [timePoint] = xDomain.domain; - while (timePoint < xDomain.domain[1]) { + let [timePoint] = xValues; + while (timePoint < xValues[1]) { result.push(timePoint); timePoint += xDomain.minInterval; } diff --git a/src/chart_types/index.ts b/src/chart_types/index.ts index a38aee1994..ab53ef2cfc 100644 --- a/src/chart_types/index.ts +++ b/src/chart_types/index.ts @@ -19,6 +19,10 @@ import { $Values } from 'utility-types'; +/** + * Available chart types + * @public + */ export const ChartTypes = Object.freeze({ Global: 'global' as const, Goal: 'goal' as const, @@ -26,6 +30,4 @@ export const ChartTypes = Object.freeze({ XYAxis: 'xy_axis' as const, Heatmap: 'heatmap' as const, }); - -/** @public */ export type ChartTypes = $Values; diff --git a/src/chart_types/xy_chart/domains/types.ts b/src/chart_types/xy_chart/domains/types.ts index bb3509d461..7939ba14cb 100644 --- a/src/chart_types/xy_chart/domains/types.ts +++ b/src/chart_types/xy_chart/domains/types.ts @@ -19,12 +19,11 @@ import { ScaleContinuousType } from '../../../scales'; import { ScaleType } from '../../../scales/constants'; -import { Domain } from '../../../utils/domain'; +import { OrdinalDomain, ContinuousDomain } from '../../../utils/domain'; import { GroupId } from '../../../utils/ids'; export interface BaseDomain { scaleType: typeof ScaleType.Ordinal | ScaleContinuousType; - domain: Domain; /* if the scale needs to be a band scale: used when displaying bars */ isBandScale: boolean; } @@ -35,6 +34,7 @@ export type XDomain = BaseDomain & { minInterval: number; /** if x domain is time, we should also specify the timezone */ timeZone?: string; + domain: OrdinalDomain | ContinuousDomain; }; export type YDomain = BaseDomain & { @@ -42,4 +42,5 @@ export type YDomain = BaseDomain & { isBandScale: false; scaleType: ScaleContinuousType; groupId: GroupId; + domain: ContinuousDomain; }; diff --git a/src/chart_types/xy_chart/domains/x_domain.ts b/src/chart_types/xy_chart/domains/x_domain.ts index de97fedd5e..a0272222b9 100644 --- a/src/chart_types/xy_chart/domains/x_domain.ts +++ b/src/chart_types/xy_chart/domains/x_domain.ts @@ -21,7 +21,12 @@ import { Optional } from 'utility-types'; import { ScaleType } from '../../../scales/constants'; import { compareByValueAsc, identity } from '../../../utils/common'; -import { computeContinuousDataDomain, computeOrdinalDataDomain, Domain } from '../../../utils/domain'; +import { + computeContinuousDataDomain, + computeOrdinalDataDomain, + OrdinalDomain, + ContinuousDomain, +} from '../../../utils/domain'; import { Logger } from '../../../utils/logger'; import { isCompleteBound, isLowerBound, isUpperBound } from '../utils/axis_type_utils'; import { BasicSeriesSpec, DomainRange, SeriesTypes, XScaleType } from '../utils/specs'; @@ -39,7 +44,7 @@ import { XDomain } from './types'; export function mergeXDomain( specs: Optional, 'seriesType'>[], xValues: Set, - customXDomain?: DomainRange | Domain, + customXDomain?: DomainRange | OrdinalDomain | ContinuousDomain, fallbackScale?: XScaleType, ): XDomain { const mainXScaleType = convertXScaleTypes(specs); @@ -76,7 +81,9 @@ export function mergeXDomain( } } } else { - seriesXComputedDomains = computeContinuousDataDomain(values, identity, { fit: true }); + seriesXComputedDomains = computeContinuousDataDomain(values, identity, mainXScaleType.scaleType === ScaleType.Log, { + fit: true, + }); let customMinInterval: undefined | number; if (customXDomain) { diff --git a/src/chart_types/xy_chart/domains/y_domain.ts b/src/chart_types/xy_chart/domains/y_domain.ts index 97fdbf6ef2..62cdea4e2f 100644 --- a/src/chart_types/xy_chart/domains/y_domain.ts +++ b/src/chart_types/xy_chart/domains/y_domain.ts @@ -20,7 +20,7 @@ import { ScaleContinuousType } from '../../../scales'; import { ScaleType } from '../../../scales/constants'; import { identity } from '../../../utils/common'; -import { computeContinuousDataDomain } from '../../../utils/domain'; +import { computeContinuousDataDomain, ContinuousDomain } from '../../../utils/domain'; import { GroupId } from '../../../utils/ids'; import { Logger } from '../../../utils/logger'; import { getSpecDomainGroupId } from '../state/utils/spec'; @@ -74,10 +74,11 @@ function mergeYDomainForGroup( const groupYScaleType = coerceYScaleTypes(yScaleTypes); const [{ stackMode, spec }] = dataSeries; const groupId = getSpecDomainGroupId(spec); + const isLogScale = groupYScaleType === ScaleType.Log; - let domain: number[]; + let domain: ContinuousDomain; if (stackMode === StackMode.Percentage) { - domain = computeContinuousDataDomain([0, 1], identity, customDomain); + domain = computeContinuousDataDomain([0, 1], identity, isLogScale, customDomain); } else { // TODO remove when removing yScaleToDataExtent const newCustomDomain = customDomain ? { ...customDomain } : {}; @@ -85,14 +86,20 @@ function mergeYDomainForGroup( if (customDomain?.fit !== true && shouldScaleToExtent) { newCustomDomain.fit = true; } + // compute stacked domain - const stackedDomain = computeYDomain(stacked, hasZeroBaselineSpecs); + const stackedDomain = computeYDomain(stacked, isLogScale, hasZeroBaselineSpecs); // compute non stacked domain - const nonStackedDomain = computeYDomain(nonStacked, hasZeroBaselineSpecs); + const nonStackedDomain = computeYDomain(nonStacked, isLogScale, hasZeroBaselineSpecs); // merge stacked and non stacked domain together - domain = computeContinuousDataDomain([...stackedDomain, ...nonStackedDomain], identity, newCustomDomain); + domain = computeContinuousDataDomain( + [...stackedDomain, ...nonStackedDomain], + identity, + isLogScale, + newCustomDomain, + ); const [computedDomainMin, computedDomainMax] = domain; @@ -124,7 +131,7 @@ function mergeYDomainForGroup( }; } -function computeYDomain(dataSeries: DataSeries[], hasZeroBaselineSpecs: boolean) { +function computeYDomain(dataSeries: DataSeries[], isLogScale: boolean, hasZeroBaselineSpecs: boolean) { const yValues = new Set(); dataSeries.forEach(({ data }) => { for (let i = 0; i < data.length; i++) { @@ -138,7 +145,7 @@ function computeYDomain(dataSeries: DataSeries[], hasZeroBaselineSpecs: boolean) if (yValues.size === 0) { return []; } - return computeContinuousDataDomain([...yValues.values()], identity, null); + return computeContinuousDataDomain([...yValues.values()], identity, isLogScale, null); } /** @internal */ diff --git a/src/chart_types/xy_chart/rendering/utils.ts b/src/chart_types/xy_chart/rendering/utils.ts index 6ed3516f2e..5a6f79523f 100644 --- a/src/chart_types/xy_chart/rendering/utils.ts +++ b/src/chart_types/xy_chart/rendering/utils.ts @@ -19,7 +19,6 @@ import { LegendItem } from '../../../common/legend'; import { Scale } from '../../../scales'; -import { LOG_MIN_ABS_DOMAIN } from '../../../scales/constants'; import { getDomainPolarity } from '../../../scales/scale_continuous'; import { isLogarithmicScale } from '../../../scales/types'; import { MarkBuffer } from '../../../specs'; @@ -163,11 +162,6 @@ export function isPointOnGeometry( * The default zero baseline for area charts. */ const DEFAULT_ZERO_BASELINE = 0; -/** - * The zero baseline for log scales. - * We are currently limiting to 1 as min accepted domain for a log scale. - */ -const DEFAULT_LOG_ZERO_BASELINE = LOG_MIN_ABS_DOMAIN; /** @internal */ export type YDefinedFn = ( @@ -203,12 +197,13 @@ export function getY1ScaledValueOrThrowFn(yScale: Scale): (datum: DataSeriesDatu export function getY0ScaledValueOrThrowFn(yScale: Scale): (datum: DataSeriesDatum) => number { const isLogScale = isLogarithmicScale(yScale); const domainPolarity = getDomainPolarity(yScale.domain); + const logBaseline = domainPolarity >= 0 ? Math.min(...yScale.domain) : Math.max(...yScale.domain); return ({ y0 }) => { if (y0 === null) { if (isLogScale) { // if all positive domain use 1 as baseline, -1 otherwise - return yScale.scaleOrThrow(domainPolarity >= 0 ? DEFAULT_LOG_ZERO_BASELINE : -DEFAULT_LOG_ZERO_BASELINE); + return yScale.scaleOrThrow(logBaseline); } return yScale.scaleOrThrow(DEFAULT_ZERO_BASELINE); } @@ -216,7 +211,7 @@ export function getY0ScaledValueOrThrowFn(yScale: Scale): (datum: DataSeriesDatu // wrong y0 polarity if ((domainPolarity >= 0 && y0 <= 0) || (domainPolarity < 0 && y0 >= 0)) { // if all positive domain use 1 as baseline, -1 otherwise - return yScale.scaleOrThrow(domainPolarity >= 0 ? DEFAULT_LOG_ZERO_BASELINE : -DEFAULT_LOG_ZERO_BASELINE); + return yScale.scaleOrThrow(logBaseline); } // if negative value, use -1 as max reference, 1 otherwise return yScale.scaleOrThrow(y0); diff --git a/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts b/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts index 7c68a34da9..ff298ef8ab 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts @@ -69,7 +69,7 @@ export const computeAxesGeometriesSelector = createCachedSelector( return getAxesGeometries( chartDimensions, chartTheme, - settingsSpec.rotation, + settingsSpec, axesSpecs, axesTicksDimensions, axesStyles, diff --git a/src/chart_types/xy_chart/state/selectors/compute_axis_ticks_dimensions.ts b/src/chart_types/xy_chart/state/selectors/compute_axis_ticks_dimensions.ts index 03a1c9a686..6b4b5bb814 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_axis_ticks_dimensions.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_axis_ticks_dimensions.ts @@ -74,7 +74,7 @@ export const computeAxisTicksDimensionsSelector = createCachedSelector( yDomains, totalBarsInCluster, bboxCalculator, - settingsSpec.rotation, + settingsSpec, axisStyle, fallBackTickFormatter, barsPadding, diff --git a/src/chart_types/xy_chart/state/selectors/compute_series_geometries.ts b/src/chart_types/xy_chart/state/selectors/compute_series_geometries.ts index 689444acb5..d4b60f28d1 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_series_geometries.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_series_geometries.ts @@ -57,7 +57,7 @@ export const computeSeriesGeometriesSelector = createCachedSelector( seriesDomainsAndData, seriesColors, chartTheme, - settingsSpec.rotation, + settingsSpec, axesSpecs, smallMultiplesScales, isHistogramMode, diff --git a/src/chart_types/xy_chart/state/selectors/compute_small_multiple_scales.ts b/src/chart_types/xy_chart/state/selectors/compute_small_multiple_scales.ts index fad03e68e3..7c4616b95e 100644 --- a/src/chart_types/xy_chart/state/selectors/compute_small_multiple_scales.ts +++ b/src/chart_types/xy_chart/state/selectors/compute_small_multiple_scales.ts @@ -22,7 +22,7 @@ import createCachedSelector from 're-reselect'; import { ScaleBand } from '../../../../scales'; import { DEFAULT_SM_PANEL_PADDING } from '../../../../specs/small_multiples'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; -import { Domain } from '../../../../utils/domain'; +import { OrdinalDomain } from '../../../../utils/domain'; import { computeChartDimensionsSelector } from './compute_chart_dimensions'; import { computeSeriesDomainsSelector } from './compute_series_domains'; import { getSmallMultipleSpec } from './get_small_multiples_spec'; @@ -50,7 +50,7 @@ export const computeSmallMultipleScalesSelector = createCachedSelector( /** * @internal */ -export function getScale(domain: Domain, maxRange: number, padding = DEFAULT_SM_PANEL_PADDING) { +export function getScale(domain: OrdinalDomain, maxRange: number, padding = DEFAULT_SM_PANEL_PADDING) { const singlePanelSmallMultiple = domain.length <= 1; const defaultDomain = domain.length === 0 ? [undefined] : domain; return new ScaleBand(defaultDomain, [0, maxRange], undefined, singlePanelSmallMultiple ? 0 : padding); diff --git a/src/chart_types/xy_chart/state/utils/types.ts b/src/chart_types/xy_chart/state/utils/types.ts index 1eb62f2c31..2eba669041 100644 --- a/src/chart_types/xy_chart/state/utils/types.ts +++ b/src/chart_types/xy_chart/state/utils/types.ts @@ -18,7 +18,7 @@ */ import { Scale } from '../../../../scales'; -import { Domain } from '../../../../utils/domain'; +import { OrdinalDomain } from '../../../../utils/domain'; import { PointGeometry, BarGeometry, @@ -78,8 +78,8 @@ export interface ComputedGeometries { export interface SeriesDomainsAndData { xDomain: XDomain; yDomains: YDomain[]; - smVDomain: Domain; - smHDomain: Domain; + smVDomain: OrdinalDomain; + smHDomain: OrdinalDomain; formattedDataSeries: DataSeries[]; } diff --git a/src/chart_types/xy_chart/state/utils/utils.test.ts b/src/chart_types/xy_chart/state/utils/utils.test.ts index cbf9ae7991..e8bfe4cee0 100644 --- a/src/chart_types/xy_chart/state/utils/utils.test.ts +++ b/src/chart_types/xy_chart/state/utils/utils.test.ts @@ -27,6 +27,7 @@ import { ScaleContinuous } from '../../../../scales'; import { ScaleType } from '../../../../scales/constants'; import { Spec } from '../../../../specs'; import { BARCHART_1Y0G, BARCHART_1Y1G } from '../../../../utils/data_samples/test_dataset'; +import { ContinuousDomain, Range } from '../../../../utils/domain'; import { SpecId } from '../../../../utils/ids'; import { PointShape } from '../../../../utils/themes/theme'; import { getSeriesIndex, XYChartSeriesIdentifier } from '../../utils/series'; @@ -85,14 +86,14 @@ describe('Chart State utils', () => { }); expect(domains.yDomains).toEqual([ { - domain: [0, 10], + domain: [1, 10], scaleType: ScaleType.Log, groupId: 'group1', isBandScale: false, type: 'yDomain', }, { - domain: [0, 10], + domain: [1, 10], scaleType: ScaleType.Log, groupId: 'group2', isBandScale: false, @@ -133,14 +134,14 @@ describe('Chart State utils', () => { }); expect(domains.yDomains).toEqual([ { - domain: [0, 5], + domain: [1, 5], scaleType: ScaleType.Log, groupId: 'group1', isBandScale: false, type: 'yDomain', }, { - domain: [0, 9], + domain: [1, 9], scaleType: ScaleType.Log, groupId: 'group2', isBandScale: false, @@ -700,8 +701,8 @@ describe('Chart State utils', () => { }); test('can compute xScaleOffset dependent on histogram mode', () => { - const domain = [0, 10]; - const range: [number, number] = [0, 100]; + const domain: ContinuousDomain = [0, 10]; + const range: Range = [0, 100]; const bandwidth = 10; const barsPadding = 0.5; const scale = new ScaleContinuous( diff --git a/src/chart_types/xy_chart/state/utils/utils.ts b/src/chart_types/xy_chart/state/utils/utils.ts index b304dcc38e..000b0d85c0 100644 --- a/src/chart_types/xy_chart/state/utils/utils.ts +++ b/src/chart_types/xy_chart/state/utils/utils.ts @@ -18,13 +18,13 @@ */ import { SeriesKey, SeriesIdentifier } from '../../../../common/series_id'; -import { Scale } from '../../../../scales'; +import { LogBase, Scale } from '../../../../scales'; import { SortSeriesByConfig } from '../../../../specs'; -import { OrderBy } from '../../../../specs/settings'; +import { OrderBy, SettingsSpec } from '../../../../specs/settings'; import { mergePartial, Rotation, Color, isUniqueArray } from '../../../../utils/common'; import { CurveType } from '../../../../utils/curves'; import { Dimensions, Size } from '../../../../utils/dimensions'; -import { Domain } from '../../../../utils/domain'; +import { ContinuousDomain, OrdinalDomain } from '../../../../utils/domain'; import { PointGeometry, BarGeometry, @@ -133,7 +133,7 @@ export function computeSeriesDomains( seriesSpecs: BasicSeriesSpec[], customYDomainsByGroupId: Map = new Map(), deselectedDataSeries: SeriesIdentifier[] = [], - customXDomain?: DomainRange | Domain, + customXDomain?: DomainRange | ContinuousDomain | OrdinalDomain, orderOrdinalBinsBy?: OrderBy, smallMultiples?: SmallMultiplesGroupBy, sortSeriesBy?: SeriesCompareFn | SortSeriesByConfig, @@ -183,7 +183,7 @@ export function computeSeriesGeometries( { xDomain, yDomains, formattedDataSeries: nonFilteredDataSeries }: SeriesDomainsAndData, seriesColorMap: Map, chartTheme: Theme, - chartRotation: Rotation, + { rotation: chartRotation, scaleLogOptions: { yLogBase, yLogMinLimit, xLogBase, xLogMinLimit } = {} }: SettingsSpec, axesSpecs: AxisSpec[], smallMultiplesScales: SmallMultipleScales, enableHistogramMode: boolean, @@ -217,6 +217,8 @@ export function computeSeriesGeometries( const yScales = computeYScales({ yDomains, range: [isHorizontalRotation(chartRotation) ? vertical.bandwidth : horizontal.bandwidth, 0], + logBase: yLogBase, + logMinLimit: yLogMinLimit, }); const computedGeoms = renderGeometries( @@ -233,6 +235,8 @@ export function computeSeriesGeometries( chartTheme, enableHistogramMode, chartRotation, + xLogBase, + xLogMinLimit, ); const totalBarsInCluster = Object.values(barIndexByPanel).reduce((acc, curr) => { @@ -245,6 +249,8 @@ export function computeSeriesGeometries( range: [0, isHorizontalRotation(chartRotation) ? horizontal.bandwidth : vertical.bandwidth], barsPadding: enableHistogramMode ? chartTheme.scales.histogramPadding : chartTheme.scales.barsPadding, enableHistogramMode, + logBase: xLogBase, + logMinLimit: xLogMinLimit, }); return { @@ -321,6 +327,8 @@ function renderGeometries( chartTheme: Theme, enableHistogramMode: boolean, chartRotation: Rotation, + xLogBase?: LogBase, + xLogMinLimit?: number, ): Omit { const len = dataSeries.length; let i; @@ -365,6 +373,8 @@ function renderGeometries( range: [0, isHorizontalRotation(chartRotation) ? smHScale.bandwidth : smVScale.bandwidth], barsPadding, enableHistogramMode, + logBase: xLogBase, + logMinLimit: xLogMinLimit, }); const { stackMode } = ds; diff --git a/src/chart_types/xy_chart/utils/axis_utils.test.ts b/src/chart_types/xy_chart/utils/axis_utils.test.ts index f1fad9cb1b..7be490c741 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.test.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.test.ts @@ -25,12 +25,13 @@ import { MockGlobalSpec, MockSeriesSpec } from '../../../mocks/specs/specs'; import { MockStore } from '../../../mocks/store/store'; import { Scale } from '../../../scales'; import { ScaleType } from '../../../scales/constants'; +import { LogBase } from '../../../scales/scale_continuous'; import { SpecTypes } from '../../../specs/constants'; import { CanvasTextBBoxCalculator } from '../../../utils/bbox/canvas_text_bbox_calculator'; import { SvgTextBBoxCalculator } from '../../../utils/bbox/svg_text_bbox_calculator'; import { Position, mergePartial } from '../../../utils/common'; import { niceTimeFormatter } from '../../../utils/data/formatters'; -import { Domain } from '../../../utils/domain'; +import { OrdinalDomain } from '../../../utils/domain'; import { AxisId, GroupId } from '../../../utils/ids'; import { LIGHT_THEME } from '../../../utils/themes/light_theme'; import { AxisStyle, TextOffset } from '../../../utils/themes/theme'; @@ -203,7 +204,7 @@ describe('Axis computational utils', () => { isBandScale: false, }; - const getSmScales = (smHDomain: Domain = [], smVDomain: Domain = []): SmallMultipleScales => ({ + const getSmScales = (smHDomain: OrdinalDomain = [], smVDomain: OrdinalDomain = []): SmallMultipleScales => ({ horizontal: getScale(smHDomain, chartDim.width), vertical: getScale(smVDomain, chartDim.height), }); @@ -234,7 +235,7 @@ describe('Axis computational utils', () => { [yDomain], 1, bboxCalculator, - 0, + MockGlobalSpec.settings(), axes, (v) => `${v}`, ); @@ -247,7 +248,7 @@ describe('Axis computational utils', () => { [yDomain], 1, bboxCalculator, - 0, + MockGlobalSpec.settings(), axes, (v) => `${v}`, undefined, @@ -268,7 +269,7 @@ describe('Axis computational utils', () => { [yDomain], 1, bboxCalculator, - 0, + MockGlobalSpec.settings(), axes, (v) => `${v}`, ); @@ -291,7 +292,7 @@ describe('Axis computational utils', () => { [yDomain], 1, bboxCalculator, - 0, + MockGlobalSpec.settings(), axes, (v) => `${v}`, ); @@ -308,7 +309,7 @@ describe('Axis computational utils', () => { [yDomain], 1, bboxCalculator, - 0, + MockGlobalSpec.settings(), axes, (v) => `${v}`, ); @@ -325,7 +326,7 @@ describe('Axis computational utils', () => { [yDomain], 1, bboxCalculator, - 0, + MockGlobalSpec.settings(), axes, (v) => `${v}`, ); @@ -352,7 +353,7 @@ describe('Axis computational utils', () => { }); test('should generate a valid scale', () => { - const yScale = getScaleForAxisSpec(verticalAxisSpec, xDomain, [yDomain], 0, 0, 100, 0); + const yScale = getScaleForAxisSpec(verticalAxisSpec, xDomain, [yDomain], 0, true, 100, 0, LogBase.Common); expect(yScale).toBeDefined(); expect(yScale?.bandwidth).toBe(0); expect(yScale?.domain).toEqual([0, 1]); @@ -360,10 +361,10 @@ describe('Axis computational utils', () => { expect(yScale?.ticks()).toEqual([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]); const ungroupedAxisSpec = { ...verticalAxisSpec, groupId: 'foo' }; - const nullYScale = getScaleForAxisSpec(ungroupedAxisSpec, xDomain, [yDomain], 0, 0, 100, 0); + const nullYScale = getScaleForAxisSpec(ungroupedAxisSpec, xDomain, [yDomain], 0, true, 100, 0, LogBase.Common); expect(nullYScale).toBe(null); - const xScale = getScaleForAxisSpec(horizontalAxisSpec, xDomain, [yDomain], 0, 0, 100, 0); + const xScale = getScaleForAxisSpec(horizontalAxisSpec, xDomain, [yDomain], 0, false, 100, 0, LogBase.Common); expect(xScale).toBeDefined(); }); @@ -384,7 +385,7 @@ describe('Axis computational utils', () => { describe('getAvailableTicks', () => { test('should compute to end of domain when histogram mode not enabled', () => { - const scale = getScaleForAxisSpec(verticalAxisSpec, xDomain, [yDomain], 0, 0, 100, 0); + const scale = getScaleForAxisSpec(verticalAxisSpec, xDomain, [yDomain], 0, true, 100, 0, LogBase.Common); const axisPositions = getAvailableTicks(verticalAxisSpec, scale!, 0, false, (v) => `${v}`, 0); const expectedAxisPositions = [ { label: '0', position: 100, value: 0 }, @@ -404,7 +405,7 @@ describe('Axis computational utils', () => { test('should compute positions with rotational offset', () => { const rotationalOffset = 2; - const scale = getScaleForAxisSpec(verticalAxisSpec, xDomain, [yDomain], 0, 0, 100, 0); + const scale = getScaleForAxisSpec(verticalAxisSpec, xDomain, [yDomain], 0, true, 100, 0, LogBase.Common); const axisPositions = getAvailableTicks(verticalAxisSpec, scale!, 0, false, (v) => `${v}`, rotationalOffset); const expectedAxisPositions = [ { label: '0', position: 100 + rotationalOffset, value: 0 }, @@ -431,7 +432,7 @@ describe('Axis computational utils', () => { isBandScale: true, minInterval: 10, }; - const xScale = getScaleForAxisSpec(horizontalAxisSpec, xBandDomain, [yDomain], 1, 0, 100, 0); + const xScale = getScaleForAxisSpec(horizontalAxisSpec, xBandDomain, [yDomain], 1, false, 100, 0, LogBase.Common); const histogramAxisPositions = getAvailableTicks( horizontalAxisSpec, xScale!, @@ -453,7 +454,7 @@ describe('Axis computational utils', () => { isBandScale: true, minInterval: 90000, }; - const xScale = getScaleForAxisSpec(horizontalAxisSpec, xBandDomain, [yDomain], 1, 0, 100, 0); + const xScale = getScaleForAxisSpec(horizontalAxisSpec, xBandDomain, [yDomain], 1, false, 100, 0, LogBase.Common); const histogramAxisPositions = getAvailableTicks( horizontalAxisSpec, xScale!, @@ -492,7 +493,7 @@ describe('Axis computational utils', () => { isBandScale: true, minInterval: 90000, }; - const xScale = getScaleForAxisSpec(horizontalAxisSpec, xBandDomain, [yDomain], 1, 0, 100, 0); + const xScale = getScaleForAxisSpec(horizontalAxisSpec, xBandDomain, [yDomain], 1, false, 100, 0, LogBase.Common); const histogramAxisPositions = getAvailableTicks( horizontalAxisSpec, xScale!, @@ -972,8 +973,6 @@ describe('Axis computational utils', () => { }); test('should compute axis ticks positions with title', () => { - const chartRotation = 0; - // validate assumptions for test expect(verticalAxisSpec.id).toEqual(verticalAxisSpecWTitle.id); @@ -988,7 +987,7 @@ describe('Axis computational utils', () => { leftMargin: 0, }, LIGHT_THEME, - chartRotation, + MockGlobalSpec.settings(), axisSpecs, axisDims, axesStyles, @@ -1020,7 +1019,7 @@ describe('Axis computational utils', () => { leftMargin: 0, }, LIGHT_THEME, - chartRotation, + MockGlobalSpec.settings(), axisSpecs, axisDims, axesStyles, @@ -1203,8 +1202,6 @@ describe('Axis computational utils', () => { }); test('should not compute axis ticks positions if missaligned specs', () => { - const chartRotation = 0; - const axisSpecs = [verticalAxisSpec]; const axisStyles = new Map(); const axisDims = new Map(); @@ -1216,7 +1213,7 @@ describe('Axis computational utils', () => { leftMargin: 0, }, LIGHT_THEME, - chartRotation, + MockGlobalSpec.settings(), axisSpecs, axisDims, axisStyles, @@ -1289,7 +1286,7 @@ describe('Axis computational utils', () => { leftMargin: 0, }, LIGHT_THEME, - 0, + MockGlobalSpec.settings(), invalidSpecs, axisDims, axisStyles, @@ -1680,7 +1677,7 @@ describe('Axis computational utils', () => { leftMargin: 0, }, LIGHT_THEME, - 0, + MockGlobalSpec.settings(), axisSpecs, axisDims, axesStyles, @@ -1710,7 +1707,7 @@ describe('Axis computational utils', () => { leftMargin: 0, }, LIGHT_THEME, - 0, + MockGlobalSpec.settings(), axisSpecs, axisDims, axesStyles, @@ -1746,7 +1743,7 @@ describe('Axis computational utils', () => { leftMargin: 0, }, LIGHT_THEME, - 0, + MockGlobalSpec.settings(), axisSpecs, axisDims, axesStyles, diff --git a/src/chart_types/xy_chart/utils/axis_utils.ts b/src/chart_types/xy_chart/utils/axis_utils.ts index 60607d3ca0..a0a51674a2 100644 --- a/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/src/chart_types/xy_chart/utils/axis_utils.ts @@ -19,6 +19,8 @@ import { Line } from '../../../geoms/types'; import { Scale } from '../../../scales'; +import { LogBase } from '../../../scales/scale_continuous'; +import { SettingsSpec } from '../../../specs'; import { BBox, BBoxCalculator } from '../../../utils/bbox/bbox_calculator'; import { Position, @@ -93,7 +95,7 @@ export function computeAxisTicksDimensions( yDomains: YDomain[], totalBarsInCluster: number, bboxCalculator: BBoxCalculator, - chartRotation: Rotation, + { rotation: chartRotation, scaleLogOptions: { yLogBase, yLogMinLimit, xLogBase, xLogMinLimit } = {} }: SettingsSpec, { gridLine, tickLabel }: AxisStyle, fallBackTickFormatter: TickFormatter, barsPadding?: number, @@ -106,14 +108,17 @@ export function computeAxisTicksDimensions( return null; } + const axisIsYDomain = isYDomain(axisSpec.position, chartRotation); const scale = getScaleForAxisSpec( axisSpec, xDomain, yDomains, totalBarsInCluster, - chartRotation, + axisIsYDomain, 0, 1, + axisIsYDomain ? yLogBase : xLogBase, + axisIsYDomain ? yLogMinLimit : xLogMinLimit, barsPadding, enableHistogramMode, ); @@ -154,20 +159,23 @@ export function getScaleForAxisSpec( xDomain: XDomain, yDomains: YDomain[], totalBarsInCluster: number, - chartRotation: Rotation, + axisIsYDomain: boolean, minRange: number, maxRange: number, + logBase?: LogBase, + logMinLimit?: number, barsPadding?: number, enableHistogramMode?: boolean, ): Scale | null { - const axisIsYDomain = isYDomain(axisSpec.position, chartRotation); - const range: [number, number] = [minRange, maxRange]; + const range: [min: number, max: number] = [minRange, maxRange]; if (axisIsYDomain) { const yScales = computeYScales({ yDomains, range, ticks: axisSpec.ticks, integersOnly: axisSpec.integersOnly, + logBase, + logMinLimit, }); if (yScales.has(axisSpec.groupId)) { return yScales.get(axisSpec.groupId)!; @@ -182,6 +190,8 @@ export function getScaleForAxisSpec( enableHistogramMode, ticks: axisSpec.ticks, integersOnly: axisSpec.integersOnly, + logBase, + logMinLimit, }); } @@ -746,7 +756,7 @@ export function getAxesGeometries( leftMargin: number; }, { chartPaddings, chartMargins, axes: sharedAxesStyle }: Theme, - chartRotation: Rotation, + { rotation: chartRotation, scaleLogOptions: { yLogBase, yLogMinLimit, xLogBase, xLogMinLimit } = {} }: SettingsSpec, axisSpecs: AxisSpec[], axisDimensions: Map, axesStyles: Map, @@ -835,14 +845,18 @@ export function getAxesGeometries( const isVertical = isVerticalAxis(axisSpec.position); const minMaxRanges = getMinMaxRange(axisSpec.position, chartRotation, panel); + const axisIsYDomain = isYDomain(axisSpec.position, chartRotation); + const scale = getScaleForAxisSpec( axisSpec, xDomain, yDomains, totalGroupsCount, - chartRotation, + axisIsYDomain, minMaxRanges.minRange, minMaxRanges.maxRange, + axisIsYDomain ? yLogBase : xLogBase, + axisIsYDomain ? yLogMinLimit : xLogMinLimit, barsPadding, enableHistogramMode, ); diff --git a/src/chart_types/xy_chart/utils/scales.ts b/src/chart_types/xy_chart/utils/scales.ts index bfc92ef7e1..0360d6a0d5 100644 --- a/src/chart_types/xy_chart/utils/scales.ts +++ b/src/chart_types/xy_chart/utils/scales.ts @@ -19,6 +19,8 @@ import { Scale, ScaleBand, ScaleContinuous } from '../../../scales'; import { ScaleType } from '../../../scales/constants'; +import { LogBase } from '../../../scales/scale_continuous'; +import { ContinuousDomain } from '../../../utils/domain'; import { GroupId } from '../../../utils/ids'; import { XDomain, YDomain } from '../domains/types'; @@ -41,11 +43,13 @@ function getBandScaleRange( interface XScaleOptions { xDomain: XDomain; totalBarsInCluster: number; - range: [number, number]; + range: [min: number, max: number]; barsPadding?: number; enableHistogramMode?: boolean; ticks?: number; integersOnly?: boolean; + logBase?: LogBase; + logMinLimit?: number; } /** @@ -56,7 +60,17 @@ interface XScaleOptions { * @internal */ export function computeXScale(options: XScaleOptions): Scale { - const { xDomain, totalBarsInCluster, range, barsPadding, enableHistogramMode, ticks, integersOnly } = options; + const { + xDomain, + totalBarsInCluster, + range, + barsPadding, + enableHistogramMode, + ticks, + integersOnly, + logBase, + logMinLimit, + } = options; const { scaleType, minInterval, domain, isBandScale, timeZone } = xDomain; const rangeDiff = Math.abs(range[1] - range[0]); const isInverse = range[1] < range[0]; @@ -66,7 +80,7 @@ export function computeXScale(options: XScaleOptions): Scale { return new ScaleBand(domain, range, bandwidth, barsPadding); } if (isBandScale) { - const [domainMin, domainMax] = domain; + const [domainMin, domainMax] = domain as ContinuousDomain; const isSingleValueHistogram = !!enableHistogramMode && domainMax - domainMin === 0; const adjustedDomainMax = isSingleValueHistogram ? domainMin + minInterval : domainMax; @@ -91,6 +105,8 @@ export function computeXScale(options: XScaleOptions): Scale { barsPadding, ticks, isSingleValueHistogram, + logBase, + logMinLimit, }, ); @@ -98,16 +114,29 @@ export function computeXScale(options: XScaleOptions): Scale { } return new ScaleContinuous( { type: scaleType, domain, range }, - { bandwidth: 0, minInterval, timeZone, totalBarsInCluster, barsPadding, ticks, integersOnly }, + { + bandwidth: 0, + minInterval, + timeZone, + totalBarsInCluster, + barsPadding, + ticks, + integersOnly, + logBase, + logMinLimit, + }, ); } interface YScaleOptions { yDomains: YDomain[]; - range: [number, number]; + range: [min: number, max: number]; ticks?: number; integersOnly?: boolean; + logBase?: LogBase; + logMinLimit?: number; } + /** * Compute the y scales, one per groupId for the y axis. * @param yDomains the y domains @@ -116,7 +145,7 @@ interface YScaleOptions { */ export function computeYScales(options: YScaleOptions): Map { const yScales: Map = new Map(); - const { yDomains, range, ticks, integersOnly } = options; + const { yDomains, range, ticks, integersOnly, logBase, logMinLimit } = options; yDomains.forEach(({ scaleType: type, domain, groupId }) => { const yScale = new ScaleContinuous( { @@ -127,6 +156,8 @@ export function computeYScales(options: YScaleOptions): Map { { ticks, integersOnly, + logBase, + logMinLimit, }, ); yScales.set(groupId, yScale); diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index 2ff315af93..ff9c19cab1 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -49,15 +49,16 @@ export type BarStyleOverride = RecursivePartial | Color | null; /** @public */ export type PointStyleOverride = RecursivePartial | Color | null; -/** @public */ export const SeriesTypes = Object.freeze({ Area: 'area' as const, Bar: 'bar' as const, Line: 'line' as const, Bubble: 'bubble' as const, }); - -/** @public */ +/** + * XY series types + * @public + */ export type SeriesTypes = $Values; /** @@ -281,7 +282,7 @@ export interface YDomainBase { */ fit?: boolean; /** - * Padding for computed domain. Pixel number or percent string. + * Padding for computed domain. Positive pixel number or percent string. * * Setting `max` or `min` will override this functionality. */ @@ -692,7 +693,6 @@ export const AnnotationTypes = Object.freeze({ Rectangle: 'rectangle' as const, Text: 'text' as const, }); - /** @public */ export type AnnotationType = $Values; diff --git a/src/index.ts b/src/index.ts index f7645bf8c4..fe96b2644d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,6 +27,7 @@ export * from './specs'; export { DebugState } from './state/types'; export { toEntries } from './utils/common'; export { CurveType } from './utils/curves'; +export { ContinuousDomain, OrdinalDomain } from './utils/domain'; export { SimplePadding } from './utils/dimensions'; export { timeFormatter, niceTimeFormatter, niceTimeFormatByDay } from './utils/data/formatters'; export { SeriesIdentifier, SeriesKey } from './common/series_id'; @@ -54,7 +55,7 @@ export { CustomTooltip, TooltipInfo } from './components/tooltip/types'; // scales export { ScaleType } from './scales/constants'; -export { ScaleContinuousType, ScaleOrdinalType, ScaleBandType } from './scales'; +export { ScaleContinuousType, ScaleOrdinalType, ScaleBandType, LogBase } from './scales'; // theme export * from './utils/themes/theme'; diff --git a/src/scales/constants.ts b/src/scales/constants.ts index 38de953140..dc0c77d08c 100644 --- a/src/scales/constants.ts +++ b/src/scales/constants.ts @@ -18,10 +18,6 @@ */ import { $Values } from 'utility-types'; -/** - * The scale type - * @public - */ export const ScaleType = Object.freeze({ Linear: 'linear' as const, Ordinal: 'ordinal' as const, @@ -32,8 +28,10 @@ export const ScaleType = Object.freeze({ Quantile: 'quantile' as const, Threshold: 'threshold' as const, }); - -/** @public */ +/** + * The scale type + * @public + */ export type ScaleType = $Values; /** @internal */ diff --git a/src/scales/index.ts b/src/scales/index.ts index 8664e12064..04d04d47e4 100644 --- a/src/scales/index.ts +++ b/src/scales/index.ts @@ -76,3 +76,5 @@ export { ScaleBand } from './scale_band'; /** @internal */ export { ScaleContinuous } from './scale_continuous'; + +export { LogBase } from './scale_continuous'; diff --git a/src/scales/scale_band.ts b/src/scales/scale_band.ts index 9ae92280cd..8e70cb829a 100644 --- a/src/scales/scale_band.ts +++ b/src/scales/scale_band.ts @@ -22,6 +22,7 @@ import { scaleBand, scaleQuantize, ScaleQuantize, ScaleBand as D3ScaleBand } fro import { Scale, ScaleBandType } from '.'; import { PrimitiveValue } from '../chart_types/partition_chart/layout/utils/group_by_rollup'; import { maxValueWithUpperLimit, stringifyNullsUndefined } from '../utils/common'; +import { Range } from '../utils/domain'; import { ScaleType } from './constants'; /** @@ -59,7 +60,7 @@ export class ScaleBand implements Scale { constructor( domain: any[], - range: [number, number], + range: Range, overrideBandwidth?: number, /** * The proportion of the range that is reserved for blank space between bands diff --git a/src/scales/scale_continuous.test.ts b/src/scales/scale_continuous.test.ts index 63280caafc..84345fe663 100644 --- a/src/scales/scale_continuous.test.ts +++ b/src/scales/scale_continuous.test.ts @@ -22,13 +22,14 @@ import { DateTime, Settings } from 'luxon'; import { ScaleContinuous, ScaleBand } from '.'; import { XDomain } from '../chart_types/xy_chart/domains/types'; import { computeXScale } from '../chart_types/xy_chart/utils/scales'; -import { Domain } from '../utils/domain'; -import { ScaleType } from './constants'; +import { ContinuousDomain, Range } from '../utils/domain'; +import { LOG_MIN_ABS_DOMAIN, ScaleType } from './constants'; +import { limitLogScaleDomain } from './scale_continuous'; import { isLogarithmicScale } from './types'; describe('Scale Continuous', () => { test('shall invert on continuous scale linear', () => { - const domain: Domain = [0, 2]; + const domain: ContinuousDomain = [0, 2]; const minRange = 0; const maxRange = 100; const scale = new ScaleContinuous({ type: ScaleType.Linear, domain, range: [minRange, maxRange] }); @@ -37,7 +38,7 @@ describe('Scale Continuous', () => { expect(scale.invert(100)).toBe(2); }); test('is value within domain', () => { - const domain: Domain = [0, 2]; + const domain: ContinuousDomain = [0, 2]; const minRange = 0; const maxRange = 100; const scale = new ScaleContinuous({ type: ScaleType.Linear, domain, range: [minRange, maxRange] }); @@ -59,8 +60,8 @@ describe('Scale Continuous', () => { expect(scale.invert(100)).toBe(endTime.toMillis()); }); test('check if a scale is log scale', () => { - const domain: Domain = [0, 2]; - const range: [number, number] = [0, 100]; + const domain: ContinuousDomain = [0, 2]; + const range: Range = [0, 100]; const scaleLinear = new ScaleContinuous({ type: ScaleType.Linear, domain, range }); const scaleLog = new ScaleContinuous({ type: ScaleType.Log, domain, range }); const scaleTime = new ScaleContinuous({ type: ScaleType.Time, domain, range }); @@ -73,9 +74,9 @@ describe('Scale Continuous', () => { expect(isLogarithmicScale(scaleBand)).toBe(false); }); test('can get the right x value on linear scale', () => { - const domain: Domain = [0, 2]; + const domain: ContinuousDomain = [0, 2]; const data = [0, 0.5, 0.8, 2]; - const range: [number, number] = [0, 2]; + const range: Range = [0, 2]; const scaleLinear = new ScaleContinuous({ type: ScaleType.Linear, domain, range }); expect(scaleLinear.bandwidth).toBe(0); expect(scaleLinear.invertWithStep(0, data)).toEqual({ value: 0, withinBandwidth: true }); @@ -127,7 +128,7 @@ describe('Scale Continuous', () => { // we tweak the maxRange removing the bandwidth to correctly compute // a band linear scale in computeXScale - const range: [number, number] = [0, 100 - 10]; + const range: Range = [0, 100 - 10]; const scaleLinear = new ScaleContinuous( { type: ScaleType.Linear, domain, range }, { bandwidth: 10, minInterval: 10 }, @@ -435,7 +436,7 @@ describe('Scale Continuous', () => { }); }); describe('ticks as integers or floats', () => { - const domain: Domain = [0, 7]; + const domain: ContinuousDomain = [0, 7]; const minRange = 0; const maxRange = 100; let scale: ScaleContinuous; @@ -476,4 +477,47 @@ describe('Scale Continuous', () => { expect(() => scale.scaleOrThrow()).toThrow(); }); }); + + describe('#limitLogScaleDomain', () => { + const LIMIT = 2; + const ZERO_LIMIT = 0; + + test.each` + domain | logMinLimit | expectedDomain + ${[0, 10]} | ${undefined} | ${[LOG_MIN_ABS_DOMAIN, 10]} + ${[0, 10]} | ${ZERO_LIMIT} | ${[LOG_MIN_ABS_DOMAIN, 10]} + ${[0, -10]} | ${undefined} | ${[-LOG_MIN_ABS_DOMAIN, -10]} + ${[0, -10]} | ${ZERO_LIMIT} | ${[-LOG_MIN_ABS_DOMAIN, -10]} + ${[0, 10]} | ${LIMIT} | ${[LIMIT, 10]} + ${[0, -10]} | ${LIMIT} | ${[-LIMIT, -10]} + ${[10, 0]} | ${undefined} | ${[10, LOG_MIN_ABS_DOMAIN]} + ${[10, 0]} | ${ZERO_LIMIT} | ${[10, LOG_MIN_ABS_DOMAIN]} + ${[-10, 0]} | ${undefined} | ${[-10, -LOG_MIN_ABS_DOMAIN]} + ${[-10, 0]} | ${ZERO_LIMIT} | ${[-10, -LOG_MIN_ABS_DOMAIN]} + ${[10, 0]} | ${LIMIT} | ${[10, LIMIT]} + ${[-10, 0]} | ${LIMIT} | ${[-10, -LIMIT]} + ${[0, 0]} | ${undefined} | ${[LOG_MIN_ABS_DOMAIN, LOG_MIN_ABS_DOMAIN]} + ${[0, 0]} | ${ZERO_LIMIT} | ${[LOG_MIN_ABS_DOMAIN, LOG_MIN_ABS_DOMAIN]} + ${[0, 0]} | ${LIMIT} | ${[LIMIT, LIMIT]} + ${[-10, 10]} | ${undefined} | ${[1, 10]} + ${[-10, 10]} | ${ZERO_LIMIT} | ${[1, 10]} + ${[-10, 10]} | ${LIMIT} | ${[LIMIT, 10]} + ${[10, -10]} | ${undefined} | ${[10, 1]} + ${[10, -10]} | ${ZERO_LIMIT} | ${[10, 1]} + ${[10, -10]} | ${LIMIT} | ${[10, LIMIT]} + ${[10, 100]} | ${undefined} | ${[10, 100]} + ${[10, 100]} | ${ZERO_LIMIT} | ${[10, 100]} + ${[10, 100]} | ${LIMIT} | ${[10, 100]} + ${[LIMIT + 1, 100]} | ${LIMIT} | ${[LIMIT + 1, 100]} + ${[0.1, 100]} | ${LIMIT} | ${[LIMIT, 100]} + ${[0.1, 0.12]} | ${LIMIT} | ${[LIMIT, LIMIT]} + ${[-100, -0.1]} | ${LIMIT} | ${[-100, -LIMIT]} + ${[-0.12, -0.1]} | ${LIMIT} | ${[-LIMIT, -LIMIT]} + `( + 'should limit $domain with limit of $logMinLimit to $expectedDomain', + ({ domain, logMinLimit, expectedDomain }) => { + expect(limitLogScaleDomain(domain, logMinLimit)).toEqual(expectedDomain); + }, + ); + }); }); diff --git a/src/scales/scale_continuous.ts b/src/scales/scale_continuous.ts index e6aaf3b8af..6b85c9596a 100644 --- a/src/scales/scale_continuous.ts +++ b/src/scales/scale_continuous.ts @@ -28,6 +28,7 @@ import { ScalePower, ScaleTime, } from 'd3-scale'; +import { $Values } from 'utility-types'; import { ScaleContinuousType, Scale } from '.'; import { PrimitiveValue } from '../chart_types/partition_chart/layout/utils/group_by_rollup'; @@ -56,52 +57,98 @@ const SCALES = { * As log(0) = -Infinite, a log scale domain must be strictly-positive * or strictly-negative; the domain must not include or cross zero value. * We need to limit the domain scale to the right value on all possible cases. + * * @param domain the domain to limit * @internal */ -export function limitLogScaleDomain(domain: any[]) { - if (domain[0] === 0) { - if (domain[1] > 0) { - return [LOG_MIN_ABS_DOMAIN, domain[1]]; +export function limitLogScaleDomain([min, max]: [min: number, max: number], logMinLimit?: number) { + const absLimit = logMinLimit !== undefined ? Math.abs(logMinLimit) : undefined; + if (absLimit !== undefined && absLimit > 0) { + if (min > 0 && min < absLimit) { + if (max > absLimit) { + return [absLimit, max]; + } + return [absLimit, absLimit]; + } + + if (max < 0 && max > -absLimit) { + if (min < -absLimit) { + return [min, -absLimit]; + } + return [-absLimit, -absLimit]; + } + } + + const fallbackLimit = absLimit || LOG_MIN_ABS_DOMAIN; + + if (min === 0) { + if (max > 0) { + return [fallbackLimit, max]; } - if (domain[1] < 0) { - return [-LOG_MIN_ABS_DOMAIN, domain[1]]; + if (max < 0) { + return [-fallbackLimit, max]; } - return [LOG_MIN_ABS_DOMAIN, LOG_MIN_ABS_DOMAIN]; + return [fallbackLimit, fallbackLimit]; } - if (domain[1] === 0) { - if (domain[0] > 0) { - return [domain[0], LOG_MIN_ABS_DOMAIN]; + if (max === 0) { + if (min > 0) { + return [min, fallbackLimit]; } - if (domain[0] < 0) { - return [domain[0], -LOG_MIN_ABS_DOMAIN]; + if (min < 0) { + return [min, -fallbackLimit]; } - return [LOG_MIN_ABS_DOMAIN, LOG_MIN_ABS_DOMAIN]; + return [fallbackLimit, fallbackLimit]; } - if (domain[0] < 0 && domain[1] > 0) { - const isD0Min = Math.abs(domain[1]) - Math.abs(domain[0]) >= 0; + if (min < 0 && max > 0) { + const isD0Min = Math.abs(max) - Math.abs(min) >= 0; if (isD0Min) { - return [LOG_MIN_ABS_DOMAIN, domain[1]]; + return [fallbackLimit, max]; } - return [domain[0], -LOG_MIN_ABS_DOMAIN]; + return [min, -fallbackLimit]; } - if (domain[0] > 0 && domain[1] < 0) { - const isD0Max = Math.abs(domain[0]) - Math.abs(domain[1]) >= 0; + if (min > 0 && max < 0) { + const isD0Max = Math.abs(min) - Math.abs(max) >= 0; if (isD0Max) { - return [domain[0], LOG_MIN_ABS_DOMAIN]; + return [min, fallbackLimit]; } - return [-LOG_MIN_ABS_DOMAIN, domain[1]]; + return [-fallbackLimit, max]; } - return domain; + return [min, max]; } +export const LogBase = Object.freeze({ + /** + * log base `10` + */ + Common: 'common' as const, + /** + * log base `2` + */ + Binary: 'binary' as const, + /** + * log base `e` (aka ln) + */ + Natural: 'natural' as const, +}); +/** + * Log bases + */ +export type LogBase = $Values; + +/** @internal */ +export const logBaseMap: Record = { + [LogBase.Common]: 10, + [LogBase.Binary]: 2, + [LogBase.Natural]: Math.E, +}; + interface ScaleData { /** The Type of continuous scale */ type: ScaleContinuousType; /** The data input domain */ domain: any[]; /** The data output range */ - range: [number, number]; + range: [min: number, max: number]; } interface ScaleOptions { @@ -138,11 +185,25 @@ interface ScaleOptions { * @defaultValue 10 */ ticks: number; - /** true if the scale was adjusted to fit one single value histogram */ + /** + * true if the scale was adjusted to fit one single value histogram + */ isSingleValueHistogram: boolean; - /** Show only integer values */ + /** + * Show only integer values + */ integersOnly?: boolean; + /** + * Set base for log scales, otherwise ignored + * @defaultValue 10 + */ + logBase: LogBase; + /** + * Set min limit for log scales, otherwise ignored + */ + logMinLimit?: number; } + const defaultScaleOptions: ScaleOptions = { bandwidth: 0, minInterval: 0, @@ -152,6 +213,7 @@ const defaultScaleOptions: ScaleOptions = { ticks: 10, isSingleValueHistogram: false, integersOnly: false, + logBase: LogBase.Common, }; /** @@ -198,12 +260,20 @@ export class ScaleContinuous implements Scale { ticks, isSingleValueHistogram, integersOnly, - } = mergePartial(defaultScaleOptions, options); + logBase, + logMinLimit, + } = mergePartial(defaultScaleOptions, options, { mergeOptionalPartialValues: true }); this.d3Scale = SCALES[type](); - const cleanDomain = type === ScaleType.Log ? limitLogScaleDomain(domain) : domain; - this.domain = cleanDomain; - this.d3Scale.domain(cleanDomain); + + if (type === ScaleType.Log) { + (this.d3Scale as ScaleLogarithmic).base(logBaseMap[logBase]); + this.domain = limitLogScaleDomain(domain as [number, number], logMinLimit); + } else { + this.domain = domain; + } + + this.d3Scale.domain(this.domain); const safeBarPadding = maxValueWithUpperLimit(barsPadding, 0, 1); this.barsPadding = safeBarPadding; @@ -218,6 +288,7 @@ export class ScaleContinuous implements Scale { this.timeZone = timeZone; this.totalBarsInCluster = totalBarsInCluster; this.isSingleValueHistogram = isSingleValueHistogram; + if (type === ScaleType.Time) { const startDomain = getMomentWithTz(this.domain[0], this.timeZone); const endDomain = getMomentWithTz(this.domain[1], this.timeZone); diff --git a/src/specs/settings.tsx b/src/specs/settings.tsx index 8ede52dfd6..3f339ade26 100644 --- a/src/specs/settings.tsx +++ b/src/specs/settings.tsx @@ -29,11 +29,12 @@ import { SeriesIdentifier } from '../common/series_id'; import { TooltipPortalSettings } from '../components'; import { CustomTooltip } from '../components/tooltip/types'; import { ScaleContinuousType, ScaleOrdinalType } from '../scales'; +import { LogBase } from '../scales/scale_continuous'; import { LegendPath } from '../state/actions/legend'; import { getConnect, specComponentFactory } from '../state/spec_factory'; import { Accessor } from '../utils/accessor'; import { Color, Position, Rendering, Rotation } from '../utils/common'; -import { Domain } from '../utils/domain'; +import { ContinuousDomain, OrdinalDomain } from '../utils/domain'; import { GeometryValue } from '../utils/geometry'; import { GroupId } from '../utils/ids'; import { SeriesCompareFn } from '../utils/series_sort'; @@ -320,6 +321,38 @@ export type LegendColorPicker = ComponentType; */ export type MarkBuffer = number | ((radius: number) => number); +export interface ScaleLogOptions { + /** + * Min log value to render y scale + * + * Defaults to min value of domain, or LOG_MIN_ABS_DOMAIN if mixed polarity + */ + yLogMinLimit?: number; + + /** + * Base for log y scales + * + * @defaultValue `common` {@link (LogBase:type) | LogBase.Common} + * (i.e. log base 10) + */ + yLogBase?: LogBase; + + /** + * Min log value to render x scale + * + * Defaults to min value of domain, or LOG_MIN_ABS_DOMAIN if mixed polarity + */ + xLogMinLimit?: number; + + /** + * Base for log x scales + * + * @defaultValue `common` {@link (LogBase:type) | LogBase.Common} + * (i.e. log base 10) + */ + xLogBase?: LogBase; +} + /** * The Spec used for Chart settings * @public @@ -410,7 +443,7 @@ export interface SettingsSpec extends Spec { onLegendItemMinusClick?: LegendItemListener; onPointerUpdate?: PointerUpdateListener; onRenderChange?: RenderChangeListener; - xDomain?: Domain | DomainRange; + xDomain?: ContinuousDomain | OrdinalDomain | DomainRange; resizeDebounce?: number; /** * Render slot to render action for legend @@ -470,6 +503,11 @@ export interface SettingsSpec extends Spec { * Render component for no results UI */ noResults?: ComponentType | ReactChild; + /** + * Options to configure log scales + * TODO: move into scales component to be per scale not per chart + */ + scaleLogOptions?: ScaleLogOptions; } /** diff --git a/src/utils/common.ts b/src/utils/common.ts index 710cfeaed7..3370b7ae9b 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -529,18 +529,18 @@ export const round = (value: number, fractionDigits = 0): number => { */ export const getPercentageValue = (ratio: string | number, relativeValue: number, defaultValue: T): number | T => { if (typeof ratio === 'number') { - return ratio; + return Math.abs(ratio); } const ratioStr = ratio.trim(); if (/\d+%$/.test(ratioStr)) { - const percentage = Number.parseInt(ratioStr.slice(0, -1), 10); + const percentage = Math.abs(Number.parseInt(ratioStr.slice(0, -1), 10)); return relativeValue * (percentage / 100); } const num = Number.parseFloat(ratioStr); - return num && !isNaN(num) ? num : defaultValue; + return num && !isNaN(num) ? Math.abs(num) : defaultValue; }; /** diff --git a/src/utils/domain.test.ts b/src/utils/domain.test.ts index 0f3a3233cf..5171e15b31 100644 --- a/src/utils/domain.test.ts +++ b/src/utils/domain.test.ts @@ -87,7 +87,7 @@ describe('utils/domain', () => { test('should compute continuous data domain: data scaled to extent', () => { const data = [{ x: 12 }, { x: 6 }, { x: 8 }]; const accessor = (datum: any) => datum.x; - const continuousDataDomain = computeContinuousDataDomain(data, accessor, { fit: true }); + const continuousDataDomain = computeContinuousDataDomain(data, accessor, false, { fit: true }); const expectedContinuousDomain = [6, 12]; expect(continuousDataDomain).toEqual(expectedContinuousDomain); @@ -97,7 +97,7 @@ describe('utils/domain', () => { const data = [{ x: 12 }, { x: 6 }, { x: 8 }]; const accessor = (datum: any) => datum.x; - const continuousDataDomain = computeContinuousDataDomain(data, accessor); + const continuousDataDomain = computeContinuousDataDomain(data, accessor, false); const expectedContinuousDomain = [0, 12]; @@ -108,7 +108,7 @@ describe('utils/domain', () => { const data: any[] = []; const accessor = (datum: any) => datum.x; - const continuousDataDomain = computeContinuousDataDomain(data, accessor); + const continuousDataDomain = computeContinuousDataDomain(data, accessor, false); const expectedContinuousDomain = [0, 0]; @@ -117,68 +117,84 @@ describe('utils/domain', () => { describe('YDomainOptions', () => { it('should not effect domain when domain.fit is true', () => { - expect(computeDomainExtent([5, 10], { fit: true })).toEqual([5, 10]); + expect(computeDomainExtent([5, 10], false, { fit: true })).toEqual([5, 10]); + }); + + // Note: padded domains are possible with log scale but not very practical + it('should not effect positive domain if log scale with padding', () => { + expect(computeDomainExtent([0.001, 10], false, { padding: 5 })).toEqual([0, 15]); + }); + + it('should not effect negative domain if log scale with padding', () => { + expect(computeDomainExtent([-10, -0.001], false, { padding: 5 })).toEqual([-15, 0]); }); describe('domain.fit is true', () => { it('should find domain when start & end are positive', () => { - expect(computeDomainExtent([5, 10], { fit: true })).toEqual([5, 10]); + expect(computeDomainExtent([5, 10], false, { fit: true })).toEqual([5, 10]); }); it('should find domain when start & end are negative', () => { - expect(computeDomainExtent([-15, -10], { fit: true })).toEqual([-15, -10]); + expect(computeDomainExtent([-15, -10], false, { fit: true })).toEqual([-15, -10]); }); it('should find domain when start is negative, end is positive', () => { - expect(computeDomainExtent([-15, 10], { fit: true })).toEqual([-15, 10]); + expect(computeDomainExtent([-15, 10], false, { fit: true })).toEqual([-15, 10]); }); }); describe('domain.fit is false', () => { it('should find domain when start & end are positive', () => { - expect(computeDomainExtent([5, 10])).toEqual([0, 10]); + expect(computeDomainExtent([5, 10], false)).toEqual([0, 10]); }); it('should find domain when start & end are negative', () => { - expect(computeDomainExtent([-15, -10])).toEqual([-15, 0]); + expect(computeDomainExtent([-15, -10], false)).toEqual([-15, 0]); }); it('should find domain when start is negative, end is positive', () => { - expect(computeDomainExtent([-15, 10])).toEqual([-15, 10]); + expect(computeDomainExtent([-15, 10], false)).toEqual([-15, 10]); }); }); describe('padding does NOT cause domain to cross zero baseline', () => { it('should get domain from positive domain', () => { - expect(computeDomainExtent([10, 70], { fit: true, padding: 5 })).toEqual([5, 75]); + expect(computeDomainExtent([10, 70], false, { fit: true, padding: 5 })).toEqual([5, 75]); }); it('should get domain from positive & negative domain', () => { - expect(computeDomainExtent([-30, 30], { fit: true, padding: 5 })).toEqual([-35, 35]); + expect(computeDomainExtent([-30, 30], false, { fit: true, padding: 5 })).toEqual([-35, 35]); }); it('should get domain from negative domain', () => { - expect(computeDomainExtent([-70, -10], { fit: true, padding: 5 })).toEqual([-75, -5]); + expect(computeDomainExtent([-70, -10], false, { fit: true, padding: 5 })).toEqual([-75, -5]); + }); + + it('should use absolute padding value', () => { + expect(computeDomainExtent([10, 70], false, { fit: true, padding: -5 })).toEqual([5, 75]); }); }); describe('padding caused domain to cross zero baseline', () => { describe('constrainPadding true - default', () => { it('should set min baseline as 0 if original domain is less than zero', () => { - expect(computeDomainExtent([5, 65], { fit: true, padding: 15 })).toEqual([0, 80]); + expect(computeDomainExtent([5, 65], false, { fit: true, padding: 15 })).toEqual([0, 80]); }); it('should set max baseline as 0 if original domain is less than zero', () => { - expect(computeDomainExtent([-65, -5], { fit: true, padding: 15 })).toEqual([-80, 0]); + expect(computeDomainExtent([-65, -5], false, { fit: true, padding: 15 })).toEqual([-80, 0]); }); }); describe('constrainPadding false', () => { it('should allow min past baseline as 0, even if original domain is less than zero', () => { - expect(computeDomainExtent([5, 65], { fit: true, padding: 15, constrainPadding: false })).toEqual([-10, 80]); + expect(computeDomainExtent([5, 65], false, { fit: true, padding: 15, constrainPadding: false })).toEqual([ + -10, + 80, + ]); }); it('should allow max past baseline as 0, even if original domain is less than zero', () => { - expect(computeDomainExtent([-65, -5], { fit: true, padding: 15, constrainPadding: false })).toEqual([ + expect(computeDomainExtent([-65, -5], false, { fit: true, padding: 15, constrainPadding: false })).toEqual([ -80, 10, ]); diff --git a/src/utils/domain.ts b/src/utils/domain.ts index b67c388d71..6cfcd1c6d3 100644 --- a/src/utils/domain.ts +++ b/src/utils/domain.ts @@ -23,7 +23,9 @@ import { YDomainRange } from '../specs'; import { AccessorFn } from './accessor'; import { getPercentageValue } from './common'; -export type Domain = any[]; +export type OrdinalDomain = (number | string)[]; +export type ContinuousDomain = [min: number, max: number]; +export type Range = [min: number, max: number]; /** @internal */ export function computeOrdinalDataDomain( @@ -49,12 +51,8 @@ function getPaddedRange(start: number, end: number, domainOptions?: YDomainRange let computedPadding = 0; - if (typeof domainOptions.padding === 'string') { - const delta = Math.abs(end - start); - computedPadding = getPercentageValue(domainOptions.padding, delta, 0); - } else { - computedPadding = domainOptions.padding; - } + const delta = Math.abs(end - start); + computedPadding = getPercentageValue(domainOptions.padding, delta, 0); if (computedPadding === 0) { return [start, end]; @@ -73,16 +71,17 @@ function getPaddedRange(start: number, end: number, domainOptions?: YDomainRange /** @internal */ export function computeDomainExtent( [start, end]: [number, number] | [undefined, undefined], + isLogScale: boolean, domainOptions?: YDomainRange, ): [number, number] { if (start != null && end != null) { const [paddedStart, paddedEnd] = getPaddedRange(start, end, domainOptions); if (paddedStart >= 0 && paddedEnd >= 0) { - return domainOptions?.fit ? [paddedStart, paddedEnd] : [0, paddedEnd]; + return domainOptions?.fit || isLogScale ? [paddedStart, paddedEnd] : [0, paddedEnd]; } if (paddedStart < 0 && paddedEnd < 0) { - return domainOptions?.fit ? [paddedStart, paddedEnd] : [paddedStart, 0]; + return domainOptions?.fit || isLogScale ? [paddedStart, paddedEnd] : [paddedStart, 0]; } return [paddedStart, paddedEnd]; @@ -101,13 +100,14 @@ export function computeDomainExtent( export function computeContinuousDataDomain( data: any[], accessor: (n: any) => number, + isLogScale: boolean, domainOptions?: YDomainRange | null, -): number[] { +): ContinuousDomain { const range = extent(data, accessor); if (domainOptions === null) { return [range[0] ?? 0, range[1] ?? 0]; } - return computeDomainExtent(range, domainOptions); + return computeDomainExtent(range, isLogScale, domainOptions); } diff --git a/stories/area/17_negative.tsx b/stories/area/17_negative.tsx index 6c409e8b46..e0090e199a 100644 --- a/stories/area/17_negative.tsx +++ b/stories/area/17_negative.tsx @@ -17,10 +17,10 @@ * under the License. */ -import { select } from '@storybook/addon-knobs'; +import { select, number } from '@storybook/addon-knobs'; import React from 'react'; -import { AreaSeries, Axis, Chart, Position, ScaleType, timeFormatter } from '../../src'; +import { AreaSeries, Axis, Chart, Position, ScaleType, timeFormatter, Settings } from '../../src'; import { KIBANA_METRICS } from '../../src/utils/data_samples/test_dataset_kibana'; import { SB_SOURCE_PANEL } from '../utils/storybook'; @@ -40,6 +40,7 @@ export const Example = () => { ); return ( + Number(d).toFixed(2)} /> diff --git a/stories/area/18_negative_positive.tsx b/stories/area/18_negative_positive.tsx index f5002af184..be7a9d3b6b 100644 --- a/stories/area/18_negative_positive.tsx +++ b/stories/area/18_negative_positive.tsx @@ -17,7 +17,7 @@ * under the License. */ -import { select } from '@storybook/addon-knobs'; +import { select, number } from '@storybook/addon-knobs'; import React from 'react'; import { AreaSeries, Axis, Chart, Position, ScaleType, Settings, timeFormatter } from '../../src'; @@ -39,7 +39,7 @@ export const Example = () => { return ( - + { /* eslint-disable no-console */ console.clear(); - console.log('data domain:', JSON.stringify(computeContinuousDataDomain(data, (d) => d.y))); - console.log('computed domain:', JSON.stringify(computeContinuousDataDomain(data, (d) => d.y, customDomain))); + console.log('data domain:', JSON.stringify(computeContinuousDataDomain(data, (d) => d.y, false))); + console.log('computed domain:', JSON.stringify(computeContinuousDataDomain(data, (d) => d.y, false, customDomain))); /* eslint-enable */ }; diff --git a/stories/scales/7_log_scale_options.tsx b/stories/scales/7_log_scale_options.tsx new file mode 100644 index 0000000000..0e88dd5bf5 --- /dev/null +++ b/stories/scales/7_log_scale_options.tsx @@ -0,0 +1,179 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { boolean, number, select } from '@storybook/addon-knobs'; +import { range } from 'lodash'; +import React from 'react'; + +import { Chart, Axis, LineSeries, Position, ScaleType, Settings, SettingsSpecProps, AreaSeries } from '../../src'; +import { LogBase } from '../../src/scales/scale_continuous'; +import { logBaseMap, logFormatter } from '../utils/formatters'; +import { getKnobsFromEnum } from '../utils/knobs'; +import { SB_SOURCE_PANEL } from '../utils/storybook'; + +type LogKnobs = Pick & { + xDataType: string; + yDataType: string; + xNegative: boolean; + yNegative: boolean; + yPadding: number; +}; + +const getDataType = (group: string, defaultType = 'increasing') => + select( + 'Data type', + { + Increasing: 'increasing', + Decreasing: 'decreasing', + 'Up Down': 'upDown', + 'Down Up': 'downUp', + }, + defaultType, + group, + ); + +const getScaleType = (type: ScaleType, group: string) => + getKnobsFromEnum('Scale Type', ScaleType, type, { group, include: ['linear', 'log'] }) as Extract< + ScaleType, + 'log' | 'linear' + >; + +const getLogKnobs = (): LogKnobs => { + const xGroup = 'X - Axis'; + const yGroup = 'Y - Axis'; + const yUseDefaultLimit = boolean('Use default limit', false, yGroup); + const yLimit = number('Log min limit', 1, { min: 0 }, yGroup); + const xUseDefaultLimit = boolean('Use default limit', true, xGroup); + const xLimit = number('Log min limit', 1, { min: 0 }, xGroup); + return { + xDataType: getDataType(xGroup), + yDataType: getDataType(yGroup, 'upDown'), + xNegative: boolean('Use negative values', false, xGroup), + yNegative: boolean('Use negative values', false, yGroup), + scaleLogOptions: { + yLogMinLimit: yUseDefaultLimit ? undefined : yLimit, + xLogMinLimit: xUseDefaultLimit ? undefined : xLimit, + yLogBase: getKnobsFromEnum('Log base', LogBase, LogBase.Common as LogBase, { + group: yGroup, + allowUndefined: true, + }), + xLogBase: getKnobsFromEnum('Log base', LogBase, LogBase.Common as LogBase, { + group: xGroup, + allowUndefined: true, + }), + }, + yPadding: number('Padding', 0, { min: 0 }, yGroup), + }; +}; + +const getDataValue = (type: string, v: number, i: number, length: number) => { + switch (type) { + case 'increasing': + return i - Math.round(length / 2); + case 'decreasing': + return -i + Math.round(length / 2); + case 'upDown': + return v; + case 'downUp': + default: + return -v; + } +}; + +const seriesMap = { + line: LineSeries, + area: AreaSeries, +}; + +const getSeriesType = () => + select( + 'Series Type', + { + Line: 'line', + Area: 'area', + }, + 'line', + ); + +const getInitalData = (rows: number) => { + const quart = Math.round(rows / 4); + return [...range(quart, -quart, -1), ...range(-quart, quart + 1, 1)]; +}; + +const getData = ( + rows: number, + { scaleLogOptions: { yLogBase, xLogBase } = {}, yDataType, xDataType, yNegative, xNegative }: LogKnobs, +) => + getInitalData(rows).map((v, i, { length }) => { + const y0 = getDataValue(yDataType, v, i, length); + const x0 = getDataValue(xDataType, v, i, length); + return { + y: Math.pow(logBaseMap[yLogBase ?? LogBase.Common], y0) * (yNegative ? -1 : 1), + x: Math.pow(logBaseMap[xLogBase ?? LogBase.Common], x0) * (xNegative ? -1 : 1), + }; + }); + +export const Example = () => { + const rows = number('Rows in dataset', 11, { min: 5, step: 2, max: 21 }); + const logKnobs = getLogKnobs(); + const data = getData(rows, logKnobs); + const type = getSeriesType(); + const Series = seriesMap[type]; + + return ( + + + + + + + ); +}; + +Example.story = { + parameters: { + options: { selectedPanel: SB_SOURCE_PANEL }, + info: { + text: `Log scales will try to best fit the data without setting a baseline to a set value. + If you provide a \`yLogMinLimit\` or \`xLogMinLimit\`, the scale will be limited to that value. + This does _not_ replace the min domain value, such that if all values are greater than this limit, + the domain min will be determined by the dataset.\n\nThe \`yLogBase\` and \`xLogBase\` + provides a way to set the base of the log to one of following: + [\`Common\`](https://en.wikipedia.org/wiki/Common_logarithm) (base 10), + [\`Binary\`](https://en.wikipedia.org/wiki/Binary_logarithm) (base 2), + [\`Natural\`](https://en.wikipedia.org/wiki/Natural_logarithm) (base e), the default is \`Common\``, + }, + }, +}; diff --git a/stories/scales/scales.stories.tsx b/stories/scales/scales.stories.tsx index 5a6fe64b56..5beae5f94c 100644 --- a/stories/scales/scales.stories.tsx +++ b/stories/scales/scales.stories.tsx @@ -32,3 +32,4 @@ export { Example as tooltipInUTC } from './3_utc_tooltip'; export { Example as specifiedTimezone } from './4_specified_timezone'; export { Example as removeDuplicateAxis } from './5_remove_duplicates'; export { Example as xScaleFallback } from './6_x_scale_fallback'; +export { Example as logScaleOptions } from './7_log_scale_options'; diff --git a/stories/utils/formatters.ts b/stories/utils/formatters.ts new file mode 100644 index 0000000000..26809a0da5 --- /dev/null +++ b/stories/utils/formatters.ts @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import numeral from 'numeral'; + +import { LogBase } from '../../src/scales/scale_continuous'; + +const superStringMap: Record = { + 0: '⁰', + 1: '¹', + 2: '²', + 3: '³', + 4: '⁴', + 5: '⁵', + 6: '⁶', + 7: '⁷', + 8: '⁸', + 9: '⁹', +}; + +export const getSuperScriptNumber = (n: number) => `${n > 0 ? '' : '⁻'}${superStringMap[Math.abs(n)]}`; + +export const logBaseMap = { + [LogBase.Common]: 10, + [LogBase.Binary]: 2, + [LogBase.Natural]: Math.E, +}; + +export const logFormatter = (base: LogBase = LogBase.Common) => (n: number) => { + const exp = Math.log(n) / Math.log(logBaseMap[base]) + Number.EPSILON; + const roundedExp = Math.floor(exp); + const constant = numeral(n / Math.pow(logBaseMap[base], roundedExp)).format('0[.]00'); + const baseLabel = base === LogBase.Natural ? 'e' : logBaseMap[base]; + return `${constant} x ${baseLabel}${getSuperScriptNumber(roundedExp)}`; +}; diff --git a/stories/utils/knobs.ts b/stories/utils/knobs.ts index 3f83a277da..c2adbdc127 100644 --- a/stories/utils/knobs.ts +++ b/stories/utils/knobs.ts @@ -19,6 +19,7 @@ import { PopoverAnchorPosition } from '@elastic/eui'; import { select, array, number, optionsKnob } from '@storybook/addon-knobs'; +import { SelectTypeKnobValue } from '@storybook/addon-knobs/dist/components/types'; import { Rotation, Position, Placement, TooltipProps } from '../../src'; import { TooltipType } from '../../src/specs/constants'; @@ -63,6 +64,41 @@ export const getTooltipTypeKnob = ( groupId, ); +/** + * Generates storybook knobs from const enum + * + * TODO: cleanup types to infer T + */ +export const getKnobsFromEnum = >( + name: string, + options: O, + defaultValue: T, + { + group, + allowUndefined, + include, + exclude, + }: { + group?: string; + allowUndefined?: boolean; + include?: Array; + exclude?: Array; + } = {}, +): T | undefined => + select( + name, + (Object.entries(options) as [keyof O, T][]) + .filter(([, v]) => !include || include.includes(v)) + .filter(([, v]) => !exclude || !exclude.includes(v)) + .reduce((acc, [key, value]) => { + // @ts-ignore + acc[key] = value; + return acc; + }, (allowUndefined ? { Undefined: undefined } : ({} as unknown)) as O), + defaultValue, + group, + ) || undefined; + export const getPositionKnob = (name = 'chartRotation', defaultValue = Position.Right) => select( name, diff --git a/yarn.lock b/yarn.lock index 82e64fcc27..85db2c216f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4151,59 +4151,48 @@ resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.6.tgz#9c70eb7e7e4abc1083c8edf7151d35a19e442c00" integrity sha512-PKTHVgMHnK5p+kcMWWNnZuoR7O19VmHiOujmVcyN50hya7qIdDb5vvsYC+dwLxApEXiABhLozq0dlIwFeS3yjg== -"@microsoft/api-documenter@^7.7.20": - version "7.7.20" - resolved "https://registry.yarnpkg.com/@microsoft/api-documenter/-/api-documenter-7.7.20.tgz#034fe4df720d62ebcc6af311168fdb0faf89da9c" - integrity sha512-B2gOOoYJDOqAn9iPg4Z9ay03MYtjmY2ldF3u1povTkzl5eJWERMDuuK5BLOdGdDTMr6TUd/FrhERFEeJNeQ3rg== - dependencies: - "@microsoft/api-extractor-model" "7.7.11" - "@microsoft/tsdoc" "0.12.19" - "@rushstack/node-core-library" "3.19.7" - "@rushstack/ts-command-line" "4.3.14" +"@microsoft/api-documenter@^7.12.7": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@microsoft/api-documenter/-/api-documenter-7.12.7.tgz#7b1742999bc083a237f78e658b3351e92012b3f7" + integrity sha512-GD3Z52yzDz8ZN3efB0mL2ogXLoL/I/cpoZmwLm8KiXL6o5g9K25fQ7HrFt4fvk9XTvcYX/EvZw4TBjrWz7cj5A== + dependencies: + "@microsoft/api-extractor-model" "7.12.2" + "@microsoft/tsdoc" "0.12.24" + "@rushstack/node-core-library" "3.36.0" + "@rushstack/ts-command-line" "4.7.8" colors "~1.2.1" js-yaml "~3.13.1" - resolve "1.8.1" - -"@microsoft/api-extractor-model@7.7.11": - version "7.7.11" - resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.7.11.tgz#9dfc5425f4a6a2b6b1ebc39332ae8101ab8da36a" - integrity sha512-Kf3RytYDq7sP4ASaaA9IcvaOPbVj1Xj34E2Wxd9DznI7sG4HzcpoOGmxaZHCzyYVh7wfAaAlvcXf3SV+djhNZw== - dependencies: - "@microsoft/tsdoc" "0.12.19" - "@rushstack/node-core-library" "3.19.7" - -"@microsoft/api-extractor-model@7.7.8": - version "7.7.8" - resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.7.8.tgz#fe2bb3a9ec90c1181ad0447fcf5ed912c54a7f5c" - integrity sha512-OWiIC3+Rnv6WzmwuOZkbpHGw9kSKTlzFZBrBwDVEkXp0SP1LsWOy7BALIR1RdTmaM9tRMzKZsjRWz4MWqXJdCQ== - dependencies: - "@microsoft/tsdoc" "0.12.14" - "@rushstack/node-core-library" "3.19.4" - -"@microsoft/api-extractor@^7.7.9": - version "7.7.9" - resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.7.9.tgz#c8a37a053af01cea66f1950f79a3d0df5558477b" - integrity sha512-sXobUDKsKx2apisLFhk5gxBPBfnCbM31hpmQwqHAbwZ7ak4Sj7I+OcN41hSwbIQksZnk2OSbu+WElEehHiS+xA== - dependencies: - "@microsoft/api-extractor-model" "7.7.8" - "@microsoft/tsdoc" "0.12.14" - "@rushstack/node-core-library" "3.19.4" - "@rushstack/ts-command-line" "4.3.11" + resolve "~1.17.0" + +"@microsoft/api-extractor-model@7.12.2": + version "7.12.2" + resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.12.2.tgz#d48b35e8ed20643b1c19d7a4f80c90c42dc7d1d8" + integrity sha512-EU+U09Mj65zUH0qwPF4PFJiL6Y+PQQE/RRGEHEDGJJzab/mRQDpKOyrzSdb00xvcd/URehIHJqC55cY2Y4jGOA== + dependencies: + "@microsoft/tsdoc" "0.12.24" + "@rushstack/node-core-library" "3.36.0" + +"@microsoft/api-extractor@^7.13.1": + version "7.13.1" + resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.13.1.tgz#0ed26ac18ccda075553e9fbe26dce4104d8d042f" + integrity sha512-mnWb5Vuyn/JnjC359HfsRmLDM4/vzyKcJuxQr5Cg/apbkMHuTB1hQrqA8zBda4N+MJZ5ET2BPsrKaUX1qhz8jw== + dependencies: + "@microsoft/api-extractor-model" "7.12.2" + "@microsoft/tsdoc" "0.12.24" + "@rushstack/node-core-library" "3.36.0" + "@rushstack/rig-package" "0.2.9" + "@rushstack/ts-command-line" "4.7.8" colors "~1.2.1" lodash "~4.17.15" - resolve "1.8.1" + resolve "~1.17.0" + semver "~7.3.0" source-map "~0.6.1" - typescript "~3.7.2" - -"@microsoft/tsdoc@0.12.14": - version "0.12.14" - resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.12.14.tgz#0e0810a0a174e50e22dfe8edb30599840712f22d" - integrity sha512-518yewjSga1jLdiLrcmpMFlaba5P+50b0TWNFUpC+SL9Yzf0kMi57qw+bMl+rQ08cGqH1vLx4eg9YFUbZXgZ0Q== + typescript "~4.1.3" -"@microsoft/tsdoc@0.12.19": - version "0.12.19" - resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.12.19.tgz#2173ccb92469aaf62031fa9499d21b16d07f9b57" - integrity sha512-IpgPxHrNxZiMNUSXqR1l/gePKPkfAmIKoDRP9hp7OwjU29ZR8WCJsOJ8iBKgw0Qk+pFwR+8Y1cy8ImLY6e9m4A== +"@microsoft/tsdoc@0.12.24": + version "0.12.24" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz#30728e34ebc90351dd3aff4e18d038eed2c3e098" + integrity sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg== "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" @@ -4489,49 +4478,39 @@ prop-types "^15.6.1" react-lifecycles-compat "^3.0.4" -"@rushstack/node-core-library@3.19.4": - version "3.19.4" - resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-3.19.4.tgz#1ad4d18f00a746beaa2e3403cf805a9518d54149" - integrity sha512-xH/eXMZycx9t69lHXZifnzuXADOqLp23lOkP6K4avgvdPoZcVIQHeL80/jdzARp5cm+HaHsNxNycqkvH7b7mxQ== +"@rushstack/node-core-library@3.36.0": + version "3.36.0" + resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-3.36.0.tgz#95dace39d763c8695d6607c421f95c6ac65b0ed4" + integrity sha512-bID2vzXpg8zweXdXgQkKToEdZwVrVCN9vE9viTRk58gqzYaTlz4fMId6V3ZfpXN6H0d319uGi2KDlm+lUEeqCg== dependencies: "@types/node" "10.17.13" colors "~1.2.1" fs-extra "~7.0.1" + import-lazy "~4.0.0" jju "~1.4.0" - semver "~5.3.0" + resolve "~1.17.0" + semver "~7.3.0" timsort "~0.3.0" z-schema "~3.18.3" -"@rushstack/node-core-library@3.19.7": - version "3.19.7" - resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-3.19.7.tgz#8d8a193fd6f99536c92dd797ab50fd5fcb7630ea" - integrity sha512-gKE/OXH5GAj8yJ1kEyRW68UekJernilZ3QTRgmQ0MUHBCQmtZ9Q6T5PQ1sVbcL4teH8BMdpZeFy1DKnHs8h3PA== +"@rushstack/rig-package@0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@rushstack/rig-package/-/rig-package-0.2.9.tgz#57ef94e7f7703b18e275b603d3f59a1a16580716" + integrity sha512-4tqsZ/m+BjeNAGeAJYzPF53CT96TsAYeZ3Pq3T4tb1pGGM3d3TWfkmALZdKNhpRlAeShKUrb/o/f/0sAuK/1VQ== dependencies: "@types/node" "10.17.13" - colors "~1.2.1" - fs-extra "~7.0.1" - jju "~1.4.0" - semver "~5.3.0" - timsort "~0.3.0" - z-schema "~3.18.3" - -"@rushstack/ts-command-line@4.3.11": - version "4.3.11" - resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.3.11.tgz#79e4ee20c42ad849e0d257790bcb3f2865c7f15a" - integrity sha512-Jzu52EzzHmIuc4dCrK+jLKwFCrrCtVBPCxeMFtHlODXkZ61IlVW+a+rRATkNNlSykv3G0dmedOFxQsVpVgoUpA== - dependencies: - "@types/argparse" "1.0.33" - argparse "~1.0.9" - colors "~1.2.1" + resolve "~1.17.0" + strip-json-comments "~3.1.1" -"@rushstack/ts-command-line@4.3.14": - version "4.3.14" - resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.3.14.tgz#5d7a437d4e9c564ff1b8e876215fca96c74858a1" - integrity sha512-YJZIyKvkm3f6ZdKSfMntHS9542Y2mmMWzaiPPoXxLFZntKxEIDE3WfUNlvOSo3yK4fNd09Tz3hfvTivQNHSiKQ== +"@rushstack/ts-command-line@4.7.8": + version "4.7.8" + resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.7.8.tgz#3aa77cf544c571be3206fc2bcba20c7a096ed254" + integrity sha512-8ghIWhkph7NnLCMDJtthpsb7TMOsVGXVDvmxjE/CeklTqjbbUFBjGXizJfpbEkRQTELuZQ2+vGn7sGwIWKN2uA== dependencies: - "@types/argparse" "1.0.33" + "@types/argparse" "1.0.38" argparse "~1.0.9" colors "~1.2.1" + string-argv "~0.3.1" "@semantic-release/changelog@^5.0.1": version "5.0.1" @@ -5593,10 +5572,10 @@ multimatch "^4.0.0" typescript "~3.9.7" -"@types/argparse@1.0.33": - version "1.0.33" - resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.33.tgz#2728669427cdd74a99e53c9f457ca2866a37c52d" - integrity sha512-VQgHxyPMTj3hIlq9SY1mctqx+Jj8kpQfoLvDlVSDNOyuYs8JYfkuY3OW/4+dO657yPmNhHpePRx0/Tje5ImNVQ== +"@types/argparse@1.0.38": + version "1.0.38" + resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.38.tgz#a81fd8606d481f873a3800c6ebae4f1d768a56a9" + integrity sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA== "@types/babel__core@^7.0.0": version "7.1.12" @@ -13399,6 +13378,11 @@ import-lazy@^2.1.0: resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= +import-lazy@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" + integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== + import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" @@ -18142,7 +18126,7 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.5, path-parse@^1.0.6: +path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== @@ -20311,13 +20295,6 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" - integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== - dependencies: - path-parse "^1.0.5" - resolve@^1.1.6, resolve@^1.10.0, resolve@^1.18.1: version "1.19.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" @@ -20333,7 +20310,7 @@ resolve@^1.11.0, resolve@^1.3.2, resolve@^1.8.1: dependencies: path-parse "^1.0.6" -resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0: +resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@~1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== @@ -20745,7 +20722,7 @@ semver@7.3.2, semver@^7.2.1: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== -semver@7.x, semver@^7.1.2, semver@^7.3.2, semver@^7.3.4: +semver@7.x, semver@^7.1.2, semver@^7.3.2, semver@^7.3.4, semver@~7.3.0: version "7.3.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== @@ -21483,7 +21460,7 @@ strict-uri-encode@^2.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= -string-argv@0.3.1: +string-argv@0.3.1, string-argv@~0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== @@ -21755,7 +21732,7 @@ strip-json-comments@^3.1.0: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== -strip-json-comments@^3.1.1: +strip-json-comments@^3.1.1, strip-json-comments@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -22541,16 +22518,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^4.1.3: +typescript@^4.1.3, typescript@~4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== -typescript@~3.7.2: - version "3.7.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" - integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== - typescript@~3.9.7: version "3.9.7" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"