【问题标题】:javaFX multiple linechart on single canvas单个画布上的javaFX多个折线图
【发布时间】:2015-08-08 08:12:25
【问题描述】:
  • 我正在尝试使用 LineChart 来显示来自不同渠道(即时间序列)的医疗数据,但这些序列应根据所附图像显示在不同的 yAxis 上

    • 我是否需要开发自己的图表组件或 LineChart 可以用于这种情况?

http://simetronsac.com/images/dx/eeg-24/eeg6.gif

====================更新... 扩展 LineChart 并添加 ExtraData 以适应 yValue,同时将 yAxis 更改为 CategoryAxis:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javafx.beans.NamedArg;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.chart.Axis;
import javafx.scene.chart.LineChart;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;

public class StackedLineChart<X,Y> extends LineChart<X,Y> {

    private double swimlaneHeight = 50;

    private double currentYoffset = 0;

    public static class ExtraData {

        public float channelPower;

        public ExtraData(float channelPower) {
            super();
            this.channelPower = channelPower;
        }
        public float getChannelPower() {
            return channelPower;
        }
        public void setChannelPower(float channelPower) {
            this.channelPower = channelPower;
        }

    }

    public StackedLineChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis) {
        this(xAxis, yAxis, FXCollections.<Series<X, Y>>observableArrayList());
    }

    public StackedLineChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis, @NamedArg("data") ObservableList<Series<X,Y>> data) {
        super(xAxis, yAxis);
        setData(data);
    }

    public double getSwimlaneHeight() {
        return swimlaneHeight;
    }

    public void setSwimlaneHeight(double swimlaneHeight) {
        this.swimlaneHeight = swimlaneHeight;
    }

    private static float getChannelPower( Object obj) {
        return ((ExtraData) obj).getChannelPower();
    }

    final int getDataSize() {
        final ObservableList<Series<X,Y>> data = getData();
        return (data!=null) ? data.size() : 0;
    }

     /** @inheritDoc */
    @Override protected void layoutPlotChildren() {

        List<LineTo> constructedPath = new ArrayList<>(getDataSize());
        currentYoffset = 0;
        for (int seriesIndex=0; seriesIndex < getDataSize(); seriesIndex++) {            
            Series<X,Y> series = getData().get(seriesIndex);            
            if(series.getNode() instanceof  Path) {
                final ObservableList<PathElement> seriesLine = ((Path)series.getNode()).getElements();
                seriesLine.clear();
                constructedPath.clear();
                for (Iterator<Data<X, Y>> it = getDisplayedDataIterator(series); it.hasNext(); ) {
                    Data<X, Y> item = it.next();

                    double yCat = getYAxis().getDisplayPosition(item.getYValue());                    
                    double x = getXAxis().getDisplayPosition(item.getXValue());                
                    double y = getChannelPower(item.getExtraValue()) + yCat;

                    if (Double.isNaN(x) || Double.isNaN(y)) {                    
                        continue;                
                    }

                    constructedPath.add(new LineTo(x, y));

                    Node symbol = item.getNode();
                    if (symbol != null) {
                        final double w = symbol.prefWidth(-1);
                        final double h = symbol.prefHeight(-1);
                        symbol.resizeRelocate(x-(w/2), y-(h/2),w,h);
                    }
                }

                if (!constructedPath.isEmpty()) {
                    LineTo first = constructedPath.get(0);
                    seriesLine.add(new MoveTo(first.getX(), first.getY()));
                    seriesLine.addAll(constructedPath);
                }
            }
            currentYoffset+= this.getSwimlaneHeight();
        }
    }      

    @Override protected void updateAxisRange() {
        final Axis<X> xa = getXAxis();
        final Axis<Y> ya = getYAxis();
        List<X> xData = null;
        List<Y> yData = null;
        if(xa.isAutoRanging()) xData = new ArrayList<X>();
        if(ya.isAutoRanging()) yData = new ArrayList<Y>();
        if(xData != null || yData != null) {
            for(Series<X,Y> series : getData()) {
                for(Data<X,Y> data: series.getData()) {
                    if(xData != null) xData.add(data.getXValue());
                    if(yData != null) yData.add(data.getYValue());
                }
            }

            // RT-32838 No need to invalidate range if there is one data item - whose value is zero. 
            if(xData != null) xa.invalidateRange(xData);
            if(yData != null) ya.invalidateRange(yData);


        }
    }

}

【问题讨论】:

  • 即:某个时间序列的 yAxis 可以向上/向下移动吗?所以系列可以绘制在不同的相对 yAxis...不确定...
  • y 轴上没有值。只需根据您想要在图表上的位置为每个系列中的每个点添加一个数量。从你的时间尺度来看,javaFX 图表不够快。
  • yes 会尝试(抵消 y 值),至于渲染性能我不确定 javafx 如何在内部渲染图表(将查看源代码)但是是的,应该是基于“画布”的图表快点 !对吗?
  • 画布更快,但 javaFX 画布仍会呈现像线条一样的节点,这比直接绘制要慢。相比之下,AWT 画布非常快。也没有时间轴,但只显示几秒钟,您可以为 NumberAxis 制作格式化程序。我会先在 FX 中尝试,因为它应该很容易。
  • 谢谢 Brian,问题是医疗数据非常密集,你可能每秒有 256 个点(数据采集采样率),屏幕必须显示至少 10 秒,这样医生才能有想法...如果 javaFX 将它们都视为对象,那么它就行不通了!

标签: javafx linechart


【解决方案1】:

您想要的是一堆图表,而不是单个图表中的多条线。所以是的,您必须开发自己的图表组件。

【讨论】:

  • 我认为更改每个数据系列的 Y 值很容易,因为我所有的系列都有相同数量的数据和相同的比例......它只是将线条向下移动 Y 值而不是创建 30 个图表(性能和高度问题)...
  • 嗯,你说的方式比写这个问题要快。那就试试吧。
  • 告诉你。但是,Gantt chart I did some time ago 可能对您有所帮助。它与您想要的相似,可能会帮助您开始开发自己的图表。你当然会有图表线,而不是矩形。不过我自己没试过。
  • 现在你在说话,这将解决显示问题!我将画线而不是矩形...当然我稍后将不得不处理性能问题,也许我可以限制数据以控制节点数量...但必须至少显示 2000 个点每次屏幕。
  • 这是我对性能的担忧,我刚刚检查了 LineChart 代码,看起来它正在使用带有 MoveTo 和 LineTo 对象的 Path 对象......所以如果我有 30 条图表线屏幕上显示 2000 个数据点,每个数据点将加起来将近 60000 个 LineTo 对象!...但令我惊讶的是,经过一个小测试后,我的笔记本电脑将它们渲染得足够快!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-30
  • 1970-01-01
相关资源
最近更新 更多