IdeaStrike Meets Raphael JS Greatness

IdeaStrike Goodness Plus HTML5 and RaphaelIn my last post, Get Me Some Video JS IdeaStrike was introduced to HTML 5 video goodness quickly, simply, and beautifully with Video.js. The New Support view provides an IdeaStrike 101 video experience leveraging HTML 5 video element and Video.js sweetness. In this installatment a review of the New Metric View will be the focus. The New Metric view leverages Raphael JS to provide an awesome, compelling, interactive user experience pie chart. Currently the Metric view only provides two pie charts; Most Ideas/Innovations and Most Votes.

Our internal Phase I is complete, leading to an evaluation period. The port to RavenDB discussed in a previous post, Project – Migrate IdeaStrike to RavenDB will commence shortly.

Phase I – New Features

  1. Support View – HTML 5 and Video JS provides IdeaStrike 101 quickly and simply.
  2. Metric View – Raphael JS provides interactive pie charts.
  3. Admin User Profile
  4. User Profile
  5. Public User Profile

For discussion concerning outlined phases and code changes read on.

Phase II

  1. Admin Tooling
    1. Delete Idea or Comment
    2. Ban User
    3. Comment Approval
    4. New Innovation Workflow
  2. RavenDB

Phase III

The charts listed below will be introduced in a subsequent release.

  1. Most Activity
  2. Most Comments
  3. Most Features

Code

As you can see the Metric View Module supports has two properties, Name and Count with a static method, GetPercentage to return the proper user percentage.

Metric View Module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MetricViewModel
{
    public MetricViewModel()
    {

    }

    public string Name { get; set; }
    public decimal Count { get; set; }

    public static string GetPercentage(decimal piTotalIdeas, decimal piCountByAuthor)
    {
        if (piTotalIdeas <= 0 || piCountByAuthor <= 0)
            return "0";

        decimal part = Math.Round(piCountByAuthor/piTotalIdeas,2);

        return GetFormatedPercentage( (int) (part * 100) );
    }

    private static string GetFormatedPercentage(int piValue)
    {
        return piValue.ToString();
    }

}

As you can see the Metric Module supports two HTTP Get routes, / or /votes. The data, a collection of MetricViewModels is returned using LINQ query.

Metric Module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MetricModule : NancyModule
{
    private readonly IIdeaRepository _ideas;
    private readonly IUserRepository _users;
    private readonly ISettingsRepository _settings;

    public MetricModule(IIdeaRepository ideas, IUserRepository users, ISettingsRepository settings) : base("/metric")
    {
        _ideas = ideas;
        _users = users;
        _settings = settings;

        Get["/"] = _ => ListIdeas(_ideas.GetAll().GroupBy(a => a.Author).Select(result => new MetricViewModel() { Name = result.Key.UserName, Count = result.Count()}), MetricSelectedTab.MostInnovations, "");

        Get["/votes"] = _ => ListIdeas(_ideas.GetAll().GroupBy(a => a.Author).Select(result => new MetricViewModel() { Name = result.Key.UserName, Count = result.Key.Votes.Count }), MetricSelectedTab.MostVotes, "");
    }

    public Response ListIdeas(IEnumerable<MetricViewModel> metric, MetricSelectedTab selected, string ErrorMessage)
    {
        var m = Context.Model(_settings.Title);
        m.Name = _settings.Name;
        m.WelcomeMessage = _settings.WelcomeMessage;
        m.Metrics = metric;
        m.Selected = selected;
        m.IdeaCount = metric.Sum(c => c.Count);
        m.ErrorMessage = ErrorMessage;

        return View["Metric/Index", m];
    }
}

Raphael.js can be downloaded here or you can install it from NuGet using Install-Package RaphaelJS or simply view the package here. It is important to note for IdeaStrike ALL resources, *.js files included are stored in ~/Resources. Therefore, I moved the Raphael scripts installed the NuGet package in ~/Scripts to the ~/Resources directory to remain consistent.

Raphael CSS References:

  1. pie.css
  2. pie-print.css

Raphael JS References:

  1. raphael.js
  2. pie.js

The raphael.js file is the main Raphael JS library. The pie.js file is specific to the pie chart functionality.

Build Chart Data

The view loops through the Model.Metrics, a collection of MetricViewModels building a table of row data in the form of user name, percentage. Each row calls a static method, MerticViewModels.GetPercentage to determine the percentage, passing in the total idea count and the metric view model count.

Metric View Index
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@using Ideastrike.Nancy.Helpers
@using Ideastrike.Nancy.Modules
@using Ideastrike.Nancy.Models
@using Ideastrike.Nancy.Models.ViewModels
@using System.Linq

@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase
@{
    Layout = "Views/Shared/Layout.cshtml";
}

@section HeadContent
{
    <link rel="stylesheet" href='@Url.Content("~/Resources/pie.css")' media="screen" />
    <link rel="stylesheet" href='@Url.Content("~/Resources/pie-print.css")' media="print" />
    <script type="text/javascript" src='@Url.Content("~/Resources/raphael.js")'></script>
    <script type="text/javascript" src='@Url.Content("~/Resources/pie.js")'></script>

     <style media="screen">
        .content
        {
            width: 700px;
            height: 700px;
        }
    </style>

}
@if (!string.IsNullOrWhiteSpace(Model.ErrorMessage))
{
    <div class="fill">
        <div class="alert-message error">
            <a class="close" href="#">&times;</a>
            <p>
                <strong>@Model.ErrorMessage</strong>
            </p>
        </div>
    </div>
}
<h1>@Model.Title</h1>
@MarkdownHelper.Markdown(Model.WelcomeMessage)
<ul class="pills">
    <li @if (Model.Selected == MetricSelectedTab.MostInnovations)
        {@:class="active"
        }
        ><a href="/metric/">Most Innovations</a></li>
    <li @if (Model.Selected == MetricSelectedTab.MostVotes)
        {@:class="active"
        }><a href="/metric/votes">Most Votes</a></li>
</ul>

<table>
    <tbody>
        @foreach (MetricViewModel m in Model.Metrics)
        {
            <tr>
                <th scope="row">@m.Name</th>
                <td>@MetricViewModel.GetPercentage(Model.IdeaCount, m.Count)</td>
            </tr>
        }
    </tbody>
</table>

<div id="holder"></div>

@section PageScript
{
     <script type="text/javascript">
         $(document).ready(function () {

             var values = [];
             var labels = [];

             $("tr").each(function () {
                 values.push(parseInt($("td", this).text(), 10));
                 labels.push($("th", this).text());
             });

             $("table").hide();
             Raphael("holder", 700, 700).pieChart(280, 280, 180, values, labels, "#fff");
         });
    </script>
}

It is important to note when and where the Raphael chart is created. As you can see from the code below, we ONLY execute the Raphael script after the entire DOM has been loaded, $(document).ready(function (). JQuery is leveraged to get the data from the HTML Table rows as arrays passing them along to the Raphael pie function to create the chart.

JQuery Document Loading
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@section PageScript
{
     <script type="text/javascript">
         $(document).ready(function () {

             var values = [];
             var labels = [];

             $("tr").each(function () {
                 values.push(parseInt($("td", this).text(), 10));
                 labels.push($("th", this).text());
             });

             $("table").hide();
             Raphael("holder", 700, 700).pieChart(280, 280, 180, values, labels, "#fff");
         });
    </script>
}

The Raphael pie chart JavaScript function was taken from the Raphaeljs.com example code base.

Rapahel Pie Script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Raphael.fn.pieChart = function (cx, cy, r, values, labels, stroke) {
    var paper = this,
        rad = Math.PI / 180,
        chart = this.set();
    function sector(cx, cy, r, startAngle, endAngle, params) {
        var x1 = cx + r * Math.cos(-startAngle * rad),
            x2 = cx + r * Math.cos(-endAngle * rad),
            y1 = cy + r * Math.sin(-startAngle * rad),
            y2 = cy + r * Math.sin(-endAngle * rad);
        return paper.path(["M", cx, cy, "L", x1, y1, "A", r, r, 0, +(endAngle - startAngle > 180), 0, x2, y2, "z"]).attr(params);
    }
    var angle = 0,
        total = 0,
        start = 0,
        process = function (j) {
            var value = values[j],
                angleplus = 360 * value / total,
                popangle = angle + (angleplus / 2),
                color = Raphael.hsb(start, .75, 1),
                ms = 500,
                delta = 30,
                bcolor = Raphael.hsb(start, 1, 1),
                p = sector(cx, cy, r, angle, angle + angleplus, {fill: "90-" + bcolor + "-" + color, stroke: stroke, "stroke-width": 3}),
                txt = paper.text(cx + (r + delta + 55) * Math.cos(-popangle * rad), cy + (r + delta + 25) * Math.sin(-popangle * rad), labels[j]).attr({fill: bcolor, stroke: "none", opacity: 0, "font-size": 20});
            p.mouseover(function () {
                p.stop().animate({transform: "s1.1 1.1 " + cx + " " + cy}, ms, "elastic");
                txt.stop().animate({opacity: 1}, ms, "elastic");
            }).mouseout(function () {
                p.stop().animate({transform: ""}, ms, "elastic");
                txt.stop().animate({opacity: 0}, ms);
            });
            angle += angleplus;
            chart.push(p);
            chart.push(txt);
            start += .1;
        };
    for (var i = 0, ii = values.length; i < ii; i++) {
        total += values[i];
    }
    for (i = 0; i < ii; i++) {
        process(i);
    }
    return chart;
};

Support View

IdeaStrike Support View

IdeaStrike Support View FAQ

Metric View: Most Innovations

IdeaStrike Metric View: Most Innovations

Metric View: Most Votes

IdeaStrike Metric View: Most Votes

Backlog Overview

  1. Categories and Tags
  2. Add Work Email to User Profile
  3. Add Work Email to Public Profile
  4. Groups
  5. Security related to groups
  6. Workflow Updates
  7. Admin Delete
  8. Create Idea via Yammer
  9. Send Idea to Yammer
  10. Create Idea via Email
  11. Send Email template to new User
  12. Edit own Features

References

Comments