diff --git a/Cargo.lock b/Cargo.lock index 185abae..9a550f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -663,7 +663,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", - "fastrand", + "fastrand 2.3.0", "http 0.2.12", "http-body 0.4.6", "percent-encoding", @@ -693,7 +693,7 @@ dependencies = [ "aws-smithy-xml", "aws-types", "bytes", - "fastrand", + "fastrand 2.3.0", "hex", "hmac", "http 0.2.12", @@ -860,7 +860,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "bytes", - "fastrand", + "fastrand 2.3.0", "http 0.2.12", "http 1.4.0", "http-body 0.4.6", @@ -1149,6 +1149,17 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e" +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor 4.0.3", +] + [[package]] name = "brotli" version = "8.0.2" @@ -1157,7 +1168,17 @@ checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", - "brotli-decompressor", + "brotli-decompressor 5.0.0", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", ] [[package]] @@ -1326,6 +1347,29 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "charton" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ce8efe1b2443acf11f8527da75f2fcaddc95f21e9b5c8aa69384a055b89bb8" +dependencies = [ + "base64 0.22.1", + "indexmap 2.13.0", + "itertools", + "kernel-density-estimation", + "num-traits", + "ordered-float", + "polars 0.49.1", + "polars-io 0.49.1", + "regex", + "resvg", + "serde", + "serde_json", + "thiserror 2.0.18", + "time", + "uuid", +] + [[package]] name = "chrono" version = "0.4.43" @@ -1888,6 +1932,12 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "data-url" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + [[package]] name = "debug_unsafe" version = "0.1.3" @@ -2124,6 +2174,15 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" +[[package]] +name = "euclid" +version = "0.22.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63" +dependencies = [ + "num-traits", +] + [[package]] name = "event-listener" version = "5.4.1" @@ -2235,6 +2294,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -2338,6 +2406,12 @@ dependencies = [ "zlib-rs", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "float-cmp" version = "0.10.0" @@ -2371,6 +2445,29 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -2543,6 +2640,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gif" version = "0.14.1" @@ -3140,11 +3247,11 @@ dependencies = [ "byteorder-lite", "color_quant", "exr", - "gif", + "gif 0.14.1", "image-webp", "moxcms", "num-traits", - "png", + "png 0.18.0", "qoi", "ravif", "rayon", @@ -3164,6 +3271,12 @@ dependencies = [ "quick-error", ] +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + [[package]] name = "imgref" version = "1.12.0" @@ -3202,6 +3315,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "interpolate_name" version = "0.2.4" @@ -3276,6 +3398,28 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kernel-density-estimation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c75fb1225deed1c9af38c80ec0c312bab52c1509b3c602b97c36b525277fe8c" +dependencies = [ + "fastrand 1.9.0", + "nalgebra 0.31.4", + "num-traits", +] + +[[package]] +name = "kurbo" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -3619,6 +3763,22 @@ dependencies = [ "pxfm", ] +[[package]] +name = "nalgebra" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20bd243ab3dbb395b39ee730402d2e5405e448c75133ec49cc977762c4cba3d1" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba 0.7.3", + "typenum", +] + [[package]] name = "nalgebra" version = "0.33.2" @@ -3632,10 +3792,21 @@ dependencies = [ "num-traits", "rand 0.8.5", "rand_distr 0.4.3", - "simba", + "simba 0.9.1", "typenum", ] +[[package]] +name = "nalgebra-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -4091,7 +4262,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ - "fastrand", + "fastrand 2.3.0", "phf_shared 0.13.1", ] @@ -4113,6 +4284,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -4237,6 +4414,19 @@ dependencies = [ "zip 4.6.1", ] +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "png" version = "0.18.0" @@ -5045,6 +5235,7 @@ dependencies = [ "polars-error 0.49.1", "polars-schema 0.49.1", "polars-utils 0.49.1", + "rand 0.8.5", "rayon", "regex", "regex-syntax", @@ -5095,10 +5286,13 @@ checksum = "7c849c10edd9511ccd4ec4130e283ee3a8b3bb48a7d74ac6354c1c20add81065" dependencies = [ "async-stream", "base64 0.22.1", + "brotli 7.0.0", "bytemuck", "ethnum", + "flate2", "futures", "hashbrown 0.15.5", + "lz4", "num-traits", "polars-arrow 0.49.1", "polars-compute 0.49.1", @@ -5107,7 +5301,9 @@ dependencies = [ "polars-utils 0.49.1", "serde", "simdutf8", + "snap", "streaming-decompression", + "zstd 0.13.3", ] [[package]] @@ -5138,7 +5334,7 @@ checksum = "93c2439d127c59e6bfc9d698419bdb45210068a6f501d44e6096429ad72c2eaa" dependencies = [ "async-stream", "base64 0.22.1", - "brotli", + "brotli 8.0.2", "bytemuck", "ethnum", "flate2", @@ -6309,6 +6505,23 @@ dependencies = [ "web-sys", ] +[[package]] +name = "resvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" +dependencies = [ + "gif 0.13.3", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", + "zune-jpeg 0.4.21", +] + [[package]] name = "rfc6979" version = "0.3.1" @@ -6325,6 +6538,9 @@ name = "rgb" version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" +dependencies = [ + "bytemuck", +] [[package]] name = "ring" @@ -6388,11 +6604,18 @@ dependencies = [ "serde", ] +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + [[package]] name = "rust-data-analysis" version = "1.0.1" dependencies = [ "aws-sdk-s3", + "charton", "comrak", "connectorx", "df-interchange", @@ -6574,6 +6797,24 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.22" @@ -6901,6 +7142,19 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simba" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3fd720c48c53cace224ae62bef1bbff363a70c68c4802a78b5cc6159618176" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[package]] name = "simba" version = "0.9.1" @@ -6951,6 +7205,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + [[package]] name = "siphasher" version = "1.0.2" @@ -7100,7 +7363,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" dependencies = [ "approx", - "nalgebra", + "nalgebra 0.33.2", "num-traits", "rand 0.8.5", ] @@ -7126,6 +7389,15 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp 0.9.0", +] + [[package]] name = "stringprep" version = "0.1.5" @@ -7174,6 +7446,16 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "svgtypes" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +dependencies = [ + "kurbo", + "siphasher", +] + [[package]] name = "syn" version = "1.0.109" @@ -7309,7 +7591,7 @@ version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ - "fastrand", + "fastrand 2.3.0", "getrandom 0.3.4", "once_cell", "rustix 1.1.3", @@ -7421,6 +7703,32 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png 0.17.16", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -7675,6 +7983,15 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + [[package]] name = "typed-arena" version = "2.0.2" @@ -7705,6 +8022,18 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + [[package]] name = "unicode-ident" version = "1.0.22" @@ -7735,12 +8064,24 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + [[package]] name = "unicode-width" version = "0.2.2" @@ -7783,6 +8124,33 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "usvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + [[package]] name = "utf8_iter" version = "1.0.4" @@ -7824,7 +8192,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e80f0c733af0720a501b3905d22e2f97662d8eacfe082a75ed7ffb5ab08cb59" dependencies = [ - "float-cmp", + "float-cmp 0.10.0", "halfbrown", "itoa", "ryu", @@ -8510,6 +8878,12 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "xxhash-rust" version = "0.8.15" diff --git a/Cargo.toml b/Cargo.toml index 89e1891..2b42dc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ rust_xlsxwriter = "0.93" # Plots plotlars = {version = "0.11", features = ["export-firefox", "export-download"] } +charton = { version = "0.2.1" } # Markdown documents comrak = "0.50" \ No newline at end of file diff --git a/examples/5_2_2_plots.rs b/examples/5_2_2_plots.rs new file mode 100644 index 0000000..1354675 --- /dev/null +++ b/examples/5_2_2_plots.rs @@ -0,0 +1,35 @@ +use charton::prelude::*; +use std::error::Error; + +fn main() -> Result<(), Box> { + // Load dataset + let df = load_dataset("iris")?; + + // Single layer chart + Chart::build(&df)? + .mark_point() + .encode(( + x("sepal_length"), + y("sepal_width"), + color("species") + ))? + .into_layered() + .save("single_layer.svg")?; + + // Multiple layers chart + let df_subset = df.head(Some(4)).tail(Some(3)); + let line = Chart::build(&df_subset)? + .mark_line() + .encode((x("sepal_length"), y("sepal_width")))?; + + let points = Chart::build(&df_subset)? + .mark_point() + .encode((x("sepal_length"), y("sepal_width")))?; + + LayeredChart::new() + .add_layer(line) + .add_layer(points) + .save("multiple_layers.svg")?; + + Ok(()) +} \ No newline at end of file diff --git a/src/1_start/2_crates.md b/src/1_start/2_crates.md index f75c96b..d89c38e 100644 --- a/src/1_start/2_crates.md +++ b/src/1_start/2_crates.md @@ -52,6 +52,7 @@ rust_xlsxwriter = "0.93" # Plots plotlars = {version = "0.11", features = ["export-firefox", "export-download"] } +charton = { version = "0.2.1" } # Markdown documents comrak = "0.50" diff --git a/src/5_pub/2_plotting.md b/src/5_pub/2_plotting.md index 67c9499..235b91d 100644 --- a/src/5_pub/2_plotting.md +++ b/src/5_pub/2_plotting.md @@ -1,12 +1,24 @@ # Plotting -Plots are the language of data analysts. There are mutiple dozens of types of plots, and the majority of them are possible in Rust. [Plotly](https://plotly.com/javascript/), the very popular JavaScript open source graphing library, has a [Rust interface](https://github.com/plotly/plotly.rs)! This chapter will use the [plotlars](https://github.com/alceal/plotlars) crate, a wrapper around the Plotly library that takes polars dataframes as input. This is a great bridge between Polars and Plotly. Plotlars allows you to build all types of graphs, like bar plots, box plots, line plots, pie charts and sankey diagrams. +Plots are the language of data analysts. There are dozens of different plot types, and the majority of them are possible in Rust today. -Run this code using `cargo run -r --example 5_2_1_plots`. +This chapter introduces **two complementary approaches to data visualization in Rust**. -## Bar Graph +The first approach builds on [Plotly](https://plotly.com/javascript/), the very popular JavaScript open source graphing library, has a [Rust interface](https://github.com/plotly/plotly.rs). We will use the [plotlars](https://github.com/alceal/plotlars) crate, a wrapper around the Plotly library that takes polars dataframes as input. This is a great bridge between Polars and Plotly. Plotlars allows you to build all types of graphs, like bar plots, box plots, line plots, pie charts and sankey diagrams. -### Setup +The second approach explores [Charton](https://github.com/wangjiawen2013/charton), a declarative plotting library designed for Rust. Charton emphasizes a grammar-of-graphics style API and first-class Polars support, and can render plots natively in Rust or delegate rendering to external visualization backends when appropriate. This model is particularly well suited for exploratory data analysis, composable chart construction, and web-oriented workflows. + +## Plotting with Plotly and plotlars + +Run the Plotly-based examples in this section using: + +```bash +cargo run -r --example 5_2_1_plots +``` + +### Bar Graph + +#### Setup Lets get some summary statistics to output as a bar graph. Here is a table of the mean income by sex and region: @@ -36,7 +48,7 @@ shape: (20, 3) └────────┴──────────────────────────┴──────────┘ ``` -### Building the bar graph +#### Building the bar graph To build a graph, you start with the data, you give it an `x` axis and a `y` axis, a `group` if you have groups and then various options, like `x_title`, `y_title`, `plot_title`, `size` for text, etc. Most functions are self-explanatory, but they are described in the documentation of the [bar graph](https://docs.rs/plotlars/latest/plotlars/struct.BarPlot.html). @@ -67,9 +79,9 @@ Opening this file, will give you this interactive bar chart: Instead of the `to_html()`, you can also write an image using this syntax: `.write_image("./data/output/out.png", 800, 600, 1.0).unwrap()` -## Line Plot +### Line Plot -### Setup +#### Setup Lets also get some summary statistics to output as a line plot. Here is a table of the mean income by sex and ours worked (groupped), pivoted on sex: @@ -92,7 +104,7 @@ shape: (4, 3) └──────────────────┴──────────┴──────────┘ ``` -### Building the line plot +#### Building the line plot Similar to the bar graph, for the line plot, you start with the data, you give it an `x` axis and a `y` axis (and here, you add another `y` axis with `additional_lines` to add a new line to the line plot), and then various options, like `x_title`, `y_title`, `plot_title`, `size` for text, etc. Most functions are self-explanatory, but they are described in the documentation of the [line plot](https://docs.rs/plotlars/latest/plotlars/struct.LinePlot.html). @@ -121,4 +133,340 @@ Opening this file, will give you this interactive bar chart: -Instead of the `to_html()`, you can also write an image using this syntax: `.write_image("./data/output/out.png", 800, 600, 1.0).unwrap()` \ No newline at end of file +Instead of the `to_html()`, you can also write an image using this syntax: `.write_image("./data/output/out.png", 800, 600, 1.0).unwrap()` + +## Declarative Plotting in Rust with Charton + +While Plotly and plotlars demonstrate that high-quality, interactive visualization is entirely possible in Rust, they also represent a design where Rust primarily acts as a data preparation layer, delegating rendering to an external visualization runtime. + +In some workflows, however, it can be desirable to treat Rust itself as the primary language for visualization: avoiding external runtimes, temporary files, or language context switches, while keeping a concise, declarative API. + +**Charton** explores this alternative design space. + +Run the Charton-based examples in this section using: + +```bash +cargo run -r --example 5_2_2_plots +``` +or evaluated interactively in a Jupyter notebook via `evcxr`. + +### Design Philosophy + +Charton is a Rust-native plotting library with first-class support for Polars. Its API is inspired by declarative visualization systems such as Altair and Vega-Lite, allowing users to describe *what* should be plotted rather than how to construct individual plot components. + +Rather than directly manipulating traces, axes, and layouts, users define mappings between data columns and visual encodings (for example, x- and y-axes), and Charton handles the rest. + +This approach is particularly well-suited for exploratory data analysis, where readability, composability, and iteration speed are more important than fine-grained control over rendering primitives. + +### A Simple Example + +The following example creates a scatter plot directly from a Polars `DataFrame`, rendered entirely in Rust as an SVG file: +```rust +use charton::prelude::*; +use std::error::Error; + +fn main() -> Result<(), Box> { + // Load the iris dataset (a polars dataframe) + let df = load_dataset("iris")?; + println!("{}", df); + + // Single layer chart + Chart::build(&df)? + .mark_point() + .encode(( + x("sepal_length"), + y("sepal_width"), + color("species") + ))? + .into_layered() + .save("single_layer.svg")?; + + Ok(()) +} +``` +```text +shape: (150, 5) +┌──────────────┬─────────────┬──────────────┬─────────────┬───────────┐ +│ sepal_length ┆ sepal_width ┆ petal_length ┆ petal_width ┆ species │ +│ --- ┆ --- ┆ --- ┆ --- ┆ --- │ +│ f64 ┆ f64 ┆ f64 ┆ f64 ┆ str │ +╞══════════════╪═════════════╪══════════════╪═════════════╪═══════════╡ +│ 5.1 ┆ 3.5 ┆ 1.4 ┆ 0.2 ┆ setosa │ +│ 4.9 ┆ 3.0 ┆ 1.4 ┆ 0.2 ┆ setosa │ +│ 4.7 ┆ 3.2 ┆ 1.3 ┆ 0.2 ┆ setosa │ +│ 4.6 ┆ 3.1 ┆ 1.5 ┆ 0.2 ┆ setosa │ +│ 5.0 ┆ 3.6 ┆ 1.4 ┆ 0.2 ┆ setosa │ +│ … ┆ … ┆ … ┆ … ┆ … │ +│ 6.7 ┆ 3.0 ┆ 5.2 ┆ 2.3 ┆ virginica │ +│ 6.3 ┆ 2.5 ┆ 5.0 ┆ 1.9 ┆ virginica │ +│ 6.5 ┆ 3.0 ┆ 5.2 ┆ 2.0 ┆ virginica │ +│ 6.2 ┆ 3.4 ┆ 5.4 ┆ 2.3 ┆ virginica │ +│ 5.9 ┆ 3.0 ┆ 5.1 ┆ 1.8 ┆ virginica │ +└──────────────┴─────────────┴──────────────┴─────────────┴───────────┘ +``` +Open the `single_layer.svg` file in a web browser to see the plot: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +4 + +5 + +6 + +7 + +8 +sepal_length + + +2.0 + +2.5 + +3.0 + +3.5 + +4.0 + +4.5 +sepal_width +species + +setosa + +versicolor + +virginica + + +In contrast to Plotly-based approaches, this example requires no JavaScript runtime or browser engine. The chart is rendered natively and written directly to disk. + +### Layered Charts + +Charton uses a layered chart model, where multiple visual layers can share a common coordinate system. This makes it straightforward to combine different plot types—such as lines and points—within the same figure. +```rust +let df = df.head(Some(4)).tail(Some(3)); +let line = Chart::build(&df)? + .mark_line() + .encode((x("sepal_length"), y("sepal_width")))?; + +let points = Chart::build(&df)? + .mark_point() + .encode((x("sepal_length"), y("sepal_width")))?; + +LayeredChart::new() + .add_layer(line) + .add_layer(points) + .save("multiple_layers.svg")?; + +Ok(()) +``` +Open the `multiple_layers.svg` file in a web browser to see the plot: + + + + + + + +4.55 + +4.60 + +4.65 + +4.70 + +4.75 + +4.80 + +4.85 + +4.90 +sepal_length + + +3.00 + +3.05 + +3.10 + +3.15 + +3.20 +sepal_width + + +This compositional approach mirrors concepts found in grammar-of-graphics systems, while remaining idiomatic to Rust. + +### Interactive Use and Web Integration + +Charton integrates with the `evcxr` Jupyter kernel, allowing charts to be displayed inline during interactive analysis sessions. The same declarative workflow used for static output can be reused without modification. + +In addition, Charton can emit Vega-Lite–compatible JSON specifications. These specifications can be consumed by modern frontend visualization libraries, making it possible to decouple data processing (in Rust) from rendering (in the browser or a web application). + +This makes Charton particularly suitable for WebAssembly-based workflows, where Polars data processing and visualization logic can be executed directly in the browser. + +### When to Use Charton + +Charton is best suited for: +- Rust-first data analysis workflows built around Polars +- Environments where external runtimes are undesirable +- Declarative, composable visualization pipelines +- WebAssembly and frontend-integrated visualization use cases + +For users who require the full breadth of Plotly’s interactive feature set, plotlars remains an excellent choice. Charton complements this ecosystem by offering a Rust-native alternative that prioritizes simplicity, performance, and portability. \ No newline at end of file