Code Monkey home page Code Monkey logo

motionone's People

Contributors

andarist avatar ankon avatar davedbase avatar mattgperry avatar olgam4 avatar productdevbook avatar ps73 avatar ranolp avatar thetarnav avatar xanderbale 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

motionone's Issues

[Documentation] Clarify the timeline

Hi :),

When using the following timeline:

const contentElements = this.querySelectorAll('.content-over-media__content');

    timeline([
      [this.items, {opacity: 1, y: [10, 0]}, {duration: 0.3}],
      [contentElements, {opacity: [0, 1]}, {duration: 0.2, at: '+0.1'}]
    ]);

I was initially setting manually for each element the opacity to 0:

contentElements.forEach(item => item.style.opacity = 0);

However, I found that Motion seems to do that under the hood automatically so that the timeline works as we expect it to work (this is awesome btw). I think it would be nice to add this in the doc:

When using a timeline, all the timeline steps will be initialized with their start values at the start of the animation. As a consequence, you do not need to manually change the style of each timeline elements.

Timeline: option to disable autoplay

For manual control with the .duration property and progress 0...1 it's now required to call .pause() directly as timelines start automatically.

Would it be nice to have a { autoplay: false } option on the timeline? 👍

[FEATURE] Spring/glide support for Dev Tools

spring and glide easings are actually keyframe generators. When these are visualised in Dev Tools, they are shown as a series of keyframes spaced 10ms apart with linear easing. Whereas we would ideally mark out blocks generated by spring/ease as just a single keyframe. Likewise when the settings for these are changed via the editor we change the placement of subsequent keyframes based on the calculated duration.

style.transform woes

animate uses its own css variables with style.transform to enable individual transforms.

But this took me by surprise:

el.style.transform = "translateX(100px)";
/** other styles **/

// ... later ...

animate(el, {rotate: 45});
// translateX is lost

// from now on, I have to remember that motion "owns" style.transform on this element (through its --motion-* css vars)
// if I try to change the transform with any other method, I'll mess up the styles

Because individual transforms are so useful, it'd be nice to opt in even when I don't want an animation. For example:

import {style, animate} from "motion";

style(el, {x:100 /** other styles **/});
// ... later ...
animate(el, {rotate: 45});

I don't know if that's API heresy for a library called motion, though.... 😂

Go-to way of making a mouse-driven spring animation?

Hi there!

Super great project 👍 Thinking about using it as my main way of doing animations in production projects—looks really promising, fast, lean!

I want to recreate this Svelte spring example. I've seen a demo on sponsoring page, but it's kind of easier in a sense that it only triggers animation on dropEnd.

Here's a very naive implementation that just doesn't work like I want it to.

Screen.Recording.2021-12-24.at.15.58.35.mov

I tried the following approaches:

  1. throttling—makes animation even more janky.
  2. any combination of calling stop/cancel/finish/nothing. stop results in best animation from them all, but it still looks far from perfect.

Can you point me in some direction? Or maybe it's not a thing this library wants to solve at all?

Thanks!

Vue warning on every render

There is a Vue warning during every render of a Motion component wrapped in Presence. Not sure if the fix should be in the library or how the docs show to use the components. Here's the warning:

[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance. 
  at <Transition name=undefined onEnter=fn<bound enter> onLeave=fn<bound exit>  ... > 
  at <Presence exitBeforeEnter="" > 
...

Monorepo - Warning - Loosing Git History

@mattgperry I see you are working on moving to a mono-repo structure. Moving/renaming existing files to a new folder's respective name will remove the git history of those files.

Git does not support this right now, and there is a considerable discussion about it. What worked for me was moving/renaming files with this script, but be careful it rewrites its history.

https://gist.github.com/emiller/6769886

Thx!

[BUG] Stagger easing does not seem to work with "ease-in" value

1. Describe the bug

When using an "ease-in" mode for stagger, everything appears right away.

2. IMPORTANT: Provide a CodeSandbox reproduction of the bug

https://codesandbox.io/s/busy-elbakyan-bg3y1k?file=/src/index.js

4. Expected behavior

I am trying to achieve a stagger animation where the items are revealed faster and faster. According to the doc I should be able to use the "easing" with an ease-in value, but unfortunately all the items are revealed at the same time, without any delay at all.

When using "ease-out" it seems items are revealed one after the other properly, but as far as I understand it ease-out is the opposite of what I am looking for (so revealing slower and slower).

Thanks for this awesome library :).

[Feature] Consider re-exporting types from 'motion' pacakge

Is your feature request related to a problem? Please describe.

I'm using motion to animate elements in some web components written in Typescript. I keep a ref to some animate calls to be able to cancel/stop/finish them if needed in certain cases. However, I cannot properly type these ref variables as AnimationControls instances easily because the types are imported internally from @motionone/types and not reexported from motion.

Describe the solution you'd like

If this is a pattern you agree with, you can export * from '@motionone/types' in the primary motion package index so that they could be imported with a statement like import { animate, AnimationControls } from 'motion'; for easier use in Typescript projects.

Example use case:

import { animate, AnimationControls } from 'motion';

export class ExampleElement extends LitElement {
    // ...
   private animationRef?: AnimationControls;
   // ...
}

Describe alternatives you've considered

I could install and import the types from @motionone/types if that's your preferred solution. I'm interested in the context behind why though.

[BUG] Animation isn't triggered first time toggling Motion component in Vue

1. Describe the bug

The first time the variable controlling v-show on the Motion component changes, the specified animation doesn't trigger. It is only on subsequent value changes of the variable that the animation triggers.

2. IMPORTANT: Provide a CodeSandbox reproduction of the bug

https://codesandbox.io/s/first-animation-doesnt-work-5vv8z6

3. Steps to reproduce

Steps to reproduce the behavior:

  1. Click "toggle". You should see the box appear without an animation.
  2. Click "toggle". You should see the box disappear with an animation.
  3. Click "toggle". You should see the box appear with an animation.

4. Expected behavior

I expect the box to appear with an animation the first time I press the "toggle" button.

5. Video or screenshots

bug

6. Browser details

Bug occurs when using Windows 10 & 11 and when using Brave (Chromium) and FireFox.

[Feature] Add playState

1. Describe the bug

The controls returned from animate have a playState field, but this field is a method that throws an exception when called instead of a plain string.

2. IMPORTANT: Provide a CodeSandbox reproduction of the bug

https://codesandbox.io/s/amazing-pare-c2lyrm

3. Steps to reproduce

Steps to reproduce the behavior:

  1. Open the code sandbox
  2. Open the console
  3. You will see a function where the playState string should be

4. Expected behavior

playState should return a string, not a function (that when called fails).

5. Video or screenshots

Screen Shot 2022-05-18 at 17 00 28

6. Browser details

Chrome 101, OSX 12.4

[BUG] Repeat inside a timeline

Hi,

I am not sure if this is done on purpose, but I am trying to create a timeline where the last sequence only should be repeated:

this._timeline = timeline([
      [this.contentElement, {y: -10, opacity: 0}, {duration: 0.2}],
      [this.animationElement, {y: [10, 0], opacity: 1}, {duration: 0.5}],
      [this.animationElement.children, {y: [0, -10], opacity: [1, 0.5]}, {duration: 2, repeat: Infinity, stagger: 0.5}]
]);

This does not seem to work, but I am not sure if this is by design or a bug. Feel free to close this if this is by design.

Feature request: `autoAlpha` from GSAP

Hi Matt,

Very excited about this library. As a long-time GSAP user, this is a really compelling option and I'm tempted to start migrating a couple of projects from GSAP. Before diving into that process, there is a GSAP feature called autoAlpha that I use heavily throughout my projects. GSAP CSSPlugin docs describe it as follows:

Identical to opacity except that when the value hits 0 the visibility property will be set to hidden in order to improve browser rendering performance and prevent clicks/interactivity on the target. When the value is anything other than 0, visibility will be set to inherit. It is not set to visible in order to honor inheritance (imagine the parent element is hidden - setting the child to visible explicitly would cause it to appear when that’s probably not what was intended). And for convenience, if the element’s visibility is initially set to hidden and opacity is 1, it will assume opacity should also start at 0. This makes it simple to start things out on your page as invisible (set your CSS visibility: hidden) and then fade them in whenever you want.

Is this something that you would consider adding to this library? (I am definitely not married to the name autoAlpha as I believe that naming is an artifact from GSAP's flash days)

Timeline: Pause and manual currentTime to duration resets

Going deep into custom timelines with this one, but it's an odd bug.

🟢 Scenario 1: You create a timeline and it starts playing automatically, eventually currentTime is equal to duration.
🔴 Scenario 2: You create a timeline and pause it, then manually transition it from 0...1 * duration (on scroll for example).

For some reason when you run .pause() and set currentTime = duration it appears to just reset to the initial state?
If you let it autoplay and finish and then set currentTime = duration it's fine.

Here's a sandbox that reproduces the scenario: https://jp0on.csb.app/

Presence component type not exported from motion/vue

When I try and use import { Motion, Presence } from "motion/vue", the Motion component's types are automatically discovered but Presence is missing which makes the typescript compiler think Presence is not a declared export of the motion/vue package. This is just a typescript problem and not an actual problem with the package exports (code works fine).

As a temporary fix I've redeclared the module in my own types as:

declare module 'motion/vue' {
  import { Motion, Presence } from '@motionone/vue/dist'
  export { Motion, Presence }
}

[Bug] Reversing a reversed animation jumps immediately to the finish frame

1. Describe the bug

Reversing a reversed animation jumps directly to the finish frame.

2. IMPORTANT: Provide a CodeSandbox reproduction of the bug

https://codesandbox.io/s/inspiring-night-qfc3mp?file=/src/App2.tsx

3. Steps to reproduce

Steps to reproduce the behavior:

  1. Open the sandbox
  2. Hover the mouse over the box
  3. The box will grow, then immediately jump to the end of the animation.

4. Expected behavior

The box should grow, shrink then grow again until the end of the animation.

5. Video or screenshots

N/A

6. Browser details

OSX 12.4, Chrome

Read duration from timeline

Hi Matt,

I was looking to replace some of my anime timelines with Motion and there's one thing I'm missing: duration.
It's useful for manually playing a timeline (for example on scroll or interaction based on progress):

Right now in anime I do this:

tl.seek(tl.duration * p); // p = 0...1

In Motion I would like to do this:

tl.currentTime = tl.duration * p // p = 0...1

Couldn't do a PR because forking is disabled, codebase is very clear tho! 🙌 💯

Prevent potential error: "TypeError: animation[key] is not a function"

Because of the way the proxy object for animation controls is set up, any property on the controls object is reported as a function if you do something like this:

controls.whatever instanceof Function

Some people use the below code to test if an object is a Promise.

foo.then instanceof Function

Code deep in the framework I was using did this to an animation control object and then tried to call the "then" function, which doesn't actually exist.

Would it make sense to check whether the property actually exists in this part of controls.ts?

default:
return () => target.animations.forEach((animation) => animation[key]())

perhaps something like

(animation[key] instanceof Function) ? animation[key]() : animation[key])

Sorry if my understanding is off, I just learned what Proxy objects are this morning :)

Support animating individual transform properties?

Since Safari does not support animating variables yet, maybe animate using the individual transforms (which it does support) instead.

https://webkit.org/blog/11420/css-individual-transform-properties/
https://drafts.csswg.org/css-transforms-2/#individual-transforms
https://developer.mozilla.org/en-US/docs/Web/CSS/translate
https://caniuse.com/mdn-css_properties_translate

I'm assuming this would require some feature sniffing at runtime to see which functionality is supported by the current browser.

Timeline: Support adding labels

Internally, it supports labelled times (at: "arbitrary-label"), but there's currently no way of defining what these labeled times map to.

[Bug] finished promise resolves immediately after initial play / stop / play or initial play / cancel / play

1. Describe the bug

The second finished promise resolves immediately if awaited after a stop/play sequence or cancel/play sequence, rather than when the second part of the animation is actually finished.

Note that if the first part (initial play) does not use the finished promise then it works correctly.

According to the web animation API:

Note: Every time the animation leaves the finished play state (that is, when it starts playing again), a new Promise is created for this property. The new Promise will resolve once the new animation sequence has completed.

But it seems to be stuck returing the first finished promise at the time it was created.

2. IMPORTANT: Provide a CodeSandbox reproduction of the bug

https://codesandbox.io/s/focused-germain-l9ln3b?file=/src/App2.tsx

3. Steps to reproduce

  1. Open the code sandbox
  2. Mouse over the box
  3. The box will grow bigger, show "e1" on the console, then start growing bigger, show "e11" and stop growing

4. Expected behavior

  1. Open the code sandbox
  2. Mouse over the box
  3. The box will grow bigger, show "e1" on the console, then start growing bigger, then stop growing and then show "e11"

5. Video or screenshots

N/A

6. Browser details

OSX 12.4, Chrome

[BUG] Issue with Safari without start value

Hi,

This is another quirk of Safari implementation and I am not sure this can be solved.

I have written a minimal test case here: https://codesandbox.io/s/adoring-easley-v7rzyd?file=/src/styles.css

Once you click the button, it will start the animation and then wait 4 seconds, and finish the animation.

On Chrome, this works properly:

chrome.mov

On Safari, you can see an odd rendering at the end:

safari.mov

To solve this issue, I had to change the line 30 from:

[button.lastElementChild, {y: -10, opacity: 0}, {duration: 0.15}]

to:

[button.lastElementChild, {y: [0, -10], opacity: 0}, {duration: 0.15}]

But from what I understand from Motion doc, it should infer the previous value so this should not be needed.

Thanks!

[BUG] Cannot find module '/sandbox/node_modules/@motionone/vue/dist/motion-vue.cjs.js'. Please verify that the package.json has a valid "main" entry

1. Describe the bug

Environment: Vue 3, Vite, and Vitest (https://github.com/vitest-dev/vitest).
Importing the library in a component works fine, when using vite, but when imported using vitest, it throws an error (see sandbox).

2. IMPORTANT: Provide a CodeSandbox reproduction of the bug

https://codesandbox.io/s/icy-sky-kin15v?file=/README.md:29-97

3. Steps to reproduce

Steps to reproduce the behavior:

  1. Toggle in browser -> works fine
  2. Second terminal
    a) yarn test -> error
    b) mv ./node_modules/@motionone/vue/dist/motion-vue.ssr.js ./node_modules/@motionone/vue/dist/motion-vue.cjs.js
    c) yarn test -> works
  3. Reload of dev server -> still works

4. Expected behavior

Importing the library using vitest works as well.

5. Analysis

Executing the project normally with "yarn dev" works fine, but running the test with "yarn test" throws an error.
The file "dist/motion-vue.cjs.js" referenced in @motionone/vue/package.json#7 does not exist.

Two fixes I found:

  1. renaming the file "dist/motion-vue.ssr.js" to the value above or
  2. changing line 7 of the package.json to "dist/motion-vue.ssr.js" both do the trick.
    The execution of the vite dev server is unaffected.

Seems to me, the bug at a mismatch between the aforementioned package.json and the rollup build config at vue/rollup.config.js#126, where the output file of the build command for the cjs-format is set to the ssr.js-version of the file name, but in the package.json it is referenced as .cjs.js. Though I admit, I'm certainly no expert on the topic.

[Feature] pre-generate keyframes for onion skinning

Is your feature request related to a problem? Please describe.
Not sure if this is currently supported, but it'd be nice to be able to somehow generate every state when changing params around to better see how they will behave without needing to run the animation. This could potentially be beneficial for snapshot testing or even creating videos like remotion since every frame can be generated. Ideally, we could eventually visualize slices of animation properties and how they will perform.

Describe the solution you'd like
I think something similar to the animate function, but it produces keyframes:

const animation = keyframes(
  { scale: 0.96, opacity: 0 },
  { scale: 1, opacity: 1 },
  { easing: spring() }
);

// [{ scale: 0.9689, opacity: 0 }, { scale: 0.9713, opacity: 0.1231 }, { scale: 0.9798, opacity: 0.2712 }, ...]

I think these should be general numbers if possible so they are flexible for different use cases.

Describe alternatives you've considered
I was looking into these other libraries, but would like to stay in one library for everything if possible 😊

https://github.com/codepunkt/css-spring
https://github.com/ymzuiku/vanilla-spring
https://github.com/hemlok/spring-keyframes

[FEATURE] Timeline as function

Hi,

I am not sure if this is something that can be integrated, but is it possible to actually have a method defined as a timeline step, like this:

const sequence = [
  ["nav", { opacity: 1 }],
  () => {doSomething},
  ["nav li", { opacity: 1 }, { at: "-0.2" }],
]

The use case here is that I am trying to have a timeline where I hide a text, change its content and then make it visible again.

I can of course do this by using two different animate method, but using a timeline would make it a bit easier to read.

Thanks :)

[Feature] Starting an animate/timeline in a stopped state

Is your feature request related to a problem? Please describe.

I'd like to call animate on an element but have it NOT start as soon as animate is called, but rather at a later time via controls.play().

Describe the solution you'd like

const controls = animate(..., {
  // guess this would be only available "globally" and not per css property
  autoPlay: false
})
// later, for example when a button is pressed
controls.play()

Describe alternatives you've considered
Calling animate only on first play and cache it. It would work, but then I'd have to mock some stuff until it is actually created (e.g. the finished promise, playState, etc).

Another option is calling pause() as soon as the controls get returned.

Additional context

Might be related to issue #22

[Bug] finished promise does not throw when the animation is cancelled

1. Describe the bug

According to the docs, if an animation is cancelled the finished promise should throw, but it completes instead.

I don't really mind if it doesn't throw, but then either the docs should be changed to match the current behaviour or the behaviour should change to match the docs :)

2. IMPORTANT: Provide a CodeSandbox reproduction of the bug

A CodeSandbox minimal reproduction will allow us to quickly follow the reproduction steps. Without one, this bug report won't be accepted.

3. Steps to reproduce

  1. Open the code sandbox
  2. mouse the mouse over the box

4. Expected behavior

The mouse over finished promise should throw since the animation gets cancelled before completion, but it completes ok.

5. Video or screenshots

N/A

6. Browser details

OSX 12.4, Chrome

animating individual transform values are susceptible to main thread load

I'm unsure if this is a known limitation, but I'm noticing that animating individual transform properties like scale and x means that the resulting animation is susceptible to dropped frames caused by main thread load. This is in both Chrome 94 and Safari 15, motion 10.3.2.

If I run this code, while blocking the main thread, my animation freezes:

 animate(
    el,
    {
      scale: toggle ? 0 : 1
    },
    { duration: 10, allowWebkitAcceleration: true }
  );
CleanShot.2021-10-18.at.17.30.07.mp4

If I run this same code, but animate using transform exclusively, my animation continues to play smoothly:

animate(
    el,
    {
      transform: `scale(${toggle ? 0 : 1})`
    },
    { duration: 10, allowWebkitAcceleration: true }
  );
CleanShot.2021-10-18.at.17.34.31.mp4

And unsurprisingly, running that same animation with WAAPI itself runs fine, too.

Unfortunately this means that seemingly all spring based animations are susceptible, since animating transform with springs doesn't appear to work.

Sandbox: https://codesandbox.io/s/silly-wind-38xgm?file=/src/index.js

[FEATURE] - Animate out of DOM for React API

If this is off-base just a quick explanation and close issue would be great.

We're specially interested in actually migrating off framer-motion to MotionOne mostly because we're chasing the smallest possible bundle size.

I'm wondering if supporting layout and AnimatePresence in MotionOne conflict with the design goals? Especially if they're implemented in way that results in potentially opt-in bundle size changes and even further potentially only within the React API?

We'd love to be able to leverage 60 FPS animations while cutting the bundle size cost of animations by 36.6KB (minified) (tenative because obviously implementing these features would grow the size).

proposed api

import { AnimationPresence } from "motion/react/animate-presence" // tree shakeable import
import { Layout } from "motion/react/layout";  // tree shakeable import

return <motion.div layout={Layout} animate={{x: Math.random() > 0.5 > 50 : 0}}/>

Dependent on how motion.div could handle an option layout prop whilst also not importing Layout itself.

I think if these libraries are purposely going to take different directions a feature comparison between motion and framer-motion should be added to motion's documentation; Or inversely is there any reason why framer-motion couldn't use motion as its animation engine?

Should timeline from-to reset values?

Hi Matt,

I noticed there's a different approach to timelines between Anime and Motion.

In the following example you can see how Anime automatically resets values when a timeline is created / played.
You can see how all the dots are placed on opacity: 0; because the animation does opacity: [0, 1].

Motion doesn't reset values automatically so if you don't do it manually it will look odd.
If I set opacity: 0; manually on all dots it will look good the first time I run the timeline, but after that it's still mixed.

Example: https://omz56.csb.app/

Could this be done automatically or with an additional option on the timeline? 👍

Canceling and resuming an animation

Hi,

Question 1:

Does motion-one support canceling and resuming an animation for a single element (using animate) or a group of elements (using timeline) like described here?

https://simplabs.com/blog/2021/01/29/web-animations-intro

Question 2:
I was curious and wanted to learn more about the timeline implementation respectively how it differentiates with animate.

It turns out you are using the animate-style function for both, which internally leverages an element's native animate implementation.
Is there a reason for not using the timeline implementation provided by the browser instead, maybe because it is still experimental?

animation = element.animate(

Thx!

SVG React <Motion.Path/> animate "d"

image

Feature comparison here seems to suggest that <motion.path/> animations should work; I.E animating "d" attribute on a SVG path with the same number of points.

I can't find this property in the typings, nor in the source code is there actually support for animating "d"? or is this documentation referencing animating paths in some other way.

Prepend selectors with a `.`

The use case for animations targeting tags is super niche and unlikely due to potential isolation problems.
Targeting classes (modules/css-in-js) though should be №1 use case by popularity. In this case it makes up lots of unnecessary template strings just to add a dot.

image

Would be super-cool if the lib modified the selector, something along these lines:

const realSelector =  ['#', '.'].includes(selector.charAt(0)) ? selector : `.${selector}`

UPD:
This will also work fine for isolated combined selectors with tagnames, like selector + " > li". Still, targeting all li shouldn't be a feature, I think 🤔

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.