从零开始的甘特图

Posted

技术标签:

【中文标题】从零开始的甘特图【英文标题】:Gantt chart from scratch 【发布时间】:2015-03-14 13:58:08 【问题描述】:

任务

我想用 JavaFX 从头开始​​创建甘特图。

示例

假设我有两台机器机器 A 和机器 B,它们有 2 个状态在线(绿色)和离线(红色)。我想在给定的时间间隔以水平彩色条显示它们的状态,X 轴是日期轴,Y 轴是机器轴。

问题

我从哪里开始?我需要使用哪些类?如果有人有一个最小的例子并且可以分享它,那就太好了。

非常感谢。

【问题讨论】:

有一个用于 JavaFX 的商业甘特图库 - FlexGanttFX。为最小的甘特图提供代码超出了 *** 答案的合理范围。 我只需要 2 个水平条,它们会随着时间的推移改变它们的颜色。我认为它不应该太复杂。例如,拥有一个带有水平线的 XYChart 可以改变其颜色就足够了。或者,也许修改后的堆积条形图可能是一个解决方案。我刚刚在合奏中看到了烛台自定义图表。我想这是我目前最好的选择。 两个随时间改变颜色的水平条在我看来与甘特图完全不同。我建议您尝试编写您的解决方案,如果您在实施解决方案时有进一步的疑问,请发布特定的新问题(包括复制您尝试解决的问题的可执行代码)。 【参考方案1】:

事实证明,BubbleChart 源是一个很好的起点。

基本上,您可以使用 XYChart 及其数据的修改版本。您需要做的是添加额外数据,例如值的有效时间和着色的一些样式。

剩下的是使用日期轴,因此使用日期值而不是数字值。

以下是我想出的办法,以防有人想玩弄它:

另一个例子:

来源:

甘特图.java:

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.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ValueAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;

public class GanttChart<X,Y> extends XYChart<X,Y> 

    public static class ExtraData 

        public long length;
        public String styleClass;


        public ExtraData(long lengthMs, String styleClass) 
            super();
            this.length = lengthMs;
            this.styleClass = styleClass;
        
        public long getLength() 
            return length;
        
        public void setLength(long length) 
            this.length = length;
        
        public String getStyleClass() 
            return styleClass;
        
        public void setStyleClass(String styleClass) 
            this.styleClass = styleClass;
        


    

    private double blockHeight = 10;

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

    public GanttChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis, @NamedArg("data") ObservableList<Series<X,Y>> data) 
        super(xAxis, yAxis);
        if (!(xAxis instanceof ValueAxis && yAxis instanceof CategoryAxis)) 
            throw new IllegalArgumentException("Axis type incorrect, X and Y should both be NumberAxis");
        
        setData(data);
    

    private static String getStyleClass( Object obj) 
        return ((ExtraData) obj).getStyleClass();
    

    private static double getLength( Object obj) 
        return ((ExtraData) obj).getLength();
    

    @Override protected void layoutPlotChildren() 

      for (int seriesIndex=0; seriesIndex < getData().size(); seriesIndex++) 

            Series<X,Y> series = getData().get(seriesIndex);

            Iterator<Data<X,Y>> iter = getDisplayedDataIterator(series);
            while(iter.hasNext()) 
                Data<X,Y> item = iter.next();
                double x = getXAxis().getDisplayPosition(item.getXValue());
                double y = getYAxis().getDisplayPosition(item.getYValue());
                if (Double.isNaN(x) || Double.isNaN(y)) 
                    continue;
                
                Node block = item.getNode();
                Rectangle ellipse;
                if (block != null) 
                    if (block instanceof StackPane) 
                        StackPane region = (StackPane)item.getNode();
                        if (region.getShape() == null) 
                            ellipse = new Rectangle( getLength( item.getExtraValue()), getBlockHeight());
                         else if (region.getShape() instanceof Rectangle) 
                            ellipse = (Rectangle)region.getShape();
                         else 
                            return;
                        
                        ellipse.setWidth( getLength( item.getExtraValue()) * ((getXAxis() instanceof NumberAxis) ? Math.abs(((NumberAxis)getXAxis()).getScale()) : 1));
                        ellipse.setHeight(getBlockHeight() * ((getYAxis() instanceof NumberAxis) ? Math.abs(((NumberAxis)getYAxis()).getScale()) : 1));
                        y -= getBlockHeight() / 2.0;

                        // Note: workaround for RT-7689 - saw this in ProgressControlSkin
                        // The region doesn't update itself when the shape is mutated in place, so we
                        // null out and then restore the shape in order to force invalidation.
                        region.setShape(null);
                        region.setShape(ellipse);
                        region.setScaleShape(false);
                        region.setCenterShape(false);
                        region.setCacheShape(false);

                        block.setLayoutX(x);
                        block.setLayoutY(y);
                    
                
            
        
    

    public double getBlockHeight() 
        return blockHeight;
    

    public void setBlockHeight( double blockHeight) 
        this.blockHeight = blockHeight;
    

    @Override protected void dataItemAdded(Series<X,Y> series, int itemIndex, Data<X,Y> item) 
        Node block = createContainer(series, getData().indexOf(series), item, itemIndex);
        getPlotChildren().add(block);
    

    @Override protected  void dataItemRemoved(final Data<X,Y> item, final Series<X,Y> series) 
        final Node block = item.getNode();
            getPlotChildren().remove(block);
            removeDataItemFromDisplay(series, item);
    

    @Override protected void dataItemChanged(Data<X, Y> item) 
    

    @Override protected  void seriesAdded(Series<X,Y> series, int seriesIndex) 
        for (int j=0; j<series.getData().size(); j++) 
            Data<X,Y> item = series.getData().get(j);
            Node container = createContainer(series, seriesIndex, item, j);
            getPlotChildren().add(container);
        
    

    @Override protected  void seriesRemoved(final Series<X,Y> series) 
        for (XYChart.Data<X,Y> d : series.getData()) 
            final Node container = d.getNode();
            getPlotChildren().remove(container);
        
        removeSeriesFromDisplay(series);

    


    private Node createContainer(Series<X, Y> series, int seriesIndex, final Data<X,Y> item, int itemIndex) 

        Node container = item.getNode();

        if (container == null) 
            container = new StackPane();
            item.setNode(container);
        

        container.getStyleClass().add( getStyleClass( item.getExtraValue()));

        return container;
    

    @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());
                        xData.add(xa.toRealValue(xa.toNumericValue(data.getXValue()) + getLength(data.getExtraValue())));
                    
                    if(yData != null)
                        yData.add(data.getYValue());
                    
                
            
            if(xData != null) xa.invalidateRange(xData);
            if(yData != null) ya.invalidateRange(yData);
        
    


GanttChartSample.java:

import java.util.Arrays;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import chart.gantt_04.GanttChart.ExtraData;

// TODO: use date for x-axis
public class GanttChartSample extends Application 

    @Override public void start(Stage stage) 

        stage.setTitle("Gantt Chart Sample");

        String[] machines = new String[]  "Machine 1", "Machine 2", "Machine 3" ;

        final NumberAxis xAxis = new NumberAxis();
        final CategoryAxis yAxis = new CategoryAxis();

        final GanttChart<Number,String> chart = new GanttChart<Number,String>(xAxis,yAxis);
        xAxis.setLabel("");
        xAxis.setTickLabelFill(Color.CHOCOLATE);
        xAxis.setMinorTickCount(4);

        yAxis.setLabel("");
        yAxis.setTickLabelFill(Color.CHOCOLATE);
        yAxis.setTickLabelGap(10);
        yAxis.setCategories(FXCollections.<String>observableArrayList(Arrays.asList(machines)));

        chart.setTitle("Machine Monitoring");
        chart.setLegendVisible(false);
        chart.setBlockHeight( 50);
        String machine;

        machine = machines[0];
        XYChart.Series series1 = new XYChart.Series();
        series1.getData().add(new XYChart.Data(0, machine, new ExtraData( 1, "status-red")));
        series1.getData().add(new XYChart.Data(1, machine, new ExtraData( 1, "status-green")));
        series1.getData().add(new XYChart.Data(2, machine, new ExtraData( 1, "status-red")));
        series1.getData().add(new XYChart.Data(3, machine, new ExtraData( 1, "status-green")));

        machine = machines[1];
        XYChart.Series series2 = new XYChart.Series();
        series2.getData().add(new XYChart.Data(0, machine, new ExtraData( 1, "status-green")));
        series2.getData().add(new XYChart.Data(1, machine, new ExtraData( 1, "status-green")));
        series2.getData().add(new XYChart.Data(2, machine, new ExtraData( 2, "status-red")));

        machine = machines[2];
        XYChart.Series series3 = new XYChart.Series();
        series3.getData().add(new XYChart.Data(0, machine, new ExtraData( 1, "status-blue")));
        series3.getData().add(new XYChart.Data(1, machine, new ExtraData( 2, "status-red")));
        series3.getData().add(new XYChart.Data(3, machine, new ExtraData( 1, "status-green")));

        chart.getData().addAll(series1, series2, series3);           

        chart.getStylesheets().add(getClass().getResource("ganttchart.css").toExternalForm());

        Scene scene  = new Scene(chart,620,350);
        stage.setScene(scene);
        stage.show();
    

    public static void main(String[] args) 
        launch(args);
    

甘特图.css

.status-red 
    -fx-background-color:rgba(128,0,0,0.7);

.status-green 
    -fx-background-color:rgba(0,128,0,0.7);

.status-blue 
    -fx-background-color:rgba(0,0,128,0.7);

【讨论】:

干得好!您是否同时提出了您在 TODO 中提到的“X-Achsis 日期”的解决方案? 是的,只需使用现有的开源日期轴实现之一。 谢谢它的工作。但是你如何计算数据的正确宽度?假设您有“new Extradata(100, ...)”,而 100 代表 100 秒? 请使用您已有的代码为此创建一个问题。 我在哪里可以获得上图中“项目阶段”的源代码

以上是关于从零开始的甘特图的主要内容,如果未能解决你的问题,请参考以下文章

从零开始学习CANoe(十七)—— Diagnostics

从零开始学习jQuery 开天辟地入门篇

从零开始学习CANoe(十九)—— Python和CANoe的数据交互

从零开始认识Dubbo

从零开始学习CANoe(十五)—— Graphics

基于 Hexo 从零开始搭建个人博客