Html chart from excel

I have a EXCEL file containing multiple Tables and Charts.
I want to create a web page to display a particular Table and a Chart. I used java script to display the table.
But cant find a way to display the CHART. By chart i mean it contains a pie graph or bar graph.

Is it possible to display using java script? Or do i have to use JSP/something else?

asked Mar 23, 2013 at 6:32

Sourav048's user avatar

2

Take a look at Google Chart Tools. Here is an example with javascript source code. If you are building the table on the server side, you may instead want to pass the data down JSON encoded and then build both the table and chart at the same time without having to pass the data twice.

answered Mar 23, 2013 at 7:03

Benjamin Kaiser's user avatar

3

An easier way would be to use a free tool like oomfo, which is a PowerPoint plugin to create charts. It lets you pull data from excel, create charts and then export the entire slide as HTML — the charts are in Flash and JavaScript (fallback). Just add a line of code in the output web page to render JavaScript charts only.

answered Mar 23, 2013 at 13:26

Pallav's user avatar

This document explains the requirements to create and
display Charts on SharePoint Page by fetching data from Excel Sheets and
displaying it in beautiful Charts generated using Chart.js library.

You might think why we need to do this much coding as we can
directly display Excel webparts on page and display charts generated from
Excel. But Excel Web parts depends on Excel Service Application to be running
in Farm and this Service is not available in Standard version of SharePoint
2013. Also Excel webpart are very heavy webparts as it tries to load Excel
functionality in browser.

Get started with some background

I had a requirement to display multiple charts from Excel on
SharePoint page as our Excel was very heavy approx. 5MB size so it was slowing
the performance of page. So I come up with angular based project to display
chart using Chart.js library.

In this project I’ve used following libraries:

  • JQuery.min.js (basic
    library)
  • Angular.min.js
    (for AngularJs application)
  • Chart.js
    (for generating Charts)
  • XLSX.min.js
    (for reading data from Excel)
  • Chartjs-stacked100.js
    (ChartJS plugin to create Stacked 100% charts)

Actual Coding Starts from here

So let’s start with creating HTML template for displaying
charts. In the HTML template I’ve referenced all the JavaScript libraries and created
a div for rendering our AngularJS application (myChartApp). This is the main
HTML that we are going to use in Content Editor Web Part to display charts on
SharePoint Page.

<body>

    <div ng-appmyChartApp« ng-controllermyChartAppController«>

        <div class=»panel
panel-primary»>

            <h1>Dashboard (updates overnight)</h1>

            <div class=»panel-body»>

                <div class=»row» styleborder:1px solid #ccc;margin:30px 0″ ng-repeatseries in TransformedData«>

                    <canvas id=»myChartDir_{{$index+1}}« chart-dir=»series»></canvas>

                </div>

            </div>

        </div>

    </div>

    <script src=»https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.14.1/xlsx.core.min.js»></script>

    <script src=»https://cdn.jsdelivr.net/npm/chart.js@2.8.0″></script>

    <script src=»https://ajax.googleapis.com/ajax/libs/angularjs/1.7.9/angular.min.js»></script>

    <script src=»../SiteAssets/Scripts/JLibs/chartjs-stacked100.js»></script>

    <script src=»../SiteAssets/Scripts/App/myChartApp.min.js»></script>

As you can see in the HTML first div has properties the
ng-app and ng-controller for associating myChartApp. You can use AngularJS
bootstrapper to load your application but for simplicity I’ve used this method.

Now it’s time start coding myChartApp, but first I’ll some
constants and configurable variable. You can skip this and apply the setting in
the application itself. But I prefer to do this way as it provide more
flexibility to change the setting at any time and will apply everywhere.

First setting, ChartConfig, as in this page I’m going to
generate 3 different charts (stacked bar, line, stacked 100% bar):

var ChartConfig = {

    «Chart 1»: { Name: «Chart 1», ChartType: «bar», DataInPercent: false, stacked100: false, HasHeader: true, Stacked: true, DisplayValues: false },

    «Chart 2»: { Name: «Chart 2», ChartType: «line»,
DataInPercent:
true,
stacked100:
false,
HasHeader:
true,
Stacked:
false, DisplayValues: false },

    «Chart 3»: { Name: «Chart 3», ChartType: «bar», DataInPercent: false, stacked100: true, HasHeader: true, Stacked: true, DisplayValues: true, Colors: [«#548235», «#ffff00», «#f00»] }

Next I’ll define configuration settings (I use this
configuration settings to change the urls any time):

var _myChartAppConfig = {

    IsDev: false,

    ExcelUrl: _spPageContextInfo.webAbsoluteUrl
+
‘/Shared%20Documents/Graphs.xlsx’,

    BaseFolder:
_spPageContextInfo.webAbsoluteUrl +
‘/SiteAssets’,

    LogEnabled: true,

    ConsoleLog: function ConsoleLog(msg) {

        if (_myChartAppConfig.LogEnabled)

           
console.log(msg);

    }

Showtime, now the actual angular application starts,
starting with defining myChartApp module:

angular.module(‘myChartApp’, []);

Create AngularJs service to fetch data from Excel file:

angular.module(‘myChartApp’).service(‘spRestService’, [‘$http’, ‘$q’, ‘$timeout’, function ($http, $q, $timeout) {

    var spRestService = this;

    this.GetExcelData = function () {

        var deferred = $q.defer();

        $http({

            method: ‘GET’,

            url:
_myChartAppConfig.ExcelUrl,

            responseType: ‘arraybuffer’

        }).then(function (result) {

           
deferred.resolve(result.data);

        }, function (error) { deferred.reject(error);
});

        return $q.when(deferred.promise);

    };

Now we’ll create our controller to actually interact with
the application:

Below is the skeleton of myChartAppController with properties and
method declaration:

angular.module(‘myChartApp’).controller(‘myChartAppController’, [‘$scope’, ‘spRestService’, function myChartAppController($scope, spRestService) {

    $scope.XLSXRange = {
row: 0, col: 0 };

    $scope.dataSeries =
[];

    $scope.colors = [‘#4472c4’, ‘#ed7d31’, ‘#a5a5a5’, ‘#ffc000’, ‘#5b9bd5’, ‘#70ad47’, ‘#264478’, ‘#9e480e’, ‘#636363’, ‘#997300’, ‘#255e91’, ‘#43682b’,

‘#698ed0’, ‘#f1975a’, ‘#b7b7b7’, ‘#ffcd33’, ‘#7cafdd’, ‘#8cc168’, ‘#548235’, ‘#ffff00’, ‘#ff0000’];

    $scope.TransformedData
= [];

    $scope.GetFileContent
=
function () {     

    }();

    $scope.TransformData =
function () {

    };

I’ve defined properties for storing data returned from service (dataSeries) and then storing the transformed data (TransformedData). I’ve also declared XLSXRange for defining
the starting row and column to read from Excel Sheet and Colors array to be
used while creating chart.

GetFileContent function: in this function we’ll call our service to
get data from Excel file.

$scope.GetFileContent = function () {

       
spRestService.GetExcelData().then(
function (data) {

            //console.log(data);

            var bstr = data;

            var workbook = XLSX.read(bstr, {
type:
‘array’, cellDates: true, cellNF: false, cellText: false });

           
$scope.$evalAsync(
function () {

               
_myChartAppConfig.ConsoleLog(
«Reading
Excel Data»
);

                for (var i = 0; i < workbook.SheetNames.length; i++) {

                    var sheetName = workbook.SheetNames[i];

                    var chartOption =
ChartConfig[sheetName];

                    var sheetJson = null;

                    if (chartOption.HasHeader) {

                       
sheetJson = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName], {
range: $scope.XLSXRange.row, dateNF:
‘DD»/»MM»/»YYYY’ });

                    } else {

                       
sheetJson = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName], {
range: $scope.XLSXRange.row, dateNF:
‘DD»/»MM»/»YYYY’, header: chartOption.HeaderColumns, blankrows: true });

                    }

                   
$scope.dataSeries.push({ name: sheetName, data: sheetJson });

                   
_myChartAppConfig.ConsoleLog(
«Loading
Series:»
+ sheetName + ‘; Data:’ + sheetJson.length);

                }

               
_myChartAppConfig.ConsoleLog(
«Trasnforming
Excel Data into Chart format»
);

               
$scope.TransformData();

               
_myChartAppConfig.ConsoleLog($scope.TransformedData);

            });

        });

As the service returns
the response in arraybuffer which is a binary format, so we
cannot use it as it is. We need to transform the data from binary to array.
Here comes XLSX library which reads the binary data and convert it into JavaScript
based Excel Workbook object. From that Excel workbook object I’ve extracted
the data from different sheets using sheet_to_json method
and stored it in dataSeries object. Then
used TransformData method to convert dataSeries to chart dataset.

$scope.TransformData = function () {

       
angular.forEach($scope.dataSeries,
function (selSeries, sIndx) {

            var chartOption =
ChartConfig[selSeries.name];

            var cdata = { Labels: [], DataSets:
[], ChartOption: chartOption };

            var datakeys = Object.keys(selSeries.data[0]);

           
angular.forEach(datakeys,
function (k, kIndx) {

                var serData = [];

               
angular.forEach(selSeries.data,
function (d, dIndx) {

                    if (k == ‘__EMPTY’) {

                        var labelText =
d[k].toString().indexOf(
‘ ‘) > -1 ? d[k].split(‘ ‘) : d[k];

                       
cdata.Labels.push(labelText);

                    } else {

                        var pdata = chartOption.DataInPercent
? d[k] * 100 : d[k];

                       
serData.push(pdata);

                    }

                });

                if (k !== ‘__EMPTY’) {

                   
cdata.DataSets.push({

                       
label: k,

                       
backgroundColor: chartOption.Colors ? chartOption.Colors[kIndx — 1] :
$scope.colors[kIndx — 1],

                       
borderColor: chartOption.Colors ? chartOption.Colors[kIndx — 1] :
$scope.colors[kIndx — 1],

                        data: serData,

                       
fill: chartOption.fill ? chartOption.fill :
false,

                       
linetension: chartOption.Curve ? chartOption.Curve : 0

                    });

                }

            });

            $scope.TransformedData.push(cdata);

            cdata = null;           

        });

After transforming
data the TransformedData object will contain all the data
related to chart including Chart Type, Coloe, Label Text, Header Text, data,
data format etc.

Now we will create a directive “chartDir” for displaying
actual charts.

Start coding with creating an AngularJS directive

angular.module(‘myChartApp’).directive(‘chartDir’, [‘$parse’, function ($parse) {

    return {

        restrict: ‘A’,

        link: function ($scope, element, attrs) {

            var canvas = element.context;

            var ctx = canvas.getContext(‘2d’);

            var chart = null;

           
$scope.$watch(attrs.chartDir,
function (data) {

                if (data && data.DataSets.length > 0) {                   

                    if (chart) {

                       
chart.clear();

                       
chart.destroy();

                    }

                    var chartConfiguration = {};

                    chart
=
new Chart(ctx, chartConfiguration);

                }

            });

        }

    };

You might have noticed
that chartConfiguration variable is declared as an
empty object. Well this is the main object that will contain all the
configuration for creating chart and displaying it on page, user interaction
etc.

var chartConfiguration = {

    // The type of chart we want to create

    type:
data.ChartOption.ChartType,

    data: {

        labels:
data.Labels,

        datasets:
data.DataSets

    },

    options: {

    }

As you can see in above code ChartType, Data Labels and DataSet is
configured. With this much configuration we can display the chart. Options
provide additional functionality to the chart  i.e. axis, responsive, title, legend,
tooltip etc. So in the next row I’ll show only recommended options others are
nice to have.

options:  {

    scales: {

        xAxes: [{

            stacked:
data.ChartOption.Stacked

        }],

        yAxes: [{

            stacked:
data.ChartOption.Stacked

        }]

    },

    responsive: true,

    title: {

        display: true,

        text:
data.ChartOption.Name,

        fontSize: 20,

        padding: 15

    },

    legend: {

        display: true,

        position: ‘bottom’

    },

    elements: {

        line: {

            tension: 0 // disables Bezier curves in line chart

        }

    },

    tooltips: {

        //intersect: true,

        //mode: ‘index’,

        callbacks: {

            label: function (tooltipItem, myData) {

                var label =
myData.datasets[tooltipItem.datasetIndex].label ||
»;

                if (label) {

                    label
+=
‘: ‘;

                }

                var n =
parseFloat(tooltipItem.value);

                label +=
(n — Math.floor(n)) !== 0 ? n.toFixed(2) : parseInt(tooltipItem.value);

                return label;

            },

            title: function (tooltipItem, myData) {

                var title = tooltipItem[0].label;

                if (title.indexOf(‘,’) > -1) {

                    title
= title.replace(
/,/g, ‘ ‘);

                }

                return title;

            }

        }

    },

    plugins: {

        stacked100: {
enable: data.ChartOption.stacked100 }

    }

In the above section, I’ve defined the following options:

  • Scales: defines the X and Y axis
  •  Responsive: responsive nature of chart
  • Title: displays Chart title, size.
  • Legend: display legend and their position
  •  Element/Line/Tension: 0 to disable the curves
    while creating line chart
  • Tooltip: callback function to display custom
    tooltip text.
  • Plugins: added stacked100 plugin to display
    Stacked 100% charts.

That all and you have your chart application ready to
display chart taking data from Excel.

Joining the Dots

If you are not familiar with the Angular then you might be
wondering how it’s going to take data and render the chart, where chartDir is
called etc.

Again go back to the
HTML template:

·      

First div contains ng-app and ng-controller
directive initialized with our module and controller.

·      

You can see there is a div with ng-repeat directive that create a loop on TransformedData array and object in series.
This directive is responsible for creating multiple div based on items in
array.

·      

Inside div, there is Canvas with chart-dir directive initialised with series
object. As series object contains data that is used in generating chart. Go
back to chartDir directive and look for $scope.$watch(attrs.chartDir, function (data) { the data passed in function comes from the HTML markup chart-dir=»series».

Enough of knowledge, you
might started thinking what we get after doing this much work.

Last thing we have to do is to add a content editor webpart in our SharePoint page and provide link to this HTML file, save and refresh. Change the data in excel file and see the chart will display the updated data.

Hope you enjoyed the
article. Please leave your thoughts/queries in the comments below.

You can convert existing excel worksheet data an charts to an HTML document by using

  1. A. FTP wizard.
  2. Internet assistant wizard.
  3. Intranet wizard.
  4. Import wizard.

How do I display data from Excel table in HTML?

Convert cells to html table with Save As command

  1. Select the cells you want to convert, and click File > Save As > Browse to show the Save As window.
  2. In the Save As window, specify a folder you are going to place the html table, type the file name you use, and then select Web Page from Save as type drop down list.

How do I open an Excel File in HTML?

In Excel, select “File” and then “Open…”. Navigate to the directory/folder where you saved the data. Change the “Files of type:” to “HTML Documents”. Select the file you saved and click “Open”.

How do I save an Excel File as HTML?

How do I export an Excel File as HTML?

  1. In Excel, navigate to File > Save As.
  2. In the Save as type list, select *.
  3. Under Save, choose Selection: Sheet and click Publish.
  4. Select the sheet(s) you wish to publish.

Which types of charts can excel produce?

Excel offers the following major chart types :

  • Column Chart.
  • Line Chart.
  • Pie Chart.
  • Doughnut Chart.
  • Bar Chart.
  • Area Chart.
  • XY (Scatter) Chart.
  • Bubble Chart.

What are the ways to complete a cell entry?

Excel Data Entry Shortcut Keys

Press To
Shift+Enter Complete cell entry and move cell cursor up one row
Ctrl+Enter Complete cell entry in all cells in selected range
Alt+Enter Begin a new line in a cell entry
Tab Complete cell entry and move cell cursor right one column

Can HTML read Excel?

The Excel file is read as Binary data using HTML5 FileReader and then the Binary data is read using the xlsx Excel plugin which returns rows from Excel in JSON Array format. Finally, a loop is executed over the JSON Array and the HTML Table is populated.

How do you link an Excel File to HTML?

Open OneDrive

  1. Open OneDrive.
  2. If your Excel workbook isn’t already stored in OneDrive, save a copy of it there.
  3. Select Your Excel File.
  4. In OneDrive, right-click on the file thumbnail and select “Embed” from the drop-down menu.
  5. Generate the Embed Code.
  6. Click the “Generate” button on the right side of the web page.

Can Google Docs Save as HTML?

Google Docs does have a native Export to HTML option but the code is often very messy. Doing a copy and paste isn’t going to cut it; you need to export as HTML to maintain most of that styling, and you want a clean export so that the resulting HTML page is readable and modifiable down the line.

Does Google Docs use HTML?

Google Docs is a very flexible word processor in many ways, but what many people don’t realize is that you can embed HTML into a Google doc. There are several ways to do this.

Good charts make a spreadsheet. Excel has its own charts and pivot charts, but have you ever wished you could have properly “fancy” charts in your spreadsheet like you see on modern websites?

Highcharts is a professional javascript charting package for creating interactive charts for websites. This blog shows how you can embed these same charts into Excel. We’ll cover plotting data both from an Excel spreadsheet and from external sources.

Note: Highcharts is a commercial product and requires a license for non-commercial use. See https://shop.highcharts.com/ for licensing information.

All the code related to this blog post can be found in the highcharts folder in the PyXLL examples github repo.

Related: Plotting in Excel with Python and Matplotlib and Plotting with Matplotlib Part 2

A Web Browser in Excel

Since Highcharts is a javascript library, we’ll need a web browser capable of rendering the charts in Excel. Fortunately Excel already has exactly that!

Add a control to Excel

Under the “Developer” tab, go to “Insert” and select “More Controls”. This will bring up a list of other controls. Find the one called “Microsoft Web Browser”. That’s the control we’ll use to host our interactive chart.

Web Browser Control in Excel

When you try and add the Web Browser control, if you’re using Excel 2013 or newer you may run into a problem. This is because by default the Web Browser control has been disabled in Excel. However, it can easily be re-enabled with a registry setting.

You can read about why the Web Browser control is disabled by default here https://support.microsoft.com/en-us/help/4058123/security-settings-for-com-objects-in-office. That page also covers in more detail how you can enable the control.

To enable the Web Browser control, first of all open “regedit” by clicking the start menu and typing “regedit”. Find the following key in the registry:

For Office 2013 and Office 2010:

  1. For 64 bit Office on 64 bit Windows (or 32 bit Office on 32 bit Windows).
    HKEY_LOCAL_MACHINESoftwareMicrosoftOfficeCommonCOM Compatibility{8856F961-340A-11D0-A96B-00C04FD705A2}
  2. For 32 bit Office on 64 bit Windows.
    HKEY_LOCAL_MACHINESoftwareWow6432NodeMicrosoftOfficeCommonCOM Compatibility
    {8856F961-340A-11D0-A96B-00C04FD705A2}

For Office 2016:

  1. For 64 bit Office on 64 bit Windows (or 32 bit Office on 32 bit Windows).
    HKEY_LOCAL_MACHINESoftwareMicrosoftOffice16.0CommonCOM Compatibility
    {8856F961-340A-11D0-A96B-00C04FD705A2}
  2. For 32 bit Office on 64 bit Windows.
    HKEY_LOCAL_MACHINESoftwareWow6432NodeMicrosoftOffice16.0CommonCOM Compatibility
    {8856F961-340A-11D0-A96B-00C04FD705A2} }

If the Web Browser control has been disabled, you will see a subkey called Compatibility Flags with its value set to 0x00000400.  Either delete this key or set its value to 0 to enable the control.

Generating the HTML for a Chart

There are a few different Python packages that will generate Highcharts HTML. For this post we’ll use python-highcharts (https://github.com/kyper-data/python-highcharts) which can be installed using pip:

pip install python-highcharts

Using PyXLL, we can call a Python function from Excel. The following function takes a 2d array of data and constructs a Highchart object. It then writes that out to an html file.

from pyxll import xl_func
from highcharts import Highchart

@xl_func
def hc_basic_line(data, filename):
    H = Highchart()

    # transform the data from a list of rows to a list of columns
    data_t = list(zip(*data))

    # add the data to the chart
    for i, series in enumerate(data_t):
        H.add_data_set(series,
                       series_type='line',
                       name="series_%d" % i)

    # write out the html
    with open(filename, "wt") as fh:
        fh.write(H.htmlcontent)

    return True

By adding this code to a new module and adding that module to our pyxll.cfg file, the function can be called from Excel. It doesn’t actually add the chart to Excel yet, it only writes out an html file.

Generate HTML for Highcharts from Excel

Opening the resulting html file in a browser reveals that it has created a plot of the data from the sheet, as we expected.

Adding the HTML Chart to Excel

Now we’ve got everything we need to add an interactive Highchart to Excel. Earlier we saw how to add a Web Browser control to an Excel sheet, and in the previous section we generated an HTML chart. The next step is to tie the two together so we can have our chart embedded in the Excel sheet.

If you haven’t already, add a Web Browser control to your sheet. By default, it will be named “WebBrowser1”. We’ll stick with that name for now, but later we’ll see how to change it.

In our function that generates the HTML file, we can find the Web Browser object and tell it to navigate to our generated file as follows.

from pyxll import xl_func, xl_app
from highcharts import Highchart

@xl_func
def hc_basic_line(data, filename):
    H = Highchart()

    # transform the data from a list of rows to a list of columns
    data_t = list(zip(*data))

    # add the data to the chart
    for i, series in enumerate(data_t):
        H.add_data_set(series, series_type='line', name="series_%d" % i)

    # write out the html
    with open(filename, "wt") as fh:
        fh.write(H.htmlcontent)

    xl = xl_app()
    control = xl.ActiveSheet.OLEObjects("WebBrowser1")
    browser = control.Object
    browser.Navigate("file://" + filename)

    return True

Above, we’re using PyXLL’s “xl_app” function to get the “Excel.Application” object. This is Excel’s COM interface, which has exactly the same API as Excel’s VBA object model.

When you run the above code, you might find that the Web Browser control just says “Loading…”. There are a couple of reasons for this that we will have to fix.

Problem 1: Trust Settings

Content might be restricted by your trust and privacy settings. Toggle out of “Developer Mode” in the “Developer” menu in Excel. If you see a yellow bar at the top of the browser saying “To help protect your security…” then the javascript has been blocked from being run. Click on the yellow bar and select “Allow Blocked Content”.

To avoid this warning, you would have to host the html file on a webserver. This could be running on http://localhost with a Python webserver running in the Excel process, using PyXLL.

You might also find that ActiveX controls are blocked from being used in Excel. If that’s the case, you’ll need to enable them in Excel. You should be careful when doing this that you are not violating your company’s IT policy as it could potentially be a security vulnerability. To change your ActiveX settings in the Excel Trust Center:

  1. Go to File > Options.
  2. Click Trust Center > Trust Center Settings > ActiveX Settings.

Problem 2: Browser Compatibility

When you get the javascript to run after trusting the content, you may then get a “Script Error”.

The reason you might see this error is that by default, the Web Browser control runs in Internet Explorer 7 compatibility mode. Fortunately, this is easy enough to work around. We just need to add a meta tag to the html document to tell the control to use the latest version of IE.

<meta http-equiv="X-UA-Compatible" content="IE=edge" />

The easiest way I found to add this meta tag to the Highcharts html document was to parse it using BeautifulSoup and add the tag that way. You can install BeautifySoup using pip.

pip install beautifulsoup4
from pyxll import xl_func, xl_app
from highcharts import Highchart
from bs4 import BeautifulSoup

@xl_func
def hc_basic_line(data, filename):
    H = Highchart()

    # transform the data from a list of rows to a list of columns
    data_t = list(zip(*data))

    # add the data to the chart
    for i, series in enumerate(data_t):
        H.add_data_set(series, series_type='line', name="series_%d" % i)

    # get the html and add the 'X-UA-Compatible' meta-tag
    soup = BeautifulSoup(H.htmlcontent)
    metatag = soup.new_tag("meta")
    metatag.attrs["http-equiv"] = "X-UA-Compatible"
    metatag.attrs['content'] = "IE=edge"
    soup.head.insert(0, metatag)

    # write out the html
    with open(filename, "wt") as fh:
        fh.write(soup.prettify())

    xl = xl_app()
    control = xl.ActiveSheet.OLEObjects("WebBrowser1")
    browser = control.Object
    browser.Navigate("file://" + filename)

    return True

Highcharts Working in Excel

After addressing the problems of the trust settings and the browser compatibility, we now have a chart rendered using Highcharts in Excel!

Refactoring for Re-use

So far we’ve demonstrated that it is possible to embed Highcharts in Excel using PyXLL and the Web Browser control. There are a few problems with the current code, however.

  1. We can’t re-use this code for other functions to plot charts.
  2. It only works if there’s a Web Browser control called “WebBrowser1”.
  3. It expects the sheet the function is on to be the current active sheet.
  4. The chart isn’t always sized correctly.
  5. The html files aren’t cleaned up.

Let’s pull out the part of the function that handles writing out the html file and updating the Web Browser control into a separate function and address these problems.

1. Refactor the plotting function for re-use

We’ll start by splitting out just the part of the code that generates the html and displays it in Excel. Then we can focus on that new function to address the remaining improvements.

from pyxll import xl_func, xl_app
from highcharts import Highchart
from bs4 import BeautifulSoup

@xl_func
def hc_basic_line(data, filename):
    H = Highchart()

    # transform the data from a list of rows to a list of columns
    data_t = list(zip(*data))

    # add the data to the chart
    for i, series in enumerate(data_t):
        H.add_data_set(series, series_type='line', name="series_%d" % i)

    return hc_plot(H, filename)


def hc_plot(chart, filename):
    # get the html and add the 'X-UA-Compatible' meta-tag
    soup = BeautifulSoup(chart.htmlcontent)
    metatag = soup.new_tag("meta")
    metatag.attrs["http-equiv"] = "X-UA-Compatible"
    metatag.attrs['content'] = "IE=edge"
    soup.head.insert(0, metatag)

    # write out the html
    with open(filename, "wt") as fh:
        fh.write(soup.prettify())

    xl = xl_app()
    control = xl.ActiveSheet.OLEObjects("WebBrowser1")
    browser = control.Object
    browser.Navigate("file://" + filename)

    return True

2. Use a named control instead of “WebBrowser1”

Rather than assume the Web Browser control is always called “WebBrowser1”, we add the control name as a parameter. Also, rather than fail if the named control can’t be found, create a new browser control. This helps when adding new charts so we don’t have to set up a browser control manually each time.

def hc_plot(chart, filename, control_name="WebBrowser1"):
    # get the html and add the 'X-UA-Compatible' meta-tag
    soup = BeautifulSoup(chart.htmlcontent)
    metatag = soup.new_tag("meta")
    metatag.attrs["http-equiv"] = "X-UA-Compatible"
    metatag.attrs['content'] = "IE=edge"
    soup.head.insert(0, metatag)

    # write out the html
    with open(filename, "wt") as fh:
        fh.write(soup.prettify())

    # find the existing webbrowser control, or create a new one
    xl = xl_app()
    sheet = xl.ActiveSheet

    try:
        control = sheet.OLEObjects(control_name[:31])
        browser = control.Object
    except:
        control = sheet.OLEObjects().Add(ClassType="Shell.Explorer.2",
                                         Left=147,
                                         Top=60.75,
                                         Width=400,
                                         Height=400)
        control.Name = control_name[:31]
        browser = control.Object

    browser.Navigate("file://" + filename)

    return True

3. Get the sheet the function is called from

When recalculating a workbook containing multiple sheets, the active sheet isn’t always the right one to use. Instead the PyXLL function “xlfCaller” can be used to get the sheet that’s calling the function. Assuming the function is on the same sheet we want the chart, this will work better than always using the active sheet.

from pyxll import xlfCaller
import re

def get_calling_sheet():
    caller = xlfCaller()
    sheet_name = caller.sheet_name

    # split into workbook and sheet name
    match = re.match("^[(.+?)](.*)$", sheet_name.strip("'""))
    if not match:
        raise Exception("Unexpected sheet name '%s'" % sheet_name)
    workbook, sheet = match.groups()

    # get the Worksheet object
    xl = xl_app()
    workbook = xl.Workbooks(workbook)
    return workbook.Sheets(sheet)

4. Resize the chart to fit the web browser

The highcharts charts will automatically resize to match the size of the Web Browser control. However, the default aspect ratio used won’t match the aspect ratio of the browser control. That can be fixed by setting the chart.height or chart.width option to a percentage string. Below, the height or width percentage is calculated from the control size.

def hc_plot(chart, filename, control_name="WebBrowser1"):
    # find the existing webbrowser control, or create a new one
    sheet = get_calling_sheet()
    try:
        control = sheet.OLEObjects(control_name[:31])
        browser = control.Object
    except:
        control = sheet.OLEObjects().Add(ClassType="Shell.Explorer.2",
                                         Left=147,
                                         Top=60.75,
                                         Width=400,
                                         Height=400)
        control.Name = control_name[:31]
        browser = control.Object

    # set the chart aspect ratio to match the browser
    if control.Width > control.Height:
        chart.set_options("chart", {
             "height": "%d%%" % (100. * control.Height / control.Width)
         })
    else:
        chart.set_options("chart", {
             "width": "%d%%" % (100. * control.Width / control.Height)
         })

    # get the html and add the 'X-UA-Compatible' meta-tag
    soup = BeautifulSoup(chart.htmlcontent)
    metatag = soup.new_tag("meta")
    metatag.attrs["http-equiv"] = "X-UA-Compatible"
    metatag.attrs['content'] = "IE=edge"
    soup.head.insert(0, metatag)

    # write out the html
    with open(filename, "wt") as fh:
        fh.write(soup.prettify())

    browser.Navigate("file://" + filename)

    return True

5. Use a temporary HTML file

Expecting the user to pass in a filename to plot a chart in Excel is counter-intuitive. The fact that a file is used is just an internal implementation detail. This detail could be hidden from the user so all they see is the chart in Excel without having to know about any files.

The Python tempfile package is a convenient way to create temporary file. We need it to be named so that the web browser can load it. It can’t be deleted immediately as it takes a second for the web browser to load the file. Instead we use the timer module (part of the pywin32 package) to delete the file after a few seconds.

import tempfile
import timer
import os

def render_html(browser, html):
    # Create a temporary file to write to
    fh = tempfile.NamedTemporaryFile("wt", suffix=".html", delete=False)
    filename = fh.name

    # Clean up the file after 10 seconds to give the browser time to load
    def on_timer(timer_id, time):
        timer.kill_timer(timer_id)
        os.unlink(filename)
    timer.set_timer(10000, on_timer)

    fh.write(html)
    fh.close()

    # Navigate to the temporary file
    browser.Navigate("file://%s" % filename)

Putting it all together: The complete “hc_plot” function

This hc_plot function can be used from any PyXLL @xl_func function to create or update a highcharts plot, embedded in Excel.

from pyxll import xl_app, xlfCaller
from bs4 import BeautifulSoup
import tempfile
import timer
import os
import re


def hc_plot(chart, control_name):
    # find the existing webbrowser control, or create a new one
    sheet = get_calling_sheet()
    try:
        control = sheet.OLEObjects(control_name[:31])
        browser = control.Object
    except:
        control = sheet.OLEObjects().Add(ClassType="Shell.Explorer.2",
                                         Left=147,
                                         Top=60.75,
                                         Width=400,
                                         Height=400)
        control.Name = control_name[:31]
        browser = control.Object

    # set the chart aspect ratio to match the browser
    if control.Width > control.Height:
        chart.set_options("chart", {
             "height": "%d%%" % (100. * control.Height / control.Width)
         })
    else:
        chart.set_options("chart", {
             "width": "%d%%" % (100. * control.Width / control.Height)
         })

    # get the html and add the 'X-UA-Compatible' meta-tag
    soup = BeautifulSoup(chart.htmlcontent)
    metatag = soup.new_tag("meta")
    metatag.attrs["http-equiv"] = "X-UA-Compatible"
    metatag.attrs['content'] = "IE=edge"
    soup.head.insert(0, metatag)

    # write out the html
    render_html(browser, soup.prettify())


def get_calling_sheet():
    # get the calling sheet
    caller = xlfCaller()
    sheet_name = caller.sheet_name

    # split into workbook and sheet name
    match = re.match("^[(.+?)](.*)$", sheet_name.strip("'""))
    if not match:
        raise Exception("Unexpected sheet name '%s'" % sheet_name)
    workbook, sheet = match.groups()

    # get the Worksheet object
    xl = xl_app()
    workbook = xl.Workbooks(workbook)
    return workbook.Sheets(sheet)


def render_html(browser, html):
    # Create a temporary file to write to
    fh = tempfile.NamedTemporaryFile("wt", suffix=".html", delete=False)
    filename = fh.name

    # Clean up the file after 10 seconds to give the browser time to load
    def on_timer(timer_id, time):
        timer.kill_timer(timer_id)
        os.unlink(filename)
    timer.set_timer(10000, on_timer)

    fh.write(html)
    fh.close()

    # Navigate to the temporary file
    browser.Navigate("file://%s" % filename)

This hc_plot function can now be called from our previous hc_basic_line function.

@xl_func
def hc_basic_line(data, title):
    H = Highchart()

    H.set_options("title", {
        "text": title
    })

    # transform the data from a list of rows to a list of columns
    data_t = list(zip(*data))

    # add the data to the chart
    for i, series in enumerate(data_t):
        H.add_data_set(series, series_type='line', name="series_%d" % i)

    # plot the chart in Excel, using the chart title as the control name
    hc_plot(H, title)

    return True

Handling Larger Data Sets

Highcharts are great for interactive data visualisation that is hard to match with Excel’s charts. They are capable of rendering large data sets, and sometimes it’s not convenient or practical to pull so much data into an Excel sheet.

PyXLL can pass Python objects around using object handles. This can be used to load large arrays into Python and pass them between functions without having to unpack them in Excel. You can read more about cached Python objects in the PyXLL user guide.

For really large data sets, you may even want to serve the data via a web server directly to your highcharts instead of loading the data in Python.

The following function loads a sample data set from highcharts.com.

from pyxll import xl_func
from highcharts.highstock.highstock_helper import jsonp_loader

@xl_func("string: object")
def hc_load_sample_data(name):
    url = "https://www.highcharts.com/samples/data/jsonp.php?filename=%s.json&callback=?" % name
    return jsonp_loader(url)

The returned object from this function can be passed to another plotting function to plot the data.

Here the data is loaded using hc_load_sample_data and passed to hc_line_time_series. The source code for these functions can be found in highcharts folder in the PyXLL examples github repo.

 

This spreadsheet contains the VBA code to write an Excel range as a clean, formatted HTML page, with optional auto-refresh.
NOTE If you click on the ‘This spreadsheet’ link above and it opens a ZIP file or a set of HTML files, right-click on the link and choose Save As.

Features:

  • Correctly renders all Excel formatting, including conditional formats.
  • Renders embedded charts.
  • Generates an HTML page with tabs identical to those in the Excel file.
  • Creates clean, formatted HTML that validates to HTML 4.01 strict.
  • Optimised stylesheet to ensure minimal page weight.
  • Supports merged cells with ROWSPAN and COLSPAN.
  • Updates the webpage automatically when cells’ values are changed.
  • The webpage automatically refreshes itself at a user-supplied interval.

The result is almost pixel-perfect, compare this Excel screenshot with the HTML page it generated.

14 March 2013. Update, improved lower tab bar to stop over-spilling (thanks to Paul Palmer) and added support for 64-bit Office.

21 August 2014. Many people have asked how to insert an image in the HTML page. It is not possible to get the contents of an embedded Excel image, but it is possible to get an embedded chart. Here’s how to perform this trick:

  1. In the sheet to be published, make sure no cells are selected
  2. Excel Menu->Insert->Chart and choose any chart type. This will insert an empty chart.
  3. Move and size the empty chart as required
  4. Select the chart by clicking its border
  5. Excel Menu->Layout and click the Picture icon
  6. Select an image file (JPG, BMP, etc) and Insert

28 September 2014. Now handles Unicode text correctly.

17 December 2018. Now outputs hyperlinks correctly.

Like this post? Please share to your friends:
  • Html opens in word
  • How to write text on a line in word
  • Html open with word
  • How to write report in word
  • Html break word in table