GWT Google 异步数据可视化 (RPC)

GWT Google Visualization with asynchronous data (RPC)

在为这项工作寻找合适的实用程序之后,我终于屈服并解决了我面临的问题:你如何渲染异步加载的数据 (RPC) 在 Google Visualization for GWT chart which itself is loaded via the asynchronous AjaxLoader 中并行执行两个请求 ?

或者换句话说:无论图表数据是在加载可视化库之前还是之后到达,我们如何才能确保图表渲染正常?

这两个限制较少(或;不太雄心勃勃)的场景非常简单。

简单场景 #1:使用静态数据

不依赖于 RPC 数据,使用可视化非常简单。这是因为只有一个异步请求:加载可视化库。因此,不存在数据在加载可视化库之前到达的风险。

示例代码片段:

public class MyViewImpl implements MyView {
    private Panel main;

    public MyViewImpl() {
        this.main = new FlowPanel();

        Runnable onVizLoaded = new Runnable() {
            @Override
            public void run() {
                DataTable data = DataTable.create();
                Options options = Options.create();

                // Populate data and options based on static data here...

                // Instantiate and attach new chart.
                LineChart lineChart = new LineChart(data, options);
                main.add(lineChart);
            }
        };
        // Load Google Visualization asynchronously.
        VisualizationUtils.loadVisualizationApi(onVizLoaded, LineChart.PACKAGE);
    }

    // View logic goes here...
}

可视化库很容易加载,加载完成后会创建和呈现 LineChart。考虑负载特性的概述:

  Viz load: |---?---|
Chart data:         |
    Render:          |--->

简单场景 #2:运行 串行请求

在通过 RPC 加载图表数据时, 可以使用上述代码段。但是,以下内容暗示在加载并准备好可视化库之前不会获取图表数据。您可能对这种性能影响没有意见 - 我不是!

  1. 主要是因为我无法控制可视化库的加载时间,因此无法控制获取数据之前的延迟
  2. 我不喜欢我的视图决定应用程序行为。应用程序逻辑属于 Presenter。

示例代码片段:

public class MyViewImpl implements MyView {
    private Panel main;

    public MyViewImpl() {
        this.main = new FlowPanel();

        Runnable onVizLoaded = new Runnable() {
            @Override
            public void run() {
                // Make RPC call.
                ClientFactory.getService().getData(new AsyncCallback<MyResult>() {
                    @Override
                    public void onSuccess(MyResult result) {
                        DataTable data = DataTable.create();
                        Options options = Options.create();

                        // Populate data from RPC result.
                        data.addColumn(ColumnType.DATE);
                        data.addRow();
                        data.setValue(0, 0, result.getDate());
                        // ... Etc.

                        // Set options.
                        options.setWidth(500);
                        // ... Etc.

                        // Instantiate and attach new chart.
                        LineChart lineChart = new LineChart(data, options);
                        main.add(lineChart);
                    }

                    @Override
                    public void onFailure(Throwable caught) {
                        // Handle RPC error.
                    }
                });
            }
        };
        // Load Google Visualization asynchronously.
        VisualizationUtils.loadVisualizationApi(onVizLoaded, LineChart.PACKAGE);
    }

    // View logic goes here...
}

负载特征说明问题:

  Viz load: |---?---|
Chart data:          |----~----|
    Render:                     |--->

期望的场景:运行并行

期望的场景将具有以下特征,可视化库加载速度快于数据:

  Viz load: |---?---|
Chart data: |----~----|
    Render:            |--->

或者比可视化库更快的数据加载:

  Viz load: |---?---|
Chart data: |--~--|
    Render:          |--->

问题是:如何?

以下策略将满足要求:

  1. 请求是 运行 并行
  2. 图表在两个请求完成后立即呈现
  3. 应用程序逻辑(RPC 调用)保留在 Presenter 中。

示例代码片段:Presenter

public class MyPresenter implements MyView.Presenter {
    private MyView view;

    // ... Constructor etc. goes here...

    @Override
    public void render() {
        // Make RPC call immediately when Presenter should begin rendering.
        ClientFactory.getService().getData(new AsyncCallback<MyResult>() {
            @Override
            public void onSuccess(MyResult result) {
                // Pass data to view for rendering (when appropriate.)
                view.render(result);
            }

            @Override
            public void onFailure(Throwable caught) {
                // Handle RPC error.
            }
        });
    }
}

示例代码片段:ViewImpl

public class MyViewImpl implements MyView {
    private Panel main;
    private AsyncChart<LineChart> asyncLineChart;

    public MyViewImpl() {
        this.main = new FlowPanel();

        // Runnable wrapper (see next snippet.)
        this.asyncLineChart = new AsyncChart<LineChart>() {
            @Override
            public LineChart onAttach(DataTable data, Options options) {
                // Instantiate and attach new chart.
                LineChart lineChart = new LineChart(data, options);
                main.add(lineChart);

                return lineChart;
            }
        };

        // Load Google Visualization asynchronously.
        VisualizationUtils.loadVisualizationApi(this.asyncLineChart, LineChart.PACKAGE);
    }

    @Override
    public void render(final MyResult result) { // Invoked from Presenter (see above snippet.)
        // Schedule rendering to be invoked ASAP (but not before Visualization library is loaded.)
        this.asyncLineChart.enqueueWriter(new AsyncChartWriter() {
            @Override
            public void onWrite(DataTable data, Options options) {
                // Populate data from RPC result.
                data.addColumn(ColumnType.DATE);
                data.addRow();
                data.setValue(0, 0, result.getDate());
                // ... Etc.

                // Set options.
                options.setWidth(500);
                // ... Etc.
            }
        });
    }
}

现在该吃肉了!

示例代码片段:AsyncChart准备复制。

import com.google.gwt.visualization.client.DataTable;
import com.google.gwt.visualization.client.VisualizationUtils;
import com.google.gwt.visualization.client.visualizations.corechart.CoreChart;
import com.google.gwt.visualization.client.visualizations.corechart.Options;


/**
 * Wrapping {@link Runnable} to use with the load methods of  {@link VisualizationUtils} 
 * allowing a chart to be populated asynchronously (e.g., via RPC.)
 * 
 *  This wrapper handles the process of deferred attachment of the chart via the 
 *  {@link #onAttach(DataTable, Options)} method.
 *  
 *  Chart data is set / updated by means of a {@link AsyncChartWriter} mutating the 
 *  passed {@link DataTable} and {@link Options} instances.
 *  
 *  {@link AsyncChartWriter} can be reassigned (via {@link #enqueueWriter(AsyncChartWriter)}) 
 *  in order to redraw the chart.
 * 
 * @param <T>   The concrete chart type.
 */
public abstract class AsyncChart<T extends CoreChart> implements Runnable {
    public interface AsyncChartWriter {
        void onWrite(DataTable data, Options options);
    }

    private enum State {
        NEW,
        LOADED,
        ATTACHED
    }

    private State state;
    private T chart;
    private AsyncChartWriter enqueuedWriter;

    public AsyncChart() {
        this.state = State.NEW;
    }

    public abstract T onAttach(DataTable data, Options options);

    /**
     * Enqueues a writer to populate or manipulate the chart. This will happen immediately 
     * if the visualization API is already loaded and will otherwise be deferred.
     * 
     * @param writer    The writer to enqueue.
     */
    public void enqueueWriter(AsyncChartWriter writer) {
        this.enqueuedWriter = writer;
        processWriter();
    }

    /* (non-Javadoc)
     * @see java.lang.Runnable#run()
     * 
     * Invoked when the visualization API has loaded.
     */
    @Override
    public void run() {
        this.state = State.LOADED;
        if (this.enqueuedWriter != null) {
            processWriter();
        }
    }

    private void processWriter() {
        if (this.state == State.LOADED || this.state == State.ATTACHED) {
            DataTable data = DataTable.create();
            Options options = CoreChart.createOptions();
            this.enqueuedWriter.onWrite(data, options);
            if (this.state == State.LOADED) {
                this.chart = onAttach(data, options); // Instantiate and attach.
                this.state = State.ATTACHED;
            } else {
                this.chart.draw(data, options); // Redraw already attached chart.
            }
            this.enqueuedWriter = null;
        }
        // If state = NEW do nothing until run() is invoked (and state has changed to LOADED.) 
    }
}

享受您的并行负载。