In this post I will describe how you can easily insert any custom chart control in a SAPUI5 Mobile app.
Image may be NSFW.
Clik here to view.
In this particular example I am going to use a SplitApp and D3 as charting library however the steps apply also to other libraries and controls. Please find the comple source code on GitHub.
Let´s start by creating a simple SplitApp with two detail pages:
var oSplitApp = new sap.m.SplitApp("mySplitApp") .addMasterPage(new sap.m.Page("master2", { title: "Master2", navButtonPress: function () { oSplitApp.backMaster(); }, content: [new sap.m.List({ mode: "SingleSelectMaster", select: function (oEv) { if (oEv.getParameter("listItem").getId() == "listDetail2") { oSplitApp.toDetail("detail2"); } else { oSplitApp.toDetail("detail1"); } }, items: [new sap.m.StandardListItem("listDetail2", { title: "To Detail 2" }), new sap.m.StandardListItem("listDetail", { title: "To Detail 1" })] })] })) .addDetailPage(new sap.m.Page("detail1", { title: "Detail 1" })) .addDetailPage(new sap.m.Page("detail2", { title: "Detail 2", })).placeAt("content");
Now we want to place a chart control on both detail pages. The chart will be drawn by the function shown in the snippet below that is taken from this D3 example. That function is passed the DOM element that will contain the chart.
My first idea was to just use jQuerys document.ready function and just add the chart in here. However there is one big issue with that approach as only the first detail page is available in the DOM once the document.ready function gets called. So DetailPage1 will contain the chart, DetailPage2 won´t. See it in action here.
$(document).ready(function () { drawChart(jQuery.sap.byId("detail1")[0]); drawChart(jQuery.sap.byId("detail2")[0]); });
So the problem here is basically that SAPUI5 is clever enough to lazy load components and only insert them into the DOM once they are actually needed.
In order to draw the chart in a SAPUI5 conforming way we will just create a simple custom control and make use of its onAfterRendering function. So the final SplitApp looks like below (see it in action here) .
sap.ui.core.Control.extend("chris.Chart", { renderer: function (oRm, oControl) { oRm.write("<div"); oRm.writeControlData(oControl); oRm.write("</div>"); }, onAfterRendering: function (eeee) { drawChart(eeee.srcControl.$()[0]); } }); var oSplitApp = new sap.m.SplitApp("mySplitApp") .addMasterPage(new sap.m.Page("master", { title: "Master", navButtonPress: function () { oSplitApp.backMaster(); }, content: [new sap.m.List({ mode: "SingleSelectMaster", select: function (oEv) { if (oEv.getParameter("listItem").getId() == "listDetail2") { oSplitApp.toDetail("detail2"); } else { oSplitApp.toDetail("detail1"); } }, items: [new sap.m.StandardListItem("listDetail2", { title: "To Detail 2" }), new sap.m.StandardListItem("listDetail", { title: "To Detail 1" })] })] })) .addDetailPage(new sap.m.Page("detail1", { title: "Detail 1", content: [new chris.Chart()] })) .addDetailPage(new sap.m.Page("detail2", { title: "Detail 2", content: [new chris.Chart()] })).placeAt("content");
DrawChart Function:
function drawChart(element) { var margin = { top: 20, right: 20, bottom: 70, left: 40 }, width = 600 - margin.left - margin.right, height = 300 - margin.top - margin.bottom; // Parse the date / time var parseDate = d3.time.format("%Y-%m").parse; var x = d3.scale.ordinal().rangeRoundBands([0, width], .05); var y = d3.scale.linear().range([height, 0]); var xAxis = d3.svg.axis() .scale(x) .orient("bottom") .tickFormat(d3.time.format("%Y-%m")); var yAxis = d3.svg.axis() .scale(y) .orient("left") .ticks(10); var svg = d3 .select(element) //.select("#detail1") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var data = [ { date: "2013-01", value: "53" }, { date: "2013-02", value: "153" }, ]; data.forEach(function (d) { d.date = parseDate(d.date); d.value = +d.value; }); x.domain(data.map(function (d) { return d.date; })); y.domain([0, d3.max(data, function (d) { return d.value; })]); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis) .selectAll("text") .style("text-anchor", "end") .attr("dx", "-.8em") .attr("dy", "-.55em") .attr("transform", "rotate(-90)"); svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text("Value ($)"); svg.selectAll("bar") .data(data) .enter().append("rect") .style("fill", "steelblue") .attr("x", function (d) { return x(d.date); }) .attr("width", x.rangeBand()) .attr("y", function (d) { return y(d.value); }) .attr("height", function (d) { return height - y(d.value); }); }