diff --git a/src/lib/axes/axis_utils.ts b/src/lib/axes/axis_utils.ts index 6b104ee7a7..c19455cba4 100644 --- a/src/lib/axes/axis_utils.ts +++ b/src/lib/axes/axis_utils.ts @@ -61,12 +61,23 @@ export function computeAxisTicksDimensions( chartRotation: Rotation, axisConfig: AxisConfig, barsPadding?: number, + enableHistogramMode?: boolean, ): AxisTicksDimensions | null { if (axisSpec.hide) { return null; } - const scale = getScaleForAxisSpec(axisSpec, xDomain, yDomain, totalBarsInCluster, chartRotation, 0, 1, barsPadding); + const scale = getScaleForAxisSpec( + axisSpec, + xDomain, + yDomain, + totalBarsInCluster, + chartRotation, + 0, + 1, + barsPadding, + enableHistogramMode, + ); if (!scale) { throw new Error(`Cannot compute scale for axis spec ${axisSpec.id}`); } @@ -112,6 +123,7 @@ export function getScaleForAxisSpec( minRange: number, maxRange: number, barsPadding?: number, + enableHistogramMode?: boolean, ): Scale | null { const axisIsYDomain = isYDomain(axisSpec.position, chartRotation); @@ -122,7 +134,7 @@ export function getScaleForAxisSpec( } return null; } else { - return computeXScale(xDomain, totalBarsInCluster, minRange, maxRange, barsPadding); + return computeXScale(xDomain, totalBarsInCluster, minRange, maxRange, barsPadding, enableHistogramMode); } } @@ -580,6 +592,7 @@ export function getAxisTicksPositions( minMaxRanges.minRange, minMaxRanges.maxRange, barsPadding, + enableHistogramMode, ); if (!scale) { diff --git a/src/lib/series/scales.test.ts b/src/lib/series/scales.test.ts index 1f83ca5ca5..a89c1e8f7e 100644 --- a/src/lib/series/scales.test.ts +++ b/src/lib/series/scales.test.ts @@ -41,6 +41,44 @@ describe('Series scales', () => { expect(scale.scale(3)).toBe(expectedBandwidth * 0); }); + describe('computeXScale with single value domain', () => { + const maxRange = 120; + const singleDomainValue = 3; + const minInterval = 1; + + test('should return extended domain & range when in histogram mode', () => { + const xDomainSingleValue: XDomain = { + type: 'xDomain', + isBandScale: true, + domain: [singleDomainValue, singleDomainValue], + minInterval: minInterval, + scaleType: ScaleType.Linear, + }; + const enableHistogramMode = true; + + const scale = computeXScale(xDomainSingleValue, 1, 0, maxRange, 0, enableHistogramMode); + expect(scale.bandwidth).toBe(maxRange); + expect(scale.domain).toEqual([singleDomainValue, singleDomainValue + minInterval]); + expect(scale.range).toEqual([0, maxRange]); + }); + + test('should return unextended domain & range when not in histogram mode', () => { + const xDomainSingleValue: XDomain = { + type: 'xDomain', + isBandScale: true, + domain: [singleDomainValue, singleDomainValue], + minInterval: minInterval, + scaleType: ScaleType.Linear, + }; + const enableHistogramMode = false; + + const scale = computeXScale(xDomainSingleValue, 1, 0, maxRange, 0, enableHistogramMode); + expect(scale.bandwidth).toBe(maxRange); + expect(scale.domain).toEqual([singleDomainValue, singleDomainValue]); + expect(scale.range).toEqual([0, 0]); + }); + }); + test('should compute X Scale ordinal', () => { const nonZeroGroupScale = computeXScale(xDomainOrdinal, 1, 120, 0); const expectedBandwidth = 60; diff --git a/src/lib/series/scales.ts b/src/lib/series/scales.ts index 6b8ab4f8fc..61feca242a 100644 --- a/src/lib/series/scales.ts +++ b/src/lib/series/scales.ts @@ -38,6 +38,23 @@ export function countBarsInCluster( }; } +function getBandScaleRange( + isInverse: boolean, + isSingleValueHistogram: boolean, + minRange: number, + maxRange: number, + bandwidth: number, +): { + start: number; + end: number; +} { + const rangeEndOffset = isSingleValueHistogram ? 0 : bandwidth; + const start = isInverse ? minRange - rangeEndOffset : minRange; + const end = isInverse ? maxRange : maxRange - rangeEndOffset; + + return { start, end }; +} + /** * Compute the x scale used to align geometries to the x axis. * @param xDomain the x domain @@ -50,6 +67,7 @@ export function computeXScale( minRange: number, maxRange: number, barsPadding?: number, + enableHistogramMode?: boolean, ): Scale { const { scaleType, minInterval, domain, isBandScale, timeZone } = xDomain; const rangeDiff = Math.abs(maxRange - minRange); @@ -60,13 +78,21 @@ export function computeXScale( return new ScaleBand(domain, [minRange, maxRange], bandwidth, barsPadding); } else { if (isBandScale) { - const intervalCount = (domain[1] - domain[0]) / minInterval; - const bandwidth = rangeDiff / (intervalCount + 1); - const start = isInverse ? minRange - bandwidth : minRange; - const end = isInverse ? maxRange : maxRange - bandwidth; - return new ScaleContinuous( + const [domainMin, domainMax] = domain; + const isSingleValueHistogram = !!enableHistogramMode && domainMax - domainMin === 0; + + const adjustedDomainMax = isSingleValueHistogram ? domainMin + minInterval : domainMax; + const adjustedDomain = [domainMin, adjustedDomainMax]; + + const intervalCount = (adjustedDomain[1] - adjustedDomain[0]) / minInterval; + const intervalCountOffest = isSingleValueHistogram ? 0 : 1; + const bandwidth = rangeDiff / (intervalCount + intervalCountOffest); + + const { start, end } = getBandScaleRange(isInverse, isSingleValueHistogram, minRange, maxRange, bandwidth); + + const scale = new ScaleContinuous( scaleType, - domain, + adjustedDomain, [start, end], bandwidth / totalBarsInCluster, minInterval, @@ -74,6 +100,8 @@ export function computeXScale( totalBarsInCluster, barsPadding, ); + + return scale; } else { return new ScaleContinuous( scaleType, diff --git a/src/state/chart_state.ts b/src/state/chart_state.ts index 55d2d73e86..acd111d8a1 100644 --- a/src/state/chart_state.ts +++ b/src/state/chart_state.ts @@ -836,6 +836,7 @@ export class ChartStore { this.chartRotation, this.chartTheme.axes, barsPadding, + this.enableHistogramMode.get(), ); if (dimensions) { this.axesTicksDimensions.set(id, dimensions); diff --git a/src/state/utils.ts b/src/state/utils.ts index b296d4b9e5..155bff5ef2 100644 --- a/src/state/utils.ts +++ b/src/state/utils.ts @@ -193,7 +193,7 @@ export function computeSeriesGeometries( const { stackedBarsInCluster, totalBarsInCluster } = countBarsInCluster(stacked, nonStacked); // compute scales - const xScale = computeXScale(xDomain, totalBarsInCluster, 0, width, barsPadding); + const xScale = computeXScale(xDomain, totalBarsInCluster, 0, width, barsPadding, enableHistogramMode); const yScales = computeYScales(yDomain, height, 0); // compute colors diff --git a/stories/annotations.tsx b/stories/annotations.tsx index 51ffe0e6cd..0156310769 100644 --- a/stories/annotations.tsx +++ b/stories/annotations.tsx @@ -547,4 +547,64 @@ storiesOf('Annotations', module) /> ); + }) + .add('[test] line annotation single value histogram', () => { + const dataValues = [ + { + dataValue: 3.5, + }, + ]; + + const style = { + line: { + strokeWidth: 3, + stroke: '#f00', + opacity: 1, + }, + details: { + fontSize: 12, + fontFamily: 'Arial', + fontStyle: 'bold', + fill: 'gray', + padding: 0, + }, + }; + + const chartRotation = select( + 'chartRotation', + { + '0 deg': 0, + '90 deg': 90, + '-90 deg': -90, + '180 deg': 180, + }, + 0, + ); + + const xDomain = { + minInterval: 1, + }; + + return ( + + + + + + + + ); });