Embedding in HTML

It is often useful to be able to embed a chart into a web page, and the Morpheus charting API makes this easy. Charts can either be presented as static image files or alternatively as interactive HTML 5 charts that support panning, zooming and dynamic tooltips. This section illustrates some examples of how to use the Morpheus API to programmatically include charts in web based content.

Javascript Approach

The Morpheus adapters for JFreeChart and Google Charts support Javascript code generation to facilitate embedding charts in a web page. In the former case, the charts are integrated as PNG images encoded as base-64 data urls inside the Javascript. In the latter case, appropriate Javascript code is generated in order to produce an interactive HTML5 based chart leveraging the Google charting API.

Example

The code below generates 4 charts, 2 based on the Google adapter and 2 based on the JFreeChart adapter. We then convert these 4 charts into Javascript code which we embed programmatically in the script element of an HTML page. More likely than not, some sort of template framework like Freemarker would be used to create the HTML, but below we use a simple code generation API that ships with the Morpheus visualization library.

Once we have the Javascript, all that remains is to place the charts in the page. By design, the web page needs to include div elements for each of the 4 charts, and each div needs to be given an appropriate id attribute so that it can be uniquely identified. You will notice that in the chart creation function below we make a call to Chart.options.withId() where we assign a unique id to a chart. This unique id is used inside the code generated Javascript to bind the chart to the relevant div.

For the image based charts generated by the JFreeChart adapter, we also call withPreferredSize() on the chart options as this will define the size of the image created. For the Google HTML5 charts, this is not necessary as the plot will fill the size of the div to which it is bound. Below we force the heigth of our div elements to be 400 pixels, and half the width of the browser window so we create a 2x2 grid of charts.

public static void main(String[] args) throws Exception {
    //Create some random data to plot
    final DataFrame<Year,String> frame = randomData();
    //Create the list of 4 charts, 2 JFree based, 2 Google based 
    final List<Chart<?>> charts = createCharts(frame);
    //Generate Javascript code which can be embedded in an HTML page
    final String javascript = Chart.create().javascript(charts);

    //Code generate some HTML, embed the Javascript and create appropriate divs
    final HtmlCode htmlCode = new HtmlCode();
    htmlCode.newElement("html", html -> {
        html.newElement("head", head -> {
            head.newElement("script", script -> {
                script.newAttribute("type", "text/javascript");
                script.newAttribute("src", "https://www.gstatic.com/charts/loader.js");
            });
            head.newElement("script", script -> {
                script.newAttribute("type", "text/javascript");
                script.text(javascript);
            });
        });
        html.newElement("body", body -> {
            IntStream.range(0, 4).forEach(id -> {
                body.newElement("div", div -> {
                    div.newAttribute("id", String.format("chart_%s", id));
                    div.newAttribute("style", "float:left;width:50%;height:400px;");
                });
            });
        });
    });
    htmlCode.browse();
}


/**
 * Returns a list of 4 charts, 2 swing based and 2 html based
 * @param frame     the data to plot in different forms
 * @return          the list of 4 charts
 */
private static List<Chart<?>> createCharts(DataFrame<Year,String> frame) {
    return Collect.asList(
        Chart.create().asHtml().withLinePlot(frame, chart -> {
            chart.options().withId("chart_0");
            chart.title().withText("Single DataFrame Line Plot (Google)");
            chart.plot().axes().domain().label().withText("Year");
            chart.plot().axes().range(0).label().withText("Random Value");
            chart.legend().on().bottom();
        }),
        Chart.create().asHtml().withBarPlot(frame, true, chart -> {
            chart.options().withId("chart_1");
            chart.title().withText("Single DataFrame Bar Plot (Google)");
            chart.plot().axes().domain().label().withText("Year");
            chart.plot().axes().range(0).label().withText("Random Value");
            chart.legend().on().bottom();
        }),
        Chart.create().asSwing().withLinePlot(frame, chart -> {
            chart.options().withPreferredSize(600, 400).withId("chart_2");
            chart.title().withText("Single DataFrame Line Plot (JFree)");
            chart.plot().axes().domain().label().withText("Year");
            chart.plot().axes().range(0).label().withText("Random Value");
            chart.legend().on().bottom();
        }),
        Chart.create().asSwing().withBarPlot(frame, true, chart -> {
            chart.options().withPreferredSize(600, 400).withId("chart_3");
            chart.title().withText("Single DataFrame Bar Plot (JFree)");
            chart.plot().axes().domain().label().withText("Year");
            chart.plot().axes().range(0).label().withText("Random Value");
            chart.legend().on().bottom();
        })
    );
}

/**
 * Creates a DataFrame of random data with 4 series
 * @return      the DataFrame with chart data
 */
private static DataFrame<Year,String> randomData() {
    final Array<Year> years = Range.of(2000, 2016).map(Year::of).toArray();
    return DataFrame.of(years, String.class, columns -> {
        Stream.of("A", "B", "C", "D").forEach(label -> {
            columns.add(label, Array.randn(years.length()).cumSum());
        });
    });
}

Sample Output

The HTML output generate by the above example is show below. The base64 images have been truncated for legibility, but hopefully this provides a good insight as to the fairly simple and unified solution for using Javascript to render charts from each adapter.



<html>
    <head>
        <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
        <script type="text/javascript">    

            google.charts.load('current', {'packages':['corechart']});
            google.charts.setOnLoadCallback(drawCharts);

            function drawCharts() {
                drawChart_0()
                drawChart_1()
                drawChart_2()
                drawChart_3()
            }

            /** This is code generation by the Morpheus Visualization library */
            function drawChart_0() {
                var data = google.visualization.arrayToDataTable([
                    [
                        {id: "domain", label: "Domain", type: "string"},
                        {id: "A", label: "A", type: "number"},
                        {id: "B", label: "B", type: "number"},
                        {id: "C", label: "C", type: "number"},
                        {id: "D", label: "D", type: "number"}
                    ],
                    ['2000',0.5649231377923367,0.5453100986562593,-0.2030931021259246,-0.48360739892318605],
                    ['2001',-0.23182829941568417,-1.03605735883078,0.09068337378223748,-2.116960106087407],
                    ['2002',-1.208044022835305,-0.23036814946864737,0.4240705851894213,-1.616996062133938],
                    ['2003',-0.7428178334708951,1.9563008036267904,1.3123458436616149,-0.7981998224236828],
                    ['2004',1.0567387300679987,2.3968672877362533,1.0239596059901945,-1.6096974640778379],
                    ['2005',1.4294955012985076,0.2901319385475669,0.9915597443573548,-1.0894259330202014],
                    ['2006',1.2901648209824153,-2.076620989957175,0.08628099969449832,0.41435190883770856],
                    ['2007',0.582294762560585,-1.795857876481523,0.2595097868637274,0.9955095602428286],
                    ['2008',-0.23808622783335387,-1.5403271477485165,-1.7707040758630166,2.8379995610627615],
                    ['2009',0.23614781485157388,-1.8756421596020807,0.20119674267818088,2.6158469831886735],
                    ['2010',-0.17183433628278583,-0.8911557982977236,-1.0555322149236557,4.0443132884329485],
                    ['2011',-1.6399147529641436,-0.11443828597796613,-1.5943434857459011,5.189000772984133],
                    ['2012',-0.5298301520679034,0.32141819425165835,-0.6173563177411512,5.228795645597423],
                    ['2013',-0.34320688750777756,1.7963170143536078,0.08061128454844213,4.96370354438183],
                    ['2014',-1.3678140181362104,3.8156318180939715,0.26431218483382923,4.0556816321141635],
                    ['2015',-2.1179730104550756,3.778589363871307,-1.057630438418794,3.3585122043768374]
                ]);

                var options = {    
                    fontSize: "automatic",
                    fontName: "Arial",
                    title: "Single DataFrame Line Plot (Google)",
                    titlePosition: "out",
                    titleTextStyle: {    
                        color: "#000000",
                        fontName: "Arial",
                        fontSize: 16,
                        bold: true,
                        italic: false
                    },
                    backgroundColor: {    
                        strokeWidth: 0
                    },
                    chartArea: {    
                        left: "auto",
                        top: "auto",
                        width: "80%",
                        height: "auto"
                    },
                    legend: {    
                        position: "bottom",
                        alignment: "start",
                        textStyle: {    
                            color: "#000000",
                            fontName: "Arial",
                            fontSize: 10,
                            bold: false,
                            italic: false
                        }
                    },
                    dataOpacity: 1.0,
                    axisTitlesPosition: "out",
                    hAxis: {    
                        baselineColor: "#000000",
                        textPosition: "out",
                        title: "Year",
                        viewWindowMode: "maximized",
                        format: "auto",
                        titleTextStyle: {    
                            color: "#000000",
                            fontName: "Arial",
                            fontSize: 12,
                            bold: false,
                            italic: false
                        },
                        textStyle: {    
                            color: "#000000",
                            fontName: "Arial",
                            fontSize: 12,
                            bold: false,
                            italic: false
                        },
                        gridLines: ""
                    },
                    vAxis: {    
                        baselineColor: "#000000",
                        textPosition: "out",
                        title: "Random Value",
                        viewWindowMode: "maximized",
                        format: "decimal",
                        titleTextStyle: {    
                            color: "#000000",
                            fontName: "Arial",
                            fontSize: 12,
                            bold: false,
                            italic: false
                        },
                        textStyle: {    
                            color: "#000000",
                            fontName: "Arial",
                            fontSize: 12,
                            bold: false,
                            italic: false
                        },
                        gridLines: ""
                    },
                    series: {    
                        0: {    
                            targetAxisIndex: 0,
                            visibleInLegend: true,
                            color: "#e6194b",
                            lineWidth: 1.0,
                            curveType: "none"
                        },
                        1: {    
                            targetAxisIndex: 0,
                            visibleInLegend: true,
                            color: "#3cb44b",
                            lineWidth: 1.0,
                            curveType: "none"
                        },
                        2: {    
                            targetAxisIndex: 0,
                            visibleInLegend: true,
                            color: "#ffe119",
                            lineWidth: 1.0,
                            curveType: "none"
                        },
                        3: {    
                            targetAxisIndex: 0,
                            visibleInLegend: true,
                            color: "#0082c8",
                            lineWidth: 1.0,
                            curveType: "none"
                        }
                    },
                    lineWidth: 1,
                    curveType: "none",
                    orientation: "horizontal",
                    explorer: {    
                        keepInBounds: true,
                        actions: ["dragToZoom","rightClickToReset"]
                    }
                };

                var target = document.getElementById('chart_0');
                var chart = new google.visualization.LineChart(target);
                chart.draw(data, options);
            }

            /** This is code generation by the Morpheus Visualization library */
            function drawChart_1() {
                var data = google.visualization.arrayToDataTable([
                    [
                        {id: "domain", label: "Domain", type: "string"},
                        {id: "A", label: "A", type: "number"},
                        {id: "B", label: "B", type: "number"},
                        {id: "C", label: "C", type: "number"},
                        {id: "D", label: "D", type: "number"}
                    ],
                    ['2000',0.5649231377923367,0.5453100986562593,-0.2030931021259246,-0.48360739892318605],
                    ['2001',-0.23182829941568417,-1.03605735883078,0.09068337378223748,-2.116960106087407],
                    ['2002',-1.208044022835305,-0.23036814946864737,0.4240705851894213,-1.616996062133938],
                    ['2003',-0.7428178334708951,1.9563008036267904,1.3123458436616149,-0.7981998224236828],
                    ['2004',1.0567387300679987,2.3968672877362533,1.0239596059901945,-1.6096974640778379],
                    ['2005',1.4294955012985076,0.2901319385475669,0.9915597443573548,-1.0894259330202014],
                    ['2006',1.2901648209824153,-2.076620989957175,0.08628099969449832,0.41435190883770856],
                    ['2007',0.582294762560585,-1.795857876481523,0.2595097868637274,0.9955095602428286],
                    ['2008',-0.23808622783335387,-1.5403271477485165,-1.7707040758630166,2.8379995610627615],
                    ['2009',0.23614781485157388,-1.8756421596020807,0.20119674267818088,2.6158469831886735],
                    ['2010',-0.17183433628278583,-0.8911557982977236,-1.0555322149236557,4.0443132884329485],
                    ['2011',-1.6399147529641436,-0.11443828597796613,-1.5943434857459011,5.189000772984133],
                    ['2012',-0.5298301520679034,0.32141819425165835,-0.6173563177411512,5.228795645597423],
                    ['2013',-0.34320688750777756,1.7963170143536078,0.08061128454844213,4.96370354438183],
                    ['2014',-1.3678140181362104,3.8156318180939715,0.26431218483382923,4.0556816321141635],
                    ['2015',-2.1179730104550756,3.778589363871307,-1.057630438418794,3.3585122043768374]
                ]);

                var options = {    
                    fontSize: "automatic",
                    fontName: "Arial",
                    title: "Single DataFrame Bar Plot (Google)",
                    titlePosition: "out",
                    titleTextStyle: {    
                        color: "#000000",
                        fontName: "Arial",
                        fontSize: 16,
                        bold: true,
                        italic: false
                    },
                    backgroundColor: {    
                        strokeWidth: 0
                    },
                    chartArea: {    
                        left: "auto",
                        top: "auto",
                        width: "80%",
                        height: "auto"
                    },
                    legend: {    
                        position: "bottom",
                        alignment: "start",
                        textStyle: {    
                            color: "#000000",
                            fontName: "Arial",
                            fontSize: 10,
                            bold: false,
                            italic: false
                        }
                    },
                    dataOpacity: 1.0,
                    axisTitlesPosition: "out",
                    hAxis: {    
                        baselineColor: "#000000",
                        textPosition: "out",
                        title: "Year",
                        viewWindowMode: "maximized",
                        format: "auto",
                        titleTextStyle: {    
                            color: "#000000",
                            fontName: "Arial",
                            fontSize: 12,
                            bold: false,
                            italic: false
                        },
                        textStyle: {    
                            color: "#000000",
                            fontName: "Arial",
                            fontSize: 12,
                            bold: false,
                            italic: false
                        },
                        gridLines: ""
                    },
                    vAxis: {    
                        baselineColor: "#000000",
                        textPosition: "out",
                        title: "Random Value",
                        viewWindowMode: "maximized",
                        format: "decimal",
                        titleTextStyle: {    
                            color: "#000000",
                            fontName: "Arial",
                            fontSize: 12,
                            bold: false,
                            italic: false
                        },
                        textStyle: {    
                            color: "#000000",
                            fontName: "Arial",
                            fontSize: 12,
                            bold: false,
                            italic: false
                        },
                        gridLines: ""
                    },
                    series: {    
                        0: {    
                            targetAxisIndex: 0,
                            visibleInLegend: true,
                            color: "#e6194b"
                        },
                        1: {    
                            targetAxisIndex: 0,
                            visibleInLegend: true,
                            color: "#3cb44b"
                        },
                        2: {    
                            targetAxisIndex: 0,
                            visibleInLegend: true,
                            color: "#ffe119"
                        },
                        3: {    
                            targetAxisIndex: 0,
                            visibleInLegend: true,
                            color: "#0082c8"
                        }
                    },
                    isStacked: true,
                    bar: {    
                        groupWidth: "70%"
                    },
                    explorer: {    
                        keepInBounds: true,
                        actions: ["dragToZoom","rightClickToReset"]
                    }
                };

                var target = document.getElementById('chart_1');
                var chart = new google.visualization.ColumnChart(target);
                chart.draw(data, options);
            }


            function drawChart_2() {
                var divElement = document.getElementById('chart_2');
                var imageElement = document.createElement('img');
                imageElement.setAttribute('src', 'data:image/png;base64, iVBORw0KGgoAAAANSUhEUgA...........');
                imageElement.setAttribute('alt', 'Embedded Chart');
                imageElement.setAttribute('class', 'chart');
                divElement.appendChild(imageElement);
            }


            function drawChart_3() {
                var divElement = document.getElementById('chart_3');
                var imageElement = document.createElement('img');
                imageElement.setAttribute('src', 'data:image/png;base64, iVBORw0KGgoAAAANS...............');
                imageElement.setAttribute('alt', 'Embedded Chart');
                imageElement.setAttribute('class', 'chart');
                divElement.appendChild(imageElement);
            }
        </script>
    </head>
<body>
    <div id="chart_0" style="float:left;width:50%;height:400px;"></div>
    <div id="chart_1" style="float:left;width:50%;height:400px;"></div>
    <div id="chart_2" style="float:left;width:50%;height:400px;"></div>
    <div id="chart_3" style="float:left;width:50%;height:400px;"></div>
</body>
</html>

Image File Approach

The Morpheus Chart interface declares overloaded writePng() methods which can be used to programmatically generate Portable Network Graphics image content to a File or an OutputStream. Note that these methods are only supported by the JFreeChart adapter, invoking them on an instance of a Google chart will be a silent operation.

The code below demonstrates how to generate a chart image via a Java Servlet as a response to some hypothetical HTTP GET request. The chart itself is based on some random data, and we output an image of a size defined by parameters in the GET request, and also impose that the chart background be transparent if the user requests it.

/**
 * A basic Java Servlet example that demonstrates how to generate a PNG image of a chart
 */
public class ChartServlet extends javax.servlet.http.HttpServlet {

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException {
        try {
            final String title = request.getParameter("title");
            final Optional<String> widthParam = Optional.ofNullable(request.getParameter("width"));
            final Optional<String> heightParam = Optional.ofNullable(request.getParameter("height"));
            final Optional<String> transParam = Optional.ofNullable(request.getParameter("transparent"));
            final DataFrame<Integer,String> frame = loadData();
            //Only the swing based charts support PNG generation
            Chart.create().asSwing().withLinePlot(frame, "DataDate", chart -> {
                try {
                    final int width = widthParam.map(Integer::parseInt).orElse(700);
                    final int height = heightParam.map(Integer::parseInt).orElse(400);
                    final boolean transparent = transParam.map(Boolean::parseBoolean).orElse(true);
                    response.setContentType("image/png");
                    chart.title().withText(title);
                    chart.legend().on().bottom();
                    chart.plot().axes().domain().label().withText("Data Date");
                    chart.plot().axes().range(0).label().withText("Temperature");
                    chart.writerPng(response.getOutputStream(), width, height, transparent);
                } catch (Exception ex) {
                    throw new RuntimeException(ex.getMessage(), ex);
                }
            });
        } catch (Exception ex) {
            response.sendError(500, ex.getMessage());
        }
    }

    /**
     * Loads the data for the chart to plot
     * @return      the DataFrame with chart data
     */
    private DataFrame<Integer,String> loadData() {
        final int rowCount = 1000;
        final LocalDate startDate = LocalDate.of(2013, 1, 1);
        final Range<Integer> rowKeys = Range.of(0, rowCount);
        final Range<LocalDate> dates = rowKeys.map(startDate::plusDays);
        return DataFrame.of(rowKeys, String.class, columns -> {
            columns.add("DataDate", dates);
            Stream.of("A", "B", "C", "D").forEach(label -> {
                columns.add(label, Array.randn(rowCount).cumSum());
            });
        });
    }
}