Skip to content
Open
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
147 changes: 56 additions & 91 deletions semcore/d3-chart/src/Axis.jsx
Original file line number Diff line number Diff line change
@@ -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');

Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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';

Expand Down Expand Up @@ -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}`;
Expand All @@ -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)(
<STick
aria-hidden
key={i}
render='text'
childrenPosition={childrenPosition}
__excludeProps={['data', 'scale', 'format', 'value']}
value={value}
index={i}
render='foreignObject'
childrenPosition={childrenPosition}
width={tickWidth}
height={tickHeight}
position={positionClass}
hide={hide}
multiline={multiline}
primaryText={props.primaryText}
{...pos(scale, value, position)}
primaryText={primaryText}
value={value}
__excludeProps={['data', 'scale', 'format', 'value']}
{...pos(scale, value, position, { width: tickWidth, height: tickHeight })}
>
{ lines.length > 1
? lines.map((line, lineIndex) => (
<tspan key={line} {...pos(scale, value, position)} dy={lineIndex * 15}>{line}</tspan>
))
: displayValue}
{displayValue}
</STick>,
);
});
Expand Down
6 changes: 4 additions & 2 deletions semcore/d3-chart/src/component/Chart/AbstractChart.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,10 @@ export type BaseChartProps<T extends ListData | ObjectData> = 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)
Expand All @@ -137,8 +139,8 @@ export type BaseChartProps<T extends ListData | ObjectData> = 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
*/
Expand Down
91 changes: 33 additions & 58 deletions semcore/d3-chart/src/style/axis.shadow.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions semcore/d3-chart/src/style/radar.shadow.css
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
5 changes: 3 additions & 2 deletions semcore/d3-chart/src/types/Axis.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions stories/components/d3-chart/docs/bar-chart.docs.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -51,3 +52,7 @@ export const TrendLine: StoryObj = {
export const LegendAndPatternFill: StoryObj = {
render: LegendAndPatternFillExample,
};

export const Links: StoryObj = {
render: LinksExample,
};
Loading
Loading