(ns gaza-stats.chart "Produce charts from data functions in other namespaces." (:require [cljc.java-time.local-date :refer [is-before]] [clojure.string :refer [capitalize starts-with?]] [com.hypirion.clj-xchart :refer [xy-chart]] [gaza-stats.core :refer [accounts-with-donations-by-date-range average-donations-received-by-month local-date->date normalise-date total-donations-by-weeks]] [gaza-stats.donors :refer [donations-by-donor]])) (defn chart-donations-by-weeks "Create and return an XY chart of data on field represented by `key` of donations made between `start-date` and `end-date`. Arguments may be supplied as `java.time.LocalDate` objects, or as strings in the format `yyyy-mm-dd`." [start-date end-date key] (let [data (total-donations-by-weeks start-date end-date)] (xy-chart {(format "%s Donations" (capitalize (name key))) [(map #(local-date->date (first %)) data) (map #(:n (nth % 1)) data)]}))) (defn chart-total-donations-by-weeks "Create and return an XY chart of data on total donations made between `start-date` and `end-date`. Arguments may be supplied as `java.time.LocalDate` objects, or as strings in the format `yyyy-mm-dd`." [start-date end-date] (chart-donations-by-weeks start-date end-date :total)) (defn chart-number-donations-by-weeks "Create and return an XY chart of data on number of donations made between `start-date` and `end-date`. Arguments may be supplied as `java.time.LocalDate` objects, or as strings in the format `yyyy-mm-dd`." [start-date end-date] (chart-donations-by-weeks start-date end-date :n)) (defn chart-donations-by-accounts-in-date-range "Return a chart of donations received by individual accounts between `start-date` (exclusive) and `end-date` (inclusive). Arguments should be supplied as `java.time.LocalDate` objects, or as strings in the format `yyyy-mm-dd`. Accounts are identified in the chart only by rank order." [start-date end-date] (let [data (accounts-with-donations-by-date-range start-date end-date)] (xy-chart {(format "Donations between\n%s and\n%s" start-date end-date) [(range (count data)) (sort (map :donations data))]}))) (defn chart-donations-as-percentage-in-date-range "Return a chart of donations received by individual accounts between `start-date` (exclusive) and `end-date` (inclusive). Arguments should be supplied as `java.time.LocalDate` objects, or as strings in the format `yyyy-mm-dd`. Accounts are identified in the chart only by rank order." [start-date end-date] (let [data (accounts-with-donations-by-date-range start-date end-date) donations (sort (map :donations data)) total (reduce + donations) percentages (map #(* 100.0 (/ % total)) donations)] (xy-chart {(format "Donations as percent\nof total donations\nbetween %s and\n%s" start-date end-date) [(range (count data)) percentages]}))) (defn chart-accounts-with-donations-by-months-in-range "Return a chart with one line for each calendar month covering from `start-date` (inclusive) to `end-date` (inclusive). Arguments should be supplied as `java.time.LocalDate` objects, or as strings in the format `yyyy-mm-dd`, showing donations as for `chart-donations-by-accounts-in-date-range`, q.v." [start-date end-date] (let [sd (normalise-date start-date "2024-01-01") ed (normalise-date end-date nil)] (loop [s (.withDayOfMonth sd 1) i (.withDayOfMonth (.plusMonths sd 1) 1) m (sorted-map)] (let [data (accounts-with-donations-by-date-range s i) m' (assoc m (format "Donations between\n%s and\n%s" s i) [(range (count data)) (sort (map :donations data))])] (if (is-before ed i) (xy-chart m') (recur i (.withDayOfMonth (.plusMonths i 1) 1) m')))))) (defn chart-average-donations-received-by-month "Return a chart comparing the average donations received per account with the total number of active accounts per month between `start-date` and `end-date`. Arguments should be supplied as `java.time.LocalDate` objects, or as strings in the format `yyyy-mm-dd`." [start-date end-date] (let [data (average-donations-received-by-month start-date end-date)] (xy-chart {(format "Average donations\nper active account\nbetween\n%s and\n%s" start-date end-date) [(map #(local-date->date %) (keys data)) (map :average (vals data))] (format "Active accounts\nbetween\n%s and\n%s" start-date end-date) [(map #(local-date->date %) (keys data)) (map :count (vals data))]}))) (defn chart-donations-by-donor "Return a rank-ordered graph of the values of this `key` in the `donations-by-donor` data between this `start-date` and this `end-date`." ([start-date end-date key] (chart-donations-by-donor start-date end-date key false)) ([start-date end-date key exclude-anons] (let [data (donations-by-donor start-date end-date) data' (if exclude-anons (remove #(let [d (:donor %)](or (nil? d) (starts-with? d "Anon"))) data) data)] (xy-chart {(format "%s dontations by donor%s" (capitalize (name key)) (when exclude-anons "\n(excluding anons)")) [(range (count data')) (sort (map key data'))]}))))