Code Monkey home page Code Monkey logo

ulid-rs's Introduction

ulid-rs

Build Status Crates.io docs.rs

This is a Rust implementation of the ulid project which provides Universally Unique Lexicographically Sortable Identifiers.

Quickstart

use ulid::Ulid;

// Generate a ulid
let ulid = Ulid::new();

// Generate a string for a ulid
let s = ulid.to_string();

// Create from a String
let res = Ulid::from_string(&s);

assert_eq!(ulid, res.unwrap());

Crate Features

  • std (default): Flag to toggle use of std and rand. Disable this flag for #[no_std] support.
  • serde: Enables serialization and deserialization of Ulid types via serde. ULIDs are serialized using their canonical 26-character representation as defined in the ULID standard. An optional ulid_as_u128 module is provided, which enables serialization through an Ulid's inner u128 primitive type. See the documentation and serde docs for more information.
  • uuid: Implements infallible conversions between ULIDs and UUIDs from the uuid crate via the std::convert::From trait.

Benchmark

Benchmarks were run on my desktop (Win 10/WSL2 Ubuntu; Ryzen 7 5950x). Run them yourself with cargo bench.

test bench_from_string        ... bench:          13 ns/iter (+/- 0)
test bench_from_time          ... bench:          13 ns/iter (+/- 0)
test bench_generator_generate ... bench:          29 ns/iter (+/- 0)
test bench_new                ... bench:          31 ns/iter (+/- 1)
test bench_to_str             ... bench:           7 ns/iter (+/- 0)
test bench_to_string          ... bench:          19 ns/iter (+/- 0)

ulid-rs's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

ulid-rs's Issues

postgres traits

I'd like to use ulids as my primary key in postgres. Right now they don't implement FromSqland ToSql.

For now I created my own struct MyUlid(Ulid) but it might be useful to others as well.

Not sure if this should be here or in https://github.com/sfackler/rust-postgres as uuid is?
Do you want a pr?

doc suggestion: use generator in quickstart guide?

So I recently ran into the same issue as

#39

in that I expected Ulid::new() to return sorted results when called repeatedly.

Since people use ULIDs over UUIDs for sortability reasons, might make sense to use a generator on the quick start section.

Consider lib upgrade

Hello!

No big rush, but here are some suggestions of libs upgrades

  • UUID is now v1
  • chrono is pretty much dead and people are moving to the time crate

Thanks

Add MAX and MIN Ulid values

These are useful when comparing ulids. So MIN would be the equivalent of 0 and MAX would be u128::MAX I think

I can see there is already a min() function but I think it would be useful if these values are available as constants, just like with the number types

web_time resolve error with --target wasm32-wasi

I believe this just started happening on ulid 1.1.1 when I execute:

cargo build --target wasm32-wasi (compiles fine with cargo build --target wasm32-unknown-unknown)

error[E0433]: failed to resolve: use of undeclared crate or module `web_time`
 --> /Users/abc/.cargo/registry/src/index.crates.io-6f17d22bba15001f/ulid-1.1.1/src/time_utils.rs:4:13
  |
4 |         use web_time::web::SystemTimeExt;
  |             ^^^^^^^^ use of undeclared crate or module `web_time`

error[E0433]: failed to resolve: use of undeclared crate or module `web_time`
 --> /Users/abc/.cargo/registry/src/index.crates.io-6f17d22bba15001f/ulid-1.1.1/src/time_utils.rs:5:16
  |
5 |         return web_time::SystemTime::now().to_std();
  |                ^^^^^^^^ use of undeclared crate or module `web_time`
  |
help: consider importing this struct
  |
1 + use std::time::SystemTime;
  |
help: if you import `SystemTime`, refer to it directly
  |
5 -         return web_time::SystemTime::now().to_std();
5 +         return SystemTime::now().to_std();
  |

For more information about this error, try `rustc --explain E0433`.
error: could not compile `ulid` (lib) due to 2 previous errors
warning: build failed, waiting for other jobs to finish...

Unable to compile the combination of `no_std` and the `serde` feature

Since the serde.rs file expects String to be made available by the prelude the compilation fails if serde is activated without std.

> cargo build
   Compiling ulid v1.0.0
error[E0433]: failed to resolve: use of undeclared type `String`
  --> ulid-1.0.0/src/serde.rs:28:32
   |
28 |         let deserialized_str = String::deserialize(deserializer)?;
   |                                ^^^^^^ use of undeclared type `String`

I propose that when the std feature is used it will depend on serde?/std and by default the optional serde dependency has { default-features = false, features = ["alloc", "derive"] }. I'll make a PR for this sometimes this coming week if no one else beats me to it.

New features: serde support, and conversions to uuid

Hi, I wrote two new (optional) features in my own fork of ulid-rs, and I'd like to know if you'd welcome pull requests for either of them. They are:

  • serde: this pulls in serde and implements Serialize and Deserialize for Ulid. The 26-char representation is used by default, and I also wrote a module that can serialize as the inner primitive u128 by annotating structs with #[serde(with = "ulid_as_u128")];
  • uuid: this is a tiny convenience feature that pulls in the uuid crate and implements the conversions From<Uuid> for Ulid and From<Ulid> for Uuid, both infallible. These come in handy when interacting with systems that expect UUIDs.

Add support for Encode and Decode traits from both bincode and bitcode

Bincode and bitcode both have their own Encode and Decode traits that they would prefer users use over serde Serialize and Deserialize.

I would propose that these be implemented with a feature of bincode for the bincode versions and bitcode for the bitcode versions.

I would be happy to design this and send over a PR

to_str variable buffer size

Hi,

Just wondering why you allow the user to specify the buffer >=ULID_LEN. Is there any situation where the user wants to supply a larger buffer than ULID_LEN? The tradeoff you're making is having the to_str API be fallible and forcing the user to supply their own buffer.

v0.6 WASM runtime error: 'time not implemented on this platform'

When using v0.5 everything runs fine in my Yew-WASM-applications, targeting wasm32-unknown-unknown and the web. So the chrono implementation works.

When using v0.6 (with default features), creating a Ulid panics with 'time not implemented on this platform', as the time crate seems to call std::time functions.

Should I use a different set of features? I were not able to find anything to control this. Neither here nor in the time crate..

`DecodeError` and `EncodeError` do not impl `std::error::Error`

Hey, thanks for the excellent ULID implementation!

The error types already implement all the traits necessary to be std Error; all that would be needed is to add the empty impl.

Interop with the error handling ecosystem would be smoother if the de facto Errors were recognized by the type system.

[FEATURE] SQLx support

Currently, the ulid crate implements postgres-types support but for some unknown reason the sqlx crate does not extend from the generic postgres-types crate. So anyone using a derivative of sqlx (sea-orm) or using the sqlx driver directly would be unable to use this crate directly without a custom wrapper type.

Investigate chrono -> time perf regressions

All methods touching the time package a significantly slower than their chrono counterparts.

v0.5.0

git checkout v0.5.0
cargo bench
test bench_from_string        ... bench:          14 ns/iter (+/- 2)
test bench_from_time          ... bench:           8 ns/iter (+/- 0)
test bench_generator_generate ... bench:          42 ns/iter (+/- 1)
test bench_new                ... bench:          44 ns/iter (+/- 0)
test bench_to_str             ... bench:           7 ns/iter (+/- 0)
test bench_to_string          ... bench:          20 ns/iter (+/- 0)

v0.6.0

git checkout v0.6.0
cargo bench
test bench_from_string        ... bench:          13 ns/iter (+/- 1)
test bench_from_time          ... bench:          12 ns/iter (+/- 1)
test bench_generator_generate ... bench:          55 ns/iter (+/- 1)
test bench_new                ... bench:          58 ns/iter (+/- 1)
test bench_to_str             ... bench:           7 ns/iter (+/- 0)
test bench_to_string          ... bench:          20 ns/iter (+/- 1)

[Question] Best way to embed a ULID as a constant?

I have a number of ULIDs in my code which are static. Currently I am just using a static function returning
Ulid::from_str("01FATR1B6ZNA09MEB7NH4DS49D")

Is there a recommended way to embed a ULID (ideally from the string form) as a constant?

Add tests for `Default` behaviours

Currently we are only testing directly-constructed objects. We should add tests for expected behavior of objects constructed through the Default trait.

use `u128` instead of `(u64, u64)` as byte level representation

Using a u128 primitive will lead to a simpler and maybe faster implementation. The only problem is that this is currently a nightly feature, so u128 support will have to be implemented after it hits stable. This is a breaking API change due to the Ulid tuple constructor.

Generator doesn't actually guarantee 80-bits of UIDs per millisecond?

I could be horribly misreading the code, but on every new millisecond it'll reseed the random state with new entropy. If the increment moves the entropy to RAND_MAX (i.e. 2^80), the generate function returns an error. My statistics is fuzzy, but I think that means on average you can actually onl increment ~2^40 times since the randomness will be close to the half-way point to RAND_MAX half the time leaving only 40 bits of increment. You would also expect that a non-infrequent amount of time you would have very few generations available if the random number generated is or is close to RAND_MAX.

Shouldn't Generator remember the starting entropy so that on hitting RAND_MAX it simply wraps and only returns an error if it were to increment back to the original random state?

allow sharing Generator across threads

I created this example https://github.com/camerondavison/ulid-rs/blob/threads/examples/threads.rs because I was having trouble figuring out how to share the Generator across threads.

I think that the Box trait needs Send added to it, but ThreadRng is not Send compatible since it does all the init per thread. So that might mean that the Generator may need to optionally have a RngCore or use a ThreadRng instead of saving it, or use a different RngCore that has Send

Or maybe I am thinking about this the wrong way

UUIDv7 conversions

UUIDv7 conversions that keep the timestamp and random value would be nice to have.

Fails to compile with `uuid` feature

When compiling with

cargo 1.57.0 (b2e52d7ca 2021-10-21)
rustc 1.57.0 (f1edd0429 2021-11-29)

the following errors occur:

โžœ cargo build --features uuid
  Compiling uuid v0.8.2
error[E0412]: cannot find type `Uuid` in this scope
  --> /Users/schmidmt/.cargo/registry/src/github.com-1ecc6299db9ec823/uuid-0.8.2/src/adapter/mod.rs:917:19
   |
917 |           impl From<Uuid> for $T {
   |                     ^^^^ not found in this scope
...
934 | / impl_adapter_traits! {
935 | |     Hyphenated<>,
936 | |     HyphenatedRef<'a>,
937 | |     Simple<>,
...   |
940 | |     UrnRef<'a>
941 | | }
   | |_- in this macro invocation
   |
   = note: this error originates in the macro `impl_adapter_from` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0412]: cannot find type `Uuid` in this scope
  --> /Users/schmidmt/.cargo/registry/src/github.com-1ecc6299db9ec823/uuid-0.8.2/src/adapter/mod.rs:919:24
   |
919 |               fn from(f: Uuid) -> Self {
   |                          ^^^^ not found in this scope
...
934 | / impl_adapter_traits! {
935 | |     Hyphenated<>,
936 | |     HyphenatedRef<'a>,
937 | |     Simple<>,
...   |
940 | |     UrnRef<'a>
941 | | }
   | |_- in this macro invocation
   |
   = note: this error originates in the macro `impl_adapter_from` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0412]: cannot find type `Uuid` in this scope
  --> /Users/schmidmt/.cargo/registry/src/github.com-1ecc6299db9ec823/uuid-0.8.2/src/adapter/mod.rs:925:27
   |
925 |           impl<$a> From<&$a Uuid> for $T<$a> {
   |                             ^^^^ not found in this scope
...

Expose base32 implementation?

I had a look at #34 and ran into kind of the opposite situation.

Thanks for this crate :] currently using it for a number of ID fields.
However I ran into a case where I wanted to use the underlying base32 directly: JTIs.

When we're talking about a JWT we already have access to a timestamp (iat).
And so using ULIDs would make the timestamp data redundant.

Base32 encoding for 128 bits of data however is plenty sound!
So I thought about using the underlying base32 implementation and here we are.

Recommended Storage For MySQL and Postgres

Hi,

Firstly thanks for this amazing crate. I intend to use this for primary keys in a MySQL and potentially Postgres database. How would you suggest I store these? Could I use binary or just use the 26-character representation? I have looked at this ulid/spec#25 but not sure how it relates to this implementation. Another option is converting them to UUIDs and storing them as native uuid types in the databases but not sure if this would retain the sorting information.

Figure out what to do with overflowing ulid values

The binary representation of Ulid is 128 bits, but the base32 representation has 130 bits. Currently the highest two bits are ignored when parsing. This should likely return an error result instead. A new error when parsing will require a major version bump.

[Proposal] use external base32 crate

Would you be interested in a PR that replaces your in-house implementation of base32 with base32 crate

In my opinion this is better because:

  • that base32 crate has zero dependencies, this allows to drop lazy_static dependency
  • slightly better test coverage
  • most-importantly standalone - that reduces the code bloat if a downstream project uses both this and base32 crate

So overall this is a net win: less code to maintain here, better for the ecosystem.
What do you say?

Is there a method to validate that a ULID is valid?

Hello and thank you for the wonderful job you did with this crate! ๐Ÿ˜ƒ

I have a question: is there a method to validate that a ULID that comes to me from outside is valid?

If I use from_string() is it ok?

base32::EncodingError does not implement std::fmt::Display

Hi,
love your library, however there is a tiny bug :)

This code does not compile:

let ulid = match Ulid::from_string(&file_ulid) {
        Ok(ulid) => ulid,
        Err(e) => {
            println!("Unable to parse ULID: {}", e);
            return
        }
    };
error[E0277]: the trait bound `ulid::EncodingError: std::fmt::Display` is not satisfied
  --> src/show.rs:97:50
   |
97 |             println!("Unable to parse ULID: {}", e);
   |                                                  ^ `ulid::EncodingError` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
   |
   = help: the trait `std::fmt::Display` is not implemented for `ulid::EncodingError`
   = note: required by `std::fmt::Display::fmt`

It works when formatting it with {:?}, but all other std errors support fmt::Display trait.

Unexpected ordering

Hi there, I'm trying to write a test that demonstrates the ULID ordering promises but am seeing some unexpected behavior every few test runs or so:

[src/models/id.rs:141] &id_0 = Ulid(1972003866952799546420484161793439825);
[src/models/id.rs:142] &id_1 = Ulid(1972003866952020694265156341296371228);
thread 'models::id::tests::test_ordering_monotonic' panicked at 'assertion failed: id_0 < id_1'

Most of the time this assertion is true but sometimes it isn't. The test is simply constructing two ULIDs one after the other and then asserting.

Infallible `Generator` generation

Hey!

I see that all the methods in Generator are currently infallible. It certainly makes sense, in order to be sure that the timestamp part of the ULID stays technically correct.

However, I'd argue that most of the time the system clock is not set to millisecond accuracy, and fewer than 2**80 ULIDs are generated per second.

As such, I'd like to introduce an infallible Generator::generate_overflowing() (or similar name) method, that'd just make +1 to the u128 in case the randomly-generated ULID was before the last one output, and still allow overflow in the time bits.

Would you be interested in such a PR?

CVEs in CLI

Using https://github.com/google/osv-scanner

ulid-rs> osv-scanner --lockfile Cargo.lock 
Scanned /home/jayvdb/rust/ulid-rs/Cargo.lock file and found 83 packages
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ OSV URL                             โ”‚ CVSS โ”‚ ECOSYSTEM โ”‚ PACKAGE   โ”‚ VERSION โ”‚ SOURCE     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ https://osv.dev/RUSTSEC-2021-0139   โ”‚      โ”‚ crates.io โ”‚ ansi_term โ”‚ 0.12.1  โ”‚ Cargo.lock โ”‚
โ”‚ https://osv.dev/GHSA-g98v-hv3f-hcfr โ”‚      โ”‚ crates.io โ”‚ atty      โ”‚ 0.2.14  โ”‚ Cargo.lock โ”‚
โ”‚ https://osv.dev/RUSTSEC-2021-0145   โ”‚      โ”‚           โ”‚           โ”‚         โ”‚            โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

https://osv.dev/RUSTSEC-2021-0145 is a different identifier for https://osv.dev/GHSA-g98v-hv3f-hcfr

Broken Equality using NewType Wrappers around Ulid and Derive Equality macros

Not so long ago I found a weird case of broken equality in my wrapper types around Ulid which I can't find an explanation for after looking at the codebase for ulid-rs. I have created an example where I replicate it, hoping it could help to find out what could be wrong:

use std::fmt;
use ulid::Ulid;

fn main() {
    struct MyNewType {
        id: MyOtherNewTypeId,
    }

    #[derive(Debug, Eq, PartialEq, Copy, Clone)]
    struct MyNewTypeId(Ulid);

    impl MyNewTypeId {
        pub fn parse(string: String) -> Result<Self, ()> {
            Ulid::from_string(&string)
                .map(|ulid| Self(ulid))
                .map_err(|_| ())
        }
    }

    impl AsRef<Ulid> for MyNewTypeId {
        fn as_ref(&self) -> &Ulid {
            &self.0
        }
    }

    struct MyOtherNewType {
        pub id: MyOtherNewTypeId,
    }

    #[derive(Debug, Eq, PartialEq, Copy, Clone)]
    struct MyOtherNewTypeId(Ulid);

    impl MyOtherNewTypeId {
        pub fn new() -> Self {
            Self(Ulid::new())
        }

        pub fn parse(string: String) -> Result<Self, ()> {
            Ulid::from_string(&string)
                .map(|ulid| Self(ulid))
                .map_err(|_| ())
        }
    }

    impl AsRef<Ulid> for MyOtherNewTypeId {
        fn as_ref(&self) -> &Ulid {
            &self.0
        }
    }

    #[derive(Debug, Eq, PartialEq)]
    enum MyIdTypesEnum {
        MyNewTypeIdEnumVariant(MyNewTypeId),
        MyOtherNewTypeIdEnumVariant(MyOtherNewTypeId),
    }

    impl From<MyNewTypeId> for MyIdTypesEnum {
        fn from(my_new_type_id: MyNewTypeId) -> Self {
            Self::MyNewTypeIdEnumVariant(my_new_type_id)
        }
    }

    impl From<MyOtherNewTypeId> for MyIdTypesEnum {
        fn from(my_other_new_type_id: MyOtherNewTypeId) -> Self {
            Self::MyOtherNewTypeIdEnumVariant(my_other_new_type_id)
        }
    }

    impl AsRef<Ulid> for MyIdTypesEnum {
        fn as_ref(&self) -> &Ulid {
            match self {
                Self::MyNewTypeIdEnumVariant(my_new_type_id) => my_new_type_id.as_ref(),
                Self::MyOtherNewTypeIdEnumVariant(my_other_new_type_id) => my_other_new_type_id.as_ref(),
            }
        }
    }

    impl fmt::Display for MyIdTypesEnum {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "{}", self.as_ref())
        }
    }

    enum MyTypesEnum {
        MyNewTypeEnumVariant(MyNewType),
        MyOtherNewTypeEnumVariant(MyOtherNewType),
    }

    impl MyTypesEnum {
        pub fn get_id(&self) -> MyIdTypesEnum {
            match self {
                Self::MyNewTypeEnumVariant(my_new_type) => my_new_type.id.into(),
                Self::MyOtherNewTypeEnumVariant(my_other_new_type) => my_other_new_type.id.into(),
            }
        }
    }

    struct SomeOtherStruct {
        pub new_types_id: MyIdTypesEnum,
    }

    let some_other_struct = SomeOtherStruct {
        new_types_id: MyIdTypesEnum::MyNewTypeIdEnumVariant(
            MyNewTypeId::parse("01FJ4HS4AQCEBWTGM951G1NHE6".to_string()).unwrap(),
        ),
    };
    let my_types_enum = MyTypesEnum::MyOtherNewTypeEnumVariant(MyOtherNewType {
        id: MyOtherNewTypeId::parse("01FJ4HS4AQCEBWTGM951G1NHE6".to_string()).unwrap(),
    });
    println!(
        "Hello, broken equality! Equality is: {:?}",
        &some_other_struct.new_types_id == &my_types_enum.get_id()
        // &some_other_struct.new_types_id.to_string() == &my_types_enum.get_id().to_string()
    );
}

Will also follow up with the community and see what they think about this.

Thanks in advance!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.