Giter Club home page Giter Club logo

sheetify's People

Contributors

ahdinosaur avatar aknuds1 avatar bcomnes avatar casr avatar cedricraison avatar goto-bus-stop avatar hughsk avatar jimmywarting avatar jongacnik avatar juliangruber avatar m90 avatar mattdesl avatar mciparelli avatar mikkoh avatar pascalgermain avatar rafaelrinaldi avatar real-alexei avatar sethvincent avatar timoxley avatar timwis avatar toddself avatar yoshuawuyts avatar zhouhanseng 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

sheetify's Issues

not possible to style top-level element

want to be able to something like:

function render (props) {
  return h('div.' + prefix, {
    textContent: 'hello world!'
  })
}
:hover {
  color: green;
}

but instead had to do:

function render (props) {
  return h('div.' + prefix, [
    h('div.thing', {
      textContent: 'hello world'
    })
  ])
}
.thing:hover {
  color: green;
}

ran into this writing: js css

or am i missing something?

Readme Example Typo

First example, the prefix value does not match the "printed" markup.

.[[[ _60ed23ec9f ]]] h1 {
  text-align: center;
}

And the rendered HTML includes the namespace:

<section class="[[[ _60ed23e ]]]">
  <h1>My beautiful, centered title</h1>
</section>

Reusing css file puts duplicate styles in <head>

I have a few components that are similar and I'd like to share styles between rather than copying & pasting the styles into each component. So I put the shared styles into a .css file and imported them into each component via sf('path/to/styles.css'). Fortunately sheetify seems to know it's the same file so it uses the same class name for both components. But it still puts the contents of the .css file into the <head><style> tag twice:

screen shot 2016-10-07 at 06 53 57

Is that expected behaviour? Is there a better way to accomplish what I'm trying to do? The only other thing I can think of is to put the css on the parent element, but that feels like unnecessary coupling. Or use insert-css to just add css like olden times.

failing test: document is not defined

offending code here

evalmachine.<anonymous>:8
    var elem = document.createElement('style');
               ^
ReferenceError: document is not defined
    at module.exports (evalmachine.<anonymous>:8:16)
    at Object.2.insert-css (evalmachine.<anonymous>:28:39)
    at s (evalmachine.<anonymous>:1:254)
    at e (evalmachine.<anonymous>:1:425)
    at evalmachine.<anonymous>:1:443
    at ContextifyScript.Script.runInNewContext (vm.js:18:15)
    at Object.exports.runInNewContext (vm.js:49:17)
    at parseBundle (/home/travis/build/stackcss/sheetify/test/prefix.js:43:10)
    at /home/travis/build/stackcss/sheetify/node_modules/browserify/index.js:776:13
    at ConcatStream.<anonymous> (/home/travis/build/stackcss/sheetify/node_modules/concat-stream/index.js:36:43)

template strings for sheetify

I'm quite fond of hyperx, and how they use template strings. Might be cool to support them too in sheetify, e.g.

const vdom = require('virtual-dom')
const hyperx = require('hyperx')
const sf = require('sheetify')
const hx = hyperx(vdom.h)

const prefix = sf`
  h1 {
    text-align: center;
  }
`

const tree = hx`
  <section className=${prefix}>
    <h1>My beautiful, centered title</h1>
  </section>
`

console.log(vdom.create(tree).toString())

Does this make sense? Am I mad? I feel like this might be a bit too much / we really need to start thinking of how to consolidate the API as currently there's already too many ways of doing things haha, though this might be very useful.

getting an error when trying to create a bundle

I may be doing something wrong.
sheetify styles/app.css > bundle.css

/home/jacob/example-css-npm/node_modules/sheetify/node_modules/style-deps/node_modules/css/lib/parse/index.js:69
    throw err;
    ^

Error: missing '{' near line 2:1
    at error (/home/jacob/example-css-npm/node_modules/sheetify/node_modules/style-deps/node_modules/css/lib/parse/index.js:64:15)

Transform error should not kill build

Getting this when using an invalid filename to sheetify function:

assert.js:89
  throw new assert.AssertionError({
  ^
AssertionError: filename must be a string
    at parseCss (/projects/mattdesl.github.com/node_modules/sheetify/index.js:31:10)
    at sheetify (/projects/mattdesl.github.com/node_modules/sheetify/index.js:23:17)
    at nr (/projects/mattdesl.github.com/node_modules/browserify/node_modules/module-deps/index.js:297:23)
    at /projects/mattdesl.github.com/node_modules/browserify/node_modules/resolve/lib/async.js:44:21
    at ondir (/projects/mattdesl.github.com/node_modules/browserify/node_modules/resolve/lib/async.js:187:31)
    at onex (/projects/mattdesl.github.com/node_modules/browserify/node_modules/resolve/lib/async.js:93:22)
    at /projects/mattdesl.github.com/node_modules/browserify/node_modules/resolve/lib/async.js:24:18
    at FSReqWrap.oncomplete (fs.js:82:15)

And it kills budo and other tools. Instead, might be better for the transform to emit an error event so budo and similar tools can catch it and display this error in the browser.

Module ideas

What modules are we missing? For me the ones I'd like to pull in are:

  • autoprefixer
  • $var expander (so we can ditch sass at work)

Styles bleed through

Example:

/* comment.css */
:host .header {
  color: blue;
}
/* article.css */
:host .header {
  color: red;
}
<article class="_article-hash">
  <h1 class="header">Article title</h1>
  <section class="comments">
    <article class="_comment-hash">
      <h2 class="header">Comment title</h2>
    </article>
  <section>
</article>

Comment header will be either red or blue, depending on the order of file inclusion. You can guard with > selectors, but this makes your CSS dependent on the exact DOM structure.

Shadow DOM naturally does not suffer from this.
BEM does not suffer from this, due to using unique names.
CSS Modules does not suffer from this, due to rewriting all class names.
Vue implementation of scoped styles does not suffer from this, due to rewriting HTML too and using different prefixing technique (<div class="header" _article-hash> and .header[_article-hash]).

Get style string without using inline-css

Hi!

I am trying to implement themes for parameters manager with sheetify, and due to namespacing css sheetify is ideal for that.
The only issue is that whenever theme is changed, old theme’s css needs to be deleted, which is unclear how can be done with insert-css, here’s a ticket.

Is there any way to get css string without insert-css being engaged?

Thanks.

stream

Be able to do this:

const sheetify = require('sheetify')
const http = require('http')

http.createServer((req, res) => {
  const s = sheetify(__dirname + '/index.css')
  s.bundle().pipe(res)
}).listen()

CSS syntax error causes build process to halt

I could be wrong but the following maybe related #48 #69

I am running budo on the following JS with sheetify/transform setup in package.json.

var sheetify = require('sheetify');

var prefix = sheetify`
  div {
    color: #F0F;
  }

  .A {
    color: #FF0;
  }
`;

var container = document.createElement('div');
container.className = prefix;

container.innerHTML = `
  <div>
    <div>HELLO</div>
    <div class="A">WORLD!</div>
  </div>
`;

document.body.appendChild(container);

If I make a syntax error in CSS such as remove a closing curly brace I get the following stack trace:

/Users/mikkohaapoja/Documents/Learning/test-sheetify/node_modules/postcss/lib/lazy-result.js:177
        if (this.error) throw this.error;
                        ^
CssSyntaxError: <css input>:5:3: Unclosed block
    at Input.error (/Users/mikkohaapoja/Documents/Learning/test-sheetify/node_modules/postcss/lib/input.js:61:22)
    at Parser.unclosedBlock (/Users/mikkohaapoja/Documents/Learning/test-sheetify/node_modules/postcss/lib/parser.js:466:26)
    at Parser.endFile (/Users/mikkohaapoja/Documents/Learning/test-sheetify/node_modules/postcss/lib/parser.js:348:39)
    at Parser.loop (/Users/mikkohaapoja/Documents/Learning/test-sheetify/node_modules/postcss/lib/parser.js:86:14)
    at parse (/Users/mikkohaapoja/Documents/Learning/test-sheetify/node_modules/postcss/lib/parse.js:26:12)
    at new LazyResult (/Users/mikkohaapoja/Documents/Learning/test-sheetify/node_modules/postcss/lib/lazy-result.js:61:24)
    at Processor.process (/Users/mikkohaapoja/Documents/Learning/test-sheetify/node_modules/postcss/lib/processor.js:34:16)
    at /Users/mikkohaapoja/Documents/Learning/test-sheetify/node_modules/sheetify/index.js:41:13
    at /Users/mikkohaapoja/Documents/Learning/test-sheetify/node_modules/sheetify/index.js:52:7

When this error is thrown it will kill budo.

--watch/-w option

Are there plans to add a watch option?

So far I've been using chokidar-cli to do something like this:

chokidar 'css/*.css' -c 'npm run bundle-css'

Where the bundle-css script is the sheetify command.

That works fine for now. But being able to watch changes with -w would be neat.

add support for including files that aren't namespaced

From #32

Scoping styles to components is ideal as a transform, but sometimes global files must be included too (such as reset.css). These files shouldn't be namespaced, but should be included in the resulting bundle.

  • What syntax do we want for this?
  • Should these files perhaps only be prepended during an exorcise step?
  • any other things to consider?

basedir should default to caller path

use case is using sheetify in a "universal" / "isomorphic" app, so as both a browserify transform and in node.

in node, if i want to do

const sheetify = require('sheetify')
const prefix = sheetify('./index.css')

in anywhere other than the same directory as process.cwd(), i'll get an error

Error: Cannot find module './index.css' from '/cwd'
    at Function.module.exports [as sync] (/cwd/node_modules/style-resolve/node_modules/resolve/lib/sync.js:32:11)
    at Function.resolveSync [as sync] (/cwd/node_modules/style-resolve/index.js:22:19)
    at sheetify (/cwd/node_modules/sheetify/index.js:20:22)
    at Object.<anonymous> (index.js:5:16)
    at Module._compile (module.js:435:26)
    at loader (/cwd/node_modules/babel-register/lib/node.js:127:5)
    at require.extensions.(anonymous function) (/cwd/node_modules/babel-register/lib/node.js:137:7)
    at Object.nodeDevHook [as .js] (/cwd/node_modules/node-dev/lib/hook.js:43:7)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:311:12)

sure i could do

const sheetify = require('sheetify')
const prefix = sheetify('./index.css', { basedir: __dirname })

but this seems silly...

i propose we default basedir to the caller path instead of process.cwd(). it's already being defaulted to process.cwd() in the cli module and path.dirname(filename) in the transform module, will need to default to something in the stream module.

thoughts?

Support for post-css plugins?

Hiya,

From looking at the source, I saw that postcss is used internally, but it’s not possible to use any of the available plugins with sheetify. Instead, there is a separate plugin mechanism implemented.

Would it be possible to also allow postcss plugins?

Cannot find module 'insert-css'

I have the following:

const sf = require('sheetify')
const prefix = sf`
  h1 {
    text-align: center;
  }
`
console.log(prefix)

I'm guessing it gets transformed into some code that includes require('insert-css') – this won't work unless my project explicitly installs that as a dependency. Due to npm3 flattening, this might not be immediately obvious in some projects. 😄

Variables don't work in template strings

When you use a template in your tagged template string css, the browserify transform breaks.

var aColor = '#fff'

var css = sf`.some-class {
  background-color: ${aColor};
}`
CssSyntaxError: /Users/bret/repos/hyperamp/renderer/index.js:3:2: Unknown word while parsing file: /Users/bret/repos/hyperamp/renderer/index.js

  1 | .some-class {
  2 |   background-color: ,;
> 3 | }[object Object]
    |  ^

I started down the road of correcting the in-node implementation, but we are going to have to statically analyze the template string during the transform somehow. I'm not too familiar with this kind of thing. Any ideas?

bcomnes@112d7b7

This is related to #54 but specific to the way template variables are busted right now vs how :host gets used.

Live style update

Hi @yoshuawuyts!

I am giving a try to sheetify in gl-spectrum and constantly stumbling on the following issue.

I use budo for developing, and sheetify is enabled as "browserify": {"transform": ["sheetify/transform"]} field in package.json. Styles are connected as follows:

var sf = require('sheetify');
var cssClass = sf('./index.css');

function Spectrum () {
...
    this.container.classList.add(cssClass);
...
}

The problem is that each time when I have to change anything in the index.css, I have to restart budo, which is quite annoying tbh.

Is there any sort of workaround, or it is not the sheetify’s concern at all, but rather budo’s?
I might mistaken, but that is not the same for glslify, it updates consistently without server restart.

Thanks.

Javascript styles

I'd like to be able to do:

var sf = require('sheetify-js')

var className = sf({
  foo: {
    fontSize: 20
  }
})

This seems like it really doesn't need to be in Sheetify core, it can just be a wrapper. Similar to sheetify-sass, there can be sheetify-js.

I could implement this, but first I need to find a good library for transforming a json object to css. Does it exist? I've had a hard time finding anything good.

Error: write after end

It appears when piping to an external stream, things can sometimes go wrong. First read goes well, but then sometimes it fails on write after end. I feel this has something to do with events not being read correctly (e.g. perhaps a conflict with watchify). Opening an issue to gather more details - this might very well be userland, but then better docs are required

custom naming schemes

@helveticade pointed out that inspecting HTML with just hashes doesn't relay a lot of information per-se; when debugging having something like the file filename / dirname available is neat; perhaps we can expose a hook for people to create their own prefix (in addition to the hash, which guarantees uniqueness). Thoughts?

Cannot import library with ES Modules

I'm using "babel-preset-es2015": "^6.9.0" with "babelify": "^7.3.0", and import sf from 'sheetify'; doesn't work (5.0.3 of sheetify). I get:

Uncaught TypeError: (0 , _sheetify2.default) is not a function
1.on-load @ google-map.js:4
s @ _prelude.js:1
(anonymous function) @ _prelude.js:12
../components/google-map @ index.js:3
s @ _prelude.js:1
e @ _prelude.js:1
(anonymous function) @ _prelude.js:1

[discussion] refactor api

supersedes #31

current status

sheetify's api is a bit all over the place. Currently we support:

  • js callback api
  • js stream api
  • command line
  • browserify transform (both js and package.json styles)

When sheetify was first written, it served as an alternative to LESS and SASS. The current version aims to provide namespaces as a browserify transform and import dependency resolution to arbitrary preprocessors (never fully implemented).

proposal

I propose we refactor the api to work optimally for the current goals. This way we can shrink the code base, provide a more coherent story to users and generally improve the usefulness of the tool.

The changes I'd like to see are:

  • drop the cli api - other preprocessors have cli's that can be used directly
  • drop the js callback and js stream api - other preprocessors have api's that can be used directly
  • add a way to trigger file watches from the transform (related GH-21)
  • add a way to exorcise inline styles from the resulting browserify bundle
  • add support for inline template strings in addition to external files (related GH-31)
  • add support for including files that aren't namespaced (such as reset.css) (iirc @hughsk had ideas for this)

caveats

  • sheetify can no longer be used as a linker; this was never fully implemented but would no longer be possible
  • sheetify becomes dependent on browserify - imo that's a good thing as that is where the value is added. webpack support could be added through an external browserify -> webpack adapter

I hope I'm making sense with this all; I'd be happy to see sheetify progress down the path to simplification.

cc/ @hughsk @ahdinosaur I'd be keen to hear your thoughts on this

Uncaught TypeError: Cannot read property 'isFile' of undefined

If you're receiving the error

Uncaught TypeError: Cannot read property 'isFile' of undefined

You probably forgot to tell browserify/budo to use the sheetify/transform.

browserify index.js -t sheetify/transform -o bundle.js
budo index.js -- -t sheetify/transform

I feel like I run into this error every few days and spend 20 minutes debugging before slapping my forehead. Next time, I'll search the issues and find this thread.

include default plugins

From #32 (comment)

an open question is whether we specify any default plugins, such as what css-modulesify does. specifically i'm thinking of postcss-import, perhaps also a way to pass values around (like this but in our own way).

I reckon including postcss-import is a good call; if any other plugins should be included too this would be the right place to discuss.

source maps

Did a bit of debugging yesterday, and I think we're messing up the nodes slightly hah

watch mode

Reopening this as a follow up to #21.

Currently watchify kinda works with sheetify - since only new .js files are recompiled by browserify, just recreating new files from these events will cause incomplete files on recreation.

Maybe it's what #21 suggested, but I think the solution to this might be to send the stream in opts.out a "file" event so that it knows which are the new files, and which should be cached. I don't know how this would work on the cli tho in conjunction to watchify - should we perhaps have plugins such as browserify to make this work? I'm not sure, hah.

Thanks!

CLI

I removed it just to push v3 out, think we should restore it soonish (':

Hoy to use npm packages with no css in "main" but "style" defined?

I have the following problem. I'm using two npm packages that use css defined on their package.json files. The first one, tachyons, defines in the main and style section a path to the css. The second one, leaflet, define a js file in his main section and a css file in his style section. When I use them with sheetify, tachyons work, but leaflet fails. I'm using it like this.

const sf = require('sheetify')

sf('tachyons', { global: true })
sf('leaflet', { global: true })

That result in

Error: Cannot find module 'leaflet' from '/home/yerko/Dev/partes/src' while parsing file: /home/yerko/Dev/partes/src/app.js

So, am I doing it wrong? or should sheetify recognize leaflet css file?

global option defaults to true?

Tests should answer this but the build is currently failing. From what I can tell, if I don't pass { global: false } I don't get my styles namespaced.

Conflicts with babelify + es2015 preset?

Getting the following when babelify is the first transform. Does not occur when babelify is defined after sheetify.

path.js:8
    throw new TypeError('Path must be a string. Received ' +
    ^

TypeError: Path must be a string. Received undefined
    at assertPath (path.js:8:11)
    at Object.posix.join (path.js:479:5)
    at walk (/projects/mattdesl.github.com/node_modules/sheetify/transform.js:144:14)
    at walk (/projects/mattdesl.github.com/node_modules/sheetify/node_modules/falafel/index.js:49:9)
    at /projects/mattdesl.github.com/node_modules/sheetify/node_modules/falafel/index.js:46:17
    at forEach (/projects/mattdesl.github.com/node_modules/sheetify/node_modules/falafel/node_modules/foreach/index.js:12:16)
    at walk (/projects/mattdesl.github.com/node_modules/sheetify/node_modules/falafel/index.js:34:9)
    at /projects/mattdesl.github.com/node_modules/sheetify/node_modules/falafel/index.js:41:25
    at forEach (/projects/mattdesl.github.com/node_modules/sheetify/node_modules/falafel/node_modules/foreach/index.js:12:16)
    at /projects/mattdesl.github.com/node_modules/sheetify/node_modules/falafel/index.js:39:17

Gulp support

is there a way too put this in a .pipe() in gulp?

my use case is i want a local file to be watched and on file change for my css to be re-bundled.

[roadmap] sheetify v6

Been using sheetify for a while now, and feel there's some rough edges still. In this issue I try and outline which problems I've been experiencing, and suggest solutions on how to fix 'em. Comments and suggestions super welcome; if we need to break stuff this is the place to mention it

problems

  • insert-css as a peer dep is hella annoying and probably makes for a not-so-great user experience - using sheetify right now doesn't "just work" and yeah it would be cool if it did
  • we don't support inline globals, which would be like a cool thing to support as it allows for inline var declaration, overrides and other stuff - people I'm working with right now have requested this already and kinda confirmed my idea this would be neat - #98 | #91 | #97 (ish)

todos

  • make insert-css a file in sheetify that can be required in, removing the peer dep (pr
  • patch css-extract to also detect the sheetify/insert (minor patch; just pick up more files)
  • merge stackcss/postcss-prefix#9 so only :host properties are prefixed

And that's it. Suggestions and thoughts super welcome!

The road forward

What changes need to happen going forward? Stuff I'm currently thinking about doing:

  • expand plugin ecosystem (e.g. reach feature parity with myth)
  • more unit tests
  • code coverage
  • possibly expose as a browserify transform? E.g. require('mystyle.css) -> inline-string with sheetify transforms applied. Not sure if it should be a separate project though (sheetifyify?); I like the idea of having a single module to rule them all though.

What else would be nice to have?

Why is the global option gone?

Why did the global option disappear in a3a3ff591832802c924cc6652e228ee19af2335e? AFAICT, styles are now global unless you prefix them with :host. The current README also indicates this, in that :host is used consequently in order to namespace styles.

Is the reason for this change documented anywhere?? Reason I'm asking is my project broke with an upgrade to sheetify, and I had no idea why my styles were no longer namespaced.

passing vars around

update 2016/03/14: :root isn't a very good idea; we should just rely on JS vars instead.
update 2016/11/16. scratch that, :root in moderate usage is a great idea, as long as a postprocessor can handle it (which it can now)

  • write tests for ES6 string interpolation
  • document variable passing using string interpolation

What do you think of making :root elements the transport for variables and such? Root would never be prefixed, and passed down to child elements so they can read values from it during compilation. Perhaps we should also do this for @custom-media selectors so breakpoints are passed down too? (example)

Opinions?


client-main.js

const sf = require('sheetify')

sf`
  :root {
    --color-bg-primary: blue;
  }
`

require('./client-sidebar')

client-sidebar.js

const sf = require('sheetify')

sf`
  :host > h1 {
    background-color: var(--color-bg-primary);
  }
`

Open Source?

It'd be nice to get this and style-deps out there in some capacity, have a couple of side projects I'd like to try use it in too. What else would we need to get sorted before we go ahead with it?

  • Deciding on a license.
  • Do we want to change the name?
  • Documentation.
  • Ironing out the API.

Semver issues

As mentioned in #8 we need to think of a way to reliably handle semver. In CSS everything is globally spaced so different orders of importing can cause different results. We need a way of handling different semver versions reliably / transparently, however that may be.

needs example cases

prefixes are being applied to arguments

trying to use the :nth-child pseudo-selector and its not working as expected:

.row {
  border: 1px solid black;
}

.row:nth-child(odd) {
  background-color: #eee;
}
<div>
<div class="row">yo!</div>
<div class="row">dude</div>
</div>

Should produce:
image

However, when you do this with sheetify, the transformed CSS looks like:

._ffdf2d5a .row {
  border: 1px solid black;
}
._ffdf2d5a .row:nth-child(._ffdf2d5a odd) {
  background-color: #eee;
}

Which does not apply the rule to the odd nth-children. If you add a rule in your inspector omitting the ._ffdf2d5a prior to odd:

._ffdf2d5a .row:nth-child(odd) {
  background-color: #eee;
}

It works as expected.

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.