如何解决在画布图表的每个蜡烛上方放置几个符号
创建了我所需的简化示例。该系列中有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);
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((_,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('+',-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以获得更多详细信息和示例。
https://github.com/d3fc/d3fc/issues/1635
解决方法
首先,scale.invert
用于具有像素坐标但不具有相应基准值的情况。例如,当您在屏幕上单击时,这是相关的。在这种情况下,它不是完成这项工作的正确工具。您已经可以访问datum
,因此只需致电scaleY(datum.close)
。
但是,d3fc
使用canvas.translate()
将零点更改为.mainvalue()
。因此,您不能简单地使用scaleY(datum.close)
来获取关闭值的y坐标。您要绘制的每个点将变为(x,y)
,而不是(0,y(arrow) - y(d.close))
。因此,您需要更改逻辑以反映这一点。
需要自己动手清理context.translate()
是解决错误的秘诀,避免这种情况几乎总是更好。
最后一点,我对您的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('+',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>
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。