【问题标题】:Chart with inverted y axis带有倒置 y 轴的图表
【发布时间】:2013-08-02 21:56:35
【问题描述】:

使用 JavaFX 图表,我需要反转堆积面积图的 y 轴,使正零位于顶部,正数在 y 轴上向下工作。下面是我想要实现的目标的模型。

在 JavaFX 中实现这一目标的最佳方式是什么(阅读:最短的开发时间和高代码重用)?

更新

将数据转换为负数不是一种选择。我正在寻找适用于“未触及”的正数的答案。

【问题讨论】:

    标签: charts javafx-2 javafx


    【解决方案1】:

    您可以使用带有负值的常规轴,但添加 TickLabelFormatter 会去除减号。

        final NumberAxis yAxis = new NumberAxis(-25, 0, 5);
    
        yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {
            @Override
            public String toString(Number value) {
                // note we are printing minus value
                return String.format("%7.1f", -value.doubleValue());
            }
        });
    
        series1.getData().add(new XYChart.Data("Jan", -1));
        series1.getData().add(new XYChart.Data("Feb", -5));
        series1.getData().add(new XYChart.Data("Mar", -20));
    

    【讨论】:

    • 您是否建议我在将所有数据用于图表之前将其转换为负数?
    • 是的。这当然不是最好的方法,但比使用反转功能扩展图表要快得多。为避免直接更新数据,您可以使用倒数逻辑扩展 XYChart.Data 类,或者引入一个实用方法,将倒数添加到图表中。
    【解决方案2】:

    简单的选项是在轴上启用inverse() 操作。如果不修补 JRE 类,它会非常复杂。

    有关如何处理此问题的一些提示:

    1) 扩展 ValueAxis 或 Axis(不幸的是 NumberAxis 是最终的)

    2) 将布尔字段和inverse() 方法添加到您的轴类

    public void inverse() {
        inversed = !inversed; // boolean property
        invalidateRange();
        requestAxisLayout();
    }
    

    3) 如果您扩展 ValueAxis - 您将需要补偿超类应用的偏移量(并截取轴大小发生变化的代码)

    @Override
    public Long getValueForDisplay(double displayPosition) {
        if (inversed)
            return super.getValueForDisplay(offset - displayPosition);
        else
            return super.getValueForDisplay(displayPosition);
    }
    
    @Override
    public double getDisplayPosition(Long value) {
        if (inversed)
            return offset - super.getDisplayPosition(value);
        else
            return super.getDisplayPosition(value);
    }
    

    4) (最丑的部分)取消隐藏 Axis 类抑制的标签刻度 - 原始实现取决于默认的刻度顺序。除了通过反射解锁它之外,我没有找到任何其他方法。所以这很脆弱。

    @Override
    protected void layoutChildren() {
        final Side side = getSide();
        boolean isHorisontal = null == side || side.isHorizontal();
        this.offset = isHorisontal ? getWidth() : getHeight();
        super.layoutChildren();
        if (inversed) {
            double prevEnd = isHorisontal ? offset + getTickLabelGap() : 0;
            for (TickMark m : getTickMarks()) {
                double position = m.getPosition();
                try {
                    final Text textNode = (Text) textNodeField.get(m);
                    final Bounds bounds = textNode.getLayoutBounds();
                    if (0 <= position && position <= offset)
                        if (isHorisontal) {
                            textNode.setVisible(position < prevEnd);
                            prevEnd = position - (bounds.getWidth() + getTickLabelGap());
                        } else {
                            textNode.setVisible(position > prevEnd);
                            prevEnd = position + (bounds.getHeight() + getTickLabelGap());
                        }
                } catch (IllegalAccessException ignored) {
                }
            }
        }
    }
    

    在这个例子中 Y 轴是倒置的

    【讨论】:

    • 不幸的是,从 OpenJFX 16 开始,偏移量仅在 ValueAxis 中具有私有访问权限。遵循这条路径的人还需要在他们的班级中实现偏移量的计算。我认为这应该基于底层 ValueAxis layoutChildren() 的逻辑工作:public double getOffset() { if(this.getSide().isVertical()) return getHeight(); else return 0; }
    • 干得好@harshtuna。您的回答使我能够在下面实施我的更新。
    【解决方案3】:

    对@harshtuna 的回答稍作更新以避免反射部分。以下代码使用 OpenJFX 16 为我工作

    @Override
    protected void layoutChildren() {
        final Side side = getSide();
        boolean isHorizontal = null == side || side.isHorizontal();
        double offSetting = isHorizontal ? getWidth() : getHeight();
    
        super.layoutChildren();
        if (inversed) {
            double prevEnd = isHorizontal ? offSetting + getTickLabelGap() : 0;
            for (TickMark m : getTickMarks()) {
                double position = m.getPosition();
                try {
                    if (0 <= position && position <= offSetting)
                        if (isHorizontal) {
                            m.setTextVisible(position < prevEnd);
                            prevEnd = position - (2 + getTickLabelGap());
                        } else {
                            m.setTextVisible(position > prevEnd);
                            prevEnd = position + (2 + getTickLabelGap());
                        }
                } catch (Exception ignored) {
                    System.out.println("illegal...?");
                }
            }
        }
    }   
    

    【讨论】:

      【解决方案4】:

      使用轴的属性lowerBoundupperBound。下界必须高于上界。以下是使用 fxml 的方法:

      <?xml version="1.0" encoding="UTF-8"?>
      
      <?import javafx.scene.*?>
      <?import javafx.scene.control.*?>
      <?import javafx.scene.layout.*?>
      
      <HBox         
          prefHeight="600.0" 
          prefWidth="800.0" 
          xmlns:fx="http://javafx.com/fxml/1">    
      
          <javafx.scene.chart.LineChart 
              fx:id="chart" >
              <xAxis >
                  <javafx.scene.chart.NumberAxis 
                      label="X Axis"
                      lowerBound="0.01"
                      upperBound="100"
                      tickUnit="10"
                      autoRanging="false" /> 
              </xAxis>
              <yAxis> 
                  <javafx.scene.chart.NumberAxis 
                          label="Y Axis"
                          lowerBound="100"
                          upperBound="0.001"
                          tickUnit="10"
                          autoRanging="false"
                      />
              </yAxis>
          </javafx.scene.chart.LineChart>            
      </Hbox>
      

      【讨论】:

      • 这不会提供预期的效果。这将正确设置下限和上限标签,但不会填充中间刻度或其标签。在内部,刻度标记系统假定一个正向上的数字线。
      猜你喜欢
      • 1970-01-01
      • 2019-03-15
      • 1970-01-01
      • 1970-01-01
      • 2011-10-31
      • 2016-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多