UNPKG

8.23 kBPlain TextView Raw
1/**
2 * Create By Bruce Too
3 * On 2020-02-10
4 */
5import { assign, deepMix, each, get } from '@antv/util';
6import View from '../chart/view';
7import { DIRECTION, VIEW_LIFE_CIRCLE } from '../constant';
8import { AxisCfg, Condition, Datum, TreeCfg, TreeData } from '../interface';
9import { getFactTitleConfig } from '../util/facet';
10import { Facet } from './facet';
11
12/**
13 * @ignore
14 * Tree Facet
15 */
16export default class Tree extends Facet<TreeCfg, TreeData> {
17 protected afterEachView(view: View, facet: TreeData) {
18 this.processAxis(view, facet);
19 }
20
21 protected beforeEachView(view: View, facet: TreeData) {}
22
23 public init() {
24 super.init();
25 this.view.on(VIEW_LIFE_CIRCLE.AFTER_RENDER, this.afterChartRender);
26 }
27
28 protected getDefaultCfg() {
29 return deepMix({}, super.getDefaultCfg(), {
30 type: 'tree',
31 line: {
32 style: {
33 lineWidth: 1,
34 stroke: '#ddd',
35 },
36 smooth: false,
37 },
38 showTitle: true,
39 title: super.getDefaultTitleCfg(),
40 });
41 }
42
43 protected generateFacets(data: Datum[]): TreeData[] {
44 const fields = this.cfg.fields;
45 if (!fields.length) {
46 throw new Error('Please specify for the fields for rootFacet!');
47 }
48 const rst = [];
49 const rootFacet: TreeData = {
50 type: this.cfg.type,
51 data,
52 region: null,
53 rowValuesLength: this.getRows(),
54 columnValuesLength: 1,
55 rowIndex: 0,
56 columnIndex: 0,
57 rowField: '',
58 columnField: '',
59 rowValue: '',
60 columnValue: '',
61 };
62 rst.push(rootFacet);
63 rootFacet.children = this.getChildFacets(data, 1, rst);
64 this.setRegion(rst);
65 return rst;
66 }
67
68 private setRegion(facets: TreeData[]) {
69 this.forceColIndex(facets);
70 facets.forEach((facet) => {
71 // @ts-ignore 允许调整
72 facet.region = this.getRegion(facet.rowValuesLength, facet.columnValuesLength, facet.columnIndex, facet.rowIndex);
73 });
74 }
75
76 protected getRegion(rows: number, cols: number, xIndex: number, yIndex: number) {
77 const xWidth = 1 / cols; // x轴方向的每个分面的偏移
78 const yWidth = 1 / rows; // y轴方向的每个分面的偏移
79
80 const start = {
81 x: xWidth * xIndex,
82 y: yWidth * yIndex,
83 };
84
85 const end = {
86 x: start.x + xWidth,
87 y: start.y + (yWidth * 2) / 3, // 预留1/3的空隙,方便添加连接线
88 };
89 return {
90 start,
91 end,
92 };
93 }
94
95 private forceColIndex(facets: TreeData[]) {
96 const leafs: TreeData[] = [];
97 let index = 0;
98 facets.forEach((facet) => {
99 if (this.isLeaf(facet)) {
100 leafs.push(facet);
101 // @ts-ignore 允许调整
102 facet.columnIndex = index;
103 index++;
104 }
105 });
106
107 leafs.forEach((facet) => {
108 // @ts-ignore
109 facet.columnValuesLength = leafs.length;
110 });
111 const maxLevel = this.cfg.fields.length;
112 for (let i = maxLevel - 1; i >= 0; i--) {
113 const levelFacets = this.getFacetsByLevel(facets, i);
114 // var yIndex = maxLevel - i;
115 for (const facet of levelFacets) {
116 if (!this.isLeaf(facet)) {
117 facet.originColIndex = facet.columnIndex;
118 // @ts-ignore
119 facet.columnIndex = this.getRegionIndex(facet.children);
120 // @ts-ignore
121 facet.columnValuesLength = leafs.length;
122 }
123 }
124 }
125 }
126
127 // get facet use level
128 private getFacetsByLevel(facets: TreeData[], level: number) {
129 const rst: TreeData[] = [];
130 facets.forEach((facet) => {
131 if (facet.rowIndex === level) {
132 rst.push(facet);
133 }
134 });
135 return rst;
136 }
137
138 // if the facet has children , make it's column index in the middle of it's children
139 private getRegionIndex(children: TreeData[]) {
140 const first = children[0];
141 const last = children[children.length - 1];
142 return (last.columnIndex - first.columnIndex) / 2 + first.columnIndex;
143 }
144
145 // is a leaf without children
146 private isLeaf(facet: TreeData) {
147 return !facet.children || !facet.children.length;
148 }
149
150 private getRows() {
151 return this.cfg.fields.length + 1;
152 }
153
154 // get child
155 private getChildFacets(data: Datum[], level: number, arr: TreeData[]) {
156 // [ 'grade', 'class' ]
157 const fields = this.cfg.fields;
158 const length = fields.length;
159 if (length < level) {
160 return;
161 }
162 const rst = [];
163 // get fist level except root node
164 const field = fields[level - 1];
165 // get field value
166 const values = this.getFieldValues(data, field);
167 values.forEach((value, index) => {
168 const conditions = [{ field, value, values } as Condition];
169 const subData = data.filter(this.getFacetDataFilter(conditions));
170 if (subData.length) {
171 const facet: TreeData = {
172 type: this.cfg.type,
173 data: subData,
174 region: null,
175 columnValue: value,
176 rowValue: '',
177 columnField: field,
178 rowField: '',
179 columnIndex: index,
180 rowValuesLength: this.getRows(),
181 columnValuesLength: 1,
182 rowIndex: level,
183 children: this.getChildFacets(subData, level + 1, arr),
184 };
185 rst.push(facet);
186 arr.push(facet);
187 }
188 });
189 return rst;
190 }
191
192 public render() {
193 super.render();
194 if (this.cfg.showTitle) {
195 this.renderTitle();
196 }
197 }
198
199 private afterChartRender = () => {
200 if (this.facets && this.cfg.line) {
201 this.container.clear();
202 this.drawLines(this.facets);
203 }
204 };
205
206 private renderTitle() {
207 each(this.facets, (facet: TreeData) => {
208 const { columnValue, view } = facet;
209 const formatter = get(this.cfg.title, 'formatter');
210
211 const config = deepMix(
212 {
213 position: ['50%', '0%'] as [string, string],
214 content: formatter ? formatter(columnValue) : columnValue,
215 },
216 getFactTitleConfig(DIRECTION.TOP),
217 this.cfg.title
218 );
219
220 view.annotation().text(config);
221 });
222 }
223
224 private drawLines(facets: TreeData[]) {
225 facets.forEach((facet) => {
226 if (!this.isLeaf(facet)) {
227 const children = facet.children;
228 this.addFacetLines(facet, children);
229 }
230 });
231 }
232
233 // add lines with it's children
234 private addFacetLines(facet: TreeData, children: TreeData[]) {
235 const view = facet.view;
236 const region = view.coordinateBBox;
237 // top, right, bottom, left
238 const start = {
239 x: region.x + region.width / 2,
240 y: region.y + region.height,
241 };
242
243 children.forEach((subFacet) => {
244 const subRegion = subFacet.view.coordinateBBox;
245 const end = {
246 x: subRegion.bl.x + (subRegion.tr.x - subRegion.bl.x) / 2,
247 y: subRegion.tr.y,
248 };
249
250 const middle1 = {
251 x: start.x,
252 y: start.y + (end.y - start.y) / 2,
253 };
254 const middle2 = {
255 x: end.x,
256 y: middle1.y,
257 };
258 this.drawLine([start, middle1, middle2, end]);
259 });
260 }
261
262 private getPath(points) {
263 const path = [];
264 const smooth = this.cfg.line.smooth;
265 if (smooth) {
266 path.push(['M', points[0].x, points[0].y]);
267 path.push(['C', points[1].x, points[1].y, points[2].x, points[2].y, points[3].x, points[3].y]);
268 } else {
269 points.forEach((point, index) => {
270 if (index === 0) {
271 path.push(['M', point.x, point.y]);
272 } else {
273 path.push(['L', point.x, point.y]);
274 }
275 });
276 }
277
278 return path;
279 }
280
281 // draw line width points
282 private drawLine(points) {
283 const path = this.getPath(points);
284 const line = this.cfg.line.style;
285 this.container.addShape('path', {
286 attrs: assign(
287 {
288 // @ts-ignore
289 path,
290 },
291 line
292 ),
293 });
294 }
295
296 protected getXAxisOption(x: string, axes: any, option: AxisCfg, facet: TreeData): object {
297 if (facet.rowIndex !== facet.rowValuesLength - 1) {
298 return {
299 ...option,
300 title: null,
301 label: null,
302 };
303 }
304 return option;
305 }
306
307 protected getYAxisOption(y: string, axes: any, option: AxisCfg, facet: TreeData): object {
308 if (facet.originColIndex !== 0 && facet.columnIndex !== 0) {
309 return {
310 ...option,
311 title: null,
312 label: null,
313 };
314 }
315 return option;
316 }
317}