Code Monkey home page Code Monkey logo

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

yarnspinner's Issues

VariableStorage does not exsist in Yarn namespace

After the first time opening the Unity project I immediately get this error ;

"Assets/Yarn Spinner/Code/DialogueRunner.cs(230,78): error CS0234: The type or namespace name VariableStorage' does not exist in the namespaceYarn'. Are you missing an assembly reference?"

Am I missing a script somewhere with this namespace?
Using Unity version 5.3.2.

Error in DialogueRunner when there are no 'sourceTexts'

I'm adding a DialogueRunner to my game at runtime, and as such, when it hits Start, there aren't any text assets in the sourceText array (they're added later when the NPCs are loaded). The code throws an error when trying to iterate over the array as it's null. I fixed this with a null check before the iteration loop, but I thought I'd point it out here.

A similar thing happens with the switch statement in the example variable storage (the defaultVariables array).

Smileys in the text cause parse errors.

The following test script does not parse:

Jane: ¯\_(ツ)_/¯
Jane: <o>
Jane: :|
Jane: :)
Jane: :(
Jane: :o
Jane: :\
Jane: :<
Jane: D:
Jane: -_-
Jane: =_=
Jane: U_U
Jane: O_O
Jane: o_o
Jane: \o/
Jane: /o\
Jane: o/
Jane: _o_
Jane: o>
Jane: / \
Jane: \ /
Jane: ~~

Window Store App Support

Currently when making a build for Windows Store apps, I get the following errors:

Reference Rewriter found some errors while running with command --

  1. - Error: method System.Void System.IO.StreamReader::.ctor(System.String) doesn't exist in target framework. It is referenced from YarnSpinner.dll at System.Void Yarn.Dialogue::LoadFile(System.String,System.Boolean,System.Boolean,System.String).
  2. - Error: method System.Boolean System.Type::IsInstanceOfType(System.Object) doesn't exist in target framework. It is referenced from YarnSpinner.dll at System.Void Yarn.Value::.ctor(System.Object).
  3. - Error: method System.Reflection.Assembly System.Type::get_Assembly() doesn't exist in target framework. It is referenced from YarnSpinner.dll at System.Collections.Generic.IEnumerable`1<System.Type> Yarn.Analysis.Context::get_defaultAnalyserClasses().
  4. - Error: method System.Boolean System.Type::get_IsAbstract() doesn't exist in target framework. It is referenced from YarnSpinner.dll at System.Collections.Generic.IEnumerable`1<System.Type> Yarn.Analysis.Context::get_defaultAnalyserClasses().

Allow storing additional types in variables

Currently, variables can only store numbers. This is a limitation of VariableStorage, not of the language - while Values are capable of storing any type currently known to the language, there's simply no way for clients to store any type besides numbers in variables.

To address this, VariableStorage should be extended to allow either storing Values directly, or have per-type methods (e.g. SetString, SetNumber, SetBool etc)

Clear() and ResetToDefaults() have no implementation guide.

TL;DR It's not clear what VariableStorageBehaviour.Clear() and VariableStorageBehaviour.ResetToDefaults() are supposed to do.

I am currently integrating Yarnspinner into my game (thanks guys! So much is so much easier!), and I'm just about to finish the process, but while most has been well-explained or pretty self-evident (not too hard to decipher the design request in Value GetValue(string variableName)), I'm stuck on the meaning of ResetToDefaults() and Clear() in VariableStorageBehaviour, and how they differ. I can imagine that one of them is supposed to wipe the save file, but I believe the other gets called at the beginning of each playsession(?). I could not figure out from the code or docs what these functions are supposed to do. I would of course appreciate an explanation for myself, but more so I think this signals a gap in the docs. Maybe Clear() could get a more verbose, clearer (heh) name? Maybe those functions could get some comment-explanations like DialogueUIBehaviour has? I was about to recommend triple-slash documentation (it's cool! It shows up in tooltips in VS and can be really helpful!), but I notice that this does not appear when you're implementing an abstract class, so it can't be quite as slick as one might hope.

Replace current lexer with a state machine

The lexer (see Tokenizer.cs) is getting pretty byzantine. It operates by defining a massive set of regexes, which are checked in order; however, it's had to develop some nasty warts, like "free text mode" and tokens that can only appear at the start of a line, as well as tokens that can reset the start-of-line state. It's kinda gross.

A better solution would be to have the lexer have different states; states contain a small number of regexes that define the tokens valid within that state. When a token is encountered, it can change the current state.

(This is basically exactly what the ACE syntax highlighter does to determine the token types.)

Localisation support

So. I've been thinking about how Yarn can support localisations, and this is the most straightforward that I've come up with so far. Big thanks to Anna Kipnis of Double Fine and Ron Gilbert of Terrible Toybox for their advice that helped to figure this out.

I'm very interested in comments and discussion on this, because this is a large set of changes to Yarn as a language.

Executive Summary

This issue outlines a system for supporting localisation in Yarn. It includes both a high-level overview of how such a system can work, and the syntax changes needed in the Yarn language.

I propose that localisation work by providing a tool that moves out all localisable text - both lines, and localisable string values - into a per-language file, and replaces them with unique linecodes. Additional syntax should be added to the Yarn language to support this.

Localising lines

Given the following script:

<<if $knows_plot == true>>
    I know about the villain's plot!
<<endif>>

Localisation would involve converting the script into something like this:

<<if $knows_plot == true>>
    {0201}
<<endif>>

In this case, {0201} is a reference to a line that's kept in a separate file. The format of the file doesn't really matter, except insofar you could use it and the number "0201" and get back the string "I know about the villain's plot!". It could be a database of somekind, or an Excel spreadsheet - it doesn't particularly matter. We'll come back to this in a bit.

We can very easily create a tool that identifies and replaces lines with unique line numbers, and exports to a file. However, it isn't without its problems.

The serious disadvantage of doing this is that it adds a burden to the writer if they need to make changes to the script. With the text of the line separated from the logic of the Yarn program, it adds the hassle of switching between the script and the exported line file. We can mitigate this a little by adding a comment containing the text, like so:

<<if $knows_plot == true>>
    {0201} // I know about the villain's plot!
<<endif>>

Running the localisation system would update any comment like this to ensure that the comment attached to the line code correctly represents what's in the line file.

Localising lines with embedded values

So, that's simple lines. Let's now talk about lines that incorporate other values. Currently, we don't have any syntax for doing this at all; I've added notes in #25 to discuss that.

Using the markup proposed in #25, let's say we have a line that looks like this:

He's going to [$method_of_destruction] the [$target] on [$day_of_week]!

Pretty much any localisation system that permits embedding values in a line does so in a way that allows for re-ordering the embedded values. This is for sentences that present words in a different order than the original language:

This means that localising a line means converting the above example into something like this, for English:

He's going to $0 the $1 on $2!

And in this for German:

Er wird das $1 am $2 $0!

(Please excuse the potentially incorrect translation - I'm not a native German speaker.)

In the case of this line, we need to store in the per-language file both the line, and all of the localised values used in the line.

Given this, and the fact that the values in this example come from variables at runtime, we need a way to represent this linecode. I propose that it look like this:

{0202,$method_of_destruction,$target,$day_of_week} // He's going to {0} the {1} on {2}!

In this case, we're specifying line 0202, and providing a comma-separated list of the variables used to supply. The position of these variables is important, since it's used to specify which variable is used for each of the numbered values. A comment is also added by the localisation tool to indicate both the original line, and the context of the variables used in it.

Localising string values in expressions

We've just talked about embedding string variables in localised lines. However, the values used in these variables need to be localised as well.

The syntax for this could look like this:

<<set $target = {"the stadium"}>>

After localisation processing, the string is extracted and replaced with a code. Additionally, a comment is appended to the line, to indicate what string is represented.

<<set $target = {0541}>> // "the stadium"

By using the brace syntax in the same way as is used in lines, we reinforce the idea that numbers in braces represent localised strings, and their value will change based on which language file is being used.

Generating line files

Earlier, I mentioned that the specific format used to store lines doesn't actually matter. There are several formats that exist for storing localised text; these range from simple CSV and Excel spreadsheets to more sophisticated solutions like XLIFF.

It's my intent that Yarn Spinner try to stay within as limited a domain as it can. Users of Yarn may already be using some kind of localisation system that they're comfortable with, and we don't need to try to invent a new one.

I think that it would be OK for Yarn Spinner to put the burden of loading localised strings given a linecode onto the host application. Much as the Dialogue class requires that the host provide functions for implementing variable storage, we could do a similar thing for lines.

We could then provide a simple reference implementation that handles common cases like CSV files, for users who aren't currently using a localisation system already (which I expect will be most users), while more sophisticated systems could make use of their own systems or 3rd-party localisation systems.

Disadvantages

Linecodes can be obtuse. We may want to look at letting users define their own linecodes - perhaps a more human-readable representation.

The primary disadvantage of this system is that making changes to localised lines requires updating each of the localised files to make changes, or to blow away the entire line and generate a new entry in each of the localisation files. However, this is a disadvantage that pretty much any localisation system encounters.


So, those are my thoughts. What do you all think? Is there a use case that I've missed? Do you have any thoughts on how this could be better?

Interest in expanding VariableStorage interface?

Is there interest in expanding the Yarn.VariableStorage interface to accept more datatypes? In particular, some of the legacy Twine code I'd be interested in porting would require allowing storage of strings, which is supported by Twine but not currently Yarn.VariableStorage. There's also the matter of Arrays to contend with but let's take it one step at a time if there's interest in expanding this functionality.

Thanks for this excellent package, BTW!

Set up Unity editor tests.

The demo game is written in Unity, which supports automated testing. It should be easy enough to write editor tests for the Unity parts of the project.

Demo game should work on iOS

The demo game currently requires a keyboard. There's no reason why it shouldn't work on iOS, too!

Adding some buttons that control movement and trigger interaction should be enough.

InvalidCastException when constructing a Value with an integer

When creating a Value like this :

var someValue = new Value(1);

The Value(object) constructor throws an InvalidCastException because the int gets boxed to an object, and then the constructor tries to unbox it as a float.

(Workaround : don't use integers, floats work)

Support saving and loading State objects

The State object represents the current state of execution. It's designed to be simple and easy to be serialised and deserialised; storing this in some kind of (JSON-like?) format on disk, and loading that file back to resume execution should be fairly straightforward.

Add a way to determine if running a node will run any lines or commands

Frequently, characters work like this:

  1. Talk to them. A bunch of lines occur.
  2. Talk to them a second time. A different batch of lines occur.
  3. Talk to them a third time. They're out of dialogue now, and so they either start looping some lines (either a single line, randomly choosing a line, or cycling lines), or disable interacting with the character.

If the fourth option is desired, the game needs a way to know to disable the interaction. (In NITW, this is done by setting a 'don't interact with this character anymore' variable, on a per character basis; the game checks this variable's value when deciding whether to let the player talk to a character).

A nicer way would be for the game to be able to ask Yarn Spinner if there's any point in attempting to run dialogue - if the dialogue would result in any commands, options or lines, then we'd consider the answer to be 'yes', otherwise 'no'.

Add the ability to expand variables inside of dialog

It would be really convenient if variables inside of dialog were replaced with their values. This would allow the dialog to be a lot more dynamic.

Some examples:
"$player_name is pretty cool!" would change to "Samus is pretty cool!"
"You have $gold_in_bank gold in your account" would change to "You have 50 gold in your account".

Label-less options are immediately jumped to

Hi folks! I've been trying to understand why my options are sometimes ignored, and I think I've tracked it down to a couple of issues. First, it seems like options without labels are never presented. This can be tested by replacing the Start node in the simple example script with the following:

   A: Hey, I'm a character in a script!
   B: And I am too! You are talking to me!
   [[Leave]]
   [[LearnMore]]

This skips the option presentation and immediately goes to Leave. Even if one of them has a label, the label-less option will be called.

Second, spaces and punctuation don't appear to be valid in node names / labels, but this isn't really described anywhere. If that's intended, it would be great to update the documentation and potentially throw some more helpful errors.

The Yarn editor doesn't seem to have a problem showing node links in these cases, so I assume they're intended as options - otherwise I suppose that should be updated as well.

Thanks!

Easy Setup for Open systems

Documentation, including the Easy Setup in README.md is heavily closed system centric (Windows, OSX) because of Unity focus. No discussion seems to be provided on how to build on linux/*bsd, except providing a claim that it's doable (afaict only a Unity demonstration is provided, nothing is provided for Open system). In fact, request for improvements to documentation in the README.md link directly to a UNITY.md file.
I'd like to propose that I drop by Secret Labs' office early next month and spend a few days addressing this. As such, I'd like to leave this as a meta-issue for now.

ExampleDialogueUI.cs compile error (ver 0.9.3 on Unity 5.2.1f1)

I get this error when importing the yarnspinner package:

"Assets/Yarn Spinner/Examples/Demo Scripts/ExampleDialogueUI.cs(147,42): error CS0246: The type or namespace name `WaitUntil' could not be found. Are you missing a using directive or an assembly reference?"

I have no idea what could be causing this or how to fix it (sorry, I'm a c# and unity newbie).

Compile to bytecode

To implement #2, we need a simple data structure that represents the entire program, and that we can have a simple "you are here" marker for. To that end, we'll need a bytecode representation of the program.

This is a big'n, so this issue will likely get multiple sub-issues.

Provide a nicer way to respond to commands

In the Unity integration, commands (like <>) are currently sent to the DialogueRunner's UI component's RunCommand method, which is expected to receive the full text of the command and parse it itself. This doesn't scale beyond a few commands while remaining in a single method.

It would be a lot nicer to have a simple API for registering for new commands. Maybe you pass the first word you're looking for, and then a delegate that receives the pre-split command as an array of strings? Or maybe you write classes that implement a method, and we use reflection to automatically register them when the scene loads?

Problem parsing if statements containing shortcut options

When using if statements to determine whether dialogue options should be presented, the normal options method works as expected, displaying all options that meet their conditions, but the same implemented with shortcut options displays only the first shortcut option to meet it's condition.

e.g.

<<if $foundClues == true>>
[[Place discovered clues on the board|MurderBoard.AddClues]]
<<endif>>
<<if $foundSuspects == true>>
[[Place known suspects on the board|MurderBoard.AddSuspects]]
<<endif>>
<<if $foundClues == true and $foundSuspects == true>>
[[I should try to tie some of these clues to a suspect|MurderBoard.AddCluesToSuspects]]
<<endif>>
[[Leave the board|MurderBoard.Exit]]

This displays all options which have met their conditions.

<<if $foundClues == true>>
->Place discovered clues on the board
    [[MurderBoard.AddClues]]
<<endif>>
<<if $foundSuspects == true>>
->Place known suspects on the board
    [[MurderBoard.AddSuspects]]
<<endif>>
<<if $foundClues == true and $foundSuspects == true>>
->I should try to tie some of these clues to a suspect
    [[MurderBoard.AddCluesToSuspects]]
<<endif>>
->Leave the board
    [[MurderBoard.Exit]]

This displays only the first option which meets it's condition.

Parse Exception Error Message Update

ParseException: Line 27:0: Expected a statement here but got Dedent instead (was there an unbalanced if statement earlier?)

^ would it be possible to get this to display the name of the node where the parse exception occurred?

That'd be a big help in tracking down these types of errors. :) (right now I'm getting one of these in a massive file)

Add unit tests, and use a CI system.

Yarn Spinner has a lot of tests, but nothing formal. Using a dedicated testing system, like NUnit, would make it possible to automate the testing. Additionally, using a CI system, like Travis, would make it even easier.

Dialogue.visitedNodes should be settable

The Dialogue object maintains a list of visited nodes in the visitedNodes property. However, this list is reset whenever the Dialogue object starts; this is undesired behavior when then the game is saved and quit, then re-started.

To fix this, Dialogue should allow for setting the visitedNodes property.

Colons seem to throw the parser off

<<if $germ_home_quest is 0>>
    [[HomeQuestIntro]]
<<elseif $germ_home_quest is 1>>
    [[HomeQuestTrampoline]]
<<endif>>

<<if $germ_home_quest > 0>>
    <<hide Collision:GermOnPorch>>
<<endif>>

The parser fails on the last line. I'm seeing the same thing happening in other files that where ":" is used in a command line etc.

Documentation on building

The current documentation assumes that you've downloaded a build, and are using that. This is fine for most users, but more advanced users will want to build a copy of the library themselves. We should have a guide on how to build from source.

Bug with parsing commands

Looks like the command "toggleBlinks" is throwing an error
"Expected a statement here but got BeginCommand (<<) instead (was there an unbalanced if statement earlier?)"

It doesn't compile with the text toggle, toggl, togg, tog, to but does compile with just: t. So I'm guessing maybe it's the word "to" that is throwing it off?

Don't include the $ prefix when communicating with variable storage

Yarn Spinner prefixes all variable names with $, to aid in parsing. However, when communicating with the variable storage system, this $ is included. This doesn't add anything meaningful, and causes confusion for users who are attempting to set variables outside of Yarn Spinner and forgetting to include the $.

The $ should be either removed, or made optional.

Interesting crash

Not sure what's up here...

ArgumentNullException: Argument cannot be null.
Parameter name: key
System.Collections.Generic.Dictionary`2[System.String,Yarn.Node].ContainsKey (System.String key) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Collections.Generic/Dictionary.cs:458)
Yarn.Dialogue.NodeExists (System.String nodeName) (at Assets/Yarn Spinner/Code/Dialogue.cs:321)
Yarn.Unity.DialogueRunner.NodeExists (System.String nodeName) (at Assets/Yarn Spinner/Code/DialogueRunner.cs:194)

Dialogue.Stop() sometimes has null VM

Wondering if It'd be alright to add a null check for the vm in Dialogue.Stop() (Dialogue.cs line 245)

Just seeing it error log a null there sometimes.

Not sure what's up here

screen shot 2016-02-23 at 12 22 37 pm

When running this text, I get a call to my run options function with 0 options. (every time running it, before getting to the if chunk with the actual options in it)

I also see "Cannot continue running dialogue. Still waiting on option selection." But things continue running fine.

yarn spinner for godot

Godot is a great open source alternative to unity! It has a json parser and uses gdscript - which is similar to python.

It would be amazing if someone ports yarn spinner to godot - so godot users can use yarn on godot games!

some links:
https://www.facebook.com/groups/godotengine/permalink/735320436604553/

http://www.godotengine.org/

http://docs.godotengine.org/en/latest/classes/class_dictionary.html?highlight=dictionary
http://docs.godotengine.org/en/latest/tutorials/engine/saving_games.html?highlight=json

Improve the "Extending" docs

Hey guys,

Great library! This has made bootstrapping a dialogue system for my game much easier! Awesome.
Wanted to call this out because it took me some trial and error to figure out:
https://github.com/thesecretlab/YarnSpinner/blob/master/Documentation/Extending.md is sneaky!
That's like, the best feature of the whole library and it's sort of tucked away.

The only reference I found to it was here:
https://github.com/thesecretlab/YarnSpinner/blob/master/Documentation/Usage.md#functions

but that suggests you can only run function in "if" statements, which seems to not be the case.
This becomes a super great DSL very fast. For me I now have just:
<<AcceptQuest()>> and that's awesome (much more powerful than the commands because you don't have to rely on global game objects).

Anyway, wanted to call out that the docs could use a quick addition or two to call attention to how good this bit is. : )

Standalone Yarn runner

It'd be really useful to provide a downloadable or browser-based app that loads a .json file authored in the Yarn editor, and runs it in a very simple, mostly text-based environment. This would allow users to try out Yarn without having to do any work inside Unity.

Video tutorial

It'd be super nice to have a very short "how to add Yarn Spinner to your game" video. These kinds of videos are great for both explaining how to do it, and make it a lot harder to skip out on explaining important steps.

Provide Node Tags in Yarn.Line

Currently, the DialogueUIBehaviour's RunLine receives a Yarn.Line argument, that only contains the text the line should display, it would be usefull if Yarn.Line could also contain the node's tags (they are in the json file).

This is usefull for providing the code with a speaker name, portrait ID, speech bubble color, etc. without needing to use a command call

Tags could also be provided in Yarn.Options

Assigning Source Text Dynamically

Hey!

For my Unity project I'm trying to setup the Yarn Spinner integration with the Dialogue Runner and Dialogue UI script to exist as a single interface and pass in new Text Assets into the DialogueRunner.cs's SourceText[] array as they're needed. For example, I have one UI screen that visualizes all of the text and I have different actors in the scene that you walk up to and activate. This replaces the Text Asset in the Source Text array and attempts to run dialogue.

The issue I'm currently having is that the first text runs fine (though it does so without me seeing a TextAsset being placed in the public SourceText array in the Inspector). Then when I approach another actor, it opens the Dialogue GUI but no text appears.

Would there be a better method to doing this? I think it has something to do with the Yarn.Unity.VariableStorageBehavior and DialogueRunner.cs' line 94 where the JSON files are being loaded in, but I figured there's probably an easier route to doing this than me rewriting some of the implementation.

Thanks for the help!

-Emerson

Separate program state from runner

The Runner class is the interface between the game and Yarn. Currently, it works using C# iterators to execute as much of the program until it hits a point where it needs input from the game - including stuff like "here's a line of dialogue, let me know when the player has read it so I know when to continue running" to "here's a choice for the player, I need to know what they picked".

Iterators work fine, but in the current implementation, it's not possible to stop iterating and resume it later. This means, for example, that if you're in the middle of a conversation in a Unity game, and you unload the scene, there's no way to pick up right where you left off when you come back.

You can get around this by adding checks and stuff to the Yarn program itself, but that doesn't scale at all.

A better choice would be to create a State class that represents the current running state of a dialogue; the Runner would then read and write to this state, which could stop and start at any point. This would also help out a lot with saving and loading.

One thing to think about: how does this relate to the variable storage system implemented by objects the implement the VariableStorage interface?

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.