创建了一个简单的示例来说明我需要什么。这个系列有50个项目。每个项目都有一个由五个+符号组成的垂直列表。
const data = [...Array(50).keys()];
const container = document.querySelector('d3fc-canvas');
const xScale = d3.scaleLinear().domain([0, data.length - 1]);
const yScale = d3.scaleLinear().domain(fc.extentLinear()(data));
const series = fc
.seriesCanvasPoint()
.xScale(xScale)
.yScale(yScale)
.crossValue((_, i) => i)
.mainValue(d => d)
.size((_, i) => 0)
.decorate((context, datum) => {
// Draw 5 symbols above current value
for (let i = 0; i < 5; i++) {
const y = yScale.invert(datum + i)
context.textAlign = 'center';
context.fillStyle = '#000';
context.font = '25px Arial';
context.fillText('+', 0, 0);
context.translate(0, y);
}
});
d3.select(container)
.on('draw', () => {
series(data);
})
.on('measure', () => {
const {
width,
height
} = event.detail;
xScale.range([0, width]);
yScale.range([height, 0]);
const ctx = container.querySelector('canvas').getContext('2d');
series.context(ctx);
});
container.requestRedraw();body {
color: #1b1e23;
font-size: small;
font-family: sans-serif;
height: calc(100vh - 2em);
margin: 1em;
display: flex;
}
body>* {
flex: auto;
}
.domain,
.gridline-y,
.gridline-x,
.annotation-line>line {
stroke: currentColor;
stroke-opacity: 0.1;
}<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://unpkg.com/d3fc/build/d3fc.js"></script>
<d3fc-canvas use-device-pixel-ratio></d3fc-canvas>
现在,我试图使它与实际的财务图表工作,并将这五个+符号垂直放置在每支蜡烛内。它不起作用,因为我不知道如何正确地将每个+的Y值转换为画布的坐标系。
const stream = fc.randomFinancial().stream();
const data = stream.take(20);
const scaleX = d3.scalePoint();
const scaleY = d3.scaleLinear();
const yExtent = fc.extentLinear().accessors([d => d.high, d => d.low]);
const xExtent = fc.extentLinear().accessors([d => d.date.getTime()]);
const gridlines = fc.annotationCanvasGridline();
const candlestick = fc.seriesCanvasCandlestick()
.crossValue((o, i) => o.date.getTime())
.lowValue((o, i) => o.low)
.highValue((o, i) => o.high)
.openValue((o, i) => o.open)
.closeValue((o, i) => o.close);
const points = fc
.seriesCanvasPoint()
.crossValue((o, i) => o.date.getTime())
.mainValue(d => d.close)
.size((_, i) => 0)
.decorate((context, datum) => {
const arrows = getMarks(datum.open, datum.close);
//context.translate(0, 0);
// Draw 5 symbols above current value
for (let i = 0; i < arrows.length; i++) {
const posCurrent = scaleY.invert(arrows[i])
//context.translate(0, 15);
context.translate(0, posCurrent); // move to current arrow's position
context.textAlign = 'center';
context.fillStyle = '#000';
context.font = '25px Arial';
context.fillText('+', 0, 0);
context.translate(0, -posCurrent); // reset and go back to start point
}
});
const multi = fc
.seriesCanvasMulti()
.series([gridlines, candlestick, points]);
const chart = fc
.chartCartesian(scaleX, scaleY)
.canvasPlotArea(multi);
function getMarks(open, close) {
let price = close;
const arrows = [];
const gap = Math.abs(close - open) / 5;
for (let i = 0; i < 5; i++) {
arrows.push(close > open ? price -= gap : price += gap);
}
return arrows;
}
function renderChart() {
data.push(stream.next());
data.shift();
scaleX.domain(data.map(o => o.date.getTime()));
scaleY.domain(yExtent(data));
chart
.xDomain(scaleX.domain())
.yDomain(scaleY.domain());
d3.select('#chart')
.datum(data)
.call(chart);
}
renderChart();
setInterval(renderChart, 1000);#chart {
height: 500px;
}<script src="https://unpkg.com/d3"></script>
<script src="https://unpkg.com/d3fc"></script>
<div id="chart"></div>
有办法在蜡烛上正确地显示这些标记吗?
有关更多细节和例子,请参考Github。
发布于 2020-10-24 10:38:20
首先,scale.invert是指当你有像素坐标,而不是相应的基准值。例如,当您在屏幕上单击时,这是相关的。在这种情况下,它不是适合这项工作的工具。您已经可以访问datum,所以只需调用scaleY(datum.close)即可。
但是,d3fc使用canvas.translate()将零点更改为.mainvalue()。因此,不能简单地使用scaleY(datum.close)来获得关闭值的y坐标。而不是(x, y),您想要画的每一个点都变成(0, y(arrow) - y(d.close))。所以你需要改变逻辑来反映这一点。
需要在使用context.translate()之后进行清理是一个bug的秘诀,避免这样的事情几乎总是更好。
最后,我对您的getMarks函数有一些困惑。如果你想画5个箭头均匀分布在你的酒吧,你需要有一个1/4的杆高的差距。把它想象成街上的树。如果树相隔10英尺,街道有100英尺长,你就会有11棵树,而不是10棵。正因为如此,我画了6个附加的标志,而不是5个,因为否则它看起来有点不平衡。
const stream = fc.randomFinancial().stream();
const data = stream.take(20);
const scaleX = d3.scalePoint();
const scaleY = d3.scaleLinear();
const yExtent = fc.extentLinear().accessors([d => d.high, d => d.low]);
const xExtent = fc.extentLinear().accessors([d => d.date.getTime()]);
const gridlines = fc.annotationCanvasGridline();
const candlestick = fc.seriesCanvasCandlestick()
.crossValue((o, i) => o.date.getTime())
.lowValue((o, i) => o.low)
.highValue((o, i) => o.high)
.openValue((o, i) => o.open)
.closeValue((o, i) => o.close);
const points = fc
.seriesCanvasPoint()
.crossValue((o, i) => o.date.getTime())
.mainValue(d => d.close)
.size((_, i) => 0)
.decorate((context, datum) => {
const arrows = getMarks(datum.open, datum.close);
const top = scaleY(datum.close);
// We need a little bit of offset, because the "+" is 20 pixels high
// and we want it in the middle of the point, not above it.
const offset = 10;
// Draw 5 symbols above current value
for (let i = 0; i < arrows.length; i++) {
context.textAlign = 'center';
context.fillStyle = '#000';
context.font = '25px Arial';
context.fillText('+', 0, scaleY(arrows[i]) - top + offset);
}
});
const multi = fc
.seriesCanvasMulti()
.series([gridlines, candlestick, points]);
const chart = fc
.chartCartesian(scaleX, scaleY)
.canvasPlotArea(multi);
function getMarks(open, close) {
let price = Math.min(open, close);
const arrows = [];
const gap = Math.abs(close - open) / 5;
for (let i = 0; i < 6; i++) {
arrows.push(price + i * gap);
}
return arrows;
}
function renderChart() {
data.push(stream.next());
data.shift();
scaleX.domain(data.map(o => o.date.getTime()));
scaleY.domain(yExtent(data));
chart
.xDomain(scaleX.domain())
.yDomain(scaleY.domain());
d3.select('#chart')
.datum(data)
.call(chart);
}
renderChart();
setInterval(renderChart, 1000);#chart {
height: 500px;
}<script src="https://unpkg.com/d3"></script>
<script src="https://unpkg.com/d3fc"></script>
<div id="chart"></div>
https://stackoverflow.com/questions/64475739
复制相似问题