Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 4 additions & 1 deletion semcore/d3-chart/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ CHANGELOG.md standards are inspired by [keepachangelog.com](https://keepachangel

## [16.2.1] - 2025-10-17

### Added

- New `multiline` property for `XAxis.Ticks/YAxis.Ticks` and `multilineXTicks/multilineYTicks` for `Chart`.

### Fixed

- Chart content remains visible after unchecking single legend item in Bar, Horizontal Bar, Histogram, and Stacked Horizontal Bar charts.


## [16.2.0] - 2025-10-03

### Changed
Expand Down
78 changes: 75 additions & 3 deletions semcore/d3-chart/src/Axis.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, sstyled } from '@semcore/core';
import React from 'react';
import React, { useState, useEffect } from 'react';

import createElement from './createElement';
import style from './style/axis.shadow.css';
Expand Down Expand Up @@ -186,6 +186,51 @@ function renderValue(value) {
return value;
}

function splitTextByWidth(root, text, maxWidth) {
if (!text || !maxWidth || maxWidth <= 0) return [];

const words = text.split(/\s+/).filter((word) => word.length > 0);
if (words.length === 0) return [];

const lines = [];
let currentLine = words[0];

for (let i = 1; i < words.length; i++) {
const testLine = `${currentLine} ${words[i]}`;
const testWidth = measureTextWidth(root, testLine);

if (testWidth <= maxWidth) {
currentLine = testLine;
} else {
lines.push(currentLine);
currentLine = words[i];

if (measureTextWidth(root, currentLine) > maxWidth) {
lines.push(currentLine);
currentLine = '';
}
}
}

if (currentLine) {
lines.push(currentLine);
}

return lines;
}

function measureTextWidth(rootRef, text, fontSize = 12) {
const textEl = document.createElementNS('http://www.w3.org/2000/svg', 'text');
textEl.setAttribute('font-size', fontSize);
textEl.setAttribute('visibility', 'hidden');
textEl.textContent = text;

rootRef.appendChild(textEl);
const width = textEl.getComputedTextLength();
rootRef.removeChild(textEl);
return width;
}

class AxisRoot extends Component {
static displayName = 'Axis';

Expand Down Expand Up @@ -246,8 +291,31 @@ function Ticks(props) {
dataHintsHandler,
children,
childrenPosition = 'inside',
rootRef,
multiline,
} = props;

const [ticksState, setTicksState] = useState([]);

useEffect(() => {
const tickBandwidth = scale[indexScale]?.bandwidth?.();

const ticksWithLines = ticks.map((tick) => {
let lines = [];

if (typeof tick === 'string' && multiline) {
lines = splitTextByWidth(rootRef.current, tick, tickBandwidth);
}

return {
tick,
lines,
};
});

setTicksState(ticksWithLines);
}, [ticks, multiline]);

const pos = MAP_POSITION_TICK[position] ?? MAP_POSITION_TICK[MAP_INDEX_SCALE_SYMBOL[indexScale]];
const positionClass = MAP_POSITION_TICK[position] ? position : `custom_${indexScale}`;

Expand All @@ -263,7 +331,7 @@ function Ticks(props) {
}
}

return ticks.map((value, i) => {
return ticksState.map(({ tick: value, lines }, i) => {
const displayValue = typeof children === 'function' ? undefined : renderValue(value);

return sstyled(styles)(
Expand All @@ -279,7 +347,11 @@ function Ticks(props) {
hide={hide}
{...pos(scale, value, position)}
>
{displayValue}
{ lines.length > 1
? lines.map((line, lineIndex) => (
<tspan key={line} {...pos(scale, value, position)} dy={lineIndex * 15}>{line}</tspan>
))
: displayValue}
</STick>,
);
});
Expand Down
20 changes: 14 additions & 6 deletions semcore/d3-chart/src/component/Chart/AbstractChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,16 @@ export abstract class AbstractChart<
}

protected renderAxis(): React.ReactNode {
const { invertAxis, showXAxis, showYAxis, data, axisXValueFormatter, axisYValueFormatter } =
this.asProps;
const {
invertAxis,
showXAxis,
showYAxis,
data,
axisXValueFormatter,
axisYValueFormatter,
multilineXTicks,
multilineYTicks,
} = this.asProps;

if (!Array.isArray(data)) {
return null;
Expand All @@ -424,10 +432,10 @@ export abstract class AbstractChart<
<YAxis>
{yTicks
? (
<YAxis.Ticks ticks={yTicks}>{childrenY}</YAxis.Ticks>
<YAxis.Ticks multiline={multilineYTicks} ticks={yTicks}>{childrenY}</YAxis.Ticks>
)
: (
<YAxis.Ticks>{childrenY}</YAxis.Ticks>
<YAxis.Ticks multiline={multilineYTicks}>{childrenY}</YAxis.Ticks>
)}
{invertAxis !== true && (yTicks ? <YAxis.Grid ticks={yTicks} /> : <YAxis.Grid />)}
</YAxis>
Expand All @@ -437,10 +445,10 @@ export abstract class AbstractChart<
<XAxis>
{xTicks
? (
<XAxis.Ticks ticks={xTicks}>{childrenX}</XAxis.Ticks>
<XAxis.Ticks multiline={multilineXTicks} ticks={xTicks}>{childrenX}</XAxis.Ticks>
)
: (
<XAxis.Ticks>{childrenX}</XAxis.Ticks>
<XAxis.Ticks multiline={multilineXTicks}>{childrenX}</XAxis.Ticks>
)}
{invertAxis === true && (xTicks ? <XAxis.Grid ticks={xTicks} /> : <XAxis.Grid />)}
</XAxis>
Expand Down
4 changes: 4 additions & 0 deletions semcore/d3-chart/src/component/Chart/AbstractChart.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ export type BaseChartProps<T extends ListData | ObjectData> = FlexProps &
* Count of ticks for Y axis
*/
yTicksCount?: number;
/** Enables multiline tick labels for X axis. */
multilineXTicks?: boolean;
/** Enables multiline tick labels for Y axis. */
multilineYTicks?: boolean;
/**
* Group key for all array-based charts (for get keys of items for legend except that group key)
*/
Expand Down
2 changes: 2 additions & 0 deletions semcore/d3-chart/src/types/Axis.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export type AxisTicksProps = Context & {
hide?: boolean;
/** Values for axis ticks */
ticks?: any[];
/** Enables multiline tick labels. */
multiline?: boolean;
};

/** @deprecated */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const Demo = () => {
const data = Array(5)
.fill({})
.map((d, i) => ({
category: `Category ${i}`,
category: `Google AI Mode ${i} Tops`,
bar: Math.random() * 10,
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const Demo = () => {
const data = Array(5)
.fill({})
.map((d, i) => ({
category: `Category ${i}`,
category: `Google AI Mode ${i}`,
bar1: Math.random() * 10,
bar2: Math.random() * 10,
}));
Expand Down
Loading