Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(D3 plugin): fix right chart margin #286

Merged
merged 4 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/plugins/d3/__stories__/scatter-performance.json
Original file line number Diff line number Diff line change
Expand Up @@ -32636,7 +32636,8 @@
"yLabel": "109 300"
},
"category": "Mbour",
"y": 109300
"y": 109300,
"radius": 10
}
],
"custom": {
Expand Down
47 changes: 31 additions & 16 deletions src/plugins/d3/renderer/components/AxisX.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {AxisScale, AxisDomain} from 'd3';
import {block} from '../../../../utils/cn';

import type {ChartScale, PreparedAxis} from '../hooks';
import {formatAxisTickLabel, parseTransformStyle} from '../utils';
import {formatAxisTickLabel, parseTransformStyle, setEllipsisForOverflowText} from '../utils';

const b = block('d3-axis');
const EMPTY_SPACE_BETWEEN_LABELS = 10;
Expand All @@ -15,10 +15,11 @@ type Props = {
width: number;
height: number;
scale: ChartScale;
chartWidth: number;
};

// FIXME: add overflow ellipsis for the labels that out of boundaries
export const AxisX = ({axis, width, height, scale}: Props) => {
export const AxisX = ({axis, width, height, scale, chartWidth}: Props) => {
const ref = React.useRef<SVGGElement>(null);

React.useEffect(() => {
Expand Down Expand Up @@ -68,20 +69,7 @@ export const AxisX = ({axis, width, height, scale}: Props) => {
svgElement.select('.tick').remove();
}

if (axis.title.text) {
const textY =
axis.title.height + parseInt(axis.labels.style.fontSize) + axis.labels.padding;

svgElement
.append('text')
.attr('class', b('title'))
.attr('text-anchor', 'middle')
.attr('x', width / 2)
.attr('y', textY)
.attr('font-size', axis.title.style.fontSize)
.text(axis.title.text);
}

// remove overlapping labels
let elementX = 0;
svgElement
.selectAll('.tick')
Expand All @@ -96,6 +84,33 @@ export const AxisX = ({axis, width, height, scale}: Props) => {
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();

if (textRect.right > chartWidth) {
const maxWidth = textRect.width - (textRect.right - chartWidth) * 2;
select(node).call(setEllipsisForOverflowText, maxWidth);
}
});

// add an axis header if necessary
if (axis.title.text) {
const textY =
axis.title.height + parseInt(axis.labels.style.fontSize) + axis.labels.padding;

svgElement
.append('text')
.attr('class', b('title'))
.attr('text-anchor', 'middle')
.attr('x', width / 2)
.attr('y', textY)
.attr('font-size', axis.title.style.fontSize)
.text(axis.title.text)
.call(setEllipsisForOverflowText, width);
}
}, [axis, width, height, scale]);

return <g ref={ref} />;
Expand Down
1 change: 1 addition & 0 deletions src/plugins/d3/renderer/components/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export const Chart = (props: Props) => {
width={boundsWidth}
height={boundsHeight}
scale={xScale}
chartWidth={width}
/>
</g>
</React.Fragment>
Expand Down
16 changes: 10 additions & 6 deletions src/plugins/d3/renderer/hooks/useAxisScales/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,17 @@ const createScales = (args: Args) => {
let xScale: ChartScale | undefined;
let yScale: ChartScale | undefined;

const xAxisMinPadding = boundsWidth * xAxis.maxPadding;
const xRange = [0, boundsWidth - xAxisMinPadding];

switch (xType) {
case 'linear': {
const domain = getDomainDataXBySeries(visibleSeries);
const range = [0, boundsWidth - boundsWidth * xAxis.maxPadding];

if (isNumericalArrayData(domain)) {
const [domainXMin, xMax] = extent(domain) as [number, number];
const xMinValue = typeof xMin === 'number' ? xMin : domainXMin;
xScale = scaleLinear().domain([xMinValue, xMax]).range(range).nice();
xScale = scaleLinear().domain([xMinValue, xMax]).range(xRange).nice();
}

break;
Expand All @@ -94,22 +96,24 @@ const createScales = (args: Args) => {
series: visibleSeries,
});
xScale = scaleBand().domain(filteredCategories).range([0, boundsWidth]);

if (xScale.step() / 2 < xAxisMinPadding) {
xScale.range(xRange);
}
}

break;
}
case 'datetime': {
const range = [0, boundsWidth - boundsWidth * xAxis.maxPadding];

if (xTimestamps) {
const [xMin, xMax] = extent(xTimestamps) as [number, number];
xScale = scaleUtc().domain([xMin, xMax]).range(range).nice();
xScale = scaleUtc().domain([xMin, xMax]).range(xRange).nice();
} else {
const domain = getDomainDataXBySeries(visibleSeries);

if (isNumericalArrayData(domain)) {
const [xMin, xMax] = extent(domain) as [number, number];
xScale = scaleUtc().domain([xMin, xMax]).range(range).nice();
xScale = scaleUtc().domain([xMin, xMax]).range(xRange).nice();
}
}

Expand Down
18 changes: 4 additions & 14 deletions src/plugins/d3/renderer/hooks/useChartOptions/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,20 +124,10 @@ const getMarginLeft = (args: {
return marginLeft;
};

const getMarginRight = (args: {
chart: ChartKitWidgetData['chart'];
hasAxisRelatedSeries: boolean;
series: ChartKitWidgetData['series'];
preparedXAxis: PreparedAxis;
}) => {
const {chart, hasAxisRelatedSeries, series, preparedXAxis} = args;
let marginRight = get(chart, 'margin.right', 0);

if (hasAxisRelatedSeries) {
marginRight += getAxisLabelMaxWidth({axis: preparedXAxis, series: series.data}) / 2;
}
const getMarginRight = (args: {chart: ChartKitWidgetData['chart']}) => {
const {chart} = args;

return marginRight;
return get(chart, 'margin.right', 0);
};

export const getPreparedChart = (args: {
Expand All @@ -158,7 +148,7 @@ export const getPreparedChart = (args: {
preparedXAxis,
});
const marginLeft = getMarginLeft({chart, hasAxisRelatedSeries, series, preparedY1Axis});
const marginRight = getMarginRight({chart, hasAxisRelatedSeries, series, preparedXAxis});
const marginRight = getMarginRight({chart});

return {
margin: {
Expand Down
1 change: 1 addition & 0 deletions src/plugins/d3/renderer/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {FormatNumberOptions} from '../../../shared';
import {DEFAULT_AXIS_LABEL_FONT_SIZE} from '../constants';

export * from './math';
export * from './text';

const CHARTS_WITHOUT_AXIS: ChartKitWidgetSeries['type'][] = ['pie'];

Expand Down
17 changes: 17 additions & 0 deletions src/plugins/d3/renderer/utils/text.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {Selection} from 'd3-selection';

export function setEllipsisForOverflowText(
selection: Selection<SVGTextElement, any, null, undefined>,
maxWidth: number,
) {
let text = selection.text();
selection.text(null).attr('text-anchor', 'left').append('title').text(text);
const tSpan = selection.append('tspan').text(text);

let textLength = tSpan.node()?.getComputedTextLength() || 0;
while (textLength > maxWidth && text.length > 1) {
text = text.slice(0, -1);
tSpan.text(text + '…');
textLength = tSpan.node()?.getComputedTextLength() || 0;
}
}