Giter VIP home page Giter VIP logo

shahnama's Introduction

Shahnama

This repository contains the Shahnama software developed at Caret, University of Cambridge and licensed open source under the AGPL license.

Before using please read the LICENSE file contained in this repository.

Before contributing please read the LICENSE and NOTICE file contained within this repository.

We have chosen to make this software open source to allow others to use it and we have chosen the AGPL license to encourage others to contribute modifications and extensions back to the wider community in some form. Obviously we would not expect or ask anyone to contribute modifications and extensions containing to private or security related information back to the wider community.

Introduction

The software is a standard DJango application for installation and deployment consult the documentation at the DJango website.

The application itself consists of 2 parts. A simple Content model that manages authored pages, and a metadata model for providing views onto the metadata associated with the manuscripts. The application allows view, creation, editing and deletion of the content model. The application only allows view of the metadata model, with data being ingested into the application via an administrator managed load process.

Dependencies

The application requires python image utilities, which can be installed with $ easy_install pil

Data loading.

Data loading is a 2 step process. There is a script, Migrate.py that creates json files, one per record from the existing meta data database (currently a MySQL instance). This is run on the commandline and requires access to that database. The json files are loaded via a administrative web UI into the Django model which is stored in whichever RDBMS backend the Django instance is configured to use.

The content model is populated by loading JSON files from disk using the same web UI. Once loaded the content pages can be edited by a suitably authenticated user in the web UI.

Please note all commands are run in src/ShahnamaDJ/ where manage.py is located.

Step 1: Edit settings.py

First edit settings.py. You will need to adjust any absolute paths in there, and may wany to select a different database type than sqllite if you are running in production. Note that SOURCE_DATA is relevent to the migration process and contains example editorial content.

Step 2: Migrate data from a Shahnama Database.

Data is migrated using the Migrate.py script. That script contains a MySQL connection which will need to be edited to point to the existing shahnama database. Running this script will extract all the data and create thousands of files under the location specified by SOURCE_DATA in settings.py

Step 3: Create the database schema

If you are not using sqllite, you will have to create a user in the database as per your settings.py, and allow that user to create tables, indexes etc. Django recommends PostgreSQL.

Once you have done that, run python manage.py syncdb to create the database. You will be asked for an admin user, please make certain that you use a secure password if you are going to make the /admin URL visible to the world.

Step 4: Start the development server

Start the development server, so that you can load the data. Please DO NOT use the development server to run in production, go and read the Django documentation (and use mod_python as recommended)/.

Run python manage.py runserver

Step 5: Login as the admin user.

Login as the admin user (created in Step 3) and go to http://localhost:8080/admin/loadDb. At this page, read the instructions and load the database with the Shahnama data you migrated in step 2. Remember to tick the checkbox so that it loads the data. If you don't the loadDB action will only check and resolver internal references.

Step 6: Load Editorial Content (Optional)

You can also load editorial content from SOURCE_DATA/content on http://localhost:8080/admin/loadDb. Once you have loaded this content (there are example files in the source repository), you can edit them online. Any user in the DJango database with content.pageedit, content.createpage, content.pageview, content.imageupload can perform those actions on editorial content. Consult the Django documentation on how to give users permissions. content.pageview allows users to see a list of all content pages.

Deployment

Any standard deployment method for Django can be used. If deploying for production you may want to use an alternative authentication framework (eg SSO), and you should look at configuring caching. The responses at each URL are cacheable and vary by URL. Doing this will eliminate all database load from the system.

You should consult the Django documentation for information on deployment and configuration with SSO systems.

Testing

The code base uses DJango unit tests. Some of the test use fixtures. The following fixtures are used during testing. src/ShahnamaDJ/contenttest.json and src/ShahnamaDJ/recordstest.json. Thes can be created by dumping your working DJango database python manage.py dumpdata. If you choose to run Django unit tests ie python manage.py test with a complete database in recordstest.json (ie abotu 44MB of json) the test suite will load and validate all 30K+ pages which will take about 1H. (Unit tests are single threadded)

Credits

  • The work was funded by : ?
  • Original code largely written by Dan Shepard whilst at Caret
  • For other contributions, please consult the commit history.

(c) 2011 University of Cambridge

shahnama's People

Contributors

ieb avatar ucamhal avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar

shahnama's Issues

Prev Next ordering links are broken.

Data is missing after the ingest that is breaking all the prev and next links. These are of the form chain-prev-folios-in-ms and chain-next-folios-in-ms.

After greping the original code base the following code looks like it computes these values.

maps = {
    'illustration': {
        'in': [{ 'our-id': 'IllustrationSerial', 'in-what': 'manuscript', 'with-id': 'ManuscriptSerial', 'to-id': 'ManuscriptSerial', 'reverse': 'illustrations' },
               { 'our-id': 'IllustrationSerial', 'in-what': 'scene', 'with-id': 'SceneSerial', 'to-id': 'SceneSerial', 'reverse': 'illustrations' }],
        'ordering': [{ 'key': ['FolioNumber'], 'chain': 'folios-in-ms', 'map': folio_num_key, 'per': ['ManuscriptSerial'] }],
    },

which appears to be used in
Unfortunately there are no comments and no documentation. (comments above have been added by me)

def create_ordering(request,table,key,name,proc,per):
    print "Rebuilding ordering %s on %s" % (name,table)
    data = DataStore.db(request,table)
    chains = collections.defaultdict(list)
    for id in data.keys():
        d = json.loads(data[id])
        k = [ str(d[x]) for x in per ]
#k contains a an array of values of per in this case the a single value array of ManuscriptSerial
        chains["\0".join(k)].append(id)
#chains[ManuscriptSerial] contains the ID of the object, ie illustration, so this is a list of illustrations for each #Manuscript serial
    for chain in chains.itervalues():
for each manuscript
        map = []
        for id in chain:
#iterate of the illustrations
            d = json.loads(data[id])
#load the illustration into d
            v = [ d[x] for x in key ]
v contains a single valued array of FolioNumber
            if proc:
#convert v using the folio_num_key function
                v = proc(*v)
            else:
                v = "\0".join(*v)

#Map contains IllustrationID, processed FolioNumber
            map.append((id,v))

#Sort the map using (ie processed Folio Number) its not a map, its a list of 2 element arrays.
#The sort appears to sort the map first using the value element 1 and then put element [0] into the map
#so the output map contains a list of IllustrationIDs ordered by the modified Folio Number
        map = [x[0] for x in sorted(map,key = lambda x: x[1])]

#Go through that ordered array of Illustration IDs adding pointers to the prev and next element in the array.
        for (i,id) in enumerate(map):
            d = json.loads(data[id])
            d["chain-prev-%s" % name] = str(map[i-1]) if i != 0 else ''
            d["chain-next-%s" % name] = str(map[i+1]) if i != len(map)-1 else ''
            data[id] = json.dumps(d)

To do this without having to load everything into memory.

create the following indexing field 1 for each element in per and key
eg ManuscriptSerial (thats per) and folio_num_key(FolioNumber) (thats key processed by map)
query using
select * from Illustration order by ManuscriptSerial, processedFolioNumber
read ahead by 3 to get i+1, i, i-1
set the values for illustration i, to i+1 and i-1 provided the manuscript serial value remains the same.

The first part can be performed when the relationships are built.
The second part will need a special ordering step.
This should be part of the model classes.

folios = re.compile(r"^([0-9]+)([vr]?)$")
def folio_num_key(k):
    if k is None:
        return None
    m = folios.match(k)
    if not m:
        return None
    val = int(m.group(1))*2
    if m.group(2) == 'v':
        val += 1
    return val

this looks like it finds keys like 1231231231v or 1231231323r
and where there where there are 2 keys with the same number, but v and r it put v later on in the sequence.

Links to country, and Manuscript are missing from illustrations.

This is displayed
in All Souls College, Oxford,
Depicts
Rustam's third labour: he kills a dragon, in Kay Kavus (150 years)

It should be
Ms 289 in All Souls College, Oxford, UK
Depicts
Rustam's third labour: he kills a dragon, in Kay Kavus (150 years)

Ms 289 and UK are missing

UK looks like its missing because the lookup to the Country authority is failing.

Broken relationships in existing runtime data.

On a full import the system reports that the following relationships are broken. In general this means the target object was mentioned in the source object but cannot be found in the dataset. This may be due to the object not being there, or it could be due to an error in the ingest mechanism. Out of the 27K objects ingested this shows that the following objects are missing.

Location: 2102134610
Location: -1927029573
Location: 1051476722
Location: 2102134590
Manuscript: 2143526957
Manuscript: 2143526955
Manuscript: 2143526959
Manuscript: 2143526979
Manuscript: 2143526980

Log Output follows

Added Errors Object 2102134610 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Manuscript object 2143526964
Added Errors Object 2102134610 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Manuscript object 2143526965
Added Errors Object 2102134610 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Manuscript object 2143526966
Added Errors Object 2102134610 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Manuscript object 2143526967
Added Errors Object 2102134610 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Manuscript object 2143526968
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -2133910274
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -2114696025
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -2098396523
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -2053256762
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -1945499414
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -1882649680
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -1586575616
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -1488782591
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -1287733459
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -1153614569
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -1048568476
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -963320547
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -938335085
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -901395165
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -416919984
Added Errors Object 1051476722 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -250713488
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -158686199
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -98348108
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object -36929576
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 260637998
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 269984785
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 404154715
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 415653579
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 502803807
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 508723496
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 510226085
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 608457754
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 978950494
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 1053552619
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 1093765892
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 1110487298
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 1401183183
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 1658269169
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 1882935858
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 2063526806
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 2073002844
Added Errors Object 2102134590 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 2147346674
Added Errors Object 2143526957 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348870
Added Errors Object 2143526955 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348906
Added Errors Object 2143526957 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348912
Added Errors Object 2143526957 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348913
Added Errors Object 2143526959 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348918
Added Errors Object 2143526959 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348919
Added Errors Object 2143526959 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348920
Added Errors Object 2143526959 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348921
Added Errors Object 2102134610 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 2147348937
Added Errors Object 2102134610 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 2147348938
Added Errors Object 2102134610 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 2147348939
Added Errors Object 2102134610 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 2147348940
Added Errors Object 2102134610 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 2147348941
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348978
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348979
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348980
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348981
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348982
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348983
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348984
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348985
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348986
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348987
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348988
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348989
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348990
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348991
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348992
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348993
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348994
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348995
Added Errors Object 2143526979 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147348996
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349043
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349044
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349045
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349046
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349047
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349048
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349049
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349050
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349051
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349052
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349053
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349054
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349055
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349056
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349057
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349058
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349059
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349060
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349061
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349062
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349063
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349064
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349065
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349066
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349067
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349068
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349069
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349070
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349071
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349072
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349073
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349074
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349075
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349076
Added Errors Object 2143526980 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147349077
Added Errors Object 2143527099 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147350080
Added Errors Object 2143527099 of type <class 'ShahnamaDJ.records.models.Manuscript'> does not exist from Illustration object 2147350081
Added Errors Object -1927029573 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 2147352089
Added Errors Object 2102134590 of type <class 'ShahnamaDJ.records.models.Location'> does not exist from Illustration object 2147356682

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.