Giter Club home page Giter Club logo

nim-gatabase's Introduction

Gatabase

screenshot

screenshot

  • Works with ARC, ORC, --panics:on, --experimental:strictFuncs.

Use

Support

  • All SQL standard syntax is supported.
  • -- Human readable comments, multi-line comments produce multi-line SQL comments, requires Stropping.
  • COMMENT, Postgres-only.
  • UNION, UNION ALL.
  • INTERSECT, INTERSECT ALL.
  • EXCEPT, EXCEPT ALL, requires Stropping.
  • CASE with multiple WHEN and 1 ELSE with correct indentation, requires Stropping.
  • INNER JOIN, LEFT JOIN, RIGHT JOIN, FULL JOIN.
  • OFFSET.
  • LIMIT.
  • FROM, requires Stropping.
  • WHERE, WHERE NOT, WHERE EXISTS, WHERE NOT EXISTS.
  • ORDER BY.
  • SELECT, SELECT *, SELECT DISTINCT.
  • SELECT TOP, SELECT MIN, SELECT MAX, SELECT AVG, SELECT SUM, SELECT COUNT.
  • SELECT trim(lower( )) for strings, SELECT round( ) for floats, useful shortcuts.
  • DELETE FROM.
  • LIKE, NOT LIKE.
  • BETWEEN, NOT BETWEEN.
  • HAVING.
  • INSERT INTO.
  • IS NULL, IS NOT NULL.
  • UPDATE, SET.
  • VALUES.
  • DROP TABLE IF EXISTS.
  • CREATE TABLE IF NOT EXISTS.

Not supported:

  • Deep complex nested SubQueries are not supported, because KISS.
  • TRUNCATE, because is the same as DELETE FROM without a WHERE.
  • WHERE IN, WHERE NOT IN, because is the same as JOIN, but JOIN is a lot faster.

API Equivalents

Nim StdLib API Gatabase ORM API
tryExec tryExec
exec exec
getRow getRow
getAllRows getAllRows
getValue getValue
tryInsertID tryInsertID
insertID insertID
execAffectedRows execAffectedRows

Output

Output Gatabase ORM API
bool tryExec
Row getRow
seq[Row] getAllRows
int64 tryInsertID
int64 insertID
int64 execAffectedRows
SqlQuery sqls
any getValue
exec

Install

Comments

-- SQL Comments are supported, but stripped when build for Release. This is SQL.

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

`--` "SQL Comments are supported, but stripped when build for Release. This is Nim."

SELECT & FROM

SELECT *
FROM sometable

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

select '*'
`from` "sometable"

SELECT somecolumn
FROM sometable

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

select "somecolumn"
`from` "sometable"

SELECT DISTINCT somecolumn

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

selectdistinct "somecolumn"

MIN & MAX

SELECT MIN(somecolumn)

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

selectmin "somecolumn"

SELECT MAX(somecolumn)

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

selectmax "somecolumn"

COUNT & AVG & SUM

SELECT COUNT(somecolumn)

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

selectcount "somecolumn"

SELECT AVG(somecolumn)

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

selectavg "somecolumn"

SELECT SUM(somecolumn)

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

selectsum "somecolumn"

TRIM & LOWER

SELECT trim(lower(somestringcolumn))

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

selecttrim "somestringcolumn"

ROUND

SELECT round(somefloatcolumn, 2)

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

selectround2 "somefloatcolumn"

SELECT round(somefloatcolumn, 4)

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

selectround4 "somefloatcolumn"

SELECT round(somefloatcolumn, 6)

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

selectround6 "somefloatcolumn"

TOP

SELECT TOP 5 *

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

selecttop 5

WHERE

SELECT somecolumn
FROM sometable
WHERE power > 9000

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

select "somecolumn"
`from` "sometable"
where "power > 9000"

LIMIT & OFFSET

OFFSET 9
LIMIT 42

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

offset 9
limit 42

INSERT

INSERT INTO person
VALUES (42, 'Nikola Tesla', true, '[email protected]', 9.6)

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

insertinto "person"
values 5

Example:

insertinto "person"
values 5

⬆️ Nim ⬆️          ⬇️ Generated SQL ⬇️

INSERT INTO person
VALUES ( ?, ?, ?, ?, ? )
  • The actual values are passed via varargs directly using stdlib, Gatabase does not format values ever.
  • Nim code values 5 generates VALUES ( ?, ?, ?, ?, ? ).

UPDATE

UPDATE person
SET name = 'Nikola Tesla', mail = '[email protected]'

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

update "person"
set ["name", "mail"]

Example:

update "person"
set ["name", "mail"]

⬆️ Nim ⬆️          ⬇️ Generated SQL ⬇️

UPDATE person
SET name = ?, mail = ?
  • The actual values are passed via varargs directly using stdlib, Gatabase does not format values ever.
  • Nim code set ["key", "other", "another"] generates SET key = ?, other = ?, another = ?.

DELETE

DELETE debts

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

delete "debts"

ORDER BY

ORDER BY ASC

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

orderby "asc"

ORDER BY DESC

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

orderby "desc"

CASE

CASE
  WHEN foo > 10 THEN 9
  WHEN bar < 42 THEN 5
  ELSE 0
END

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

`case` {
  "foo > 10": "9",
  "bar < 42": "5",
  "else":     "0"
}

COMMENT

COMMENT ON TABLE myTable IS 'This is an SQL COMMENT on a TABLE'

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

commentontable {"myTable": "This is an SQL COMMENT on a TABLE"}

COMMENT ON COLUMN myColumn IS 'This is an SQL COMMENT on a COLUMN'

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

commentoncolumn {"myColumn": "This is an SQL COMMENT on a COLUMN"}

COMMENT ON DATABASE myDatabase IS 'This is an SQL COMMENT on a DATABASE'

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

commentondatabase {"myDatabase": "This is an SQL COMMENT on a DATABASE"}

GROUP BY

GROUP BY country

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

groupby "country"

JOIN

FULL JOIN tablename

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

fulljoin "tablename"

INNER JOIN tablename

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

innerjoin "tablename"

LEFT JOIN tablename

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

leftjoin "tablename"

RIGHT JOIN tablename

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

rightjoin "tablename"

HAVING

HAVING beer > 5

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

having "beer > 5"

UNION

UNION ALL

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

union true

UNION

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

union false

INTERSECT

INTERSECT ALL

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

intersect true

INTERSECT

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

intersect false

EXCEPT

EXCEPT ALL

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

`except` true

EXCEPT

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

`except` false

IS NULL

IS NULL

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

isnull true

IS NOT NULL

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

isnull false

DROP TABLE

DROP TABLE IF EXISTS tablename

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

dropTable "tablename"

CREATE TABLE

CREATE TABLE IF NOT EXISTS kitten(
  id    INTEGER     PRIMARY KEY,
  age   INTEGER     NOT NULL  DEFAULT 1,
  sex   VARCHAR(1)  NOT NULL  DEFAULT 'f',
  name  TEXT        NOT NULL  DEFAULT 'fluffy',
  rank  REAL        NOT NULL  DEFAULT 3.14,
);

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

let myTable = createTable "kitten": [
  "age"  := 1,
  "sex"  := 'f',
  "name" := "fluffy",
  "rank" := 3.14,
]

No default values:

CREATE TABLE IF NOT EXISTS kitten(
  id    INTEGER     PRIMARY KEY,
  age   INTEGER,
  sex   VARCHAR(1),
  name  TEXT,
  rank  REAL,
);

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

let myTable = createTable "kitten": [
  "age"  := int,
  "sex"  := char,
  "name" := string,
  "rank" := float,
]

More examples:

CREATE TABLE IF NOT EXISTS kitten(
  id    INTEGER     PRIMARY KEY,
  age   INTEGER     NOT NULL  DEFAULT 1,
  sex   VARCHAR(1),
);

⬆️ SQL ⬆️          ⬇️ Nim ⬇️

let myTable = createTable "kitten": [
  "age"  := 1,
  "sex"  := char,
]

And more examples: https://github.com/juancarlospaco/nim-gatabase/blob/master/examples/database_fields_example.nim#L1


Wildcards

  • Nim '*' ➡️ SQL *.
  • Nim '?' ➡️ SQL ?.

Anti-Obfuscation

Gatabase wont like Obfuscation, its code is easy to read and similar to Pretty-Printed SQL. nimpretty friendly. Very KISS.

Compiles Ok:

let variable = sqls:
  select  '*'
  `from`  "clients"
  groupby "country"
  orderby AscNullsLast

Fails to Compile:

  • let variable = sqls: select('*') from("clients") groupby("country") orderby(AscNullsLast)
  • let variable = sqls: '*'.select() "clients".from() "country".groupby() AscNullsLast.orderby()
  • let variable = sqls: select '*' from "clients" groupby "country" orderby AscNullsLast
  • let variable = sqls:select'*' from"clients" groupby"country" orderby AscNullsLast

This helps on big projects where each developer tries to use a different code style.

Your data, your way

Nim has template is like a literal copy&paste of code in-place with no performance cost, that allows you to create your own custom ORM function callbacks on-the-fly, like the ones used on scripting languages.

template getMemes(): string =
  result = [].getValue:
    select "url"
    `from` "memes"
    limit 1

Then you do getMemes() when you need it❕. The API that fits your ideas.

From this MyClass.meta.Session.query(Memes).all().filter().first() to this getMemes().

For Python Devs

Remember on Python2 you had like print "value"?, on Nim you can do the same for any function, then we made functions to mimic basic standard SQL, like select "value" and it worked, its Type-Safe and valid Nim code, you have an ORM that gives you the freedom and power, this allows to support interesting features, like CASE, UNION, INTERSECT, COMMENT, etc.

When you get used to template it requires a lot less code to do the same than SQLAlchemy.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from sqlalchemy import create_engine, MetaData, Table
from sqlalchemy import Column, Integer, String, Boolean, Float

engine = create_engine("sqlite:///:memory:", echo=False)
engine.execute("""
  create table if not exists person(
    id      integer     primary key,
    name    varchar(9)  not null unique,
    active  bool        not null default true,
    rank    float       not null default 0.0
  ); """
)


meta = MetaData()
persons = Table(
  "person", meta,
  Column("id", Integer, primary_key = True),
  Column("name", String, nullable = False, unique = True),
  Column("active", Boolean, nullable = False, default = True),
  Column("rank", Float, nullable = False, default = 0.0),
)


conn = engine.connect()


ins = persons.insert()
ins = persons.insert().values(id = 42, name = "Pepe", active = True, rank = 9.6)
result = conn.execute(ins)


persons_query = persons.select()
result = conn.execute(persons_query)
row = result.fetchone()

print(row)

⬆️ CPython 3 + SQLAlchemy ⬆️          ⬇️ Nim 1.0 + Gatabase ⬇️

import db_sqlite, gatabase

let db = open(":memory:", "", "", "")
db.exec(sql"""
  create table if not exists person(
    id      integer     primary key,
    name    varchar(9)  not null unique,
    active  bool        not null default true,
    rank    float       not null default 0.0
  ); """)


exec [42, "Pepe", true, 9.6]:
  insertinto "person"
  values 4


let row = [].getRow:
  select '*'
  `from` "person"

echo row

Smart SQL Checking

screenshot

It will perform a SQL Syntax checking at compile-time. Examples here Fail intentionally as expected:

exec []:
  where "failure"

Fails to compile as expected, with a friendly error:

gatabase.nim(48, 16) Warning: WHERE without SELECT nor INSERT nor UPDATE nor DELETE.

Typical error of making a DELETE FROM without WHERE that deletes all your data:

exec []:
  delete "users"

Compiles but prints a friendly warning:

gatabase.nim(207, 57) Warning: DELETE FROM without WHERE.

Typical bad practice of using SELECT * everywhere:

exec []:
  select '*'

Compiles but prints a friendly warning:

gatabase.nim(20, 50) Warning: SELECT * is bad practice.

Non-SQL wont compile, even if its valid Nim:

sqls:
  discard

sqls:
  echo "This is not SQL, wont compile"

Gatabase Diagrams

Tests

$ nimble test

[Suite] Gatabase ORM Tests
  [OK] let   INSERT INTO
  [OK] let   SELECT ... FROM ... WHERE
  [OK] let   SELECT ... (comment) ... FROM ... COMMENT
  [OK] let   SELECT ... FROM ... LIMIT ... OFFSET
  [OK] let   INSERT INTO
  [OK] let   UNION ALL ... ORBER BY ... IS NOT NULL
  [OK] let   SELECT DISTINCT ... FROM ... WHERE
  [OK] let INSERT INTO
  [OK] const SELECT ... FROM ... WHERE
  [OK] const SELECT ... (comment) ... FROM ... COMMENT
  [OK] const SELECT ... FROM ... LIMIT ... OFFSET
  [OK] const INSERT INTO
  [OK] const UNION ALL ... ORBER BY ... IS NOT NULL
  [OK] const INTERSECT ALL
  [OK] const EXCEPT ALL
  [OK] const SELECT DISTINCT ... FROM ... WHERE
  [OK] var   CASE
  [OK] var   SELECT MAX .. WHERE EXISTS ... OFFSET ... LIMIT ... ORDER BY
  [OK] SELECT TRIM
  [OK] SELECT ROUND
  [OK] var   DELETE FROM WHERE

Requisites

  • None.

Stars

Stars over time

FAQ

  • This is not an ORM ?.

Wikipedia defines ORM as:

Object-relational mapping in computer science is a programming technique for converting data between incompatible type systems using object-oriented programming languages.

Feel free to contribute to Wikipedia.

  • Supports SQLite ?.

Yes.

  • Supports MySQL ?.

No.

  • Will support MySQL someday ?.

No.

  • Supports Mongo ?.

No.

  • Will support Mongo someday ?.

No.

  • How is Parameter substitution done ?.

It does NOT make Parameter substitution internally, its delegated to standard library.

  • This works with Synchronous code ?.

Yes.

  • This works with Asynchronous code ?.

Yes.

  • SQLite mode dont support some stuff ?.

We try to keep as similar as possible, but SQLite is very limited.

⬆️ ⬆️ ⬆️ ⬆️

nim-gatabase's People

Contributors

juancarlospaco avatar on-kato avatar

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

nim-gatabase's Issues

Sugar alias doesn't work with float

I am quite new to Nim. I tried using the sugar templates with strutils but I ran into a problem with floats.

The row template for float numbers uses parseInt, which accepts a string, not a float.

For example the following code

import db_sqlite, strutils
include gatabase/sugar

let a: Row = @["10.0"]

0.0.a

Raises the following error.

Error: type mismatch: got <float64>
but expected one of: 
proc parseInt(s: string): int
  first type mismatch at position: 1
  required type for s: string
  but expression '0.0' is of type: float64

expression: parseInt(0.0)

Perhaps just use type casting?

How to deal with the null insert/update?

For the following statement using db_postgres:

db.exec(sql"insert into data (name) values (?)", name)

the name can be null or some(value), is there any facility around this optional concept so we can deal with some(value) or none in both case without overhead of writing different sql string?

Example code can't compile

Test code below copied from README.md :

import db_sqlite, gatabase

let db = open(":memory:", "", "", "")
db.exec(sql"""
  create table if not exists person(
    id      integer     primary key,
    name    varchar(9)  not null unique,
    active  bool        not null default true,
    rank    float       not null default 0.0
  ); """)

exec [42, "Pepe", true, 9.6]:
  insertinto "person"
  values 4

let row = [].getRow:
  select '*'
  `from` "person"

echo row

It compiles fail:

$ nim c -r test.nim
test.nim(12, 11) Error: type mismatch: got 'string' for '"Pepe"' but expected 'int literal(42)'

$ nim -v
Nim Compiler Version 1.6.8 [MacOSX: arm64]
Compiled at 2022-09-27
Copyright (c) 2006-2021 by Andreas Rumpf

active boot switches: -d:release -d:nimUseLinenoise

Document Async & Pooling Usage

Using Gatabase in an async CRUD application would be delightful.

It'd be good to have documentation detailing how to use Gatabase in the context of a web server with async endpoints.

Circular dependency with creating more than one table with `createTable`

"""
nim c -r dbConnect.nim
Hint: used config file '/home/nixfreak/.choosenim/toolchains/nim-1.6.6/config/nim.cfg' [Conf]
Hint: used config file '/home/nixfreak/.choosenim/toolchains/nim-1.6.6/config/config.nims' [Conf]
.................................................................................
/home/nixfreak/.choosenim/toolchains/nim-1.6.6/lib/impure/db_sqlite.nim(184, 1) Warning: Circular dependency detected. codeReordering pragma may not be able to reorder some nodes properly [User]
..
/home/nixfreak/nim/myBlog/dbConnect.nim(21, 30) template/generic instantiation of createTable from here
/home/nixfreak/nim/myBlog/dbConnect.nim(22, 13) Error: ambiguous call; both dbConnect.:=(dbfieldgensym0: static[string], valuegensym0: typedesc[SomeInteger]) [template declared in /home/nixfreak/.nimble/pkgs/gatabase-0.9.9/gatabase/sugar.nim(92, 12)] and dbConnect.:=(dbfieldgensym21: static[string], valuegensym21: typedesc[SomeInteger]) [template declared in /home/nixfreak/.nimble/pkgs/gatabase-0.9.9/gatabase/sugar.nim(92, 12)] match for: (string, typedesc[int])
"""

import std/db_postgres
import gatabase/sugar

# Connect to database (postgres)
let db  = open("localhost", "user", "password", "dbname")
db.close()

let userTable = createTable "users": [
  "id" := int,
  "fullname" := string,
  "username" := string,
  "password" := string
]
  
let postsTable = createTable "posts": [
  "post_id" := int,
  "author_id" := int,
  "created" := int,
  "title" := string,
  "body" := string
  
]

UPDATE not supported

UPDATE is currently commented/removed out of the code,
I can not find a way to implement it properly,
the complicated part is doing the SET key = value, key = value;,
the point is that we do the replacement using standard library ? on the sql().

  • The keys can be just a few not all, the keys can be on any order.
  • json wont work at compile time, so is out.
  • Table[string, string] you can only pass string values.
  • tuple with keys, I can not find a way to strip they keys from the tuple inside a macro.

The commented out API is like:

update "tablename"
`set` {"key0": "value0", "key1": "value1"}

This only allows string values.

  • Probably the tuple with keys is the way to go. 🤔

createTable sql error, generate comma in last query field

First of all sorry for my english

Repro

import db_sqlite
import gatabase
include gatabase/sugar

let db = open("data.sqlite3", "", "", "")
        
let myTable = createTable "kitten": [
  "age"    := 1,
  "sex"    := 'f',
  "name"   := "unnamed",
  "rank"   := 3.14,
  "weight" := int,
  "color"  := char,
  "owner"  := string,
  "food"   := float,
]

echo myTable.string

db.tryExec(myTable).echo

Result

Hint: C:\Users\nirn2\nimcache\repro_d\repro.exe  [Exec]
CREATE TABLE IF NOT EXISTS kitten(
  id    INTEGER PRIMARY KEY,
        age     INTEGER NOT NULL        DEFAULT 1,
        sex     VARCHAR(1)      NOT NULL        DEFAULT 'f',
        name    TEXT    NOT NULL        DEFAULT 'unnamed',
        rank    REAL    NOT NULL        DEFAULT 3.14,
        weight  INTEGER,
        color   VARCHAR(1),
        owner   TEXT,
        food    REAL,
);
false

When createTable generate fields, it alwayes add a comma as last character, if delete comma in last field SqlQuery is ok

createTable
...
  for field in code: cueri.add field
  cueri.delete(cueri.len-2, cueri.len-2) #import strutils for test
  cueri.add ");" # http://blog.2ndquadrant.com/postgresql-10-identity-columns 
  sql(cueri)

Result

Hint: C:\Users\nirn2\nimcache\repro_d\repro.exe  [Exec]
CREATE TABLE IF NOT EXISTS kitten(
id    INTEGER PRIMARY KEY,
      age     INTEGER NOT NULL        DEFAULT 1,
      sex     VARCHAR(1)      NOT NULL        DEFAULT 'f',
      name    TEXT    NOT NULL        DEFAULT 'unnamed',
      rank    REAL    NOT NULL        DEFAULT 3.14,
      weight  INTEGER,
      color   VARCHAR(1),
      owner   TEXT,
      food    REAL
);
true

Sql error

G:\Programming\Languages\Nim\HelloTwo\repro.nim(20) repro
G:\Programming\nim\lib\impure\db_sqlite.nim(227) exec
G:\Programming\nim\lib\impure\db_sqlite.nim(147) dbError
Error: unhandled exception: near ")": syntax error [DbError]

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.