<commit_before>/****************************************************************************
#
# This file is part of the Vilfredo Client.
#
# Copyright © 2009-2014 <NAME> / <NAME>.
#
# VilfredoReloadedCore is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation version 3 of the License.
#
# VilfredoReloadedCore is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License
# for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with VilfredoReloadedCore.  If not, see <http://www.gnu.org/licenses/>.
#
****************************************************************************/
var question_id = getQuerySegment('question');
if (!question_id) { 
    question_id = getQuerySegment('domination');
}
console.log(question_id);

var generation_id = parseInt(getQuerySegment('gen'));

var room = getQuerySegment('room');
console.log(room);

//var room = $.url().param('room') ? $.url().param('room') : '';
//var room = getQuerySegment('room');

var DEFAULT_MAP_TYPE = 'all';

var currentUserViewModel;
var proposalsViewModel;
var questionViewModel;
var viewProposalViewModel;
var loginViewModel;
var addProposalViewModel;
var newQuestionViewModel;
var svggraph;
var userid = false;
var votesgraph;
var pfvotesgraph;
var triangle;

function getKeys(object)
{
    //return ["5", "6", "7", "8"];
    var keys = [];
    map = object;
    if (map === undefined) return;
    $.each(map, function( index, value ) {
        keys.push(index);
    });
    return keys;
}

function arrayDiff(arr1, arr2)
{
    diff = [];
    jQuery.grep(arr2, function(el) {
        if (jQuery.inArray(el, arr1) == -1) 
        {
            diff.push(el);
        }
    });
    return diff;
}

function getQuerySegment(variable)
{
    //var params = $.url('0.0.0.0:8080/room/vilfredo/question/2').segment();
	var params = $.url().segment();
	var match = params.indexOf(variable);
	//console.log('getQueryVariable: match index = ' + match);
	if (match > -1 && typeof params[match+1] != "undefined")
	{
		return params[match+1]
	}
	else
	{
		return false;
	}
}
function getQueryVariable(variable)
{
    //var params = $.url('0.0.0.0:8080/room/vilfredo/question/2').segment();
	return $.url().param(variable);
}


function plotThresholdEndpoint(Ax, Ay, Bx, By, Ty) {
    return (Ty - (Ay - (Ax * (By - Ay) ) / (Bx - Ax))) / ((By - Ay) / (Bx - Ax));
}

function getNumericStyleProperty(style, prop){
    return parseInt(style.getPropertyValue(prop),10) ;
}

function element_position(e) {
    var x = 0, y = 0;
    var inner = true ;
    do {
        x += e.offsetLeft;
        y += e.offsetTop;
        var style = getComputedStyle(e,null) ;
        var borderTop = getNumericStyleProperty(style,"border-top-width") ;
        var borderLeft = getNumericStyleProperty(style,"border-left-width") ;
        y += borderTop ;
        x += borderLeft ;
        if (inner){
          var paddingTop = getNumericStyleProperty(style,"padding-top") ;
          var paddingLeft = getNumericStyleProperty(style,"padding-left") ;
          y += paddingTop ;
          x += paddingLeft ;
        }
        inner = false ;
    } while (e = e.offsetParent);
    return { x: x, y: y };
}

// Get point in global SVG space
function cursorPoint(svg, evt){
  var pt = svg.createSVGPoint();
  pt.x = evt.clientX; pt.y = evt.clientY;
  return pt.matrixTransform(svg.getScreenCTM().inverse());
}
function initCVTriangle_v2(svg)
{
	$('#votenowwindow .showtriangle .votenow').on( "click", function(e) {
    	var loc = cursorPoint(svg, e);
        console.log(loc.x + ' , ' + loc.y);
    	//svg = $('#votenowwindow .showtriangle').svg('get');
    	//svg.circle(currentx, currenty, 12, {class: 'vote', fill: 'white', stroke: 'white', strokeWidth: 2, cursor:     'pointer'});
	});
}


function drawTriangle2(svg, width, height)
{
	var path = svg.createPath();
    var triangle = svg.path(
        path.move( 100, 0)
        .line( -100, 200, true )
        .line( 200, 0, true )
        .close(),
        {
            fill: 'blue', 
            stroke: 'white', 
            strokeWidth: 2
        }
    );
}

function initCVTriangleLarge_v2(svg)
{
    var c = document.getElementsByClassName('pointer');
    var cx = c.cx.baseVal;
    var cy = c.cy.baseVal;
    var svg = this;
    var point = svg.createSVGPoint();
    svg.onmousemove = function(e){
        point.x = e.clientX;
        point.y = e.clientY;
        var ctm = c.getScreenCTM();
        var inverse = ctm.inverse();
        var p = point.matrixTransform(inverse);
        cx.value = p.x;
        cy.value = p.y;
    };
}



function resetSize(svg, width, height) 
{
	svg.configure({width: width, height: height}, true);
}

// Add current votes to votemap
function setVote(proposal)
{
    console.log('setVote called...');
	var votemap = $('#modalvotesmap');
	var svg = $(votemap).svg('get');
	
	max_x = $(svg._container).innerWidth();
	console.log('container_width = ' + max_x);
    max_y = 0.7 * max_x;
    console.log('container_height = ' + max_y);

	cx = max_x * proposal.mapx;
	cy = max_y * proposal.mapy;
	
	var votesgroup = $('.votes', svg.root());
    var radius = 10;
	
	// If a vote is already plotted, remove it
	var vote = $('.vote', svg.root()).filter(function() {
		return $(this).data("pid") == proposal.id();
	});
	if (vote)
	{
		$(vote).remove();
	}
	
	// Set color based on endorse_type
	switch(proposal.endorse_type())
    {
    case 'endorse':
        fill_color = 'green';
        break;
    case 'oppose':
        fill_color = 'red';
        break;
    case 'confused':
        fill_color = 'blue';
        break;
    default:
        fill_color = 'yellow';
    }
	
	// Plot vote on votemap
	vote = svg.circle(votesgroup, cx, cy, radius+1, {class: 'vote', fill: fill_color, cursor: 'pointer'});
    $(vote).data('pid', proposal.id());
    $(vote).on( "mouseenter", function(e) {
        //console.log("Proposal " + $(this).data('pid'));
        console.log("Proposal cx " + $(this).attr('cx') + ", cy " + $(this).attr('cy'));
        //$('#votemap-thisprop').html($(this).data('pid'));
    });
}

function voteHandler(e)
{
    console.log('vote recorded by voteHandler function...');
	var posX = $('#map').offset().left;
	var cx = e.pageX - posX - radius;
    var posY = $(triangle).offset().top;
    var cy = e.pageY - posY - radius;
	//svg.circle(g, cx, cy, radius+1, {class: 'vote', fill: 'yellow', cursor: 'pointer'});
	// Endorse with normalised vote coordinates
	var n_cx = cx / max_x;
	var n_cy = cy / max_y;
	console.log("Vote to be added at " + cx + ", " + cy);
	//proposalsViewModel.mapEndorseWithIndex(n_cx, n_cy, voteMapViewModel.proposal_index);
}

function showProposalVotes(svg, threshold, voters)
{    
    var container_width = $(svg._container).innerWidth();
    var container_height = 0.7 * container_width;
    
    $('#allvotes').remove();
    
    //threshold = {'mapx': container_width * questionViewModel.mapx, 'mapy': container_height * questionViewModel.mapy}
    console.log("Threshold at (" + threshold.mapx + ", " + threshold.mapy +")");
    
    var g = svg.group({id : 'allvotes'});
    var radius = 10;
    fill_color;
        
    //svg.circle(g, threshold.mapx, threshold.mapx, radius+1, {fill: 'yellow', title: 'THRESHOLD ' + threshold.mapx + ', ' +  threshold.mapy});
    
    jQuery.each(voters, function(userid, coords) {
        cx = container_width * coords.mapx;
        cy = container_height * coords.mapy;
        //console.log("Draw " + coords.username + "'s vote at (" + cx + ", " + cy +")");
                
        // Set fill colour
        if (cy > threshold.mapy)
        {
            //console.log(cy + ' > ' + threshold.mapy + ' == True');
            //console.log('Set to blue');
            fill_color = 'blue';
        }
        else if (cx < threshold.mapx)
        {
            //console.log(cy + ' > ' + threshold.mapy + ' == False AND ' + cx + ' < ' + threshold.mapx);
            //console.log('Set to red');
            fill_color = 'red';
        }
        else
        {
            //console.log('Else set to green');
            fill_color = 'green';
        }
        
        vote = svg.circle(g, cx, cy, radius+1, {class: 'allvotes', fill: fill_color, cursor: 'pointer', title: 'User ' + userid});
        $(vote).data('userid', userid);
        
        // Display username
        var txtx, texty;
        if (cx < 30)
        {
            txtx = cx + 25;
        }
        else
        {
            txtx = cx - 20;
        }
        if (cy < 30)
        {
            txty = cy + 25;
        }
        else
        {
            txty = cy - 20;
        }
        svg.text(g, txtx, txty, coords.username); 
    });
}


function redoResultsMap()
{
    console.log('redoResultsMap called...');
    $.when(questionViewModel.fetchVotingResults()).done(function()
    {
	    svg = $('#resultstriangle').svg('get');
        createResultsMap(svg);
    });
}


function createResultsMap(svg) // jazz
{
	console.log('createResultsMap called...');

	var container_width = $(svg._container).innerWidth();
	console.log('container_width = ' + container_width);
    var container_height = 0.7 * container_width;
    console.log('container_height = ' + container_height);
    
    var max_x = container_width;
    var max_y = container_height;
    var mid_x = container_width/2;
    var mid_y = container_height/2;
    
    resetSize(svg, container_width, container_height); 
    
	var path = svg.createPath();
    var triangle = svg.path(
        path.move(0, 0)
        .line( container_width/2, container_height, true )
        .line( container_width/2, -container_height, true )
        .close(),
        {
            fill: 'white',
            stroke: '#CDCDCD',
            strokeWidth: 2,
            id: 'map'
        }
    );

    var threshold_x = container_width*questionViewModel.mapx;
    var threshold_y = container_height*questionViewModel.mapy;
    // Add current threshold point - debugging
    
    var threshold = {'mapx': threshold_x, 'mapy': threshold_y};
    
    
    // Add threshold marker
    var marker_top = svg.line(threshold_x, 0, threshold_x, threshold_y, {id: 'marker_top', strokeWidth: 2, stroke: 'black'});
    
    var Lx = plotThresholdEndpoint(0, 0, mid_x, max_y, threshold_y);
    console.log("Left point at " + Lx + ", " + threshold_y);
    
    var Rx = plotThresholdEndpoint(max_x, 0, mid_x, max_y, threshold_y);
    console.log("Right point at " + Rx + ", " + threshold_y);
    // #CDCDCD
    var marker_left = svg.line(Lx, threshold_y, Rx, threshold_y, {id: 'marker_top', strokeWidth: 2, stroke: 'black'});

    var g = svg.group({id : 'votes'});
    var radius = 10;
    
    /*
    var threshold_x = container_width*questionViewModel.mapx;
    var threshold_y = container_height*questionViewModel.mapy;
    console.log("Threshold at " + threshold_x + ", " + threshold_y);
    */
    /*
    {1L: 
    { 'median': {'medx': 0.631388, 'medy': 0.598698},
      'voters': {
       1L: {'mapx': 0.65, 'mapy': 0.16},
       2L: {'mapx': 0.497361, 'mapy': 0.598698},
       3L: {'mapx': 0.75, 'mapy': 0.46},
       4L: {'mapx': 0.631388, 'mapy': 0.634726},
       5L: {'mapx': 0.428218, 'mapy': 0.710889}}
      },
    */
    
    jQuery.each(questionViewModel.results, function(pid, coords) {
        if (!coords['median'])
        {
            return;
        }
        
        cx = container_width * coords['median'].medx;
        cy = container_height * coords['median'].medy;
        
        console.log("Draw result vote at (" + cx + ", " + cy +")");
        
        fill_color = '#BEBEBE';
        
        med_fill = '#BEBEBE';
        med_selected_fill_color = '#7e7e7e';
        
        // var title ='Proposal ' + pid;
        var title; 
        var prop = proposalsViewModel.getProposal(pid);
        
        //title = 'Coords (' + cx + ', ' + cy + ')';
        
        if (prop)
        {
             title = prop.title();
        }
        else
        {
            title = 'Proposal ID ' + pid
        }
        
        med = svg.circle(g, cx, cy, radius+1, {class: 'med', fill: med_fill, cursor: 'pointer', title: title});
        $(med).data('pid', pid);
        
        // Display proposal ID
        var txtx, texty;
        txtx = cx;
        if (cy < 30)
        {
            txty = cy + 25;
        }
        else
        {
            txty = cy - 20;
        }
        svg.text(g, txtx, txty, pid); 
        
        $(med).on( "click", function(e) {
            console.log('click on median...');
            //alert('Median clicked - PID ' + $(this).data('pid')); // huh
            $('.med').attr('fill', med_fill);
            
            $(this).attr('fill', med_selected_fill_color);
            showProposalVotes(svg, threshold, coords['voters']);
            /*
            var evt = new jQuery.Event("click");
            evt.pageX = e.pageX;
            evt.pageY = e.pageY;
            */
            //$(this).parent().siblings('#map').trigger(e);
        });
        
        // Add error triangle if defined
        if (false && coords['e_error'])
        {<commit_msg>Connect votes to median in results map by colour coded line.<commit_after>/****************************************************************************
#
# This file is part of the Vilfredo Client.
#
# Copyright © 2009-2014 <NAME> / <NAME>.
#
# VilfredoReloadedCore is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation version 3 of the License.
#
# VilfredoReloadedCore is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License
# for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with VilfredoReloadedCore.  If not, see <http://www.gnu.org/licenses/>.
#
****************************************************************************/
var question_id = getQuerySegment('question');
if (!question_id) { 
    question_id = getQuerySegment('domination');
}
console.log(question_id);

var generation_id = parseInt(getQuerySegment('gen'));

var room = getQuerySegment('room');
console.log(room);

//var room = $.url().param('room') ? $.url().param('room') : '';
//var room = getQuerySegment('room');

var DEFAULT_MAP_TYPE = 'all';

var currentUserViewModel;
var proposalsViewModel;
var questionViewModel;
var viewProposalViewModel;
var loginViewModel;
var addProposalViewModel;
var newQuestionViewModel;
var svggraph;
var userid = false;
var votesgraph;
var pfvotesgraph;
var triangle;

function getKeys(object)
{
    //return ["5", "6", "7", "8"];
    var keys = [];
    map = object;
    if (map === undefined) return;
    $.each(map, function( index, value ) {
        keys.push(index);
    });
    return keys;
}

function arrayDiff(arr1, arr2)
{
    diff = [];
    jQuery.grep(arr2, function(el) {
        if (jQuery.inArray(el, arr1) == -1) 
        {
            diff.push(el);
        }
    });
    return diff;
}

function getQuerySegment(variable)
{
    //var params = $.url('0.0.0.0:8080/room/vilfredo/question/2').segment();
	var params = $.url().segment();
	var match = params.indexOf(variable);
	//console.log('getQueryVariable: match index = ' + match);
	if (match > -1 && typeof params[match+1] != "undefined")
	{
		return params[match+1]
	}
	else
	{
		return false;
	}
}
function getQueryVariable(variable)
{
    //var params = $.url('0.0.0.0:8080/room/vilfredo/question/2').segment();
	return $.url().param(variable);
}


function plotThresholdEndpoint(Ax, Ay, Bx, By, Ty) {
    return (Ty - (Ay - (Ax * (By - Ay) ) / (Bx - Ax))) / ((By - Ay) / (Bx - Ax));
}

function getNumericStyleProperty(style, prop){
    return parseInt(style.getPropertyValue(prop),10) ;
}

function element_position(e) {
    var x = 0, y = 0;
    var inner = true ;
    do {
        x += e.offsetLeft;
        y += e.offsetTop;
        var style = getComputedStyle(e,null) ;
        var borderTop = getNumericStyleProperty(style,"border-top-width") ;
        var borderLeft = getNumericStyleProperty(style,"border-left-width") ;
        y += borderTop ;
        x += borderLeft ;
        if (inner){
          var paddingTop = getNumericStyleProperty(style,"padding-top") ;
          var paddingLeft = getNumericStyleProperty(style,"padding-left") ;
          y += paddingTop ;
          x += paddingLeft ;
        }
        inner = false ;
    } while (e = e.offsetParent);
    return { x: x, y: y };
}

// Get point in global SVG space
function cursorPoint(svg, evt){
  var pt = svg.createSVGPoint();
  pt.x = evt.clientX; pt.y = evt.clientY;
  return pt.matrixTransform(svg.getScreenCTM().inverse());
}
function initCVTriangle_v2(svg)
{
	$('#votenowwindow .showtriangle .votenow').on( "click", function(e) {
    	var loc = cursorPoint(svg, e);
        console.log(loc.x + ' , ' + loc.y);
    	//svg = $('#votenowwindow .showtriangle').svg('get');
    	//svg.circle(currentx, currenty, 12, {class: 'vote', fill: 'white', stroke: 'white', strokeWidth: 2, cursor:     'pointer'});
	});
}


function drawTriangle2(svg, width, height)
{
	var path = svg.createPath();
    var triangle = svg.path(
        path.move( 100, 0)
        .line( -100, 200, true )
        .line( 200, 0, true )
        .close(),
        {
            fill: 'blue', 
            stroke: 'white', 
            strokeWidth: 2
        }
    );
}

function initCVTriangleLarge_v2(svg)
{
    var c = document.getElementsByClassName('pointer');
    var cx = c.cx.baseVal;
    var cy = c.cy.baseVal;
    var svg = this;
    var point = svg.createSVGPoint();
    svg.onmousemove = function(e){
        point.x = e.clientX;
        point.y = e.clientY;
        var ctm = c.getScreenCTM();
        var inverse = ctm.inverse();
        var p = point.matrixTransform(inverse);
        cx.value = p.x;
        cy.value = p.y;
    };
}



function resetSize(svg, width, height) 
{
	svg.configure({width: width, height: height}, true);
}

// Add current votes to votemap
function setVote(proposal)
{
    console.log('setVote called...');
	var votemap = $('#modalvotesmap');
	var svg = $(votemap).svg('get');
	
	max_x = $(svg._container).innerWidth();
	console.log('container_width = ' + max_x);
    max_y = 0.7 * max_x;
    console.log('container_height = ' + max_y);

	cx = max_x * proposal.mapx;
	cy = max_y * proposal.mapy;
	
	var votesgroup = $('.votes', svg.root());
    var radius = 10;
	
	// If a vote is already plotted, remove it
	var vote = $('.vote', svg.root()).filter(function() {
		return $(this).data("pid") == proposal.id();
	});
	if (vote)
	{
		$(vote).remove();
	}
	
	// Set color based on endorse_type
	switch(proposal.endorse_type())
    {
    case 'endorse':
        fill_color = 'green';
        break;
    case 'oppose':
        fill_color = 'red';
        break;
    case 'confused':
        fill_color = 'blue';
        break;
    default:
        fill_color = 'yellow';
    }
	
	// Plot vote on votemap
	vote = svg.circle(votesgroup, cx, cy, radius+1, {class: 'vote', fill: fill_color, cursor: 'pointer'});
    $(vote).data('pid', proposal.id());
    $(vote).on( "mouseenter", function(e) {
        //console.log("Proposal " + $(this).data('pid'));
        console.log("Proposal cx " + $(this).attr('cx') + ", cy " + $(this).attr('cy'));
        //$('#votemap-thisprop').html($(this).data('pid'));
    });
}

function voteHandler(e)
{
    console.log('vote recorded by voteHandler function...');
	var posX = $('#map').offset().left;
	var cx = e.pageX - posX - radius;
    var posY = $(triangle).offset().top;
    var cy = e.pageY - posY - radius;
	//svg.circle(g, cx, cy, radius+1, {class: 'vote', fill: 'yellow', cursor: 'pointer'});
	// Endorse with normalised vote coordinates
	var n_cx = cx / max_x;
	var n_cy = cy / max_y;
	console.log("Vote to be added at " + cx + ", " + cy);
	//proposalsViewModel.mapEndorseWithIndex(n_cx, n_cy, voteMapViewModel.proposal_index);
}

function showProposalVotes(med, svg, threshold, voters)
{    
    var container_width = $(svg._container).innerWidth();
    var container_height = 0.7 * container_width;
    
    $('#allvotes').remove();
    
    //threshold = {'mapx': container_width * questionViewModel.mapx, 'mapy': container_height * questionViewModel.mapy}
    console.log("Threshold at (" + threshold.mapx + ", " + threshold.mapy +")");
    
    var g = svg.group({id : 'allvotes'});
    var radius = 10;
    fill_color;
        
    //svg.circle(g, threshold.mapx, threshold.mapx, radius+1, {fill: 'yellow', title: 'THRESHOLD ' + threshold.mapx + ', ' +  threshold.mapy});
    
    jQuery.each(voters, function(userid, coords) {
        cx = container_width * coords.mapx;
        cy = container_height * coords.mapy;
        //console.log("Draw " + coords.username + "'s vote at (" + cx + ", " + cy +")");
                
        // Set fill colour
        if (cy > threshold.mapy)
        {
            //console.log(cy + ' > ' + threshold.mapy + ' == True');
            //console.log('Set to blue');
            fill_color = 'blue';
        }
        else if (cx < threshold.mapx)
        {
            //console.log(cy + ' > ' + threshold.mapy + ' == False AND ' + cx + ' < ' + threshold.mapx);
            //console.log('Set to red');
            fill_color = 'red';
        }
        else
        {
            //console.log('Else set to green');
            fill_color = 'green';
        }
        
        // Draw line to connect vote with median
        svg.line(g, cx, cy, parseInt($(med).attr('cx')), parseInt($(med).attr('cy')), {strokeWidth: 1, stroke: fill_color});
        
        vote = svg.circle(g, cx, cy, radius+1, {class: 'allvotes', fill: fill_color, cursor: 'pointer', title: 'User ' + userid});
        $(vote).data('userid', userid);
        
        
        // Display username
        var txtx, texty;
        if (cx < 30)
        {
            txtx = cx + 25;
        }
        else
        {
            txtx = cx - 20;
        }
        if (cy < 30)
        {
            txty = cy + 25;
        }
        else
        {
            txty = cy - 20;
        }
        svg.text(g, txtx, txty, coords.username); 
    });
}


function redoResultsMap()
{
    console.log('redoResultsMap called...');
    $.when(questionViewModel.fetchVotingResults()).done(function()
    {
	    svg = $('#resultstriangle').svg('get');
        createResultsMap(svg);
    });
}


function createResultsMap(svg) // jazz
{
	console.log('createResultsMap called...');

	var container_width = $(svg._container).innerWidth();
	console.log('container_width = ' + container_width);
    var container_height = 0.7 * container_width;
    console.log('container_height = ' + container_height);
    
    var max_x = container_width;
    var max_y = container_height;
    var mid_x = container_width/2;
    var mid_y = container_height/2;
    
    resetSize(svg, container_width, container_height); 
    
	var path = svg.createPath();
    var triangle = svg.path(
        path.move(0, 0)
        .line( container_width/2, container_height, true )
        .line( container_width/2, -container_height, true )
        .close(),
        {
            fill: 'white',
            stroke: '#CDCDCD',
            strokeWidth: 2,
            id: 'map'
        }
    );

    var threshold_x = container_width*questionViewModel.mapx;
    var threshold_y = container_height*questionViewModel.mapy;
    // Add current threshold point - debugging
    
    var threshold = {'mapx': threshold_x, 'mapy': threshold_y};
    
    
    // Add threshold marker
    var marker_top = svg.line(threshold_x, 0, threshold_x, threshold_y, {id: 'marker_top', strokeWidth: 2, stroke: 'black'});
    
    var Lx = plotThresholdEndpoint(0, 0, mid_x, max_y, threshold_y);
    console.log("Left point at " + Lx + ", " + threshold_y);
    
    var Rx = plotThresholdEndpoint(max_x, 0, mid_x, max_y, threshold_y);
    console.log("Right point at " + Rx + ", " + threshold_y);
    // #CDCDCD
    var marker_left = svg.line(Lx, threshold_y, Rx, threshold_y, {id: 'marker_top', strokeWidth: 2, stroke: 'black'});

    var g = svg.group({id : 'votes'});
    var radius = 10;
    
    /*
    var threshold_x = container_width*questionViewModel.mapx;
    var threshold_y = container_height*questionViewModel.mapy;
    console.log("Threshold at " + threshold_x + ", " + threshold_y);
    */
    /*
    {1L: 
    { 'median': {'medx': 0.631388, 'medy': 0.598698},
      'voters': {
       1L: {'mapx': 0.65, 'mapy': 0.16},
       2L: {'mapx': 0.497361, 'mapy': 0.598698},
       3L: {'mapx': 0.75, 'mapy': 0.46},
       4L: {'mapx': 0.631388, 'mapy': 0.634726},
       5L: {'mapx': 0.428218, 'mapy': 0.710889}}
      },
    */
    
    jQuery.each(questionViewModel.results, function(pid, coords) {
        if (!coords['median'])
        {
            return;
        }
        
        cx = container_width * coords['median'].medx;
        cy = container_height * coords['median'].medy;
        
        console.log("Draw result vote at (" + cx + ", " + cy +")");
        
        fill_color = '#BEBEBE';
        
        med_fill = '#BEBEBE';
        med_selected_fill_color = '#7e7e7e';
        
        // var title ='Proposal ' + pid;
        var title; 
        var prop = proposalsViewModel.getProposal(pid);
        
        //title = 'Coords (' + cx + ', ' + cy + ')';
        
        if (prop)
        {
             title = prop.title();
        }
        else
        {
            title = 'Proposal ID ' + pid
        }
        
        med = svg.circle(g, cx, cy, radius+1, {class: 'med', fill: med_fill, cursor: 'pointer', title: title});
        $(med).data('pid', pid);
        
        // Display proposal ID
        var txtx, texty;
        txtx = cx;
        if (cy < 30)
        {
            txty = cy + 25;
        }
        else
        {
            txty = cy - 20;
        }
        svg.text(g, txtx, txty, pid); 
        
        $(med).on( "click", function(e) {
            console.log('click on median...');
            //alert('Median clicked - PID ' + $(this).data('pid')); // huh
            $('.med').attr('fill', med_fill);
            
            $(this).attr('fill', med_selected_fill_color);
            showProposalVotes(this, svg, threshold, coords['voters']);
            /*
            var evt = new jQuery.Event("click");
            evt.pageX = e.pageX;
            evt.pageY = e.pageY;
            */
            //$(this).parent().siblings('#map').trigger(e);
        });
        
        // Add error triangle if defined
        if (false && coords['e_error'])
        {
{'fraction_non_alphanumeric': 0.16666666666666666, 'fraction_numerical': 0.014052651093807935, 'mean_word_length': 2.3504347826086955, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 4, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2708799', 'n_tokens_mistral': 9108, 'n_tokens_neox': 8720, 'n_words': 2062}