# Software Programming

Kunuk Nykjaer

## Percent distribution of numbers with C#

You have a list of n integers and you want to display the percentage distribution of the values.
The requirement:
You must round the numbers such the percent sum is 100 and the percent values must be integer values.
The percent values must be fair distributed according to the values.

How would you do it?
If you calculate the distribution and round the numbers the sum might not be equal to 100.

Here is one way to do it.

program.cs (algorithm)

```
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

class Program
{
const string DrawFilePath = @"C:\temp\";

static void Main(string[] args)
{
// Insert test data here
var list = new List<int> { 30, 70, 10, 90 };

var result = GetValuesMapped(list);
GenerateJavascriptDrawFile(result);

Console.WriteLine("Open canvas.html to watch the result\npress a key to exit ..");
}

static List<Data> GetValuesMapped(List<int> list)
{
const int MAPTO = 100; // 100 %
const int min = 0;
const int max = 10000;

var mapped = new List<MapData>();
var noresult = new Data[list.Count].ToList();
double sumList = list.Sum();

// No value
if (sumList <= 0)
{
return noresult;
}

// Sanitize data
for (int i = 0; i < list.Count; i++)
{
if (list[i] < min) { list[i] = min; }
if (list[i] > max) { list[i] = max; }
}

// Map data
int id = 0;
foreach (var i in list)
{
var percent = Map(i, 0, sumList, 0, MAPTO);
{
Id = id++,
Modulus = percent - Math.Truncate(percent),
Divisor = (int)percent
});
}

// Iterate increment by 1 until sum is correct

// sort by decimal values
var sorted = mapped.OrderByDescending(i => i.Modulus).ToList();
int sumFloor = mapped.Sum(i => i.Divisor);

int addsNeeded = MAPTO - sumFloor;

for (int i = 0; i < addsNeeded; i++)
{
sorted[i % sorted.Count].Divisor++;
}

// Sort back by id
var ordered = sorted.OrderBy(i => i.Id).ToList();

// Populate data
var result = list.Select((t, i) =>
new Data { Value = t, Percent = ordered[i].Divisor }).ToList();

return result;
}

internal class MapData
{
public int Id { get; set; }
public double Modulus { get; set; }
public int Divisor { get; set; }
}

internal class Data
{
public int Value { get; set; }
public int Percent { get; set; }
}

/// <summary>
/// Value x in range [a;b] is mapped to a new value in range [c;d]
/// </summary>
static double Map(double x, double a, double b, double c, double d)
{
var r = (x - a) / (b - a) * (d - c) + c;
return r;
}

internal static class FileUtil
{
public static void WriteFile(string data, FileInfo fileInfo)
{
try
{
using (StreamWriter streamWriter = File.CreateText(fileInfo.FullName))
{
streamWriter.Write(data);
}
}
catch
{
throw;
}
}
}

// Create canvas data in draw.js file
static void GenerateJavascriptDrawFile(List<Data> list)
{
var sb = new StringBuilder("function drawData(ctx) {\n");

const int xbeg = 30; // start x coord
const int ybeg = 100;
const int lenMultiply = 5; // percentage length display
const int yline = 40; // next line offset
const int txtX = 50; // text display offset
const int txtY = 4;
const int colorMin = 20; // min color range
const int colorMax = 200;
const int topY = -70;
const string textCount = "count";
const string textPercent = "%";

if (list != null && list.Count > 0)
{
// draw top
sb.Append("\t// top\n");
sb.AppendFormat("\tctx.fillText('{0}{1}', {2}, {3});\n",
100, textPercent, xbeg, topY + ybeg - txtY);
sb.AppendFormat("\tdrawLine({0}, {1}, 'rgb(0,0,0)', {2}, ctx);\n",
xbeg, ybeg + topY, (100 * lenMultiply) + 1);

// draw lines
var rand = new Random();
sb.Append("\n\t// lines\n");
for (int i = 0; i < list.Count(); i++)
{
var color = string.Format("'rgb({0},{1},{2})'",
rand.Next(colorMin, colorMax), rand.Next(colorMin, colorMax), rand.Next(colorMin, colorMax));
sb.AppendFormat("\tdrawLine({0}, {1}, {2}, {3}, ctx);\n",
xbeg, (ybeg + i * yline), color, (list[i].Percent * lenMultiply) + 1);
}

// draw text
sb.Append("\n\t// text\n");
sb.Append("\tctx.fillStyle = 'rgb(0,0,0)';\n");
for (int i = 0; i < list.Count(); i++)
{
sb.AppendFormat("\tctx.fillText('{0}{1}', {2}, {3});\n",
list[i].Percent, textPercent, xbeg, i * yline + ybeg - txtY);
sb.AppendFormat("\tctx.fillText('{0}: {1}', {2}, {3});\n",
textCount, list[i].Value, xbeg + txtX, i * yline + ybeg - txtY);
}
}

sb.Append("}");
var path = new FileInfo(string.Concat(DrawFilePath, "draw.js"));
FileUtil.WriteFile(sb.ToString(), path);
}
}
```

Put both the html and javascript file in the c:\temp folder.
The C# program creates a new draw.js file in the c:\temp folder.
Open the html file with a modern browser that support canvas, like Firefox, Chrome or Internet Explorer 10+.

canvas.html (visualization)

```
<html>
<title>Canvas</title>
<script type="text/javascript" src="draw.js?v=1"></script>
<script type="text/javascript">
function drawLine(x, y, color, len, ctx) {
ctx.lineWidth = 2;
ctx.strokeStyle = color;
ctx.strokeRect(x, y, len, 2);
}

var canvas = document.getElementById('canvas');
if (canvas != null && canvas != undefined && canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.strokeStyle = "black";
ctx.font = "10pt Arial";
drawData(ctx);
}
}
</script>
<style type="text/css">
body
{
margin-left: 10px;
margin-top: 10px;
}
canvas
{
border: 1px solid red;
}
</style>
<body>
<canvas id="canvas" width="600" height="400">
<p>
Try Firefox, Chrome, Internet Explorer 10+ or an another modern browser.
</p>
</canvas>
</body>
</html>
```

The javascript file will be overwritten when you run the C# code.
draw.js (data)

```
function drawData(ctx) {
// top
ctx.fillText('100%', 30, 26);
drawLine(30, 30, 'rgb(0,0,0)', 501, ctx);

// lines
drawLine(30, 100, 'rgb(101,78,132)', 76, ctx);
drawLine(30, 140, 'rgb(32,101,168)', 176, ctx);
drawLine(30, 180, 'rgb(139,151,71)', 26, ctx);
drawLine(30, 220, 'rgb(163,73,143)', 226, ctx);

// text
ctx.fillStyle = 'rgb(0,0,0)';
ctx.fillText('15%', 30, 96);
ctx.fillText('count: 30', 80, 96);
ctx.fillText('35%', 30, 136);
ctx.fillText('count: 70', 80, 136);
ctx.fillText('5%', 30, 176);
ctx.fillText('count: 10', 80, 176);
ctx.fillText('45%', 30, 216);
ctx.fillText('count: 90', 80, 216);
}
```

For the list = 30, 70, 10, 90
gives this result: