javascript - 带有双向图表的 D3 v4 图表
问题描述
如何用 D3 创建这个图表?任何帮助都将有助于对高图表进行全面尝试,但对完整的点击事件不起作用添加了此向下钻取事件,但对条形图或 y 轴标签的点击不起作用。
const data = [
{ "name": 'IT', "value": 20, "negativeValue": -80 },
{ "name": 'Capital Invest', "value": 30, "negativeValue": -70 },
{ "name": 'Infrastructure', "value": 40, "negativeValue": -60 }
];
Highcharts.setOptions({
lang: {
drillUpText: `◁ Back to {series.description}`,
},
});
Highcharts.chart({
chart: {
type: 'bar',
renderTo: 'alignmentChart',
height: 530,
marginRight: 20,
backgroundColor: 'transparent',
events: {
drilldown(e: any) {
if (e.seriesOptions.fits) {
linesPositive = e.seriesOptions.line;
} else {
lineNegative = e.seriesOptions.line;
}
labels = !!e.seriesOptions && e.seriesOptions.data.map(a => a.name);
},
drillup(e: any) {
if (e.seriesOptions.fits) {
linesPositive = e.seriesOptions.line;
} else {
lineNegative = e.seriesOptions.line;
}
labels = !!e.seriesOptions && e.seriesOptions.data.map(a => a.name);
},
},
},
title: {
text: '',
},
colors: ['#f7a704', '#458dde'],
// tooltip: this.getTooltip(this),
xAxis: {
reversed: false,
tickPositions: Array.from(Array(this.multi.positive.length).keys()),
labels: {
useHTML: true,
formatter() {
return `<span title="${labels[this.value]}">${labels[this.value]}</span>`;
},
style: {
color: '#000000',
},
step: 1,
},
lineWidth: 0,
tickWidth: 0,
},
yAxis: {
title: {
text: null,
},
max: 100,
min: -100,
plotLines: [{
color: '#e5e5e5',
value: 0,
width: 1,
zIndex: 20,
}],
lineWidth: 1,
gridLineWidth: 0,
tickWidth: 1,
// offset: 100,
labels: {
y: 30,
align: 'center',
},
},
plotOptions: {
bar: {
pointWidth: 12,
},
series: {
stacking: 'normal',
dataLabels: {
enabled: true,
color: '#6b6b6b',
style: {
fontSize: '12px',
fontFamily: 'Proxima Nova'
},
formatter() {
return '';
},
inside: false,
},
},
},
series: [{
name: 'Fits Role',
description: 'Subfunctions',
data: this.multi.positive,
type: undefined
}, {
name: 'Not Fit Role',
description: 'Subfunctions',
data: this.multi.negative,
type: undefined
}],
drilldown: {
allowPointDrilldown: false,
activeAxisLabelStyle: {
fontSize: '12px',
fontWeight: 'bold',
color: '#007bc7',
textDecoration: 'none',
},
series: this.multi.drilldowns,
},
credits: {
enabled: false,
},
legend: {
enabled: false,
},
exporting: {
enabled: false,
},
});
解决方案
与我分享的答案相比,我只需要进行很少的更改。正如我在评论中所说,我为每个项目创建一个g
节点,并为每个项目绘制两个rect
s。
然后我将rect
s 更新为具有相同的基准形状 ( { name: string, value: number }
),无论它是正数还是负数。这让我可以对这两种类型做完全相同的事情。
// Now, the data can also be negative
const data = [{
"name": 'IT',
"value": 20,
"negativeValue": -80
}, {
"name": 'Capital Invest',
"value": 30,
"negativeValue": -70
}, {
"name": 'Infrastructure',
"value": 40,
"negativeValue": -60
}];
const width = 600,
height = 300,
margin = {
top: 20,
left: 100,
right: 40,
bottom: 40
};
// Now, we don't use 0 as a minimum, but get it from the data using d3.extent
const x = d3.scaleLinear()
.domain([-100, 100])
.range([0, width]);
const y = d3.scaleBand()
.domain(data.map(d => d.name))
.range([height, 0])
.padding(0.1);
const svg = d3.select('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
const g = svg
.append('g')
.attr('transform', `translate(${margin.left} ${margin.right})`);
// One group per data entry, each holding two bars
const barGroups = g
.selectAll('.barGroup')
.data(data);
barGroups.exit().remove();
const newBarGroups = barGroups.enter()
.append('g')
.attr('class', 'barGroup');
// Append one bar for the positive value, and one for the negative one
newBarGroups
.append('rect')
.attr('class', 'positive')
.attr('fill', 'darkgreen');
newBarGroups
.append('rect')
.attr('class', 'negative')
.attr('fill', 'darkred');
const positiveBars = newBarGroups
.merge(barGroups)
.select('.positive')
.datum(d => ({
name: d.name,
value: d.value
}));
const negativeBars = newBarGroups
.merge(barGroups)
.select('.negative')
.datum(d => ({
name: d.name,
value: d.negativeValue
}));
newBarGroups.selectAll('rect')
// If a bar is positive it starts at x = 0, and has positive width
// If a bar is negative it starts at x < 0 and ends at x = 0
.attr('x', d => d.value > 0 ? x(0) : x(d.value))
.attr('y', d => y(d.name))
// If the bar is positive it ends at x = v, but that means it's x(v) - x(0) wide
// If the bar is negative it ends at x = 0, but that means it's x(0) - x(v) wide
.attr('width', d => d.value > 0 ? x(d.value) - x(0) : x(0) - x(d.value))
.attr('height', y.bandwidth())
// Let's color the bar based on whether the value is positive or negative
g.append('g')
.classed('x-axis', true)
.attr('transform', `translate(0, ${height})`)
.call(d3.axisBottom(x))
g.append('g')
.classed('y-axis', true)
.attr('transform', `translate(${x(0)}, 0)`)
.call(d3.axisLeft(y))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg></svg>
或者,您可以在不合并选择的情况下执行此操作,如下所示:
// Now, the data can also be negative
const data = [{
"name": 'IT',
"value": 20,
"negativeValue": -80
}, {
"name": 'Capital Invest',
"value": 30,
"negativeValue": -70
}, {
"name": 'Infrastructure',
"value": 40,
"negativeValue": -60
}];
const width = 600,
height = 300,
margin = {
top: 20,
left: 100,
right: 40,
bottom: 40
};
// Now, we don't use 0 as a minimum, but get it from the data using d3.extent
const x = d3.scaleLinear()
.domain([-100, 100])
.range([0, width]);
const y = d3.scaleBand()
.domain(data.map(d => d.name))
.range([height, 0])
.padding(0.1);
const svg = d3.select('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
const g = svg
.append('g')
.attr('transform', `translate(${margin.left} ${margin.right})`);
// One group per data entry, each holding two bars
const positiveBars = g
.selectAll('.positive')
.data(data);
positiveBars.exit().remove();
positiveBars.enter()
.append('rect')
.attr('class', 'positive')
.attr('fill', 'darkgreen')
.merge(positiveBars)
.attr('x', x(0))
.attr('y', d => y(d.name))
// The bar is positive. It ends at x = v, but that means it's x(v) - x(0) wide
.attr('width', d => x(d.value) - x(0))
.attr('height', y.bandwidth());
const negativeBars = g
.selectAll('.negative')
.data(data);
negativeBars.exit().remove();
negativeBars.enter()
.append('rect')
.attr('class', 'negative')
.attr('fill', 'darkred')
.merge(negativeBars)
.attr('x', d => x(d.negativeValue))
.attr('y', d => y(d.name))
// The bar is negative. It ends at x = 0, but that means it's x(0) - x(v) wide
.attr('width', d => x(0) - x(d.negativeValue))
.attr('height', y.bandwidth());
g.append('g')
.classed('x-axis', true)
.attr('transform', `translate(0, ${height})`)
.call(d3.axisBottom(x))
g.append('g')
.classed('y-axis', true)
.attr('transform', `translate(${x(0)}, 0)`)
.call(d3.axisLeft(y))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg></svg>
推荐阅读
- c# - 有没有办法从 DataTable 填充 DataGrid 中的 DataGridComboBoxColumn?
- jquery - 如何在 JQuery 中解决此旋转图像问题?
- c# - 使用 MQL 的 WIC (exif) 元数据查询语法
- ios - iOS上的Pjsip视频解码问题
- memory - TYPO3 Extbase:clearState 不释放内存
- scala - 如果过去 1 小时内没有推送任何记录,如何设置火花流作业警报?
- azure-data-factory - 为未在监视器中显示的 ADFv2 活动定义的用户属性
- c# - 任务用 .dll 方法阻塞 UI 线程
- javascript - 如何使我的 javascript 滑块工作?
- c# - 使用 OfType 与 GetType == typeof 搜索列表