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

track raceConcepts

  • 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.

track race tableHide 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

Recommended Posts

Leave A Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.