From 15c797e6c8fdf8e21d547a730a9f86bf58fcad82 Mon Sep 17 00:00:00 2001 From: "Irina V. Kuzmina" Date: Mon, 18 Sep 2023 18:51:43 +0300 Subject: [PATCH 1/5] feat(D3 plugin): auto rotate overlapping labels --- src/plugins/d3/renderer/components/AxisX.tsx | 111 +++++++-------- src/plugins/d3/renderer/components/AxisY.tsx | 7 +- src/plugins/d3/renderer/components/Chart.tsx | 3 +- .../d3/renderer/constants/defaults/axis.ts | 5 + .../d3/renderer/constants/defaults/index.ts | 1 + src/plugins/d3/renderer/constants/index.ts | 1 - .../hooks/useChartDimensions/index.ts | 65 --------- .../hooks/useChartDimensions/index.tsx | 118 ++++++++++++++++ .../renderer/hooks/useChartOptions/chart.ts | 2 +- .../renderer/hooks/useChartOptions/types.ts | 2 +- .../renderer/hooks/useChartOptions/x-axis.ts | 5 +- .../renderer/hooks/useChartOptions/y-axis.ts | 19 +-- .../renderer/utils/axis-generators/bottom.ts | 126 ++++++++++++++++++ .../renderer/utils/axis-generators/index.ts | 1 + src/plugins/d3/renderer/utils/axis.ts | 64 +++++++++ src/plugins/d3/renderer/utils/text.ts | 92 +++++++++++++ src/types/widget-data/axis.ts | 18 ++- 17 files changed, 494 insertions(+), 146 deletions(-) create mode 100644 src/plugins/d3/renderer/constants/defaults/axis.ts delete mode 100644 src/plugins/d3/renderer/hooks/useChartDimensions/index.ts create mode 100644 src/plugins/d3/renderer/hooks/useChartDimensions/index.tsx create mode 100644 src/plugins/d3/renderer/utils/axis-generators/bottom.ts create mode 100644 src/plugins/d3/renderer/utils/axis-generators/index.ts diff --git a/src/plugins/d3/renderer/components/AxisX.tsx b/src/plugins/d3/renderer/components/AxisX.tsx index 6f3d67fd..f8f2a8e3 100644 --- a/src/plugins/d3/renderer/components/AxisX.tsx +++ b/src/plugins/d3/renderer/components/AxisX.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {axisBottom, ScaleLinear, select} from 'd3'; +import {select} from 'd3'; import type {AxisScale, AxisDomain} from 'd3'; import {block} from '../../../../utils/cn'; @@ -8,13 +8,13 @@ import type {ChartScale, PreparedAxis} from '../hooks'; import { formatAxisTickLabel, getClosestPointsRange, - parseTransformStyle, setEllipsisForOverflowText, getTicksCount, + getScaleTicks, } from '../utils'; +import {axisBottom} from '../utils/axis-generators'; const b = block('d3-axis'); -const EMPTY_SPACE_BETWEEN_LABELS = 10; type Props = { axis: PreparedAxis; @@ -24,8 +24,24 @@ type Props = { chartWidth: number; }; -// FIXME: add overflow ellipsis for the labels that out of boundaries -export const AxisX = ({axis, width, height, scale, chartWidth}: Props) => { +function getLabelFormatter({axis, scale}: {axis: PreparedAxis; scale: ChartScale}) { + const ticks = getScaleTicks(scale as AxisScale); + const tickStep = getClosestPointsRange(axis, ticks); + + return (value: any) => { + if (!axis.labels.enabled) { + return ''; + } + + return formatAxisTickLabel({ + axis, + value, + step: tickStep, + }); + }; +} + +export const AxisX = React.memo(({axis, width, height, scale, chartWidth}: Props) => { const ref = React.useRef(null); React.useEffect(() => { @@ -35,71 +51,44 @@ export const AxisX = ({axis, width, height, scale, chartWidth}: Props) => { const svgElement = select(ref.current); svgElement.selectAll('*').remove(); - const tickSize = axis.grid.enabled ? height * -1 : 0; - const ticks = - axis.type === 'category' ? [] : (scale as ScaleLinear).ticks(); - const tickStep = getClosestPointsRange(axis, ticks); - let xAxisGenerator = axisBottom(scale as AxisScale) - .tickSize(tickSize) - .tickPadding(axis.labels.padding) - .tickFormat((value) => { - if (!axis.labels.enabled) { - return ''; - } - - return formatAxisTickLabel({ - axis, - value, - step: tickStep, - }); - }); - - const ticksCount = getTicksCount({axis, range: width}); - if (ticksCount) { - xAxisGenerator = xAxisGenerator.ticks(ticksCount); - } + + const minTickWidth = + (width - axis.labels.padding) / + (parseInt(axis.labels.style.fontSize) + axis.labels.padding); + const maxTickCount = Math.floor(width / minTickWidth); + + const xAxisGenerator = axisBottom({ + scale: scale as AxisScale, + ticks: { + size: axis.grid.enabled ? height * -1 : 0, + labelFormat: getLabelFormatter({axis, scale}), + labelsPaddings: axis.labels.padding, + labelsDistance: axis.labels.distance, + count: getTicksCount({axis, range: width}), + maxTickCount: maxTickCount, + autoRotation: axis.labels.autoRotation, + }, + domain: { + size: width, + color: axis.lineColor, + }, + }); svgElement.call(xAxisGenerator).attr('class', b()); - svgElement - .select('.domain') - .attr('d', `M0,0V0H${width}`) - .style('stroke', axis.lineColor || ''); if (axis.labels.enabled) { - svgElement.selectAll('.tick text').style('font-size', axis.labels.style.fontSize); - } - - const transformStyle = svgElement.select('.tick').attr('transform'); - const {x} = parseTransformStyle(transformStyle); - - if (x === 0) { - // Remove tick that has the same x coordinate like domain - svgElement.select('.tick').remove(); + svgElement.style('font-size', axis.labels.style.fontSize); } - // remove overlapping labels - let elementX = 0; - svgElement - .selectAll('.tick') - .filter(function () { - const node = this as unknown as Element; - const r = node.getBoundingClientRect(); - - if (r.left < elementX) { - return true; - } - elementX = r.right + EMPTY_SPACE_BETWEEN_LABELS; - return false; - }) - .remove(); - // add an ellipsis to the labels on the right that go beyond the boundaries of the chart svgElement.selectAll('.tick text').each(function () { const node = this as unknown as SVGTextElement; - const textRect = node.getBoundingClientRect(); + const textRect = node.getBBox(); + const matrix = node.transform.baseVal.consolidate()?.matrix || ({} as SVGMatrix); + const right = matrix.a * textRect.right + matrix.c * textRect.bottom + matrix.e; - if (textRect.right > chartWidth) { - const maxWidth = textRect.width - (textRect.right - chartWidth) * 2; + if (right > chartWidth) { + const maxWidth = textRect.width - (right - chartWidth) * 2; select(node).call(setEllipsisForOverflowText, maxWidth); } }); @@ -122,4 +111,4 @@ export const AxisX = ({axis, width, height, scale, chartWidth}: Props) => { }, [axis, width, height, scale]); return ; -}; +}); diff --git a/src/plugins/d3/renderer/components/AxisY.tsx b/src/plugins/d3/renderer/components/AxisY.tsx index c7662584..c1154089 100644 --- a/src/plugins/d3/renderer/components/AxisY.tsx +++ b/src/plugins/d3/renderer/components/AxisY.tsx @@ -15,7 +15,6 @@ import { } from '../utils'; const b = block('d3-axis'); -const EMPTY_SPACE_BETWEEN_LABELS = 10; const MAX_WIDTH = 80; type Props = { @@ -41,7 +40,7 @@ export const AxisY = ({axises, width, height, scale}: Props) => { let yAxisGenerator = axisLeft(scale as AxisScale) .tickSize(tickSize) - .tickPadding(axis.labels.padding) + .tickPadding(axis.labels.distance) .tickFormat((value) => { if (!axis.labels.enabled) { return ''; @@ -94,13 +93,13 @@ export const AxisY = ({axises, width, height, scale}: Props) => { if (r.bottom > elementY && index !== 0) { return true; } - elementY = r.top - EMPTY_SPACE_BETWEEN_LABELS; + elementY = r.top - axis.labels.padding; return false; }) .remove(); if (axis.title.text) { - const textY = axis.title.height + axis.labels.padding; + const textY = axis.title.height + axis.labels.distance; svgElement .append('text') diff --git a/src/plugins/d3/renderer/components/Chart.tsx b/src/plugins/d3/renderer/components/Chart.tsx index 13260133..3b92b784 100644 --- a/src/plugins/d3/renderer/components/Chart.tsx +++ b/src/plugins/d3/renderer/components/Chart.tsx @@ -12,7 +12,6 @@ import { useShapes, useTooltip, } from '../hooks'; -import {isAxisRelatedSeries} from '../utils'; import {AxisY} from './AxisY'; import {AxisX} from './AxisX'; import {Legend} from './Legend'; @@ -49,13 +48,13 @@ export const Chart = (props: Props) => { preparedYAxis: yAxis, }); const {boundsWidth, boundsHeight} = useChartDimensions({ - hasAxisRelatedSeries: data.series.data.some(isAxisRelatedSeries), width, height, margin: chart.margin, preparedLegend, preparedXAxis: xAxis, preparedYAxis: yAxis, + preparedSeries: preparedSeries, }); const {xScale, yScale} = useAxisScales({ boundsWidth, diff --git a/src/plugins/d3/renderer/constants/defaults/axis.ts b/src/plugins/d3/renderer/constants/defaults/axis.ts new file mode 100644 index 00000000..fe6588f6 --- /dev/null +++ b/src/plugins/d3/renderer/constants/defaults/axis.ts @@ -0,0 +1,5 @@ +export const axisLabelsDefaults = { + distance: 10, + padding: 5, + fontSize: 11, +}; diff --git a/src/plugins/d3/renderer/constants/defaults/index.ts b/src/plugins/d3/renderer/constants/defaults/index.ts index a0482efa..bdb02733 100644 --- a/src/plugins/d3/renderer/constants/defaults/index.ts +++ b/src/plugins/d3/renderer/constants/defaults/index.ts @@ -1 +1,2 @@ +export * from './axis'; export * from './legend'; diff --git a/src/plugins/d3/renderer/constants/index.ts b/src/plugins/d3/renderer/constants/index.ts index f42e5202..945a2ce3 100644 --- a/src/plugins/d3/renderer/constants/index.ts +++ b/src/plugins/d3/renderer/constants/index.ts @@ -24,5 +24,4 @@ export const DEFAULT_PALETTE = [ ]; export const DEFAULT_AXIS_LABEL_FONT_SIZE = '11px'; -export const DEFAULT_AXIS_LABEL_PADDING = 10; export const DEFAULT_AXIS_TITLE_FONT_SIZE = '14px'; diff --git a/src/plugins/d3/renderer/hooks/useChartDimensions/index.ts b/src/plugins/d3/renderer/hooks/useChartDimensions/index.ts deleted file mode 100644 index 86670215..00000000 --- a/src/plugins/d3/renderer/hooks/useChartDimensions/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type {ChartMargin} from '../../../../../types/widget-data'; - -import type {PreparedAxis, PreparedLegend} from '../../hooks'; -import {getHorisontalSvgTextHeight} from '../../utils'; -import {getBoundsWidth} from './utils'; - -export {getBoundsWidth} from './utils'; - -type Args = { - hasAxisRelatedSeries: boolean; - width: number; - height: number; - margin: ChartMargin; - preparedLegend: PreparedLegend; - preparedXAxis: PreparedAxis; - preparedYAxis: PreparedAxis[]; -}; - -const getHeightOccupiedByXAxis = (preparedXAxis: PreparedAxis) => { - let height = preparedXAxis.title.height; - - if (preparedXAxis.labels.enabled) { - height += - preparedXAxis.labels.padding + - getHorisontalSvgTextHeight({text: 'Tmp', style: preparedXAxis.labels.style}); - } - - return height; -}; - -const getBottomOffset = (args: { - hasAxisRelatedSeries: boolean; - preparedLegend: PreparedLegend; - preparedXAxis: PreparedAxis; -}) => { - const {hasAxisRelatedSeries, preparedLegend, preparedXAxis} = args; - let result = 0; - - if (preparedLegend.enabled) { - result += preparedLegend.height + preparedLegend.margin; - } - - if (hasAxisRelatedSeries) { - result += getHeightOccupiedByXAxis(preparedXAxis); - } - - return result; -}; - -export const useChartDimensions = (args: Args) => { - const { - hasAxisRelatedSeries, - margin, - width, - height, - preparedLegend, - preparedXAxis, - preparedYAxis, - } = args; - const bottomOffset = getBottomOffset({hasAxisRelatedSeries, preparedLegend, preparedXAxis}); - const boundsWidth = getBoundsWidth({chartWidth: width, chartMargin: margin, preparedYAxis}); - const boundsHeight = height - margin.top - margin.bottom - bottomOffset; - - return {boundsWidth, boundsHeight}; -}; diff --git a/src/plugins/d3/renderer/hooks/useChartDimensions/index.tsx b/src/plugins/d3/renderer/hooks/useChartDimensions/index.tsx new file mode 100644 index 00000000..4ecbdd1a --- /dev/null +++ b/src/plugins/d3/renderer/hooks/useChartDimensions/index.tsx @@ -0,0 +1,118 @@ +import type {ChartMargin} from '../../../../../types/widget-data'; + +import type {PreparedAxis, PreparedLegend} from '../../hooks'; +import { + formatAxisTickLabel, + getClosestPointsRange, + getHorisontalSvgTextHeight, + getLabelsMaxHeight, + getMaxTickCount, + getTicksCount, + getXAxisItems, + hasOverlappingLabels, + isAxisRelatedSeries, +} from '../../utils'; +import {getBoundsWidth} from './utils'; +import {createXScale, PreparedSeries} from '../../hooks'; +import {AxisDomain, AxisScale} from 'd3'; +import React from 'react'; + +export {getBoundsWidth} from './utils'; + +type Args = { + width: number; + height: number; + margin: ChartMargin; + preparedLegend: PreparedLegend; + preparedXAxis: PreparedAxis; + preparedYAxis: PreparedAxis[]; + preparedSeries: PreparedSeries[]; +}; + +const getHeightOccupiedByXAxis = ({ + preparedXAxis, + preparedSeries, + width, +}: { + preparedXAxis: PreparedAxis; + preparedSeries: PreparedSeries[]; + width: number; +}) => { + let height = preparedXAxis.title.height; + + if (preparedXAxis.labels.enabled) { + const scale = createXScale(preparedXAxis, preparedSeries, width); + const tickCount = getTicksCount({axis: preparedXAxis, range: width}); + const ticks = getXAxisItems({ + scale: scale as AxisScale, + count: tickCount, + maxCount: getMaxTickCount({width, axis: preparedXAxis}), + }); + const step = getClosestPointsRange(preparedXAxis, ticks); + const labels = ticks.map((value: AxisDomain) => { + return formatAxisTickLabel({ + axis: preparedXAxis, + value, + step, + }); + }); + const overlapping = hasOverlappingLabels({ + width, + labels, + padding: preparedXAxis.labels.padding, + style: preparedXAxis.labels.style, + }); + const labelsHeight = overlapping + ? getLabelsMaxHeight({ + labels, + style: preparedXAxis.labels.style, + transform: 'rotate(-45)', + }) + : getHorisontalSvgTextHeight({text: 'Tmp', style: preparedXAxis.labels.style}); + height += preparedXAxis.labels.distance + labelsHeight; + } + + return height; +}; + +const getBottomOffset = (args: { + hasAxisRelatedSeries: boolean; + preparedLegend: PreparedLegend; + preparedXAxis: PreparedAxis; + preparedSeries: PreparedSeries[]; + width: number; +}) => { + const {hasAxisRelatedSeries, preparedLegend, preparedXAxis, preparedSeries, width} = args; + let result = 0; + + if (preparedLegend.enabled) { + result += preparedLegend.height + preparedLegend.margin; + } + + if (hasAxisRelatedSeries) { + result += getHeightOccupiedByXAxis({preparedXAxis, preparedSeries, width}); + } + + return result; +}; + +export const useChartDimensions = (args: Args) => { + const {margin, width, height, preparedLegend, preparedXAxis, preparedYAxis, preparedSeries} = + args; + + return React.useMemo(() => { + console.log('useChartDimensions'); + const hasAxisRelatedSeries = preparedSeries.some(isAxisRelatedSeries); + const bottomOffset = getBottomOffset({ + hasAxisRelatedSeries, + preparedLegend, + preparedXAxis, + preparedSeries, + width, + }); + const boundsWidth = getBoundsWidth({chartWidth: width, chartMargin: margin, preparedYAxis}); + const boundsHeight = height - margin.top - margin.bottom - bottomOffset; + + return {boundsWidth, boundsHeight}; + }, [margin, width, height, preparedLegend, preparedXAxis, preparedYAxis, preparedSeries]); +}; diff --git a/src/plugins/d3/renderer/hooks/useChartOptions/chart.ts b/src/plugins/d3/renderer/hooks/useChartOptions/chart.ts index fd789c68..b68c7d6a 100644 --- a/src/plugins/d3/renderer/hooks/useChartOptions/chart.ts +++ b/src/plugins/d3/renderer/hooks/useChartOptions/chart.ts @@ -39,7 +39,7 @@ const getMarginLeft = (args: { if (hasAxisRelatedSeries) { marginLeft += AXIS_LINE_WIDTH + - preparedY1Axis.labels.padding + + preparedY1Axis.labels.distance + (preparedY1Axis.labels.maxWidth || 0) + preparedY1Axis.title.height; } diff --git a/src/plugins/d3/renderer/hooks/useChartOptions/types.ts b/src/plugins/d3/renderer/hooks/useChartOptions/types.ts index 1228e639..9f4628df 100644 --- a/src/plugins/d3/renderer/hooks/useChartOptions/types.ts +++ b/src/plugins/d3/renderer/hooks/useChartOptions/types.ts @@ -8,7 +8,7 @@ import type { } from '../../../../../types/widget-data'; type PreparedAxisLabels = Omit & - Required> & { + Required> & { style: BaseTextStyle; maxWidth?: number; }; diff --git a/src/plugins/d3/renderer/hooks/useChartOptions/x-axis.ts b/src/plugins/d3/renderer/hooks/useChartOptions/x-axis.ts index 53e9d97d..f98686a2 100644 --- a/src/plugins/d3/renderer/hooks/useChartOptions/x-axis.ts +++ b/src/plugins/d3/renderer/hooks/useChartOptions/x-axis.ts @@ -3,8 +3,8 @@ import get from 'lodash/get'; import type {ChartKitWidgetData} from '../../../../../types/widget-data'; import { + axisLabelsDefaults, DEFAULT_AXIS_LABEL_FONT_SIZE, - DEFAULT_AXIS_LABEL_PADDING, DEFAULT_AXIS_TITLE_FONT_SIZE, } from '../../constants'; import type {PreparedAxis} from './types'; @@ -21,7 +21,8 @@ export const getPreparedXAxis = ({xAxis}: {xAxis: ChartKitWidgetData['xAxis']}): type: get(xAxis, 'type', 'linear'), labels: { enabled: get(xAxis, 'labels.enabled', true), - padding: get(xAxis, 'labels.padding', DEFAULT_AXIS_LABEL_PADDING), + distance: get(xAxis, 'labels.distance', axisLabelsDefaults.distance), + padding: get(xAxis, 'labels.padding', axisLabelsDefaults.padding), dateFormat: get(xAxis, 'labels.dateFormat'), numberFormat: get(xAxis, 'labels.numberFormat'), style: {fontSize: get(xAxis, 'labels.style.fontSize', DEFAULT_AXIS_LABEL_FONT_SIZE)}, diff --git a/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts b/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts index ddeda605..86abb741 100644 --- a/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts +++ b/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts @@ -1,4 +1,4 @@ -import type {ScaleLinear, AxisDomain} from 'd3'; +import type {AxisDomain, AxisScale} from 'd3'; import {select} from 'd3'; import get from 'lodash/get'; @@ -9,11 +9,16 @@ import type { } from '../../../../../types/widget-data'; import { + axisLabelsDefaults, DEFAULT_AXIS_LABEL_FONT_SIZE, - DEFAULT_AXIS_LABEL_PADDING, DEFAULT_AXIS_TITLE_FONT_SIZE, } from '../../constants'; -import {getHorisontalSvgTextHeight, formatAxisTickLabel, getClosestPointsRange} from '../../utils'; +import { + getHorisontalSvgTextHeight, + formatAxisTickLabel, + getClosestPointsRange, + getScaleTicks, +} from '../../utils'; import type {PreparedAxis} from './types'; import {createYScale} from '../useAxisScales'; import {PreparedSeries} from '../useSeries/types'; @@ -26,10 +31,7 @@ const getAxisLabelMaxWidth = (args: {axis: PreparedAxis; series: ChartKitWidgetS } const scale = createYScale(axis, series as PreparedSeries[], 1); - const ticks: AxisDomain[] = - axis.type === 'category' - ? axis.categories || [] - : (scale as ScaleLinear).ticks(); + const ticks: AxisDomain[] = getScaleTicks(scale as AxisScale); // FIXME: it is necessary to filter data, since we do not draw overlapping ticks @@ -86,7 +88,8 @@ export const getPreparedYAxis = ({ type: get(yAxis1, 'type', 'linear'), labels: { enabled: get(yAxis1, 'labels.enabled', true), - padding: get(yAxis1, 'labels.padding', DEFAULT_AXIS_LABEL_PADDING), + distance: get(yAxis1, 'labels.distance', axisLabelsDefaults.distance), + padding: get(yAxis1, 'labels.padding', axisLabelsDefaults.padding), dateFormat: get(yAxis1, 'labels.dateFormat'), numberFormat: get(yAxis1, 'labels.numberFormat'), style: y1LabelsStyle, diff --git a/src/plugins/d3/renderer/utils/axis-generators/bottom.ts b/src/plugins/d3/renderer/utils/axis-generators/bottom.ts new file mode 100644 index 00000000..f1d4918a --- /dev/null +++ b/src/plugins/d3/renderer/utils/axis-generators/bottom.ts @@ -0,0 +1,126 @@ +import type {AxisDomain, AxisScale, Selection} from 'd3'; +import {getXAxisItems, getXAxisOffset, getXTickPosition} from '../axis'; + +type AxisBottomArgs = { + scale: AxisScale; + ticks: { + count?: number; + maxTickCount: number; + labelFormat: (value: any) => string; + labelsPaddings?: number; + labelsDistance?: number; + size: number; + autoRotation?: boolean; + }; + domain: { + size: number; + color?: string; + }; +}; + +function addDomain( + selection: Selection, + options: { + size: number; + color?: string; + }, +) { + const {size, color} = options; + + selection + .selectAll('.domain') + .data([null]) + .enter() + .insert('path', '.tick') + .attr('class', 'domain') + .attr('stroke', color || 'currentColor') + .attr('d', `M0,0V0H${size}`); +} + +function hasOverlappingLabels(nodes: SVGTextElement[], padding = 0) { + let elementX = 0; + + return nodes.some(function (node) { + const r = node.getBoundingClientRect(); + + if (r.left < elementX) { + return true; + } + elementX = r.right + padding; + return false; + }); +} + +export function axisBottom(args: AxisBottomArgs) { + const { + scale, + ticks: { + labelFormat, + labelsPaddings = 0, + labelsDistance = 0, + size: tickSize, + count: ticksCount, + maxTickCount, + autoRotation = true, + }, + domain: {size: domainSize, color: domainColor}, + } = args; + const offset = getXAxisOffset(); + const spacing = Math.max(tickSize, 0) + labelsDistance; + const position = getXTickPosition({scale, offset}); + const values = getXAxisItems({scale, count: ticksCount, maxCount: maxTickCount}); + + return function (selection: Selection) { + selection + .selectAll('.tick') + .data(values) + .order() + .join((el) => { + const tick = el.append('g').attr('class', 'tick'); + tick.append('line').attr('stroke', 'currentColor').attr('y2', tickSize); + tick.append('text') + .attr('fill', 'currentColor') + .attr('y', spacing) + .attr('dy', '0.71em') + .text((d) => labelFormat(d)); + + return tick; + }) + .attr('transform', function (d) { + return `translate(${position(d as AxisDomain) + offset},0)`; + }); + + const labels = selection.selectAll('.tick text'); + const labelNodes = labels.nodes() as SVGTextElement[]; + + if (hasOverlappingLabels(labelNodes, labelsPaddings)) { + if (autoRotation) { + const labelHeight = labelNodes[0]?.getBoundingClientRect()?.height; + const labelOffset = (labelHeight / 2 + labelsDistance) / 2; + labels + .attr('text-anchor', 'end') + .attr('transform', `rotate(-45) translate(-${labelOffset}, -${labelOffset})`); + } else { + // remove overlapping labels + let elementX = 0; + selection + .selectAll('.tick') + .filter(function () { + const node = this as unknown as Element; + const r = node.getBoundingClientRect(); + + if (r.left < elementX) { + return true; + } + elementX = r.right + labelsPaddings; + return false; + }) + .remove(); + } + } + + selection.call(addDomain, {size: domainSize, color: domainColor}); + + selection.attr('text-anchor', 'middle'); + }; +} diff --git a/src/plugins/d3/renderer/utils/axis-generators/index.ts b/src/plugins/d3/renderer/utils/axis-generators/index.ts new file mode 100644 index 00000000..2321bbf8 --- /dev/null +++ b/src/plugins/d3/renderer/utils/axis-generators/index.ts @@ -0,0 +1 @@ +export * from './bottom'; diff --git a/src/plugins/d3/renderer/utils/axis.ts b/src/plugins/d3/renderer/utils/axis.ts index e6006cc2..99c617f0 100644 --- a/src/plugins/d3/renderer/utils/axis.ts +++ b/src/plugins/d3/renderer/utils/axis.ts @@ -1,4 +1,5 @@ import {PreparedAxis} from '../hooks'; +import {AxisDomain, AxisScale, ScaleBand} from 'd3'; export function getTicksCount({axis, range}: {axis: PreparedAxis; range: number}) { let ticksCount: number | undefined; @@ -9,3 +10,66 @@ export function getTicksCount({axis, range}: {axis: PreparedAxis; range: number} return ticksCount; } + +export function isBandScale(scale: AxisScale): scale is ScaleBand { + return 'bandwidth' in scale && typeof scale.bandwidth === 'function'; +} + +export function getScaleTicks(scale: AxisScale, ticksCount?: number) { + return 'ticks' in scale && typeof scale.ticks === 'function' + ? scale.ticks(ticksCount) + : scale.domain(); +} + +export function getXAxisOffset() { + return typeof window !== 'undefined' && window.devicePixelRatio > 1 ? 0 : 0.5; +} + +function number(scale: AxisScale) { + return (d: AxisDomain) => Number(scale(d)); +} + +function center(scale: ScaleBand, offset: number) { + offset = Math.max(0, scale.bandwidth() - offset * 2) / 2; + if (scale.round()) { + offset = Math.round(offset); + } + return (d: AxisDomain) => Number(scale(d)) + offset; +} + +export function getXTickPosition({scale, offset}: {scale: AxisScale; offset: number}) { + return isBandScale(scale) ? center(scale.copy(), offset) : number(scale.copy()); +} + +export function getXAxisItems({ + scale, + count, + maxCount, +}: { + scale: AxisScale; + count?: number; + maxCount: number; +}) { + const offset = getXAxisOffset(); + let values = getScaleTicks(scale, count); + const position = getXTickPosition({scale, offset}); + + if (values.length > maxCount) { + const step = Math.ceil(values.length / maxCount); + values = values.filter((_: AxisDomain, i: number) => i % step === 0); + } + + // Remove tick that has the same x coordinate like domain + if (values.length && position(values[0]) === 0) { + values = values.slice(1); + } + + return values; +} + +export function getMaxTickCount({axis, width}: {axis: PreparedAxis; width: number}) { + const minTickWidth = + (width - axis.labels.padding) / + (parseInt(axis.labels.style.fontSize) + axis.labels.padding); + return Math.floor(width / minTickWidth); +} diff --git a/src/plugins/d3/renderer/utils/text.ts b/src/plugins/d3/renderer/utils/text.ts index 8a24ffa1..48eabd26 100644 --- a/src/plugins/d3/renderer/utils/text.ts +++ b/src/plugins/d3/renderer/utils/text.ts @@ -1,5 +1,6 @@ import type {Selection} from 'd3'; import {select} from 'd3'; +import {BaseTextStyle} from '../../../../types'; export function setEllipsisForOverflowText( selection: Selection, @@ -25,3 +26,94 @@ export function setEllipsisForOverflowTexts( setEllipsisForOverflowText(select(this), maxWidth); }); } + +export function hasOverlappingLabels({ + width, + labels, + padding = 0, + style, +}: { + width: number; + labels: string[]; + style?: BaseTextStyle; + padding?: number; +}) { + const maxWidth = (width - padding * (labels.length - 1)) / labels.length; + + const textElement = select(document.body) + .append('text') + .style('font-size', style?.fontSize || ''); + + const result = labels.some((label) => { + const textWidth = textElement.text(label).node()?.getBoundingClientRect()?.width || 0; + return textWidth > maxWidth; + }); + + textElement.remove(); + + return result; +} + +function renderLabels( + selection: Selection, + { + labels, + style, + transform, + }: { + labels: string[]; + style?: BaseTextStyle; + transform?: string; + }, +) { + const text = selection + .append('g') + .append('text') + .attr('transform', transform || '') + .style('font-size', style?.fontSize || ''); + text.selectAll('tspan') + .data(labels) + .enter() + .append('tspan') + .attr('x', 0) + .attr('dy', 0) + .text((d) => d); + + return text; +} + +export function getLabelsMaxWidth({ + labels, + style, + transform, +}: { + labels: string[]; + style?: BaseTextStyle; + transform?: string; +}) { + const svg = select(document.body).append('svg'); + svg.call(renderLabels, {labels, style, transform}); + + const maxWidth = (svg.select('g').node() as Element)?.getBoundingClientRect()?.width || 0; + svg.remove(); + + return maxWidth; +} + +export function getLabelsMaxHeight({ + labels, + style, + transform, +}: { + labels: string[]; + style?: BaseTextStyle; + transform?: string; +}) { + const svg = select(document.body).append('svg'); + svg.call(renderLabels, {labels, style, transform}); + + const maxHeight = (svg.select('g').node() as Element)?.getBoundingClientRect()?.height || 0; + svg.remove(); + + return maxHeight; +} diff --git a/src/types/widget-data/axis.ts b/src/types/widget-data/axis.ts index ce5c984f..ca60598d 100644 --- a/src/types/widget-data/axis.ts +++ b/src/types/widget-data/axis.ts @@ -6,11 +6,27 @@ export type ChartKitWidgetAxisType = 'category' | 'datetime' | 'linear'; export type ChartKitWidgetAxisLabels = { /** Enable or disable the axis labels. */ enabled?: boolean; - /** The pixel padding for axis labels. */ + + /** The label's pixel distance from the perimeter of the plot area. + * + * @default: 10 + */ + distance?: number; + + /** The pixel padding for axis labels, to ensure white space between them. + * + * @defaults: 5 + * */ padding?: number; + dateFormat?: string; numberFormat?: FormatNumberOptions; style?: Partial; + + /** For horizontal axes, enable label rotation to prevent overlapping labels. + * If there is enough space, labels are not rotated. + * As the chart gets narrower, it will start rotating the labels -45 degrees. */ + autoRotation?: boolean; }; export type ChartKitWidgetAxis = { From af073ca6a79f3ca5520d85fb4f42b4375235669f Mon Sep 17 00:00:00 2001 From: "Irina V. Kuzmina" Date: Mon, 18 Sep 2023 19:45:12 +0300 Subject: [PATCH 2/5] fix Y axis categories --- src/plugins/d3/renderer/components/AxisX.tsx | 9 ++---- src/plugins/d3/renderer/components/AxisY.tsx | 5 +-- .../d3/renderer/constants/defaults/axis.ts | 2 +- .../hooks/useChartDimensions/index.tsx | 7 ++-- .../renderer/hooks/useChartOptions/y-axis.ts | 32 +++++++------------ .../renderer/utils/axis-generators/bottom.ts | 31 ++++++++---------- src/plugins/d3/renderer/utils/axis.ts | 4 +-- 7 files changed, 38 insertions(+), 52 deletions(-) diff --git a/src/plugins/d3/renderer/components/AxisX.tsx b/src/plugins/d3/renderer/components/AxisX.tsx index f8f2a8e3..e35f54b0 100644 --- a/src/plugins/d3/renderer/components/AxisX.tsx +++ b/src/plugins/d3/renderer/components/AxisX.tsx @@ -11,6 +11,7 @@ import { setEllipsisForOverflowText, getTicksCount, getScaleTicks, + getMaxTickCount, } from '../utils'; import {axisBottom} from '../utils/axis-generators'; @@ -52,11 +53,6 @@ export const AxisX = React.memo(({axis, width, height, scale, chartWidth}: Props const svgElement = select(ref.current); svgElement.selectAll('*').remove(); - const minTickWidth = - (width - axis.labels.padding) / - (parseInt(axis.labels.style.fontSize) + axis.labels.padding); - const maxTickCount = Math.floor(width / minTickWidth); - const xAxisGenerator = axisBottom({ scale: scale as AxisScale, ticks: { @@ -64,8 +60,9 @@ export const AxisX = React.memo(({axis, width, height, scale, chartWidth}: Props labelFormat: getLabelFormatter({axis, scale}), labelsPaddings: axis.labels.padding, labelsDistance: axis.labels.distance, + labelsStyle: axis.labels.style, count: getTicksCount({axis, range: width}), - maxTickCount: maxTickCount, + maxTickCount: getMaxTickCount({axis, width}), autoRotation: axis.labels.autoRotation, }, domain: { diff --git a/src/plugins/d3/renderer/components/AxisY.tsx b/src/plugins/d3/renderer/components/AxisY.tsx index c1154089..255af582 100644 --- a/src/plugins/d3/renderer/components/AxisY.tsx +++ b/src/plugins/d3/renderer/components/AxisY.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {axisLeft, ScaleLinear, select} from 'd3'; +import {axisLeft, select} from 'd3'; import type {AxisScale, AxisDomain} from 'd3'; import {block} from '../../../../utils/cn'; @@ -12,6 +12,7 @@ import { setEllipsisForOverflowText, setEllipsisForOverflowTexts, getTicksCount, + getScaleTicks, } from '../utils'; const b = block('d3-axis'); @@ -36,7 +37,7 @@ export const AxisY = ({axises, width, height, scale}: Props) => { const svgElement = select(ref.current); svgElement.selectAll('*').remove(); const tickSize = axis.grid.enabled ? width * -1 : 0; - const step = getClosestPointsRange(axis, (scale as ScaleLinear).ticks()); + const step = getClosestPointsRange(axis, getScaleTicks(scale as AxisScale)); let yAxisGenerator = axisLeft(scale as AxisScale) .tickSize(tickSize) diff --git a/src/plugins/d3/renderer/constants/defaults/axis.ts b/src/plugins/d3/renderer/constants/defaults/axis.ts index fe6588f6..b2f43ae0 100644 --- a/src/plugins/d3/renderer/constants/defaults/axis.ts +++ b/src/plugins/d3/renderer/constants/defaults/axis.ts @@ -1,5 +1,5 @@ export const axisLabelsDefaults = { distance: 10, - padding: 5, + padding: 10, fontSize: 11, }; diff --git a/src/plugins/d3/renderer/hooks/useChartDimensions/index.tsx b/src/plugins/d3/renderer/hooks/useChartDimensions/index.tsx index 4ecbdd1a..618f241e 100644 --- a/src/plugins/d3/renderer/hooks/useChartDimensions/index.tsx +++ b/src/plugins/d3/renderer/hooks/useChartDimensions/index.tsx @@ -62,6 +62,7 @@ const getHeightOccupiedByXAxis = ({ padding: preparedXAxis.labels.padding, style: preparedXAxis.labels.style, }); + const labelsHeight = overlapping ? getLabelsMaxHeight({ labels, @@ -101,16 +102,16 @@ export const useChartDimensions = (args: Args) => { args; return React.useMemo(() => { - console.log('useChartDimensions'); const hasAxisRelatedSeries = preparedSeries.some(isAxisRelatedSeries); + const boundsWidth = getBoundsWidth({chartWidth: width, chartMargin: margin, preparedYAxis}); const bottomOffset = getBottomOffset({ hasAxisRelatedSeries, preparedLegend, preparedXAxis, preparedSeries, - width, + width: boundsWidth, }); - const boundsWidth = getBoundsWidth({chartWidth: width, chartMargin: margin, preparedYAxis}); + const boundsHeight = height - margin.top - margin.bottom - bottomOffset; return {boundsWidth, boundsHeight}; diff --git a/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts b/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts index 86abb741..7ba54319 100644 --- a/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts +++ b/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts @@ -1,5 +1,4 @@ import type {AxisDomain, AxisScale} from 'd3'; -import {select} from 'd3'; import get from 'lodash/get'; import type { @@ -18,6 +17,7 @@ import { formatAxisTickLabel, getClosestPointsRange, getScaleTicks, + getLabelsMaxWidth, } from '../../utils'; import type {PreparedAxis} from './types'; import {createYScale} from '../useAxisScales'; @@ -36,26 +36,18 @@ const getAxisLabelMaxWidth = (args: {axis: PreparedAxis; series: ChartKitWidgetS // FIXME: it is necessary to filter data, since we do not draw overlapping ticks const step = getClosestPointsRange(axis, ticks); - const svg = select(document.body).append('svg'); - const text = svg.append('g').append('text').style('font-size', axis.labels.style.fontSize); - text.selectAll('tspan') - .data(ticks as (string | number)[]) - .enter() - .append('tspan') - .attr('x', 0) - .attr('dy', 0) - .text((d) => { - return formatAxisTickLabel({ - axis, - value: d, - step, - }); - }); + const labels = (ticks as (string | number)[]).map((tick) => + formatAxisTickLabel({ + axis, + value: tick, + step, + }), + ); - const maxWidth = (text.node() as SVGTextElement).getBoundingClientRect()?.width || 0; - svg.remove(); - - return maxWidth; + return getLabelsMaxWidth({ + labels, + style: axis.labels.style, + }); }; const applyLabelsMaxWidth = (args: { diff --git a/src/plugins/d3/renderer/utils/axis-generators/bottom.ts b/src/plugins/d3/renderer/utils/axis-generators/bottom.ts index f1d4918a..0559c7bb 100644 --- a/src/plugins/d3/renderer/utils/axis-generators/bottom.ts +++ b/src/plugins/d3/renderer/utils/axis-generators/bottom.ts @@ -1,5 +1,7 @@ import type {AxisDomain, AxisScale, Selection} from 'd3'; import {getXAxisItems, getXAxisOffset, getXTickPosition} from '../axis'; +import {BaseTextStyle} from '../../../../../types'; +import {hasOverlappingLabels} from '../text'; type AxisBottomArgs = { scale: AxisScale; @@ -9,6 +11,7 @@ type AxisBottomArgs = { labelFormat: (value: any) => string; labelsPaddings?: number; labelsDistance?: number; + labelsStyle?: BaseTextStyle; size: number; autoRotation?: boolean; }; @@ -37,20 +40,6 @@ function addDomain( .attr('d', `M0,0V0H${size}`); } -function hasOverlappingLabels(nodes: SVGTextElement[], padding = 0) { - let elementX = 0; - - return nodes.some(function (node) { - const r = node.getBoundingClientRect(); - - if (r.left < elementX) { - return true; - } - elementX = r.right + padding; - return false; - }); -} - export function axisBottom(args: AxisBottomArgs) { const { scale, @@ -58,6 +47,7 @@ export function axisBottom(args: AxisBottomArgs) { labelFormat, labelsPaddings = 0, labelsDistance = 0, + labelsStyle, size: tickSize, count: ticksCount, maxTickCount, @@ -82,7 +72,7 @@ export function axisBottom(args: AxisBottomArgs) { .attr('fill', 'currentColor') .attr('y', spacing) .attr('dy', '0.71em') - .text((d) => labelFormat(d)); + .text(labelFormat); return tick; }) @@ -93,7 +83,14 @@ export function axisBottom(args: AxisBottomArgs) { const labels = selection.selectAll('.tick text'); const labelNodes = labels.nodes() as SVGTextElement[]; - if (hasOverlappingLabels(labelNodes, labelsPaddings)) { + const overlapping = hasOverlappingLabels({ + width: domainSize, + labels: values.map(labelFormat), + padding: labelsPaddings, + style: labelsStyle, + }); + + if (overlapping) { if (autoRotation) { const labelHeight = labelNodes[0]?.getBoundingClientRect()?.height; const labelOffset = (labelHeight / 2 + labelsDistance) / 2; @@ -121,6 +118,6 @@ export function axisBottom(args: AxisBottomArgs) { selection.call(addDomain, {size: domainSize, color: domainColor}); - selection.attr('text-anchor', 'middle'); + selection.attr('text-anchor', 'middle').style('font-size', labelsStyle?.fontSize || ''); }; } diff --git a/src/plugins/d3/renderer/utils/axis.ts b/src/plugins/d3/renderer/utils/axis.ts index 99c617f0..2b1ab80e 100644 --- a/src/plugins/d3/renderer/utils/axis.ts +++ b/src/plugins/d3/renderer/utils/axis.ts @@ -68,8 +68,6 @@ export function getXAxisItems({ } export function getMaxTickCount({axis, width}: {axis: PreparedAxis; width: number}) { - const minTickWidth = - (width - axis.labels.padding) / - (parseInt(axis.labels.style.fontSize) + axis.labels.padding); + const minTickWidth = parseInt(axis.labels.style.fontSize) + axis.labels.padding; return Math.floor(width / minTickWidth); } From 29bd7a0f26945d36561580f909c30ce56645c21f Mon Sep 17 00:00:00 2001 From: "Irina V. Kuzmina" Date: Tue, 19 Sep 2023 12:41:34 +0300 Subject: [PATCH 3/5] fix --- src/plugins/d3/renderer/hooks/useChartOptions/x-axis.ts | 1 + src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/plugins/d3/renderer/hooks/useChartOptions/x-axis.ts b/src/plugins/d3/renderer/hooks/useChartOptions/x-axis.ts index f98686a2..cb69affa 100644 --- a/src/plugins/d3/renderer/hooks/useChartOptions/x-axis.ts +++ b/src/plugins/d3/renderer/hooks/useChartOptions/x-axis.ts @@ -25,6 +25,7 @@ export const getPreparedXAxis = ({xAxis}: {xAxis: ChartKitWidgetData['xAxis']}): padding: get(xAxis, 'labels.padding', axisLabelsDefaults.padding), dateFormat: get(xAxis, 'labels.dateFormat'), numberFormat: get(xAxis, 'labels.numberFormat'), + autoRotation: get(xAxis, 'labels.autoRotation', true), style: {fontSize: get(xAxis, 'labels.style.fontSize', DEFAULT_AXIS_LABEL_FONT_SIZE)}, }, lineColor: get(xAxis, 'lineColor'), diff --git a/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts b/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts index 7ba54319..e75d1e29 100644 --- a/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts +++ b/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts @@ -82,6 +82,7 @@ export const getPreparedYAxis = ({ enabled: get(yAxis1, 'labels.enabled', true), distance: get(yAxis1, 'labels.distance', axisLabelsDefaults.distance), padding: get(yAxis1, 'labels.padding', axisLabelsDefaults.padding), + autoRotation: get(yAxis1, 'labels.autoRotation', false), dateFormat: get(yAxis1, 'labels.dateFormat'), numberFormat: get(yAxis1, 'labels.numberFormat'), style: y1LabelsStyle, From 2e6757b0f342b92eaf941927e26da4097a4fcb00 Mon Sep 17 00:00:00 2001 From: "Irina V. Kuzmina" Date: Tue, 19 Sep 2023 12:48:53 +0300 Subject: [PATCH 4/5] rename distance -> margin --- src/plugins/d3/renderer/components/AxisX.tsx | 2 +- src/plugins/d3/renderer/components/AxisY.tsx | 4 ++-- src/plugins/d3/renderer/constants/defaults/axis.ts | 2 +- .../d3/renderer/hooks/useChartDimensions/index.tsx | 2 +- src/plugins/d3/renderer/hooks/useChartOptions/chart.ts | 2 +- src/plugins/d3/renderer/hooks/useChartOptions/types.ts | 2 +- src/plugins/d3/renderer/hooks/useChartOptions/x-axis.ts | 2 +- src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts | 2 +- src/plugins/d3/renderer/utils/axis-generators/bottom.ts | 8 ++++---- src/types/widget-data/axis.ts | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/plugins/d3/renderer/components/AxisX.tsx b/src/plugins/d3/renderer/components/AxisX.tsx index e35f54b0..bd2d2f42 100644 --- a/src/plugins/d3/renderer/components/AxisX.tsx +++ b/src/plugins/d3/renderer/components/AxisX.tsx @@ -59,7 +59,7 @@ export const AxisX = React.memo(({axis, width, height, scale, chartWidth}: Props size: axis.grid.enabled ? height * -1 : 0, labelFormat: getLabelFormatter({axis, scale}), labelsPaddings: axis.labels.padding, - labelsDistance: axis.labels.distance, + labelsMargin: axis.labels.margin, labelsStyle: axis.labels.style, count: getTicksCount({axis, range: width}), maxTickCount: getMaxTickCount({axis, width}), diff --git a/src/plugins/d3/renderer/components/AxisY.tsx b/src/plugins/d3/renderer/components/AxisY.tsx index 255af582..e3345fd9 100644 --- a/src/plugins/d3/renderer/components/AxisY.tsx +++ b/src/plugins/d3/renderer/components/AxisY.tsx @@ -41,7 +41,7 @@ export const AxisY = ({axises, width, height, scale}: Props) => { let yAxisGenerator = axisLeft(scale as AxisScale) .tickSize(tickSize) - .tickPadding(axis.labels.distance) + .tickPadding(axis.labels.margin) .tickFormat((value) => { if (!axis.labels.enabled) { return ''; @@ -100,7 +100,7 @@ export const AxisY = ({axises, width, height, scale}: Props) => { .remove(); if (axis.title.text) { - const textY = axis.title.height + axis.labels.distance; + const textY = axis.title.height + axis.labels.margin; svgElement .append('text') diff --git a/src/plugins/d3/renderer/constants/defaults/axis.ts b/src/plugins/d3/renderer/constants/defaults/axis.ts index b2f43ae0..11fe72a0 100644 --- a/src/plugins/d3/renderer/constants/defaults/axis.ts +++ b/src/plugins/d3/renderer/constants/defaults/axis.ts @@ -1,5 +1,5 @@ export const axisLabelsDefaults = { - distance: 10, + margin: 10, padding: 10, fontSize: 11, }; diff --git a/src/plugins/d3/renderer/hooks/useChartDimensions/index.tsx b/src/plugins/d3/renderer/hooks/useChartDimensions/index.tsx index 618f241e..cdc4b6c0 100644 --- a/src/plugins/d3/renderer/hooks/useChartDimensions/index.tsx +++ b/src/plugins/d3/renderer/hooks/useChartDimensions/index.tsx @@ -70,7 +70,7 @@ const getHeightOccupiedByXAxis = ({ transform: 'rotate(-45)', }) : getHorisontalSvgTextHeight({text: 'Tmp', style: preparedXAxis.labels.style}); - height += preparedXAxis.labels.distance + labelsHeight; + height += preparedXAxis.labels.margin + labelsHeight; } return height; diff --git a/src/plugins/d3/renderer/hooks/useChartOptions/chart.ts b/src/plugins/d3/renderer/hooks/useChartOptions/chart.ts index b68c7d6a..9ad778d8 100644 --- a/src/plugins/d3/renderer/hooks/useChartOptions/chart.ts +++ b/src/plugins/d3/renderer/hooks/useChartOptions/chart.ts @@ -39,7 +39,7 @@ const getMarginLeft = (args: { if (hasAxisRelatedSeries) { marginLeft += AXIS_LINE_WIDTH + - preparedY1Axis.labels.distance + + preparedY1Axis.labels.margin + (preparedY1Axis.labels.maxWidth || 0) + preparedY1Axis.title.height; } diff --git a/src/plugins/d3/renderer/hooks/useChartOptions/types.ts b/src/plugins/d3/renderer/hooks/useChartOptions/types.ts index 9f4628df..911c4c06 100644 --- a/src/plugins/d3/renderer/hooks/useChartOptions/types.ts +++ b/src/plugins/d3/renderer/hooks/useChartOptions/types.ts @@ -8,7 +8,7 @@ import type { } from '../../../../../types/widget-data'; type PreparedAxisLabels = Omit & - Required> & { + Required> & { style: BaseTextStyle; maxWidth?: number; }; diff --git a/src/plugins/d3/renderer/hooks/useChartOptions/x-axis.ts b/src/plugins/d3/renderer/hooks/useChartOptions/x-axis.ts index cb69affa..179d1bb3 100644 --- a/src/plugins/d3/renderer/hooks/useChartOptions/x-axis.ts +++ b/src/plugins/d3/renderer/hooks/useChartOptions/x-axis.ts @@ -21,7 +21,7 @@ export const getPreparedXAxis = ({xAxis}: {xAxis: ChartKitWidgetData['xAxis']}): type: get(xAxis, 'type', 'linear'), labels: { enabled: get(xAxis, 'labels.enabled', true), - distance: get(xAxis, 'labels.distance', axisLabelsDefaults.distance), + margin: get(xAxis, 'labels.margin', axisLabelsDefaults.margin), padding: get(xAxis, 'labels.padding', axisLabelsDefaults.padding), dateFormat: get(xAxis, 'labels.dateFormat'), numberFormat: get(xAxis, 'labels.numberFormat'), diff --git a/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts b/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts index e75d1e29..4a0fe149 100644 --- a/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts +++ b/src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts @@ -80,7 +80,7 @@ export const getPreparedYAxis = ({ type: get(yAxis1, 'type', 'linear'), labels: { enabled: get(yAxis1, 'labels.enabled', true), - distance: get(yAxis1, 'labels.distance', axisLabelsDefaults.distance), + margin: get(yAxis1, 'labels.margin', axisLabelsDefaults.margin), padding: get(yAxis1, 'labels.padding', axisLabelsDefaults.padding), autoRotation: get(yAxis1, 'labels.autoRotation', false), dateFormat: get(yAxis1, 'labels.dateFormat'), diff --git a/src/plugins/d3/renderer/utils/axis-generators/bottom.ts b/src/plugins/d3/renderer/utils/axis-generators/bottom.ts index 0559c7bb..afb126af 100644 --- a/src/plugins/d3/renderer/utils/axis-generators/bottom.ts +++ b/src/plugins/d3/renderer/utils/axis-generators/bottom.ts @@ -10,7 +10,7 @@ type AxisBottomArgs = { maxTickCount: number; labelFormat: (value: any) => string; labelsPaddings?: number; - labelsDistance?: number; + labelsMargin?: number; labelsStyle?: BaseTextStyle; size: number; autoRotation?: boolean; @@ -46,7 +46,7 @@ export function axisBottom(args: AxisBottomArgs) { ticks: { labelFormat, labelsPaddings = 0, - labelsDistance = 0, + labelsMargin = 0, labelsStyle, size: tickSize, count: ticksCount, @@ -56,7 +56,7 @@ export function axisBottom(args: AxisBottomArgs) { domain: {size: domainSize, color: domainColor}, } = args; const offset = getXAxisOffset(); - const spacing = Math.max(tickSize, 0) + labelsDistance; + const spacing = Math.max(tickSize, 0) + labelsMargin; const position = getXTickPosition({scale, offset}); const values = getXAxisItems({scale, count: ticksCount, maxCount: maxTickCount}); @@ -93,7 +93,7 @@ export function axisBottom(args: AxisBottomArgs) { if (overlapping) { if (autoRotation) { const labelHeight = labelNodes[0]?.getBoundingClientRect()?.height; - const labelOffset = (labelHeight / 2 + labelsDistance) / 2; + const labelOffset = (labelHeight / 2 + labelsMargin) / 2; labels .attr('text-anchor', 'end') .attr('transform', `rotate(-45) translate(-${labelOffset}, -${labelOffset})`); diff --git a/src/types/widget-data/axis.ts b/src/types/widget-data/axis.ts index ca60598d..f7af55f6 100644 --- a/src/types/widget-data/axis.ts +++ b/src/types/widget-data/axis.ts @@ -11,7 +11,7 @@ export type ChartKitWidgetAxisLabels = { * * @default: 10 */ - distance?: number; + margin?: number; /** The pixel padding for axis labels, to ensure white space between them. * From 1c299da464471a20b8edfa5509bdca13ad408bb7 Mon Sep 17 00:00:00 2001 From: "Irina V. Kuzmina" Date: Tue, 19 Sep 2023 13:26:54 +0300 Subject: [PATCH 5/5] revert file renaming --- .../hooks/useChartDimensions/{index.tsx => index.ts} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename src/plugins/d3/renderer/hooks/useChartDimensions/{index.tsx => index.ts} (94%) diff --git a/src/plugins/d3/renderer/hooks/useChartDimensions/index.tsx b/src/plugins/d3/renderer/hooks/useChartDimensions/index.ts similarity index 94% rename from src/plugins/d3/renderer/hooks/useChartDimensions/index.tsx rename to src/plugins/d3/renderer/hooks/useChartDimensions/index.ts index cdc4b6c0..e04001ca 100644 --- a/src/plugins/d3/renderer/hooks/useChartDimensions/index.tsx +++ b/src/plugins/d3/renderer/hooks/useChartDimensions/index.ts @@ -1,6 +1,9 @@ -import type {ChartMargin} from '../../../../../types/widget-data'; +import {AxisDomain, AxisScale} from 'd3'; +import React from 'react'; -import type {PreparedAxis, PreparedLegend} from '../../hooks'; +import type {ChartMargin} from '../../../../../types'; +import type {PreparedAxis, PreparedLegend, PreparedSeries} from '../../hooks'; +import {createXScale} from '../../hooks'; import { formatAxisTickLabel, getClosestPointsRange, @@ -13,9 +16,6 @@ import { isAxisRelatedSeries, } from '../../utils'; import {getBoundsWidth} from './utils'; -import {createXScale, PreparedSeries} from '../../hooks'; -import {AxisDomain, AxisScale} from 'd3'; -import React from 'react'; export {getBoundsWidth} from './utils';