diff --git a/semcore/d3-chart/src/Axis.jsx b/semcore/d3-chart/src/Axis.jsx
index 34d235ae12..249313143b 100644
--- a/semcore/d3-chart/src/Axis.jsx
+++ b/semcore/d3-chart/src/Axis.jsx
@@ -1,10 +1,13 @@
import { Component, sstyled } from '@semcore/core';
-import React, { useState, useEffect } from 'react';
+import React from 'react';
import createElement from './createElement';
import style from './style/axis.shadow.css';
import { scaleOfBandwidth } from './utils';
+const TICK_MARGIN_X = 16;
+const TICK_MARGIN_Y = 8;
+
const CUSTOM_0 = Symbol('custom_0');
const CUSTOM_1 = Symbol('custom_1');
@@ -82,32 +85,40 @@ const MAP_POSITION_AXIS = {
};
const MAP_POSITION_TICK = {
- top: ([xScale, yScale], value) => {
+ top: ([xScale, yScale], value, _, { width, height }) => {
const yRange = yScale.range();
+ const [translateX, translateY] = [-width / 2, -height - TICK_MARGIN_Y];
+
return {
- x: scaleOfBandwidth(xScale, value),
- y: yRange[1],
+ x: scaleOfBandwidth(xScale, value) + translateX,
+ y: yRange[1] + translateY,
};
},
- bottom: ([xScale, yScale], value) => {
+ bottom: ([xScale, yScale], value, _, { width }) => {
const yRange = yScale.range();
+ const [translateX, translateY] = [-width / 2, TICK_MARGIN_Y];
+
return {
- x: scaleOfBandwidth(xScale, value),
- y: yRange[0],
+ x: scaleOfBandwidth(xScale, value) + translateX,
+ y: yRange[0] + translateY,
};
},
- right: ([xScale, yScale], value) => {
+ right: ([xScale, yScale], value, _, { height }) => {
const xRange = xScale.range();
+ const [translateX, translateY] = [TICK_MARGIN_X, -height / 2];
+
return {
- x: xRange[1],
- y: scaleOfBandwidth(yScale, value),
+ x: xRange[1] + translateX,
+ y: scaleOfBandwidth(yScale, value) + translateY,
};
},
- left: ([xScale, yScale], value) => {
+ left: ([xScale, yScale], value, _, { width, height }) => {
const xRange = xScale.range();
+ const [translateX, translateY] = [-width - TICK_MARGIN_X, -height / 2];
+
return {
- x: xRange[0],
- y: scaleOfBandwidth(yScale, value),
+ x: xRange[0] + translateX,
+ y: scaleOfBandwidth(yScale, value) + translateY,
};
},
[CUSTOM_0]: ([xScale, yScale], value, pos) => {
@@ -179,61 +190,16 @@ const MAP_POSITION_TITlE = {
},
};
-function renderValue(value) {
+function renderValue(value, locale = 'en') {
if (value instanceof Date) {
- return value.toLocaleDateString();
+ return new Intl.DateTimeFormat(locale, {
+ day: 'numeric',
+ month: 'short',
+ }).format(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]}`.trim();
- const testWidth = measureTextWidth(root, testLine);
-
- if (testWidth <= maxWidth) {
- currentLine = testLine;
- } else {
- if (currentLine) {
- 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';
@@ -294,22 +260,25 @@ function Ticks(props) {
dataHintsHandler,
children,
childrenPosition = 'inside',
- rootRef,
- multiline,
+ locale,
+ primaryText,
+ size,
} = props;
- const [rootRefElement, setRootRefElement] = useState(null);
- useEffect(() => {
- if (rootRef.current) setRootRefElement(rootRef.current);
- }, []);
+ const isXScale = indexScale === 0;
+
+ const [_, plotHeight] = size;
+ const currentScale = scale[indexScale];
+ const secondaryScale = scale[isXScale ? 1 : 0];
+
+ const [startPointCurrentScale, endPointCurrentScale] = currentScale.range();
+ const [startPointSecondaryScale, endPointSecondaryScale] = secondaryScale.range();
- const tickBandwidth = scale[indexScale]?.bandwidth?.();
- const ticksWithLines = ticks.map((tick) => ({
- tick,
- lines: typeof tick === 'string' && multiline && rootRefElement
- ? splitTextByWidth(rootRefElement, tick, tickBandwidth)
- : [],
- }));
+ const tickStepSize = currentScale?.step?.() ?? Math.abs(startPointCurrentScale - endPointCurrentScale) / ticks.length;
+
+ const [tickWidth, tickHeight] = isXScale
+ ? [tickStepSize, Math.max(Math.abs(plotHeight - startPointSecondaryScale), endPointSecondaryScale)]
+ : [startPointSecondaryScale, tickStepSize];
const pos = MAP_POSITION_TICK[position] ?? MAP_POSITION_TICK[MAP_INDEX_SCALE_SYMBOL[indexScale]];
const positionClass = MAP_POSITION_TICK[position] ? position : `custom_${indexScale}`;
@@ -326,29 +295,25 @@ function Ticks(props) {
}
}
- return ticksWithLines.map(({ tick: value, lines }, i) => {
- const displayValue = typeof children === 'function' ? undefined : renderValue(value);
+ return ticks.map((value, i) => {
+ const displayValue = typeof children === 'function' ? undefined : renderValue(value, locale);
return sstyled(styles)(
- { lines.length > 1
- ? lines.map((line, lineIndex) => (
- {line}
- ))
- : displayValue}
+ {displayValue}
,
);
});
diff --git a/semcore/d3-chart/src/component/Chart/AbstractChart.type.ts b/semcore/d3-chart/src/component/Chart/AbstractChart.type.ts
index edcc2fef8e..824060c594 100644
--- a/semcore/d3-chart/src/component/Chart/AbstractChart.type.ts
+++ b/semcore/d3-chart/src/component/Chart/AbstractChart.type.ts
@@ -127,8 +127,10 @@ export type BaseChartProps = NSFlex.Props & {
*/
yTicksCount?: number;
/** Enables multiline tick labels for X axis, applicable only for band scales */
+ /** @deprecated has no effect since v.18 */
multilineXTicks?: boolean;
/** Enables multiline tick labels for Y axis, applicable only for band scales */
+ /** @deprecated has no effect since v.18 */
multilineYTicks?: boolean;
/**
* Group key for all array-based charts (for get keys of items for legend except that group key)
@@ -137,8 +139,8 @@ export type BaseChartProps = NSFlex.Props & {
/**
* function for format axis item text
*/
- axisXValueFormatter?: (value: unknown) => string;
- axisYValueFormatter?: (value: unknown) => string;
+ axisXValueFormatter?: (value: unknown) => React.ReactNode;
+ axisYValueFormatter?: (value: unknown) => React.ReactNode;
/**
* Function for format text for tooltip
*/
diff --git a/semcore/d3-chart/src/style/axis.shadow.css b/semcore/d3-chart/src/style/axis.shadow.css
index 44ba9bc553..8c88def800 100644
--- a/semcore/d3-chart/src/style/axis.shadow.css
+++ b/semcore/d3-chart/src/style/axis.shadow.css
@@ -8,19 +8,44 @@ SAxis[hide] {
STick {
font-size: var(--intergalactic-fs-100, 12px);
- fill: var(--intergalactic-chart-grid-text-label, oklch(0.097 0.023 156.5 / 0.468));
-}
-STick[primaryText] {
- fill: var(--intergalactic-text-primary, oklch(0.1 0.03 137 / 0.899));
-}
+ font-variant-numeric: tabular-nums;
+ line-height: var(--intergalactic-lh-100, 133%);
+ color: var(--intergalactic-text-secondary, oklch(0.097 0.023 156.5 / 0.468));
-STick[hide] {
- display: none;
+ &[position='bottom'] {
+ text-align: center;
+ }
+
+ &[position='left'] {
+ text-align: end;
+ align-content: center;
+ dominant-baseline: central;
+ }
+
+ &[position='top'] {
+ text-align: center;
+ align-content: end;
+ }
+
+ &[position='right'] {
+ align-content: center;
+ dominant-baseline: central;
+ }
+
+ &[hide] {
+ display: none;
+ }
+
+ &[primaryText] {
+ color: var(--intergalactic-text-primary, oklch(0.1 0.03 137 / 0.899));
+ }
}
SGrid {
fill: transparent;
- stroke: var(--intergalactic-border-secondary, oklch(0.176 0.033 175.7 / 0.07));
+ stroke: var(--intergalactic-border-primary, oklch(0.137 0.026 175.7 / 0.161));
+ stroke-dasharray: 0 4;
+ stroke-linecap: round;
}
STitle {
@@ -55,56 +80,6 @@ STitle[position='left'][verticalWritingMode] {
transform: none;
}
-STick[position='top'] {
- transform: translateY(-12px);
- text-anchor: middle;
-}
-
-STick[position='bottom'] {
- transform: translateY(12px);
- text-anchor: middle;
- dominant-baseline: hanging;
-
- tspan {
- dominant-baseline: hanging;
- }
-}
-
-STick[position='right'] {
- transform: translateX(16px);
- dominant-baseline: middle;
-
- tspan {
- dominant-baseline: middle;
- }
-
- &[multiline] {
- dominant-baseline: auto;
-
- tspan {
- dominant-baseline: auto;
- }
- }
-}
-
-STick[position='left'] {
- transform: translateX(-16px);
- text-anchor: end;
- dominant-baseline: middle;
-
- tspan {
- dominant-baseline: middle;
- }
-
- &[multiline] {
- dominant-baseline: auto;
-
- tspan {
- dominant-baseline: auto;
- }
- }
-}
-
STick[position='custom_0'] {
transform: translateY(12px);
text-anchor: middle;
diff --git a/semcore/d3-chart/src/style/radar.shadow.css b/semcore/d3-chart/src/style/radar.shadow.css
index 0f9c593b09..1f94076777 100644
--- a/semcore/d3-chart/src/style/radar.shadow.css
+++ b/semcore/d3-chart/src/style/radar.shadow.css
@@ -1,12 +1,12 @@
SAxis {
- stroke: var(--intergalactic-chart-grid-line, oklch(0.95 0.002 180));
+ stroke: var(--intergalactic-chart-grid-line, oklch(0.88 0.002 175.6));
stroke-width: 1;
fill: transparent;
}
SAxisLine,
SAxisTick {
- stroke: var(--intergalactic-chart-grid-line, oklch(0.95 0.002 180));
+ stroke: var(--intergalactic-chart-grid-line, oklch(0.88 0.002 175.6));
stroke-width: 1;
stroke-dasharray: 2;
fill: transparent;
diff --git a/semcore/d3-chart/src/types/Axis.d.ts b/semcore/d3-chart/src/types/Axis.d.ts
index 5fd52fd2b4..44906e02da 100644
--- a/semcore/d3-chart/src/types/Axis.d.ts
+++ b/semcore/d3-chart/src/types/Axis.d.ts
@@ -7,7 +7,7 @@ import type { IntergalacticD3Component } from './Plot';
export interface IXAxisProps extends XAxisProps, UnknownProperties {}
export type XAxisProps = Context & {
/** The position of the axis relative chart
- * @default 'button' */
+ * @default 'bottom' */
position?: 'top' | 'right' | 'bottom' | 'left' | number;
/** Element hide property
* @default false */
@@ -38,7 +38,8 @@ export type AxisTicksProps = Context & {
hide?: boolean;
/** Values for axis ticks */
ticks?: any[];
- /** Enables multiline tick labels, applicable only for band scales */
+ /** Enables multiline tick labels, applicable only for band scales */
+ /** @deprecated has no effect since v.18 */
multiline?: boolean;
/**
* Enable `--intergalactic-text-primary` color for ticks
diff --git a/stories/components/d3-chart/docs/bar-chart.docs.stories.tsx b/stories/components/d3-chart/docs/bar-chart.docs.stories.tsx
index 8ed49225e6..91c142ad5d 100644
--- a/stories/components/d3-chart/docs/bar-chart.docs.stories.tsx
+++ b/stories/components/d3-chart/docs/bar-chart.docs.stories.tsx
@@ -6,6 +6,7 @@ import BasicUsageExample from './examples/bar-chart/basic-usage';
import DateFormatExample from './examples/bar-chart/date-format';
import GroupedBarsExample from './examples/bar-chart/grouped-bars';
import LegendAndPatternFillExample from './examples/bar-chart/legend-and-pattern-fill';
+import LinksExample from './examples/bar-chart/links';
import NegativeValuesExample from './examples/bar-chart/negative-values';
import TooltipExample from './examples/bar-chart/tooltip';
import TrendLineExample from './examples/bar-chart/trend-line';
@@ -51,3 +52,7 @@ export const TrendLine: StoryObj = {
export const LegendAndPatternFill: StoryObj = {
render: LegendAndPatternFillExample,
};
+
+export const Links: StoryObj = {
+ render: LinksExample,
+};
diff --git a/stories/components/d3-chart/docs/examples/bar-chart/links.tsx b/stories/components/d3-chart/docs/examples/bar-chart/links.tsx
new file mode 100644
index 0000000000..6507f1c835
--- /dev/null
+++ b/stories/components/d3-chart/docs/examples/bar-chart/links.tsx
@@ -0,0 +1,33 @@
+import { Chart } from '@semcore/ui/d3-chart';
+import Link from '@semcore/ui/link';
+import React from 'react';
+
+import BarMockData from '../../../__mocks__/bar';
+
+const links: Record = {
+ 'Category 1': 'https://google.com',
+ 'Category 2': 'https://semrush.com',
+ 'Category 3': 'https://developer.semrush.com/intergalactic/',
+};
+
+const Demo = () => {
+ return (
+ {
+ if (typeof value === 'string') {
+ const href = links[value];
+ return href ? {value} : value;
+ }
+ }}
+ />
+ );
+};
+
+const data = BarMockData.Default;
+
+export default Demo;
diff --git a/tools/theme/src/theme.ts b/tools/theme/src/theme.ts
index 72da812a77..a8feb96157 100644
--- a/tools/theme/src/theme.ts
+++ b/tools/theme/src/theme.ts
@@ -677,7 +677,7 @@ export const theme: Theme = {
description: 'Border for distinguishing data sets and chart dots on the chart grid.',
},
chart_grid_line: {
- value: neutral.at(L_BORDER_SECONDARY),
+ value: neutral.at(L_BORDER_PRIMARY),
description: 'Grid and axis guide lines for charts.',
},
chart_grid_period_bg: {
diff --git a/website/docs/style/design-tokens/design-tokens.json b/website/docs/style/design-tokens/design-tokens.json
index df1e7d1053..73329bc40c 100644
--- a/website/docs/style/design-tokens/design-tokens.json
+++ b/website/docs/style/design-tokens/design-tokens.json
@@ -692,7 +692,7 @@
},
{
"name": "--intergalactic-chart-grid-line",
- "value": "oklch(0.95 0.002 180)",
+ "value": "oklch(0.88 0.002 175.6)",
"description": "Grid and axis guide lines for charts.",
"components": [
"d3-chart"