| /* | 
 | * Licensed to the Apache Software Foundation (ASF) under one | 
 | * or more contributor license agreements.  See the NOTICE file | 
 | * distributed with this work for additional information | 
 | * regarding copyright ownership.  The ASF licenses this file | 
 | * to you under the Apache License, Version 2.0 (the | 
 | * "License"); you may not use this file except in compliance | 
 | * with the License.  You may obtain a copy of the License at | 
 | * | 
 | *   http://www.apache.org/licenses/LICENSE-2.0 | 
 | * | 
 | * Unless required by applicable law or agreed to in writing, | 
 | * software distributed under the License is distributed on an | 
 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
 | * KIND, either express or implied.  See the License for the | 
 | * specific language governing permissions and limitations | 
 | * under the License. | 
 | */ | 
 |  | 
 | import { parsePercent } from '../../util/number'; | 
 | import * as zrUtil from 'zrender/src/core/util'; | 
 | import GlobalModel from '../../model/Global'; | 
 | import ExtensionAPI from '../../core/ExtensionAPI'; | 
 | import SunburstSeriesModel, { SunburstSeriesOption } from './SunburstSeries'; | 
 | import { TreeNode } from '../../data/Tree'; | 
 |  | 
 | // let PI2 = Math.PI * 2; | 
 | const RADIAN = Math.PI / 180; | 
 |  | 
 | export default function sunburstLayout( | 
 |     seriesType: 'sunburst', | 
 |     ecModel: GlobalModel, | 
 |     api: ExtensionAPI | 
 | ) { | 
 |     ecModel.eachSeriesByType(seriesType, function (seriesModel: SunburstSeriesModel) { | 
 |         let center = seriesModel.get('center'); | 
 |         let radius = seriesModel.get('radius'); | 
 |  | 
 |         if (!zrUtil.isArray(radius)) { | 
 |             radius = [0, radius]; | 
 |         } | 
 |         if (!zrUtil.isArray(center)) { | 
 |             center = [center, center]; | 
 |         } | 
 |  | 
 |         const width = api.getWidth(); | 
 |         const height = api.getHeight(); | 
 |         const size = Math.min(width, height); | 
 |         const cx = parsePercent(center[0], width); | 
 |         const cy = parsePercent(center[1], height); | 
 |         const r0 = parsePercent(radius[0], size / 2); | 
 |         const r = parsePercent(radius[1], size / 2); | 
 |  | 
 |         const startAngle = -seriesModel.get('startAngle') * RADIAN; | 
 |         const minAngle = seriesModel.get('minAngle') * RADIAN; | 
 |  | 
 |         const virtualRoot = seriesModel.getData().tree.root; | 
 |         const treeRoot = seriesModel.getViewRoot(); | 
 |         const rootDepth = treeRoot.depth; | 
 |  | 
 |         const sort = seriesModel.get('sort'); | 
 |         if (sort != null) { | 
 |             initChildren(treeRoot, sort); | 
 |         } | 
 |  | 
 |         let validDataCount = 0; | 
 |         zrUtil.each(treeRoot.children, function (child) { | 
 |             !isNaN(child.getValue() as number) && validDataCount++; | 
 |         }); | 
 |  | 
 |         const sum = treeRoot.getValue() as number; | 
 |         // Sum may be 0 | 
 |         const unitRadian = Math.PI / (sum || validDataCount) * 2; | 
 |  | 
 |         const renderRollupNode = treeRoot.depth > 0; | 
 |         const levels = treeRoot.height - (renderRollupNode ? -1 : 1); | 
 |         const rPerLevel = (r - r0) / (levels || 1); | 
 |  | 
 |         const clockwise = seriesModel.get('clockwise'); | 
 |  | 
 |         const stillShowZeroSum = seriesModel.get('stillShowZeroSum'); | 
 |  | 
 |         // In the case some sector angle is smaller than minAngle | 
 |         // let restAngle = PI2; | 
 |         // let valueSumLargerThanMinAngle = 0; | 
 |  | 
 |         const dir = clockwise ? 1 : -1; | 
 |  | 
 |         /** | 
 |          * Render a tree | 
 |          * @return increased angle | 
 |          */ | 
 |         const renderNode = function (node: TreeNode, startAngle: number) { | 
 |             if (!node) { | 
 |                 return; | 
 |             } | 
 |  | 
 |             let endAngle = startAngle; | 
 |  | 
 |             // Render self | 
 |             if (node !== virtualRoot) { | 
 |                 // Tree node is virtual, so it doesn't need to be drawn | 
 |                 const value = node.getValue() as number; | 
 |  | 
 |                 let angle = (sum === 0 && stillShowZeroSum) | 
 |                     ? unitRadian : (value * unitRadian); | 
 |                 if (angle < minAngle) { | 
 |                     angle = minAngle; | 
 |                     // restAngle -= minAngle; | 
 |                 } | 
 |                 // else { | 
 |                 //     valueSumLargerThanMinAngle += value; | 
 |                 // } | 
 |  | 
 |                 endAngle = startAngle + dir * angle; | 
 |  | 
 |                 const depth = node.depth - rootDepth | 
 |                     - (renderRollupNode ? -1 : 1); | 
 |                 let rStart = r0 + rPerLevel * depth; | 
 |                 let rEnd = r0 + rPerLevel * (depth + 1); | 
 |  | 
 |                 const levelModel = seriesModel.getLevelModel(node); | 
 |                 if (levelModel) { | 
 |                     let r0 = levelModel.get('r0', true); | 
 |                     let r = levelModel.get('r', true); | 
 |                     const radius = levelModel.get('radius', true); | 
 |  | 
 |                     if (radius != null) { | 
 |                         r0 = radius[0]; | 
 |                         r = radius[1]; | 
 |                     } | 
 |  | 
 |                     (r0 != null) && (rStart = parsePercent(r0, size / 2)); | 
 |                     (r != null) && (rEnd = parsePercent(r, size / 2)); | 
 |                 } | 
 |  | 
 |                 node.setLayout({ | 
 |                     angle: angle, | 
 |                     startAngle: startAngle, | 
 |                     endAngle: endAngle, | 
 |                     clockwise: clockwise, | 
 |                     cx: cx, | 
 |                     cy: cy, | 
 |                     r0: rStart, | 
 |                     r: rEnd | 
 |                 }); | 
 |             } | 
 |  | 
 |             // Render children | 
 |             if (node.children && node.children.length) { | 
 |                 // currentAngle = startAngle; | 
 |                 let siblingAngle = 0; | 
 |                 zrUtil.each(node.children, function (node) { | 
 |                     siblingAngle += renderNode(node, startAngle + siblingAngle); | 
 |                 }); | 
 |             } | 
 |  | 
 |             return endAngle - startAngle; | 
 |         }; | 
 |  | 
 |         // Virtual root node for roll up | 
 |         if (renderRollupNode) { | 
 |             const rStart = r0; | 
 |             const rEnd = r0 + rPerLevel; | 
 |  | 
 |             const angle = Math.PI * 2; | 
 |             virtualRoot.setLayout({ | 
 |                 angle: angle, | 
 |                 startAngle: startAngle, | 
 |                 endAngle: startAngle + angle, | 
 |                 clockwise: clockwise, | 
 |                 cx: cx, | 
 |                 cy: cy, | 
 |                 r0: rStart, | 
 |                 r: rEnd | 
 |             }); | 
 |         } | 
 |  | 
 |         renderNode(treeRoot, startAngle); | 
 |     }); | 
 | } | 
 |  | 
 | /** | 
 |  * Init node children by order and update visual | 
 |  */ | 
 | function initChildren(node: TreeNode, sortOrder?: SunburstSeriesOption['sort']) { | 
 |     const children = node.children || []; | 
 |  | 
 |     node.children = sort(children, sortOrder); | 
 |  | 
 |     // Init children recursively | 
 |     if (children.length) { | 
 |         zrUtil.each(node.children, function (child) { | 
 |             initChildren(child, sortOrder); | 
 |         }); | 
 |     } | 
 | } | 
 |  | 
 | /** | 
 |  * Sort children nodes | 
 |  * | 
 |  * @param {TreeNode[]}               children children of node to be sorted | 
 |  * @param {string | function | null} sort sort method | 
 |  *                                   See SunburstSeries.js for details. | 
 |  */ | 
 | function sort(children: TreeNode[], sortOrder: SunburstSeriesOption['sort']) { | 
 |     if (zrUtil.isFunction(sortOrder)) { | 
 |         const sortTargets = zrUtil.map(children, (child, idx) => { | 
 |             const value = child.getValue() as number; | 
 |             return { | 
 |                 params: { | 
 |                     depth: child.depth, | 
 |                     height: child.height, | 
 |                     dataIndex: child.dataIndex, | 
 |                     getValue: () => value | 
 |                 }, | 
 |                 index: idx | 
 |             }; | 
 |         }); | 
 |         sortTargets.sort((a, b) => { | 
 |             return sortOrder(a.params, b.params); | 
 |         }); | 
 |  | 
 |         return zrUtil.map(sortTargets, (target) => { | 
 |             return children[target.index]; | 
 |         }); | 
 |     } | 
 |     else { | 
 |         const isAsc = sortOrder === 'asc'; | 
 |         return children.sort(function (a, b) { | 
 |             const diff = ((a.getValue() as number) - (b.getValue() as number)) * (isAsc ? 1 : -1); | 
 |             return diff === 0 | 
 |                 ? (a.dataIndex - b.dataIndex) * (isAsc ? -1 : 1) | 
 |                 : diff; | 
 |         }); | 
 |     } | 
 | } |