// 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(65535); 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 = '' + CR + '' + CR + ' ' + CR + ' ' + CR + ' ' + CR + ' ' + CR + ' ' + CR + ' ' + CR; for (var j = 0 ; j < count1 ; j++) { html += ' ' + CR; } html += ' ' + CR; } html += '
' + CR + ' ' + CR + '' + 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 = '' + CR + '' + CR + ' ' + CR + ' ' + CR + ' ' + CR + ' ' + CR + ' ' + CR + '
' + CR + ' ' + CR + '' + 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 = '' + CR + '' + CR + ' ' + CR + ' ' + CR + ' ' + CR + '' + CR + ' ' + CR + '
' + CR + ' ' + CR + '' + 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); };