Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Chord charts #20522

Draft
wants to merge 8 commits into
base: v6
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/chart/chord/ChordEdge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { PathProps } from 'zrender/src/graphic/Path';
import PathProxy from 'zrender/src/core/PathProxy';
import * as graphic from '../../util/graphic';

export class ChordPathShape {
// Souce node, two points forming an arc
s1: [number, number] = [0, 0];
s2: [number, number] = [0, 0];
sStartAngle: number = 0;
sEndAngle: number = 0;

// Target node, two points forming an arc
t1: [number, number] = [0, 0];
t2: [number, number] = [0, 0];
tStartAngle: number = 0;
tEndAngle: number = 0;

cx: number = 0;
cy: number = 0;
// series.r0 of ChordSeries
r: number = 0;
}

interface ChordEdgePathProps extends PathProps {
shape?: Partial<ChordPathShape>
}

export class ChordEdge extends graphic.Path<ChordEdgePathProps> {
shape: ChordPathShape;

constructor(opts?: ChordEdgePathProps) {
super(opts);
}

buildPath(ctx: PathProxy | CanvasRenderingContext2D, shape: ChordPathShape): void {
// Start from n11
ctx.moveTo(shape.s1[0], shape.s1[1]);

const ratio = 0.7;

// Draw the arc from n11 to n12
ctx.arc(shape.cx, shape.cy, shape.r, shape.sStartAngle, shape.sEndAngle, false);

// // Bezier curve to cp1 and then to n21
ctx.bezierCurveTo(
(shape.cx - shape.s2[0]) * ratio + shape.s2[0],
(shape.cy - shape.s2[1]) * ratio + shape.s2[1],
(shape.cx - shape.t1[0]) * ratio + shape.t1[0],
(shape.cy - shape.t1[1]) * ratio + shape.t1[1],
shape.t1[0],
shape.t1[1]
);

// Draw the arc from n21 to n22
ctx.arc(shape.cx, shape.cy, shape.r, shape.tStartAngle, shape.tEndAngle, false);

// Bezier curve back to cp2 and then to n11
ctx.bezierCurveTo(
(shape.cx - shape.t2[0]) * ratio + shape.t2[0],
(shape.cy - shape.t2[1]) * ratio + shape.t2[1],
(shape.cx - shape.s1[0]) * ratio + shape.s1[0],
(shape.cy - shape.s1[1]) * ratio + shape.s1[1],
shape.s1[0],
shape.s1[1]
);

ctx.closePath();
}
}
44 changes: 44 additions & 0 deletions src/chart/chord/ChordPiece.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { extend } from 'zrender/src/core/util';
import * as graphic from '../../util/graphic';
import SeriesData from '../../data/SeriesData';
import { getSectorCornerRadius } from '../helper/sectorHelper';
import ChordSeriesModel, { ChordNodeItemOption } from './ChordSeries';

export default class ChordPiece extends graphic.Sector {

constructor(data: SeriesData, idx: number, startAngle: number) {
super();

this.z2 = 2;

const text = new graphic.Text();

this.setTextContent(text);

this.updateData(data, idx, startAngle, true);
}

updateData(data: SeriesData, idx: number, startAngle?: number, firstCreate?: boolean): void {
const sector = this;
const node = data.graph.getNodeByIndex(idx);

const seriesModel = data.hostModel as ChordSeriesModel;

Check warning on line 25 in src/chart/chord/ChordPiece.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

'seriesModel' is assigned a value but never used
const itemModel = node.getModel<ChordNodeItemOption>();

// layout position is the center of the sector
const layout = data.getItemLayout(idx) as graphic.Sector['shape'];
// console.log(layout)

const sectorShape = extend(
getSectorCornerRadius(
itemModel.getModel('itemStyle'),
layout,
true
),
layout
);
sector.setShape(sectorShape);
sector.useStyle(data.getItemVisual(idx, 'style'));
}

}
232 changes: 232 additions & 0 deletions src/chart/chord/ChordSeries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
/*
* 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 * as zrUtil from 'zrender/src/core/util';
import {
SeriesOption,
SeriesOnCartesianOptionMixin,
SeriesOnPolarOptionMixin,
SeriesOnCalendarOptionMixin,
SeriesOnGeoOptionMixin,
SeriesOnSingleOptionMixin,
OptionDataValue,
RoamOptionMixin,
SeriesLabelOption,
ItemStyleOption,
LineStyleOption,
SymbolOptionMixin,
BoxLayoutOptionMixin,
CircleLayoutOptionMixin,
SeriesLineLabelOption,
StatesOptionMixin,
GraphEdgeItemObject,
OptionDataValueNumeric,
CallbackDataParams,
DefaultEmphasisFocus,
ZRColor
} from '../../util/types';
import Model from '../../model/Model';
import SeriesModel from '../../model/Series';
import GlobalModel from '../../model/Global';
import SeriesData from '../../data/SeriesData';
import createGraphFromNodeEdge from '../helper/createGraphFromNodeEdge';
import Graph from '../../data/Graph';
import { LineDataVisual } from '../../visual/commonVisualTypes';

interface ChordStatesMixin {
emphasis?: DefaultEmphasisFocus
}

interface ChordEdgeStatesMixin {
emphasis?: DefaultEmphasisFocus
}

type ChordDataValue = OptionDataValue | OptionDataValue[];

export interface ChordItemStyleOption<TCbParams = never> extends ItemStyleOption<TCbParams> {
borderRadius?: (number | string)[] | number | string
}

export interface ChordNodeStateOption<TCbParams = never> {
itemStyle?: ChordItemStyleOption<TCbParams>
label?: SeriesLabelOption
}

export interface ChordNodeItemOption extends ChordNodeStateOption,
StatesOptionMixin<ChordNodeStateOption, ChordStatesMixin> {

id?: string
name?: string
value?: ChordDataValue
}

export interface ChordEdgeLineStyleOption<Clr = ZRColor> extends LineStyleOption {
curveness?: number
color: 'source' | 'target' | 'gradient' | Clr
}

export interface ChordEdgeStateOption {
lineStyle?: ChordEdgeLineStyleOption
label?: SeriesLineLabelOption
}

export interface ChordEdgeItemOption extends ChordEdgeStateOption,
StatesOptionMixin<ChordEdgeStateOption, ChordEdgeStatesMixin>,
GraphEdgeItemObject<OptionDataValueNumeric> {

value?: number
}

export interface ChordSeriesOption
extends SeriesOption<ChordNodeStateOption<CallbackDataParams>, ChordStatesMixin>,
SeriesOnCartesianOptionMixin, SeriesOnPolarOptionMixin, SeriesOnCalendarOptionMixin,
SeriesOnGeoOptionMixin, SeriesOnSingleOptionMixin,
SymbolOptionMixin<CallbackDataParams>,
RoamOptionMixin,
BoxLayoutOptionMixin,
CircleLayoutOptionMixin
{
type?: 'chord'

coordinateSystem?: 'none'

legendHoverLink?: boolean

clockwise?: boolean
startAngle?: number
endAngle?: number | 'auto'
padAngle?: number
minAngle?: number

data?: (ChordNodeItemOption | ChordDataValue)[]
nodes?: (ChordNodeItemOption | ChordDataValue)[]

edges?: ChordEdgeItemOption[]
links?: ChordEdgeItemOption[]

edgeLabel?: SeriesLineLabelOption
label?: SeriesLabelOption

itemStyle?: ChordItemStyleOption<CallbackDataParams>
lineStyle?: ChordEdgeLineStyleOption
}

class ChordSeriesModel extends SeriesModel<ChordSeriesOption> {

static type = 'series.chord';
readonly type = ChordSeriesModel.type;

init(option: ChordSeriesOption) {
super.init.apply(this, arguments as any);
}

getInitialData(option: ChordSeriesOption, ecModel: GlobalModel): SeriesData {
const edges = option.edges || option.links || [];
const nodes = option.data || option.nodes || [];

if (nodes && edges) {
// auto curveness
// initCurvenessList(this);
const graph = createGraphFromNodeEdge(nodes as ChordNodeItemOption[], edges, this, true, beforeLink);
zrUtil.each(graph.edges, function (edge) {
// createEdgeMapForCurveness(edge.node1, edge.node2, this, edge.dataIndex);
}, this);
return graph.data;
}

function beforeLink(nodeData: SeriesData, edgeData: SeriesData) {
// Overwrite nodeData.getItemModel to
// nodeData.wrapMethod('getItemModel', function (model) {

// });

// TODO Inherit resolveParentPath by default in Model#getModel?
const oldGetModel = Model.prototype.getModel;
function newGetModel(this: Model, path: any, parentModel?: Model) {
const model = oldGetModel.call(this, path, parentModel);
model.resolveParentPath = resolveParentPath;
return model;
}

edgeData.wrapMethod('getItemModel', function (model: Model) {
model.resolveParentPath = resolveParentPath;
model.getModel = newGetModel;
return model;
});

function resolveParentPath(this: Model, pathArr: readonly string[]): string[] {
if (pathArr && (pathArr[0] === 'label' || pathArr[1] === 'label')) {
const newPathArr = pathArr.slice();
if (pathArr[0] === 'label') {
newPathArr[0] = 'edgeLabel';
}
else if (pathArr[1] === 'label') {
newPathArr[1] = 'edgeLabel';
}
return newPathArr;
}
return pathArr as string[];
}
}
}

getGraph(): Graph {
return this.getData().graph;
}

getEdgeData() {
return this.getGraph().edgeData as SeriesData<ChordSeriesModel, LineDataVisual>;
}

static defaultOption: ChordSeriesOption = {
// zlevel: 0,
z: 2,

coordinateSystem: 'none',

legendHoverLink: true,
colorBy: 'data',

left: 0,
top: 0,
right: 0,
bottom: 0,
width: null,
height: null,

center: ['50%', '50%'],
radius: ['70%', '80%'],

startAngle: 90,
endAngle: 'auto',
minAngle: 0,
padAngle: 3,

itemStyle: {
borderRadius: [0, 0, 5, 5]
},

lineStyle: {
color: 'source',
opacity: 0.5
}
};
}

export default ChordSeriesModel;
Loading
Loading