Skip to content

Commit 6148bf6

Browse files
committed
style(#178): extended @dsvgui/line for labels and color
- Added line color - Adjusted width and height according to the content - Added period labels
1 parent 8f82217 commit 6148bf6

File tree

4 files changed

+194
-84
lines changed

4 files changed

+194
-84
lines changed

lib/@dsvgui/components/calendar/index.stories.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default meta;
2323

2424
type Story = StoryObj<typeof Calendar>;
2525

26-
export const produceFakeDates = () => {
26+
const produceFakeDates = () => {
2727
const today = Date.now();
2828
const fakeDates: { [key: string]: number } = {};
2929
for (let i = 0; i < 365; i++) {
@@ -33,7 +33,6 @@ export const produceFakeDates = () => {
3333
}
3434
return fakeDates;
3535
};
36-
3736
const dates = produceFakeDates();
3837

3938
export const Regular: Story = {

lib/@dsvgui/components/line/index.stories.tsx

+24-4
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,36 @@ import { Line } from "./index";
44
const meta: Meta<typeof Line> = {
55
title: "Line",
66
component: Line,
7+
argTypes: {
8+
leftTitle: { control: "text" },
9+
leftSubtitle: { control: "text" },
10+
rightTitle: { control: "text" },
11+
rightSubtitle: { control: "text" },
12+
period: { control: "radio", options: ["month", "weekday", "day", null] },
13+
points: { control: "array" },
14+
lineColor: { control: "color" },
15+
},
716
};
817
export default meta;
918

1019
type Story = StoryObj<typeof Line>;
1120

21+
const randomPoints = (length: number) => {
22+
const points = [];
23+
for (let i = 0; i < length; i++) {
24+
points.push(Math.round(Math.random() * 50));
25+
}
26+
return points;
27+
};
28+
1229
export const Base: Story = {
1330
args: {
14-
title: "Line",
15-
subtitle: "Last 30 Days",
16-
total: "124 hrs 48 mins",
17-
points: [32, 56, 9, 27, 22, 55, 2],
31+
leftTitle: "Line",
32+
leftSubtitle: "Last 30 Days",
33+
rightTitle: "124 hrs 48 mins",
34+
rightSubtitle: "since last week",
35+
period: "month",
36+
points: randomPoints(3),
37+
lineColor: "#000000",
1838
},
1939
};

lib/@dsvgui/components/line/index.tsx

+159-68
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
import { Document } from "@/lib/@dsvgui";
2-
import { getTextWidth } from "@/lib/@dsvgui/utils/index";
2+
import { getTextWidth, hexToRgb } from "@/lib/@dsvgui/utils/index";
33

44
type ILine = {
5-
title: string;
6-
subtitle: string;
7-
total: string;
5+
leftTitle: string;
6+
leftSubtitle: string;
7+
rightTitle: string;
8+
rightSubtitle: string;
89
points: Array<number>;
10+
period?: "month" | "weekday" | "day" | null;
11+
lineColor: string;
912
};
1013

11-
export const Line: React.FC<ILine> = ({ title, subtitle, total, points }) => {
14+
export const Line: React.FC<ILine> = ({
15+
leftTitle,
16+
leftSubtitle,
17+
rightTitle,
18+
rightSubtitle,
19+
points,
20+
period,
21+
lineColor = "#000000",
22+
}) => {
1223
const documentPadding = 40;
24+
const today = Date.now();
25+
const pointGap = 30;
1326

1427
function createDynamicSvgPath({
1528
lineHeight,
@@ -21,45 +34,88 @@ export const Line: React.FC<ILine> = ({ title, subtitle, total, points }) => {
2134
ratio: number;
2235
}): string {
2336
const maxValue = Math.max(...points);
24-
const xGap = lineWidth / points.length;
25-
let x = 0;
37+
const xGap = lineWidth / (points.length - 1);
38+
let x = -xGap;
2639
let path =
2740
"M " +
28-
x +
41+
(x + xGap) +
2942
" " +
30-
(lineHeight +
31-
documentPadding / 2 -
32-
5 -
43+
(lineHeight -
44+
(period ? 7 : -5) -
3345
(points[0] / maxValue) * lineHeight * ratio);
34-
for (let i = 0; i <= points.length; i++) {
46+
47+
for (const point of points) {
3548
x += xGap;
3649
path +=
3750
" L " +
3851
x +
3952
" " +
40-
(lineHeight +
41-
documentPadding / 2 -
42-
5 -
43-
(points[i] / maxValue) * lineHeight * ratio);
53+
(lineHeight -
54+
(period ? 7 : -5) -
55+
(point / maxValue) * lineHeight * ratio);
4456
}
4557

4658
return path;
4759
}
4860

49-
const titleWidth = getTextWidth(title, { fontSize: 16 });
50-
const subtitleWidth = getTextWidth(subtitle, { fontSize: 16 });
61+
const leftTitleWidth = getTextWidth(leftTitle, { fontSize: 22, ratio: 0.58 });
62+
const leftSubtitleWidth = getTextWidth(leftSubtitle, {
63+
fontSize: 16,
64+
ratio: 0.57,
65+
});
66+
const leftTitlesWidth = Math.max(leftTitleWidth, leftSubtitleWidth);
5167

52-
const titlesWidth = Math.max(titleWidth, subtitleWidth);
68+
const rightTitleWidth = getTextWidth(rightTitle, {
69+
fontSize: 22,
70+
ratio: 0.58,
71+
});
72+
const rightSubtitleWidth = getTextWidth(rightSubtitle, {
73+
fontSize: 16,
74+
ratio: 0.527,
75+
});
76+
const rightTitlesWidth = Math.max(rightTitleWidth, rightSubtitleWidth);
5377

54-
const totalTextWidth = titlesWidth + getTextWidth(total, { fontSize: 24 });
55-
const height = 90;
56-
const width = totalTextWidth + 100;
78+
const totalTextWidth = leftTitlesWidth + rightTitlesWidth + 50;
79+
const height = 140;
80+
const totalPointWidth =
81+
points.length * pointGap < 100 ? 100 : points.length * pointGap;
82+
const width =
83+
totalPointWidth > totalTextWidth ? totalPointWidth : totalTextWidth;
5784
const pathValue = createDynamicSvgPath({
5885
lineHeight: height,
5986
lineWidth: width,
60-
ratio: 0.7,
87+
ratio: 0.6,
6188
});
6289

90+
let labels: Array<JSX.Element> = [];
91+
if (period) {
92+
labels = points.map((_, i) => {
93+
const dateGap = {
94+
month: 30,
95+
weekday: 1,
96+
day: 1,
97+
};
98+
const nthDay = points.length - i - 1;
99+
const date = new Date(
100+
today - nthDay * dateGap[period] * 24 * 60 * 60 * 1000
101+
);
102+
103+
let dateOptions = {};
104+
if (period === "month") dateOptions = { month: "short" };
105+
if (period === "weekday") dateOptions = { weekday: "short" };
106+
if (period === "day") dateOptions = { day: "numeric" };
107+
108+
const periodLabel = date.toLocaleDateString("en-US", dateOptions);
109+
const xGap = width / (points.length - 1);
110+
const x = i * xGap - getTextWidth(periodLabel, { fontSize: 8 }) / 2;
111+
return (
112+
<text key={i} className="clabel">
113+
<tspan x={x}>{periodLabel}</tspan>
114+
</text>
115+
);
116+
});
117+
}
118+
63119
return (
64120
<Document w={width} h={height} padding={documentPadding}>
65121
<path
@@ -69,58 +125,93 @@ export const Line: React.FC<ILine> = ({ title, subtitle, total, points }) => {
69125
strokeLinecap="round"
70126
strokeLinejoin="round"
71127
/>
72-
<text
73-
xmlSpace="preserve"
74-
fontFamily="Roboto"
75-
className="title"
76-
fontWeight="bolder"
77-
letterSpacing="0.5px"
78-
>
79-
<tspan x="0" y="15">
80-
{title}
81-
</tspan>
82-
</text>
83-
<text
84-
fill="#555555"
85-
xmlSpace="preserve"
86-
fontFamily="Roboto"
87-
className="subtitle"
88-
fontWeight={500}
89-
>
90-
<tspan x="0" y="33">
91-
{subtitle}
92-
</tspan>
93-
</text>
94-
<text
95-
fill="black"
96-
xmlSpace="preserve"
97-
fontFamily="Roboto"
98-
className="title"
99-
fontWeight="bolder"
100-
>
101-
<tspan
102-
x={width - getTextWidth(total, { fontSize: 22, ratio: 0.52 })}
103-
y="15"
128+
<g id="left">
129+
<text
130+
xmlSpace="preserve"
131+
fontFamily="Roboto"
132+
className="title"
133+
fontWeight="bolder"
134+
letterSpacing="0.5px"
135+
>
136+
<tspan x="0" y="15">
137+
{leftTitle}
138+
</tspan>
139+
</text>
140+
<text
141+
fill="#555555"
142+
xmlSpace="preserve"
143+
fontFamily="Roboto"
144+
className="subtitle"
145+
fontWeight={500}
104146
>
105-
{total}
106-
</tspan>
107-
</text>
147+
<tspan x="0" y="33">
148+
{leftSubtitle}
149+
</tspan>
150+
</text>
151+
</g>
152+
<g id="right">
153+
<text className="title lineText" fontWeight="bolder">
154+
<tspan
155+
x={width - getTextWidth(rightTitle, { fontSize: 22, ratio: 0.5 })}
156+
y="15"
157+
>
158+
{rightTitle}
159+
</tspan>
160+
</text>
161+
<text className="subtitle lineText" fontWeight={300}>
162+
<tspan
163+
x={
164+
width - getTextWidth(rightSubtitle, { fontSize: 16, ratio: 0.46 })
165+
}
166+
y="33"
167+
>
168+
{rightSubtitle}
169+
</tspan>
170+
</text>
171+
</g>
172+
{period && (
173+
<g id="Labels" transform={`translate(0 ${height + 7})`}>
174+
{labels}
175+
</g>
176+
)}
108177
<defs>
178+
<style>{`
179+
.clabel{ fill: #999; font-size: 8px; }
180+
.lineText{ fill: ${lineColor} !important; }
181+
`}</style>
109182
<linearGradient
183+
x1="0"
184+
y1={height}
185+
x2={width}
186+
y2={height}
110187
id="paint0_linear_135_79"
111-
x1="317"
112-
y1="38.6358"
113-
x2="29.4084"
114-
y2="155.088"
115188
gradientUnits="userSpaceOnUse"
116189
>
117190
<stop offset="0" stopColor="#ffffff" />
118-
<stop offset="0.141455" stopColor="#FF12E7" />
119-
<stop offset="0.201455" stopColor="#FF5ecc" />
120-
<stop offset="0.363128" stopColor="#FF9eeE" />
121-
<stop offset="0.581288" stopColor="#FF0AAC" />
122-
<stop offset="0.729526" stopColor="#FF046E" />
123-
<stop offset="0.943128" stopColor="#FF9eeE" />
191+
<stop
192+
offset="0.141455"
193+
stopColor={`rgba(${hexToRgb(lineColor, 0.7).join(", ")})`}
194+
/>
195+
<stop
196+
offset="0.201455"
197+
stopColor={`rgba(${hexToRgb(lineColor, 0.3).join(", ")})`}
198+
/>
199+
<stop
200+
offset="0.363128"
201+
stopColor={`rgba(${hexToRgb(lineColor, 0.5).join(", ")})`}
202+
/>
203+
<stop
204+
offset="0.581288"
205+
stopColor={`rgba(${hexToRgb(lineColor, 0.7).join(", ")})`}
206+
/>
207+
<stop
208+
offset="0.729526"
209+
stopColor={`rgba(${hexToRgb(lineColor, 0.8).join(", ")})`}
210+
/>
211+
<stop
212+
offset="0.923128"
213+
stopColor={`rgba(${hexToRgb(lineColor, 0.7).join(", ")})`}
214+
/>
124215
<stop offset="1" stopColor="#ffffff" />
125216
</linearGradient>
126217
</defs>

lib/@dsvgui/utils/index.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ export const convertDateToReadableFormat: IConvertDateToReadbleFormat = (
8383
return `${month} ${day} '${year}`;
8484
};
8585

86+
export function hexToRgb(hex: string, alpha = 1) {
87+
const bigint = parseInt(hex.slice(1), 16);
88+
const r = (bigint >> 16) & 255;
89+
const g = (bigint >> 8) & 255;
90+
const b = bigint & 255;
91+
92+
return [r, g, b, alpha];
93+
}
94+
8695
export function stringToColorCode(str: string) {
8796
let hash = 0;
8897
for (let i = 0; i < str.length; i++) {
@@ -94,18 +103,9 @@ export function stringToColorCode(str: string) {
94103
}
95104

96105
export function generateColorVariations(inputColor: string, step: number = 5) {
97-
function hexToRgb(hex: string) {
98-
const bigint = parseInt(hex.slice(1), 16);
99-
const r = (bigint >> 16) & 255;
100-
const g = (bigint >> 8) & 255;
101-
const b = bigint & 255;
102-
103-
return [r, g, b];
104-
}
105-
106106
const variations: Array<string> = [];
107107
for (let i = 0; i <= step; i++) {
108-
const rgb = hexToRgb(inputColor);
108+
const rgb = hexToRgb(inputColor, 1);
109109
const alpha = i * (1 / step);
110110
variations.push(`rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${alpha || 0.05})`);
111111
}

0 commit comments

Comments
 (0)