带宽度可变的条形图的Altair条形图?

时间:2020-01-06 08:01:57

标签: python plot charts bar-chart altair

我正在尝试在Python中使用Altair制作条形图,其中条形的宽度根据源数据帧的一列中的数据而变化。最终目标是获得像这样的图表:

A bar chart with bars of variable width

条形图的高度对应于每种能源技术的边际成本(在源数据框中以列的形式给出)。条形宽度对应于每种能源技术的容量(也在源数据框中以列的形式给出)。颜色也是源数据帧中的序数数据。条形按边际成本的升序排序。 (这样的图在能源行业中被称为“发电堆栈”)。如下面的代码所示,这在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行如下:

enter image description here

(与上面显示的图表不完全匹配)。

1 个答案:

答案 0 :(得分:4)

在Altair中,执行此操作的方法是使用JSpinner标记并显式构造钢筋。这是一个模拟您的数据的示例:

AbstractFormatterFactory

enter image description here

要获得相同的结果而不进行数据预处理,可以使用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);
    }
}