Git Product home page Git Product logo

tablecloth'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  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tablecloth's Issues

Add String.splitAt

This should work just like the functionList:

val splitAt: string -> index:int -> (string * string)

Fold signature - `t` first / last

Currently Tablecloth sticks to Elm's API by naively using flip in the implementation of fold for Lists / Arrays.

@j-m-hoffman showed this had some pretty disastrous consequences for performance in a benchmark so it looks like we have two options:

  • Revert to the 'b -> 'a -> 'b type signature
  • Re-implement foldLeft / foldRight from scratch to have the Elm signatures.

It's actually kinda a big deal, as making it ergonomic to use with the rest of the library means we have to ensure all of the other modules have API's that permit "non-flipped" usage

e.g. Having foldLeft take f : 'a -> 'b -> 'b means that we should also make sure that the signature for Set.add is 'a -> 'a t -> 'a t.

This means making a commitment to either:

  1. t-last like Elm
  2. t-first like Belt
  3. t-middle aka all of the optional arguments come first, then the t then any non-labelled arguments then the labelled arguments aka the approach of Base / Core.

I would be in favour of either 1 or 3, I think being able to consistently use |> is important:

  • I am not a huge fan of the mental overhead of having to switch between -> and |> when using Belt
  • It actually gets a lot of recognition outside of the OCaml ecosystem thanks to the likes of Elixir and some JS proposals.
  • I think labels provide a great user experience compared the the API on offer in Elm / Belt, and usage of labels is one of the stated goal's of Tablecloth.

Originally posted by @Dean177 in #27

Error when compiling

I got this error after installing and compiliing with the lastest bucklescript

  Warning number 3
  /home/liftit/code/ml/redemo/node_modules/tablecloth-bucklescript/src/tablecloth.ml 797:54-63

  795 ┆ let regex s : Js.Re.t = Js.Re.fromStringWithFlags ~flags:"g" s
  796 ┆
  797 ┆ let contains ~(re : Js.Re.t) (s : string) : bool = Js.Re.test s re
  798 ┆
  799 ┆ let replace ~(re : Js.Re.t) ~(repl : string) (str : string) =

  deprecated: Js.Re.test
Please use Js.Re.test_ instead

  Warning number 3
  /home/liftit/code/ml/redemo/node_modules/tablecloth-bucklescript/src/tablecloth.ml 804:5-14

  802 │
  803 │   let matches ~(re : Js.Re.t) (s : string) : Js.Re.result option =
  804 │     Js.Re.exec s re
  805 │ end

Looks like the definitions of these functions changed

Js_of_ocaml support

I don't think one need to differentiate 'native' and 'js_of_ocaml'.
Just removing the dependency on Str will make the lib fully usable with js_of_ocaml.

Str is only used to split strings. One should be able to achieve the same thing with a bit of glue around String.Search_pattern.index_all

Add Option.unwrapLazy

This should work the same way as Option.unwrap but the default value should be a 'a lazy rather than a 'a

getLazy : 'a option -> default:('a Lazy.t) -> 'a

String.to_list doesn't return a char list in bucklescript

In the bucklescript code path, String.to_list produces a list of something that is not quite an ocaml char, so that a |> String.to_list |> String.from_list does not give back a, and attempting to do anything that expects an ocaml char with the contents of String.to_list a gives unpredictable results (mostly i seem to get a bunch of 0x0 unless i pass it straight back to a Js function).

overdue for a new release

npm install tablecloth-bucklescript currently installs a version from over 100 commits ago - could you please make a new release?

(also if the project can be installed directly from github, could you add instructions to the readme?)

Dedup test cases?

This is sort of a wishlist / half-baked idea. Is there a clever way for us to share the test cases between bs and native?

This may be over-engineered, but perhaps an S-expression DSL that could compile into test cases would be simple enough for us to seriously DRY up the testing code and ensure that our assertions are aligned between bs and native.

Or maybe there's a better way to design this that wouldn't involve writing a DSL compiler if it were just native OCaml.

Here's a sketch of a datastructure that I think could help dedup a lot of the assertions:

(List
  (init
    ("returns None for an empty list" [] None)
    ("returns an empty list for a single item list" ['a'] (Some []))
    ("returns all but the last item for a list with two or more items" ['a';'b';'c';'d'] ['a';'b';'c'])))

Like I said, this is half-baked, and I think an OCaml solution would be preferable. Just wanted to raise the issue before forgetting.

Specific issues to contribute to

Hi all! So I'm a college student who loves Reason/OCaml, and also have my college's annual Hackathon next weekend. What's cool about the University of Illinois' Hackathon is that it's fully open-source; this got me thinking, what better project to do then to contribute to a standard library!

If one of the maintainers could post several issues (new algorithms/methods, documentation fixes, etc.) they think I could tackle in a 36 hour period, that'd be awesome! If there are enough doable things I can contribute to, I definitely have a shot at winning 😄

Thanks in advance!

Why is Option.foldrValues in the .mli file?

It looks as if foldrValues is a helper function for values. (What it really does is prepend an item to a list, so its name as a stand-alone function is not very meaningful.)

Suggestion: remove it from the .mli file, and nest the function in the values function in the .ml file. This would also allow you to get rid of foldr_values, which does not appear to be used anywhere else.

duplicated function

We have got flatten and concat which do the same.
Elm and Base use concat.
Remove flatten?

Add String.sliding / chunksOf

Array has the sliding and chunksOf functions:

val sliding : ?step:int -> 'a t -> size:int -> 'a t t
   (** Provides a sliding 'window' of sub-arrays over an array.

     The first sub-array starts at index [0] of the array and takes the first [size] elements.

     The sub-array then advances the index [step] (which defaults to 1) positions before taking the next [size] elements.

     The sub-arrays are guaranteed to always be of length [size] and iteration stops once a sub-array would extend beyond the end of the array.

     {[Array.sliding [|1;2;3;4;5|] ~size:1 = [|[|1|]; [|2|]; [|3|]; [|4|]; [|5|]|] ]}

     {[Array.sliding [|1;2;3;4;5|] ~size:2 = [|[|1;2|]; [|2;3|]; [|3;4|]; [|4;5|]|] ]}

     {[Array.sliding [|1;2;3;4;5|] ~size:3 = [|[|1;2;3|]; [|2;3;4|]; [|3;4;5|]|] ]}

     {[Array.sliding [|1;2;3;4;5|] ~size:2 ~step:2 = [|[|1;2|]; [|3;4|]|] ]}

     {[Array.sliding [|1;2;3;4;5|] ~size:1 ~step:3 = [|[|1|]; [|4|]|] ]}
   *)

let chunksOf t ~size = sliding t ~step:size ~size

These would be good additions to the String module too

Surprising return from insert_at

This surprised me:

insert_at ~index:(-1) ~value:999 [100;101;102;103] = [999]
insert_at ~index:5 ~value:999 [100;101;102;103] = [999]

Looking at the code, this happens because take and drop return the empty list when given values that are out of bounds.

However, I would expect the function to do one of these:

  • Return a list the same as the original when given values that are out of bounds, or
  • Insert the value at the beginning for negative values, and at the end for values greater than the list length

Crashes VS-Code extension?

As soon as I install tablecloth-bucklescript (following the instructions in the README) to a project and open it in VS Code, the reason language server crashes and all the reason-specific IDE features disappear. My logs say

Fatal error: exception Failure("Invalid subdirs entry")
Raised at file "stdlib.ml", line 33, characters 17-33
Called from file "src/analyze/FindFiles.re", line 49, characters 44-76
Called from file "list.ml", line 88, characters 20-23
Called from file "src/analyze/FindFiles.re", line 35, characters 30-69
Called from file "util/Infix.re", line 17, characters 68-73
Called from file "src/analyze/FindFiles.re", line 55, characters 2-51
Called from file "src/analyze/FindFiles.re", line 313, characters 19-70
Called from file "util/Infix.re" (inlined), line 16, characters 62-67
Called from file "src/analyze/FindFiles.re", line 303, characters 11-1023
Called from file "list.ml", line 88, characters 20-23
Called from file "src/analyze/FindFiles.re", line 300, characters 4-1023
Called from file "src/analyze/State.re", line 160, characters 55-129
Called from file "src/analyze/State.re", line 583, characters 24-73
Called from file "src/lsp/MessageHandlers.re" (inlined), line 15, characters 17-92
Called from file "src/lsp/MessageHandlers.re", line 296, characters 11-35
Called from file "src/lsp/BasicServer.re", line 44, characters 17-39
Called from file "src/lsp/BasicServer.re", line 111, characters 61-123
Called from file "src/lsp/Main.re", line 130, characters 6-255
Called from file "bin/Bin.re", line 3, characters 9-24
[Info - 1:51:43 PM] Connection to server got closed. Server will restart.

It then restarts 4 times before giving up entirely.

I'm using Windows 10, and VS-Code v.1.33.0 (user setup), the latest extension "reason-vscode" v. 1.5.2 (by Jared Forsyth). And I'm writing my code in BuckleScript (OCaml syntax) not the Reason syntax.

I have a minimal repro here. If I do npm install and npm run build, and then open it in VS Code, the language server will crash. If I remove "tablecloth-bucklescript" from bsconfig.json and reopen VS Code, it'll open fine with all IDE features working.

Any ideas where to start looking to get to the bottom of this? I'm not even sure where to start.

Add ocamlformat

We should ocamlformat using:

profile = sparse
break-string-literals = never
escape-strings = preserve

This would be a check in CI (once it's set up) to verify that everything is properly formatted.

Add Option.unwrapLazy

This should work the same way as Result.unwrap but the default value should be a 'a lazy rather than a 'a

val getLazy : 'a Result. -> default:('a Lazy.t) -> 'a

Add List.partitionMap

 (* Partition into two lists, of potentially different type, using function
   * `f`.  Returns value in the first list for `Left` and second list for
   * `Right`. *)
  let partitionMap (items : 'c list) ~(f : 'c -> ('a, 'b) Either.t): ('a list * 'b list) =
    TableclothList.foldRight
      ~initial:([], [])
      ~f:(fun (lefts, rights) item ->
        match f item with
        | Left a ->
            (a :: lefts, rights)
        | Right b ->
            (lefts, b :: rights))
      items

foldl vs. List.fold_left

These produce different output:

let _ = Js.log(foldl ~f:(-) ~init:0 [1;2;3]) (* output is 2 *)
let _ = Js.log(List.fold_left (-) 0 [1;2;3]) (* output is -6 *)

It looks like the reducer function forList.fold_left takes the accumulator and list item as its parameters (in that order); foldl takes the list item and accumulator.

Possible performance issue: the code for foldl in bs/src/tablecloth.ml uses List.fold_right, which is not tail recursive, according to http://caml.inria.fr/pub/docs/manual-ocaml/libref/List.html

Upstream from Dark repo

Making a note of the functions we've written in the darklang repo that I haven't upstreamed yet:

Here's the bucklescript functions:

module Array = struct
  let iter ~(f : 'a -> unit) (arr : 'a array) : unit = Belt.Array.forEach arr f
end

module Option = struct
  let exec ~(f : 'a -> unit) (v : 'a option) : unit =
    match v with Some v -> f v | None -> ()

  let orLazy (v : 'a option) (v2 : unit -> 'a option) : 'a option =
    match v with Some v -> Some v | None -> v2 ()

  let orElseLazy (v : unit -> 'a option) (v2 : 'a option) : 'a option =
    match v2 with Some v2 -> Some v2 | None -> v ()

  let pair (a : 'a option) (b : 'b option) : ('a * 'b) option =
    match (a, b) with Some a, Some b -> Some (a, b) | _ -> None

  let map2 (a : 'a option) (b : 'b option) ~(f : 'a -> 'b -> 'c) : 'c option =
    match (a, b) with Some a, Some b -> Some (f a b) | _ -> None

  let andThen2 (a : 'a option) (b : 'b option) ~(f : 'a -> 'b -> 'c option) :
      'c option =
    match (a, b) with Some a, Some b -> f a b | _ -> None

  let isSomeEqualTo ~(value : 'a) (o : 'a option) : bool = Some value = o

  (* If a is some, then apply fn to a, return both a and the result.
    if either a or b is none, then return none
  *)
  let thenAlso (a : 'a option) ~(f : 'a -> 'b option) : ('a * 'b) option =
    let b = andThen ~f a in
    pair a b

  let withDefaultLazy (a : 'a option) ~(default : unit -> 'a) : 'a =
    match a with Some a -> a | None -> default ()
end

module Result = struct
  let isOk (v : ('err, 'ok) t) : bool = match v with Ok _ -> true | _ -> false
end

module List = struct
  (* From https://github.com/janestreet/base/blob/eaab227499b36bb90c2537bc6358a2d5caf75227/src/list.ml#L247 *)
  let findMap t ~f =
    let rec loop = function
      | [] ->
          None
      | x :: l ->
        (match f x with None -> loop l | Some _ as r -> r)
    in
    loop t


  let findWithIndex ~(f : int -> 'a -> bool) (l : 'a list) : int option =
    let rec findIndexHelper
        ~(i : int) ~(predicate : int -> 'a -> bool) (l : 'a list) : int option =
      match l with
      | [] ->
          None
      | x :: rest ->
          if predicate i x
          then Some i
          else findIndexHelper ~i:(i + 1) ~predicate rest
    in
    findIndexHelper ~i:0 ~predicate:f l

  let rec dropLeft ~(count : int) (l : 'a list) : 'a list =
    if count <= 0
    then l
    else
      match l with
      | [] ->
          []
      | [_] ->
          []
      | _ :: rest ->
          dropLeft ~count:(count - 1) rest

  let dropRight ~(count : int) (l : 'a list) : 'a list =
    l |> reverse |> dropLeft ~count |> reverse

  let range (start : int) (end_ : int) : 'a list =
    let length = end_ - start in
    if length < 0 then [] else Belt.List.makeBy length (fun i -> i + start)

  (* Takes everything before and after, but not including nexted element *)
  let splitOn ~(index : int) (l : 'a list) : 'a list * 'a list =
    (take ~count:index l, drop ~count:(index + 1) l)

  (* Moves item in oldPos into the position at newPos, pushing the element already at newPos down. Ex:
    l = [a b c d]
    moveInto 3 1 l, takes d and moves it between a & b. => [a d b c]
    NOTE: This is not swapping the elements in newPos & oldPos
  *)
  let moveInto ~(oldPos : int) ~(newPos : int) (l : 'a list) : 'a list =
    match getAt ~index:oldPos l with
    | Some value ->
        let index =
          (* Checks to see if we need to offset the newPos by -1, after removing the element at oldPos *)
          if newPos > oldPos
          then
            let len = List.length l in
            (* Clamp at list length to prevent overflow *)
            if newPos > len then len - 1 else newPos - 1
          else newPos
        in
        l |> removeAt ~index:oldPos |> insertAt ~index ~value
    | None ->
        l

  (* Partition into two lists, of potentially different type, using function
   * `f`.  Returns value in the first list for `Left` and second list for
   * `Right`. *)
  let partitionMap ~(f : 'c -> ('a, 'b) Either.t) (items : 'c list) :
      'a list * 'b list =
    Tablecloth.List.foldr
      ~init:([], [])
      ~f:(fun item (lefts, rights) ->
        match f item with
        | Left a ->
            (a :: lefts, rights)
        | Right b ->
            (lefts, b :: rights))
      items
end

module Float = struct
  let toString (f : float) : string = Js.Float.toString f
end

module Int = struct
  let toString (i : int) : string = Js.Int.toString i
end

module String = struct
  let splitAt ~(index : int) (s : string) : string * string =
    (slice ~from:0 ~to_:index s, slice ~from:index ~to_:(length s) s)

  let left ~(count : int) (s : string) : string = slice ~from:0 ~to_:count s

  let rec segment ~(size : int) (s : string) : string list =
    let front, back = splitAt ~index:size s in
    if back = "" then [front] else front :: segment ~size back

  let replaceChunk ~(from : int) ~(to_ : int) ~(replacement : string) s : string
      =
    slice ~from:0 ~to_:from s ^ replacement ^ slice ~from:to_ ~to_:(length s) s

  (* returns the index of the last occurrence of character c in string s before position i+1 or None if c does not occur in s before position i+1. *)
  let rindex_from_opt ~(pos : int) (s : string) (c : char) : int option =
    String.rindex_from_opt s pos c

  (* returns the index of the first occurrence of character c in string s after position i or None if c does not occur in s after position i.
 *)
  let index_from_opt ~(pos : int) (s : string) (c : char) : int option =
    String.index_from_opt s pos c
end

module StrDict = struct
  let values (dict : 'a t) : 'a list =
    Belt.Map.String.valuesToArray dict |> Array.toList

  let updateIfPresent ~(key : key) ~(f : 'v -> 'v) (dict : 'value t) : 'value t
      =
    update ~key ~f:(Option.map ~f) dict

  let mergeLeft (dict1 : 'v t) (dict2 : 'v t) : 'v t =
    Tablecloth.StrDict.merge
      ~f:(fun (_key : string) (v1 : 'v option) (v2 : 'v option) ->
        match (v1, v2) with Some _, _ -> v1 | None, _ -> v2)
      dict1
      dict2

  let mergeRight (dict1 : 'v t) (dict2 : 'v t) : 'v t =
    Tablecloth.StrDict.merge
      ~f:(fun (_key : string) (v1 : 'v option) (v2 : 'v option) ->
        match (v1, v2) with _, Some _ -> v2 | _, None -> v1)
      dict1
      dict2

  let count (dict : 'v t) = Belt.Map.String.size dict

  let mapValues (dict : 'v t) ~(f : 'v -> 'x) : 'x list =
    dict |> values |> List.map ~f

  let filterMapValues (dict : 'v t) ~(f : 'v -> 'x option) : 'x list =
    dict |> values |> List.filterMap ~f

  let mapWithKey (dict : 'v t) ~(f : key:string -> 'v -> 'x) : 'x t =
    Belt.Map.String.mapWithKey dict (fun key v -> f ~key v)

  let remove (dict : 'v t) ~(key : key) : 'v t = Belt.Map.String.remove dict key

  let removeMany (dict : 'v t) ~(keys : key list) : 'v t =
    Belt.Map.String.removeMany dict (Belt.List.toArray keys)

  let singleton ~(key : key) ~(value : 'v) : 'v t = empty |> insert ~key ~value

  let has ~key (dict : 'v t) : bool = Belt.Map.String.has dict key
end


module StrSet = struct
  let removeMany ~(values : string list) (set : t) : t =
    Belt.Set.String.removeMany set (Array.fromList values)

  let eq = Belt.Set.String.eq
end

Add Map.count

This should work the same way as Array.count and List.count

Result.map inconsistent with Option.map

Option.map uses a labelled parameter for the function and therefore can be called either subject first or subject last (meaning it is interchangeable with -> and |>). However, Result.map does not use a labelled parameter for the function meaning it can only be called subject-last, meaning it only works with |>. Is there a reason for this inconsistency? Would you accept a PR updating Result to work like Option?

bs-platform 7.1.0 not compatible

 
  Warning number 3 (configured as error) 
  node_modules/tablecloth-bucklescript/src/tablecloth.ml 806:47-63
  
  804 ┆ let capitalize (s : string) : string = String.capitalize s
  805 ┆ 
  806 ┆ let isCapitalized (s : string) : bool = s = String.capitalize s
  807 ┆ 
  808 ┆ let is_capitalized = isCapitalized
  
  deprecated: String.capitalize
Use String.capitalize_ascii instead.
  
  We've found a bug for you!
  node_modules/tablecloth-bucklescript/src/tablecloth.ml
  
  The implementation node_modules/tablecloth-bucklescript/src/tablecloth.ml
       does not match the interface src/tablecloth.cmi:
       ...
       In module Array:
       Values do not match:
         val empty : '_a array
       is not included in
         val empty : 'a array
       File "node_modules/tablecloth-bucklescript/src/tablecloth.mli", line 77, characters 2-22:
         Expected declaration
       File "node_modules/tablecloth-bucklescript/src/tablecloth.ml", line 12, characters 6-11:
         Actual declaration

Documentation?

Is any documentation generated? Or is the recommendation to read the .mli files?

Why is Float.radians the same as identity?

Since Float.degrees converts degrees to radians, Float.radians should do the complementary operation of converting radians to degrees. This would be useful for any user-facing program involving angles, as non-mathematicians think of angles in degrees and expect to see both input and output in those units. (Either that or implement functions with names to_radiansand to_degrees.)

Add Option.thenAlso

Sample implemenetation

  (* If a is some, then apply fn to a, return both a and the result.
    if either a or b is none, then return none
  *)
  let thenAlso (a : 'a option) ~(f : 'a -> 'b option) : ('a * 'b) option =
    let b = andThen ~f a in
    pair a b

`Array.get(~index=0, xs)` gets rewritten to `0[xs]`

Possibly this is a Reason issue, not a Tablecloth one, but I'm too new to distinguish, so apologies:

open Tablecloth;
let x = Array.get(~index=0, xs);

gets rewritten by refmt to

open Tablecloth;
let x = 0[xs];

which is surprising to me at least. (I'm new to both Ocaml and Reason, so maybe I have different expectations.) Note that I originally wrote xs[0] but that's a type error...

Another related issue is that 0[xs], even if it is intended syntax, triggers a warning about omitted labels.

My workaround is to write

open Tablecloth;
let x = Tablecloth.Array.get(~index=0, xs);

Add Map.insert_or_fail

This function should have the following signature:

val insertNoOverride :  'key 'value 'id Map.t -> key:'key -> 'value -> ( 'key 'value 'id Map.t) 'value Result.t

It should return an Ok containing the newly update map *if key has not already been set.

If it has it should return an Error containing the existing value for the key.

Open to suggestions for the name. Some inspiration:

  • (insert / add)NoOverride
  • insertOrFail
  • insertUnique

Add List.partitionMap

This would allow you to transform a list, returning either a Left or a Right to signal which component of the tuple the result should end up in

let partitionMap : 'a list -> f:('a -> ('b, 'c) Either.t)  -> 'b list * 'c list

parameter names all over the place

in List, list parameter is named l, ls or xs.
think we should stick to one.
i often renamed ls to l and chose a in the Array module,
but xs would make it more uniform.
Suggestions?

MLI Link is broken

Looks like sometime today (or yesterday evening) the MLI link on the readme was broken. It pointed to here: https://github.com/darklang/tablecloth/blob/master/bs/src/tablecloth.mli

It looks like the bs folder got renamed to bucklescript, which caused that path to change. Seems important to fix, since it's probably the first thing people try to access when they reach the repository. 

The new link should point here: https://github.com/darklang/tablecloth/blob/master/bucklescript/src/tablecloth.mli

Feature Request: String.get, String.indexOf

Hey all, thanks for the hard work on Tablecloth (and Standard) and for deciding to merge the two together, I think it's the right move even though compromises like that can be very tough!

Would the two following operations make sense to add? No preference for naming.

String.get exists in Standard which is handy since the Stdlib-version throws.
String.indexOf exists in Stdlib as index_opt

EDIT: Semi-related and I realise this may be personal preference, but I also kind of miss utility-functions like String.isEmpty(foo) vs doing foo == ""

Fix CircleCI

I hooked up tablecloth to CircleCI, but I didn't make any effort to make it work.

There's two test scripts that could be run. It might be necessary to use esy or similar to have a repeatable install for opam packages.

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.