我正在尝试使用Plotly.js创建一个类似于该图中的图表的图表:
这是一个带有两个y轴的分组箱线图(按站点,目前只有一个)。
我设法创建了两个版本,但两个版本都不起作用:
这是方法1(https://codepen.io/wacmemphis/pen/gJQJeO?editors=0010)中的代码
var data =[
{
"x":[
"Site 1",
"Site 1",
"Site 1",
"Site 1",
"Site 1",
"Site 1"
],
"xaxis":"x",
"yaxis":"y",
"name":"A",
"type":"box",
"boxpoints":false,
"y":[
"3.81",
"3.74",
"3.62",
"3.50",
"3.50",
"3.54"
]
},
{
"x":[
"Site 1",
"Site 1",
"Site 1",
"Site 1",
"Site 1",
"Site 1"
],
"xaxis":"x",
"yaxis":"y",
"name":"B",
"type":"box",
"boxpoints":false,
"y":[
"1.54",
"1.54",
"1.60",
"1.41",
"1.65",
"1.47"
]
},
{
"x":[
"Site 1",
"Site 1",
"Site 1",
"Site 1",
"Site 1",
"Site 1"
],
"xaxis":"x",
"yaxis":"y",
"name":"C",
"type":"box",
"boxpoints":false,
"y":[
"3.31",
"3.81",
"3.74",
"3.63",
"3.76",
"3.68"
]
},
{
"x":[
"Site 1",
"Site 1",
"Site 1",
"Site 1",
"Site 1",
"Site 1"
],
"xaxis":"x2",
"yaxis":"y2",
"name":"A",
"type":"box",
"boxpoints":false,
"y":[
"3.81",
"3.74",
"3.62",
"3.50",
"3.50",
"3.54"
]
},
{
"x":[
"Site 1",
"Site 1",
"Site 1",
"Site 1",
"Site 1",
"Site 1"
],
"xaxis":"x2",
"yaxis":"y2",
"name":"C",
"type":"box",
"boxpoints":false,
"y":[
"3.31",
"3.81",
"3.74",
"3.63",
"3.76",
"3.68"
]
}
];
var layout = {
yaxis: {
domain: [0, 0.5],
title: 'axis 1',
},
yaxis2: {
domain: [0.5, 1],
title: 'axis2',
},
boxmode: 'group'
};
Plotly.newPlot('myDiv', data, layout);
有人有什么想法吗?
答案 0 :(得分:1)
首先,我想强调一点,这是一个 workaraound ,因为Plotly当前不支持将单个数据源分布到多轴,而不将其解释为新的跟踪实例(尽管最好设置一个目标轴数组,例如{ yaxis: [ "y", "y2" ] }
)。
但是,Plotly在处理轨迹的排序和分组方面具有非常确定性,这可以使我们受益。
以下变通办法以下列方式解决该问题:
A
,B
,C
)使用单一数据源假设我们可以使用两个布局相同的图表:
<head>
<!-- Plotly.js -->
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<!-- render the upper axis 2 chart -->
<div id="myDiv_upper"></div>
<!-- render the lower axis 1 chart -->
<div id="myDiv_lower"></div>
<script>
/* JAVASCRIPT CODE GOES HERE */
</script>
</body>
使用随附的js代码创建具有给定布局的两个初始空图表:
const myDiv = document.getElementById("myDiv_lower");
const myDiv2 = document.getElementById("myDiv_upper");
const layout = {
yaxis: {
domain: [0, 0.5],
title: "axis 1",
constrain: "range"
},
margin: {
t: 0,
b: 0,
pad: 0
},
showlegend: false,
boxmode: "group"
};
const layout2 = {
yaxis: {
domain: [ 0.5, 1 ],
title: "axis 2",
},
xaxis: {
domain: [ 0, 1 ]
},
margin: {
t: 0,
b: 0,
pad: 0
},
boxmode: "group"
};
Plotly.newPlot(myDiv, [], layout);
Plotly.newPlot(myDiv2, [], layout2);
如果未添加其他数据,则生成的空图将如下所示:
A
,B
,C
)然后我们可以将数据分为三个主要的源对象:
const A = {
x: ["Site 1", "Site 1", "Site 1", "Site 1", "Site 1", "Site 1"],
xaxis: "x",
yaxis: "y",
name: "A",
legendgroup: "A",
type: "box",
boxpoints: false,
y: ["3.81", "3.74", "3.62", "3.50", "3.50", "3.54"]
};
const B = {
x: ["Site 1", "Site 1", "Site 1", "Site 1", "Site 1", "Site 1"],
xaxis: "x",
yaxis: "y",
name: "B",
legendgroup: "B",
type: "box",
boxpoints: false,
y: ["1.54", "1.54", "1.60", "1.41", "1.65", "1.47"]
};
const C = {
x: ["Site 1", "Site 1", "Site 1", "Site 1", "Site 1", "Site 1"],
xaxis: "x",
yaxis: "y",
name: "C",
legendgroup: "C",
type: "box",
boxpoints: false,
y: ["3.31", "3.81", "3.74", "3.63", "3.76", "3.68"]
}
首先,我们创建一个助手add
,该助手根据新的传入数据更新图表,并创建一个名为placeholder
的幽灵对象助手:
const placeholder = src => {
const copy = Object.assign({}, src)
// use one of the strategies here to make this a ghost object
return copy
}
const add = ({ src, y1, y2 }) => {
let src2
if (y1 && y2) {
Plotly.addTraces(myDiv, [src])
Plotly.addTraces(myDiv2, [src])
} else if (y1 && !y2) {
src2 = placeholder(src)
Plotly.addTraces(myDiv, [src])
Plotly.addTraces(myDiv2, [src2])
} else if (!y1 && y2) {
src2 = placeholder(src)
Plotly.addTraces(myDiv, [src2])
Plotly.addTraces(myDiv2, [src])
} else {
throw new Error('require either y1 or y2 to be true to add data')
}
}
根据给定的图像,将数据添加到轴的决定将导致以下调用:
add({ src: A, y1: true, y2: true })
add({ src: B, y1: true, y2: false })
add({ src: C, y1: true, y2: true })
这将产生以下结果(但尚不能令人满意):
现在,我们至少已经解决了分组和颜色问题。下一步是寻找制作B
幻影对象的可能方法,该幻影对象需要在上部图表中留有间距,但不会显示数据。
在研究不同的选项之前,让我们看看如果删除数据或将数据为空会发生什么情况。
删除数据
删除数据将意味着placeholder
没有x / y值:
const placeholder = src => {
const copy = Object.assign({}, src)
delete copy.x
delete copy.y
return copy
}
结果仍然不能满足要求:
清空数据
取消数据的效果很好,将数据添加到图例中(与visible: 'legendonly'
基本上具有相同的效果:
const placeholder = src => {
const copy = Object.assign({}, src)
copy.x = [null]
copy.y = [null]
return copy
}
尽管至少图例分组现在是正确的,但结果仍然不能满足要求:
a)使用不透明
创建幻影对象的一种方法是将其不透明度设置为零:
const placeholder = src => {
const copy = Object.assign({}, src)
copy.opacity = 0
copy.hoverinfo = "none" // use "name" to show "B"
return copy
}
结果具有以下优势:将对象摆放到正确的位置。一个很大的缺点是,图例对B的透明性与对象的不透明度有关,并且仅显示标签B
,而没有显示彩色框。
另一个缺点是B
的数据仍然会影响yaxis
的缩放比例:
b)使用最小宽度
使用最小的零值会导致痕迹几乎消失,而仍然保留一小行。
const placeholder = src => {
const copy = Object.assign({}, src)
copy.width = 0.000000001
copy.hoverinfo = "none" // or use "name"
return copy
}
此示例使分组,定位和图例保持正确,但是缩放比例仍然受到影响,剩余的行可能会被误解(这可能是非常有问题的IMO):
c)使用阈值
现在,这是唯一可以满足所有要求的解决方案:它需要在yaxis上设置range
:
const layout2 = {
yaxis: {
domain: [ 0.5, 1 ],
title: "axis 2",
range: [3.4, 4] // this is hardcoded for now
},
xaxis: {
domain: [ 0, 1 ]
},
margin: {
t: 0,
b: 0,
pad: 0
},
boxmode: "group"
}
// ...
// with ranges we can safely add
// data to both charts, because they
// get ghosted, based on their fit
// within / outside the range
const add = ({ src }) => {
Plotly.addTraces(myDiv, [src])
Plotly.addTraces(myDiv2, [src])
}
add({ src: A })
add({ src: B })
add({ src: C })
结果将如下所示:
现在唯一的问题仍然是,在添加新数据之后如何确定范围?幸运的是,Plotly提供了一个用于更新布局的功能,名为Plotly.relayout
。
在此示例中,我们可以选择一个简单的锚点,例如均值。当然,可以使用任何其他方法来确定范围。
const add = ({ src }) => {
Plotly.addTraces(myDiv, [src])
Plotly.addTraces(myDiv2, [src])
return src.y
}
// add the data and generate a sum of all values
const avalues = add({ src: A })
const bvalues = add({ src: B })
const cvalues = add({ src: C })
const allValues = [].concat(avalues, bvalues, cvalues)
// some reusable helpers to determine our range
const highest = arr => Math.max.apply( Math, arr )
const mean = arr => arr.reduce((a, b) => Number(a) + Number(b), 0) / arr.length
const upperRange = highest(allValues) // 3.81
const meanRange = mean(allValues) // 2.9361111111111113
// our new values to update the upper layour
const updatedLayout = {
yaxis: {
range: [meanRange, upperRange]
}
}
Plotly.relayout(myDiv2, updatedLayout)
生成的图看起来最像期望的结果:
您可以根据需要使用此链接进行修改和改进:https://codepen.io/anon/pen/agzKBV?editors=1010
此示例仍被认为是解决方法,未经超出给定数据的测试。在可重用性和代码效率方面也有改进的余地,并且以顺序的方式将其全部写下以使该代码尽可能容易理解。
还请记住,在两个不同的轴上显示相同的数据可能会误导为将其解释为两组不同的数据。
允许任何改进建议,免费使用代码。