| rev | 
   line source | 
| 
bsw@1045
 | 
     1 function util.initiative_pie(initiative, d, gap)
 | 
| 
bsw@1045
 | 
     2   if not initiative.issue.closed or not initiative.admitted then
 | 
| 
bsw@1045
 | 
     3     return
 | 
| 
bsw@1045
 | 
     4   end
 | 
| 
bsw@1045
 | 
     5   
 | 
| 
bsw@1045
 | 
     6   if initiative.issue.voter_count == 0 or initiative.positive_votes == nil or initiative.negative_votes == nil then
 | 
| 
bsw@1045
 | 
     7     return
 | 
| 
bsw@1045
 | 
     8   end
 | 
| 
bsw@1045
 | 
     9   
 | 
| 
bsw@1045
 | 
    10   local first_preference_votes = initiative.first_preference_votes
 | 
| 
bsw@1045
 | 
    11   if first_preference_votes == nil then
 | 
| 
bsw@1045
 | 
    12     first_preference_votes = 0
 | 
| 
bsw@1045
 | 
    13   end
 | 
| 
bsw@1045
 | 
    14     
 | 
| 
bsw@1045
 | 
    15   local d = d or 100
 | 
| 
bsw@1045
 | 
    16   local gap = gap or d / 20
 | 
| 
bsw@1045
 | 
    17   
 | 
| 
bsw@1045
 | 
    18   local r = d/2
 | 
| 
bsw@1045
 | 
    19   local r_circle = r - gap
 | 
| 
bsw@1045
 | 
    20   
 | 
| 
bsw@1045
 | 
    21   local function circle(p)
 | 
| 
bsw@1045
 | 
    22     return
 | 
| 
bsw@1045
 | 
    23       gap + 2 * r_circle * ( 1 + math.sin( 2 * math.pi * p ) ) / 2,
 | 
| 
bsw@1045
 | 
    24       gap + 2 * r_circle * ( 1 - math.cos( 2 * math.pi * p ) ) / 2
 | 
| 
bsw@1045
 | 
    25   end
 | 
| 
bsw@1045
 | 
    26   
 | 
| 
bsw@1045
 | 
    27   local function getpath(start, stop)
 | 
| 
bsw@1045
 | 
    28     local start_x, start_y = circle(start)
 | 
| 
bsw@1045
 | 
    29     local stop_x, stop_y = circle(stop)
 | 
| 
bsw@1045
 | 
    30     local large = stop - start > 0.5 and "1" or "0"
 | 
| 
bsw@1045
 | 
    31     return "M" .. r .. "," .. r .. " "
 | 
| 
bsw@1045
 | 
    32         .. "L" .. start_x .. ",".. start_y .. " " .. " "
 | 
| 
bsw@1045
 | 
    33         .. "A" .. r_circle .. "," .. r_circle .. " 0 " .. large .. ",1 " .. stop_x .. "," .. stop_y .. " "
 | 
| 
bsw@1045
 | 
    34         .. "z"
 | 
| 
bsw@1045
 | 
    35   end
 | 
| 
bsw@1045
 | 
    36   
 | 
| 
bsw@1045
 | 
    37   local function uniPie(color, content)
 | 
| 
bsw@1045
 | 
    38     ui.tag {
 | 
| 
bsw@1045
 | 
    39       tag = "svg",
 | 
| 
bsw@1045
 | 
    40       attr = {
 | 
| 
bsw@1045
 | 
    41         class = "initiative_pie",
 | 
| 
bsw@1045
 | 
    42         width = d .. "px",
 | 
| 
bsw@1045
 | 
    43         height = d .. "px",
 | 
| 
bsw@1045
 | 
    44       },
 | 
| 
bsw@1045
 | 
    45       content = function ()
 | 
| 
bsw@1045
 | 
    46         ui.tag { tag = "circle", attr = {
 | 
| 
bsw@1045
 | 
    47           cx=r, cy=r, r=r_circle, fill = color, stroke = "#fff", ["stroke-width"] = "2"
 | 
| 
bsw@1045
 | 
    48         }, content = function () ui.tag { tag = "title", content = content } end  }
 | 
| 
bsw@1045
 | 
    49       end
 | 
| 
bsw@1045
 | 
    50     }
 | 
| 
bsw@1045
 | 
    51   end
 | 
| 
bsw@1045
 | 
    52   
 | 
| 
bsw@1045
 | 
    53   local function piePiece(path, fill, content)
 | 
| 
bsw@1045
 | 
    54     ui.tag {
 | 
| 
bsw@1045
 | 
    55       tag = "path",
 | 
| 
bsw@1045
 | 
    56       attr = {
 | 
| 
bsw@1045
 | 
    57         d = path,
 | 
| 
bsw@1045
 | 
    58         fill = fill,
 | 
| 
bsw@1045
 | 
    59         stroke = "#fff",
 | 
| 
bsw@1045
 | 
    60         ["stroke-width"] = "2",
 | 
| 
bsw@1045
 | 
    61         ["stroke-linecap"] = "butt"
 | 
| 
bsw@1045
 | 
    62       }, 
 | 
| 
bsw@1045
 | 
    63       content = function ()
 | 
| 
bsw@1045
 | 
    64         ui.tag {
 | 
| 
bsw@1045
 | 
    65           tag = "title", 
 | 
| 
bsw@1045
 | 
    66           content = content
 | 
| 
bsw@1045
 | 
    67         }
 | 
| 
bsw@1045
 | 
    68       end  
 | 
| 
bsw@1045
 | 
    69     }
 | 
| 
bsw@1045
 | 
    70   end
 | 
| 
bsw@1045
 | 
    71   
 | 
| 
bsw@1045
 | 
    72   local function pie(args)
 | 
| 
bsw@1045
 | 
    73     local offset = args.offset or 0
 | 
| 
bsw@1045
 | 
    74     local list = {}
 | 
| 
bsw@1045
 | 
    75     local sum = 0
 | 
| 
bsw@1045
 | 
    76     for i, element in ipairs(args) do
 | 
| 
bsw@1045
 | 
    77       element.start = sum + offset
 | 
| 
bsw@1045
 | 
    78       list[#list+1] = element
 | 
| 
bsw@1045
 | 
    79       sum = sum + element.value
 | 
| 
bsw@1045
 | 
    80     end
 | 
| 
bsw@1045
 | 
    81     
 | 
| 
bsw@1045
 | 
    82     for i, element in ipairs(list) do
 | 
| 
bsw@1045
 | 
    83       if element.value == sum then
 | 
| 
bsw@1045
 | 
    84         uniPie(element.fill, _(element.label, { count = element.value } ))
 | 
| 
bsw@1045
 | 
    85         return
 | 
| 
bsw@1045
 | 
    86       end
 | 
| 
bsw@1045
 | 
    87     end
 | 
| 
bsw@1045
 | 
    88     ui.tag {
 | 
| 
bsw@1045
 | 
    89       tag = "svg",
 | 
| 
bsw@1045
 | 
    90       attr = {
 | 
| 
bsw@1045
 | 
    91         class = "initiative_pie",
 | 
| 
bsw@1045
 | 
    92         width = d .. "px",
 | 
| 
bsw@1045
 | 
    93         height = d .. "px"
 | 
| 
bsw@1045
 | 
    94       },
 | 
| 
bsw@1045
 | 
    95       content = function ()
 | 
| 
bsw@1045
 | 
    96         table.sort(list, function (a, b)
 | 
| 
bsw@1045
 | 
    97           return a.value < b.value
 | 
| 
bsw@1045
 | 
    98         end )
 | 
| 
bsw@1045
 | 
    99         for i, element in ipairs(list) do
 | 
| 
bsw@1045
 | 
   100           local path = getpath(element.start / sum, (element.start + element.value) / sum)
 | 
| 
bsw@1045
 | 
   101           local content = _(element.label, { count = element.value })
 | 
| 
bsw@1045
 | 
   102           piePiece(path, element.fill, content)
 | 
| 
bsw@1045
 | 
   103         end
 | 
| 
bsw@1045
 | 
   104       end
 | 
| 
bsw@1045
 | 
   105     }
 | 
| 
bsw@1045
 | 
   106   end
 | 
| 
bsw@1045
 | 
   107   
 | 
| 
bsw@1045
 | 
   108   local yes1 = first_preference_votes
 | 
| 
bsw@1045
 | 
   109   local yes = initiative.positive_votes - first_preference_votes
 | 
| 
bsw@1045
 | 
   110   local neutral = initiative.issue.voter_count - initiative.positive_votes - initiative.negative_votes
 | 
| 
bsw@1045
 | 
   111   local no = initiative.negative_votes
 | 
| 
bsw@1045
 | 
   112   
 | 
| 
bsw@1045
 | 
   113   local sum = yes1 + yes + neutral + no
 | 
| 
bsw@1045
 | 
   114   
 | 
| 
bsw@1045
 | 
   115   local q = initiative.issue.policy.direct_majority_num / initiative.issue.policy.direct_majority_den
 | 
| 
bsw@1045
 | 
   116       
 | 
| 
bsw@1045
 | 
   117   
 | 
| 
bsw@1045
 | 
   118   local maxrot = sum * 7 / 12 - no
 | 
| 
bsw@1045
 | 
   119   
 | 
| 
bsw@1045
 | 
   120   local offset = 0
 | 
| 
bsw@1045
 | 
   121   
 | 
| 
bsw@1045
 | 
   122   if maxrot > 0 then
 | 
| 
bsw@1045
 | 
   123     offset = math.min (
 | 
| 
bsw@1045
 | 
   124       maxrot, 
 | 
| 
bsw@1045
 | 
   125       no * ( 1 / ( 1 / q - 1 ) -1 ) / 2
 | 
| 
bsw@1045
 | 
   126     )
 | 
| 
bsw@1045
 | 
   127   end
 | 
| 
bsw@1045
 | 
   128     
 | 
| 
bsw@1045
 | 
   129   pie{
 | 
| 
bsw@1045
 | 
   130     { value = yes1,    fill = "#0a0", label = _"#{count} Yes, first choice" },
 | 
| 
bsw@1045
 | 
   131     { value = yes,     fill = "#6c6", label = _"#{count} Yes, alternative choice" },
 | 
| 
bsw@1045
 | 
   132     { value = neutral, fill = "#ccc", label = _"#{count} Neutral" },
 | 
| 
bsw@1045
 | 
   133     { value = no,      fill = "#c00", label = _"#{count} No" },
 | 
| 
bsw@1045
 | 
   134     offset = - offset
 | 
| 
bsw@1045
 | 
   135   }
 | 
| 
bsw@1045
 | 
   136     
 | 
| 
bsw@1045
 | 
   137 end |