javascript - 使用旧数据重新创建图表

标签 javascript jsf primefaces highcharts

我使用了JSF 2.2,Primefaces 6.0,CDI和Highcharts 4.2.3。我的页面之一包含一张图表(Highcharts)和一个干扰我图表的简单表格。我剪切了代码(请看下面),向您展示代码中最重要的部分。

我想实现这样的目标:

  • 当我在表单中按commandButton时,图表应使用新数据重新创建。

  • 到目前为止,我已经:
  • 在脚本中的函数中声明了几个js变量;
  • 创建了一个createChart函数,该函数使用#{bean.getDataForChart1a(typeOfData)}从CDI bean(实际上是从数据库)下载数据(对于我的图表),并将这些数据放入js变量中。除了下移数据外,该函数还创建图表。
  • 创建了一个createNewChart函数,该函数销毁了我当前的图表,并调用createChart函数来创建应包含新数据的新图表。

  • 问题是最后阶段。在下面的 View 中,我已经将oncomplete="createNewChart();"属性添加到了commandButton中,此刻,我的页面如下所示:
  • 打开页面时,一切正常。下载数据并创建图表。
  • 当我按commandButton时,将重新创建图表,但它使用的是旧数据。我注意到createChart函数中没有再次下载数据,js没有执行#{bean.getDataForChart1a(typeOfData)}。因此,#{bean.getDataForChart1a(typeOfData)}在开始时仅执行一次。我不明白为什么。

  • 如何解决此问题?

    我的xhtml页面:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" 
    xmlns:ui="http://java.sun.com/jsf/facelets" 
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:p="http://primefaces.org/ui"
    xmlns:pt="http://xmlns.jcp.org/jsf/passthrough"
    xmlns:pe="http://primefaces.org/ui/extensions">
    
    <h:head> 
        <!-- loading css -->
    </h:head>
    
    <h:body>
    
        <div id="container" style="height: 750px; width: 100%; margin: 0 auto;"/>
    
        <script type="text/javascript">
    
            //<![CDATA[
    
            var chart;
    
            var protos;
            var dataChart;
            var color;
    
            var seriesChart;
            var i, len;
    
            function createChart() {    
    
                //Downloading data for the chart
                protos = #{visualizationController.getDataForChart1a("protos")};
                dataChart = #{visualizationController.getDataForChart1a("data")};
                color = #{visualizationController.getDataForChart1a("color")};
    
                seriesChart = [];
    
                //Creating several series of the data
                for (i = 0, len = protos.length; i < len; i++) {
    
                    seriesChart.push({
                        color: color[i],
                        name: protos[i],
                        data: dataChart[i]
                        //other options
                    });
                }
    
                console.time('scatter');
                console.time('asyncRender');
    
                Highcharts.setOptions({
                    lang: {
                        //translate
                    }
                });
    
                // Create the chart        
                chart = new Highcharts.Chart({
    
                    //other options
    
                    chart: {
    
                        //other options                     
                        renderTo: "container"
                    },                  
    
                    series : seriesChart            
                }); 
    
                console.timeEnd('scatter');
    
            }
    
            function createNewChart(){
    
                chart.destroy();
                createChart();  
            }
    
            createChart();
    
            //]]>
        </script>
    
        <h:form id="filterForm">
    
            <p:selectManyCheckbox id="filter" value="#{visualizationController.selectedKindOfNetwork}" layout="responsive" columns="4">
                <p:ajax update="filterButton" />
                <f:selectItems value="#{visualizationController.kindOfNetwork}" var="kindOfNetwork" itemLabel="#{kindOfNetwork}" itemValue="#{kindOfNetwork}" />
            </p:selectManyCheckbox>
    
            <br/>
    
            <p:commandButton id="filterButton" value="Filtruj" action="#{visualizationController.actionFilterButtonForChart1a()}"
                            disabled="#{!visualizationController.visibilityFilterButtonForChart1a}"
                            update="filterForm"
                            oncomplete="createNewChart();"/>
    
        </h:form>
    
    </h:body>        
    </html>
    

    最佳答案

    我已经达到了预期的结果。基于@SiMag的评论,我发布了答案,其中包括几种可能的解决方案。帖子分为几个部分,使您可以了解我做错了什么以及为解决此问题所做的事情。

    我做错什么了?

    就像@SiMag写道:

    The script tag isn't updated after the first load so the #{visualizationController.getData...} expressions aren't evaluated anymore and they are referring to the old data.



    我到底是什么问题?

    此刻,当我知道自己做错了什么时,正确的问题是:
  • 如何将几个值从CDI bean传递到javascript?
  • 如何在服务器或客户端调用javascript函数(将就绪数据和必需数据传递给此函数)?


  • 我的页面应该如何工作?

    这是您必须知道的最后一个问题,以了解我将在下一部分中进行的操作。我的页面应如下所示:
  • 加载/打开页面时,应执行createChart javascript函数(自动执行,无需任何按钮或链接),并且该函数应使用我在CDI bean中设置的多个值。作为此操作的结果,应显示该图表。
  • 每次按下按钮时,应执行changeData javascript函数,并且该函数应使用在CDI bean中设置的几个值。作为此操作的结果,图表应更新其显示的数据。

  • 如果您想实现其他目标,那不是问题。根据您在这里可以找到的解决方案,您将能够实现所需的解决方案。

    第一个解决方案

    第一种解决方案基于:
  • 通过RequestContext将一些值从CDI bean传递到javascript。为了实现这一点,我基于素语表documentation (第11.1章RequestContext);
  • 在服务器端调用createChart函数;
  • 在客户端调用changeChart函数。

  • 首先,我在javascript函数中添加了三个参数(对于每个要从CDI bean传递到javascript代码的值,每个参数一个)。此刻,脚本应如下所示:
    <script type="text/javascript">
    
        //<![CDATA[
    
        var chart;
    
        var protos;
        var dataChart;
        var color;
    
        var seriesChart;
        var i, len;
    
        //Creating the chart.
        function createChart(protosArg, dataArg, colorsArg) { 
    
            $(function() {
    
                //The data are parsed
                protos = JSON.parse(protosArg);
                dataChart = eval(dataArg);
                colors = JSON.parse(colorsArg);
    
                seriesChart = [];
    
                //Creating several series of the data
                for (i = 0, len = protos.length; i < len; i++) {
    
                    seriesChart.push({
                        color: color[i],
                        name: protos[i],
                        data: dataChart[i]
                        //other options
                    });
                }
    
                console.time('scatter');
                console.time('asyncRender');
    
                Highcharts.setOptions({
                    lang: {
                        //translate
                    }
                });
    
                // Create the chart        
                chart = new Highcharts.Chart({
    
                    //other options
    
                    chart: {
    
                        //other options                     
                        renderTo: "container"
                    },                  
    
                    series : seriesChart            
                }); 
    
                console.timeEnd('scatter');
            });
        }
    
        //Updating the chart using the new supplied data.
        function changeData(protosArg, dataArg, colorsArg){
    
            $(function() {
    
                protos = JSON.parse(protosArg);
                dataChart = eval(dataArg);
                colors = JSON.parse(colorsArg);
    
                seriesChart = [];
    
                //Creating several series of the data using the new supplied data.
                for (i = 0, len = protos.length; i < len; i++) {
    
                    seriesChart.push({
                        color: color[i],
                        name: protos[i],
                        data: dataChart[i]
                        //other options
                    });
                }
    
                //Removing the old data from the chart.
                for (i = 0, len = chart.series.length; i < len; i++) {
    
                    chart.series[0].remove(false);
                }   
    
                //Inserting the new data to the chart.
                for (i = 0, len = protos.length; i < len; i++) {
    
                    chart.addSeries(seriesChart[i],false);
                }
    
                chart.redraw();
    
            }); 
        }
    
        //]]>
    </script>
    

    让我们继续到服务器。在这里,我向您展示我的数据的样子。在服务器端,我创建了三个String变量。在服务器端,此变量的输出如下所示:
  • 原型(prototype):["tcp","udp",...]
  • 数据:[[[date,23],[date,1234]],[[date,1234]]]
  • 颜色:["black","white",...]

  • 上面的样子,我使用JSON.parse()eval()方法保存传递的数据。为什么?由于浏览器将传递的数据解释为字符串,因此我们必须将数据转换为数组。

    在服务器端,我还使用RequestContext#addCallbackParam()方法创建了几个回调参数。 getProtos()getData()getColors()方法返回带有我准备好的数据的字符串。
    requestContext = RequestContext.getCurrentInstance();
    requestContext.addCallbackParam("protosArg", getProtos());
    requestContext.addCallbackParam("dataArg", getData());
    requestContext.addCallbackParam("colorsArg", getColors());
    

    最后,我调用RequestContext#execute()方法,该方法调用createChart javascript函数并传递三个必需值:
        @PostConstruct
        public void init() {
    
            //Creating the callback parameters.
            initData();     
    
            requestContext.execute(String.format("createChart('%s', '%s', '%s')", getProtos(),getData(),getColors()));
        }
    

    请注意,,我已使用''字符在javascript端实现了正确的表示。目前,浏览器会将数据解释为字符串。我在CDI bean的execute()方法(具有init()批注)中调用@PostConstruct方法,因此,我确定:
  • createChart javascript函数在开始时仅执行一次(CDI bean具有@ViewScoped批注);
  • 将准备在脚本中下载的数据。

  • 我在客户端完成的最后一件事是:更改oncomplete组件的<p:commandButton>属性的值。 oncomplete属性调用changeData javascript函数。 args参数引用我在服务器端设置的回调参数。
    <p:commandButton id="filterButton" value="Filter" action="#{chart2Controller.actionFilterButton()}"
                    disabled="#{!chart2Controller.visibilityFilterButton}"
                    update="filterForm"
                    oncomplete="changeData(args.protosArg, args.dataArg, args.colorsArg);"/>
    

    我的CDI bean的一部分:
    @Named
    @ViewScoped
    public class Chart2Controller implements Serializable {
    
        /**
         * Start method.
         */
        @PostConstruct
        public void init() {
    
            //Creating the callback parameters.
            initData();     
    
            requestContext.execute(String.format("createChart('%s', '%s', '%s')", getProtos(),getData(),getColors()));
        }
    
        /**
         * Downloading the data from the database.
         */
        private void initData(){
    
    
            /*
             * Sending the query to the database including the filter options of the form,
             * saving the result and preparing the data basing on the result.
             */
    
            //Preparing the callback parameters.
            requestContext = RequestContext.getCurrentInstance();
            requestContext.addCallbackParam("protos", protos);
            requestContext.addCallbackParam("data", data);
            requestContext.addCallbackParam("colors", colors);
        }
    
        /**
         * Action for the filter button.
         */
        public void actionFilterButton(){
    
            initData(); 
        }   
    
        /**
         * Visibility for filter button.
         */
        public boolean getVisibilityFilterButton(){     
            //return true or false.
        }   
    
        //Getter and Setter
    
        private static final long serialVersionUID = -8128862377479499815L;
    
        @Inject
        private VisualizationService visualizationService;
    
        private RequestContext requestContext;
    
        private List<String> kindOfNetwork;
        private List<String> selectedKindOfNetwork;
    
        private String protos;
        private String data;
        private String colors;
    }
    

    第二解决方案

    第二种解决方案基于:
  • 通过RequestContext将一些值从CDI bean传递到javascript;
  • 在客户端调用createChart函数;
  • 在客户端调用changeChart函数。

  • 看起来,该解决方案与先前的解决方案相似,但是createChart函数正在调用的位置不同。在这种情况下,我在客户端调用了createChart,并使用EL从CDI bean提供值。

    我不会重复代码,因此仅告诉您为实现预期结果所做的工作。在服务器端,除了调用RequestContext#execute()方法外,我已经完成了与以前的解决方案相同的操作。取而代之的是,我在脚本结尾处在客户端调用了createChart函数。该脚本应如下所示:
    <script type="text/javascript">
    
        //<![CDATA[
    
        var protos;
        var dataChart;
        var color;
    
        //other code
    
    
        //Creating the chart.
        function createChart(protosArg, dataArg, colorsArg) { 
    
            $(function() {
    
                protos = protosArg;
                dataChart = dataArg;
                colors = colorsArg;
    
                //other code
            });
        }
    
        //Updating the chart using the new supplied data.
        function changeData(protosArg, dataArg, colorsArg){
    
            $(function() {
    
                //The data are parsed
                protos = JSON.parse(protosArg);
                dataChart = eval(dataArg);
                colors = JSON.parse(colorsArg);
    
                //other code                
            }); 
        }
    
        createChart(#{chart2Controller.protos}, #{chart2Controller.data}, #{chart2Controller.colors});
    
        //]]>
    </script>
    

    请注意,,我还从JSON.parse()函数中删除了eval()createChart方法,因为EL已直接写入javascript代码,并且浏览器将数据正确解释为数组。

    第三种解决方案

    第三种解决方案基于:
  • 通过<h:inputHidden>组件将一些值从CDI bean传递到javascript;
  • 在客户端调用createChart函数;
  • 在客户端调用changeChart函数。

  • 此解决方案完全不同。一开始,我在表单中添加了三个<h:inputHidden> componenet(每个要传递给javascript的值一个)。该表格应如下所示:
    <h:form id="filterForm">
    
        <h:inputHidden id="protos" value="#{chart2Controller.protos}" />
        <h:inputHidden id="data" value="#{chart2Controller.data}" />
        <h:inputHidden id="colors" value="#{chart2Controller.colors}" />
    
        <p:selectManyCheckbox id="filter" value="#{chart2Controller.selectedKindOfNetwork}" layout="responsive" columns="4">
            <p:ajax update="filterButton" />
            <f:selectItems value="#{chart2Controller.kindOfNetwork}" var="kindOfNetwork" itemLabel="#{kindOfNetwork}" itemValue="#{kindOfNetwork}" />
        </p:selectManyCheckbox>
    
        <br/>
    
        <p:commandButton id="filterButton" value="Filtruj" action="#{chart2Controller.actionFilterButton()}"
                        disabled="#{!chart2Controller.visibilityFilterButton}"
                        update="filterForm"
                        oncomplete="changeData();"/>                
    
    </h:form>
    

    上面的样子,我添加了idvalue<h:inputHidden>属性,并将所需的数据保存在value属性中。

    请注意,,当您按下按钮时,表单会更新(请查看update组件的<p:commandButton>属性),因此,我确定每次按下时,都会下载输入的value属性中的数据按钮。

    最后,我已经通过document.getElementById("filterForm:idOfInput").value在javascript代码中引用了这些值。请记住使用JSON.parse()eval(),因为数据被解释为字符串。该脚本应如下所示:
    <script type="text/javascript">
    
        //<![CDATA[
    
        var chart;
    
        var protos;
        var dataChart;
        var colors;
    
        function createChart() {    
    
            $(function() {  
    
                protos = JSON.parse(document.getElementById("filterForm:protos").value);    
                dataChart = eval(document.getElementById("filterForm:data").value);
                colors = JSON.parse(document.getElementById("filterForm:colors").value);
    
                //other code
            });      
        }
    
        function changeData(){
    
            $(function() {  
    
                protos = JSON.parse(document.getElementById("filterForm:protos").value);    
                dataChart = eval(document.getElementById("filterForm:data").value);
                colors = JSON.parse(document.getElementById("filterForm:colors").value);
    
                //other code
            });
        }
    
        //]]>
    </script>
    

    高图并更改图表数据

    就我而言,我不知道每次按下按钮后会得到多少系列数据,因此有可能每次我都会获得不同系列的数据。 Highcharts API不支持这种情况。您可以在循环中结合使用Chart.addSeries()Series.setData()Series.remove()方法。如果还有更多时间,我会做,然后更新此答案。您可以在下面找到一些重新创建/更新图表的方法。

    如果您不知道数据序列号。

    第一个解决方案

    如果您不知道要获得多少数据序列(例如在我的情况下),则可以使用Series.remove()删除当前数据序列,然后通过Chart.addSeries()添加新的数据序列:
    function changeData(...){
    
        //Preparation of the received data.
    
        //Removing the old data from the chart.
        for (i = 0, len = chart.series.length; i < len; i++) {
    
            chart.series[0].remove(false);
        }   
    
        //Inserting the new data to the chart.
        for (i = 0, len = protos.length; i < len; i++) {
    
            chart.addSeries(seriesChart[i],false);
        }
    
        chart.redraw(); 
    }
    

    注意,由于效率高,我为false参数设置了redraw。 Highcharts API说:

    If doing more operations on the chart, it is a good idea to set redraw to false and call chart.redraw() after.



    第二解决方案

    您还可以销毁图表并重新创建它。在这种情况下,您还必须像这样更改changeData javascript函数:
    function changeData(...){
    
        chart.destroy();
        createChart(...);
    }
    

    如果您知道数据序列号。

    这种情况比前一种情况容易。看下面的代码,不需要任何解释:
    function changeData(...){
    
        //Preparation of the received data.
    
        for(var i = 0; i < chart.series.length; i++){
    
            chart.series[i].setData(seriesData[i],false);
        }
    
        chart.redraw();
    }
    

    关于javascript - 使用旧数据重新创建图表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38463938/

    相关文章:

    jakarta-ee - Jakarta EE 9 上的 Primefaces,错误 javax/servlet/ServletRequestListener

    javascript - 单击滚动按钮时不想将 anchor href 添加到 url

    javascript - AngularJS:计算 ng-repeat 的迭代次数

    javascript - 在这种情况下,为什么 Sequelize 对象没有 setOwner 方法?

    jsf - 创建指向子文件夹中 JSF 页面的链接

    java - GlassFish 3.1 问题与/faces/*

    javascript - 是否可以制作 p :dataTable with a column that contains a stopwatch using javascript?

    javascript - 如果文档高度高于视口(viewport),则执行 X

    java - 如何在处理数据时禁用按钮并更改按钮名称

    java - 如何在 JSF 中显示服务器生成的消息?