D3 鼠标交互问题,圆圈未出现在数据点
D3 mouse interactivity issues, circles not appearing at data points
我正在以 class 格式实现一些 D3 代码,以便我有一个可重复使用的图表。
报错如下:
GetElementsByClassName
returns 一个长度为 0 的 HTMLCollection,但要选择的元素已 class 正确编辑。
Circles
出现在 x0 和 yMax,而不是数据位置(问题可能与第一个有关)。
Text
未附加到圆圈且不可见(这可能在圆圈起作用时有效)。
我正在实施 this pretty much exactly as it looks,除了我将工具提示放在 2/4 的行上,并且我正在使用 class.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
<div id="graph"></div>
<script>
class Chart {
constructor(opts) {
this.data = opts.data;
this.element = opts.element;
}
draw() {
this.width = this.element.offsetWidth;
this.height = this.width / 2;
this.padding = 50;
this.margin = {
top: 20,
bottom: 20,
left: 30,
right: 50
};
this.element.innerHTML = '';
const svg = d3.select(this.element).append('svg');
svg.attr('width', this.width);
svg.attr('height', this.height);
this.plot = svg.append('g')
.attr('transform', `translate(${this.margin.left},${this.margin.top})`);
this.createScales();
this.addAxes();
this.addLine();
this.tTip();
}
createScales() {
this.keynames = d3.scaleOrdinal();
this.keynames.domain(Object.keys(this.data[0]).filter(key => key !== 'date'));
this.keymap = this.keynames.domain().map(
keyname => ({
name: keyname, values: this.data.map(
d => ({ date: d.date, key: +d[keyname] })
)
})
);
const m = this.margin;
const xExtent = d3.extent(this.data, d => d.date);
const yExtent = [0, d3.max(this.keymap, d => d3.max(d.values, function (v) { return v.key }))];
this.xScale = d3.scaleTime()
.range([0, this.width - m.right])
.domain(xExtent).nice();
this.yScale = d3.scaleLinear()
.range([this.height - (m.top + m.bottom), 0])
.domain(yExtent).nice();
}
addAxes() {
const m = this.margin;
const xAxis = d3.axisBottom()
.scale(this.xScale);
const yAxis = d3.axisLeft()
.scale(this.yScale);
this.plot.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${this.height - (m.top + m.bottom)})`)
.call(xAxis.ticks(8));
this.plot.append("g")
.attr("class", "y axis")
.call(yAxis.ticks(4))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.attr("fill", "black")
.style("text-anchor", "end")
.text("$USD");
}
addLine() {
const line = d3.line()
.x(d => this.xScale(d.date))
.y(d => this.yScale(d.key));
this.plot.append('g')
.selectAll('path')
.data(this.keymap)
.join('path')
.classed('line', true)
.attr('d', function (d) { return line(d.values) })
.style('stroke', this.lineColor || 'red')
.style('fill', 'none');
}
tTip(){
let mouseG = this.plot.append("g")
.attr("class", "mouse-over-effects");
mouseG.append("path")
.attr("class", "mouse-line")
.style("stroke", "rgba(50,50,50,1)")
.style("stroke-width", "0.5px")
.style("opacity", "0");
var lines = document.getElementsByClassName('.standard'); //issue here
let mousePerLine = mouseG.selectAll('.mouse-per-line')
.data(this.keymap)
.enter()
.append("g") //join instead of append?
.attr("class", "mouse-per-line");
mousePerLine.append("circle") //join instead of append?
.attr("r", 4)
.style("stroke", "black"
)
.style("fill", "blue"
)
.style("fill-opacity", "0.3")
.style("stroke-width", "1px")
.style("opacity", "0");
mousePerLine.append("text") //join instead of append?
.attr("transform", function(d){
if (d.name == 'aapl') {
return "translate(-50,30)"
} else {
return "translate(-50, -30)"
}
}).style("text-shadow",
" -2px -2px 0 #FFF, 0 -2px 0 #FFF, 2px -2px 0 #FFF, 2px 0 0 #FFF, 2px 2px 0 #FFF, 0 2px 0 #FFF,-2px 2px 0 #FFF,-2px 0 0 #FFF");
mouseG.append('svg:rect')
.attr('width', this.width)
.attr('height', this.height)
.attr('x', '0')
.attr('fill', 'none')
.attr('pointer-events', 'all')
.on('mouseout', function() {
d3.select(".mouse-line")
.style("opacity", "0");
d3.selectAll(".mouse-per-line circle")
.style("opacity", "0");
d3.selectAll(".mouse-per-line text")
.style("opacity", "0");
})
.on('mouseover', function() {
d3.select(".mouse-line")
.style("opacity", "1");
d3.selectAll(".mouse-per-line circle")
.style("opacity", "1");
d3.selectAll(".mouse-per-line text")
.style("opacity", "1");
})
.on('mousemove', () => {
let mouse = d3.pointer(event);
d3.select(".mouse-line")
.attr("d", () => {
let d = "M" + mouse[0] + "," + this.height;
d += " " + mouse[0] + "," + 0;
return d;
});
d3.selectAll(".mouse-per-line")
.attr("transform", (d, i) => {
let xDate = this.xScale.invert(mouse[0]),
bisect = d3.bisector(d => d.date).right,
idx = bisect(d.values, xDate);
let beginning = 0,
end = lines[i].getTotalLength(),
target = null;
while (true){
let target = Math.floor((beginning + end) / 2),
pos = lines[i].getPointAtLength(target); //issue here
if ((target === end || target === beginning) && pos.x !== mouse[0]) {
break;
}
if (pos.x > mouse[0]){
end = target;
}
else if (pos.x < mouse[0]){
beginning = target;
}
else break; //position found
}
d3.select(this).select('text')
.text( () => { "$" + this.yScale.invert(pos.y).toFixed(2)})
return "translate(" + mouse[0] + "," + pos.y +")";
})
.style('font-family', 'Helvetica')
.style('font-size', '11px')
.style('letter-spacing', '1px')
.style('text-transform', 'uppercase');
});
}
setColor(newColor) {
this.plot.select('.line')
.style('stroke', newColor);
this.lineColor = newColor;
}
setData(data) {
this.data = data;
this.draw();
}
}
const chart = new Chart({ element: document.querySelector('#graph') });
const data = d3.csvParse(`Date,AAPL,SMA_AAPL,TSLA,SMA_TSLA
2018-12-31,38.33848571777344,,66.55999755859375,
2019-01-02,38.382225036621094,,62.02399826049805,
2019-01-03,34.55907440185547,,60.071998596191406,
2019-01-04,36.03437805175781,,63.53799819946289,
2019-01-07,35.95417022705078,,66.99199676513672,
2019-01-08,36.63956832885742,,67.06999969482422,
2019-01-09,37.26177215576172,,67.70600128173828,
2019-01-10,37.380863189697266,,68.99400329589844,
2019-01-11,37.013858795166016,,69.4520034790039,
2019-01-14,36.4572868347168,,66.87999725341797,
2019-01-15,37.20343780517578,,68.88600158691406,
2019-01-16,37.657936096191406,,69.20999908447266,
2019-01-17,37.88154602050781,,69.46199798583984,
2019-01-18,38.11487579345703,,60.45199966430664,
2019-01-22,37.259342193603516,,59.784000396728516,
2019-01-23,37.410030364990234,,57.518001556396484,
2019-01-24,37.113521575927734,,58.301998138427734,
2019-01-25,38.34333801269531,,59.40800094604492,
2019-01-28,37.988487243652344,,59.2760009765625,
2019-01-29,37.59474182128906,,59.492000579833984,
2019-01-30,40.16377258300781,,61.75400161743164,
2019-01-31,40.453006744384766,,61.40399932861328,
2019-02-01,40.472450256347656,,62.44200134277344,
2019-02-04,41.622066497802734,,62.577999114990234,
2019-02-05,42.33420181274414,,64.2699966430664,
2019-02-06,42.34878158569336,,63.444000244140625,
2019-02-07,41.546722412109375,,61.50199890136719,
2019-02-08,41.59553909301758,,61.15999984741211,
2019-02-11,41.35633087158203,,62.56800079345703,
2019-02-12,41.71269989013672,38.606483713785806,62.36199951171875,63.48539975484212
2019-02-13,41.539398193359375,38.71318079630534,61.63399887084961,63.32119979858398
2019-02-14,41.69073486328125,38.823464457194014,60.75400161743164,63.278866577148435
2019-02-15,41.59797286987305,39.05809440612793,61.57600021362305,63.32899996439616
2019-02-19,41.72246551513672,39.247697321573895,61.12799835205078,63.24866663614909
2019-02-20,41.990962982177734,39.44892374674479,60.512001037597656,63.032666778564455
2019-02-21,41.75419616699219,39.619411341349284,58.24599838256836,62.738533401489256
2019-02-22,42.22041702270508,39.78469950358073,58.94200134277344,62.44640007019043
2019-02-25,42.5279655456543,39.95626958211263,59.75400161743164,62.13840001424153`, function (d) {
function removeNaN(e, c) {
if (e > 0) { return e; } else { return c; }
}
return {
date: d3.timeParse("%Y-%m-%d")(d.Date),
aapl: +d.AAPL,
tsla: +d.TSLA,
aapl_sma: removeNaN(+d.SMA_AAPL, +d.AAPL),
tsla_sma: removeNaN(+d.SMA_TSLA, +d.TSLA)
};
});
chart.setData(data);
</script>
</body>
</html>
如您所见,鼠标交互非常糟糕,所以我希望有人能提供帮助。
箭头函数和正则函数之间存在一些。
修复了一些错误:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
<div id="graph"></div>
<script>
let pos = null;
class Chart {
constructor(opts) {
this.data = opts.data;
this.element = opts.element;
}
draw() {
this.width = this.element.offsetWidth;
this.height = this.width / 2;
this.padding = 50;
this.margin = {
top: 20,
bottom: 20,
left: 30,
right: 50
};
this.element.innerHTML = '';
const svg = d3.select(this.element).append('svg');
svg.attr('width', this.width);
svg.attr('height', this.height);
this.plot = svg.append('g')
.attr('transform', `translate(${this.margin.left},${this.margin.top})`);
this.createScales();
this.addAxes();
this.addLine();
this.tTip();
}
createScales() {
this.keynames = d3.scaleOrdinal();
this.keynames.domain(Object.keys(this.data[0]).filter(key => key !== 'date'));
this.keymap = this.keynames.domain().map(
keyname => ({
name: keyname, values: this.data.map(
d => ({ date: d.date, key: +d[keyname] })
)
})
);
const m = this.margin;
const xExtent = d3.extent(this.data, d => d.date);
const yExtent = [0, d3.max(this.keymap, d => d3.max(d.values, function (v) { return v.key }))];
this.xScale = d3.scaleTime()
.range([0, this.width - m.right])
.domain(xExtent).nice();
this.yScale = d3.scaleLinear()
.range([this.height - (m.top + m.bottom), 0])
.domain(yExtent).nice();
}
addAxes() {
const m = this.margin;
const xAxis = d3.axisBottom()
.scale(this.xScale);
const yAxis = d3.axisLeft()
.scale(this.yScale);
this.plot.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${this.height - (m.top + m.bottom)})`)
.call(xAxis.ticks(8));
this.plot.append("g")
.attr("class", "y axis")
.call(yAxis.ticks(4))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.attr("fill", "black")
.style("text-anchor", "end")
.text("$USD");
}
addLine() {
const line = d3.line()
.x(d => this.xScale(d.date))
.y(d => this.yScale(d.key));
this.plot.append('g')
.selectAll('path')
.data(this.keymap)
.join('path')
.classed('line', true)
.attr('d', function (d) { return line(d.values) })
.style('stroke', this.lineColor || 'red')
.style('fill', 'none');
}
tTip() {
let mouseG = this.plot.append("g")
.attr("class", "mouse-over-effects");
mouseG.append("path")
.attr("class", "mouse-line")
.style("stroke", "rgba(50,50,50,1)")
.style("stroke-width", "0.5px")
.style("opacity", "0");
var lines = document.getElementsByClassName('line'); //issue here
let mousePerLine = mouseG.selectAll('.mouse-per-line')
.data(this.keymap)
.enter()
.append("g") //join instead of append?
.attr("class", "mouse-per-line");
mousePerLine.append("circle") //join instead of append?
.attr("r", 4)
.style("stroke", "black"
)
.style("fill", "blue"
)
.style("fill-opacity", "0.3")
.style("stroke-width", "1px")
.style("opacity", "0");
mousePerLine.append("text") //join instead of append?
.attr("transform", function (d) {
if (d.name == 'aapl') {
return "translate(-50,30)"
} else {
return "translate(-50, -30)"
}
}).style("text-shadow",
" -2px -2px 0 #FFF, 0 -2px 0 #FFF, 2px -2px 0 #FFF, 2px 0 0 #FFF, 2px 2px 0 #FFF, 0 2px 0 #FFF,-2px 2px 0 #FFF,-2px 0 0 #FFF");
mouseG.append('svg:rect')
.attr('width', this.width)
.attr('height', this.height)
.attr('x', '0')
.attr('fill', 'none')
.attr('pointer-events', 'all')
.on('mouseout', function () {
d3.select(".mouse-line")
.style("opacity", "0");
d3.selectAll(".mouse-per-line circle")
.style("opacity", "0");
d3.selectAll(".mouse-per-line text")
.style("opacity", "0");
})
.on('mouseover', function () {
d3.select(".mouse-line")
.style("opacity", "1");
d3.selectAll(".mouse-per-line circle")
.style("opacity", "1");
d3.selectAll(".mouse-per-line text")
.style("opacity", "1");
})
.on('mousemove', () => {
let mouse = d3.pointer(event);
d3.select(".mouse-line")
.attr("d", () => {
let d = "M" + mouse[0] + "," + this.height;
d += " " + mouse[0] + "," + 0;
return d;
});
d3.selectAll(".mouse-per-line")
.attr("transform", function (d, i) {
let xDate = chart.xScale.invert(mouse[0]),
bisect = d3.bisector(d => d.date).right,
idx = bisect(d.values, xDate);
let beginning = 0,
end = lines[i].getTotalLength(),
target = null;
while (true) {
let target = Math.floor((beginning + end) / 2);
pos = lines[i].getPointAtLength(target); //issue here
if ((target === end || target === beginning) && pos.x !== mouse[0]) {
break;
}
if (pos.x > mouse[0]) {
end = target;
}
else if (pos.x < mouse[0]) {
beginning = target;
}
else break; //position found
}
d3.select(this).select('text')
.text("$" + chart.yScale.invert(pos.y).toFixed(2))
return "translate(" + mouse[0] + "," + pos.y + ")";
})
.style('font-family', 'Helvetica')
.style('font-size', '11px')
.style('letter-spacing', '1px')
.style('text-transform', 'uppercase');
});
}
setColor(newColor) {
this.plot.select('.line')
.style('stroke', newColor);
this.lineColor = newColor;
}
setData(data) {
this.data = data;
this.draw();
}
}
const chart = new Chart({ element: document.querySelector('#graph') });
const data = d3.csvParse(`Date,AAPL,SMA_AAPL,TSLA,SMA_TSLA
2018-12-31,38.33848571777344,,66.55999755859375,
2019-01-02,38.382225036621094,,62.02399826049805,
2019-01-03,34.55907440185547,,60.071998596191406,
2019-01-04,36.03437805175781,,63.53799819946289,
2019-01-07,35.95417022705078,,66.99199676513672,
2019-01-08,36.63956832885742,,67.06999969482422,
2019-01-09,37.26177215576172,,67.70600128173828,
2019-01-10,37.380863189697266,,68.99400329589844,
2019-01-11,37.013858795166016,,69.4520034790039,
2019-01-14,36.4572868347168,,66.87999725341797,
2019-01-15,37.20343780517578,,68.88600158691406,
2019-01-16,37.657936096191406,,69.20999908447266,
2019-01-17,37.88154602050781,,69.46199798583984,
2019-01-18,38.11487579345703,,60.45199966430664,
2019-01-22,37.259342193603516,,59.784000396728516,
2019-01-23,37.410030364990234,,57.518001556396484,
2019-01-24,37.113521575927734,,58.301998138427734,
2019-01-25,38.34333801269531,,59.40800094604492,
2019-01-28,37.988487243652344,,59.2760009765625,
2019-01-29,37.59474182128906,,59.492000579833984,
2019-01-30,40.16377258300781,,61.75400161743164,
2019-01-31,40.453006744384766,,61.40399932861328,
2019-02-01,40.472450256347656,,62.44200134277344,
2019-02-04,41.622066497802734,,62.577999114990234,
2019-02-05,42.33420181274414,,64.2699966430664,
2019-02-06,42.34878158569336,,63.444000244140625,
2019-02-07,41.546722412109375,,61.50199890136719,
2019-02-08,41.59553909301758,,61.15999984741211,
2019-02-11,41.35633087158203,,62.56800079345703,
2019-02-12,41.71269989013672,38.606483713785806,62.36199951171875,63.48539975484212
2019-02-13,41.539398193359375,38.71318079630534,61.63399887084961,63.32119979858398
2019-02-14,41.69073486328125,38.823464457194014,60.75400161743164,63.278866577148435
2019-02-15,41.59797286987305,39.05809440612793,61.57600021362305,63.32899996439616
2019-02-19,41.72246551513672,39.247697321573895,61.12799835205078,63.24866663614909
2019-02-20,41.990962982177734,39.44892374674479,60.512001037597656,63.032666778564455
2019-02-21,41.75419616699219,39.619411341349284,58.24599838256836,62.738533401489256
2019-02-22,42.22041702270508,39.78469950358073,58.94200134277344,62.44640007019043
2019-02-25,42.5279655456543,39.95626958211263,59.75400161743164,62.13840001424153`, function (d) {
function removeNaN(e, c) {
if (e > 0) { return e; } else { return c; }
}
return {
date: d3.timeParse("%Y-%m-%d")(d.Date),
aapl: +d.AAPL,
tsla: +d.TSLA,
aapl_sma: removeNaN(+d.SMA_AAPL, +d.AAPL),
tsla_sma: removeNaN(+d.SMA_TSLA, +d.TSLA)
};
});
chart.setData(data);
</script>
</body>
</html>
我没有列出所有这些,而是创建了一个 diff 文件。 Download 并检查。左边是原代码。
我正在以 class 格式实现一些 D3 代码,以便我有一个可重复使用的图表。
报错如下:
GetElementsByClassName
returns 一个长度为 0 的 HTMLCollection,但要选择的元素已 class 正确编辑。
Circles
出现在 x0 和 yMax,而不是数据位置(问题可能与第一个有关)。
Text
未附加到圆圈且不可见(这可能在圆圈起作用时有效)。
我正在实施 this pretty much exactly as it looks,除了我将工具提示放在 2/4 的行上,并且我正在使用 class.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
<div id="graph"></div>
<script>
class Chart {
constructor(opts) {
this.data = opts.data;
this.element = opts.element;
}
draw() {
this.width = this.element.offsetWidth;
this.height = this.width / 2;
this.padding = 50;
this.margin = {
top: 20,
bottom: 20,
left: 30,
right: 50
};
this.element.innerHTML = '';
const svg = d3.select(this.element).append('svg');
svg.attr('width', this.width);
svg.attr('height', this.height);
this.plot = svg.append('g')
.attr('transform', `translate(${this.margin.left},${this.margin.top})`);
this.createScales();
this.addAxes();
this.addLine();
this.tTip();
}
createScales() {
this.keynames = d3.scaleOrdinal();
this.keynames.domain(Object.keys(this.data[0]).filter(key => key !== 'date'));
this.keymap = this.keynames.domain().map(
keyname => ({
name: keyname, values: this.data.map(
d => ({ date: d.date, key: +d[keyname] })
)
})
);
const m = this.margin;
const xExtent = d3.extent(this.data, d => d.date);
const yExtent = [0, d3.max(this.keymap, d => d3.max(d.values, function (v) { return v.key }))];
this.xScale = d3.scaleTime()
.range([0, this.width - m.right])
.domain(xExtent).nice();
this.yScale = d3.scaleLinear()
.range([this.height - (m.top + m.bottom), 0])
.domain(yExtent).nice();
}
addAxes() {
const m = this.margin;
const xAxis = d3.axisBottom()
.scale(this.xScale);
const yAxis = d3.axisLeft()
.scale(this.yScale);
this.plot.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${this.height - (m.top + m.bottom)})`)
.call(xAxis.ticks(8));
this.plot.append("g")
.attr("class", "y axis")
.call(yAxis.ticks(4))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.attr("fill", "black")
.style("text-anchor", "end")
.text("$USD");
}
addLine() {
const line = d3.line()
.x(d => this.xScale(d.date))
.y(d => this.yScale(d.key));
this.plot.append('g')
.selectAll('path')
.data(this.keymap)
.join('path')
.classed('line', true)
.attr('d', function (d) { return line(d.values) })
.style('stroke', this.lineColor || 'red')
.style('fill', 'none');
}
tTip(){
let mouseG = this.plot.append("g")
.attr("class", "mouse-over-effects");
mouseG.append("path")
.attr("class", "mouse-line")
.style("stroke", "rgba(50,50,50,1)")
.style("stroke-width", "0.5px")
.style("opacity", "0");
var lines = document.getElementsByClassName('.standard'); //issue here
let mousePerLine = mouseG.selectAll('.mouse-per-line')
.data(this.keymap)
.enter()
.append("g") //join instead of append?
.attr("class", "mouse-per-line");
mousePerLine.append("circle") //join instead of append?
.attr("r", 4)
.style("stroke", "black"
)
.style("fill", "blue"
)
.style("fill-opacity", "0.3")
.style("stroke-width", "1px")
.style("opacity", "0");
mousePerLine.append("text") //join instead of append?
.attr("transform", function(d){
if (d.name == 'aapl') {
return "translate(-50,30)"
} else {
return "translate(-50, -30)"
}
}).style("text-shadow",
" -2px -2px 0 #FFF, 0 -2px 0 #FFF, 2px -2px 0 #FFF, 2px 0 0 #FFF, 2px 2px 0 #FFF, 0 2px 0 #FFF,-2px 2px 0 #FFF,-2px 0 0 #FFF");
mouseG.append('svg:rect')
.attr('width', this.width)
.attr('height', this.height)
.attr('x', '0')
.attr('fill', 'none')
.attr('pointer-events', 'all')
.on('mouseout', function() {
d3.select(".mouse-line")
.style("opacity", "0");
d3.selectAll(".mouse-per-line circle")
.style("opacity", "0");
d3.selectAll(".mouse-per-line text")
.style("opacity", "0");
})
.on('mouseover', function() {
d3.select(".mouse-line")
.style("opacity", "1");
d3.selectAll(".mouse-per-line circle")
.style("opacity", "1");
d3.selectAll(".mouse-per-line text")
.style("opacity", "1");
})
.on('mousemove', () => {
let mouse = d3.pointer(event);
d3.select(".mouse-line")
.attr("d", () => {
let d = "M" + mouse[0] + "," + this.height;
d += " " + mouse[0] + "," + 0;
return d;
});
d3.selectAll(".mouse-per-line")
.attr("transform", (d, i) => {
let xDate = this.xScale.invert(mouse[0]),
bisect = d3.bisector(d => d.date).right,
idx = bisect(d.values, xDate);
let beginning = 0,
end = lines[i].getTotalLength(),
target = null;
while (true){
let target = Math.floor((beginning + end) / 2),
pos = lines[i].getPointAtLength(target); //issue here
if ((target === end || target === beginning) && pos.x !== mouse[0]) {
break;
}
if (pos.x > mouse[0]){
end = target;
}
else if (pos.x < mouse[0]){
beginning = target;
}
else break; //position found
}
d3.select(this).select('text')
.text( () => { "$" + this.yScale.invert(pos.y).toFixed(2)})
return "translate(" + mouse[0] + "," + pos.y +")";
})
.style('font-family', 'Helvetica')
.style('font-size', '11px')
.style('letter-spacing', '1px')
.style('text-transform', 'uppercase');
});
}
setColor(newColor) {
this.plot.select('.line')
.style('stroke', newColor);
this.lineColor = newColor;
}
setData(data) {
this.data = data;
this.draw();
}
}
const chart = new Chart({ element: document.querySelector('#graph') });
const data = d3.csvParse(`Date,AAPL,SMA_AAPL,TSLA,SMA_TSLA
2018-12-31,38.33848571777344,,66.55999755859375,
2019-01-02,38.382225036621094,,62.02399826049805,
2019-01-03,34.55907440185547,,60.071998596191406,
2019-01-04,36.03437805175781,,63.53799819946289,
2019-01-07,35.95417022705078,,66.99199676513672,
2019-01-08,36.63956832885742,,67.06999969482422,
2019-01-09,37.26177215576172,,67.70600128173828,
2019-01-10,37.380863189697266,,68.99400329589844,
2019-01-11,37.013858795166016,,69.4520034790039,
2019-01-14,36.4572868347168,,66.87999725341797,
2019-01-15,37.20343780517578,,68.88600158691406,
2019-01-16,37.657936096191406,,69.20999908447266,
2019-01-17,37.88154602050781,,69.46199798583984,
2019-01-18,38.11487579345703,,60.45199966430664,
2019-01-22,37.259342193603516,,59.784000396728516,
2019-01-23,37.410030364990234,,57.518001556396484,
2019-01-24,37.113521575927734,,58.301998138427734,
2019-01-25,38.34333801269531,,59.40800094604492,
2019-01-28,37.988487243652344,,59.2760009765625,
2019-01-29,37.59474182128906,,59.492000579833984,
2019-01-30,40.16377258300781,,61.75400161743164,
2019-01-31,40.453006744384766,,61.40399932861328,
2019-02-01,40.472450256347656,,62.44200134277344,
2019-02-04,41.622066497802734,,62.577999114990234,
2019-02-05,42.33420181274414,,64.2699966430664,
2019-02-06,42.34878158569336,,63.444000244140625,
2019-02-07,41.546722412109375,,61.50199890136719,
2019-02-08,41.59553909301758,,61.15999984741211,
2019-02-11,41.35633087158203,,62.56800079345703,
2019-02-12,41.71269989013672,38.606483713785806,62.36199951171875,63.48539975484212
2019-02-13,41.539398193359375,38.71318079630534,61.63399887084961,63.32119979858398
2019-02-14,41.69073486328125,38.823464457194014,60.75400161743164,63.278866577148435
2019-02-15,41.59797286987305,39.05809440612793,61.57600021362305,63.32899996439616
2019-02-19,41.72246551513672,39.247697321573895,61.12799835205078,63.24866663614909
2019-02-20,41.990962982177734,39.44892374674479,60.512001037597656,63.032666778564455
2019-02-21,41.75419616699219,39.619411341349284,58.24599838256836,62.738533401489256
2019-02-22,42.22041702270508,39.78469950358073,58.94200134277344,62.44640007019043
2019-02-25,42.5279655456543,39.95626958211263,59.75400161743164,62.13840001424153`, function (d) {
function removeNaN(e, c) {
if (e > 0) { return e; } else { return c; }
}
return {
date: d3.timeParse("%Y-%m-%d")(d.Date),
aapl: +d.AAPL,
tsla: +d.TSLA,
aapl_sma: removeNaN(+d.SMA_AAPL, +d.AAPL),
tsla_sma: removeNaN(+d.SMA_TSLA, +d.TSLA)
};
});
chart.setData(data);
</script>
</body>
</html>
如您所见,鼠标交互非常糟糕,所以我希望有人能提供帮助。
箭头函数和正则函数之间存在一些
修复了一些错误:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
<div id="graph"></div>
<script>
let pos = null;
class Chart {
constructor(opts) {
this.data = opts.data;
this.element = opts.element;
}
draw() {
this.width = this.element.offsetWidth;
this.height = this.width / 2;
this.padding = 50;
this.margin = {
top: 20,
bottom: 20,
left: 30,
right: 50
};
this.element.innerHTML = '';
const svg = d3.select(this.element).append('svg');
svg.attr('width', this.width);
svg.attr('height', this.height);
this.plot = svg.append('g')
.attr('transform', `translate(${this.margin.left},${this.margin.top})`);
this.createScales();
this.addAxes();
this.addLine();
this.tTip();
}
createScales() {
this.keynames = d3.scaleOrdinal();
this.keynames.domain(Object.keys(this.data[0]).filter(key => key !== 'date'));
this.keymap = this.keynames.domain().map(
keyname => ({
name: keyname, values: this.data.map(
d => ({ date: d.date, key: +d[keyname] })
)
})
);
const m = this.margin;
const xExtent = d3.extent(this.data, d => d.date);
const yExtent = [0, d3.max(this.keymap, d => d3.max(d.values, function (v) { return v.key }))];
this.xScale = d3.scaleTime()
.range([0, this.width - m.right])
.domain(xExtent).nice();
this.yScale = d3.scaleLinear()
.range([this.height - (m.top + m.bottom), 0])
.domain(yExtent).nice();
}
addAxes() {
const m = this.margin;
const xAxis = d3.axisBottom()
.scale(this.xScale);
const yAxis = d3.axisLeft()
.scale(this.yScale);
this.plot.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${this.height - (m.top + m.bottom)})`)
.call(xAxis.ticks(8));
this.plot.append("g")
.attr("class", "y axis")
.call(yAxis.ticks(4))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.attr("fill", "black")
.style("text-anchor", "end")
.text("$USD");
}
addLine() {
const line = d3.line()
.x(d => this.xScale(d.date))
.y(d => this.yScale(d.key));
this.plot.append('g')
.selectAll('path')
.data(this.keymap)
.join('path')
.classed('line', true)
.attr('d', function (d) { return line(d.values) })
.style('stroke', this.lineColor || 'red')
.style('fill', 'none');
}
tTip() {
let mouseG = this.plot.append("g")
.attr("class", "mouse-over-effects");
mouseG.append("path")
.attr("class", "mouse-line")
.style("stroke", "rgba(50,50,50,1)")
.style("stroke-width", "0.5px")
.style("opacity", "0");
var lines = document.getElementsByClassName('line'); //issue here
let mousePerLine = mouseG.selectAll('.mouse-per-line')
.data(this.keymap)
.enter()
.append("g") //join instead of append?
.attr("class", "mouse-per-line");
mousePerLine.append("circle") //join instead of append?
.attr("r", 4)
.style("stroke", "black"
)
.style("fill", "blue"
)
.style("fill-opacity", "0.3")
.style("stroke-width", "1px")
.style("opacity", "0");
mousePerLine.append("text") //join instead of append?
.attr("transform", function (d) {
if (d.name == 'aapl') {
return "translate(-50,30)"
} else {
return "translate(-50, -30)"
}
}).style("text-shadow",
" -2px -2px 0 #FFF, 0 -2px 0 #FFF, 2px -2px 0 #FFF, 2px 0 0 #FFF, 2px 2px 0 #FFF, 0 2px 0 #FFF,-2px 2px 0 #FFF,-2px 0 0 #FFF");
mouseG.append('svg:rect')
.attr('width', this.width)
.attr('height', this.height)
.attr('x', '0')
.attr('fill', 'none')
.attr('pointer-events', 'all')
.on('mouseout', function () {
d3.select(".mouse-line")
.style("opacity", "0");
d3.selectAll(".mouse-per-line circle")
.style("opacity", "0");
d3.selectAll(".mouse-per-line text")
.style("opacity", "0");
})
.on('mouseover', function () {
d3.select(".mouse-line")
.style("opacity", "1");
d3.selectAll(".mouse-per-line circle")
.style("opacity", "1");
d3.selectAll(".mouse-per-line text")
.style("opacity", "1");
})
.on('mousemove', () => {
let mouse = d3.pointer(event);
d3.select(".mouse-line")
.attr("d", () => {
let d = "M" + mouse[0] + "," + this.height;
d += " " + mouse[0] + "," + 0;
return d;
});
d3.selectAll(".mouse-per-line")
.attr("transform", function (d, i) {
let xDate = chart.xScale.invert(mouse[0]),
bisect = d3.bisector(d => d.date).right,
idx = bisect(d.values, xDate);
let beginning = 0,
end = lines[i].getTotalLength(),
target = null;
while (true) {
let target = Math.floor((beginning + end) / 2);
pos = lines[i].getPointAtLength(target); //issue here
if ((target === end || target === beginning) && pos.x !== mouse[0]) {
break;
}
if (pos.x > mouse[0]) {
end = target;
}
else if (pos.x < mouse[0]) {
beginning = target;
}
else break; //position found
}
d3.select(this).select('text')
.text("$" + chart.yScale.invert(pos.y).toFixed(2))
return "translate(" + mouse[0] + "," + pos.y + ")";
})
.style('font-family', 'Helvetica')
.style('font-size', '11px')
.style('letter-spacing', '1px')
.style('text-transform', 'uppercase');
});
}
setColor(newColor) {
this.plot.select('.line')
.style('stroke', newColor);
this.lineColor = newColor;
}
setData(data) {
this.data = data;
this.draw();
}
}
const chart = new Chart({ element: document.querySelector('#graph') });
const data = d3.csvParse(`Date,AAPL,SMA_AAPL,TSLA,SMA_TSLA
2018-12-31,38.33848571777344,,66.55999755859375,
2019-01-02,38.382225036621094,,62.02399826049805,
2019-01-03,34.55907440185547,,60.071998596191406,
2019-01-04,36.03437805175781,,63.53799819946289,
2019-01-07,35.95417022705078,,66.99199676513672,
2019-01-08,36.63956832885742,,67.06999969482422,
2019-01-09,37.26177215576172,,67.70600128173828,
2019-01-10,37.380863189697266,,68.99400329589844,
2019-01-11,37.013858795166016,,69.4520034790039,
2019-01-14,36.4572868347168,,66.87999725341797,
2019-01-15,37.20343780517578,,68.88600158691406,
2019-01-16,37.657936096191406,,69.20999908447266,
2019-01-17,37.88154602050781,,69.46199798583984,
2019-01-18,38.11487579345703,,60.45199966430664,
2019-01-22,37.259342193603516,,59.784000396728516,
2019-01-23,37.410030364990234,,57.518001556396484,
2019-01-24,37.113521575927734,,58.301998138427734,
2019-01-25,38.34333801269531,,59.40800094604492,
2019-01-28,37.988487243652344,,59.2760009765625,
2019-01-29,37.59474182128906,,59.492000579833984,
2019-01-30,40.16377258300781,,61.75400161743164,
2019-01-31,40.453006744384766,,61.40399932861328,
2019-02-01,40.472450256347656,,62.44200134277344,
2019-02-04,41.622066497802734,,62.577999114990234,
2019-02-05,42.33420181274414,,64.2699966430664,
2019-02-06,42.34878158569336,,63.444000244140625,
2019-02-07,41.546722412109375,,61.50199890136719,
2019-02-08,41.59553909301758,,61.15999984741211,
2019-02-11,41.35633087158203,,62.56800079345703,
2019-02-12,41.71269989013672,38.606483713785806,62.36199951171875,63.48539975484212
2019-02-13,41.539398193359375,38.71318079630534,61.63399887084961,63.32119979858398
2019-02-14,41.69073486328125,38.823464457194014,60.75400161743164,63.278866577148435
2019-02-15,41.59797286987305,39.05809440612793,61.57600021362305,63.32899996439616
2019-02-19,41.72246551513672,39.247697321573895,61.12799835205078,63.24866663614909
2019-02-20,41.990962982177734,39.44892374674479,60.512001037597656,63.032666778564455
2019-02-21,41.75419616699219,39.619411341349284,58.24599838256836,62.738533401489256
2019-02-22,42.22041702270508,39.78469950358073,58.94200134277344,62.44640007019043
2019-02-25,42.5279655456543,39.95626958211263,59.75400161743164,62.13840001424153`, function (d) {
function removeNaN(e, c) {
if (e > 0) { return e; } else { return c; }
}
return {
date: d3.timeParse("%Y-%m-%d")(d.Date),
aapl: +d.AAPL,
tsla: +d.TSLA,
aapl_sma: removeNaN(+d.SMA_AAPL, +d.AAPL),
tsla_sma: removeNaN(+d.SMA_TSLA, +d.TSLA)
};
});
chart.setData(data);
</script>
</body>
</html>
我没有列出所有这些,而是创建了一个 diff 文件。 Download 并检查。左边是原代码。