typescript - Ako môžem posunúť zdrojové a cieľové body Bézierovej krivky pomocou D3 generátora odkazov?
Kódujem horizontálny hierarchický strom Power BI vlastný vizuál použitím TypeScriptu a D3, využívajúc D3 treeLayout, a potrebujem napísať generátor odkazov, ktorý dokáže vykresliť Bézierove, krokové A diagonálne odkazy podľa želania používateľa.
Háčik (pre mňa) je: keďže uzly sú obdĺžniky s určitú šírkou, odkazy by mali ísť z (source.x + width/2, y) na (target.x - width/2, y). Podarilo sa mi to urobiť s možnosťami diagonal a step, použitím funkcie d3.line() (ktorá vráti reťazec cesty), ale narazil som na možnosť bezier a funkciu linkHorizontal (ktorá vráti funkciu link()). Prečítal som si celú dokumentáciu a dokonca aj samotný kód d3, ale zatiaľ sa mi nepodarilo použiť jeho funkcie source, target a context na dosiahnutie toho, čo potrebujem.
Tu je môj doterajší kód, zjednodušený:
- this.settings.links.style drží používateľovu možnosť odkazu „bezier“, „curve“ alebo „step“
- this.settings.nodes.width uchováva šírku uzla
- this.orientation.x mapuje funkcie x a y ako je vidieť v Bostockovom https://bl.ocks.org/mbostock/3184089 (pôvodný kód tiež zohľadňuje iné orientácie)
const linkH = d3.linkHorizontal().x(d => this.orientation.x(d)).y(d => this.orientation.y(d));
let linkGenerator = this.settings.links.style == "bezier" ? linkH
:
(this.settings.links.style == "step" ?
d => d3.line().curve(d3.curveStep)([[this.orientation.x(d.source) + this.settings.nodes.width / 2, this.orientation.y(d.source)],
[this.orientation.x(d.target) - this.settings.nodes.width / 2, this.orientation.y(d.target)]])
:
d => d3.line()([[this.orientation.x(d.source) + this.settings.nodes.width / 2, this.orientation.y(d.source)],
[this.orientation.x(d.target) - this.settings.nodes.width, this.orientation.y(d.target)]])
)
var links = linkGroup.selectAll("path")
.data(this.viewModel.hierarchy.links())
.enter()
.append("path")
.attr("d", linkGenerator);
Odpoveď č. 1
Tu je jednoduchá funkcia Bezierovej krivky, ktorú môžete použiť namiesto D3:
const getBezierPath = (from, to) => {
if (Math.abs(from.x - to.x) > Math.abs(from.y - to.y)) {
const midX = (to.x + from.x) / 2;
return `M ${from.x},${from.y} C ${midX},${from.y} ${midX},${to.y} ${to.x},${to.y}`;
} else {
const midY = (to.y + from.y) / 2;
return `M ${from.x},${from.y} C ${from.x},${midY} ${to.x},${midY} ${to.x},${to.y}`;
}
};
Pozrite si ukážku vo Fiddle
Odpoveď č. 2
Nakoniec som prišiel s touto funkciou, ktorá rieši cieľ použiť zároveň d3.line() a d3.linkHorizontal(). Kľúčom bolo použiť implementáciu d3.DefaultLinkObject, aby som mohol použiť pôvodný zdroj a cieľ predaný cez attr("d", f(d)).
Takže, ak to niekomu pomôže:
class myLinkObject implements d3.DefaultLinkObject {
public source: [number, number];
public target: [number, number];
constructor(s: [number, number], t: [number, number]) {
this.source = s;
this.target = t;
}
}
function linkGenerator(d) {
var deltaX = self.settings.nodes.width / 2;
var pSource: [number, number] = [self.orientation.x(d.source) + deltaX, self.orientation.y(d.source);
var pTarget: [number, number] = [self.orientation.x(d.target) - deltaX, self.orientation.y(d.target);
var points = [pSource, pTarget];
var linkObject: myLinkObject = new myLinkObject(pSource, pTarget);
var path = "";
if (self.settings.links.style == "step") {
var lineGenerator = d3.line().curve(d3.curveStep);
path = lineGenerator(points);
} else if (self.settings.links.style == "diagonal") {
var lineGenerator = d3.line();
path = lineGenerator(points);
} else { // bezier
var linkGen = d3.linkHorizontal().x(d => d[0]).y(d => d[1]);
path = linkGen(linkObject);
}
return path;
}
