I read Dong Pan’s excellent blog “Migrate VizPacker-based Lumira Visualization Extensions to Web IDE” and wanted to check for myself that the migration (or porting) is that simple.

This blog provides a very detailed description of how I ported the Bullet chart visualization extension (from Matt Lloyd) from the Lumira VizPacker Utility to SAP Web IDE.

Spoiler – Yes, porting visualization extensions from Lumira VizPacker Utility to SAP Web IDE is that simple.

Some background information

SAP Web IDE version 1.10.2.

SAP Web IDE VizPacker Plugin version 1.1.2.

SAP Lumira Personal Edition version 1.25.1.

Starting Point / Pre-Requisites

2015-05 Vizpacker Utility Bullet.png

This blog expands a bit more than just the porting process. I’ll present the following:

  1. Several ways to know that the SAP Web IDE VizPacker Plugin is enabled.
  2. Enabling the SAP Web IDE VizPacker Plugin.
  3. Generating an empty visualization extension project that will hold the ported Bullet chart visualization extension.
  4. Porting from Lumira VizPacker Utility to SAP Web IDE.
  5. Deploying to Lumira.

Due to the size of the blog I split it to two parts.

This part will cover all of the porting from the SAP Lumira VizPacker Utility to SAP Web IDE.

The second part (Migrate Bullet Chart from Lumira VizPacker Utility to SAP Web IDE VizPacker Plugin – Part 2) covers the deployment from SAP Web IDE to SAP Lumira.

Let’s start …

Is the VizPacker Plugin Enabled?

Once logged into SAP Web IDE, you’ll see the VizPacker plugin pane icon at the right-hand toolbar and the VizPacker “Pack” icon at the top toolbar as appears in the image below. If you do not see them, it means that you need to enable the VizPacker plugin. Enabling the VizPacker plugin is described in the next section.

2015-05 Web IDE Plugin VizPacker Verify_.png

Enabling the SAP Web IDE VizPacker Plugin (also an additional way to verify that the VizPacker plugin is enabled)

In SAP Web IDE – Got to Tools > Preferences (see image below)

2015-05 Web IDE Plugin VizPacker Enable.png

In the screen that appears select “Optional Plugins” (see image below).

2015-05 Web IDE Plugin VizPacker Enable-2_.png

In the screen that appears – If the checkbox next to the VizPacker plugin is checked, it means that the VizPacker plugin is enabled.

If the checkbox is not checked, enabled the VizPacker plugin by checking it and then refresh the browser for the enablement to take place.

2015-05 Web IDE Plugin VizPacker Enable-3_.png

Generating an empty visualization extension project that will hold the ported Bullet chart visualization extension

In the Web IDE, select File > New > Project from Template (see image below).

2015-05 Web IDE Plugin VizPacker Bullet Port.png

Use the drop down to filter the VizPacker template:

2015-05 Web IDE Plugin VizPacker Bullet Port-2_.png

Select the “Visualization Extension” template and click “Next”.

2015-05 Web IDE Plugin VizPacker Bullet Port-3_.png

Fill-in the project name. This will be the project name that will appear in the SAP Web IDE workspace.

Click “Next”.

2015-05 Web IDE Plugin VizPacker Bullet Port-4_.png

Fill-in the Extension Name. This name will appear in SAP Lumira when selecting the visual extension.

Fill-in the Extension ID. When packing the extension from SAP Web IDE as well as deploying the extension to SAP Lumira, you’ll use this ID.

Fill-in the version number.

You can leave the rest of the fields with their defaults.

Click “Next”.

2015-05 Web IDE Plugin VizPacker Bullet Port-5_.png

Since the Bullet chart is based on SVG, make sure that the “Using SVG” is marked.

Uncheck the “Legend”.

Click “Next”.

2015-05 Web IDE Plugin VizPacker Bullet Port-6_.png

Click “Browse”.

2015-05 Web IDE Plugin VizPacker Bullet Port-7_.png

Select the “bullets.csv” that you used when developing the Bullet chart with SAP Lumira VizPacker Utility.

2015-05 Web IDE Plugin VizPacker Bullet Port-8_.png

The “bullet.csv” sample data appears on the screen.

Click “Next”.

2015-05 Web IDE Plugin VizPacker Bullet Port-9_.png

Create the Dimensions/Measures. Following are a few important tips:

  • Make sure that the order of them matches the order in the render.js in the Lumira VizPacker Utility. As you can see in the below image the first measure (measure (0)) is Actuals, etc.

2015-05 VizPacker Utility Measures Order_.png

  • Use the pencil marker in order to edit the Dimension/Measure name.
  • Click the “+” sign to add the relevant fields.
  • Click the “Add Set” to add an additional Dimension/Measure.

2015-05 Web IDE Plugin VizPacker Bullet Port-10_.png

Following is what you need to reach before clicking “Next”.

2015-05 Web IDE Plugin VizPacker Bullet Port-15_.png

Click “Finish”.

2015-05 Web IDE Plugin VizPacker Bullet Port-16_.png

The project is created in SAP Web IDE and the render.js file is opened in the editor pane.


2015-05 Web IDE Plugin VizPacker Bullet Port-18.png

Porting from Lumira VizPacker Utility to SAP Web IDE


Copy the Lumira VizPacker Utility render.js code from right under the “var render = …” to “// END: sample render code”.

Copy the segment that starts here:

2015-05 Vizpacker Utility Render Code_.png

… and ends here:

2015-05 Vizpacker Utility Render Code End_.png

Paste the code segment to the render.js file in the SAP Web IDE where the “//TODO: Add your own visualization implementation code below …” appears.

Use the SAP Web IDE Edit > Beautify” to make the code more readable.

2015-05 Web IDE Plugin VizPacker Bullet Port-20_.png

As you can see from the “*” in the tab header the file is not saved and in the preview pane to the right, the chart is not updated.

Click the save to “Save” the render.js file and then the “refresh” to refresh teh preview pane.

What a surprise … the chart is in the right direction, but something is wrong. All the bars are black.

2015-05 Web IDE Plugin VizPacker Bullet Port-21.png

The reason is that the CSS is not updated. We need to port is from the Lumira VizPacker Utility as well.

return to the Lumira VizPacker Utility and select the style.css tab.

Copy the content of the style.css tab.

2015-05 Vizpacker Utility CSS_.png

In the SAP Web IDE workspace expand the “style” foldel and open the “default.css” file (double click it).

Paste the code you copied from “style.css” to this file. It should completely replace all the code that was there.

Nothing changed? Do not forget to “Save” the file and click the preview pane “Refresh” button.

2015-05 Web IDE Plugin VizPacker Bullet Port-24.png

To summarize what we’ve done so far – The visual extension was ported to SAP Web IDE and is ready to be deployed from SAP Web IDE to SAP Lumira.

Continue reading … Migrate Bullet Chart from Lumira VizPacker Utility to SAP Web IDE VizPacker Plugin – Part 2

— Raz

To report this post you need to login first.

14 Comments

You must be Logged on to comment or reply to a post.

  1. Meng Wang

    Hi,

    This is really an excellent How-to guide. I benefit a lot from it.

    Just want to point out two places need special emphasis.

    1:If the checkbox is not checked, enabled the VizPacker plugin by checking it and then refresh the browser for the enablement to take place.


    Here the refresh is really important.

    2:when copying the render.js content, seems only the content in sap.viz.ext.bullet.zip works.

    Best Regards.

    Meng

    (0) 
  2. Juan Carlos Lazaro

    Raz, thanks a lot for the post. However, I have followed all the steps and I am not able to make it work.

    When I paste the code in the render.js, I get a lot errors saying: xxxx variable is already declared in the upper scope.

    Do you know what this could be?

    Thank you

    (0) 
      1. Juan Carlos Lazaro

        I must be doing something wrong then, because I don’t see anything when running the code.

        Could you provide me with the exact code that I have to paste in the render.js file?

        Thank you

        (0) 
        1. Pradeep Kumar

          define(“sap_viz_ext_bullet-src/js/render”, [], function(){

              /*

               * This function is a drawing function; you should put all your drawing logic in it.

               * it’s called in moduleFunc.prototype.render

               * @param {Object} data – proceessed dataset, check dataMapping.js

               * @param {Object} container – the target d3.selection element of plot area

               * @example

               *   container size:     this.width() or this.height()

               *   chart properties:   this.properties()

               *   dimensions info:    data.meta.dimensions()

               *   measures info:      data.meta.measures()

               */

              var render = function(data, container) {

            var width = this.width(),

            height = this.height(),

            colorPalette = this.colorPalette(),

            properties = this.properties(),

            dispatch = this.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);

            // START: sample render code for a column chart

            // Replace the code below with your own one to develop a new extension

            /**

            * To get the dimension set, you can use either name or index of the dimension set, for example

            *     var dset_xaxis = data.meta.dimensions(“X Axis”);    // by name

            *     var dset1 = data.meta.dimensions(0);                // by index

            *

            * To get the dimension or measure name, you should only use index of the dimension and avoid

            * hardcoded names.

            *     var dim1= dset_xaxis[0];        // the name of first dimension of dimension set “X Axis”

            *

            * The same rule also applies to get measures by using data.meta.measures()

            */

            var dset1 = data.meta.dimensions(0), // Titles

            dim_title = dset1[0],

            dim_subtitle = dset1[1];

            var mset1 = data.meta.measures(0), // Actuals

            ms_actual = mset1[0],

            ms_pace = mset1[1];

            var mset2 = data.meta.measures(1), // Ranges

            ms_target = mset2[0];

            var mset3 = data.meta.measures(2), // Target

            ms_range1 = mset3[0],

            ms_range2 = mset3[1],

            ms_range3 = mset3[2];

            // Chart design based on the recommendations of Stephen Few. Implementation

            // based on the work of Clint Ivy, Jamie Love, and Jason Davies.

            // http://projects.instantcognition.com/protovis/bulletchart/

            var d3_bullet = function() {

            var orient = “left”, // TODO top & bottom

            reverse = false,

            duration = 0,

            ranges = bulletRanges,

            markers = bulletMarkers,

            measures = bulletMeasures,

            width = 380,

            height = 30,

            tickFormat = null;

            // For each small multiple…

            function bullet(g) {

            g.each(function(d, i) {

            var rangez = ranges.call(this, d, i).slice().sort(d3.descending),

            markerz = markers.call(this, d, i).slice().sort(d3.descending),

            measurez = measures.call(this, d, i).slice().sort(d3.descending),

            g = d3.select(this);

            // MDL: Adjust ranges so we always have something to draw in the background.

            var maxMarker = (markerz.length > 0 ? markerz[0] : 0);

            var maxMeasure = (measurez.length > 0 ? measurez[0] : 0);

            var maxRanges = (rangez.length > 0 ? rangez[0] : 0);

            var maxValue = Math.max(maxMarker, maxMeasure, maxRanges);

            if (rangez.length == 0) {

            rangez.push(maxValue);

            } else if (rangez[0] < maxValue) {

            rangez[0] = maxValue;

            }

            // MDL: end

            // Compute the new x-scale.

            var x1 = d3.scale.linear()

            // MDL: domain is: 0..maxValue

            .domain([0, maxValue])

            // MDL: end

            .range(reverse ? [width, 0] : [0, width]);

            // Retrieve the old x-scale, if this is an update.

            var x0 = this.__chart__ || d3.scale.linear()

            .domain([0, Infinity])

            .range(x1.range());

            // Stash the new scale.

            this.__chart__ = x1;

            // Derive width-scales from the x-scales.

            var w0 = calcbulletWidth(x0),

            w1 = calcbulletWidth(x1);

            // Update the range rects.

            var range = g.selectAll(“rect.range”)

            .data(rangez);

            range.enter().append(“rect”)

            .attr(“class”, function(d, i) {

            return “range s” + i;

            })

            .attr(“width”, w0)

            .attr(“height”, height)

            .attr(“x”, reverse ? x0 : 0)

            .transition()

            .duration(duration)

            .attr(“width”, w1)

            .attr(“x”, reverse ? x1 : 0);

            range.transition()

            .duration(duration)

            .attr(“x”, reverse ? x1 : 0)

            .attr(“width”, w1)

            .attr(“height”, height);

            // Update the measure rects.

            var measure = g.selectAll(“rect.measure”)

            .data(measurez);

            measure.enter().append(“rect”)

            // MDL: Use the darker measure color when there is only one measure.

            .attr(“class”, function(d, i) {

            var count = measurez.length;

            if (count == 1) {

            // Only one measure so use darker color.

            return “measure s” + 1;

            }

            return “measure s” + i;

            })

            // MDL: end

            .attr(“width”, w0)

            .attr(“height”, height / 3)

            .attr(“x”, reverse ? x0 : 0)

            .attr(“y”, height / 3)

            .transition()

            .duration(duration)

            .attr(“width”, w1)

            .attr(“x”, reverse ? x1 : 0);

            measure.transition()

            .duration(duration)

            .attr(“width”, w1)

            .attr(“height”, height / 3)

            .attr(“x”, reverse ? x1 : 0)

            .attr(“y”, height / 3);

            // Update the marker lines.

            var marker = g.selectAll(“line.marker”)

            .data(markerz);

            marker.enter().append(“line”)

            .attr(“class”, “marker”)

            .attr(“x1”, x0)

            .attr(“x2”, x0)

            .attr(“y1”, height / 6)

            .attr(“y2”, height * 5 / 6)

            .transition()

            .duration(duration)

            .attr(“x1”, x1)

            .attr(“x2”, x1);

            marker.transition()

            .duration(duration)

            .attr(“x1”, x1)

            .attr(“x2”, x1)

            .attr(“y1”, height / 6)

            .attr(“y2”, height * 5 / 6);

            // Compute the tick format.

            var format = tickFormat || x1.tickFormat(8);

            // Update the tick groups.

            var tick = g.selectAll(“g.tick”)

            .data(x1.ticks(8), function(d) {

            return this.textContent || format(d);

            });

            // Initialize the ticks with the old scale, x0.

            var tickEnter = tick.enter().append(“g”)

            .attr(“class”, “tick”)

            .attr(“transform”, bulletTranslate(x0))

            .style(“opacity”, 1e-6);

            tickEnter.append(“line”)

            .attr(“y1”, height)

            .attr(“y2”, height * 7 / 6);

            tickEnter.append(“text”)

            .attr(“text-anchor”, “middle”)

            .attr(“dy”, “1em”)

            .attr(“y”, height * 7 / 6)

            .text(format);

            // Transition the entering ticks to the new scale, x1.

            tickEnter.transition()

            .duration(duration)

            .attr(“transform”, bulletTranslate(x1))

            .style(“opacity”, 1);

            // Transition the updating ticks to the new scale, x1.

            var tickUpdate = tick.transition()

            .duration(duration)

            .attr(“transform”, bulletTranslate(x1))

            .style(“opacity”, 1);

            tickUpdate.select(“line”)

            .attr(“y1”, height)

            .attr(“y2”, height * 7 / 6);

            tickUpdate.select(“text”)

            .attr(“y”, height * 7 / 6);

            // Transition the exiting ticks to the new scale, x1.

            tick.exit().transition()

            .duration(duration)

            .attr(“transform”, bulletTranslate(x1))

            .style(“opacity”, 1e-6)

            .remove();

            });

            d3.timer.flush();

            }

            // left, right, top, bottom

            bullet.orient = function(x) {

            if (!arguments.length) return orient;

            orient = x;

            reverse = orient == “right” || orient == “bottom”;

            return bullet;

            };

            // ranges (bad, satisfactory, good)

            bullet.ranges = function(x) {

            if (!arguments.length) return ranges;

            ranges = x;

            return bullet;

            };

            // markers (previous, goal)

            bullet.markers = function(x) {

            if (!arguments.length) return markers;

            markers = x;

            return bullet;

            };

            // measures (actual, forecast)

            bullet.measures = function(x) {

            if (!arguments.length) return measures;

            measures = x;

            return bullet;

            };

            bullet.width = function(x) {

            if (!arguments.length) return width;

            width = x;

            return bullet;

            };

            bullet.height = function(x) {

            if (!arguments.length) return height;

            height = x;

            return bullet;

            };

            bullet.tickFormat = function(x) {

            if (!arguments.length) return tickFormat;

            tickFormat = x;

            return bullet;

            };

            bullet.duration = function(x) {

            if (!arguments.length) return duration;

            duration = x;

            return bullet;

            };

            return bullet;

            };

            function bulletRanges(d) {

            // MDL: Return an empty array if the ranges property is missing.

            // MDL: Renamed ranges to “Ranges”

            return (mset2 ? [d[ms_range1], d[ms_range2], d[ms_range3]] : []);

            // MDL: end

            }

            function bulletMarkers(d) {

            // MDL: Return an empty array if the markers property is missing.

            // MDL: Renamed markers to “Target”

            return (mset3 ? [d[ms_target]] : []);

            // MDL: end

            }

            function bulletMeasures(d) {

            // MDL: Return an empty array if the measures property is missing.

            // MDL: Renamed measures to “Actuals”

            return (mset1 ? [d[ms_actual], d[ms_pace]] : []);

            // MDL: end

            }

            function bulletTranslate(x) {

            return function(d) {

            return “translate(” + x(d) + “,0)”;

            };

            }

            function calcbulletWidth(x) {

            var x0 = x(0);

            return function(d) {

            return Math.abs(x(d) – x0);

            };

            }

            // MDL: Added bullet width to make it easier to adapt to the page width.

            // MDL: Added bullet height so can calculate the y-position for each bullet SVG.

            var bulletWidth = width;

            var bulletHeight = 52;

            var margin = {

            top: 5,

            right: 40,

            bottom: 20,

            left: 120

            },

            width = bulletWidth – margin.left – margin.right,

            height = bulletHeight – margin.top – margin.bottom;

            // MDL: end

            var chart = d3_bullet()

            .width(width)

            .height(height);

            // MDL: JSON is already embedded.

            // d3.json(“bullets.json”, function(error, data) {

            // MDL: end

            var svg = vis.selectAll(“svg”)

            // MDL: Renamed data to fdata.

            .data(data)

            // MDL: end

            .enter().append(“svg”)

            .attr(“class”, “bullet”)

            .attr(“width”, width + margin.left + margin.right)

            .attr(“height”, height + margin.top + margin.bottom)

            // MDL: Y position

            .attr(“y”, function(d, idx) {

            return bulletHeight * idx;

            })

            // MDL: end

            .append(“g”)

            .attr(“transform”, “translate(” + margin.left + “,” + margin.top + “)”)

            .call(chart);

            var title = svg.append(“g”)

            .style(“text-anchor”, “end”)

            .attr(“transform”, “translate(-6,” + height / 2 + “)”);

            title.append(“text”)

            .attr(“class”, “title”)

            // MDL: title is the first item in titles

            .text(function(d) {

            return (dset1 && dset1.length > 0 ? d[dim_title] : “”);

            });

            // MDL: end

            title.append(“text”)

            .attr(“class”, “subtitle”)

            .attr(“dy”, “1em”)

            // MDL: title is the second item in titles

            .text(function(d) {

            return (dset1 && dset1.length > 1 ? d[dim_subtitle] : “”);

            });

            // MDL: end

            // MDL: Remove randomize as the data will be passed in instead

            //  d3.selectAll(“button”).on(“click”, function() {

            //    svg.datum(randomize).call(chart.duration(1000)); // TODO automatic transition

            //  });

            // MDL: end

            // MDL: JSON is already embedded.

            //});

            // MDL: end

            // MDL: Remove randomize as the data will be passed in instead.

            //function randomize(d) {

            //  if (!d.randomizer) d.randomizer = randomizer(d);

            //  // MDL: Renamed ranges to “Ranges”, markers to “Target” and measures to “Actuals”

            //  d.Ranges = d.Ranges.map(d.randomizer);

            //  d.Target = d.Target.map(d.randomizer);

            //  d.Actuals = d.Actuals.map(d.randomizer);

            //  // MDL:end

            //  return d;

            //}

            // MDL: end

            // MDL: Remove randomize as the data will be passed in instead.

            //function randomizer(d) {

            //  // MDL: Renamed ranges to “Ranges”

            //  var k = d3.max(d.Ranges) * .2;

            //  // MDL: end

            //  return function(d) {

            //    return Math.max(0, d + k * (Math.random() – .5));

            //  };

            //}

            // MDL: end

            // END: sample render code

            };

            return render;

          });

          This is bullet chart render code and can u say what’s the error its throwing so that i can say some solution

          (0) 
          1. Juan Carlos Lazaro

            Thanks for the link Raz.

            I have copied the code I found there and also tried with Pradeep’s code, but none of them worked for me.

            I get errors like variables used before being define, variables aleready defined and so on. When running the code, all I see is a blank page.

            Not sure what can be wrong, the version of a specific component maybe?

            (0) 

Leave a Reply