0
  • I am trying to use the LineChart to display medical data coming from different channels (i..e: time series) but those series should be displayed on different yAxis as per the attached image

    • Do I need to develop my own charting component or LineChart could be used for this scenario?

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

===================Update ... Extended LineChart and added ExtraData to cater for yValue while changing yAxis to 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);


        }
    }

}
egovconcepts
  • 69
  • 1
  • 8
  • i.e.: could the yAxis of a certain timeserie moved up/down? so series can be plotted on different relative yAxis...not sure... – egovconcepts Aug 08 '15 at 08:22
  • There are no values on the y-axis. Just add an amount to each point in each series depending on where you want it on the graph. Judging by your time scale, the javaFX charts won't be fast enough. – brian Aug 08 '15 at 14:13
  • yes will try that (offsetting the y value), as for rendering performance I am not sure how javafx internally render charts( will look at the source code) but yes a "canvas" based chart should be faster ! right? – egovconcepts Aug 08 '15 at 17:40
  • A canvas is faster but a javaFX canvas still renders nodes like lines which is slower than direct drawing. An AWT canvas is super fast in comparison. There's also no time axis but with just seconds showing, you can make a formatter for NumberAxis. I would try it in FX first, since it should be easy. – brian Aug 08 '15 at 17:55
  • Thanks Brian, the problem is that medical data is very dense, you might have 256 points per second (data acquisition sampling rate) and the screen has to display at least 10 seconds so the doctor can have an idea...if javaFX will treat em all as objects then it is not gonna work ! – egovconcepts Aug 08 '15 at 18:26

1 Answers1

0

What you want is a stack of charts, not various lines in a single chart. So yes, you have to develop your own charting component.

Roland
  • 18,114
  • 12
  • 62
  • 93
  • I thought it would be easy to change the Y value of each data serie coz all my series have same amount of data and same scale...it is only moving the lines down by Y value instead of creating 30 charts (performance nd height issues)... – egovconcepts Aug 08 '15 at 14:37
  • Well, the way you say that it's faster done than writing this question. Just try it then. – Roland Aug 08 '15 at 20:52
  • Told you. However, maybe the [Gantt chart I did some time ago](http://stackoverflow.com/questions/27975898/gantt-chart-from-scratch/27978436#27978436) may be of help for you. It's similar to what you want and might help you starting development of your own chart. Instead of rectangles of course you'd have chart lines. I haven't tried it myself though. – Roland Aug 09 '15 at 14:29
  • Now you are talking, this will solve the display problem ! instead of rectangles I will draw the lines...sure I will have to deal with performance later and maybe I can throttle the data in order to keep the number of nodes under control...but will have to display at least 2000 points on each screen every time. – egovconcepts Aug 09 '15 at 20:19
  • And here'e my concern about performance, I just checked out the LineChart code and looks like it is using a Path object with MoveTo and LineTo objects...so If I have 30 chart lines to be displayed on screen with 2000 data points each that will add up to almost 60000 LineTo objects !...but to my surprise and after a small test my laptop rendered them fast enough !! – egovconcepts Aug 09 '15 at 21:29
  • Maybe you could post your solution in order to help others who face the same problem? – Roland Aug 10 '15 at 05:13
  • Couldnt post an answer...but here you go: @Override protected void layoutPlotChildren() and add yAxis offset double y = getYAxis().getDisplayPosition( getYAxis().toRealValue(getYAxis().toNumericValue(item.getYValue()) + currentYoffset + getSwimlaneHeight())); – egovconcepts Aug 10 '15 at 08:17
  • Maybe post a full working example on pastebin? Or edit your question and put it there. – Roland Aug 10 '15 at 08:53