1
1
import { Document } from "@/lib/@dsvgui" ;
2
- import { getTextWidth } from "@/lib/@dsvgui/utils/index" ;
2
+ import { getTextWidth , hexToRgb } from "@/lib/@dsvgui/utils/index" ;
3
3
4
4
type ILine = {
5
- title : string ;
6
- subtitle : string ;
7
- total : string ;
5
+ leftTitle : string ;
6
+ leftSubtitle : string ;
7
+ rightTitle : string ;
8
+ rightSubtitle : string ;
8
9
points : Array < number > ;
10
+ period ?: "month" | "weekday" | "day" | null ;
11
+ lineColor : string ;
9
12
} ;
10
13
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
+ } ) => {
12
23
const documentPadding = 40 ;
24
+ const today = Date . now ( ) ;
25
+ const pointGap = 30 ;
13
26
14
27
function createDynamicSvgPath ( {
15
28
lineHeight,
@@ -21,45 +34,88 @@ export const Line: React.FC<ILine> = ({ title, subtitle, total, points }) => {
21
34
ratio : number ;
22
35
} ) : string {
23
36
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 ;
26
39
let path =
27
40
"M " +
28
- x +
41
+ ( x + xGap ) +
29
42
" " +
30
- ( lineHeight +
31
- documentPadding / 2 -
32
- 5 -
43
+ ( lineHeight -
44
+ ( period ? 7 : - 5 ) -
33
45
( points [ 0 ] / maxValue ) * lineHeight * ratio ) ;
34
- for ( let i = 0 ; i <= points . length ; i ++ ) {
46
+
47
+ for ( const point of points ) {
35
48
x += xGap ;
36
49
path +=
37
50
" L " +
38
51
x +
39
52
" " +
40
- ( lineHeight +
41
- documentPadding / 2 -
42
- 5 -
43
- ( points [ i ] / maxValue ) * lineHeight * ratio ) ;
53
+ ( lineHeight -
54
+ ( period ? 7 : - 5 ) -
55
+ ( point / maxValue ) * lineHeight * ratio ) ;
44
56
}
45
57
46
58
return path ;
47
59
}
48
60
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 ) ;
51
67
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 ) ;
53
77
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 ;
57
84
const pathValue = createDynamicSvgPath ( {
58
85
lineHeight : height ,
59
86
lineWidth : width ,
60
- ratio : 0.7 ,
87
+ ratio : 0.6 ,
61
88
} ) ;
62
89
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
+
63
119
return (
64
120
< Document w = { width } h = { height } padding = { documentPadding } >
65
121
< path
@@ -69,58 +125,93 @@ export const Line: React.FC<ILine> = ({ title, subtitle, total, points }) => {
69
125
strokeLinecap = "round"
70
126
strokeLinejoin = "round"
71
127
/>
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 }
104
146
>
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
+ ) }
108
177
< defs >
178
+ < style > { `
179
+ .clabel{ fill: #999; font-size: 8px; }
180
+ .lineText{ fill: ${ lineColor } !important; }
181
+ ` } </ style >
109
182
< linearGradient
183
+ x1 = "0"
184
+ y1 = { height }
185
+ x2 = { width }
186
+ y2 = { height }
110
187
id = "paint0_linear_135_79"
111
- x1 = "317"
112
- y1 = "38.6358"
113
- x2 = "29.4084"
114
- y2 = "155.088"
115
188
gradientUnits = "userSpaceOnUse"
116
189
>
117
190
< 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
+ />
124
215
< stop offset = "1" stopColor = "#ffffff" />
125
216
</ linearGradient >
126
217
</ defs >
0 commit comments