@easyv/charts 1.0.73 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,19 +6,23 @@ import React, {
6
6
  memo,
7
7
  ReactComponentElement,
8
8
  useContext,
9
+ CSSProperties,
10
+ MouseEventHandler,
9
11
  } from 'react';
10
12
  import { getTickCoord, getGridCoord } from '../utils';
11
13
  import { chartContext } from '../context';
12
14
  import { Line } from '../element';
13
-
15
+ const defaultEvent = () => {};
14
16
  const defaultAlign: Align = {
15
17
  textAnchor: 'middle',
16
18
  dominantBaseline: 'middle',
17
19
  };
18
20
  const defaultOrientation = 'bottom';
19
21
  const defaultTickSize = 6;
20
- const defaultFormatter: (d: string, { suffix }: { suffix: string }) => string =
21
- (d, { suffix }) => d + suffix;
22
+ const defaultFormatter: (
23
+ d: string,
24
+ { suffix }: { suffix: string }
25
+ ) => string = (d, { suffix }) => d + suffix;
22
26
 
23
27
  const getAxesPath: (
24
28
  orientation: Orientation,
@@ -145,80 +149,92 @@ type LabelType = {
145
149
  formatter: Function;
146
150
  tickSize: number;
147
151
  rotate: number;
148
- config: { show: boolean; translate: Translate; font: Font };
152
+ onClick?: MouseEventHandler;
153
+ config: {
154
+ show: boolean;
155
+ translate: Translate;
156
+ font: Font;
157
+ style: CSSProperties | Function;
158
+ };
149
159
  };
150
160
 
151
- const Label: (props: LabelType) => ReactComponentElement<ComponentType> | null =
152
- ({
153
- className,
154
- orientation = defaultOrientation,
155
- label,
156
- coordinate,
157
- formatter = defaultFormatter,
158
- tickSize,
159
- rotate = 0,
160
- config,
161
- config: {
162
- show,
163
- translate: { x: translateX, y: translateY },
164
- font: { fontFamily, fontSize, color, bold, italic, letterSpacing },
165
- },
166
- }) => {
167
- if (!show) return null;
168
- const _label = formatter(label, config);
169
- const { dominantBaseline, textAnchor, directionX, directionY } = getLayout(
170
- orientation,
171
- rotate
172
- );
173
- const isVertical = orientation == 'left' || orientation == 'right';
161
+ const Label: (
162
+ props: LabelType
163
+ ) => ReactComponentElement<ComponentType> | null = ({
164
+ className,
165
+ orientation = defaultOrientation,
166
+ label,
167
+ coordinate,
168
+ formatter = defaultFormatter,
169
+ tickSize,
170
+ rotate = 0,
171
+ config,
172
+ onClick = defaultEvent,
173
+ config: {
174
+ show,
175
+ style,
176
+ translate: { x: translateX, y: translateY },
177
+ font: { fontFamily, fontSize, color, bold, italic, letterSpacing },
178
+ },
179
+ }) => {
180
+ if (!show) return null;
181
+ const _label = formatter(label, config);
182
+ const { dominantBaseline, textAnchor, directionX, directionY } = getLayout(
183
+ orientation,
184
+ rotate
185
+ );
186
+ const isVertical = orientation == 'left' || orientation == 'right';
174
187
 
175
- const x =
176
- (isVertical ? tickSize * directionX : coordinate) +
177
- translateX * directionX;
178
- const y =
179
- (isVertical ? coordinate : tickSize * directionY) +
180
- translateY * directionY;
188
+ const x =
189
+ (isVertical ? tickSize * directionX : coordinate) + translateX * directionX;
190
+ const y =
191
+ (isVertical ? coordinate : tickSize * directionY) + translateY * directionY;
181
192
 
182
- const translateText = 'translate(' + x + ', ' + y + ')';
183
-
184
- return (
185
- <text
186
- className={className}
187
- style={{
188
- whiteSpace:"pre"
189
- }}
190
- dominantBaseline={dominantBaseline}
191
- textAnchor={textAnchor}
192
- fontFamily={fontFamily}
193
- fontSize={fontSize}
194
- fill={color}
195
- fontWeight={bold ? 'bold' : 'normal'}
196
- fontStyle={italic ? 'italic' : 'normal'}
197
- letterSpacing={letterSpacing}
198
- dx='0'
199
- dy='0'
200
- transform={
201
- Array.isArray(_label)
202
- ? rotate !== 0
203
- ? isVertical
204
- ? translateText + 'rotate(' + rotate + ')'
205
- : 'rotate(' + rotate + ', ' + x + ', ' + y + ')'
206
- : isVertical
207
- ? translateText
208
- : 'translate(' + translateX + ', ' + translateY + ')'
209
- : translateText + 'rotate(' + rotate + ')'
210
- }
211
- >
212
- {!!Array.isArray(_label)
213
- ? _label.map((item, index) => (
214
- <tspan key={index} x={x} dy='1em'>
215
- {item}
216
- </tspan>
217
- ))
218
- : _label}
219
- </text>
220
- );
221
- };
193
+ const translateText = 'translate(' + x + ', ' + y + ')';
194
+
195
+ const _style = style && (typeof style == 'object' ? style : style(_label));
196
+
197
+ return (
198
+ <text
199
+ className={className}
200
+ style={{
201
+ ..._style,
202
+ whiteSpace: 'pre',
203
+ }}
204
+ onClick={onClick}
205
+ data-data={JSON.stringify({ x: label })}
206
+ dominantBaseline={dominantBaseline}
207
+ textAnchor={textAnchor}
208
+ fontFamily={fontFamily}
209
+ fontSize={fontSize}
210
+ fill={color}
211
+ fontWeight={bold ? 'bold' : 'normal'}
212
+ fontStyle={italic ? 'italic' : 'normal'}
213
+ letterSpacing={letterSpacing}
214
+ dx='0'
215
+ dy='0'
216
+ transform={
217
+ Array.isArray(_label)
218
+ ? rotate !== 0
219
+ ? isVertical
220
+ ? translateText + 'rotate(' + rotate + ')'
221
+ : 'rotate(' + rotate + ', ' + x + ', ' + y + ')'
222
+ : isVertical
223
+ ? translateText
224
+ : 'translate(' + translateX + ', ' + translateY + ')'
225
+ : translateText + 'rotate(' + rotate + ')'
226
+ }
227
+ >
228
+ {!!Array.isArray(_label)
229
+ ? _label.map((item, index) => (
230
+ <tspan key={index} x={x} dy='1em'>
231
+ {item}
232
+ </tspan>
233
+ ))
234
+ : _label}
235
+ </text>
236
+ );
237
+ };
222
238
 
223
239
  export default memo(
224
240
  ({
@@ -228,6 +244,7 @@ export default memo(
228
244
  ticks,
229
245
  formatter,
230
246
  rotate,
247
+ triggerClick,
231
248
  config: { on, label, axisLine, tickLine, gridLine, unit },
232
249
  positions,
233
250
  }: any) => {
@@ -309,6 +326,7 @@ export default memo(
309
326
  tickSize={_tickSize}
310
327
  formatter={formatter}
311
328
  rotate={rotate}
329
+ onClick={triggerClick}
312
330
  />
313
331
  )}
314
332
  {gridLine && (
@@ -140,12 +140,14 @@ export default memo(
140
140
  style={{
141
141
  width: '100%',
142
142
  height: '100%',
143
- opacity:opacity,
143
+ /** Safari Bug **/
144
+ position: 'fixed',
145
+ opacity: fillType == 'pattern' ? opacity : 1,
144
146
  background:
145
147
  fillType == 'pattern'
146
148
  ? `50% 50% / ${size.width}px ${size.height}px repeat ` +
147
149
  'url(' +
148
- url +
150
+ url +
149
151
  ')'
150
152
  : getBandBackground(
151
153
  pattern,
@@ -132,7 +132,9 @@ const Chart = memo(
132
132
  )}
133
133
  {[...axes.values()].map((item, index) => {
134
134
  const config = item.axisType == 'x' ? axisX : item;
135
- return <Axis {...config} key={index} />;
135
+ return (
136
+ <Axis triggerClick={onInteraction} {...config} key={index} />
137
+ );
136
138
  })}
137
139
 
138
140
  {showTooltip && <Indicator {...indicator} {...indicatorAttr} />}
@@ -173,8 +175,12 @@ const Chart = memo(
173
175
  {...tooltip}
174
176
  data={tooltipData}
175
177
  x={tooltipX}
178
+ marginLeft={marginLeft}
179
+ marginTop={marginTop}
176
180
  tickName={tickName}
177
181
  series={series}
182
+ width={width}
183
+ height={height}
178
184
  />
179
185
  )}
180
186
  {brush && <Brush config={brush} width={width} />}
@@ -1,74 +1,83 @@
1
1
  /**
2
2
  * 区域图发光跟踪路径
3
3
  */
4
- import React, { useState, useEffect, useRef } from 'react';
4
+ import React, {
5
+ useState,
6
+ useEffect,
7
+ useRef,
8
+ useCallback,
9
+ useMemo,
10
+ } from 'react';
5
11
  import { scaleLinear } from 'd3v7';
6
12
  import { getColorList } from '../utils';
7
-
8
- const accelerateStep = 2;
9
- const loop = false;
13
+ import { svgPathProperties } from 'svg-path-properties';
10
14
 
11
15
  export default ({
12
16
  path: d,
17
+ config,
13
18
  config: { length: curveLength, width, fill, unitStep },
14
19
  }) => {
15
20
  const pointIndexRef = useRef(0);
16
21
  const raf = useRef(null);
17
22
  const [points, setPoints] = useState([]);
18
23
  const [lighter, setLighter] = useState(null);
19
- const [path, setPath] = useState(null);
20
24
  const pointLength = points.length;
21
25
  const totalLength = getLength(pointLength, curveLength);
22
26
  const easingFunc = easing.linear;
23
- const interpolateList = getColorsInterpolate(getColorList(fill));
24
-
25
- const drawLigher = () => {
26
- const currentPointLength = pointIndexRef.current;
27
- const pointIndex =
28
- currentPointLength >= totalLength
29
- ? loop
30
- ? curveLength
31
- : 0
32
- : currentPointLength +
33
- unitStep +
34
- Math.floor(
35
- easingFunc(pointIndexRef.current / totalLength) * accelerateStep
36
- );
37
-
38
- const overstep = pointIndex - pointLength;
39
- const startIndex = getPointIndex(pointIndex, pointLength, curveLength);
40
- const endIndex = getPointIndex(
41
- pointIndex + curveLength,
42
- pointLength,
43
- curveLength
44
- );
45
- const overstepPoints =
46
- loop && overstep > 0 ? points.slice(0, overstep) : [];
47
27
 
48
- drawCircle(
49
- Array.from(lighter.children),
50
- [...points.slice(startIndex, endIndex), ...overstepPoints],
51
- interpolateList
52
- );
28
+ const interpolateList = useMemo(
29
+ () => getColorsInterpolate(getColorList(fill)),
30
+ [fill]
31
+ );
53
32
 
54
- pointIndexRef.current = pointIndex;
55
- raf.current = requestAnimationFrame(drawLigher);
56
- };
33
+ const drawLigher = useCallback(() => {
34
+ const STEP = 2;
35
+ const LOOP = false;
36
+ if (lighter && lighter.children) {
37
+ const currentPointLength = pointIndexRef.current;
38
+ const pointIndex =
39
+ currentPointLength >= totalLength
40
+ ? LOOP
41
+ ? curveLength
42
+ : 0
43
+ : currentPointLength +
44
+ unitStep +
45
+ Math.floor(easingFunc(pointIndexRef.current / totalLength) * STEP);
46
+
47
+ const overstep = pointIndex - pointLength;
48
+ const startIndex = getPointIndex(pointIndex, pointLength, curveLength);
49
+ const endIndex = getPointIndex(
50
+ pointIndex + curveLength,
51
+ pointLength,
52
+ curveLength
53
+ );
54
+ const overstepPoints =
55
+ LOOP && overstep > 0 ? points.slice(0, overstep) : [];
56
+ drawCircle(
57
+ Array.from(lighter.children),
58
+ [...points.slice(startIndex, endIndex), ...overstepPoints],
59
+ interpolateList
60
+ );
61
+
62
+ pointIndexRef.current = pointIndex;
63
+ }
57
64
 
65
+ raf.current = requestAnimationFrame(drawLigher);
66
+ }, [lighter, totalLength, curveLength, unitStep, points, interpolateList]);
58
67
  useEffect(() => {
59
- if (path) {
60
- const totalLength = path.getTotalLength();
61
- const points = [];
62
- for (let i = 0; i < totalLength; i++) {
63
- const { x, y } = path.getPointAtLength(i);
64
- points.push({
65
- x,
66
- y,
67
- });
68
- }
69
- setPoints(points);
68
+ const path = new svgPathProperties(d);
69
+ const totalLength = path.getTotalLength();
70
+ const points = [];
71
+ for (let i = 0; i < totalLength; i++) {
72
+ const { x, y } = path.getPointAtLength(i);
73
+ points.push({
74
+ index: i,
75
+ x,
76
+ y,
77
+ });
70
78
  }
71
- }, [d, path]);
79
+ setPoints(points);
80
+ }, [d, config]);
72
81
 
73
82
  useEffect(() => {
74
83
  raf.current = requestAnimationFrame(drawLigher);
@@ -76,27 +85,29 @@ export default ({
76
85
  cancelAnimationFrame(raf.current);
77
86
  };
78
87
  }, [drawLigher]);
79
-
80
88
  return (
81
- <>
82
- <path ref={setPath} d={d} fill='none' stroke='none' />
83
- <g ref={setLighter}>
84
- {points.map((item, index) => (
85
- <circle key={index} r={width} fill='none' />
86
- ))}
87
- </g>
88
- </>
89
+ <g ref={setLighter}>
90
+ {points.map(({ x, y }, index) => (
91
+ <circle cx={x} cy={y} key={index} r={width} fill='none' />
92
+ ))}
93
+ </g>
89
94
  );
90
95
  };
91
96
 
92
97
  const drawCircle = (children, points, interpolateList) => {
93
98
  const pointLength = points.length;
94
- points.forEach(({ x, y }, index) => {
95
- children[index].setAttribute('cx', x);
96
- children[index].setAttribute('cy', y);
97
- children[index].setAttribute(
99
+ // const [startPoint] = points;
100
+ // const startIndex = startPoint ? startPoint.index : 0;
101
+ children.forEach((child, index) => {
102
+ const pointIndex = points.findIndex(({ index: i }) => i == index);
103
+ child.setAttribute(
98
104
  'fill',
99
- getColor(index / pointLength, interpolateList)
105
+ pointIndex > -1
106
+ ? getColor(
107
+ pointIndex / pointLength, //TODO: 颜色插值尚需完善
108
+ interpolateList
109
+ )
110
+ : 'none'
100
111
  );
101
112
  });
102
113
  };
@@ -572,7 +572,8 @@ const Component = memo(
572
572
  style={{
573
573
  ...categoryTextStyle,
574
574
  fontWeight:categoryTextStyle.bold?"bold":"normal",
575
- fontStyle:categoryTextStyle.italic?"italic":"normal"
575
+ fontStyle:categoryTextStyle.italic?"italic":"normal",
576
+ pointerEvents:"none"
576
577
  }}
577
578
  fill={categoryTextStyle.color}
578
579
  >
@@ -599,9 +600,11 @@ const Component = memo(
599
600
  )}
600
601
  {label && <Label config={label} arcs={_arcs} />}
601
602
  {current && (
602
- <g fillOpacity={y} style={{transform:"rotate("+(-rotate_)+"deg)"}}>
603
+ <g fillOpacity={y} style={{ transform:"rotate("+(-rotate_)+"deg)" }}>
603
604
  <Current
604
605
  config={current}
606
+ width={width}
607
+ height={height}
605
608
  data={_arcs}
606
609
  currentIndex={+currentIndex}
607
610
  />
@@ -649,6 +652,8 @@ const Current = ({
649
652
  },
650
653
  },
651
654
  },
655
+ width,
656
+ height,
652
657
  data,
653
658
  currentIndex,
654
659
  }) => {
@@ -671,13 +676,14 @@ const Current = ({
671
676
  }
672
677
  nameList.push(nameTemp)
673
678
  let foreignStyle={ //foreignObject标签样式,
674
- width:"100%",
675
- height:"100%",
676
- transform:"translate(-50%,-50%)",
679
+ width,
680
+ height,
681
+ transform:`translate(-${width/2}px,-${height/2}px)`,
682
+ pointerEvents:"none"
677
683
  },
678
684
  boxStyle={ //弹性盒子样式,用于当前值的上下居中对齐等
679
- width:"100%",
680
- height:'100%',
685
+ width,
686
+ height,
681
687
  display:"flex",
682
688
  flexDirection:"column",
683
689
  justifyContent:"center",
@@ -686,59 +692,57 @@ const Current = ({
686
692
 
687
693
  return (
688
694
  show && (
689
- <>
690
- <foreignObject style={foreignStyle}>
691
- <div style={boxStyle}>
692
- { //类目名称
693
- showName && <div
694
- style={{
695
- ...getFontStyle(nameFont),
696
- margin:gap/2+"px 0",
697
- display:"flex",
698
- flexDirection:"column",
699
- alignItems:"center"
700
- }}>
701
- {
702
- nameList.map((d,i)=>{
703
- return <span key={i}>{d}</span>
704
- })
705
- }
706
- </div>
707
- }
708
- { //真实值
709
- showValue && <span
710
- style={{
711
- ...getFontStyle(valueFont),
712
- transform:"translate("+translateValueX+"px,"+translateValueY+"px)",
713
- margin:gap/2+"px 0"
714
- }}>
715
- {value}
716
- {showSuffix && text && (
717
- <span
718
- style={{
719
- transform:"translate("+translateSuffixX+"px,"+translateSuffixY+"px)",
720
- fontSize:fontSize
721
- }}
722
- >
723
- {text}
724
- </span>
725
- )}
726
- </span>
727
- }
728
- { //百分比值
729
- showPercent && <span
730
- style={{
731
- transform:"translate("+translatePercentX+"px,"+translatePercentY+"px)",
732
- ...getFontStyle(percentFont),
733
- margin:gap/2+"px 0"
734
- }}
735
- >
736
- {percent + '%'}
737
- </span>
738
- }
739
- </div>
740
- </foreignObject>
741
- </>
695
+ <foreignObject style={foreignStyle}>
696
+ <div style={boxStyle}>
697
+ { //类目名称
698
+ showName && <div
699
+ style={{
700
+ ...getFontStyle(nameFont),
701
+ margin:gap/2+"px 0",
702
+ display:"flex",
703
+ flexDirection:"column",
704
+ alignItems:"center"
705
+ }}>
706
+ {
707
+ nameList.map((d,i)=>{
708
+ return <span key={i}>{d}</span>
709
+ })
710
+ }
711
+ </div>
712
+ }
713
+ { //真实值
714
+ showValue && <span
715
+ style={{
716
+ ...getFontStyle(valueFont),
717
+ transform:"translate("+translateValueX+"px,"+translateValueY+"px)",
718
+ margin:gap/2+"px 0"
719
+ }}>
720
+ {value}
721
+ {showSuffix && text && (
722
+ <span
723
+ style={{
724
+ transform:"translate("+translateSuffixX+"px,"+translateSuffixY+"px)",
725
+ fontSize:fontSize
726
+ }}
727
+ >
728
+ {text}
729
+ </span>
730
+ )}
731
+ </span>
732
+ }
733
+ { //百分比值
734
+ showPercent && <span
735
+ style={{
736
+ transform:"translate("+translatePercentX+"px,"+translatePercentY+"px)",
737
+ ...getFontStyle(percentFont),
738
+ margin:gap/2+"px 0"
739
+ }}
740
+ >
741
+ {percent + '%'}
742
+ </span>
743
+ }
744
+ </div>
745
+ </foreignObject>
742
746
  )
743
747
  );
744
748
  };