How to add a D3 extension for SAP Lumira
Last time out we created the Hello World extension for SAP Lumira. If you have not created an SAP Lumira extension before I’d suggest reading that post first.
This time I will walk you through a more real world example showing how to bring in a D3 chart as extension for SAP Lumira, I will be using the D3 Bullet Chart as an example but the steps should be similar for other charts.
Updated for SAP Lumira 1.20 as there were improvements made for creating SAP Lumira extensions since this was first written.
You will need to be comfortable with JavaScript and D3 to follow this guide as this is a more advanced example.
Step by step guide
What applications do you need?
You need to install:
- SAP Lumira 1.20 or later (which includes vizPacker), you can get your free Personal Edition here
- Google Chrome as vizPacker only works with Google Chrome
Prepare the D3 code for re-use
To save time I have already made these changes to a reusable local copy of the D3 Bullet Chart code (look for the // MDL comments), see the resources section (below), you can find the modified files on GitHub.
The Bullet Chart uses a simple JSON data structure, think of it being a JSON version of a CSV file.
[ {“title”:”Revenue”,”subtitle”:”US$, in thousands”,”ranges”:[150,225,300],”measures”:[220,270],”markers”:[250]}, {“title”:”Profit”,”subtitle”:”%”,”ranges”:[20,25,30],”measures”:[21,23],”markers”:[26]}, {“title”:”Order Size”,”subtitle”:”US$, average”,”ranges”:[350,500,600],”measures”:[100,320],”markers”:[550]}, {“title”:”New Customers”,”subtitle”:”count”,”ranges”:[1400,2000,2500],”measures”:[1000,1650],”markers”:[2100]}, {“title”:”Satisfaction”,”subtitle”:”out of 5″,”ranges”:[3.5,4.25,5],”measures”:[3.2,4.7],”markers”:[4.4]} ] |
Originally vizPacker used this JSON format which suited the D3 Bullet Chart but not many other D3 charts. The majority of D3 charts use D3.csv data format, so vizPacker now uses the D3.csv data format as well to make things easier.
We will see later on in this blog how to convert/map from the D3.csv format to the JSON format for use with the Bullet Chart.
At a high level here are the changes I made:
- Save a local copy of the bullet chart index.html, bullet.js and bullets.json files from: Bullet Charts
- Update index.html:
- We are going to mimic the vizPacker behavior
- Add an SVG tag as the container for the extension into the page body and set the id to vis
<svg id=”vis”></svg>
<script src=”http://d3js.org/d3.v3.min.js“></script>
- Add the vis variable and assign it from the vis SVG tag in the top of the script – this is the variable the vizPacker render code uses:
<script>
var vis = d3.select(“#vis”);
- Hard code the test JSON data in a variable called fdata – this is what the vizPacker render function does by default
- See the following optional step
- Optional step – change the fdata JSON property names to make them more user friendly when they are shown in Lumira:
- Combine title and subtitle into one property called Titles
- Change measures to Actuals
- Change markers to Target
- Change ranges to Ranges
- Update the bullet chart code to use the user friendly property names:
var vis = d3.select(“#vis”);
// MDL: embed JSON so we can test with Google Chrome locally.
// MDL: renamed “data” to “fdata”.
// MDL: Combined “title” and “subtitle” as “Titles” array.
// MDL: Renamed ranges to “Ranges”.
// MDL: Renamed measures to “Actuals”.
// MDL: Renamed markers to “Target”.
var fdata = [
{“Titles”:[“Revenue”,”US$, in thousands”],”Ranges”:[150,225,300],”Actuals”:[220,270],”Target”:[250]},
{“Titles”:[“Profit”,”%”],”Ranges”:[20,25,30],”Actuals”:[21,23],”Target”:[26]},
{“Titles”:[“Order Size”,”US$, average”],”Ranges”:[350,500,600],”Actuals”:[100,320],”Target”:[550]},
{“Titles”:[“New Customers”,”count”],”Ranges”:[1400,2000,2500],”Actuals”:[1000,1650],”Target”:[2100]},
{“Titles”:[“Satisfaction”,”out of 5″],”Ranges”:[3.5,4.25,5],”Actuals”:[3.2,4.7],”Target”:[4.4]}
];
// MDL: end
- Add a y position attribute that positions each bullet chart in the vis SVG tag
- Remove the randomize button and code as that is not needed in Lumira
- Update bullet.js:
- Test the updated index.html file in Google Chrome:
- To make sure it runs and visually looks the same as the original did
Create the D3 extension
Follow these steps to create and test your D3 Bullet Chart extension inside the vizPacker:
- Using Google Chrome
Open: <installdir>\SAP Lumira\Desktop\utilities\vizPacker\vizPacker.html - The LAYOUT DESIGN tab is where you define the ID, Name and Layout of your extension as well as edit the code
- Using the LAYOUT DESIGN tab, set the ID and name of your extension:
- Click the outer gray box to display the chart visualization properties:
- About IDs:
- The ID naming convention is similar to making unique Java class names
- You use the reverse of your company web address and then add the name of the extension
For example: com.sap.sample.d3.bulletchart - IMPORTANT note for SAP Lumira 1.15:
- The ID must be all lowercase otherwise it will not work inside SAP Lumira
- Settings:
- ID: com.sap.sample.d3.bulletchart
- Name: Bullet Chart
- Note: As you close the properties window or move between fields the vizPacker updates the ID and Name used in the code editor
- Click the outer gray box to display the chart visualization properties:
- Using the LAYOUT DESIGN tab, set the ID and name of your extension plot area:
- Click the inner gray box to display the chart plot are properties:
- About IDs:
- Follow the same rules as for the extension ID, but append module to the end
- Settings:
- ID: com.sap.sample.d3.bulletchartmodule
- Name: Bullet Chart Module
- Note: As you close the properties window or move between fields the vizPacker updates the ID and Name used in the code editor
- Click the inner gray box to display the chart plot are properties:
- Remove the legend as the Bullet Chart does not need a legend
- Import test data for the Bullet Chart:
- Create a CSV version of the JSON Bullet Chart data, see the resources section (below) for the CSV file I made for the Bullet Chart
- Switch to the DATA MODEL tab
- Click to select and upload the CSV file:
- After you have chosen your CSV file.
- Click OK when you see a JavaScript alert as you import the CSV file:
- The data model now shows the imported CSV data:
- Note: There are 8 dimensions (one for each column) and 0 measures
- Next we will create the measure and dimension groups (another names for arrays, as they will be used in Lumira) and map test data columns to them.
- Columns Title and Subtile ===> Titles
- Columns Target ===> Target
- Columns Range 1, Range 2 and Range 3 ===> Ranges
- Measure and dimension groups:
- Measure groups (arrays) are for the numeric values in Lumira
- So for the Bullet Chart these are the Actuals, Target and Ranges
- Dimension groups (arrays) are for everything else:
- So for the Bullet Chart these are the Titles
- Measure groups (arrays) are for the numeric values in Lumira
- Create the measure and dimension groups to match the JSON format that we need:
- Create Titles as the first dimension group:
- Click the drop down arrow next to Title
- Then the right arrow (if expanded will show a drop down arrow as below) for the dimension
- Double click the name X Axis (the name the first radio button) and change the name to Titles, then press ENTER
- And select the first radio button, so the Title column is now mapped into the dimension group called Titles
- Map the Subtitle column to the Titles dimension group as well
- Create Actuals as the first measure group:
- Click the drop down arrow next to Actual
- Then the right arrow (if expanded will show a drop down arrow as below) for the measure
- Double click the name Y Axis (the name of the first measure radio button) and change the name to Actuals, then press ENTER
- And select the first radio button, so the Actual column is now mapped into the measure group called Actuals
- Map Pace into the Actuals measure group
- Map Target into measure group 2 and call it Target
- Map Range 1, Range 2 and Range 3 into measure group 3 and call it Ranges
- Click to Apply the Data Model:
The data model, structure and test data are now applied in the vizPacker
- Create Titles as the first dimension group:
- Prepare the render function:
- The only lines we really need from the template render function are for creating the vis container
- So update the render function so it looks like this:
var render = function(data, container, width, height, colorPalette, properties, dispatch) {
//prepare canvas with width and height of container
container.selectAll(‘svg’).remove();
var vis = container.append(‘svg’).attr(‘width’, width).attr(‘height’, height)
.append(‘g’).attr(‘class’, ‘vis’).attr(‘width’, width).attr(‘height’, height);};
- Add the code from bullets.js:
- The code is wrapped in an anonymous function that we do not need
- We do not want that anonymous function, so copy all the code except the first and last line in the file (note they have a red strike through them in these images):
Copy down to: - And paste the code you just copied into the bottom of the vizPcker just before the return render call:
- Change the bullet chart from a global namespace (as that is bad practice for extensions – d3.bullet) to a local variable (var d3_bullet):
- Look for the line that says:
d3.bullet = function() { - And change it to a local variable like this:
var d3_bullet = function() { - We can now refer to d3_bullet when we need to create the bullet chart later on.
- Look for the line that says:
- Add in the code from index.html to create the bullet chart:
- Copy all of the script code after the fdata line because we already have the fdata generated in our render function:
Copy down to: - And paste the code you just copied into the bottom of the render function:
- Change from the d3.bullet call to d3_bullet (the local variable):
- Look for the line that says:
var chart = d3.bullet - And change the dot to an underscore like this:
var chart = d3_bullet
- Look for the line that says:
- Copy all of the script code after the fdata line because we already have the fdata generated in our render function:
- Remember I mentioned that the Bullet Chart expects data as JSON, that means we need to convert the data from D3.csv.
If your D3 extension uses the D3.csv data format there is no need to do this conversion.
This is how I chose to do this for this example:- Copy the code from VizPacker__d3csv_to_JSON.txt
- Paste the code in just below the vis container and above the bullet chart code
- Here are the first few lines of that code to convert between D3.csv and JSON formats:
var fdata = [];
var meta = data.meta;
var titleFields = meta.dimensions(‘Titles’);
var actualFields = meta.measures(‘Actuals’);
var targetFields = meta.measures(‘Target’);
var rangeFields = meta.measures(‘Ranges’); - Let’s break this code down as it is specific to the vizPacker and how you get the names of the CSV columns that are used in the measure and dimension groups (in vizPacker and later in Lumira as well) that we defined earlier in the DATA MODEL tab:
- var fdata = [[;
This is the JSON data variable that was used in the original vizPacker, this is where we will store the converted JSON data. - var meta = data.meta;
Get access to the metadata that is passed in by vizPacker (and later in Lumira as well). - var titleFields = meta.dimensions(‘Titles’);
Get the list of column names from the CSV file that are mapped into the Titles dimension group (this matches to the Titles dimension group we mapped Title and Subtitle to earlier). - var actualFields = meta.measures(‘Actuals’);
Get the list of column names from the CSV file that are mapped to the Actuals measure group. - Then we do the same for Target and Ranges measure groups.
- That is most of what we need to do because we now have the bullet chart code and rendering code in.
What we are missing are the CSS styles, we will do that soon. - Change the Bullet Chart so it adjusts to the width variable passed in to the render function:
- Look for the line:
var bulletWidth = 960; - And change it to:
var bulletWidth = width;
- Look for the line:
- Add in the CSS styles from index.html to style the bullet chart:
- Switch to the style tab, go to >> and then select style.css:
- Copy the CSS bullet styles:
.bullet { font: 10px sans-serif; }
.bullet .marker { stroke: #000; stroke-width: 2px; }
.bullet .tick line { stroke: #666; stroke-width: .5px; }
.bullet .range.s0 { fill: #eee; }
.bullet .range.s1 { fill: #ddd; }
.bullet .range.s2 { fill: #ccc; }
.bullet .measure.s0 { fill: lightsteelblue; }
.bullet .measure.s1 { fill: steelblue; }
.bullet .title { font-size: 14px; font-weight: bold; }
.bullet .subtitle { fill: #999; } - Delete all of existing style code from style.css
- Paste in the CSS bullet chart styles
- Alter the first line of CSS (the added bit is in bold) to stop Lumira from overriding the font style:
.bullet { font: 10px sans-serif; !important; }
- Switch to the style tab, go to >> and then select style.css:
- Validate your code inside the vizPacker:
- Click on Run Code
- If you get a JavaScript error message then there is something wrong with your code:
- Close the JavaScript error message
- Right-click on code editor and select Inspect Element to enable the Google Chrome developer area
- Then go to the Console tab and look at the error messages
- Fix your code in the vizPacker code editor and then click Run Code again
- Click on Run Code
- Once your code is validated, preview your extension inside the vizPacker:
- Next you are ready to pack(age) and test inside SAP Lumira
Pack and install your extension into SAP Lumira
- Click Pack
- The extension will be packed into a ZIP file. Click the ZIP file link:
- Google Chrome will download the packed ZIP file
- Select the downloaded ZIP file and select Show in folder
- Extract out the the packed extension and install it into SAP Lumira:
- Extract out all the files and folders from the ZIP file
- Copy the bundles/com folder to <installdir>\SAP Lumira\Desktop\extensions\bundles
For example: <installdir>\SAP Lumira\Desktop\extensions\bundles\com If you already have a com folder you will be asked to merge. That is Okay because like we did for Hello World you should have created a unique ID for this extension to the sub-folders will not clash with another extension.
- Next we can test the extension inside SAP Lumira
Test your D3 chart inside SAP Lumira
- Start SAP Lumira
- Import the bullet chart test CSV file, see the resources section (below)
- Select the Bullet Chart extension:
- Use these settings with the chart:
Titles Title, Subtitle Actuals Actual, Pace Target Target Ranges Range 1, Range 2, Range 3 - The Bullet Chart should now look like this when running in Lumira:
Note: If this does not work, you will need to refer to the SAP Lumira SDK Getting Started Guide on how to debug within SAP Lumira
- That’s it, you have created, packaged and tested your first D3 extension using the vizPacker!
- Congratulations and happy coding…
More information
Have a look at the:
Resources
You can find all of the Bullet Chart files on GitHub.
Thanks Matt for the wonderful tutorial. This was really helpful. My CSS/JS/HTML5 are mediocre. The code snippets were very helpful. Having said that, this was a great start for me to get excited and learn some advanced scripting and exploit the SDK as much as possible. 😀
Can't wait to see what you create Kalyan!
Hi Matt,
Thank you for this blog, I did follow the Hello World blog but didn't make any progress.
I needed the examples in this one to progress and the details you provided.
Cheers,
Robert
Matt,
Good to see a great blog post from you! Great stuff. Hope to bump into you again at a future SAP event. I'll definitely be playing with this component (and tweaking it, etc.)
Matt, thanks for all these tutorials about Lumira + vizPacker!
Do you know if it is possible to feed these graphics (bullet, exoplanets, etc) with another source (eg: SAP BEx) ?
Cheers,
Pablo
Hi Pablo,
Lumira handles the data access, so if you have BI universes you can look via that route for now, we hope to have more to say about this later in the year.
Regards
Matt
Hi,
Just to complete on Matt's explanation, viz extensions and data acquisition are two separate topics. In other words, any data that is presently accessible to SAP Lumira should be able to feed the viz extensions.
Best regards
Antoine
Hey Matt, you rock! Many thanks for taking the time to draft your series on how to extend SAP Lumira. I have been furtling in this space for a little while and was finally able to crack it using your examples 😎 .
I've even created my own blog on how to build a Gauge extension for SAP Lumira:
SAP Lumira Extension - Taking D3 Gauges to the next level
Would appreciate your comments.
Keep up the good work.
Regards,
Andrew
Very nice post, it was useful for us!!!
We were working on another kind of charts and got stucked.
We're trying to create a new Lumira component for using Chords charts.
When we give the lumira component a complete matrix (that means all sources are in the destination field and viceversa) it works fine and the results are as expected, but when trying to draw the chart with a datasource that is not complete (that means not all the sources are in the destintions field also) the chart gives errors or even the visualization is not as expected.
We need to search into the input data which combinations are not available and if there is any add them to the dataset with a 0 value.
Does anyone has an idea of how to proceeed to ccomplish this?
We tried using the MAP function but it returns only the actual data and loose
the source-destination relationships.
Thanks in advance
Regards
Hi Matt,
Thanks for your posting
I am a fresh man to VizPacker
I saw in your VizPacker, you only have to tab: JS.File, HTML
But when I open my VizPacker, I saw render.js/datamapping.js, etc
Would you kindly let me know how to display the VizPacker same as you (Only two tab in the header for code editor)
Best regards
Alex yang
Hi, Matt, I'm very greatful for this post, i really need it, but i have a trouble, check it:
i have the extencion in Vizpacker, but when i put the file of my extencion and star Lumira, the extencion isnt there, i try out whit other archives, for example whit examples extencion and i dont have the same problem, what can i do, any suggestion?
rgrds
Hi Luis,
One of the possibility is that the code isn't complete and there is an error, that requires debugging. you can enable debugging as highlighted in our developer guide and check in the console log for a potential error.
The developer guide is available here http://help.sap.com/businessobject/product_guides/vi01/en/lum_120_vp_en.pdf
Please look for the section - 4. Debugging an extension in SAP Lumira.
Thanks
Raghu
I updated this for SAP Lumira 1.20 as there were improvements made for creating SAP Lumira extensions since this was first written (and that is also why vizPacker looked a bit different for some of you).
Hi Matt,
So for any D3 visualization we will need to do the same thing - modify the source code and place the snippets in the respective sections as you described here? Or these steps are just Bullet Chart specific?
Thanks
-Prab
Hi Prab,
These steps are for the Bullet Chart, although quite a few of the steps will be the same or similar for other D3 charts.
You may also want to consider doing this with the Web IDE version of VizPacker as that has additional capabilities like debugging and being able to create additional files, you can read more about that here with Dong: SAP Lumira Visualization Extension - Hello World from SAP Web IDE and here: Migrate VizPacker-based Lumira Visualization Extensions to Web IDE