Giter Club home page Giter Club logo

theaterjs's Introduction

TheaterJS

Typing animation mimicking human behavior.

If you're not particularly interested in managing multiple actors and the several features TheaterJS has to offer (e.g mistakes, variable speed, callbacks, html support, and so on), have a look at this fiddle. It's a minimalist version that supports play/stop, it has a lot of comments so you understand what's going on under the hood. It might well be enough for you usage :)

Installation

With npm:

npm install theaterjs

With yarn:

yarn add theaterjs

Example

<div id="vader"></div>
<div id="luke"></div>

<script src="path/to/theater.min.js"></script>
<script>
  var theater = theaterJS();

  theater
    .on("type:start, erase:start", function() {
      // add a class to actor's dom element when he starts typing/erasing
      var actor = theater.getCurrentActor();
      actor.$element.classList.add("is-typing");
    })
    .on("type:end, erase:end", function() {
      // and then remove it when he's done
      var actor = theater.getCurrentActor();
      actor.$element.classList.remove("is-typing");
    });

  theater.addActor("vader").addActor("luke");

  theater
    .addScene("vader:Luke...", 400)
    .addScene("luke:What?", 400)
    .addScene("vader:I am", 200, ".", 200, ".", 200, ". ")
    .addScene("Your father!")
    .addScene(theater.replay);
</script>

Documentation

To get started, you'll first need to create a new TheaterJS object by eventually providing some options.

Example

var theater = theaterJS({ locale: "fr" });

Usage

theaterJS(<options>)
Param Default Description
options {autoplay, locale, minSpeed, maxSpeed} Options (see below).

Breakdown of the available options:

Option Default Description
autoplay true If true, automatically play the scenario (when calling addScene).
locale detect Determine which keyboard to use when typing random characters (mistakes). Note: "detect" is an option to detect the user's locale and use if it's supported.
minSpeed { erase: 80, type: 80 } Minimum delay between each typed characters (the lower, the faster).
maxSpeed { erase: 450, type: 450 } The maximum delay between each typed characters (the greater, the slower).

Regarding minSpeed and maxSpeed, you can also just pass a number instead of an object. If you do so, this value will be used for both the erase and type speed, e.g:

{
  "minSpeed": {
    "erase": 80,
    "type": 80
  },

  "maxSpeed": {
    "erase": 450,
    "type": 450
  }
}

Is equivalent to:

{
  "minSpeed": 80,
  "maxSpeed": 450
}

TheaterJS objects have two public (read only) properties:

  • theater.options: object's options.
  • theater.status: object's status (whether "playing", "stopping" or "ready").

addActor

Add an actor to the casting.

Example

var theater = theaterJS();

theater
  .addActor("vader")
  .addActor("luke", 0.8, ".luke-selector")
  .addActor("yoda", { accuracy: 0.4, speed: 0.6 }, function(displayValue) {
    console.log("%s said yoda", displayValue);
  });

Usage

theater.addActor(<name>, <options>, <callback>)
Param Default Description
name Name used to identify the actor.
options 0.8 Actor's options (see below).
callback (see below) A function to call when actor's display value changes.

Actors have two options:

  • accuracy (number between 0 and 1): used to determine how often an actor should make mistakes.
  • speed (number between 0 and 1): used to determine how fast the actor types.

Note: the delay between each typed character varies to "mimick human behavior".

An actor callback is a function that is called when its display value is set. It can also be a string, in such case TheaterJS will assume it's a DOM selector and will look for the corresponding element. It's then going to set the element's innerHTML when the value changes. You can safely ignore this argument if you gave the target element an id with the name of the actor, i.e:

theater.addActor("vader");

In this situation, TheaterJS will look for an element that matches the selector #vader. Also note that the actor will have an additional $element property referring to the DOM element when using a selector string.

getCurrentActor

Return the actor that is currently playing.

Example

var theater = theaterJS();

theater
  .addActor("vader")
  .addScene("vader:Luke...")
  .addScene(function(done) {
    var vader = theater.getCurrentActor();
    vader.$element.classList.add("dying");
    done();
  });

Usage

theater.getCurrentActor();

addScene

Add scenes to the scenario and play it if options.autoplay is true.

Example

var theater = theaterJS();

theater
  .addActor("vader")
  .addScene("vader:Luke... ", "Listen to me!", 500)
  .addScene(theater.replay);

Usage

theater.addScene(<scene>)

A scene can be of 5 different types:

theater
  .addScene("vader:Luke... ") // 1
  .addScene(800) // 2
  .addScene("I am your father!") // 3
  .addScene(-7) // 4
  .addScene("mother!")
  .addScene(function(done) {
    // do stuff
    done();
  }); // 5
  1. .addScene('vader:Luke... ') erase actor's current display value, then type the new value.
  2. .addScene(800) make a break of 800 milliseconds before playing the next scene.
  3. .addScene('I am your father!') append value to the current actor's display value.
  4. .addScene(-7) erase 7 characters.
  5. .addScene(fn) call fn which receives a done callback as first argument (calling done() plays the next scene in the scenario).

Note that addScene actually accepts an infinite number of arguments so you could just do:

theater
  .addScene("vader:Luke... ", 800, "I am your father!")
  .addScene(-7, "mother!")
  .addScene(fn);

getCurrentSpeech

Return the speech that is currently playing.

Example

var theater = theaterJS();

theater
  .addActor("vader")
  .addScene("vader:Luke...")
  .on("type:start", function() {
    console.log(theater.getCurrentSpeech()); // outputs 'Luke...'
  });

Usage

theater.getCurrentSpeech();

play

Play the scenario.

Example

var theater = theaterJS({ autoplay: false });

theater.addActor("vader").addScene("vader:Luke...");

document.querySelector("button").addEventListener(
  "click",
  function() {
    theater.play();
  },
  false
);

Usage

theater.play();

replay

Replay the scenario from scratch (can be used as a callback to create a loop).

Example

var theater = theaterJS();

theater
  .addActor("vader")
  .addScene("vader:Luke...")
  .addScene(theater.replay);

Usage

theater.replay();

stop

Stop the scenario after the current playing scene ends.

Example

var theater = theaterJS();

theater.addActor("vader").addScene("vader:Luke... ", "I am your father...");

document.querySelector("button").addEventListener(
  "click",
  function() {
    theater.stop();
  },
  false
);

Usage

theater.stop();

on

Add a callback to execute when an event is emitted (e.g when a scene starts/ends).

Example

var theater = theaterJS();

theater
  .on("type:start, erase:start", function() {
    var actor = theater.getCurrentActor();
    actor.$element.classList.add("blinking-caret");
  })
  .on("type:end, erase:end", function() {
    var actor = theater.getCurrentActor();
    actor.$element.classList.remove("blinking-caret");
  });

theater.addActor("vader").addScene("vader:Luke...");

Usage

theater.on(<eventName>, <callback>)
Param Default Description
eventName Event's name to listen to.
callback Function to call when the event got published.

The callback function receives the event's name as first argument.

A couple of things to note:

  • Listen to all event by using the shortcut: theater.on('*', callback).
  • An event is emitted when a sequence starts (sequence:start) and ends (sequence:end), e.g theater.addScene('vader:Luke.', 'vader:I am your father.') is one sequence.
  • An event is emitted when the scenario starts and ends, respectively scenario:start and scenario:end.
  • The scenario is stoppable within :end event listeners. It means that calling theater.stop() within a callback that listen for the :end of a scene will stop the scenario. This is useful for asynchronous callbacks (e.g animations).

Localized Keyboards

When making a mistake, an actor's gonna type a random character near the one he intended to. Those characters are taken from a "mapped" keyboard that you can configure on TheaterJS' instantiation: theaterJS({locale: 'en'}).

Change Log

3.2.0 - 2018-06-04

  • add "getCurrentSpeech()"

3.1.0 - 2016-11-14

  • add "main" property to the package.json
  • remove irrelevant files from the npm package

3.0.0 - 2016-03-20

  • disabling the erase option should still clear display value

2.2.1 - 2016-03-19

  • fix end scenes event that throwed an error due to how .replay() works

2.2.0 - 2016-03-17

  • publish an event when the scenario starts and ends
  • scenario should be stoppable in :end events callbacks

2.1.0 - 2016-03-15

  • emit an event when a sequence starts and ends

2.0.2 - 2016-03-13

  • compile a non-minified version along with the minified one
  • fix window detection
  • fix bower.json configuration
  • add support for slash-less void elements (e.g <br> instead of <br/>)
  • fix play/stop issue #49
  • add option to configure erase's min/max speed independently

2.0.1 - 2015-11-02

  • publish to npm, fix for non-browser environment
  • add a .npmignore file
  • add source map

2.0.0 - 2015-11-02

  • Brand new version

theaterjs's People

Contributors

adambutler avatar amitguptagwl avatar andrusieczko avatar bernikr avatar bronzehedwick avatar datio avatar desko27 avatar devanp92 avatar fay-jai avatar fernandofussuma avatar floffel avatar gantit avatar gitter-badger avatar kkirsche avatar lukasdrgon avatar narr avatar palmerj3 avatar prrrnd avatar puttepingvin avatar stephnr avatar timwangdev avatar zhouzi 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  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

theaterjs's Issues

theater.play() and theater.stop() issue

Hello again ! I've been playing a lot with your awesome javascript, and i found an issue in one of the core features.
When you assign the theater.play() and theater.stop() toggles on a button, pressing it multiple times button within a certain amount of time (before the current scene/event stops) make Theater completely buggy, a fiddle will explain it better than me :

https://jsfiddle.net/AmineIzumi/yexw3do1/3/

(I also found a workaround, which is simply binding the toggle to the typing:end event)

Decompose "Experience" into "Speed" and "Accuracy"

Right now the "experience" factor increases speed while decreasing mistakes. I don't think this is totally accurate. I think, optimally, "experience" should be decomposed into "speed" and "accuracy" rather than just one parameter like it is now. This would allow you to have speed demons that make lots of mistakes or slow, deliberate typists that are almost flawless.

Infinite Loops

Hi,

I am trying to run theater inside an infinite loop where I create the random text to show.

However I had the impresion that Theater is blocking the execution.

Is that possible ?

theater.stop() to instantly stop a scene

Hi again !
Actually, when you send theater.stop() , the JS wait until the sentence is finished before stopping.
A great enhancement could be to add a parameter to choose if the sentence must be finished or not :

I see a lot of uses for that feature, my try is to pause writing when the webpage isn't visible (= window minimized or user in another tab), and resume when it's visible so the user won't miss the text.
With the current theater.stop() it is impossible to correctly achieve, since the sentence finishes before stopping. You can see that concept here, try to switch tabs for more than 2 seconds :) https://jsfiddle.net/AmineIzumi/zy118txu/
Thanks in advance for your help ! 👍

Homepage link is broken

The demo is also available on the TheaterJS page.

in README.md links to a dead page.

Not Found

The requested URL /TheaterJS was not found on this server.

Differentiation between erase and type speeds

I am trying the following options
`theater = theaterJS({
"minSpeed": {
"erase": 80,
"type": 450
},

"maxSpeed": {
"erase": 80,
"type": 450
}
});`
However I am unable to have a different speed between type and erase

HTML tags not closing correctly

The result of theater.write('Vader:<a href="#">Search</a> your feelings...'); is <a href="#">Search your feelings...</a> instead of <a href="#">Search</a> your feelings...

You might be interested by this issue @williamg

Adding a 'showcase' to the readme

Maybe it would be a nice idea to add a showcase of what is powered by TheaterJS. It would allow new users to see what Theater is able to do, I guess ? Also, showcases are always inspirating 😄 !

[bug]theater.replay not working anymore as of 2.2.0

Well, the title is pretty clear. Since 2.2.0, a replay call in a Scene can't trigger the "Sequence:end" event, and shows that there is no name for that Scene. (Unable to get property 'name' of undefined or null reference
Open the console, and look at this fiddle where the bug is easily reproducible - Just wait for the replay scene to start :)

How to make the scene say something different each time?

Here is my code (using JavaScript and Jinja templating):

var myOffers = [{% for o in offers %}"{{ o }}",{% endfor %}];
var rand = myOffers[Math.floor(Math.random() * myOffers.length)];
theater
  .addScene('jjbot:' + rand, 900)
  .addScene('jjbot:')
  })

Basically every time an element is clicked, it runs theater.play(). The problem is, I would like it, if clicked again, to say something different (i.e. a different string in rand). I've tried doing theater.replay() but it only keeps displaying the same text. Is there a way to do this? Thanks!

theater.play(true) is not working

Hi,
From your example and documentation, there is a function

theater.play(true)

that will replay the loop.

But actually, it's not working.
It's stopped at the end of last .write() function.
or, am I missing something here?

Initial value

Is it possible to have an initial value? Let's say we start off with a sentence:

I like to drink coffee.

And then the word coffee gets erased and changed to beer.

JS in demo not hosted over HTTPS

The demo page is hosted on Codepen, which has HTTPS enabled. The javascript-file requested is not. This breaks the demo in many modern browsers.

Write parsing `:` issue

Firstly, awesome library! I've been having some fun playing around with it 😄

I've found an issue with the write function. If you call it in the shorthand way and the speech contains a :, it will error.

e.g.

theater.write("Vader:Luke", 200, "I am your father :D");
Uncaught TypeError: Cannot read property 'model' of undefined
c.erase
c.next
c.actor
c.next
(anonymous function)

Erasing a sentence/word faster

Hi there,

We love using your script at our page, and we wanted to use it even more. I've been looking to the docs, looking for a faster way of erasing a sentence but I didn't found any API. Should I write my own custom callback ?

Thanks a lot !!

theater.restart() to instantly replay the scenario from scratch

Hi, I really like your plugin - great work and easy to use.
As far as I understand, theater.replay() restarts the scenario as soon as it reaches its end - but I wonder if there's a way to instantly restart or otherwise reinitialize the whole scenario e.g. at the click of a button.
Thanks in advance!

obj.onValuesChange not triggered if running project from core.getProject()

Hello,

first of all, i have to say that theaterjs is a pretty need piece of software imo. i'm not really into es6, but managed to wrap threejs/studio an integrate it into an angular application pretty easy. The studio part works except of some self-inflicted css fuckery flawless. :)

I'm stuck at the moment to play() a persisted project. No matter if i'm using the theater localstorage implementation nor if i try to load a json project exported from studio.createContentOfSaveFile(projectUuid);. I will atttach an excample json, looks good to me, but i do not get how the revision history works, maybe it's part of the problem. I digged into the js code a bit and learned that project.ready.then(()=>{}) will never resolve if studio is imported. I'm now able to run sheet.sequence.play() ->

//resolving persisted sheet
const [sheet, setSheet] = useState(project.sheet('Scene', 'default'));

//start sequence after component mounted
  useEffect(() => {
    project.ready.then(() => {
      sheet.sequence.play().then((finished: boolean) => {
        if (finished) {
          console.log('Playback finished');
        } else {
          console.log('Playback interrupted');
        }
      });
    });
  });

The squence plays and also logs when the playback finishes. What i'm trying to do in the first place to use a bunch of React.FC which are coupled to SequenceObjects, which works works in studio mode. but if i'm reloading the objects from the persistent sequence and listen to subscribe to onValuesChange on these objects, the event is emmited once, and neveer again.
Maybe somebody has an idea what could make this work. Here some code ->

//resolving the old sequence objects
const obj: ISheetObject<EditorElementProps> = sheet.object(curDef.id, EditorElementDefaultPropeties);

//reinitialzing the linked component
    if (curDef.type === 'image') {
      return <Image
        id={curDef.id}
        key={'c' + curDef.id}
        sheet={curDef.sheet}
        payload={curDef.payload}
        selectedObject={curDef.selectedObject}
        rotation={0}
        CurState={null}
        obj={obj}/>;
    } 
    
  //linking 
 const [style, setSyle] = useState<EditorElementStyle>(obj.value);
 
//subscribing to value change
    useLayoutEffect(() => {
      const unsubscribeFromChanges = curState.childObj.onValuesChange((newValues) => {
        setSyle(newValues);
        Object.assign(moveable.props, [moveable.props, {left: newValues.x}]);
        moveable.updateRect();
      });
    }, []);

state.json -> https://gist.github.com/my5t3ry/c060d339bc7e13a29fc9fd65d776e80e#file-state-json

thank you : )

How can I stop writing?

I write on page scrolling.
I need to stop writing when user see some element.
How can i stop writing even if actor write only half of sentence.

Custom speed/accuracy function for a scene

Hi ! First of all, great job, real cool stuff here.

I had an idea, could it be possible to add more customization for each scene ?
I mean when someones says something sad he's not supposed to have the same speed/rythm than when he says something happy or else...
As a way to handle this I was thinking of adding speed and accuracy for each scene instead of each character.
I was also thinking for even more customization could we passed functions for speed ?
Like if I say speed = function(time) { return time2}, and then the character would speed up during the scene following a y=x2 rule. What do you think ?

I would be more than happy to help btw !

Is there a way to control the ERASE speed?

Hey dude,

Love this but I'm wondering if there is a way to set a parameter to control the erasing speed?

Something similar to this.
.describe("reader", { speed: 15, accuracy: 1, invincibility: 4, erase:25 }, "#reader")

Thanks for the awesome plugin.

A tool that mimicks system tools automatically ?

Like lets say it's the wifi manager of an android phone
And we want to mimicry it to use in a phishing captive portal page for pentesting
Is there a way to do make it even half automatic?
Cause every phone has a different wifi manager !

What do you think ?

Print scene by line

First, thank you for this cool works.

I have an idea, could it be possible that add a function theater.addLineScene() , and this can be used to print the whole scene line at once.
Because sometimes the typing is kind of too slow even I set the maxSpeed to 1.

Thank you for your consideration!

[enhancement] fire an event when a scene is finished

Hi ! I'm working with the events .on('type:start, erase:start') or type:end erase:end to make the text container duplicate itself when someone finished typing. However, i added many 'long scenes', like this :

    .addScene('vader:Luke...', 400)
    .addScene('luke:What?', 400)
    .addScene('vader:I am', 200, '.', 200, '.', 200, '. ')
    .addScene('Your father!')
    .addScene('luke: ...',400,'Whaat the',700,-3,'are you saying!?')
    .addScene(800)

Fiddle showing the (in)usability of type:start

so the 'type:end' event doesn't fit my needs anymore, since it's triggered multiple times in one 'sentence' .

I know that adding a function scene after the one i want to duplicate would probably work, but in a long scenario I then have to copy that function scene after every scene, which isn't really convenient. Could it be possible to add an event "scene: start" and "scene : stop" ?

Mistyping repeating keys

People won't mistype repeated keys. For instance, when you are typing ... it is very unlikely for someone to mistype 2nd or 3rd dot. So maybe you should check if previous character is same as current one and don't mistype if so.

What do you think?

Add mistakes including incorrect capitalizations and typing characters ahead and then erasing

I think it would be awesome if we could improve the realism of mistakes made by making it so that capital letters are sometimes confused or users go back to fix a capital letter.

i.e. And then the fox ran. so... --> And then the fox ran. So...

Also mistakes where users type the next letter in the word (sometimes this happens because they didn't push the key with one of their fingers hard enough).

i.e. And then the fx... --> And then the fox...

Regardless, really cool library.

Script freezing (randomly) during multiple typed phrases

On my agency's homepage, we are making good use of theater.js. My boss has trouble with it freezing mid sentence on his computer (year and a half old macbook air). I know it's hard to replicate without his actual machine, but view it here and see if the same happens with you. Maybe you can throttle your bandwidth of memory in dev tools to recreate?

http://lift1428.com

Thanks bud!

Unable to use theater js in Angular 2+

I installed theaterjs via npm and added it to my typing file like any other javascript library. But whenever i add it to my component, I get an error. PS the code works fine in .html files.
screenshot 227
screenshot 228
screenshot 229

TheaterJS halts with blinking text cursor

In your example, I get random "Uncaught TypeError: Cannot read property '0' of undefined" at line 152. This causes text cursor blink at the end of a word and cannot complete it's job.

I believe p in p = chars[c]; is undefined.

I think it happens when your code is mis-typing a space character.

Correction: It happens after space, before first letter of a word.

Using latest chrome and Win 7.

Kind regards and keep up the good work.
Ertug

CDN ?

Hi!

Would you mind if TheatherJS was hosted on a CDN like cdnjs or jsDelivr ? It would be great as this would allows us to use the lib without actually serving it from our own servers.

Script File Throwing Error on Line 85

Hi there,

AWESOME STUFF first of all.

So in codepen, everything is working just great (http://codepen.io/andandandandrew/pen/XmKEMK). I'm importing the theater.js file then typing the plugin scripty part in the JS panel, which is tantamount to an inline script if I'm correct.

So where I'm trying to use this on a site I'm working on, I have the theater.js script dynamically loading using PHP (it's only used on one page) loading using src="" after the footer, immediately followed by another inline script with the following code in it, which is also php'd in as an inline script:

<script type="text/javascript">(function () { "use strict"; var theater = new TheaterJS(); theater.describe("typeit", { speed: 0.85, accuracy: 1, invincibility: 6 }, "#typeit"); theater .write("typeit:What's up my homies?!?", 1000) .write("typeit:It's typing itself. OMG!!!", 800) .write("typeit:CAN YOU BELIEVE IT?", 1600) .write("typeit:We can change the length each message stays too. This one is ten seconds.", 10000) .write("typeit:This is one second.", 1000) .write("typeit:Andrew is super neat.", 1000) .write(function () { theater.play(true); }); })();</script>

I am getting a console error (screenshot attached) on line 85 of the script file: Uncaught TypeError: Cannot set property 'innerHTML' of null

screen shot 2015-09-28 at 2 13 17 pm

Any idea what's going on here? It's being used on the homepage of a WordPress Site and is not typing anything, even though the markup is the same as in the pen (id to JS it is anyways). This is where it should be typing, but it's not:

screen shot 2015-09-28 at 2 24 10 pm

Any ideas? Is it the PHP loading that's doing it? I've copied the script exactly from the repo—there's not something in the actual script file I need to update, right?

Thanks for the help.

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.