import controlP5.*; // TO-DO: // 5) create a large and small version of the applet // 6) publish it! // NOTES // York South-Weston, Toronto Centre-Rosedale and York West all needed to have there NON values added manually. // relevant to data quality? // Manually added all vote data for Ottawa-OrlŽans (missed because of french character I assume). // vote and riding data for 2003 Ontario elections, previously pulled in from MySQL db. // TO-DO: some of this data, specifically total voters and no votes, is out by a few hundred compared to http://www2.elections.on.ca/results/2003_results/stat_summary.jsp?e_code=38&rec=0&flag=E&layout=G int[] NDPVotes = { 9459, 21239, 5262, 4159, 3832, 8513, 7243, 3058, 2540, 6274, 4351, 4063, 12614, 3400, 8952, 3516, 2544, 5587, 12017, 4973, 15666, 5514, 6781, 6084, 4523, 2469, 12051, 11414, 7403, 3606, 3828, 4962, 16567, 2613, 2858, 13547, 11362, 2778, 4306, 6507, 4099, 5318, 6275, 3838, 9796, 3377, 5092, 3944, 6740, 6482, 11379, 2209, 3653, 2246, 6688, 5032, 5515, 4999, 2616, 6582, 4548, 5741, 14941, 9112, 18253, 19268, 3084, 7383, 3494, 6247, 3954, 10433, 2479, 5103, 7884, 2893, 5250, 5666, 13468, 9035, 4827, 4703, 5641, 4029, 4931, 4720, 3237, 3950, 4286, 2679, 23289, 5419, 6745, 1639, 3970, 4196, 3148, 3554, 5155, 4464, 5210, 3690, 4697 }; int[] libVotes = { 14520, 10070, 24236, 14881, 19654, 16559, 15586, 21327, 23488, 18590, 23743, 24914, 20559, 22070, 19680, 16727, 28956, 28112, 23524, 19879, 6746, 28877, 18280, 22456, 18533, 17667, 13920, 20212, 25581, 17211, 20878, 18904, 13759, 18003, 22428, 9383, 22295, 25300, 24647, 22188, 23127, 16135, 23008, 13332, 24626, 22937, 18629, 25319, 24887, 18179, 20050, 23026, 21698, 23976, 17501, 17505, 19713, 24631, 21419, 17735, 21938, 18499, 12373, 23872, 12246, 12927, 21823, 21993, 18808, 19932, 16102, 19692, 16686, 28926, 17171, 23022, 21798, 23045, 15600, 16015, 16661, 17017, 21998, 21054, 19306, 17151, 18466, 16290, 21548, 27253, 12526, 24751, 23607, 19558, 17344, 27903, 14859, 23466, 22593, 30126, 20382, 24970, 36928 }; int[] conVotes = { 5168, 8157, 13618, 23338, 21506, 19996, 1977, 12027, 17394, 23814, 12402, 13149, 11234, 17610, 14524, 6978, 10921, 33610, 8637, 16594, 3343, 9640, 16120, 23957, 15060, 21443, 11777, 13460, 15463, 16977, 31662, 15353, 4804, 14978, 18991, 14566, 11217, 20762, 16413, 10878, 20277, 18656, 6436, 18776, 18418, 12800, 19274, 12932, 11203, 11852, 2674, 11337, 11686, 9468, 11826, 26114, 23393, 5068, 20623, 5365, 2912, 6330, 2527, 9968, 6562, 4985, 19957, 4187, 7862, 4930, 2330, 4162, 13832, 26414, 24297, 11586, 14323, 18141, 8185, 4033, 15656, 15680, 31529, 24517, 15549, 20109, 15846, 20348, 13709, 21257, 10336, 19517, 20735, 13948, 22550, 20406, 29222, 29641, 27240, 32647, 17816, 23960, 21744 }; int[] grnVotes = { 680, 1995, 1014, 769, 1086, 983, 907, 558, 1239, 1183, 1236, 673, 998, 1584, 708, 503, 1471, 1295, 494, 934, 305, 3137, 1728, 1774, 1133, 1799, 568, 780, 805, 949, 2200, 1124, 479, 528, 0, 636, 3821, 1402, 1741, 1876, 1309, 838, 2758, 2277, 1605, 628, 671, 1167, 2266, 1414, 441, 566, 642, 1326, 689, 875, 1540, 1009, 705, 762, 882, 489, 219, 1739, 1368, 2362, 933, 1233, 1496, 794, 437, 1315, 666, 811, 956, 1069, 668, 903, 727, 563, 820, 1201, 1278, 1854, 1176, 1088, 776, 713, 1311, 824, 768, 898, 3917, 2098, 1203, 1395, 3161, 2564, 1375, 1821, 1839, 1946, 2412 }; int[] othVotes = { 0, 0, 295, 1672, 523, 1001, 790, 579, 0, 707, 0, 671, 0, 0, 705, 3265, 0, 1123, 748, 1029, 0, 735, 0, 1344, 1833, 649, 493, 674, 460, 555, 0, 0, 0, 0, 751, 901, 738, 0, 562, 0, 353, 1399, 1309, 484, 592, 229, 0, 714, 354, 316, 606, 550, 3995, 536, 586, 1212, 554, 0, 304, 0, 0, 0, 0, 542, 290, 1012, 669, 386, 0, 475, 408, 253, 614, 1388, 936, 281, 285, 727, 1053, 1062, 356, 1241, 441, 497, 1371, 548, 588, 666, 673, 697, 0, 0, 914, 968, 978, 989, 1202, 1275, 0, 0, 0, 0, 0 }; int[] noVotes = { 21916, 33278, 34936, 26568, 29334, 40705, 27927, 30574, 30144, 36291, 30606, 28753, 39984, 27044, 30473, 34133, 32587, 47240, 31835, 22018, 27763, 40512, 38054, 38764, 27896, 26993, 35470, 36152, 33314, 29944, 35713, 30209, 22128, 21686, 27421, 37120, 39716, 29135, 33649, 40528, 30139, 28175, 32946, 25865, 32798, 30065, 24730, 34244, 33086, 26373, 22508, 33819, 32325, 40855, 30907, 33591, 32789, 28323, 33588, 24450, 24303, 19880, 22519, 40298, 30826, 37783, 37143, 45425, 32572, 31815, 29959, 39466, 32669, 61120, 30026, 31189, 29338, 27978, 31799, 35346, 38200, 27071, 48023, 37379, 43170, 30040, 37863, 27159, 29411, 45328, 31176, 31199, 37385, 30557, 37461, 45833, 36760, 38154, 37168, 58815, 29924, 37128, 47693 }; int[] totalVotes = { 29827, 41461, 44425, 44819, 46601, 47052, 26503, 37549, 44661, 50568, 41732, 43470, 45405, 44664, 44569, 30989, 43892, 69727, 45420, 43409, 26060, 47903, 42909, 55615, 41082, 44027, 38809, 46540, 49712, 39298, 58568, 40343, 35609, 36122, 45028, 39033, 49433, 50242, 47669, 41449, 49165, 42346, 39786, 38707, 55037, 39971, 43666, 44076, 45450, 38243, 35150, 37688, 41674, 37552, 37290, 50738, 50715, 35707, 45667, 30444, 30280, 31059, 30060, 45233, 38719, 40554, 46466, 35182, 31660, 32378, 23231, 35855, 34277, 62642, 51244, 38851, 42324, 48482, 39033, 30708, 38320, 39842, 60887, 51951, 42333, 43616, 38913, 41967, 41527, 52710, 46919, 50585, 55918, 38211, 46045, 54889, 51592, 60500, 56363, 69058, 45247, 54566, 65781 }; int[] totalRegisteredVoters = { 51743, 74739, 79361, 71387, 75935, 87757, 54430, 68123, 74805, 86859, 72338, 72223, 85389, 71708, 75042, 65122, 76479, 116967, 77255, 65427, 53823, 88415, 80963, 94379, 68978, 71020, 74279, 82692, 83026, 69242, 94281, 70552, 57737, 57808, 72449, 76153, 89149, 79377, 81318, 81977, 79304, 70521, 72732, 64572, 87835, 70036, 68396, 78320, 78536, 64616, 57658, 71507, 73999, 78407, 68197, 84329, 83504, 64030, 79255, 54894, 54583, 50939, 52579, 85531, 69545, 78337, 83609, 80607, 64232, 64193, 53190, 75321, 66946, 123762, 81270, 70040, 71662, 76460, 70832, 66054, 76520, 66913, 108910, 89330, 85503, 73656, 76776, 69126, 70938, 98038, 78095, 81784, 93303, 68768, 83506, 100722, 88352, 98654, 93531, 127873, 75171, 91694, 113474 }; String[] ridingNames = { "Algoma-Manitoulin", "Beaches-East York", "Brant", "Bruce-Grey-Owen Sound", "Burlington", "Cambridge", "Davenport", "Don Valley East", "Don Valley West", "Durham", "Eglinton-Lawrence", "Elgin-Middlesex-London", "Essex", "Etobicoke Centre", "Etobicoke-Lakeshore", "Etobicoke North", "Glengarry-Prescott-Russell", "Halton", "Hamilton Mountain", "Huron-Bruce", "Kenora-Rainy River", "Kingston and the Islands", "Kitchener Centre", "Kitchener-Waterloo", "Lambton-Kent-Middlesex", "Leeds-Grenville", "London-Fanshawe", "London North Centre", "London West", "Mississauga South", "Nepean-Carleton", "Niagara Falls", "Nickel Belt", "Nipissing", "Oakville", "Oshawa", "Ottawa Centre", "Ottawa-OrlŽans", "Ottawa South", "Ottawa-Vanier", "Ottawa West-Nepean", "Oxford", "Parkdale-High Park", "Parry Sound-Muskoka", "Peterborough", "Prince Edward-Hastings", "Renfrew-Nipissing-Pembroke", "St. Catharines", "St. Paul's", "Sarnia-Lambton", "Sault Ste. Marie", "Scarborough-Agincourt", "Scarborough Centre", "Scarborough-Rouge River", "Scarborough Southwest", "Simcoe-Grey", "Simcoe North", "Sudbury", "Thornhill", "Thunder Bay-Atikokan", "Thunder Bay-Superior North", "Timiskaming-Cochrane", "Timmins-James Bay", "Toronto Centre-Rosedale", "Toronto-Danforth", "Trinity-Spadina", "Willowdale", "Windsor West", "York Centre", "York South-Weston", "York West", "Windsor-St. Clair", "Mississauga East", "Brampton West-Mississauga", "Haliburton-Victoria-Brock", "Chatham-Kent Essex", "Scarborough East", "Ancaster-Dundas-Flamborough-Aldershot", "Hamilton West", "Hamilton East", "Brampton Centre", "Perth-Middlesex", "Barrie-Simcoe-Bradford", "York North ", "Bramalea-Gore-Malton-Springdale", "Haldimand-Norfolk-Brant", "Mississauga Centre", "Erie-Lincoln", "Hastings-Frontenac-Lennox and Addington", "Markham", "Niagara Centre ", "Stoney Creek", "Guelph-Wellington", "Stormont-Dundas-Charlottenburgh", "Waterloo-Wellington", "Mississauga West", "Dufferin-Peel-Wellington-Grey", "Lanark-Carleton", "Whitby-Ajax", "Oak Ridges", "Northumberland", "Pickering-Ajax-Uxbridge", "Vaughan-King-Aurora" }; // arrays used in sorting data int[] tmpDataToSortBy = new int[103]; String[] tmpDataToSortByStrings = new String[103]; int[] sortOrder = new int[103]; int centreX, centreY, spokeThickness, votesPerPx, loserAlpha, barSpacing, numberOfRidings, i, centreDiameter, j, numberOfVotes, l, numberOfParties, otherPartiesVotes; boolean winnerBar, reDraw = true, alphaSort, showRidingNames = true, showGUI = true, showTitle = true, showKey = true; color NDPColor, libColor, conColor, grnColor, othColor, noColor, barLength, totalBarLength, longestTotalBar; String partyName; PFont font; int[] votes = new int[6]; color[] partyColors = new color[6]; int[] votesSorted = new int[6]; float degreesPerRiding; ControlP5 controlP5; PImage pressAnyKey; PImage sortRidingsBy; PImage chartKey; int viewPortWidth = 1000; int viewPortHeight = 1000; int fontSize = 12; // position and dimension variables for the GUI panel int guiX = 10; int guiY = 10; int guiWidth = 215; int guiHeight = 270; int previousSortType = 1, sortType = 5; // store the ID of the current and previous sort types, start them different for correct behaviour. void setup() { size(viewPortWidth, viewPortHeight); size(viewPortWidth, viewPortHeight); size(viewPortWidth, viewPortHeight); size(viewPortWidth, viewPortHeight); background(0); strokeCap(SQUARE); centreY = viewPortWidth / 2; centreX = viewPortHeight / 2; spokeThickness = 5; votesPerPx = 400; loserAlpha = 120; barSpacing = 1; // gap between bar secsions numberOfRidings = 103; centreDiameter = 175; // diameter of the 'hole' at the centre of the chart numberOfParties = 6; // how many parties are we tracking (counts other and non-voting as one each). degreesPerRiding = float(360) / float(numberOfRidings); pressAnyKey = loadImage("press_any_key.gif"); sortRidingsBy = loadImage("sort_ridings_by.gif"); chartKey = loadImage("key.gif"); // add the UI controlP5 = new ControlP5(this); controlP5.setColorBackground(color(122)); controlP5.setColorForeground(color(100)); controlP5.setColorActive(color(200)); controlP5.setColorValue (color(0)); Radio r = controlP5.addRadio("radio",20,32); r.add("Number of registered voters",0); r.add("Number of Conservative votes",1); r.add("Number of Liberal votes",2); r.add("Number of NDP votes",3); r.add("Number of Green votes",4); r.add("Number of independent and other votes",5); r.add("Number of non-votes",6); r.add("Alphabetically",7); Slider zoom = controlP5.addSlider("zoom", 1, 9, 11 - (votesPerPx / 100), 20, 150, 60, 20); Slider fontSizeSlider = controlP5.addSlider("fontSize", 10, 16, fontSize, 20, 175, 60, 20); fontSizeSlider.setLabel("Text size"); Toggle ridingTextToggle = controlP5.addToggle("ridingTextToggle", true, 20, 200, 10, 10); ridingTextToggle.setLabel("Display riding names"); Toggle titleTextToggle = controlP5.addToggle("titleTextToggle", true, 20, 225, 10, 10); titleTextToggle.setLabel("Display chart title"); Toggle titleKeyToggle = controlP5.addToggle("titleKeyToggle", true, 20, 250, 10, 10); titleKeyToggle.setLabel("Display key"); font = createFont("CourierNewPSMT", 12, true); NDPColor = color(255, 119, 0); libColor = color(153, 0, 0); conColor = color(0, 85, 166); grnColor = color(98, 125, 42); othColor = color(255); noColor = color(55); // this here array copy defines which of the data arrays the spokes will be arranged by in initial display. arraycopy(totalRegisteredVoters, tmpDataToSortBy); alphaSort = false; sortType = 0; } void resetSortOrder() { int i = 0; while (i < sortOrder.length) { sortOrder[i] = i; // fill the sortOrder array with sequent ial numeric values. i++; } } void draw() { // avoid excess work by only calculating/outputing the radial image when the reDraw flag is set. if (reDraw) { background(color(0)); outputRadialChart(); if (showGUI) { // draw background for the GUI fill(30, 200); noStroke(); rect(guiX, guiY, guiWidth, guiHeight); // hacks to display messages in the same font as UI (I don't have it), using an image. image(sortRidingsBy, guiX + 8, guiY + 8); rect(guiX, guiY + guiHeight + 5, guiWidth, 20); image(pressAnyKey, guiX + 8, guiY + guiHeight + 8); reDraw = false; } if (showTitle) { String chartTitle = "2003 Ontario provincial election results"; float titleWidth = textWidth(chartTitle); noStroke(); fill(30, 225); rect(width - titleWidth - 25, guiY, titleWidth + 15, 20); fill(255); textAlign(RIGHT); text(chartTitle, width - 18, guiY + 14); textAlign(LEFT); } if (showKey) { noStroke(); fill(30, 225); rect(width - 137, height - 130, 125, 120); image(chartKey, width - 125, height - 120); // draw the scale line stroke(255); strokeWeight(1); line(width - 109, height - 32, width - 109 + ( 10000 / votesPerPx ), height - 32); line(width - 109, height - 32, width - 109, height - 32 - 3); line(width - 109 + ( 10000 / votesPerPx ), height - 32, width - 109 + ( 10000 / votesPerPx ), height - 32 - 3); } } } void outputRadialChart() { if (sortType != previousSortType) { // do the bubble sort, which involves comparing pairs of neighbouring values and transposing them if they're not in the sort order. if (alphaSort) { // do a sort based on String values. for(i = 0; i < tmpDataToSortByStrings.length; i++) { for(int j = 1; j < tmpDataToSortByStrings.length - i; j++) { if(tmpDataToSortByStrings[j-1].compareTo(tmpDataToSortByStrings[j]) > 0) { // sort descending, use > if you want ascending order. String dataTmp = tmpDataToSortByStrings[j-1]; tmpDataToSortByStrings[j-1] = tmpDataToSortByStrings[j]; tmpDataToSortByStrings[j] = dataTmp; int orderTemp = sortOrder[j-1]; sortOrder[j-1] = sortOrder[j]; sortOrder[j] = orderTemp; } } } } else { // do a sort based on int values. for(i = 0; i < tmpDataToSortBy.length; i++) { for(int j = 1; j < tmpDataToSortBy.length - i; j++) { if(tmpDataToSortBy[j-1] < tmpDataToSortBy[j]) { // sort descending, use > if you want ascending order. int dataTmp = tmpDataToSortBy[j-1]; tmpDataToSortBy[j-1] = tmpDataToSortBy[j]; tmpDataToSortBy[j] = dataTmp; int orderTemp = sortOrder[j-1]; sortOrder[j-1] = sortOrder[j]; sortOrder[j] = orderTemp; } } } } } previousSortType = sortType; textFont(font, fontSize); strokeWeight(spokeThickness); smooth(); i = 0; int nextIndex = 0; longestTotalBar = 0; while (i < sortOrder.length) { nextIndex = sortOrder[i]; votes[0] = NDPVotes[nextIndex]; votes[1] = libVotes[nextIndex]; votes[2] = conVotes[nextIndex]; votes[3] = grnVotes[nextIndex]; votes[4] = othVotes[nextIndex]; partyColors[0] = NDPColor; partyColors[1] = libColor; partyColors[2] = conColor; partyColors[3] = grnColor; partyColors[4] = othColor; votesSorted = votes; votesSorted = reverse(sort(votesSorted)); j = 0; totalBarLength = 0; winnerBar = true; // set the winner flag // record the transform/rotate instructions, for a clean slate later. pushMatrix(); translate(centreX, centreY); // translate shapes out to the centre of the canvas. rotate(radians(degreesPerRiding * i)); // rotate, to create the radial effect. // output the vote bars in size order, ie winner inside. while (j < votesSorted.length) { numberOfVotes = votesSorted[j]; // number of votes for the next candidate in sequence l = 0; while (l < votesSorted.length) { if (votes[l] == numberOfVotes) { stroke(partyColors[l]); if (winnerBar) { barLength = (votes[l] / votesPerPx) + (centreDiameter / 2); // take into account central black disc in bar length. winnerBar = false; // that was the winner, switch the flag off now } else { barLength = (votes[l] / votesPerPx); } line(0, 0 - totalBarLength, 0, 0 - barLength - totalBarLength); if (barLength > 0) { totalBarLength = totalBarLength + barLength + barSpacing; } } l++; } j++; } // output the non-votes last, so they're always at the outside of the spoke. stroke(noColor); barLength = (noVotes[nextIndex] / votesPerPx); line(0, 0 - totalBarLength, 0, 0 - barLength - totalBarLength); totalBarLength = totalBarLength + barLength; // keep an eye out for a new record breaking bar length if (totalBarLength > longestTotalBar) { longestTotalBar = totalBarLength; } // undo the transform/rotate instructions, for a clean slate. popMatrix(); i++; } if(showRidingNames) { i = 0; while (i < NDPVotes.length) { nextIndex = sortOrder[i]; pushMatrix(); // output the text at the end of each ray. translate(centreX, centreY); rotate(radians(degreesPerRiding * i)); // rotate, to create the radial effect. translate(0 + 5, 0 - longestTotalBar - 10); // translate(0 + 5, 0 - 10 - ); rotate(radians(270)); //translate(centreX, centreY); fill(150); text(ridingNames[nextIndex], 0, 0); i++; popMatrix(); } } // draw a circle at the hub of the spokes, to tidy display. fill(0); stroke(122); strokeWeight(1); ellipse(centreX, centreY, centreDiameter, centreDiameter); } // define what happens on UI radio button selection void radio(int theID) { switch(theID) { case(0): arraycopy(totalRegisteredVoters, tmpDataToSortBy); resetSortOrder(); reDraw = true; alphaSort = false; sortType = 0; break; case(1): arraycopy(conVotes, tmpDataToSortBy); resetSortOrder(); reDraw = true; alphaSort = false; sortType = 1; break; case(2): arraycopy(libVotes, tmpDataToSortBy); resetSortOrder(); reDraw = true; alphaSort = false; sortType = 2; break; case(3): arraycopy(NDPVotes, tmpDataToSortBy); resetSortOrder(); reDraw = true; alphaSort = false; sortType = 3; break; case(4): arraycopy(grnVotes, tmpDataToSortBy); resetSortOrder(); reDraw = true; alphaSort = false; sortType = 4; break; case(5): arraycopy(othVotes, tmpDataToSortBy); resetSortOrder(); reDraw = true; alphaSort = false; sortType = 5; break; case(6): arraycopy(noVotes, tmpDataToSortBy); resetSortOrder(); reDraw = true; alphaSort = false; sortType = 6; break; case(7): arraycopy(ridingNames, tmpDataToSortByStrings); resetSortOrder(); reDraw = true; alphaSort = true; sortType = 7; break; } } // handles the response to zoom slider changes. void zoom(int theVal) { votesPerPx = (11 - theVal) * 100; reDraw = true; } // handles the response to zoom slider changes. void fontSize(int theVal) { fontSize = int(theVal); reDraw = true; } // handles the response to show/hide riding names toggle. void ridingTextToggle(boolean theFlag) { if (theFlag) { showRidingNames = true; } else { showRidingNames = false; } reDraw = true; } // handles the response to show/hide riding names toggle. void titleTextToggle(boolean theFlag) { if (theFlag) { showTitle = true; } else { showTitle = false; } reDraw = true; } // handles the response to show/hide key toggle. void titleKeyToggle(boolean theFlag) { if (theFlag) { showKey = true; } else { showKey = false; } reDraw = true; } // listen for a keypress and switch GUI on/off void keyPressed() { if (showGUI) { controlP5.hide(); } else { controlP5.show(); } reDraw = true; showGUI = !showGUI; }