Skip to Content
Author's profile photo Pascal GAULIN

How to build a Custom Elements service for SAP Web Intelligence 4.2

DISCLAIMER: The SCN, Content, and Services are being provided to You AS IS. To the fullest extent allowable by law, SAP does not guarantee or warrant any features or qualities of the SCN, Content, or Services or give any undertaking with regard to any other quality. Statements and explanations to SCN, Content or Services in promotional material or on SCN and in the documentation are made for explanatory purposes only; they are not meant to constitute any guarantee or warranty of certain features. No warranty or undertaking shall be implied by a User from any published SAP description of or advertisement except to the extent SAP has expressly confirmed such warranty or undertaking in writing. Warranties are validly given only with the express written confirmation of SAPs management.


One of the great features delivered in Web Intelligence 4.2 is the “Custom Elements” feature. Custom Elements are external visualizations defined by you or by third-parties and which can be used in your Web Intelligence reports, like any native chart.

A Custom Element can be anything you want: a new type of chart or table, or any other kind of visualization, as long as it complies with the following rules:

  • Support for at least one media type output – text/HTML is preferred, bitmap format is also recommended to be able to print or publish the Custom Element to PDF or Excel
  • Support for the Web Intelligence metadata and data feeding model
  • (In a future release) Support for the Web Intelligence settings model

Multiple Custom Elements can be delivered by a same service. This service is accessed through a simple HTTP URL, managed from the BOE Central Management Console.

Public APIs have been defined to support the communication between the Web Intelligence clients and the Custom Elements service. See the documentation on SAP Help Portal: http://help.sap.com/businessobject/product_guides/sbo42/en/sbo42sp1_webisl_dev_guide_en.pdf

Pre-requisites for this sample

This sample is based on Google Charts (see https://developers.google.com/chart/) and uses some open source software as well as a proprietary JavaScript application to wrap the Google Charts API in a Custom Elements service.

Get the necessary open source software

In this sample, we chose to run the service on a very simple JavaScript server called NodeJS.  It is available for free on NodeJS website: http://nodejs.org.


Download and execute the MSI file for Windows (in this sample we are assuming the installation is done on Microsoft Windows). Note that for the purpose of this sample, we have used NodeJS v5.1.0.


In order to create a bitmap output from your Custom Elements, you will also need to install PhantomJS, available on the PhantomJS website: http://phantomjs.org. It comes as a ZIP file:

  1. Extract the EXE file from the ZIP
  2. Paste it into the NodeJS folder (“C:\Program Files\nodejs”, by default)

Finally, you will also need a few NodeJS plugins to execute the sample application. To download and install these plugins:

  1. Open a command window in Administrator mode in the NodeJS folder
  2. Set the npm proxy: this is mandatory if you access the internet through a proxy server, since the following instructions will download additional packages:

    npm config set proxy “http://your_proxy:port

    3. Type in the following instructions (do not copy and paste, to prevent special characters from being copied):

        npm install phantom-proxy (necessary to use phantomJS from nodeJS)

        npm install xmldoc (necessary to parse the XML code)

        npm install body-parser (necessary to parse the commands from Web Intelligence)

        npm install pm2 –g (PM2 is a NodeJS process manager and is mandatory to manage your Custom Elements service)


Note that there are a few errors and warnings when installing these plugins but they are not blocking:

/wp-content/uploads/2016/03/nodejs_909507.jpg


If the instructions “npm install” run in a few seconds, then it is very likely that the proxy setting is incorrect. Typically, these instructions should take a few minutes to download and install each additional package. All plugins are installed in the “node_modules” sub-folder of NodeJS.


Build your Custom Elements service

Create a JavaScript program to use the Google Charts API

In the NodeJS folder, save the “CustomElementsGoogleCharts.txt” file attached to this article and change its file name extension into CustomElementsGoogleCharts.js. This application contains the necessary JavaScript code to use a limited set of the Google Charts API as Custom Elements in Web Intelligence documents.

You will need to modify this code to choose a port number. For instance:

// Set the port number

app.listen(8095);


Start the sample

Using PM2, you are now ready to start your Custom Elements service.

  1. Open a command window in the NodeJS folder.
  2. Type in the following command:

    pm2 start “CustomElementsGoogleCharts.js”


If successful, you should now see in the command window a table showing CustomElementsGoogleCharts running as a service (pid value might differ from the example below):

/wp-content/uploads/2016/03/pm2_909508.jpg

Concerning the PM2 NodeJS process manager, the following commands can be useful:

    pm2 list                                            Shows all processes installed on your NodeJS server

    pm2 stop <App name|id|all>        Stops all or the specified JavaScript application

    pm2 restart <App name|id|all>  Restarts all or the specified JavaScript application

    pm2 delete <App name|id|all>    Removes all or the specified JavaScript application from the server

    pm2 reload <App name|all>          Reloads all or the specified JavaScript application – useful after you have modified the application


Test your Custom Elements service


Test the service in a browser: http://your_service:port  (no backslash at the end of the URL!). The port number is the one set in your service.

If all goes well, you should see this message in your browser: “Server up and running!” If you don’t see that message, check the NodeJS error log file stored in C:\Users\your_user_account\.pm2\logs.

More information on how to debug NodeJS applications can be found on the GitHub Web site: https://github.com/joyent/node/wiki/Using-Eclipse-as-Node-Applications-Debugger

Plug your Custom Elements service into Web Intelligence

This last step is the easiest! In order to use your Custom Elements service in Web Intelligence, you need to declare that service in the BI Platform Central Management Console (CMC).


In the Custom Elements tab of the Web Intelligence application parameters:

  1. In the CMC, click on “Applications”, right-click on “Web Intelligence” and select “Properties”
  2. In the “Properties” dialog box, click on the “Custom Elements” tab and then click on “Add Service…”
  3. Give a name to your service
  4. Enter its URL. This should be “http://your_service:port” (no backslash at the end of the URL!)
  5. Click on “Test” to make sure the service is alive and correctly answers all tests. If it does, then the supported media should be displayed.
  6. Keep the default “Element Format” value and click OK.
  7. On the next screen, make sure you click in the checkbox to enable your service, then save and close the window.
  8. Next time you edit a Web Intelligence document on that BI Platform you should see the Custom Elements button in the Web Intelligence toolbar. If you click on that button, you should be able to insert one of the proposed Google Charts in the document you are editing.


That’s it!

Speedometer.jpg

Assigned tags

      76 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Matthew Shaw
      Matthew Shaw

      Wonderful document, thank you so much Pascal

      With this I was able to build this

      BI4.2 Webi Custom Chart.jpg

      Author's profile photo Thierry Baraton
      Thierry Baraton

      Hello

      thanks for the post

      have you post the way to do this

      have you try with d3js ?

      thanks a lot

       

      Author's profile photo Pierrick HORPIN
      Pierrick HORPIN

      Hi Matthew,

      do you have posted somewhere a How-to guide to perform this beautiful Sankey diagram ?

      Thanks in advance.

      Rgds

      Pierrick

       

      Author's profile photo Matthew Shaw
      Matthew Shaw

      Hello Pierrick, Sorry I don't. I think if you follow this blog, you should be able to do it. I did. Regards, Matthew

       

      Author's profile photo Pierrick HORPIN
      Pierrick HORPIN

      Hi Matthew,

      could you provide me the CustomElementsGoogleCharts.js file, because I am able to display a Google Pie chart but not a Sankey Diagram.

      Thank you in advance.

      Pierrick

      Author's profile photo Alfons Gonzalez Comas
      Alfons Gonzalez Comas

      Great. This looks very promising. I am sure that this will make WebI a solid candidate for advanced visualization pruposes with outstanding calculation capabilities!

      Would be possible to include (in the future) another sample of custom element service based on the other well-known API visualization: D3.js

      Thanks

      Author's profile photo Hayden Gill
      Hayden Gill

      It really would be great to see something based on D3, as quite a few Lumira extensions are based on D3 as well. It would let us see how easy/difficult it will be to leverage the work the Lumira dev community has put in.

      Author's profile photo Matthew Shaw
      Matthew Shaw

      Thank you Hayden and Alfons. Over the next week or so I'm going to try and take a Lumira Extension and bring it into Webi. Webi needs a 'wrapper' around it, but in theory its quite easy. I'll keep you posted! Regards Matthew

      Author's profile photo Hayden Gill
      Hayden Gill

      That would be fantastic, especially if you can walk us through the steps required/where the two interfaces differ.

      Author's profile photo Alfons Gonzalez Comas
      Alfons Gonzalez Comas

      Hi Matthew,

       

      Any update about the D3 based sample? This google chart implementation is a good demo of charting capabilities but is restricted to charts delivered by Google.

       

      Thx

      Author's profile photo Thomas Nielsen
      Thomas Nielsen

      The official documentation on how to make the corresponding web service is a bit vague, so this is an excellent and understandable example.

      From what I have heard, this was also presented at the DSUG event last month with a more extended example. It would be nice if that presentation could be shared here on SCN as well 🙂

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hello Thomas,

      At the DSAG, we have shown early previews of what some of SAP partners are currently building with this new feature... 🙂

      Galigeo WebIntelligence Custom Elements - YouTube

      Embedding CMaps Analytics in Webi 4.2 - YouTube

          Pascal.

      Author's profile photo Former Member
      Former Member

      Hi Pascal,

      From the user guide, I found that there is a query parameter 'locale'. So I want to know how to use this query parameter, do you have any ideas?

      Author's profile photo Arnaud DEVELAY
      Arnaud DEVELAY

      If you want a better integration with Web Intelligence, you should use this parameter to localize your response.

      For example, when Web Intelligence retrieves the list of supported visualizations your service will be called using the following URL:

      http://hostname:port/api/visualizations?locale=en_US

      In this case, you should provide a JSON response with localized name and description.

      Author's profile photo Oliver Gricksch
      Oliver Gricksch

      Works great, thank you Pascal!

      Author's profile photo Craig Wilson
      Craig Wilson

      I'm trying to create a custom element using .NET.  For the render, the documentation is contradictory.  It says:

      Request

      URI: api/visualizations/render<vizID>/render

      HTTP Method: POST

      Then it gives the example:

      Request Example:POST /api/visualizations/funnel/feeds/render?locale=en_US

      I'm having trouble getting it to Render.  Could you let me know what the correct path should be?

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hello Craig,

      This is en error in the documentation. It has been corrected in the next version (not yet available).

      The documentation should read:

      URI: api/visualizations/<vizID>/render

      And the example is:

      POST /api/visualizations/funnel/render?locale=en_US

      Regards,

          Pascal.

      Author's profile photo Craig Wilson
      Craig Wilson

      I'm not able to create more than 2 dimensional feeds. When I do, there is an informational message at the top of the custom element saying that only 2 of type dimension are allowed.  I'm trying to create a Google Gantt chart, which needs Task Name, Start Date and End Date at a bare minimum.  Why is there a limit on dimensions?  The Gantt chart could use up to 7 dimensions.

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hello Craig,

      Although you can define and use as many feeds as you want, the number of dimensional axis is limited to 2. This is because the Custom Elements feeding model is based on the Web Intelligence chart engine feeding model. In all WebI charts, the feeding works like in a cross-table, i.e. with a maximum of 2 axis.

      Regards,

      Pascal.

      Author's profile photo Arnaud DEVELAY
      Arnaud DEVELAY

      Hello Craig,

      You can have as much dimensional feed as you want, but each dimesional feeds must be bound to one of our 2 axis: the dataset model of CustomElement is the same as the one for CrossTable.

      Regards,

      Arnaud

      Author's profile photo Craig Wilson
      Craig Wilson

      Ah.  Thank you for that clarification.  I reviewed the CrossTable and saw that I can bind multiple dimensions to a single axis.  That was what I needed to pass multiple dimensions to the Google Gantt chart, and I've now got it working!  Thank you!

      Author's profile photo Hayden Gill
      Hayden Gill

      Hi Craig, where did you find the documentation on the CrossTable ? Thanks

      Author's profile photo Craig Wilson
      Craig Wilson

      I didn't actually look at documentation.  I just remembered that for many charts in general, you can add more than 1 dimension to a single axis.  The Web Intelligence user guide might document this, I'm not sure.  The Webi charting engine tries to interpret all the data and pass what would be the final representation on a chart.  For my Gantt chart, that would not work because I need more than 2 dimensions and I don't want Webi to combine/interpret the data.  I just need the raw data for each dimension, and the order of the rows needs to be preserved across the data so that I can reconstruct a row with multiple columns in my code.  Using multiple dimensions on a single axis accomplishes this.  I'm posting a screenshot in response to Arnauds request below.

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Thank you Craig.

      Indeed, Custom Elements use the same feeding model than WebI traditional charts and this model is based on a cross-table, i.e.: two axis with as many dimensions as you wish on each axis.

      Author's profile photo Arnaud DEVELAY
      Arnaud DEVELAY

      You're welcome.

      Can you share a screenshot of the result ?

      Author's profile photo Craig Wilson
      Craig Wilson

      Gantt.png

      Author's profile photo Former Member
      Former Member

      Where did you get the gantt chart visualizations? Can that be exported to Excel and PDF?

      Author's profile photo Fernando Girante
      Fernando Girante

      Hello Craig,

      do you mind to share how you did it?

      Can you please share your js file?

       

      Best regards

      Author's profile photo Matthew Shaw
      Matthew Shaw

      Perhaps a small, but important note! This solution will work in Web Intelligence, but not in Web Intelligence Rich Client. When using Web Intelligence Rich Client, the visualisation will appear blank.

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hi Matthew,

      This is because the Custom Elements service goes through the BOE server where it has been configured. The Rich Client therefore needs to be connected to that BOE server in order to show the Custom Elements visualizations.

      Best,

      Pascal.

      Author's profile photo Francisco Almeida
      Francisco Almeida

      Thanks a lot, great guide.

      I have not been able to get it to work with WebI Java Applet - it only worked with the HTML version (although I can view the custom elements via the applet interface - I cannot create a new custom element via Applet). Using 4.2 SP2.

      com.businessobjects.rebean.wi.exception.CustomElementExceptions$CustomElementAvailableTypesException: An exception occured when retrieving the feeding metadata from the CustomElement third party service. (Error: RWI 00634)

      Author's profile photo Craig Wilson
      Craig Wilson

      I can't understand what is going on with the data that Webi is passing to the custom element.  Here is a picture of the table, and below is the JSON from Fiddler of the render HTTP POST.

      Oddities:

      1. It is not combining the two "5/9/16" values.  I think this may be a leading or trailing space in my data

      2. How do I relate Task Name with Task Start Date?

      3. The measures are even more crazy.  I have no idea what to make of them or how to deal with them!

      task data image.png

      {

        "width": 400,

        "height": 300,

        "dpi": 96,

        "font": {

          "name": "Arial",

          "size": 9,

          "color": "#333333",

          "isBold": false,

          "isItalic": false

        },

        "feeding": [

          {

            "id": "task-name",

            "expressions": [

              {

                "dataId": 1

              }

            ]

          },

          {

            "id": "start-date",

            "expressions": [

              {

                "dataId": 3

              }

            ]

          },

          {

            "id": "duration",

            "expressions": [

              {

                "dataId": 5

              }

            ]

          },

          {

            "id": "percent-complete",

            "expressions": [

              {

                "dataId": 7

              }

            ]

          }

        ],

        "data": [

          {

            "values": {

              "dataType": "string",

              "dataStructure": "simple",

              "rawvalues": [

                "DMR,

                "DMR Ops",

                "IMR",

                "PjMR",

                "PMR",

                "PMR Ops",

                "PPR",

                "SMR for Partners",

                "SMR for Services"

              ],

              "cardinality": 1

            },

            "id": "1",

            "title": "Task Name",

            "type": "dimension"

          },

          {

            "values": {

              "dataType": "string",

              "dataStructure": "simple",

              "rawvalues": [

                "2/1/16",

                "4/11/16",

                "5/9/16",

                "5/9/16",

                "5/18/16",

                "6/8/16"

              ],

              "cardinality": 1

            },

            "id": "3",

            "title": "Task Start Date",

            "type": "dimension"

          },

          {

            "values": {

              "dataType": "double",

              "dataStructure": "simple",

              "rawvalues": [

                [

                  128,

                  1.7e+308,

                  1.7e+308,

                  123,

                  65,

                  1.7e+308,

                  53,

                  1.7e+308,

                  1.7e+308

                ],

                [

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  0,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308

                ],

                [

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  4

                ],

                [

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  0,

                  1.7e+308

                ],

                [

                  1.7e+308,

                  1.7e+308,

                  0,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308

                ],

                [

                  1.7e+308,

                  6,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308

                ]

              ],

              "cardinality": 2

            },

            "id": "5",

            "title": "Task Duration (days)",

            "type": "measure"

          },

          {

            "values": {

              "dataType": "double",

              "dataStructure": "simple",

              "rawvalues": [

                [

                  8,

                  1.7e+308,

                  1.7e+308,

                  3,

                  6,

                  1.7e+308,

                  25,

                  1.7e+308,

                  1.7e+308

                ],

                [

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  11,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308

                ],

                [

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  13

                ],

                [

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  9,

                  1.7e+308

                ],

                [

                  1.7e+308,

                  1.7e+308,

                  18,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308

                ],

                [

                  1.7e+308,

                  14,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308,

                  1.7e+308

                ]

              ],

              "cardinality": 2

            },

            "id": "7",

            "title": "Task Percent Complete (fake data)",

            "type": "measure"

          }

        ]

      }

      Author's profile photo Arnaud DEVELAY
      Arnaud DEVELAY

      Hello Craig,

      There are a lot of NaN values (1.7e+308) in your dataset.

      I think there is something wrong with your feeding declaration. Can you paste the result of a call to /api/<vizid>/feeds ?

      Thanks,

      Arnaud

      Author's profile photo Craig Wilson
      Craig Wilson

      Is it possible to use the custom element html format when viewing a report in the Webi html viewer and then substitute the custom element image format when the report is exported/viewed in PDF mode?  How does one accomplish this so that one report can be used for both viewing (html format) and exporting (image format) of a custom element?

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hello Craig,

      Yes, it is possible. In the CMC, set the media to text/html. Then, WebI clients will automatically request html content when displaying the custom element content in documents, while WebI server will keep requesting for bitmap content when exporting to PDF or Excel.

      Whatever media you set in the CMC, WebI server will always request a bitmap content when exporting to PDF or Excel.

      Regards,

          Pascal.

      Author's profile photo Craig Wilson
      Craig Wilson

      Perfect!  Thanks!

      Author's profile photo Former Member
      Former Member

      Hi,

      Is there a bug that the second try call is also not POST (Apache access log):

      10.1.9.157 - - [01/Aug/2016:13:02:31 +0000] "POST /custom/api/visualizations/EmailAlert/render?format=text/html&locale=en_US HTTP/1.1" 301 310

      10.1.9.157 - - [01/Aug/2016:13:02:31 +0000] "GET /custom/api/visualizations/EmailAlert/render/?format=text/html&locale=en_US HTTP/1.1" 200 117

      Thanks, Timo

      Author's profile photo Arnaud DEVELAY
      Arnaud DEVELAY

      Hello,

      I don't think this call comes from Web Intelligence. Every calls to /render endpoint are made with the verb POST. GET is simply not implemented on our side.

      Arnaud

      Author's profile photo Former Member
      Former Member

      Here is speedometer gauge but using PHP. The render file cannot be in /render/index.php, so file "render" needs to be able to execute PHP. Add this to httpd.conf:

      <Location "/gauge/api/visualizations/gauge">

        ForceType application/x-httpd-php

      </Location>

      FEEDS:

      <?php

      header("Content-type:application/json");

      echo '{

      "feeds": [

      {

      "id": "Title",

      "name": "Title Text",

      "description": "Title text",

      "type": "dimension",

      "axis": "0",

      "min": "1",

      "max": "1"

      },

      {

      "id": "Reverse",

      "name": "Reverse Colors (Y/N)",

      "description": "Reverse Colors",

      "type": "dimension",

      "axis": "1",

      "min": "1",

      "max": "1"

      },

      {

      "id": "Value",

      "name": "Value to Display",

      "description": "Value to Display",

      "type": "measure",

      "min": "1",

      "max": "1"

      },

      {

      "id": "Min",

      "name": "Scale Minimum Value",

      "description": "Scale Minimum Value",

      "type": "measure",

      "min": "0",

      "max": "1"

      },

      {

      "id": "Max",

      "name": "Scale Maximum Value",

      "description": "Scale Maximum Value",

      "type": "measure",

      "min": "0",

      "max": "1"

      },

      {

      "id": "Red",

      "name": "Red Band Start",

      "description": "Red Band Size",

      "type": "measure",

      "min": "0",

      "max": "1"

      },

      {

      "id": "Yellow",

      "name": "Yellow Band Start",

      "description": "Yellow Band Size",

      "type": "measure",

      "min": "0",

      "max": "1"

      },

      {

      "id": "Green",

      "name": "Green Band Start",

      "description": "Green Band Size",

      "type": "measure",

      "min": "0",

      "max": "1"

      },

      {

      "id": "Size",

      "name": "Size of the Graph",

      "description": "Size of Graph",

      "type": "measure",

      "min": "0",

      "max": "1"

      }]}';

      ?>

      RENDER:

      <?php

      $a = file_get_contents('php://input');

      header("Content-type:text/html");

      $b = json_decode($a,true);

      isset( $b['data']['0']['values']['rawvalues'][0]) ? $label = $b['data']['0']['values']['rawvalues'][0] : $label = '' ;

      isset( $b['data']['1']['values']['rawvalues'][0]) ? $reverse = $b['data']['1']['values']['rawvalues'][0] : $reverse = 'N' ;

      isset( $b['data']['2']['values']['rawvalues'][0][0]) ? $value = $b['data']['2']['values']['rawvalues'][0][0] : $value = 0 ;

      isset( $b['data']['3']['values']['rawvalues'][0][0]) ? $min = $b['data']['3']['values']['rawvalues'][0][0] : $min = 0 ;

      isset( $b['data']['4']['values']['rawvalues'][0][0]) ? $max = $b['data']['4']['values']['rawvalues'][0][0] : $max = 100 ;

      isset( $b['data']['5']['values']['rawvalues'][0][0]) ? $red = $b['data']['5']['values']['rawvalues'][0][0] : $red = 100 ;

      isset( $b['data']['6']['values']['rawvalues'][0][0]) ? $yellow = $b['data']['6']['values']['rawvalues'][0][0] : $yellow = 100 ;

      isset( $b['data']['7']['values']['rawvalues'][0][0]) ? $green = $b['data']['7']['values']['rawvalues'][0][0] : $green = 100 ;

      isset( $b['data']['8']['values']['rawvalues'][0][0]) ? $size = $b['data']['8']['values']['rawvalues'][0][0] : $size = '200' ;

      ?>

      <html>

        <head>

          <script type="text/javascript" src="https://www.google.com/jsapi"></script>

          <script type="text/javascript">

            google.load("visualization", "1", {packages:["gauge"]});

            google.setOnLoadCallback(drawChart);

            function drawChart() {

              var data = google.visualization.arrayToDataTable([

                ['Label', 'Value'],

                ['<?php echo $label; ?>', <?php echo $value; ?>]

              ]);

              var options = {

                <?php if ($reverse=='N') { ?>

                redFrom: <?php echo $red; ?>, redTo: <?php echo $yellow; ?>,

                yellowFrom:<?php echo $yellow; ?>, yellowTo: <?php echo $green; ?>,

                greenFrom:<?php echo $green; ?>, greenTo: <?php echo $max; ?>,

                <?php } else { ?>

                 redFrom: <?php echo $red; ?>, redTo: <?php echo $max; ?>,

                yellowFrom:<?php echo $yellow; ?>, yellowTo: <?php echo $red; ?>,

                greenFrom:<?php echo $green; ?>, greenTo: <?php echo $yellow; ?>,

                <?php } ?>

                height: <?php echo $size; ?>,

                minorTicks: 5, max: <?php echo $max; ?>, min: <?php echo $min; ?>

              };

              var chart = new google.visualization.Gauge(document.getElementById('chart_div'));

              chart.draw(data, options);

            }

          </script>

        </head>

        <body>

          <div id="chart_div"></div>

        </body>

      </html>

      Author's profile photo vivek kumar
      vivek kumar

      Hi,

      Please let me know if anyone worked on d3 visualization custom element or have brief idea about this.

      Thanks,

      Vivek

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hi Vivek,

      Yes, we have tested some D3 visualizations, such as the famous calendar example. That works fine. Like with the Google chart sample we have documented in this article, it needs a little bit of coding for the wrapper: to list the visualization, its feeds and then to render it.

      Regards,

      Pascal.

      Author's profile photo vivek kumar
      vivek kumar

      Hi Pascal,

      Thanks for your reply.

      Could you please share some sample code for wrapping?

      will this code work for Lumira CVOM extensions too?

      Thanks,

      Vivek

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hi Vivek,

      The only code we can share, is the one in this sample. But, basically, it's very similar to the code used with a D3 visualization, or a Lumira visualization or any other kind of javascript visualization... You just need to figure out how to map the Custom Elements APIs to the visualization library you want to use.

      Regards,

      Pascal.

      Author's profile photo Craig Wilson
      Craig Wilson

      Pascal,

      Have you worked with the new ability in SP3 to provide Custom Settings?  I can get custom settings to show up for the user to interact with, but they are not being passed back to me in the render call (api/visualizations/<vizID>/render).  The documentation says "Rendering setting values are saved in the Web Intelligence document" on page 62 of the "SAP BusinessObjects BI Developer’s Guide for Web Intelligence and the BI Semantic Layer", but that doesn't appear to be the case for me.  Once I provide a value for a custom setting, it seems to disappear.

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hello Craig,

      I have personally not played with the new Custom Elements settings API in Web Intelligence 4.2 SP3. However some of my colleagues did. I also know that some SAP partners are currently working with it.

      I know one bug has been found with the "string" type parameter, in the dHTML client, but I have not heard of any other problem...
      Regards,

      Pascal.

      Author's profile photo Craig Wilson
      Craig Wilson

      I created a ticket with SAP support, but they are struggling to even begin to help me because they have not worked at all with Custom Elements. Could you ask your colleagues to describe the basic flow of the custom settings?  As I understand it, I create a json string that defines the format for the custom settings I want.  This json format is returned to Web Intelligence when the GET api/visualizations/<viz_id>/settings is invoked when a user right clicks a custom element and chooses format.  From that point on, Web Intelligence server side code should handle saving the values the user provides?  It should also pass those values to the POST api/visualizations/<viz_id>/render call?  But Web Intelligence is doing neither.  It is not saving, and it is not passing setting values back.  I must be missing something?

      Author's profile photo Former Member
      Former Member

      Hi Pascal,
      can I have this attachment (CustomElementsGoogleCharts.txt, couldn’t see it in this article), as I’m newbie to Java and have no idea on how to build one from scratch? and when you said testing service http://your_service:port, what did you mean, is it BO server URL or what else.

       

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hi Richard,

      See below.

      Regards,

      Pascal.

      Author's profile photo Former Member
      Former Member

      Hi Pascal,
      can I have the file CustomElementsGoogleCharts.txt, couldn’t see it in this article.

       

      Thanks,

       

      Regards

       

      José

       

      Author's profile photo Former Member
      Former Member

      Hi,

      As per my understanding, there are mainly two parts i.e.

      1. Develop your custom element
      2. Publish your custom element using NodeJS as a URL

      In order to achieve the first step, is there any reference document available ? I am not getting it anywhere. Please help me to find it.

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hi Devesh,

      See below.

      Regards,

      Pascal.

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Dear all,

      It seems the sample attachment has been lost during the SCN migration. I'm deeply sorry about that. Here it is again, inline :

      // DISCLAIMER: The SCN, Content, and Services are being provided to You AS IS.
      // To the fullest extent allowable by law, SAP does not guarantee or warrant any
      // features or qualities of the SCN, Content, or Services or give any undertaking
      // with regard to any other quality. Statements and explanations to SCN, Content
      // or Services in promotional material or on SCN and in the documentation are
      // made for explanatory purposes only; they are not meant to constitute any
      // guarantee or warranty of certain features. No warranty or undertaking shall be
      // implied by a User from any published SAP description of or advertisement
      // except to the extent SAP has expressly confirmed such warranty or undertaking
      // in writing. Warranties are validly given only with the express written
      // confirmation of SAPs management.

      // For a description of the Google visualizations used in this application,
      // see https://developers.google.com/chart/interactive/docs/

      // Initialization
      var express = require('express');
      var bodyParser = require('body-parser')
      var path = require('path');
      var phantomProxy = require('phantom-proxy');
      var fs = require('fs');
      var util = require('util');
      var app = express();
      app.use(bodyParser.json());
      var urlencodedParser = bodyParser.urlencoded({ extended: true });
      var CR = String.fromCharCode(13);

      // Set the port number
      app.listen(port);

      app.get("/", function(req, res) {
      res.send("Server Up and Running !");
      });

      app.get("/api/about", function(req, res) {
      logInfo("About");
      res.send("Google Visualizations");
      });

      app.get("/api/formats", function(req, res) {
      logInfo("Start Format");
      var format = {"formats": ["text/html", "image/png"]};
      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify(format));
      logInfo("Stop Format");
      });

      app.get("/api/visualizations", function(req, res) {
      logInfo("Start Visualizations");
      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify(factory));
      logInfo("Stop Visualizations");
      });

      app.get("/api/visualizations/:vizid/feeds", function(req, res) {
      logInfo("Start feeds");
      var viz = factory.getViz(req.params.vizid);
      logInfo(" Viz ID => " + viz.name);

      var feedArray = [];
      if (!(viz.categoryAxisMin == 0 && viz.categoryAxisMax == 0)) {
      feedArray.push(
      {
      "id": "category-axis",
      "name": viz.categoryAxisName,
      "description": viz.categoryAxisDesc,
      "axis": "0",
      "type": "dimension",
      "min": ""+viz.categoryAxisMin+"",
      "max": ""+viz.categoryAxisMax+""
      }
      );
      }
      if (!(viz.regionColorMin == 0 && viz.regionColorMax == 0)) {
      feedArray.push(
      {
      "id": "region-color",
      "name": viz.regionColorName,
      "description": viz.regionColorDesc,
      "axis": "1",
      "type": "dimension",
      "min": ""+viz.regionColorMin+"",
      "max": ""+viz.regionColorMax+""
      }
      );
      }
      if (!(viz.primaryValueMin == 0 && viz.primaryValueMax == 0)) {
      feedArray.push(
      {
      "id": "primary-values",
      "name": viz.primaryValueName,
      "description": viz.primaryValueDesc,
      "type": "measure",
      "min": ""+viz.primaryValueMin+"",
      "max": ""+viz.primaryValueMax+""
      }
      );
      }
      var feeds = {
      "feeds": feedArray
      };
      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify(feeds));
      logInfo("Stop feeds");
      });

      app.post("/api/visualizations/:vizid/render", function(req, res) {
      logInfo("Start Render");
      var viz = factory.getViz(req.params.vizid);
      var html = viz.generate(req);
      var height = req.body.height;
      var width = req.body.width;
      var format = req.headers.accept;
      logInfo(" Viz ID => "+viz);

      // Bitmap generation
      if(format == "image/png") {
      var self = this;
      var randomnumber=Math.floor(Math.random()*101)
      var path = 'html_page_'+randomnumber+'.html';
      fs.writeFileSync(path, html);
      phantomProxy.create(function(proxy) {
      proxy.page.open(path, function (result) {
      proxy.page.set('viewportSize', { width: width, height: height });
      proxy.page.set('clipRect', { top: 0, left: 0, width: width, height: height });
      proxy.page.waitForSelector("#chart", function (result){
      proxy.page.renderBase64('PNG', function(result){
      var bitmap = new Buffer(result, 'base64');
      res.writeHead(200, {'Content-Type': 'image/png' });
      res.end(bitmap, 'binary');
      phantomProxy.end();
      fs.unlinkSync(path); // Delete temp file
      });
      });
      });
      });
      }

      // HTML generation
      else {
      res.writeHead(200, {'Content-Type': 'text/html' });
      res.end(html);
      }
      logInfo("Stop Render");
      });

      var VizFactory = function() {
      this.visualizations = [];
      this.getViz = function(id) {
      for (var index = 0 ; index < this.visualizations.length ; index++) {
      if (this.visualizations[index].id === id) return this.visualizations[index];
      }
      };
      };

      var Visualization = function(id, name, catMin, catMax, regMin, regMax, valMin, valMax, catName, catDesc, regName, regDesc, valName, valDesc) {
      this.id = id;
      this.name = name;
      this.categoryAxisMin = catMin;
      this.categoryAxisMax = catMax;
      this.categoryAxisName = catName;
      this.categoryAxisDesc = catDesc;
      this.regionColorMin = regMin;
      this.regionColorMax = regMax;
      this.regionColorName = regName;
      this.regionColorDesc = regDesc;
      this.primaryValueMin = valMin;
      this.primaryValueMax = valMax;
      this.primaryValueName = valName;
      this.primaryValueDesc = valDesc;
      };

      /*******************
      * Google Pie Chart
      *
      * 1: ID
      * 2: Name
      * 3: Category feeds min = 1
      * 4: Category feeds max = 1
      * 5: Region feeds min = 0 (optional)
      * 6: Region feeds max = 1
      * 7: Value feeds min = 1
      * 8: Value feeds max = 1
      * 9: Category feeds name
      * 10: Category feeds description
      * 11: Region feeds name
      * 12: Region feeds description
      * 13: Value feeds name
      * 14: Value feeds description
      */

      var vizPie = new Visualization("googlepie", "Google Pie", 1, 1, 0, 1, 1, 1, "Sectors Dimension", "Dimension on which the pie will be splitted", "Treillis Dimension", "Dimension to repeat the pies", "Measure", "Measure values");

      vizPie.generate = function(req) {
      var w = req.body.width?req.body.width:800;
      var h = req.body.height?req.body.height:800;
      var hCell = h;
      var dimData = getData(req, getFeedings(req, "category-axis")[0].dataId);
      var regFeed = getFeedings(req, "region-color");
      var mesData = getData(req, getFeedings(req, "primary-values")[0].dataId);
      var count0 = 1;
      var count1 = 1;
      var values = [];
      var value = [];

      if (regFeed) {
      if (regFeed.length === 1) {
      var regData0 = getData(req, regFeed[0].dataId);
      count0 = regData0.values.rawvalues.length;
      for (var j = 0 ; j < count0 ; j++) {
      value = [];
      var reg = regData0.values.rawvalues[j];
      for (var i = 0 ; i < dimData.values.rawvalues.length ; i++) {
      var dim = dimData.values.rawvalues[i];
      var mes = mesData.values.rawvalues[j][i];
      if (mes == 1.7e+308) mes = 0; // 1.7e+308 = WebI overflow
      value.push([dim.toString(), mes]);
      }
      values.push(value);
      }
      } else {
      var regData0 = getData(req, regFeed[0].dataId);
      var regData1 = getData(req, regFeed[1].dataId);
      var regValues0 = distinct(regData0.values.rawvalues);
      var regValues1 = distinct(regData1.values.rawvalues);
      count0 = regValues0.length;
      count1 = regValues1.length;
      hCell = Math.floor(h/count1);
      var allData = {};
      for (var i = 0 ; i < mesData.values.rawvalues.length ; i++) {
      if (!allData[regData0.values.rawvalues[i]]) allData[regData0.values.rawvalues[i]] = {};
      allData[regData0.values.rawvalues[i]][regData1.values.rawvalues[i]] = mesData.values.rawvalues[i];
      }
      for (var j = 0 ; j < count1 ; j++) {
      for (var i = 0 ; i < count0 ; i++) {
      var k = i + count0*j;
      value = [];
      if (allData[regValues0[i]] && allData[regValues0[i]][regValues1[j]]) {
      var m = allData[regValues0[i]][regValues1[j]];
      for (var l = 0 ; l < dimData.values.rawvalues.length ; l++) {
      var dim = dimData.values.rawvalues[l];
      var mes = m[l];
      if (mes == 1.7e+308) mes = 0;
      value.push([dim.toString(), mes]);
      }
      }
      values.push(value);
      }
      }
      }
      } else {
      for (var i = 0 ; i < dimData.values.rawvalues.length ; i++) {
      var dim = dimData.values.rawvalues[i];
      var mes = mesData.values.rawvalues[i];
      if (mes == 1.7e+308) mes = 0;
      value.push([dim.toString(), mes]);
      }
      values.push(value);
      }
      var html =
      '<!DOCTYPE html>' + CR +
      '<html style="height:100%; display:table">' + CR +
      ' <head>' + CR +
      ' <script type="text/javascript" src="https://www.google.com/jsapi"></script>' + CR +
      ' <script type="text/javascript">' + CR +
      ' google.load("visualization", "1.0", {"packages":["corechart"]});' + CR +
      ' google.setOnLoadCallback(drawChart);' + CR +
      ' function drawChart() {' + CR +
      ' var data, chart;' + CR;
      for (var j = 0 ; j < count1 ; j++) {
      for (var i = 0 ; i < count0 ; i++) {
      var k = j + count1 * i;
      html +=
      ' data = new google.visualization.DataTable();' + CR +
      ' data.addColumn("string", "' + dimData.title + '");' + CR +
      ' data.addColumn("number", "' + mesData.title + '");' + CR +
      ' data.addRows(' + JSON.stringify(values[k]) + ');' + CR +
      ' var options = {' + CR;
      if (count1 > 100) {
      html += ' title: "' + regValues1[j] + ' / ' + regValues0[i] + '",' + CR;
      } else if (count0 > 100) {
      html += ' title: "' + regData0.values.rawvalues[k] + '",' + CR;
      }
      html +=
      ' backgroundColor: "transparent",' + CR +
      ' legend: "none"' + CR +
      ' };' + CR +
      ' new google.visualization.PieChart(document.getElementById("chart_cell_' + j + '_' + i + '")).draw(data, options);;' + CR;
      }
      }
      html +=
      ' }' + CR +
      ' </script>' + CR +
      ' </head>' + CR +
      ' <body style="border:0; margin:0; padding:0; height:100%; vertical-align:middle; display:table-cell">' + CR +
      ' <table style="border:0; margin:0; padding:0; width:100%; height:100%; table-layout:fixed">' + CR;
      for (var j = 0 ; j < count1 ; j++) {
      html += ' <tr style="border:0; margin:0; padding:0">' + CR;
      for (var i = 0 ; i < count0 ; i++) {
      var k = i + i*j;
      html += ' <td style="border:0; margin:0; padding:0; height:' + hCell + 'px" id="chart_cell_' + j + '_' + i + '"></td>' + CR;
      }
      html += ' </tr>' + CR;
      }
      html +=
      ' </table>' + CR +
      ' </body>' + CR +
      '</html>' + CR;
      return html;
      };

      /*********************
      * Google Gauge Chart
      *
      * 1: ID
      * 2: Name
      * 3: Category feeds min = 1
      * 4: Category feeds max = 1
      * 5: Region feeds min = 0 (unused)
      * 6: Region feeds max = 0 (unused)
      * 7: Value feeds min = 1
      * 8: Value feeds max = 1
      * 9: Category feeds name
      * 10: Category feeds description
      * 11: Region feeds name
      * 12: Region feeds description
      * 13: Value feeds name
      * 14: Value feeds description
      */
      var vizGauge = new Visualization("googlegauge", "Google Gauge", 1, 1, 0, 0, 1, 1, "Gauge Dimension", "Dimension values to repeat the gauges", "N/A", "N/A", "Measure", "Gauge value (should be 1 to 100)")

      vizGauge.generate = function(req) {
      var w = req.body.width?req.body.width:800;
      var h = req.body.height?req.body.height:800;
      var dimData = getData(req, getFeedings(req, "category-axis")[0].dataId);
      var mesData = getData(req, getFeedings(req, "primary-values")[0].dataId);
      var values = [['Label', 'Value']];
      var RenderingValues = [];

      for (var i = 0 ; i < dimData.values.rawvalues.length ; i++) {
      values.push([dimData.values.rawvalues[i], 0]);
      RenderingValues.push(mesData.values.rawvalues[i]);
      }
      var html =
      '<!DOCTYPE html>' + CR +
      '<html style="height:100%; margin:auto; display:table">' + CR +
      ' <head>' + CR +
      ' <script type="text/javascript" src="https://www.google.com/jsapi"></script>' + CR +
      ' <script type="text/javascript">' + CR +
      ' google.load("visualization", "1", {packages:["gauge"]});' + CR +
      ' google.setOnLoadCallback(drawChart);' + CR +
      ' function drawChart() {' + CR +
      ' var data = google.visualization.arrayToDataTable(' + CR +
      ' ' + JSON.stringify(values) + CR +
      ' );' + CR +
      ' var options = {' + CR +
      ' width:' + w + ',' + CR +
      ' height:' + h + ',' + CR +
      ' greenFrom: 0,' + CR +
      ' greenTo: 20,' + CR +
      ' redFrom: 90,' + CR +
      ' redTo: 100,' + CR +
      ' yellowFrom:75,' + CR +
      ' yellowTo: 90,' + CR +
      ' minorTicks: 5,' + CR +
      ' animation : {' + CR +
      ' duration: 2500,' + CR +
      ' easing: "out"' + CR +
      ' }' + CR +
      ' };' + CR +
      ' var chart = new google.visualization.Gauge(document.getElementById("chart_div"));' + CR +
      ' chart.draw(data, options);' + CR +
      ' setInterval(function() { '+ CR;
      for(var j = 0;j < values.length-1 ; j++){
      html += 'data.setValue('+j+',1,'+ RenderingValues[j] +');' + CR;
      }
      html +=
      ' chart.draw(data,options);' + CR +
      ' }, 1000);' + CR +
      ' }' + CR +
      ' </script>' + CR +
      ' </head>' + CR +
      ' <body style="border:0; margin:0; padding:0; height:100%; vertical-align:middle; display:table-cell">' + CR +
      ' <div style="border:0; margin:0; padding:0" id="chart_div" onmouseover="drawChart();"></div>' + CR +
      ' </body>' + CR +
      '</html>' + CR;
      return html;
      };

      /************************
      * Google Sankey Diagram
      *
      * 1: ID
      * 2: Name
      * 3: Category feeds min = 2
      * 4: Category feeds max = 2
      * 5: Region feeds min = 0 (unused)
      * 6: Region feeds max = 0 (unused)
      * 7: Value feeds min = 1
      * 8: Value feeds max = 1
      * 9: Category feeds name
      * 10: Category feeds description
      * 11: Region feeds name
      * 12: Region feeds description
      * 13: Value feeds name
      * 14: Value feeds description
      */
      var vizSankey = new Visualization("googlesankey", "Google Sankey", 2, 2, 0, 0, 1, 1, "Source & Destination Dimensions", "Source and destination dimensions", "N/A", "N/A", "Measure", "Measure values")

      vizSankey.generate = function(req) {
      var w = req.body.width?req.body.width:1100;
      var h = req.body.height?req.body.height:900;
      var dimCount = getFeedings(req,"category-axis").length;
      var mesCount;
      var values = [];
      var V = [];
      var mesData;
      var FromDimId, FromDimData, ToDimId, ToDimData;

      if(getFeedings(req,"primary-values")){
      mesCount = getFeedings(req,"primary-values").length;
      } else {
      mesCount = 0;
      }
      for(var j = 0; j < dimCount-1; j++){
      if(mesCount == 1){
      mesData = getData(req, getFeedings(req, "primary-values")[0].dataId);
      } else if(mesCount == dimCount-1){
      mesData = getData(req, getFeedings(req, "primary-values")[j].dataId);
      }
      FromDimId = getFeedings(req, "category-axis")[j].dataId;
      ToDimId = getFeedings(req, "category-axis")[j+1].dataId;
      FromDimData = getData(req, FromDimId );
      ToDimData = getData(req, ToDimId);
      for (var i = 0 ; i < FromDimData.values.rawvalues.length; i++) {
      var From = FromDimData.values.rawvalues[i];
      var To = ToDimData.values.rawvalues[i];
      if(From == To){
      To += ' ' + ToDimData.title;
      ToDimData.values.rawvalues[i] = To;
      }
      if(!V[From]){
      V[From]= {};
      }
      if(!V[From][To]){
      V[From][To]= 0;
      }
      if(!mesCount){
      V[From][To] += 1; // =1 if no ponderation desired
      } else {
      V[From][To] += mesData.values.rawvalues[i];
      }
      }
      FromDimLov = distinct(FromDimData.values.rawvalues);
      ToDimLov = distinct(ToDimData.values.rawvalues);
      for(var k = 0; k < FromDimLov.length;k++){
      for(var l = 0; l < ToDimLov.length; l++){
      if(V[FromDimLov[k]][ToDimLov[l]]){
      values.push([FromDimLov[k],ToDimLov[l],V[FromDimLov[k]][ToDimLov[l]]]);
      }
      }
      }
      }
      var html =
      '<!DOCTYPE html>' + CR +
      '<html style="height:100%; margin:auto; display:table">' + CR +
      ' <head>' + CR +
      ' <script type="text/javascript" src="https://www.google.com/jsapi"></script>' + CR +
      ' <script type="text/javascript">' + CR +
      ' google.load("visualization", "1.1", {packages:["sankey"]});' + CR +
      ' google.setOnLoadCallback(drawChart);' + CR +
      ' function drawChart() {' + CR +
      ' var data = new google.visualization.DataTable();' + CR +
      ' data.addColumn(\'string\', \'From\');' + CR +
      ' data.addColumn(\'string\', \'To\');' + CR +
      ' data.addColumn(\'number\', \'Weight\');' + CR;
      for(var n = 0; n < values.length ; n++){
      html +=
      ' data.addRows([' + CR +
      ' [ "'+ values[n][0] +'", "'+ values[n][1] +'", '+ values[n][2] +' ]' + CR +
      ' ]);' + CR;
      }
      html +=
      ' // Sets chart options.' + CR +
      ' var options = {' + CR +
      ' width: ' + w +',' + CR +
      ' height: ' + h + ',' + CR +
      ' };' + CR +
      ' // Instantiates and draws our chart, passing in some options.' + CR +
      ' var chart = new google.visualization.Sankey(document.getElementById(\'sankey_basic\'));' + CR +
      ' chart.draw(data, options);' + CR +
      ' }' + CR +
      '</script>' + CR +
      '</head>' + CR +
      ' <body style="border:0; margin:0; padding:0; height:100%; vertical-align:middle; display:table-cell">' + CR +
      ' <div style="border:0; margin:0; padding:0" id="sankey_basic"></div>' + CR +
      ' </body>' + CR +
      '</html>' + CR;
      return html;
      }

      var factory = new VizFactory();
      factory.visualizations.push(vizGauge);
      factory.visualizations.push(vizPie);
      factory.visualizations.push(vizSankey);

      function getFeedings(req, id) {
      for (var i = 0 ; i < req.body.feeding.length ; i++) {
      if (req.body.feeding[i].id === id) {
      return req.body.feeding[i].expressions;
      }
      }
      };

      function getData(req, id) {
      for (var i = 0 ; i < req.body.data.length ; i++) {
      if (req.body.data[i].id == id) {
      return req.body.data[i];
      }
      }
      };

      function distinct(arr) {
      var values = [];
      for (var i = 0 ; i < arr.length ; i++) {
      var value = arr[i];
      if (values.indexOf(value) === -1) {
      values.push(value);
      }
      }
      return values;
      };

      function logInfo(info){
      var currentdate = new Date();
      console.log(currentdate.toLocaleString() + " => " + info);
      };

      Author's profile photo Lionel Valadou
      Lionel Valadou

      When using the Google gauge extension, is it possible to print it to a PDF file?

       

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hi,

      It is up to the Custom Elements service to implement the bitmap generation required for printing and publishing.

      Regards,

      Pascal.

       

      Author's profile photo Former Member
      Former Member

      Hi Pascal,

      I didn't find the file (CustomElementsGoogleCharts.txt) attached to this article.

      can you send it to me at asy041atgmail

      Author's profile photo Milind Chavan
      Milind Chavan

      Hi All,

       

      I have few queries :

      1.We do not have internet access on our servers.So what needs to be mentioned for proxy “http://your_proxy:port

       

      2.How can we get below packages installed

      npm install phantom-proxy (necessary to use phantomJS from nodeJS)

      npm install xmldoc (necessary to parse the XML code)

      npm install body-parser (necessary to parse the commands from Web Intelligence)

      npm install pm2 –g (PM2 is a NodeJS process manager and is mandatory to manage your Custom Elements service)

       

      3.The above setting are for windows server can we work with Linux servers.

       

      Regards.

      Author's profile photo IT PI SAP BW team Team
      IT PI SAP BW team Team
      1. Enter its URL. This should be “http://your_service:port” (no backslash at the end of the URL!)

      this step is not working for me, can you please let me know what would be the service name and port that should be mentioned here

       

      Author's profile photo Pierrick HORPIN
      Pierrick HORPIN

      Hi all,

       

      is there anybody who have file CustomElementsGoogleCharts.txt ?

      because I copy/paste what Pascal posted here, but when I want to start by :

      pm2 start CustomElementsGoogleCharts.js

      the service failed immediately on error

      🙁

      Author's profile photo Utente Vimar Generico Utente Vimar Generico
      Utente Vimar Generico Utente Vimar Generico

      Hi Pierrick,

      I had the same problem with copy/paste.

      In C:\Users\<user_name>\.pm2\logs I found the CustomElementsGoogleCharts-error-0.log file with this error:

      So the issue is the quote (and also the double quote) symbol. Replace in the file the  with  and  with to solve the problem (you must replace opened/closed quotes and double quotes).

      Regards,

      Denis.

       

      Author's profile photo Pierrick HORPIN
      Pierrick HORPIN

      Hi Denis,

      thank you very much it is working now.

      I was able to add the custom service in the CMC.

      In WebI document I see as custom extension Google Gauge, Google Pie and Google Sankey , but only Google Pie is working :-/

       

      If you have any clue !

      Thanks a lot

      Author's profile photo Utente Vimar Generico Utente Vimar Generico
      Utente Vimar Generico Utente Vimar Generico

      Hi Pierrick,

      yeah not all charts work, also for me the same issue. I used this example to understand the base logic to pass the parameters from Webi to .js file, then I have created my custom chart writing a bit of javascript language and using also D3.js. In any case I will try to fine where is the problem….

      Ok, try this piece of code for Gauge (replace the current generate function with this):

       

      vizGauge.generate = function(req) {
      var w = req.body.width ? req.body.width : 800;
      var h = req.body.height ? req.body.height : 800;
      var dimData = getData(req, getFeedings(req, "category-axis")[0].dataId);
      var mesData = getData(req, getFeedings(req, "primary-values")[0].dataId);
      var values = [
      ['Label', 'Value']
      ];

      for (var i = 0; i < dimData.values.rawvalues.length; i++) {
      values.push([dimData.values.rawvalues[i], mesData.values.rawvalues[i]]);
      }
      var html =
      '<!DOCTYPE html>' + CR +
      '<html>' + CR +
      '<head>' + CR +
      ' <script src="https://www.gstatic.com/charts/loader.js"></script>' + CR +
      ' <script type="text/javascript">' + CR +
      'google.charts.load(\'current\', {\'packages\':[\'gauge\']});' + CR +
      ' google.charts.setOnLoadCallback(drawChart);' + CR +
      ' function drawChart() {' + CR +
      ' var data = google.visualization.arrayToDataTable(' + CR +
      ' ' + JSON.stringify(values) + CR +
      ' );' + CR +
      ' var options = {' + CR +
      ' width:' + w + ',' + CR +
      ' height:' + h + ',' + CR +
      ' greenFrom: 0,' + CR +
      ' greenTo: 20,' + CR +
      ' redFrom: 90,' + CR +
      ' redTo: 100,' + CR +
      ' yellowFrom:75,' + CR +
      ' yellowTo: 90,' + CR +
      ' minorTicks: 5,' + CR +
      ' animation : {' + CR +
      ' duration: 2500,' + CR +
      ' easing: "out"' + CR +
      ' }' + CR +
      ' };' + CR +
      ' var chart = new google.visualization.Gauge(document.getElementById(\'chart_div\'));' + CR +
      ' chart.draw(data, options);' + CR +
      ' }' + CR +
      ' </script>' + CR +
      '</head>' + CR +
      '<body>' + CR +
      ' <div id="chart_div" style="width: 400px; height: 120px;"></div>' + CR +
      '</body>' + CR +
      '</html>' + CR;
      return html;
      };

       

      Regards,

      Denis.

      Author's profile photo Claudio Sanft
      Claudio Sanft

      Hi, I'm trying to have it working. I went through all the steps described so far, but as soon as I try to start the service, this fails with the following log lines:

      Error: Cannot find module 'phantom-proxy'
      at Function.Module._resolveFilename (module.js:538:15)
      at Function.Module._load (module.js:468:25)

      ........

      I double checked for "phantom-proxy" install by running npm list, and what I get is:

      ...............

      │ ├── wrappy@1.0.1
      │ └── write-file-atomic@1.1.3
      ├─┬ phantom-proxy@0.1.792
      │ ├── colors@0.6.2
      │ ├─┬ event-stream@3.0.20

      ......

      Did anybody hit the head against this?

      Thank you so much in advance

      Claudio

      Author's profile photo Shilpi Srivastava
      Shilpi Srivastava

      Hi ,

       

      I am able to create Custom Element service and add that service in CMC. Its Test is successful. But when I assign data to any charts in custom element of BI 4.2 , it is not working.

       

      Please suggest.

       

      Thanks

      Shilpi

      Author's profile photo Ludéric MAURY
      Ludéric MAURY

      Hi,

      I have my CustomElementsGoogleCharts.js started on pm2.

      But when I try to create the service in the CMC : the test is not valid.

      I try this :

      http://BOServer/CustomElementsGoogleCharts:80

      And I try this :

      http://BOServer/CustomElementsGoogleCharts:80/Google Gauge     (for example…)

      I have this message :

      Le test a échoué.
      java.net.ConnectException: Connection refused: connect

       

      I tried another thing :

      http://BOServer:BOPort/CustomElementsGoogleCharts:80

      Le test a échoué.
      http://BOserver:BOPort/CustomElementsGoogleCharts:80/api/formats

       

      and when i try :

      http://BOServer:BOPort/CustomElementsGoogleCharts:80/Google Gauge

      I have only :

      Le test a échoué.

       

      What’s my problem ? Can you help me ?

      Thanks.

      Ludéric

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hi Luderic,

      It's difficult to answer your question without knowing where your Custom element service has been deployed. Are you sure about the path and the port number?

      In our example, the correct URL is: http://mymachine:8095 where "mymachine" is the machine where NodeJS has been installed and the service is running

      Thanks,

       

      Pascal.

      Author's profile photo Ludéric MAURY
      Ludéric MAURY

       

      Hi Pascal,

      First : Thanks for this blog !

      and thanks for answering me.

      Edit : Ok : the http://mymachine:Port works !!

      Thanks a lot.

      Ludéric

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hi Ludéric,

      The port to use in the URL is the one defined in the JS code. Using the port 80 is a bit risky since this is a generic HTTP port. Try with another port number, such as the one given in our example, above.

      Also, in the URL, there should not be any path, like "/CustomElementsGoogleCharts", if the JS code is at the root of the nodeJS folder.

      Thanks,

      Pascal.

       

      Author's profile photo WILLIAM MARCY
      WILLIAM MARCY

      Hi,

      Is it possible to download the file "CustomElementsGoogleCharts.txt" you mentionned ? Where I can find the content of this file ? Thank you.

      Regards.

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hi William,

       

      Sorry, this blogging application does not allow to attach files, as the old SCN application used to... 🙁

      I copied the text inline, above https://blogs.sap.com/2016/03/15/how-to-build-a-custom-elements-service-for-sap-web-intelligence-42/comment-page-1/#comment-357729

      That's the only workaround I could find. Sorry about that...

       

      Pascal.

      Author's profile photo Anandaraman Raghavan
      Anandaraman Raghavan

      This article was very helpful in getting me started down the route of custom elements. After a few run-ins with issues, was able to finally get a custom element visualization using leaflet and some custom json map layers.. works beautifully with input controls as well. A real game-changer for webi now that any visualization is within scope of webi. Kudos to you and the custom elements team.

      Author's profile photo Anandaraman Raghavan
      Anandaraman Raghavan

      I am getting some errors with getting the CMC to work with custom elements  using HTTPS.

      HTTP works though, just not HTTPS.

      Is HTTPS even supported for Custom elements calls ?

      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hi,

      Yes, HTTPS is supported.

      Thanks,

       

      Pascal.

      Author's profile photo Pratik Kumar Mankar
      Pratik Kumar Mankar

      Hi,

      Do you have any documentation that explains how to do that.

      Because, when I changed my HTTP Custom element service to be served over HTTPS, CMC is able to test it, but in WebI and Rich Client it fails with error:

      An exception occurred when retrieving the feeding metadata from the CustomElement third party service. (Error: RWI 00634) (Error: INF )
      Author's profile photo Pascal GAULIN
      Pascal GAULIN
      Blog Post Author

      Hi,

      No, there isn't any documentation on HTTPS, since there's nothing specific to do with the Custom Element service setting.

      The exception you get seems to indicate there is a communication issue between the Custom Element service and the Web Intelligence client.

      Regards,

      Pascal.

      Author's profile photo Pratik Kumar Mankar
      Pratik Kumar Mankar

      I can confirm that changing the service to HTTPS causes the problem. I too am getting the same error. Since it is already more than 2 years, do you plan to provide any updates on this?

      Author's profile photo Anandaraman Raghavan
      Anandaraman Raghavan

      Hi Pascal,

      Wanted to let you know that I integrated leaflet maps into webi and i can do visualizations like this that was not possible before. This one takes it up a notch for sure.

      Thanks for all your support on this forum and to the team for implementing custom elements feature.