This track race like any type of gamification visualization tries to create some competition between participants.
Disclaimer: This is not a finished report and should not be considered as such. This is just a proof of concept to show what the reporting engine of CRM On Demand is capable of.
HTML5 Canvas and Javascript are not a required skill for normal CRM On Demand report generation at all! But using these techniques enable a wide range of unusual data visualizations.
Here is all you need to make this track race report in CRM On Demand. But before you try this out, you might want to learn more about how narrative views actually work by checking out this video recording
Result Example
Concepts
- The base report is one where activities per salesrep are counted, but anything could be counted
- The activity count is capped at 10
- The colors and runner image can be changed there too
Criteria Fields
From the ‘Reporting’ subject areas, I used: ‘Activities’
Field Name | Calculated Field | Formula |
---|---|---|
Salesrep | Account.Owner | |
# Activities | Yes | CASE WHEN “Activity Metrics”.”# of Activities” > 10 THEN 10 ELSE “Activity Metrics”.”# of Activities” END |
Rep # | Yes | RSUM(COUNT(DISTINCT 1)) |
# Reps | Yes | SUM(COUNT(DISTINCT 1)) |
Criteria Filters
- Any filter to count activities in a certain timeframe
- Any filter that limits the count of activities to only a specific type of activities, or for opportunities with a specific product, …
Data Formatting
Format the date till the table view looks something like the example below.
Hide the table once the narrative view is ready.
Narrative View Definition
Prefix
<canvas id="myCanvas" width="800" height="400"></canvas> <script> var canvas = document.getElementById('myCanvas'); var context = canvas.getContext('2d'); var params = []; params['raceGoal'] = 10; params['perspectiveX'] = 200; params['canvasWidth'] = 800; params['canvasBottomY'] = 350; params['canvasTopY'] = 50; context.font = '12pt Calibri'; context.fillStyle = 'black'; context.fillText('Let us race to get to '+ params['raceGoal'] + ' customer meetings this month', params['canvasWidth'] / 2, params['canvasTopY'] / 2); function getDetails(repname, activities, repnumber, numberreps) { var details = []; details['repname'] = repname + '(' + activities + ')'; if (activities <= params['raceGoal']) { details['activities'] = activities; } else { details['activities'] = params['raceGoal']; } details['repnumber'] = repnumber; details['numberreps'] = numberreps; details['laneWidth'] = params['canvasWidth'] / numberreps; details['maxImageH'] = 200; details['maxImageW'] = Math.round(200/597*354) details['laneEndX'] = (details['laneWidth'] / 2) + (details['laneWidth'] * (details['repnumber'] -1)); details['laneEndY'] = params['canvasBottomY']; details['laneX'] = details['laneWidth'] * (details['repnumber'] -1); details['laneY'] = params['canvasBottomY']; details['laneStartWidth'] = Math.round(details['laneWidth'] * params['canvasTopY'] / params['canvasBottomY']); details['nameX'] = details['laneWidth'] * (details['repnumber'] -1); if (details['laneEndX'] < params['perspectiveX']) { var x = details['laneEndX'] - params['perspectiveX']; var y = 350; var z = Math.sqrt(Math.pow(x,2) + Math.pow(y,2)); details['nameAngle'] = -1 * Math.asin(y/z); details['nameAlign'] = 'light'; } else if (details['laneEndX'] > params['perspectiveX']) { var x = params['perspectiveX'] - details['laneEndX']; var y = 350; var z = Math.sqrt(Math.pow(x,2) + Math.pow(y,2)); details['nameAngle'] = 2* Math.PI - Math.asin(y/z) * -1; details['nameAlign'] = 'right'; } else { details['nameAngle'] = -1 * Math.PI / 2; details['nameAlign'] = 'light'; } details['startLeftX'] = Math.round(params['perspectiveX'] / params['canvasBottomY'] * (params['canvasBottomY'] - params['canvasTopY'])) + Math.round(details['laneX'] * params['canvasTopY'] / params['canvasBottomY']); details['startRightX'] = details['startLeftX'] + details['laneStartWidth']; details['imageLaneY'] = Math.round(50 + (300/params['raceGoal']*details['activities'])); details['imageLaneX'] = Math.round(details['imageLaneY']/details['laneEndY']*(details['laneEndX']-params['perspectiveX'])) + params['perspectiveX']; details['realImageH'] = Math.round(details['maxImageH'] * details['imageLaneY'] / details['laneEndY']); details['realImageW'] = Math.round(details['maxImageW'] * (details['imageLaneX'] - params['perspectiveX']) / (details['laneEndX'] - params['perspectiveX'])); details['imageY'] = details['imageLaneY'] - details['realImageH']; details['imageX'] = details['imageLaneX'] - Math.round(details['realImageW']/2); return details; } function drawTrack(details) { // draw sprint path // context.beginPath(); context.moveTo(params['perspectiveX'], 0); context.lineWidth = 1; context.strokeStyle = 'black'; context.lineTo(details['laneEndX'], details['laneEndY']); context.stroke(); // draw lane context.beginPath(); context.moveTo(details['startLeftX'], params['canvasTopY']); context.lineTo(details['startRightX'] , params['canvasTopY']); context.lineTo(details['laneX'] + details['laneWidth'], details['laneY']); context.lineTo(details['laneX'] , details['laneY']); context.lineTo(details['startLeftX'], details['canvasTopY']); // complete lane var laneColor = '#FFF5EE'; if (details['repnumber']%2 == 1) { laneColor = '#FAAFBA'; } context.closePath(); context.fillStyle = laneColor; context.fill(); context.lineWidth = 1; context.strokeStyle = 'white'; context.stroke(); // salesrep name context.font = '12pt Calibri'; context.fillStyle = 'black'; context.save(); context.translate(details['laneEndX'], 340); context.textBaseline = 'middle'; context.textAlign = details['nameAlign']; context.rotate(details['nameAngle']); context.fillText(details['repname'] , 0,0); context.restore(); } function drawPerson(details) { // draw person line // context.beginPath(); context.moveTo(details['imageLaneX'], details['imageY']); context.lineTo(details['imageLaneX'] , details['imageLaneY']); context.lineWidth = 3; context.strokeStyle = 'black'; context.stroke(); // draw image var image = new Image(); image.onload = function() { context.drawImage(image, details['imageX'], details['imageY'], details['realImageW'], details['realImageH']); }; image.src = 'http://www.clker.com/cliparts/V/X/8/M/m/B/man-running-hi.png'; }
Narrative
details = getDetails('@1', @2, @3, @4); drawTrack(details); drawPerson(details);
Row separator: empty
Postfix
</script>
Rows to Display: empty