在v5中扩展d3.selection-这是正确的方法吗? [英] Extending d3.selection in v5—is this the correct way to do it?
问题描述
背景:我正在尝试解决将现有本地SVG
文件附加到Electron
桌面应用程序中的d3
SVG容器中的问题.我发现我不能在本地文件上使用d3.svg()
,因为fetch
不能与file
协议配合使用(所以说错误msg).
Background: I was trying to solve the problem of appending existing local SVG
files to a d3
SVG container in an Electron
desktop app. I discovered that I can't use d3.svg()
on local files because fetch
does not work with the file
protocol (so said the error msg).
我遇到了d3.selection
,它似乎完全可以满足我的需求,添加了appendHTML
和appendSVG
函数.
I came across this gist referenced by this question for extending d3.selection
and it appears to do exactly what I need, adding appendHTML
and appendSVG
functions.
尽管我测试了代码(在下面的底部),但它抛出了一个错误:无法读取属性'未定义的原型" –在此行窒息:
When I tested the code though (at bottom below), it threw an error: "Cannot read property 'prototype of undefined" – choking on this line:
d3.selection.enter.prototype.appendHTML
我通过登录控制台进行了一些挖掘,并提出了此更改,它似乎可以工作:
I dug around a bit through logging to the console and came up with this change and it seems to work:
d3.selection.prototype.enter.prototype.appendHTML
我的问题:我这样做对吗? d3
中是否发生了某些更改,因此需要附加的prototype
引用?我不是Javascript或d3英雄,我想了解这里的区别.
My Question: Am I doing this right? Did something change in d3
which necessitates the additional prototype
reference? I'm no Javascript or d3 hero and would like to understand what the difference is here.
d3.selection.prototype.appendHTML =
d3.selection.prototype.enter.prototype.appendHTML = function (HTMLString) {
return this.select(function () {
return this.appendChild(document.importNode(new DOMParser().parseFromString(HTMLString, 'text/html').body.childNodes[0], true));
});
};
原始代码
Original code
d3.selection.prototype.appendHTML =
d3.selection.enter.prototype.appendHTML = function(HTMLString) {
return this.select(function() {
return this.appendChild(document.importNode(new DOMParser().parseFromString(HTMLString, 'text/html').body.childNodes[0], true));
});
};
d3.selection.prototype.appendSVG =
d3.selection.enter.prototype.appendSVG = function(SVGString) {
return this.select(function() {
return this.appendChild(document.importNode(new DOMParser()
.parseFromString('<svg xmlns="http://www.w3.org/2000/svg">' + SVGString + '</svg>', 'application/xml').documentElement.firstChild, true));
});
};
推荐答案
您提到的答案使用的是D3 v3,但从v3到v4/v5的情况已经发生了很大变化.关于选择的主要区别在于
The answers you mentioned are using D3 v3, but things have changed considerably from v3 to v4/v5. The main difference when it comes to selections is covered by just one sentence in the changelog:
选择不再使用原型链注入子数组;它们现在是普通对象,可以提高性能.
Selections no longer subclass Array using prototype chain injection; they are now plain objects, improving performance.
尽管这听起来很简单,但需要在引擎盖下进行大量更改.现在,所有选择对象都是Selection
不会直接公开的功能. d3.selection
是返回Selection
的新实例的函数:
Although this sounds quite simple it nonetheless required vast changes under the hood. All selection objects are now instances of the Selection
function which is not directly exposed. d3.selection
is a function returning a new instance of a Selection
:
function selection() {
return new Selection([[document.documentElement]], root);
}
尽管Selection
和d3.selection
共享相同的原型(包含.enter
属性),没有属性.enter
,除非创建了实例,因此代码中的错误.
Although both Selection
and d3.selection
share the same prototype, which contains the .enter
property, there is no property .enter
unless an instance is created, hence the error in your code.
在v4/v5中扩展D3选择对象的正确方法如下:
The correct way of extending D3's selection objects in v4/v5 would be along the following lines:
d3.selection
.prototype // This prototype is shared across all types of selections.
.appendHTML = // Apply changes to the selection's prototype.
由于Selection
和d3.selection
的prototype
属性指向同一个对象,因此这些更改将同时影响正常选择和输入选择,因为它们都是Selection
函数的实例.
Since the prototype
property of Selection
and d3.selection
points to the same object, these changes will affect both normal as well as enter selections because they are both instances of the Selection
function.
如您所见,这只是您自己的代码的第一行,非常好.您使用d3.selection.prototype.enter.prototype.appendHTML
进行的扩展仅适用于某种工作:既无害也无益!在.enter
函数上设置属性是没有意义的,因为从来没有从该函数创建实例.
As you can see, this is just the first line of your own code, which is perfectly fine. Your extension using d3.selection.prototype.enter.prototype.appendHTML
only kind of works: it does neither harm nor good! Setting the property on the .enter
function is pointless as there is never an instance created from this function.
看看下面的工作演示,我是从您在问题中链接到的要点采纳的:
Have a look at the following working demo which I adopted from the gist you linked to in your question:
d3.selection.prototype.appendHTML =
function(HTMLString) {
return this.select(function() {
return this.appendChild(
document.importNode(
new DOMParser().parseFromString(HTMLString, 'text/html').body.childNodes[0], true)
);
});
};
d3.selection.prototype.appendSVG =
function(SVGString) {
return this.select(function() {
return this.appendChild(
document.importNode(
new DOMParser()
.parseFromString('<svg xmlns="http://www.w3.org/2000/svg">' + SVGString + '</svg>', 'application/xml').documentElement.firstChild, true));
});
};
d3.select('.container').appendHTML('<svg><g><rect width="50" height="50" /></g></svg>');
var svg = d3.select('.container')
.appendHTML('<svg xmlns="http://www.w3.org/2000/svg"><g><circle class="circle1" cx="50" cy="50" r="50"></circle></g></svg>')
.select('g');
svg.appendSVG('<circle class="circle2" cx="20" cy="20" r="20"></circle>');
svg.appendSVG('<rect width="30" height="30"></rect>');
div,
svg {
border: 1px solid silver;
margin: 10px;
}
rect {
fill: skyblue;
}
.circle1 {
fill: orange;
}
.circle2 {
fill: lime;
}
<script src="https://d3js.org/d3.v5.js"></script>
<div class="container"></div>
这篇关于在v5中扩展d3.selection-这是正确的方法吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!