Code Monkey home page Code Monkey logo

driver.js's Introduction


Driver.js

version downloads

Powerful, highly customizable vanilla JavaScript engine to drive the user's focus across the page
No external dependencies, light-weight, supports all major browsers and highly customizable


  • Simple: is simple to use and has no external dependency at all
  • Light-weight: is just 5kb gzipped as compared to other libraries which are +12kb gzipped
  • Highly customizable: has a powerful API and can be used however you want
  • Highlight anything: highlight any (literally any) element on page
  • Feature introductions: create powerful feature introductions for your web applications
  • Focus shifters: add focus shifters for users
  • User friendly: Everything is controllable by keyboard
  • TypeScript: Written in TypeScript
  • Consistent behavior: usable across all browsers
  • MIT Licensed: free for personal and commercial use

Documentation

For demos and documentation, visit driverjs.com

Please note that above documentation is for version 1.x which is the complete rewrite of driver.js.
For 0.x documentation, please visit this page


So, yet another tour library?

No, it's more than a tour library. Tours are just one of the many use-cases. Driver.js can be used wherever you need some sort of overlay for the page; some common usecases could be: highlighting a page component when user is interacting with some component to keep them focused, providing contextual help e.g. popover with dimmed background when user is filling a form, using it as a focus shifter to bring user's attention to some component on page, using it to simulate those "Turn off the Lights" widgets that you might have seen on video players online, usage as a simple modal, and of-course product tours etc.

Driver.js is written in Vanilla TypeScript, has zero dependencies and is highly customizable. It has several options allowing you to change how it behaves and also provides you the hooks to manipulate the elements as they are highlighted, about to be highlighted, or deselected.

Also, comparing the size of Driver.js with other libraries, it's the most light-weight, it is just ~5kb gzipped while others are 12kb+.


Contributions

Feel free to submit pull requests, create issues or spread the word.

License

MIT © Kamran Ahmed

driver.js's People

Contributors

aloupfor avatar anthonylebrun avatar aulisius avatar controversial avatar dependabot[bot] avatar diogomoretti avatar edbrdi avatar gondar00 avatar henrebotha avatar homyeeking avatar jamesbirtles avatar kamranahmedse avatar kangcor avatar kevincobain2000 avatar linuscenterstrom avatar martinst1 avatar moghya avatar mrzordex avatar neilberkman avatar nicolapps avatar ntnyq avatar prayagverma avatar protogenesis avatar rafh avatar raphael22 avatar schmulschubiak avatar sovetski avatar teodragovic avatar vickychenglau avatar vishwanathovi 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

driver.js's Issues

Overlay: highlighted-element and stage-element: z-index in container with fixed position

If I have a div with "position: fixed" as a container and in it an element (e.g. a p element) like
<div style="position: fixed; ..."><p id="selector"></p></div>
and I try to highlight the p inside the div, then the hightlight-stage will overlay the p (and of course the div).
The z-index of the selected element won't work right, because of the parent-div.
You can see a demo at my site (I also included at first normal examples for test reasons) and in the image below.

The only way I found to fix it with dev tools was putting "z-index: 99999999999" in the div, but thats not a really good solution, if there are multiple elements in the div...

grafik

Supporting an array of selectors

I've the need to create a tour that highlight a subset of DOM sibling elements.

Something like: driver.highlight('.foo, .bar');

I can't use a parent node because the scope is to not highlight other siblings.

The expected final result should be probably to highlight the minimal rect that contains all of the elements.

Do you think that this new feature would be difficult to be implemented?

Overlay disappears in Vuejs

Im using driver in a vuejs application.

The following will work properly if I use it inside mounted() but if I trigger it inside of a method the overlay briefly flashes and then disappears again.

const driver = new Driver();
// Define the steps for introduction
driver.defineSteps([
  {
    element: '#first-element-introduction',
    popover: {
      title: 'Title on Popover',
      description: 'Body of the popover',
      position: 'left'
    }
  },
  {
    element: '#second-element-introduction',
    popover: {
      title: 'Title on Popover',
      description: 'Body of the popover',
      position: 'top'
    }
  },
  {
    element: '#third-element-introduction',
    popover: {
      title: 'Title on Popover',
      description: 'Body of the popover',
      position: 'right'
    }
  },
]);
// Start the introduction
driver.start();

I feel like I am missing something really simple but am not sure what. Any suggestion?

Tour on kamranahmed.info/driver - popups are cut off

While stepping through the list of features, the popups are cut off at the bottom, so the user needs to manual scroll to move them into the visible window, in order to read or click buttons.

screen shot 2018-03-14 at 10 10 45 am

In latest Chrome:

Google Chrome is up to date
Version 65.0.3325.162 (Official Build) (64-bit)

VueJS <> Driver flickering on hover on all browsers

I'm experiencing a flickering effect in Chrome, and a complete blank in Safari when I hover over a selected region. Here's a video recording of Chrome and Safari: https://spaces.hightail.com/space/iDRl2s1est/

I'm using VueJS with a Bootswatch theme. I've tried replicating in plunkr (https://plnkr.co/edit/nVsu3YUKOy9EHBMXfMdg?p=preview) and everything works just fine with Bootswatch, so I'm guessing it might be an issue with VueJS, but I need to verify.

Popover outside of window bounds

Great project!
What I've not been able to find is if there's a way to prevent the popover from going outside the document/window bounds? I don't have a position manually set, and assumed the libarary would pick the best position for display, however my popovers constantly go out of window bounds.

screen shot 2018-04-25 at 11 36 51 pm

[BUG] Responsive popover

When go responsive and "position" is set in popover, it will should override that position with one "more responsive" like "bottom" or "top"

cattura1

Examples lower down demo page don't work

I'm sure this is just an issue for me on my office network & machine - but makes it hard for me to advocate to non-technical team that we could use this.

GET https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/monokai.min.css net::ERR_INSECURE_RESPONSE
driver:359 GET https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js net::ERR_INSECURE_RESPONSE
2buttons.js:1 [Violation] Avoid using document.write().
C @ buttons.js:1
L @ buttons.js:1
e @ buttons.js:1
l @ buttons.js:1
driver-demo.js:120 Uncaught ReferenceError: hljs is not defined
at driver-demo.js:120
at NodeList.forEach ()
at HTMLDocument. (driver-demo.js:119)
(anonymous) @ driver-demo.js:120
(anonymous) @ driver-demo.js:119
widgets.js:1 GET https://platform.twitter.com/js/button.556f0ea0e4da4e66cfdc182016dbd6db.js net::ERR_INSECURE_RESPONSE
Function.bind.test.window.__twttr.window.__twttr.widgets.window.__twttr.widgets.init.e.e @ widgets.js:1
r @ widgets.js:10
i.initialize @ widgets.js:8
(anonymous) @ widgets.js:8
(anonymous) @ widgets.js:8
o @ widgets.js:8
i @ widgets.js:8
r.flush @ widgets.js:8
(anonymous) @ widgets.js:8
p @ widgets.js:8
characterData (async)
(anonymous) @ widgets.js:8
K @ widgets.js:8
r.add @ widgets.js:8
o @ widgets.js:8
(anonymous) @ widgets.js:8
o @ widgets.js:8
s @ widgets.js:8
(anonymous) @ widgets.js:1
r @ widgets.js:11
o @ widgets.js:11
syndication.twitter.com/i/jot?l=%7B%22widget_origin%22%3A%22http%3A%2F%2Fkamranahmed.info%2Fdriver%23%22%2C%22widget_frame%22%3Anull%2C%22duration_ms%22%3A23.55999999999983%2C%22_category
%22%3A%22tfw_client_event%22%2C%22triggered_on%22%3A1521062763757%2C%22dnt%22%3Afalse%2C%22client_version%22%3A%22c419f42%3A1520970215484%22%2C%22format_version%22%3A1%2C%22event_namespace%22%3A%7B%22client%22%3A%22tfw%22%2C%22action%22%3A%22render%22%2C%22page%22%3A%22page%22%2C%22component%22%3A%22performance%22%7D%7D:1 GET https://syndication.twitter.com/i/jot?l=%7B%22widget_origin%22%3A%22http%3A%2F%2Fkamranahmed.info%2Fdriver%23%22%2C%22widget_frame%22%3Anull%2C%22duration_ms%22%3A23.55999999999983%2C%22_category_%22%3A%22tfw_client_event%22%2C%22triggered_on%22%3A1521062763757%2C%22dnt%22%3Afalse%2C%22client_version%22%3A%22c419f42%3A1520970215484%22%2C%22format_version%22%3A1%2C%22event_namespace%22%3A%7B%22client%22%3A%22tfw%22%2C%22action%22%3A%22render%22%2C%22page%22%3A%22page%22%2C%22component%22%3A%22performance%22%7D%7D net::ERR_INSECURE_RESPONSE
Image (async)
l @ widgets.js:9
s @ widgets.js:9
i @ widgets.js:9
(anonymous) @ widgets.js:8
r @ widgets.js:8
o @ widgets.js:8
r._flush @ widgets.js:9
(anonymous) @ widgets.js:8
setTimeout (async)
r._scheduleFlush @ widgets.js:9
r.add @ widgets.js:9
s @ widgets.js:8
t.exports @ widgets.js:11
(anonymous) @ widgets.js:11
(anonymous) @ widgets.js:8
(anonymous) @ widgets.js:8
setTimeout (async)
d @ widgets.js:8
(anonymous) @ widgets.js:8
trigger @ widgets.js:8
(anonymous) @ widgets.js:8
Promise resolved (async)
o @ widgets.js:8
s @ widgets.js:8
(anonymous) @ widgets.js:1
r @ widgets.js:11
o @ widgets.js:11
driver:368 GET https://www.google-analytics.com/analytics.js net::ERR_INSECURE_RESPONSE
(anonymous) @ driver:368
(anonymous) @ driver:369
VM129 buttons.js:1 GET https://api.github.com/repos/kamranahmedse/driver.js net::ERR_INSECURE_RESPONSE
V @ VM129 buttons.js:1
m @ VM129 buttons.js:1
M @ VM129 buttons.js:1
(anonymous) @ VM129 buttons.js:1
(anonymous) @ VM129 buttons.js:1
VM131 buttons.js:1 GET https://api.github.com/users/kamranahmedse net::ERR_INSECURE_RESPONSE
V @ VM131 buttons.js:1
m @ VM131 buttons.js:1
M @ VM131 buttons.js:1
(anonymous) @ VM131 buttons.js:1
(anonymous) @ VM131 buttons.js:1
buttons.js:1 GET https://api.github.com/repos/kamranahmedse/driver.js net::ERR_INSECURE_RESPONSE
V @ buttons.js:1
m @ buttons.js:1
M @ buttons.js:1
(anonymous) @ buttons.js:1
(anonymous) @ buttons.js:1
buttons.js:1 GET https://api.github.com/users/kamranahmedse net::ERR_INSECURE_RESPONSE
V @ buttons.js:1
m @ buttons.js:1
M @ buttons.js:1
(anonymous) @ buttons.js:1
(anonymous) @ buttons.js:1

Define buttons' text in the Driver Options instead

For each step, it it possible to change to buttons' text:

const stepDefinition = {
  element: '#some-item',        // Query selector for the item to be highlighted
  popover: {                    // There will be no popover if empty or not given
    title: 'Title',             // Title on the popover
    description: 'Description', // Body of the popover
    showButtons: false,         // Do not show control buttons in footer
    doneBtnText: 'Done',        // Text on the last button
    closeBtnText: 'Close',      // Text on the close button
    nextBtnText: 'Next',        // Next button text
    prevBtnText: 'Previous',    // Previous button text
  }
};

In my opinion, it would be more logical to define them in the Driver Options, no? Or at least in both places...

not working properly with 'addEventListner'

since 0.2.0 driver.js was not working to be properly with addEventLister method, just blinking.

I tested with Firefox and Google Chorme on Mac, either same results..

var driver = new Driver();
document.getElementById('btn1')
        .addEventListener('click', function(event) {
  
    driver.highlight({
      element: '#form1',
      popover: {
        title: 'Title for the Popover',
        description: 'Description for it',
      }
    });
});

abcdd

Popovers is not matching the focused element

I tried the demo page and was able to trigger popovers that don't match the focused element.

I started with "With Animation" then switched to "without Animation" when it was available with popover "Lets talk features".

popovers-out-of-focus

I think it is good to let you know this.

[z-index / 3d transform] Highlighted element with any parent that have transform-style: preserve-3d property are not showing

As stated in the title, having any element that lives in a parent having transform-style: preserve-3d makes them not showed when highlighted by driver.js.

Reproduced test case is very straightforward :

<div class="container">
    <span id="highlight_item">test</span>
</div>
.container {
    transform-style: preserve-3d;
}
const driver=new Driver({});
driver.highlight('#highlight_item');

And here's is a jsfiddle

I'm not really asking a fix for this issue as I've just removed the preserve-3d transform style of the parents when an item needs to be highlighted but I would love to understand what's going on. I guess there is something to do with the fact that z-index is not working as expected in the 3d space.

Anyway, thanks for this great plugin I've been loving using it so far.
Cheers,
Joris

API for React Wrapper

I've got this to work so far, only supporting the highlight api.
Also this is based on writing it externally, but I'm open to ideas on how it should be bundled. It could either be a separate package, or exported from this one. i.e any of:

import {DriverReactWrapper as Driver} from 'driver.js'
import Driver from 'driver.js/react'
import Driver from 'driver.js.react'

For the most simple use case, I was thinking:

<Driver options={options}> 
    <ChildYouWishToFocus/>
</Driver>

This works fine- I've set up a create-react-app to test it, with below replacing the App.js.

import React, { Component } from "react";
import ReactDOM from "react-dom";
import logo from "./logo.svg";
import "./App.css";
import Driver from "driver.js";
import "driver.js/dist/driver.min.css";

class DriverWrapper extends Component {
  componentDidMount() {
    const id = ReactDOM.findDOMNode(this.childElement).id;
    this.driver = new Driver(this.props.options);
    this.driver.highlight(`#${id}`);
  }
  render() {
    const { children } = this.props;
    const childrenWithProps = React.Children.map(children, child =>
      React.cloneElement(child, { ref: el => (this.childElement = el) })
    );
    return childrenWithProps;
  }
}
class Child extends Component {
  render() {
    return <div id="cheese">Potato</div>;
  }
}

class App extends Component {
  render() {
    return (
      <DriverWrapper options={{ allowClose: false }}>
        <Child />
      </DriverWrapper>
    );
  }
}

export default App;

It would also be good if the driver.js supported something like driver.highlight(element), so you could pass in a dom element as an argument instead of a selector. I don't think it would be very hard to achieve with how the code is currently.

Can't add more than one step on the same element

Hi there!

I have a use case that involves running two different steps on the same element. (Specifically, I'm trying to update the contents of this div with Angular between steps.)

Example:

driver.defineSteps([
	{
		element: '#thing1',
		popover: {
			title: 'Thing 1',
			position: 'top'
		}
	},
	{
		element: '#thing2',
		popover: {
			title: 'Thing 2, Part 1',
			position: 'top'
		}
	},
	{
		element: '#thing2',
		popover: {
			title: 'Thing 2, Part 2',
			position: 'top'
		}
	},
]);

When I do that, I see "Thing 1" and "Thing 2, Part 1". When I click Next, nothing seems to happen – i.e. the popover doesn't update, so the title doesn't change to "Thing2, Part 2" and the Close button isn't added. When I click Next again, the driver ends altogether as if I were looking at the last step.

I've also tried using a different element selector, like div#thing2 or .class#thing2, but it behaves the same way as if I reference them identically (like in my example).

Any light you can shed on this would be wonderful! 😄

[Feature request] New events

It will be very helpful if there was these events:

  • onBegins: called before the tour starts
  • onDone: called when user click on "done" buttons (tour is completed in all parts)
  • onClosed: called when user click "close" or outside (the tour is not completed)

These actions will encourage to use cookies to show the joyride.

PS: please add some extra css classes to "done" button (ex: "driver-next-btn driver-done-btn")

Feature Req: Provide more control on Next/Previous Handlers

In "Tour Mode", I would need to call a function on a step's next, and deffer highlighting the next element until some time (think async function/promise/observable/etc.)

Use Case:

  • Start Tour
  • Highlight an item
  • Click next
    • A function should be called that would open an animated panel.
    • The new Item should be highlighted only after the panel has been fully opened.

Right now, the "tour" is broken because it cannot find the yet-to-be-shown element

defineSteps can't restart

It seems something wrong when I restart the defineSteps. the mask just flashed and no pop appear.

Invalid typings file

The index.d.ts typings file exports classes that aren't structured in the same way as the exports are in index.js. This makes it very difficult to use driver.js in a TypeScript project.

The index.d.ts also has missing return types for many functions (should be void if doesn't return anything) and attempts to use defaults which aren't allowed in typings.

It seems as if the typings file is created manually. You should either generate the typings file from source or don't include the typings file at all.

Accessibility improvements

Hi! Thanks for a nice library.

I think it's accessibility could be significantly improved. Currently, when some element is highlighted, keyboard focus is not restricted within this element so you can press Tab and focus will move out of highlighted element bounds. As well, popover buttons are made of <a> so they are not clickable by pressing Space.

I see two potential improvements:

  1. Create a focus trap on highlighted elements (see https://hackernoon.com/its-a-focus-trap-699a04d66fb5)
  2. Use semantic markup for popover controls (e.g. <button> instead of <a href="javascript:void(0)">

.driver-fix-stacking need to reset will-change property as well

I couldn't see the highlighted item as well and after a lot of back and forth found the guilty property:
will-change: transform also changes stacking context

Fix

add will-change reset to the stacking context reset class.

.driver-fix-stacking{
  will-change: unset !important;  // better "initial" because IE;
}

notes on stacking context

This issue has been reported in different contexts #31 and recently #60. I understand that this fix is a necessary evil and it has been also reported in other similar libraries. In one of those i found this really clever "camera aperture trick" PR, i'll leave it here as a reference as you might want to take a look at it. I might give it a shot one of this days if you want to try this.

Thanks and keep up the good work!

No functionality in IE11

Hi there, although the README says that this works with IE, I'm experiencing issues with IE11, which unfortunately I need to support for a current project.

None of the pop-ups or overlaps display when opening in IE11, and your demo site also does not function either. Other than that, it's a really awesome project, and it works well in modern browsers!

I've found a limited workaround, which is documented here: https://stackoverflow.com/questions/35215360/getting-error-object-doesnt-support-property-or-method-assign#39021339

This has restored some functionality - the boxes appear where they should, and they cycle through in the correct order, but there is no framing of the highlighted element - just reduced opacity background across the whole page. It works, but doesn't look as nice.

Equally, there is no animation, although this is less of an issue.

Closing on mobile

I tested on mobile since I hoped it worked well and it does look great! The only problem I see is that I can't seem to close the driver when I tap outside. Bug?

What is the browser compatibility?

Forgive me if I overlooked it, but I can't seem to find a browser compatibility chart or list? You say "all major browsers", but for someone worried about supporting IE, I'd appreciate being more specific about what newer features are being used (if any). Thanks!

Wrong positioning

I have some positioning problem using the latest version of the library. This code

const driver = new Driver({ padding: 0 });
driver.defineSteps([{
  element: '#company-profile-display-name-input',
  popover: {
    title: 'Title on Popover',
    description: 'Body of the popover',
    position: 'top',
  },
}]);
driver.start();

with this html elements

<div class="row">
  <div class="large-12 columns">
    <label>
      {{"b2bsettingsapp.companysettings.information.form_settings_displayname" | translate }}
      <input
        id="company-profile-display-name-input"
        type="text"
        ng-model="businessUnitDetails.displayName"
        strip-html
      />
    </label>
  </div>
</div>

render like this

screen shot 2018-03-27 at 11 34 49

If I inspect the styles, there are correctly applied (not override by something else), but the top value seems wrong

Scroll issue on firefox

Hey Ahmed, great work !

While on chrome the scroll is as smooth as ever on firefox it doesn't scroll at all but jumps to the target container

To reproduce simply do the Quick Tour on the latest version of Firefox

EDIT 👍
After a bit of testing the native .scroll function on firefox does not animate but I did the trick by doing :

let t = 0:
let interval = null;

interval = setInterval(() => window.scroll(0, t = t + 10), 15);

Do you think that this can be a good fix to implement for firefox @kamranahmedse ?

popover background

is there an option for setting the background of popover?
i use it in my vue app,but i find out that i can only modify driver.css to change the background of popover.
thx.

How to use with React

Thanks for awesome work!
how can use driver.js inside react components?
can someone provide simple code to show use case with react?

Should be support element query selector

Thanks for awesome work!

I saw on the to-do list "Create wrappers for Angular, Vue and React".
Maybe we will have to wait very long.

If you now support the element query selector then driver.js easy integrate with any other library.

driver.highlight({
    element: document.querySelector("#container"),
    stageBackground: 'blue'
});

Win7/10 IE11: Not working as expected

I'm having some issues in IE11.

It's showing the first one, but I can't seem to go to the next step. The buttons aren't working.
I can close it by pressing on the overlay, but the overlay won't disappear.

Is anyone else having the same issues?

bs_win10_ie_11 0

Cheers,

it's not vanilla.

from looking at the source code, you can tell quite quickly that it is not vanilla as said in the README

Can't see highlighted item

Image attached. No error messages in the console.

code:

const driver = new Driver({
    opacity:1
  });
  driver.highlight({
    element: '.subNavOptionW[data-option="Topics"]',
    popover: {
      title: 'Title for the Popover',
      description: 'Description for it',
    }
  });

screen shot 2018-03-17 at 6 40 32 pm

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.