1 | import { JSX } from '../jsx/jsx-namespace';
|
2 | import { ScaleConfig } from '@antv/scale';
|
3 | import { each, findIndex, isArray } from '@antv/util';
|
4 | import Component from '../base/component';
|
5 | import equal from '../base/equal';
|
6 | import Layout from '../base/layout';
|
7 | import Coord from '../coord';
|
8 | import Children from '../children';
|
9 |
|
10 | import LayoutController from '../controller/layout';
|
11 | import CoordController from '../controller/coord';
|
12 | import ScaleController from '../controller/scale';
|
13 |
|
14 | interface Point {
|
15 | x: number;
|
16 | y: number;
|
17 | }
|
18 |
|
19 | export interface Props {
|
20 | zIndex?: number;
|
21 | data: any;
|
22 | scale?: any;
|
23 | coord?: any;
|
24 | start?: Point;
|
25 | end?: Point;
|
26 | children: any;
|
27 | }
|
28 |
|
29 | export interface ChartChildProps {
|
30 | data?: any;
|
31 | chart?: Chart;
|
32 | coord?: Coord;
|
33 | layout?: Layout;
|
34 | [k: string]: any;
|
35 | }
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 | interface IChart {
|
42 | props: Props;
|
43 | }
|
44 |
|
45 | export interface PositionLayout {
|
46 | position: 'top' | 'right' | 'bottom' | 'left';
|
47 | width: number;
|
48 | height: number;
|
49 | }
|
50 |
|
51 | export interface ComponentPosition {
|
52 | component: Component;
|
53 | layout: PositionLayout | PositionLayout[];
|
54 | }
|
55 |
|
56 |
|
57 | class Chart extends Component implements IChart {
|
58 | data: any;
|
59 |
|
60 | private layout: Layout;
|
61 |
|
62 | private coord: Coord;
|
63 | private componentsPosition: ComponentPosition[] = [];
|
64 |
|
65 |
|
66 | private layoutController: LayoutController;
|
67 | private coordController: CoordController;
|
68 | private scaleController: ScaleController;
|
69 | scale: ScaleController;
|
70 |
|
71 | constructor(props, context?, updater?) {
|
72 | super(props, context, updater);
|
73 |
|
74 | const { data, coord: coordOption, scale = [] } = props;
|
75 |
|
76 | this.layoutController = new LayoutController();
|
77 | this.coordController = new CoordController();
|
78 | this.scaleController = new ScaleController(data);
|
79 | this.scale = this.scaleController;
|
80 |
|
81 | const { layoutController, coordController, scaleController } = this;
|
82 |
|
83 |
|
84 | const style = this.getStyle(props, context);
|
85 | this.layout = layoutController.create(style);
|
86 |
|
87 |
|
88 | this.coord = coordController.create(coordOption, this.layout);
|
89 |
|
90 |
|
91 | scaleController.create(scale);
|
92 |
|
93 | this.data = data;
|
94 |
|
95 |
|
96 | this.state = {
|
97 | filters: {},
|
98 | };
|
99 | }
|
100 |
|
101 |
|
102 | willReceiveProps(nextProps, context) {
|
103 | const { layoutController, coordController, scaleController, props: lastProps } = this;
|
104 | const { style: nextStyle, data: nextData, scale: nextScale } = nextProps;
|
105 | const { style: lastStyle, data: lastData, scale: lastScale } = lastProps;
|
106 |
|
107 |
|
108 | if (!equal(nextStyle, lastStyle) || context !== this.context) {
|
109 | const style = this.getStyle(nextProps, context);
|
110 | this.layout = layoutController.create(style);
|
111 | coordController.updateLayout(this.layout);
|
112 | }
|
113 |
|
114 | if (nextData !== lastData) {
|
115 | scaleController.changeData(nextData);
|
116 | }
|
117 |
|
118 |
|
119 | if (!equal(nextScale, lastScale)) {
|
120 | scaleController.update(nextScale);
|
121 | }
|
122 | }
|
123 |
|
124 | willUpdate() {
|
125 | const { coordController, props } = this;
|
126 |
|
127 |
|
128 | this.coord = coordController.create(props.coord, this.layout);
|
129 | }
|
130 |
|
131 | private getStyle(props, context) {
|
132 | const { theme, px2hd, left, top, width, height } = context;
|
133 | const { style } = props;
|
134 | return px2hd({
|
135 | left,
|
136 | top,
|
137 | width,
|
138 | height,
|
139 | ...theme.chart,
|
140 | ...style,
|
141 | });
|
142 | }
|
143 |
|
144 |
|
145 | layoutCoord(layout: PositionLayout) {
|
146 | const { coord } = this;
|
147 | const { position, width: boxWidth, height: boxHeight } = layout;
|
148 | let { left, top, width, height } = coord;
|
149 | switch (position) {
|
150 | case 'left':
|
151 | left += boxWidth;
|
152 | width = Math.max(0, width - boxWidth);
|
153 | break;
|
154 | case 'right':
|
155 | width = Math.max(0, width - boxWidth);
|
156 | break;
|
157 | case 'top':
|
158 | top += boxHeight;
|
159 | height = Math.max(0, height - boxHeight);
|
160 | break;
|
161 | case 'bottom':
|
162 | height = Math.max(0, height - boxHeight);
|
163 | break;
|
164 | }
|
165 | coord.update({ left, top, width, height });
|
166 | }
|
167 |
|
168 | resetCoordLayout() {
|
169 | const { coord, layout } = this;
|
170 | coord.update(layout);
|
171 | }
|
172 |
|
173 | updateCoordLayout(layout: PositionLayout | PositionLayout[]) {
|
174 | if (isArray(layout)) {
|
175 | layout.forEach((item) => {
|
176 | this.layoutCoord(item);
|
177 | });
|
178 | return;
|
179 | }
|
180 | this.layoutCoord(layout);
|
181 | }
|
182 |
|
183 | updateCoordFor(component: Component, layout: PositionLayout | PositionLayout[]) {
|
184 | if (!layout) return;
|
185 | const { componentsPosition } = this;
|
186 | const componentPosition = { component, layout };
|
187 | const existIndex = findIndex(componentsPosition, (item) => {
|
188 | return item.component === component;
|
189 | });
|
190 |
|
191 | if (existIndex > -1) {
|
192 | componentsPosition.splice(existIndex, 1, componentPosition);
|
193 |
|
194 |
|
195 | this.resetCoordLayout();
|
196 | componentsPosition.forEach((componentPosition) => {
|
197 | const { layout } = componentPosition;
|
198 | this.updateCoordLayout(layout);
|
199 | });
|
200 | return;
|
201 | }
|
202 |
|
203 |
|
204 | componentsPosition.push(componentPosition);
|
205 | this.updateCoordLayout(layout);
|
206 | }
|
207 |
|
208 | getGeometrys() {
|
209 | const { children } = this;
|
210 | const geometrys: Component[] = [];
|
211 |
|
212 | Children.toArray(children).forEach((element) => {
|
213 | if (!element) return false;
|
214 | const { component } = element;
|
215 | if (component && component.isGeometry) {
|
216 | geometrys.push(component);
|
217 | }
|
218 | });
|
219 | return geometrys;
|
220 | }
|
221 |
|
222 | |
223 |
|
224 |
|
225 |
|
226 |
|
227 | getPosition(record) {
|
228 | const coord = this.getCoord();
|
229 | const xScale = this.getXScales()[0];
|
230 | const xField = xScale.field;
|
231 | const yScales = this.getYScales();
|
232 |
|
233 | let yScale = yScales[0];
|
234 | let yField = yScale.field;
|
235 | for (let i = 0, len = yScales.length; i < len; i++) {
|
236 | const scale = yScales[i];
|
237 | const field = scale.field;
|
238 | if (record[field]) {
|
239 | yScale = scale;
|
240 | yField = field;
|
241 | break;
|
242 | }
|
243 | }
|
244 | const x = xScale.scale(record[xField]);
|
245 | const y = yScale.scale(record[yField]);
|
246 | return coord.convertPoint({ x, y });
|
247 | }
|
248 |
|
249 | getSnapRecords(point, inCoordRange?) {
|
250 | const geometrys = this.getGeometrys();
|
251 | if (!geometrys.length) return;
|
252 |
|
253 | return geometrys[0].getSnapRecords(point, inCoordRange);
|
254 | }
|
255 |
|
256 | getLegendItems(point?) {
|
257 | const geometrys = this.getGeometrys();
|
258 | if (!geometrys.length) return;
|
259 |
|
260 | return geometrys[0].getLegendItems(point);
|
261 | }
|
262 |
|
263 | setScale(field: string, option: ScaleConfig) {
|
264 | this.scaleController.setScale(field, option);
|
265 | }
|
266 |
|
267 | getScale(field: string) {
|
268 | return this.scaleController.getScale(field);
|
269 | }
|
270 |
|
271 | getScales() {
|
272 | return this.scaleController.getScales();
|
273 | }
|
274 |
|
275 | getXScales() {
|
276 | const geometrys = this.getGeometrys();
|
277 | return geometrys.map((component) => {
|
278 |
|
279 | return component.getXScale();
|
280 | });
|
281 | }
|
282 |
|
283 | getYScales() {
|
284 | const geometrys = this.getGeometrys();
|
285 | return geometrys.map((component) => {
|
286 |
|
287 | return component.getYScale();
|
288 | });
|
289 | }
|
290 |
|
291 | getCoord() {
|
292 | return this.coord;
|
293 | }
|
294 |
|
295 | filter(field: string, condition) {
|
296 | const { filters } = this.state;
|
297 | this.setState({
|
298 | filters: {
|
299 | ...filters,
|
300 | [field]: condition,
|
301 | },
|
302 | });
|
303 | }
|
304 |
|
305 | _getRenderData() {
|
306 | const { props, state } = this;
|
307 | const { data } = props;
|
308 | const { filters } = state;
|
309 | if (!filters || !Object.keys(filters).length) {
|
310 | return data;
|
311 | }
|
312 | let filteredData = data;
|
313 | each(filters, (condition, field) => {
|
314 | if (!condition) return;
|
315 | filteredData = filteredData.filter((record) => {
|
316 | return condition(record[field], record);
|
317 | });
|
318 | });
|
319 | return filteredData;
|
320 | }
|
321 |
|
322 | render(): JSX.Element {
|
323 | const { props, layout, coord } = this;
|
324 | const { children, data: originData } = props;
|
325 | if (!originData) return null;
|
326 | const data = this._getRenderData();
|
327 |
|
328 | return Children.map(children, (child) => {
|
329 | return Children.cloneElement(child, {
|
330 | chart: this,
|
331 | coord,
|
332 | data,
|
333 | layout,
|
334 | });
|
335 | });
|
336 | }
|
337 | }
|
338 |
|
339 | export default Chart;
|