Compare commits

...

4 Commits

Author SHA1 Message Date
e9064f9b61 Update README.md 2025-07-15 19:41:28 +02:00
33410e68ed Reestrcture and new graph library 2025-07-15 19:17:51 +02:00
9cf756ffbd Update README.md
Added idea for charts
2025-07-08 15:46:23 +02:00
28720bfcd5 graph tests 2025-07-07 21:55:07 +02:00
6 changed files with 419 additions and 23 deletions

View File

@@ -69,9 +69,68 @@ FIXME: listing of options this app accepts.
... ...
### Any Other Sections ### Graphs
### That You Think
### Might be Useful Posible ASCII art charts
#### Simple day
A one dimension diagram. Draw a tick each day the player has played
```
####### ### ###### ###########
------------------------------------------------------------------------------------------
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
june
```
#### Day and hour
The x axis represents days of month, while y axis represents hours of each day. A tick is drawed in the hour
when the player was playing.
```
23 |
22 |
21 |
20 |
19 | # #
18 | # # #
17 | # # # #
16 | #
15 | # # # #
14 | # # # #
13 | #
12 | # # # #
11 | # # # # #
10 | # # # #
09 | # #
08 |
07 |
06 |
05 |
04 |
03 |
02 |
01 |
00 |
------------------------------------------------------------------------------------------
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
june
```
## Interesting graphic and UI libraries
- Raster SVG: https://github.com/soulspace-org/cmp.batik
- Modify SVG graphics: https://github.com/stathissideris/dali
- Create Vega-lite SVG graphics: https://github.com/techascent/tech.viz
- Enhance CLI text: https://github.com/clj-commons/pretty?tab=readme-ov-file
- Universal UI: https://github.com/phronmophobic/membrane?tab=readme-ov-file
- Interesting libraries: https://www.clojure-toolbox.com/
## License ## License

View File

@@ -5,18 +5,29 @@
:license {:name "MIT" :license {:name "MIT"
:url "https://mit-license.org/"} :url "https://mit-license.org/"}
:dependencies [[org.clojure/clojure "1.11.1"] :dependencies [[org.clojure/clojure "1.11.1"] ;; Clojure itself
[clj-http/clj-http "2.0.0"] [clj-http/clj-http "2.0.0"] ;; HTTP client
[cheshire/cheshire "6.0.0"] [cheshire/cheshire "6.0.0"] ;; Convert to/from JSON
[slingshot/slingshot "0.12.2"] [slingshot/slingshot "0.12.2"] ;; Better exceptions for clj-http
[org.clojure/tools.cli "1.1.230"] ;; [org.clojure/tools.cli "1.1.230"] ;; Parse params. NOT USED
[cli-matic/cli-matic "0.5.4"] [cli-matic/cli-matic "0.5.4"] ;; Parse params and make a nice CLI app
[buddy/buddy-core "1.12.0-430"] [buddy/buddy-core "1.12.0-430"] ;; Cipher data
[org.clojure/tools.namespace "1.5.0"] [org.clojure/tools.namespace "1.5.0"] ;; Hot reload from REPL
[lt.tokenmill/timewords "0.5.0"] [lt.tokenmill/timewords "0.5.0"] ;; Parse dates and times in natural languaje
[com.hypirion/clj-xchart "0.2.0"]]
:plugins [[io.taylorwood/lein-native-image "0.3.1"]] ;;; LIBS FOR GRAPHS, SOME OF THEM WILL BE REMOVED IN THE FUTURE
;[com.hypirion/clj-xchart "0.2.0"] ;; Graphs and charts
;[aerial.hanami "0.15.1"] ;; Parse vega-lite data and generate graphics
;[folcon/oz "1.6.0-alpha6.2"] ;; Parse vega and vega-lite
[metasoarous/oz "1.6.0-alpha36"] ;; Other version of Oz
;[metasoarous/oz "2.0.0-alpha5"] ;; Newer version of Oz
[incanter/incanter-charts "1.9.3"] ;; graphics with Incanter
;[hswick/jutsu "0.1.1"] ;; Wrapper para plotly
[techascent/tech.viz "6.00-beta-16-4"] ;; Vega-lite parser (https://github.com/techascent/tech.viz)
]
:plugins [[io.taylorwood/lein-native-image "0.3.1"]] ;; Compile to native using graal
:main ^:skip-aot riot.app :main ^:skip-aot riot.app
:target-path "target/%s" :target-path "target/%s"

View File

@@ -223,8 +223,8 @@
:type :string :default nil :type :string :default nil
:as "Summoner tagline"}] :as "Summoner tagline"}]
:runs cmd-active} :runs cmd-active}
{:command "timeline" :short "t" {:command "last" :short "l"
:description ["Gets times from last played matches" :description ["Gets time info from last matches."
"You can search by PUUID or name and tagline, but not both." "You can search by PUUID or name and tagline, but not both."
"" ""
"You can set the PUUID passing only one argument or using -p param." "You can set the PUUID passing only one argument or using -p param."
@@ -292,7 +292,7 @@
:as ["Ending date (not included) in 'yyyy-MM-dd' format." :as ["Ending date (not included) in 'yyyy-MM-dd' format."
"Other valid values: today, yesterday, tomorrow"]}] "Other valid values: today, yesterday, tomorrow"]}]
:runs cmd-timeline} :runs cmd-timeline}
{:command "bulk" :short "b" {:command "timeline" :short "t"
:description ["Get info from LOL and TFT matches in a time range." :description ["Get info from LOL and TFT matches in a time range."
"Warning: it can take several minutes to obtain all data, due to api restrictions." "Warning: it can take several minutes to obtain all data, due to api restrictions."
"You can search by PUUID or name and tagline, but not both." "You can search by PUUID or name and tagline, but not both."

View File

@@ -2,8 +2,6 @@
;; Core functions to get data from server ;; Core functions to get data from server
(ns riot.core (ns riot.core
(:import [java.time Instant LocalDateTime ZoneId]
[java.net URLEncoder])
(:require [clj-http.client :as client] (:require [clj-http.client :as client]
[buddy.core.crypto :as crypto] [buddy.core.crypto :as crypto]
[buddy.core.codecs :as codecs]) [buddy.core.codecs :as codecs])

View File

@@ -31,16 +31,21 @@
(java.util.Date/from (.toInstant (.atZone (.atStartOfDay localdate) (ZoneId/systemDefault)))))) (java.util.Date/from (.toInstant (.atZone (.atStartOfDay localdate) (ZoneId/systemDefault))))))
(defn parse-localdate (defn parse-localdate
"Parse a yyyy-MM-dd date to java.util.Date" "Parse a yyyy-MM-dd date to java.time.LocalDate"
[s] [s]
(java.time.LocalDate/parse s (DateTimeFormatter/ofPattern DEFAULT_DATE_FORMAT)) (java.time.LocalDate/parse s (DateTimeFormatter/ofPattern DEFAULT_DATE_FORMAT))
) )
(defn parse-date (defn parse-date-str
[s] [s]
(localdate-to-date(parse-localdate s))) (localdate-to-date(parse-localdate s)))
(defn parse-date-epoch
[millis]
(localdate-to-date (unix-to-datetime millis)))
(defn format-datetime (defn format-datetime
"Converts a LocalDateTime to its string representation" "Converts a LocalDateTime to its string representation"
([dtime] (format-datetime dtime DEFAULT_DATE_TIME_FORMAT)) ([dtime] (format-datetime dtime DEFAULT_DATE_TIME_FORMAT))

View File

@@ -0,0 +1,323 @@
(ns riot.graph_experiments
(:use riot.data)
(:require ;[com.hypirion.clj-xchart :as c]
[clojure.pprint :as pprint]
[clojure.math :as m]
[clojure.core.matrix :as mtx])
(:require [incanter.core :as incanter]
[incanter.stats :as stats]
[incanter.charts :as charts]
[incanter.datasets :as datasets])
(:require [oz.core :as oz])
(:require [tech.viz.vega :as vega]))
;; ;;;; XCHART
;; ;; XY chart
;; (def xchart_xy
;; (c/xy-chart {"Expected rate" [(range 10) (range 10)]
;; "Actual rate" [(range 10) (map #(+ % (rand-int 5) -2) (range 10))]}))
;; (comment
;; (c/view xchart_xy))
;; ;;;; HANAMI
;; (comment
;; (hc/xform ht/point-chart
;; :UDATA "data/cars.json"
;; :X "Horsepower" :Y "Miles_per_Gallon" :COLOR "Origin"))
;;;; OZ
(def test-plot
{:data {:values
[{:time "18:00" :volume 10}
{:time "18:02" :volume 41}
{:time "18:07" :volume 192}
{:time "18:30" :volume 257}
{:time "19:00" :volume 300}]
:format {:parse {:time "date:'%H:%M'"}}}
:encoding {:x {:field "time" :type "temporal" :timeUnit "hoursminutes"}
:y {:field "volume" :type "quantitative"}}
:mark "point"})
;;; to compile and view in Clojure - Oz:
(comment
(do
(println "calling (oz/start-server!)")
(oz/start-server!)
(println "calling (oz/view!)")
(oz/view! test-plot)
(println "calling (Thread/sleep)")
(Thread/sleep 5000))
)
;;;; INCANTER
;; Simple histogram
(comment
(incanter/view (charts/histogram (stats/sample-normal 1000))))
;; Boxplot demo
(defn create-box-plot [data]
(let [box-plot (charts/box-plot data
:title "Box Plot Example"
:y-label "Values"
:x-label "Dataset")]
(incanter/view box-plot)))
(def sample-data [5, 7, 8, 9, 10, 14, 15, 21, 23, 23, 24, 26, 28, 30, 37])
(comment
(create-box-plot sample-data))
;; Scatter
(comment
(incanter/view (charts/scatter-plot :Sepal.Length :Sepal.Width
:data (datasets/get-dataset :iris)))
(clojure.pprint/pprint (datasets/get-dataset :iris)))
;; Scatter grouped
(comment
(incanter/view (charts/scatter-plot :Sepal.Length :Sepal.Width
:group-by :Species
:data (datasets/get-dataset :iris)
:legend true)))
;; Scatter matrix (too complicated)
(comment
(incanter/view (charts/scatter-plot-matrix
(datasets/get-dataset :iris)
:nbins 20
:group-by :Species))
(incanter/with-data (datasets/get-dataset :iris) (incanter/view (charts/scatter-plot-matrix :nbins 20 :group-by :Species)))
(incanter/view (charts/scatter-plot-matrix
(datasets/get-dataset :chick-weight)
:group-by :Diet
:nbins 20)))
;; Heatmaps
(comment
(defn f [x y] (incanter/sin (incanter/sqrt (mtx/add (incanter/sq x) (incanter/sq y)))))
(incanter/view (charts/heat-map f -10 10 -15 15))
(incanter/view (charts/heat-map f -10 10 -10 10 :color? false))
(incanter/view (charts/heat-map f 5 10 5 10 :include-zero? false))
)
;; Personalize a scatter
(def my-data '({:start 1751537581404,
:end 1751539178158,
:duration 1583,
:active false,
:game-type "lol",
:id "EUW1_7450441192",
:winner true}
{:start 1751489335800,
:end 1751491099813,
:duration 1748,
:active false,
:game-type "lol",
:id "EUW1_7450117155",
:winner true}
{:start 1751486177961,
:end 1751487764068,
:duration 1568,
:active false,
:game-type "lol",
:id "EUW1_7450048068",
:winner true}
{:start 1751484450321,
:end 1751485680510,
:duration 1201,
:active false,
:game-type "lol",
:id "EUW1_7450013510",
:winner true}
{:start 1751400380588,
:end 1751401545922,
:duration 1133,
:active false,
:game-type "lol",
:id "EUW1_7449129548",
:winner true}
{:start 1751398221584,
:end 1751399322722,
:duration 1020,
:active false,
:game-type "lol",
:id "EUW1_7449082553",
:winner false}
{:start 1751394541765,
:end 1751396611534,
:duration 2040,
:active false,
:game-type "lol",
:id "EUW1_7449005657",
:winner false}
{:start 1751388187077,
:end 1751390143357,
:duration 1937,
:active false,
:game-type "lol",
:id "EUW1_7448887620",
:winner true}
{:start 1751384103153,
:end 1751385903519,
:duration 1767,
:active false,
:game-type "lol",
:id "EUW1_7448818616",
:winner false}
{:start 1751363056183,
:end 1751364555063,
:duration 1462,
:active false,
:game-type "lol",
:id "EUW1_7448564681",
:winner false}))
(defn extract-date-millis
"Extract date from a long epoch timestamp in milliseconds"
[x]
;(java.util.Date. x)
(let [date (new java.util.Date x)]
(. (java.util.Date. (. date getYear) (. date getMonth) (. date getDay)) getTime)))
(defn extract-date
"Extract date from a long epoch timestamp in milliseconds"
[x]
;(java.util.Date. x)
(let [date (new java.util.Date x)]
(java.util.Date. (. date getYear) (. date getMonth) (. date getDay))))
(comment
(extract-date-millis 1751363056183)
(. (extract-date-millis 1751363056183) getTime)
)
(defn extract-hour
"Extract hour from a long epoch timestamp in milliseconds"
[x]
;(java.util.Date. x)
(let [date (new java.util.Date x)]
(. date getHours)))
(comment
(java.util.Date. 1751537581404)
(java.util.Date. 1751539178158)
(extract-hour 1751537581404)
(extract-hour 1751539178158)
)
(defn extract-active-hours-millis
[match]
(when (every? some? [ (:start match) (:end match)])
(let [d-ini (extract-date-millis (:start match))
d-end (extract-date-millis (:end match))
h-ini (extract-hour (:start match))
h-end (extract-hour (:end match))
game-type (:game-type match)
winner (:winner match)]
(if (= d-ini d-end)
(for [day [d-ini]
hour (range h-ini (inc h-end))]
[day hour game-type winner])
(concat
(for [day [d-ini]
hour (range h-ini 24)]
[day hour game-type winner])
(for [day [d-ini]
hour (range 0 (inc h-end))]
[day hour game-type winner]))))))
(defn extract-active-hours
[match]
(when (every? some? [(:start match) (:end match)])
(let [d-ini (extract-date (:start match))
d-end (extract-date (:end match))
h-ini (extract-hour (:start match))
h-end (extract-hour (:end match))
game-type (:game-type match)
winner (:winner match)]
(if (= d-ini d-end)
(for [day [d-ini]
hour (range h-ini (inc h-end))]
[day hour game-type winner])
(concat
(for [day [d-ini]
hour (range h-ini 24)]
[day hour game-type winner])
(for [day [d-ini]
hour (range 0 (inc h-end))]
[day hour game-type winner]))))))
(comment
(extract-active-hours-millis (first my-data))
(partition 4 (flatten (map extract-active-hours-millis my-data)))
)
(defn extract-dataset-millis
[matches]
(incanter/dataset
[:days :hours :game-type :winner]
(partition 4 (flatten (map extract-active-hours-millis matches)))))
(defn extract-dataset
[matches]
(incanter/dataset
[:days :hours :game-type :winner]
(partition 4 (flatten (map extract-active-hours matches)))))
(comment
(extract-dataset-millis my-data)
(extract-dataset my-data)
(incanter/view (charts/scatter-plot :days :hours
:data (extract-dataset-millis my-data)
:group-by :game-type
:x-label "Day"
:y-label "Hour"
:legend false))
)
(comment
(defn process-my-data [x y] (+ x y (m/random)))
(incanter/view (charts/heat-map process-my-data
-10 10 -15 15
:x-label "Day"
:y-label "Hour"
:z-label "Count"
:color? true
:include-zero? false))
)
;;;; tech.viz
(comment
(let [ my-graph (vega/scatterplot [{:a 1 :b 2} {:a 2 :b 3}] :a :b)]
(vega/vega->svg-file my-graph "timeseries.svg"))
)