Позначки

, ,

Раніше я вже писав про статистику збору коштів для Громадського Радіо, де викладав і програмний код для отримання данних з сайту biggggidea.com і розрахунку статистику по будь-якому проекту звідти.

Сьогодні на черзі Громадське Телебачення – той проект, через який власне і з’явилась в мене ідея зібрати статистику внесків на biggggidea.com, адже було справді цікаво взнати, наскільки популярна і взагалі можлива в Україні ідея створення незалежних ЗМІ та взагалі незалежних ні від кого крім громадськості проектів. Спробуємо зробити висновки, але для початку – цифри та графіки:

Статистика збору коштів для Громадського Телебачення (hromadske.tv)

Сторінка проекту на biggggidea.com – http://biggggidea.com/project/392/

За даними сайту, зібрано 1 241 111 грн з необхідних 1 000 000.

Всього внесків (на основі історії внесків з сайту): 3908
Середній розмір внеску: 270.79 грн.
Всього внесків прихованої суми: 579
Середній розмір внеску з прихованою сумою: 315.80 грн. *
(* – розрахований як різниця між сумою відкритих внесків та загальною сумою вказаною на сторінці проекту, розділена на загальну кількість внесків з прихованою сумою)

Найперший внесок: Омельченко Поліна, 2013-12-26 11:04:18 = 300
Останній внесок: Юрий, 2014-04-05 23:59:30 = 575

Найбільші внески, грн:
Андрій Стельмащук, 2014-01-07 10:35:29 = 20000
V.I.Tech, 2014-01-27 17:25:11 = 16000
., 2014-01-23 00:00:50 = 12000
Антон, 2014-01-23 10:23:57 = 11488
Сергій Єлов, 2014-01-27 14:29:36 = 10200
Nataliya Dombrovska, 2014-01-25 10:51:32 = 10200
Oksana Motyka, 2014-01-09 14:49:42 = 10001
Марина Савкова, 2014-02-27 13:22:40 = 10000
Алексей Рогожинский, 2014-02-21 17:49:27 = 10000
Oksana Markarova, 2014-03-11 12:02:57 = 10000
Кондратюк Світлана, 2014-01-17 11:38:12 = 10000
Nazaryan Hanna, 2014-03-18 14:23:28 = 10000
Ляпун Александр, 2014-01-21 14:45:23 = 10000
Оксана Харченко, 2014-02-26 09:52:17 = 10000
Павло Шатохін, 2014-02-10 19:29:46 = 10000
Компанія “Реактор”, 2014-02-04 02:06:30 = 10000
Редчиць Ігор, 2014-01-25 15:49:30 = 6000
Ігор Блистів, 2013-12-28 11:45:31 = 6000
Евгений, 2013-12-29 10:20:49 = 6000
Андрій Дилин, 2014-03-04 22:39:56 = 5020
Sergiy Mykhayliv, 2014-01-09 22:16:42 = 5001
Мельник Юрий, 2013-12-28 19:46:28 = 5001
Максим Гринів, 2013-12-26 17:00:06 = 5001
Max Ischenko, 2013-12-26 20:42:24 = 5000
Дєєв Євген Валерійович, 2014-01-25 20:39:55 = 5000
Edward Burns, 2014-01-21 20:28:11 = 5000
Mykola Makhin, 2014-01-28 13:33:58 = 5000
Анастасія Ніцой, 2014-01-24 14:18:46 = 5000

В попередньому пості (про Громадське Радіо) була така примітка, яка доречна і тут: Доречі, якщо хто не знає – Max Ischenko це лідер проекту Developers.org.ua AKA dou.ua. Айтішники вони скрізь (-:


Графіки (клікніть/клацніть по картинці щоб збільшити)

hromadske.tv - внески по розмірам

hromadske.tv - внески по часу

hromadske.tv - внески по тижням

Hromadske.TV - дата досягнення мети

 


Висновки

Є декілька як позитивних так і негативних моментів, які хотілось би відзначити.

З одного боку, не дивлячись на значну популярність та актуальність Громадського ТБ (про що свідчить хоча б та ж ретрансляція на першому національному), досягти поставленої мети в 1 мільйон гривень проект зміг лише 13 березня, тобто десь через два з половиною місяці зборів з відведених трьох з третиною – точніше на 77-й день з 100 днів, відведених на збори.

З іншого боку, після досягнення мети потік внесків не припинився, останній внесок був внесений всього лише за 30 секунд до кінця зборів!

Також один мільйон гривень на рік для проекту на кшталт Громадське ТБ це насправді дуже і дуже мало (а також не слід забувати що переважно певний відсоток комісії отримує сам сайт який надає засоби збору – і biggggidea.com бере немалі 10%). З інтервью з працівниками Громадського в ЗМІ виходить що наразі “в режимі стартапу” участь в роботі Громадського ТБ приймають 29 людей, які переважно працюють паралельно на інших роботах. Якщо ж уявити що Громадське ТБ має встати повністью на власні ноги і тримати штат принаймні в 20 людей, один тільки зарплатний фонд при середній зарплатні в “чистих” 5 тисяч гривень (що для столиці є зовсім не багато) становитиме: 5000 грн. x 1.5 для врахування податків x 12 місяців x 20 працівників = 1 800 000, тобто майже 2 мільйони гривень. Тепер згадаєм про оренду приміщення, оплату прийстойного інтернет каналу для можливості ведення онлайнт трасляції з одночасним прийманням деількох потоків від журналістів “в полі” (тобто + вартість 3G інтернету для кожного журналіста, котрий передає потокове відео “з полів”), вартість устаткування (а її немало – камери, освітлювачі, планшети для трансляції котрі періодично крадуть тітушки, та й ті ж самі меблі в кінці кінців – раз на рік треба і пару крісел поміняти мабуть – та і т.п.) і так далі (мабуть я не знаю і про половину речей котрі мають бути в кошторисі подібного проекту). В результаті вимальовується дещо невтішна картина: для пристойної роботи незалежних журналістів в котрих ніколи б не виникало бажання подавати матеріал дещо тенденційно за “скромний внесок” від якого-небудь місцевого олігарха і/або запихати в ефіри якомога більше всеможливої реклами – потрібно десь так в 4-5 разів більше грошей. Тому на мій погляд поява реклами на Громадському – лише питання часу. Врятувати від цього зможуть хіба що щедрі меценатські пожертви (які також можуть кинути тінь на суть Громадського), державна підтримка (аналогічно) або самообмеження проекту в рамках невеликої команди неповної зайнятості без перспектив значного росту. Нажаль, найкращій варіант для росту – платна підписка – не можливий на даний момент стану справ в вітчизняному інтернет-просторі (що є причина цьому – нерозвинене законодавство, низька “інтернетизація” країни, чи і те і інше – не візьмусь судити).

Тим не менше, можливо Громадське насправді зібрало відчутно більше грошей ніж мільйон з biggggidea – адже вони збирали гроші також через інші канали (прямі пожертви через їх сайт і т.п.) – чим дещо зіпсували мені збір статистики (-: Та і на самому biggidea було зібрано майже на чверть більше коштів ніж заплановано (і хоча “чверть” не звучить вражаюче – в абсолютних числах це майже 250 тисяч гривень – до прикладу це в 3 рази більше ніж загальна сума внесків для Громадського Радіо).

Майже 4 тисячі доброчинців це досить значна кількість як для краудфандінгових (спільнокоштових) проектів. Хоча можливо не така значна як для проектів національного масштабу. Сподіватимемося що справа в тому, що Громадське лише тільки виходить на цей масштаб.

Хоча переважають дрібні внески, і вельми значна частина внесків була в рамках від 10 до 100 гривень, все ж таки знайшлись сотні людей, які пожертвували більше (до 1000) і десятки тих, хто пожертвував значно більше (до 2000). Майже 3 десятки людей (та одна організація – львівські ІТ-шники V.I.Tech) внесли по 5 і більше тисяч гривень, аж до найбільшого внеску в 20 тисяч. Можливо таких було і більше – не можна сказати точно через те, що суми сотень пожертв були приховані. Так чи інакше, приємно що в країні хоч по трошки але з’являється розуміння того, що безоплатний сир – тільки в мишоловці, а безоплатна інформація – тільки там, де наживаються на чомусь іншому (в кращому випадку – реклама, в гіршому – пропаганда та інші маніпуляції масовою свідомістью), і тому за отримання невикривленої інформації потрібно платити, і вона є достаньо цінної щоб бути вартою оплати.

Отже, з одного боку безумовно чудовим результатом є те, що збір коштів для Громадського не тільки досяг але і помітно перевищів необхідну суму, і рік існування Громадського ТБ. З іншого, я вважаю що українці можуть кращє, і сподіваюсь що зроблять кращє. Мабуть збір коштів для майдану а згодом для армії доводить що це насправді так. Можливо що саме ці паралельні збори, які багатьма могли вважатись значно приорітетнішими, дещо занизили результат Громадьского – це теж варто врахувати. Але також варто врахувати те, що найбільший поштовх до збору коштів надають, нажаль, критичні події – такі як події на майдані в лютому, які співпали з найбільшою активністью при пожертві коштів. (Аналогічно збори на армію активізувались під час загрози – тобто дещо пізно). Лишається сподіватись що після всих цих подій ми порозумнішаєм і почнем, так би мовити, готувати дрова влітку.


Код!

Т.я. я є програміст по професії, то звичайно рахував статистику не руками. Для підрахунку статистики був написаний скріпт для GroovyShell з використанням чудової бібліотеки для малювання графіків під назвою JFreeChart (версія 1.0.17). Для повноти коду потрібні також деякі “макроси” якими я користуюсь в GroovyShell і відповідно тримаю в своєму groovysh.rc – копію якого маю на github.

Я вже публікував цей код в статті про результати Громадського Радіо, але мабуть не завадись продублювати його, з деякими дрібними додатками.

Сам код в поточному вигляді (без гарного форматування бо це лише скріпт для groovyshell а не повноцінний софт – вибачайте):

projectId = 392;
//projectId = 323;

groovy.grape.Grape.grab(group:'org.codehaus.groovy.modules.http-builder', module:'http-builder')

def httpGet(url, encoding) {
http = new groovyx.net.http.HTTPBuilder(url);
return http.get(["uri":url, contentType: groovyx.net.http.ContentType.BINARY], { resp, inputStream -> return new InputStreamReader(inputStream, encoding).text; })
}

def httpGet(url) {
http = new groovyx.net.http.HTTPBuilder(url);
return http.get(["uri":url, contentType: groovyx.net.http.ContentType.BINARY], { resp, inputStream -> return new InputStreamReader(inputStream, "UTF-8").text; })
}

def nekoParse(String content) {
nekoDomParser = new org.cyberneko.html.parsers.DOMParser();
nekoDomParser.parse(new org.xml.sax.InputSource(new ByteArrayInputStream(content.getBytes("UTF-8"))));
return nekoDomParser.getDocument();
}

def xpathnum(doc, xpathStr) {
xpath = javax.xml.xpath.XPathFactory.newInstance().newXPath();
return xpath.evaluate(xpathStr, doc, javax.xml.xpath.XPathConstants.NUMBER);
}

def xpathstr(doc, xpathStr) {
xpath = javax.xml.xpath.XPathFactory.newInstance().newXPath();
return xpath.evaluate(xpathStr, doc, javax.xml.xpath.XPathConstants.STRING);
}

def xpathbool(doc, xpathStr) {
xpath = javax.xml.xpath.XPathFactory.newInstance().newXPath();
return xpath.evaluate(xpathStr, doc, javax.xml.xpath.XPathConstants.BOOLEAN);
}

def xpathn(doc, xpathStr) {
xpath = javax.xml.xpath.XPathFactory.newInstance().newXPath();
return xpath.evaluate(xpathStr, doc, javax.xml.xpath.XPathConstants.NODE);
}

def xpathns(doc, xpathStr) {
xpath = javax.xml.xpath.XPathFactory.newInstance().newXPath();
return xpath.evaluate(xpathStr, doc, javax.xml.xpath.XPathConstants.NODESET);
}

public class Donation {
private final String name;
private final Date date;
private final long sum = 0;
public Donation(final String name, final Date date, final long sum) {
this.name=name;this.date=date;this.sum=sum;
}
public long getSum() { return sum; }
public String getName() { return name; }
public Date getDate() { return date; }

public boolean equals(Object other) {
if(other==null) return false;
if(!other instanceof Donation) return false;
Donation od = (Donation)other;
return (sum==od.sum) && this.name.equals(od.getName()) && this.date.equals(od.getDate());
}

public int hashCode() {
return this.sum*13+this.name.hashCode()+this.date.hashCode();
}
public String toString() {
return "Donation: "+name+" ["+new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)+"] = "+sum;
}
}

def getDonations(page, xpathns, xpathstr) {
dateFormat = new java.text.SimpleDateFormat("d MMMM yyyy 'р.' HH:mm:ss", new Locale("UK"));
donations = new ArrayList();
xpathns(page, "//*[@class='founds']//TR").each{
 name = xpathstr(it, ".//TD[contains(@class, 'founds-td2')]/*[not(@class='date')]").trim();
 date = dateFormat.parse(xpathstr(it, ".//TD[contains(@class, 'founds-td2')]/*[@class='date']").trim());
 sumStr = xpathstr(it, ".//TD[contains(@class, 'founds-td4')]/SPAN/text()").trim();
 sum = 0L;
 if(sumStr.length()>0) {
 sum = Long.parseLong(sumStr);
 }
 donations.add(new Donation(name, date, sum));
}
return donations;
}

dateFormat = new java.text.SimpleDateFormat("d MMMM yyyy 'р.' HH:mm:ss", new Locale("UK"));

projectUrl = "http://biggggidea.com/project/"+projectId+"/spilnokosht/";

if(true){

homePage = nekoParse(httpGet(projectUrl));

totalPages = Integer.parseInt(xpathstr(homePage.documentElement, "//*[@class='pager-list']/A[not(@class='pager-list-next')][last()]").trim())
totalSupporters = Long.parseLong(xpathstr(homePage.documentElement, "//*[@class='support-text'][.//*[@class='counter-text'][contains(text(),'Доброч')]]//*[@class='counter-value']/text()").trim());

donations = new HashSet();

currentPage = totalPages;
while(currentPage>0) {
println "Processing page "+currentPage;
page = nekoParse(httpGet(projectUrl+"?page="+currentPage));

supporters = Long.parseLong(xpathstr(page.documentElement, "//*[@class='support-text'][.//*[@class='counter-text'][contains(text(),'Доброч')]]//*[@class='counter-value']/text()").trim());

if(supporters!=totalSupporters) {
println "Supporters number changed: "+totalSupporters+" => "+supporters;
println "Going 1 page back to account for that."
totalSupporters = supporters;
currentPage++;
continue;
}

donations.addAll(getDonations(page.documentElement, xpathns, xpathstr));

currentPage--;
}

println "Processing done. Donations found: "+donations.size();

homePage = nekoParse(httpGet(projectUrl));

// Get totals
totalMoney = Long.parseLong(xpathstr(homePage.documentElement, "//*[@class='support-text'][.//*[@class='counter-text'][starts-with(text(),'Зібрано з')]]//*[@class='counter-value']/text()").trim());
totalSupporters = Long.parseLong(xpathstr(homePage.documentElement, "//*[@class='support-text'][.//*[@class='counter-text'][contains(text(),'Доброч')]]//*[@class='counter-value']/text()").trim());

//xpathns(homePage.documentElement, "//*[@class='reward']").each{ println xpathstr(it, ".//H2")+" - "+xpathstr(it, ".//SPAN[@class='virtue']"); };

donationsSum=0;
donations.each{ if(it.sum>0){donationsSum+=it.sum;}}
hiddenSum=totalMoney-donationsSum

totalHiddenDonations = donations.grep{ it.sum==0 }.size();
averageHiddenDonation = hiddenSum/totalHiddenDonations;

groups=[
"Hidden" : {it.sum==0},
"0<x<=5" : {it.sum>0 && it.sum<=5},
"5<x<=10" : {it.sum>5 && it.sum<=10},
"10<x<=25" : {it.sum>10 && it.sum<=25},
"25<x<=50" : {it.sum>25 && it.sum<=50},
"50<x<=100" : {it.sum>50 && it.sum<=100},
"100<x<=250" : {it.sum>100 && it.sum<=250},
"250<x<=500" : {it.sum>250 && it.sum<=500},
"500<x<=1000" : {it.sum>500 && it.sum<=1000},
"1000<x<=2000" : {it.sum>1000 && it.sum<=2000},
"2000<x<=3000" : {it.sum>2000 && it.sum<=3000},
"3000<x<=4000" : {it.sum>3000 && it.sum<=4000},
"4000<x<=5000" : {it.sum>4000 && it.sum<=5000},
"5000<x<=10000": {it.sum>5000 && it.sum<=10000},
"10000<x" : {it.sum>10000}
]

dfSkipTime = new java.text.SimpleDateFormat("yyyy-MM-dd");
donationsCalendar=donations.groupBy{dfSkipTime.format(it.date)};

results = new StringBuilder();

results.append("\n\n--=== Project Statistics ===--\n\nTotal donations:").append(donations.size());
results.append("\nAverage donation size:").append(donationsSum/donations.size());
results.append("\nHidden sum donations:").append(totalHiddenDonations);
results.append("\nAverage hidden sum donation:").append(String.valueOf(averageHiddenDonation));

results.append("\n\nDonations by categories:\n[Range]\t[Donations]\n");

groups.each{ results.append(it.key).append("\t").append(String.valueOf(donations.grep(it.value).size())).append("\n"); }

results.append("\nEarliest donation == ").append(donations.min{it.date}.toString());
results.append("\nLatest donation == ").append(donations.max{it.date}.toString());
results.append("\n\nDonations by dates:\n[Date]\t[Donations]\t[Sum]\n");
donationsCalendar.keySet().sort().each{ dd=donationsCalendar.get(it); results.append(it+"\t"+dd.size()+"\t"+dd.sum{it.sum}).append("\n"); }

minTop25Sum=donations.sort{-it.sum}.toList().subList(0,25).min{it.sum}.sum;
results.append("\nTop donators:\n");
donations.grep{it.sum>=minTop25Sum}.sort{-it.sum}.each{ results.append(it).append("\n"); }
}

////// Chart
new File("/Users/mvmn/_my/_prog/_dev/_lib/jfreechart-1.0.17/lib").listFiles().each{ mv_LD(it); }

def makeChart(dataset, axisname, name, hc, io) {
orientation = io ? org.jfree.chart.plot.PlotOrientation.VERTICAL : org.jfree.chart.plot.PlotOrientation.HORIZONTAL;
//chart = org.jfree.chart.ChartFactory.createLineChart(
chart = org.jfree.chart.ChartFactory.createBarChart(
null, axisname, name, dataset, orientation, false, true, false);
if(orientation == org.jfree.chart.plot.PlotOrientation.VERTICAL) {
chart.plot.domainAxis.setCategoryLabelPositions(org.jfree.chart.axis.CategoryLabelPositions.DOWN_90);
}
chart.plot.renderer.setSeriesItemLabelGenerator(0, new org.jfree.chart.labels.StandardCategoryItemLabelGenerator());
chart.plot.renderer.setSeriesItemLabelsVisible(0, true)
chart.plot.renderer.setItemMargin(0.5);
chartPanel = new org.jfree.chart.ChartPanel(chart);
if(hc!=null) {
if(orientation == org.jfree.chart.plot.PlotOrientation.VERTICAL) {
chartPanel.setMinimumDrawWidth(hc*dataset.columnCount);
chartPanel.setMaximumDrawWidth(hc*dataset.columnCount);
chartPanel.setPreferredSize(new java.awt.Dimension(hc*dataset.columnCount, (int)chartPanel.preferredSize.height));
} else {
chartPanel.setMinimumDrawHeight(hc*dataset.columnCount);
chartPanel.setMaximumDrawHeight(hc*dataset.columnCount);
chartPanel.setPreferredSize(new java.awt.Dimension((int)chartPanel.preferredSize.width, hc*dataset.columnCount));
}
}
return chartPanel;
}

if(true) {

datasetNum = new org.jfree.data.category.DefaultCategoryDataset();
datasetSum = new org.jfree.data.category.DefaultCategoryDataset();
datasetAvg = new org.jfree.data.category.DefaultCategoryDataset();

donationsCalendar.keySet().sort().each{ dd=donationsCalendar.get(it);
datasetNum.addValue(dd.size(), "Donations", it);
datasetSum.addValue(dd.sum{it.sum}, "Sum", it);
datasetAvg.addValue(dd.sum{it.sum}/dd.size(), "Avg.", it); }

chartPanelNum = makeChart(datasetNum, "День", "Кількість внесків (за цей день)", 12, false);
chartPanelSum = makeChart(datasetSum, "День", "Загальна сума внесків (за цей день), Грн.", 12, false);
chartPanelAvg = makeChart(datasetAvg, "День", "Середній розмір внеску (за цей день), Грн.", 12, false);
chartPanelSum.chart.plot.domainAxis.setVisible(false);
chartPanelAvg.chart.plot.domainAxis.setVisible(false);


panel = new javax.swing.JPanel();
panel.setLayout(new java.awt.GridLayout(1,3));
panel.add(chartPanelNum);
panel.add(chartPanelSum);
panel.add(chartPanelAvg);
panel.setPreferredSize(new java.awt.Dimension(800, 600));
frame = new javax.swing.JFrame();
frame.contentPane.setLayout(new java.awt.BorderLayout());
frame.contentPane.add(new javax.swing.JScrollPane(panel), java.awt.BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);

/////
datasetDBC = new org.jfree.data.category.DefaultCategoryDataset();
groups.each{ datasetDBC.addValue(donations.grep(it.value).size(),"К-сть внесків.",it.key) }
chartPanelDBC = makeChart(datasetDBC, "Діапазон", "К-сть внесків по розмірам", null, false);
mv_enframe(chartPanelDBC);

/////
datasetTotal = new org.jfree.data.category.DefaultCategoryDataset();
totalSum = 0;
donationsCalendar.keySet().sort().each{ dd=donationsCalendar.get(it);
datasetTotal.addValue(totalSum+=dd.sum{it.sum}, "Donations", it); }
mv_enframe(makeChart(datasetTotal, "День", "Загальна сума внесків на дату", 14, true));
}

if(true) {
dfWeeks = new java.text.SimpleDateFormat("yyyy-ww");
donationsCalendar=donations.groupBy{val=dfWeeks.format(it.date); if(val.endsWith("-01")) {val="2014-01"}; return val};

datasetNum = new org.jfree.data.category.DefaultCategoryDataset();
datasetSum = new org.jfree.data.category.DefaultCategoryDataset();
datasetAvg = new org.jfree.data.category.DefaultCategoryDataset();

donationsCalendar.keySet().sort().each{ dd=donationsCalendar.get(it);
datasetNum.addValue(dd.size(), "Donations", it);
datasetSum.addValue(dd.sum{it.sum}, "Sum", it);
datasetAvg.addValue(dd.sum{it.sum}/dd.size(), "Avg.", it); }

chartPanelNum = makeChart(datasetNum, "Тиждень", "Кількість внесків (за цей тиждень)", null, false);
chartPanelSum = makeChart(datasetSum, "Тиждень", "Загальна сума внесків (за цей тиждень), Грн.", null, false);
chartPanelAvg = makeChart(datasetAvg, "Тиждень", "Середній розмір внеску (за цей тиждень), Грн.", null, false);
chartPanelSum.chart.plot.domainAxis.setVisible(false);
chartPanelAvg.chart.plot.domainAxis.setVisible(false);


panel = new javax.swing.JPanel();
panel.setLayout(new java.awt.GridLayout(1,3));
panel.add(chartPanelNum);
panel.add(chartPanelSum);
panel.add(chartPanelAvg);
panel.setPreferredSize(new java.awt.Dimension(1000, 600));
mv_enframe(panel);
}

sum=0;db=null;donations.sort{ it.date }.each{ sum+=it.sum; if(sum>1000000) { println "Dealbreaking donation: "+it+" - "+sum; db=it; sum = 0} }; println "";
if(db!=null) {
println "Succedded at day "+(db.date - donations.min{it.date}.date) +" of "+(donations.max{it.date}.date - donations.min{it.date}.date)+" ("+db.date+")";
}

println results;

P.S.

Special bonus: статистика внесків по годинам дня по діапазонам розмірів – подивимся, коли люди сидять в Інтернеті, і чи залежить це від їх статків (-:

Donations by hourofday

Статистично підтверджено – вночі люди переважно сплять (-: Розмір внесків ніби від часу сидіння в Інтернеті не залежить. Піки користування – 17-а година дня, 22-а година вечора, дещо менший – lunch time (12-а/13-а година).

Додатковий код:

def makeDataset(refkeyset, donations, groupcloj, calccloj) {
groupedDonations = donations.groupBy{groupcloj.call(it);}
dataset = new org.jfree.data.category.DefaultCategoryDataset();
if(refkeyset!=null) {
refkeyset = new HashSet(refkeyset);
refkeyset.addAll(groupedDonations.keySet());
} else { refkeyset = groupedDonations.keySet(); }
refkeyset.sort().each{ dd=groupedDonations.get(it); if(dd==null) { dd = []}; dataset.addValue(calccloj.call(dd),"Val", it); }
return dataset;
}

if(true){
charts = [];
charts.add(makeChart(makeDataset(null, donations, { new java.text.SimpleDateFormat("HH").format(it.date); }, {it.size()}), "Год", "Кількість внесків (в цю год дня)", null, false));
refkeyset = donations.groupBy{ new java.text.SimpleDateFormat("HH").format(it.date); }.keySet();
["Hidden" : {it.sum==0},
"0<x<=250" : {it.sum>0 && it.sum<=250},
"250<x<=1000" : {it.sum>250 && it.sum<=1000},
"1000<x" : {it.sum>1000}
].each{ charts.add(makeChart(makeDataset(refkeyset, donations.grep(it.value), { new java.text.SimpleDateFormat("HH").format(it.date); }, {it.size()}), "Год", "в діапазоні "+it.key, null, false)); }

pnl = new javax.swing.JPanel(new java.awt.GridLayout(1, charts.size()));
charts.each{ it.setPreferredSize(new java.awt.Dimension((int)1280/charts.size(), 600)); pnl.add(it); }
mv_enframe(pnl);
}
Advertisements