in src/app/views/panels/link_creator.tsx [169:479]
private getDefaultAnchor(
manager: Prototypes.ChartStateManager,
linkMode: string,
cs: Graphics.CoordinateSystem,
glyph1: Specification.Glyph,
glyphState1: Specification.GlyphState,
// eslint-disable-next-line
glyph2: Specification.Glyph,
glyphState2: Specification.GlyphState
): {
linkType: Prototypes.Links.LinkType;
interpolationType: Prototypes.Links.InterpolationType;
anchor1: Specification.Types.LinkAnchorPoint[];
anchor2: Specification.Types.LinkAnchorPoint[];
color: Specification.Mapping;
opacity: Specification.Mapping;
} {
// Default color and opacity
let color: Specification.Mapping;
let opacity: Specification.Mapping;
switch (this.state.linkType) {
case "line":
{
color = {
type: MappingType.value,
value: { r: 0, g: 0, b: 0 },
} as Specification.ValueMapping;
opacity = {
type: MappingType.value,
value: 1,
} as Specification.ValueMapping;
}
break;
case "band":
{
color = {
type: MappingType.value,
value: { r: 0, g: 0, b: 0 },
} as Specification.ValueMapping;
opacity = {
type: MappingType.value,
value: 0.5,
} as Specification.ValueMapping;
}
break;
}
// Get anchor candidates
let candidates1: Prototypes.LinkAnchor.Description[] = [];
let candidates2: Prototypes.LinkAnchor.Description[] = [];
for (const mark of glyphState1.marks) {
const c = manager.getMarkClass(mark);
if (c.getLinkAnchors) {
candidates1 = candidates1.concat(c.getLinkAnchors("begin"));
}
}
for (const mark of glyphState2.marks) {
const c = manager.getMarkClass(mark);
if (c.getLinkAnchors) {
candidates2 = candidates2.concat(c.getLinkAnchors("end"));
}
}
// Filter based on link type
switch (this.state.linkType) {
case "line":
{
candidates1 = candidates1.filter((x) => x.points.length == 1);
candidates2 = candidates2.filter((x) => x.points.length == 1);
}
break;
case "band": {
candidates1 = candidates1.filter((x) => x.points.length == 2);
candidates2 = candidates2.filter((x) => x.points.length == 2);
}
}
const glyphAttributes1 = glyphState1.attributes as Prototypes.Glyphs.RectangleGlyphAttributes;
const glyphAttributes2 = glyphState2.attributes as Prototypes.Glyphs.RectangleGlyphAttributes;
const determineRelationship = (
a1: number,
a2: number,
b1: number,
b2: number
): "before" | "overlap" | "after" => {
// Make sure order is correct
[a1, a2] = [Math.min(a1, a2), Math.max(a1, a2)];
[b1, b2] = [Math.min(b1, b2), Math.max(b1, b2)];
if (a2 <= b1) {
return "after";
} else if (a1 >= b2) {
return "before";
} else {
return "overlap";
}
};
// Determine relative position
const xRelationship = determineRelationship(
glyphAttributes1.x1,
glyphAttributes1.x2,
glyphAttributes2.x1,
glyphAttributes2.x2
);
const yRelationship = determineRelationship(
glyphAttributes1.y1,
glyphAttributes1.y2,
glyphAttributes2.y1,
glyphAttributes2.y2
);
const meanPoint = (points: Point[]): Point => {
let x = 0,
y = 0;
for (const pt of points) {
x += pt.x;
y += pt.y;
}
return {
x: x / points.length,
y: y / points.length,
};
};
let c1: Prototypes.LinkAnchor.Description = null,
c2: Prototypes.LinkAnchor.Description = null;
if (xRelationship == "after") {
if (linkMode == "link-table") {
c1 = candidates1[argMin(candidates1, (c) => meanPoint(c.points).y)];
c2 = candidates2[argMin(candidates2, (c) => meanPoint(c.points).y)];
} else {
c1 = candidates1[argMax(candidates1, (c) => meanPoint(c.points).x)];
c2 = candidates2[argMin(candidates2, (c) => meanPoint(c.points).x)];
}
} else if (xRelationship == "before") {
if (linkMode == "link-table") {
c1 = candidates1[argMin(candidates1, (c) => meanPoint(c.points).y)];
c2 = candidates2[argMin(candidates2, (c) => meanPoint(c.points).y)];
} else {
c1 = candidates1[argMin(candidates1, (c) => meanPoint(c.points).x)];
c2 = candidates2[argMax(candidates2, (c) => meanPoint(c.points).x)];
}
} else {
if (yRelationship == "after") {
if (linkMode == "link-table") {
c1 = candidates1[argMin(candidates1, (c) => meanPoint(c.points).x)];
c2 = candidates2[argMin(candidates2, (c) => meanPoint(c.points).x)];
} else {
c1 = candidates1[argMax(candidates1, (c) => meanPoint(c.points).y)];
c2 = candidates2[argMin(candidates2, (c) => meanPoint(c.points).y)];
}
} else if (yRelationship == "before") {
if (linkMode == "link-table") {
c1 = candidates1[argMin(candidates1, (c) => meanPoint(c.points).x)];
c2 = candidates2[argMin(candidates2, (c) => meanPoint(c.points).x)];
} else {
c1 = candidates1[argMin(candidates1, (c) => meanPoint(c.points).y)];
c2 = candidates2[argMax(candidates2, (c) => meanPoint(c.points).y)];
}
} else {
c1 =
candidates1[
argMin(candidates1, (c) => Math.abs(meanPoint(c.points).y))
];
c2 =
candidates2[
argMin(candidates2, (c) => Math.abs(meanPoint(c.points).y))
];
}
}
switch (this.state.linkType) {
case "line":
{
if (c1 == null) {
c1 = {
element: null,
points: [
{
xAttribute: "icx",
yAttribute: "icy",
x: 0,
y: 0,
direction: { x: 0, y: 0 },
},
],
};
}
if (c2 == null) {
c2 = {
element: null,
points: [
{
xAttribute: "icx",
yAttribute: "icy",
x: 0,
y: 0,
direction: { x: 0, y: 0 },
},
],
};
}
}
break;
case "band":
{
if (c1 == null) {
c1 = {
element: null,
points: [
{
xAttribute: "icx",
yAttribute: "iy1",
x: 0,
y: 0,
direction: { x: 1, y: 0 },
},
{
xAttribute: "icx",
yAttribute: "iy2",
x: 0,
y: 0,
direction: { x: 1, y: 0 },
},
],
};
}
if (c2 == null) {
c2 = {
element: null,
points: [
{
xAttribute: "icx",
yAttribute: "iy1",
x: 0,
y: 0,
direction: { x: -1, y: 0 },
},
{
xAttribute: "icx",
yAttribute: "iy2",
x: 0,
y: 0,
direction: { x: -1, y: 0 },
},
],
};
}
}
break;
}
const anchor1 = c1.points.map((pt) => {
return {
x: { element: c1.element, attribute: pt.xAttribute },
y: { element: c1.element, attribute: pt.yAttribute },
direction: pt.direction,
} as Specification.Types.LinkAnchorPoint;
});
const anchor2 = c2.points.map((pt) => {
return {
x: { element: c2.element, attribute: pt.xAttribute },
y: { element: c2.element, attribute: pt.yAttribute },
direction: pt.direction,
} as Specification.Types.LinkAnchorPoint;
});
if (linkMode != "link-table") {
if (c1.element != null) {
const element1 = getById(glyph1.marks, c1.element);
switch (element1.classID) {
case "mark.symbol":
{
if (element1.mappings.fill != null) {
color = element1.mappings.fill;
}
}
break;
case "mark.rect":
{
if (element1.mappings.fill != null) {
color = element1.mappings.fill;
}
}
break;
}
}
}
let interpolationType: Prototypes.Links.InterpolationType = "line";
if (cs instanceof Graphics.PolarCoordinates) {
interpolationType = "bezier";
} else {
interpolationType = "line";
}
// If directions are the same direction, switch line to circle.
if (Geometry.vectorDot(anchor1[0].direction, anchor2[0].direction) > 0) {
if (interpolationType == "line") {
interpolationType = "circle";
}
}
return {
linkType: this.state.linkType,
interpolationType,
anchor1,
anchor2,
color,
opacity,
};
}