我正在尝试在Python中使用Altair制作条形图,其中条形的宽度根据源数据帧的一列中的数据而变化。最终目标是获得像这样的图表:
条形图的高度对应于每种能源技术的边际成本(在源数据框中以列的形式给出)。条形宽度对应于每种能源技术的容量(也在源数据框中以列的形式给出)。颜色也是源数据帧中的序数数据。条形按边际成本的升序排序。 (这样的图在能源行业中被称为“发电堆栈”)。如下面的代码所示,这在matplotlib中很容易实现:
import matplotlib.pyplot as plt
# Make fake dataset
height = [3, 12, 5, 18, 45]
bars = ('A', 'B', 'C', 'D', 'E')
# Choose the width of each bar and their positions
width = [0.1,0.2,3,1.5,0.3]
y_pos = [0,0.3,2,4.5,5.5]
# Make the plot
plt.bar(y_pos, height, width=width)
plt.xticks(y_pos, bars)
plt.show()
(来自https://python-graph-gallery.com/5-control-width-and-space-in-barplots/的代码)
但是Altair有办法做到这一点吗?我想用Altair做到这一点,所以我仍然可以获得Altair的其他重要功能,例如工具提示,选择器/绑定,因为我想在条形图旁边显示许多其他数据。
源数据的前20行如下:
(与上面显示的图表不完全匹配)。
答案 0 :(得分:4)
在Altair中,执行此操作的方法是使用JSpinner
标记并显式构造钢筋。这是一个模拟您的数据的示例:
AbstractFormatterFactory
要获得相同的结果而不进行数据预处理,可以使用Altair的变换语法:
import java.awt.Dimension;
import java.text.ParseException;
import java.util.Objects;
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFormattedTextField.AbstractFormatter;
import javax.swing.JFormattedTextField.AbstractFormatterFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JSpinner.DefaultEditor;
import javax.swing.SpinnerNumberModel;
public class Main {
public static class IntegerFormatter extends AbstractFormatter {
private final int min, max;
public IntegerFormatter(final int min, final int max) {
this.min = min;
this.max = max;
}
@Override
public Object stringToValue(final String text) throws ParseException {
try {
//This is where we compare for out of bounds values:
final int val = Integer.parseInt(text);
if (val < min)
return min;
if (val > max)
return max;
return val;
}
catch (final NumberFormatException nfx) {
//Find where in the string is the parsing error (so as to return ParseException accordingly).
int i = 0;
for (final int cp: text.codePoints().toArray()) {
if (!Character.isDigit(cp))
throw new ParseException("Not a digit.", i);
++i;
}
//Should not happen:
throw new ParseException("Failed to parse input \"" + text + "\".", 0);
}
}
@Override
public String valueToString(final Object value) throws ParseException {
return Objects.toString(value);
}
}
public static class IntegerFormatterFactory extends AbstractFormatterFactory {
private final int min, max;
public IntegerFormatterFactory(final int min, final int max) {
this.min = min;
this.max = max;
}
@Override
public AbstractFormatter getFormatter(final JFormattedTextField tf) {
if (!(tf.getFormatter() instanceof IntegerFormatter))
return new IntegerFormatter(min, max);
return tf.getFormatter();
}
}
public static void main(final String[] args) {
final SpinnerNumberModel model = new SpinnerNumberModel(0, -10, 10, 1);
final JSpinner spin = new JSpinner(model);
spin.setPreferredSize(new Dimension(100, 25));
((DefaultEditor) spin.getEditor()).getTextField().setFormatterFactory(
new IntegerFormatterFactory((Integer) model.getMinimum(), (Integer) model.getMaximum())
);
final JPanel contents = new JPanel(); //FlowLayout.
contents.add(spin);
contents.add(new JButton("Click me to change focus!"));
final JFrame frame = new JFrame("Spinner out of bounds");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(contents);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}