Giter Club home page Giter Club logo

vue2-dragula's Introduction

vue2-dragula

👌 Drag and drop so simple it hurts

Vue wrapper for dragula drag'n drop library, based on vue-dragula by @Astray-git.

This library has been refactored, upgraded and extended with powerful new features for use with Vue 2.

Call for help: copy error

This library has a long standing issue with the copy operation, resulting in items being inserted twice in the target container.

I have tried to debug it thorougly but with no success so far. I suspect it has to do with the Vue2 override of Array splice method, used in the ModelManager and the correct use of the Vue update queue.

You can try the ImmutableModelManager and see if that works better as it will return a new immutable array with a new pointer on each change.

$set for modifying objects in array for reactivity

Vue.set(this.model, changedModel)

Here a JSFiddle to play with

Please help fix this bug!

You can also try a simple array example in a Vue component, with buttons and handlers to simulate each of the effects (on underlying array model) for the drag effects:

  • copy
  • move

To better help track and fix the problem with the way Vue updates arrays.

insertAt(index, insertModel) {
  const splicedModel = this.model.splice(index, 0, insertModel)
  const modelAfterInsert = this.model
  return splicedModel
}

Currently the splicedModel returns an empty array [] and modelAfterInsert the same (unmodified) array as before the splice. Yet, copy (or Vue?) still ends up inserting the item twice in the UI

Copy: ensure clone

When making a copy error we need to ensure we are not reusing the same reference in the two container models. We need to clone the value first.

Otherwise, if "copy" is deleted or modified in one container, it will be deleted/modifed in both due to shared reference.

dropModelTarget(dropElm, target, source) {
  let notCopy = this.dragElm === dropElm
  let dropElmModel = notCopy ? this.dropElmModel() : this.jsonDropElmModel()
  if (notCopy) {
    this.notCopy()
  }
  // ...
}

jsonDropElmModel() {
  // ...
  let jsonStr = JSON.stringify(stringable || model)
  return JSON.parse(jsonStr)
}

So we should be handling this correctly!?

Call for care taker or more contributors

I haven't been using Vue2 much for the past year, so could use one or more contributors to be take care of this project and keep it up to date. Thanks!

Overview

  • Works with Vue 2
  • More flexible and powerful than original (Vue 1) plugin
  • Removed concept of bags. Reference named drakes directly
  • Vue2 demo app

See Changelog for details.

Demo

See vue2-dragula demo

Gotchas

Beware of Vue 2 reactivity issues when working with Arrays.

See post: reactivity in Vue2 vs Vue3

Use Vue.set or vm.$set to explicitly set/initialize an Array on a component and notify Vue about it.

Beware of pointers and instances which can lead to bugs where a service is triggered multiple times, leading to duplication of events.

Installation and configuration

npm

npm install vue2-dragula --save

yarn

yarn add vue2-dragula

Vue configuration

import Vue from 'vue'
import { Vue2Dragula } from 'vue2-dragula'

Vue.use(Vue2Dragula, {
  logging: {
    service: true // to only log methods in service (DragulaService)
  }
});

Dragula's CSS, which provides visual feedback for drag effects, is not included in this package and must be imported or provided in your app.

import 'dragula/dist/dragula.css'

Documentation

For additional documentation, see the docs folder

Dragula events and drag effects

See Dragula events and drag effects

Template Usage

<div class="wrapper">
  <div class="container" v-dragula="colOne" drake="first">
    <!-- with click -->
    <div v-for="text in colOne" :key="text" @click="onClick">{{text}} [click me]</div>
  </div>
  <div class="container" v-dragula="colTwo" drake="first">
    <div v-for="text in colTwo" :key="text">{{text}}</div>
  </div>
</div>

NOTE: Since Vue 2.x, having the :key attribute when using v-for is reqired.

API

You can access the global app service via Vue.$dragula.$service or from within a component via this.$dragula.$service (recommended for most scenarios).

You can also create named services for more fine grained control.

Service configuration

Set dragula options

Use service.options(name, options) to configure service options

// ...
new Vue({
  // ...
  created () {
    const service = Vue.$dragula.$service
    service.options('my-drake', {
      direction: 'vertical'
    })
  }
})

find drake by name

Use service.find(name) to return a named drake instance registered with the service.

Event handlers via event bus

See drake events

Use service.eventBus.$on to define drake event handlers

service.eventBus.$on('drop', (args) => {
  console.log('drop: ' + args[0])
})

Development

npm scripts included:

  • npm run build to build new distribution in /dist
  • npm run dev run example in dev mode
  • npm run lint lint code using ESlint

Vue 2 demo app

The API in depth

Access this.$dragula in your created () { ... } life cycle hook of any component which uses the v-dragula directive.

Add a named service via this.$dragula.createService({name, eventBus, drakes}) factory method. Initialise each service with the drakes you want to use.

$dragula

$dragula API:

  • createService({name, eventBus, drakes}) : to create a named service
  • createServices({names, ...}) : to create multiple services (names list)
  • on(handlerConfig = {}) : add event handlers to all services
  • on(name, handlerConfig = {}) : add event handlers to specific service
  • drakesFor(name, drakes = {}) : configure a service with drakes
  • service(name) : get named service
  • .services : get list of all registered services
  • .serviceNames : get list of names for all registered services

DragulaService

The DragulaService constructor takes the following deconstructed arguments. Only name and eventBus are required.

Note: You don't normally need to create the DragulaService yourself. Use the API to handle this for you.

class DragulaService {
  constructor ({name, eventBus, drakes, options}) {
    ...
  }
  // ...
}

Drakes are indexed by name in the drakes Object of the service. Each key is the name of a drake which points to a drake instance. The drake can have event handlers, models, containers etc. See dragula options

Binding models to draggable elements

Please note that vue-dragula expects the v-dragula binding expression to point to a model in the VM of the component, ie. v-dragula="items"

When you move the elements in the UI you also (by default) rearrange the underlying model list items (using findModelForContainer in the service). This is VERY powerful!

Note that special Vue events removeModel, dropModel and insertAt are emitted as model items are moved around.

this.name, el, source, this.dragIndex
  'my-first:removeModel': ({name, el, source, dragIndex, sourceModel}) => {
    // ...
  },
  'my-first:dropModel': ({name, el, source, target, dropIndex, sourceModel}) => {
    // ...
  },
  'my-first:insertAt': ({indexes, models, elements}) => {
    // ...
  },
  • el main DOM element of element (f.ex element being dropped on)
  • source is the element being dragged
  • target is the element being dragged to
  • dragIndex and dropIndex are indexes in the VM models (lists)

If you need more advanced control over models (such as filtering, conditions etc.) you can use watchers on these models and then create derived models in response, perhaps dispatching local model state to a Vuex store. We recommend keeping the "raw" dragula models intact and in sync with the UI models/elements.

Event delegation

Each drake is setup to delegate dragula events to the Vue event system (ie. $emit) and sends events of the same name. This lets you define custom drag'n drop event handling as regular Vue event handlers.

A named service my-first emits events such as drop and my-first:drop so you can choose to setup listeneres to for service specific events!

There are also two special events for when the underlying models are operated on: removeModel and dropModel. These also have service specific variants.

Advanced concepts

Logging

See Logging

Model mechanics

See Model mechanics

Customization

See Customization

Drake mechanics and configuration

See Drake mechanics and configuration

Adding Drag Effects

See Drag Effects

Time travel

See Time travel

VueX integration

See VueX integration example

Please help add more examples.

Wiki with Bonus Recipes

Please see the Wiki

Recipes

Auto-sorted lists

Add an Rx Observable or a watch to your model (list) which triggers a sort of a derived (ie. immutable) model whenever it is updated. You should then display the derived model in your view. Otherwise each sort operation would trigger a new sort.

License

MIT Kristian Mandrup 2016

vue2-dragula's People

Contributors

amcsi avatar astray-git avatar distortriver avatar fominamasha avatar itnok avatar kristianmandrup avatar paddingme avatar peteygao avatar qwales1 avatar razh avatar tarikhamilton avatar tmcdos avatar wwwjfy 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

vue2-dragula's Issues

How to get the position of the dragged divs?

How do I actually get the position of dragged div? I need to save it in the local storage and retrieve it on the next reload such that I can position the elements as they were before.

Nesting

Does this library support nesting? In particular lists?

Move items when pressing custom element

Let's assume the following snippet:

<div v-dragula="col" drake="second">
	<div v-for="item in 2">
		<span>move</span>
		<div>test</div>
	</div>
</div>

How can I move the whole elements only when I press the <span> elems?


Also, are nested drags supported?

how to remove or destroy an service

Hi guys,

I have the problem, that when a component is created multiple times (for example caused by a v-if clause that changes" the previous services / event listeners are not removed, so a drop event is triggered multiple times.

In the original dragula js framework I could use drake.destroy to remove the old listeners. But in this framework I didnt found a way?

ReferenceError: msg is not defined

Tiny bug which is fixed in the source (service.js:116). Just need to update the dist file so we get the update when we install the npm package.

Cheers!

Npm and _typeof fix issue

We need this fix for _typeof with npm. Npm is not working, it takes previous version, before this fix. Could you please look into that? Thank you

How to use it with vuex store states

Hi,
Thank you for this library.

I want to ask you how can I use this library with vuex store state? As you know we should commit changes to store and do not manipulate them directly.

I used below code but it did not worked and set method of computed property did not called.

created() {
    const $service = this.$dragula.$service
    $service.options( 'discountList', { direction: 'vertical' } )
    $service.eventBus.$on( 'dragend', (args) => {
      this.discountList.forEach( ( value, index ) => {
        value.ordering = index + 1;
      });
      this.saveDiscountsOrder();
    } )
  },
// Component computed properties.
computed : {
    discountList : {
      get : function() {
        return this.$store.state.discountList;
      },
      set : function( value ) {
        console.log( 'setting value' );
        this.$store.commit( 'SET_CART_DISCOUNT_LIST', value );
      }
    }
  }

// Template
<tbody class="container" v-dragula="discountList" drake="discountList">
        <tr v-for="discount in discountList" v-bind:key="discount">
          <td></td>
          <td>{{ discount.id }}</td>
          <td>{{ discount.name }}</td>
          <td>{{ discount.start_date }}</td>
          <td>{{ discount.end_date }}</td>
          <td>
            <button class="button button-primary" @click="deleteCartDiscount( discount.id )">{{ strings.delete }}</button>
            <router-link :to="'/discount/' + discount.id" class="button button-primary">{{ strings.edit }}</router-link>
          </td>
        </tr>
      </tbody>

I think that above code does not calls set method of computed property because of not setting a value like discountList = newValue from dragula to discountList.

Please let me know how can I solve this problem.

Copying issues

I'm curious if anyone has encountered this issue. I can't seem to find the root cause yet. I have a component with 2 droppable containers. Like so:

<div class="myComponent">
  <div class="container" v-dragula drake="first" service="first-service">
      <div class="drag-test">Test item</div>
      <div class="drag-test">Test item</div>
  </div>
  <div class="container" v-dragula drake="first" service="first-service">
      <div class="drag-test">Test item</div>
      <div class="drag-test">Test item</div>
  </div>
</div>

And I have a service setup like this inside of my created() function:

const myService = Vue.$dragula.createService({
   name: 'first-service',
     drakes: {
        first: {
          copy: true,
       },
   },
});

The copying is working however when trying to drop into one of the containers, I keep getting the following error:

Uncaught SyntaxError: Unexpected token u in JSON at position 0

Any ideas? Thanks!

Dragging & Dropping a single element often times moves two elements.

Here's a minimum reproduction case:
http://jsbin.com/zejaqatiqa/1/edit?html,output

If you drag any element that is NOT the last one in the list (e.g. if you drag "Stuff 6" from the bottom container to the top container, or any "Stuff 1" through "Stuff 4"), you'll notice that two items are taken along with the single item that was dragged 😱!

This is with the default setting straight out of the box on Vue 2.3.3. No fancy configurations or anything...

I'm too new to Vue to be able to debug, but I attached the Vue app to window.dragApp to help debug.

If you introspect dragApp.data after dragging & dropping for a while, you'll notice that what's in the container and what's actually in the data doesn't match up! So is this a case of a Vue rendering bug, or something else...?

Complicated relationship

After I read the docs in README.md and demo project, it seem to be a powerful dnd lib. But I still not entirely clear the relationship between drake, service and model.

Another problem is whether the this.$dragula.$service is not the instanceof DragulaService? and why on is not a method of this.$dragula.$service?

I need you help that to provide a further description. Thank you.

2.5.2 have error

"Uncaught TypeError: this.calcOpts is not a function".

In version 2.5.2, the example, vue2-dragula-demo, does not work

TypeScript usage

First of all, thanks for this awesome wrapper, works great with JS, but I have an issue with TypeScript.

I installed vue2-dragula with:

npm install vue2-dragula --save

Configured like:

import { Vue2Dragula } from 'vue2-dragula'

Vue.use(Vue2Dragula, {
  logging: {
    service: true
});

But I cannot access to this.$dragula inside a component or Vue.$dragula, throws unresolved variable.

Am I missing something? Thank you for your time.

Dragging video stops playback

I have few <video> components with WebRTC streams in flexbox, and I want them to be dnd-ed freely
It works, but video stop playing when I do it

      <div style="display: flex; flex-wrap: wrap" v-dragula="activeConnections" drake="first">
        <video 
          v-for="conn in activeConnections"
          :id="conn.id"
          muted autoplay
        ></video>
      </div>

Model undefined

I have this simple code

<ol class="dd-list" v-dragula="items" drake="items">
        <li class="dd-item dd3-item" v-for="field in items" :key="field.id">
          <div class="dd-handle dd3-handle handle"></div>
          <div class="dd3-content"> {{ field.name }} </div>
        </li>
      </ol>

Almost everything is working, but models are always undefined - https://cl.ly/013K1m0A122x

My model from the start is empty array, but if i add manually data, it seems to work.
I can not see in docs, maybe i need to restart dragula after response from my server?

How to get underlaying vue model data on drop event handler?

I have pulled vue2-dragula into a demo application for Vuetiful Datatable and I've gotten the basic drag and drop functionality working well (it's really quite smooth!). Here's an example screenshot so you can see what I am working with, each of the generated <td> elements in there is draggable.

I have the datatable loading rows after a lodash debounced ajax call. Each of my records coming back contain many properties , one of these is display_order. The data I am dealing with involves invoices so we're showing line items in the datatable and this display_order property controls in what order they show.

I have setup a drop event which is wired up, it looks like this:

/* on my .vue file */
created () {
    let service = this.$dragula.$service

    service.eventBus.$on('drop', this.handleDrop)
},
methods : {
    handleDrop(e) {
        /* e contains the data in the screenshot below 
            I'd like to access the display_order of my row here so I can sync it with the new order of the row
            I've noticed that e.el.rowIndex shows the new index of the row but I don't have access to the model
            that I can see
        */
    }
}

"e" data example

I've tried removing the handleDrop() method and going with the handler as a function within the service.eventBus.$on('drop', (e) => {}) but noticed the data looking the same way. I have seen that e.drake.models[0].model seems to contain the datacell records but trying to fetch the model by its rowIndex has not worked (it's always off and shows me the wrong record).

This could be handled on the server side when I submit my rows and just update the display order based on what order they appear in the rows but I assume later on I may want to access some other properties on the models for a different purpose.

TL;DR - Does there exist a way of defining a handler for the drop event of a table row (a datacell) so that you can access the underlaying properties of your model?

Can't get options to work

I suppose I'm overlooking something obvious, but I simply can't get options to work.

I have a vue2 project containing a simple drag and drop which allows reordering items in a list:

<div id="drag-container" v-dragula="items" bag="items">
    <div v-for="(item, index) in item" :key="item.id" class="row">
        <div class="col drag-handle">
          <div class="drag-icon"></div>
        </div>

        ... item contents here ...
    </div>
</div>

Drag and drop works fine, but I want to allow dragging only via specific handle. I can use the moves option of dragula to control that, but I can't get it working in vue2-dragula. I tried things like:

Vue.$dragula.$service.options('items', {
  moves: function (el, source, handle, sibling) {
    console.log('moves', el);
    return el.classList.contains('drag-handle')
  }
})

and

  beforeMount: function () {
    this.$dragula.$service.options('items', {
      moves: function (el, source, handle, sibling) {
        console.log('moves', el);
        return el.classList.contains('drag-handle')
      }
    })
  },

But the moves function is never invoked. Any idea what I'm overlooking?

All items in one array, bucket/column set with property

As Vuejs is data based, it would be more apropriate to store all items in one array, and specify the bucket (column) they are in with a property. It's the same direction as vuelidate took over vee-validate. Each bucket in the template would have a v-if based on that field. Of course this has to be two-way synced, so if I set the .bucket property of an item, it would be moved to the apropriate bucket/column in the interface, and if I drag them around, the .bucket property shall be updated. Is this possible or planned? The current setup makes this compplicated.

Same as Astray-git#34

i want aply this code, help please

valor-software/ng2-dragula#282

constructor(private dragulaService: DragulaService) {
//setup dragNdrop for creating content
dragulaService.setOptions('page-bag', {
copy: function (el, source) {
// To copy only elements in left container, the right container can still be sorted
return source.id === 'left';
},
copySortSource: false,
accepts: function(el, target, source, sibling) {
// To avoid draggin from right to left container
return target.id !== 'left';
}
})
}

More example

Is it possible to get an example showcasing multiple list, like a kanban board ?
What would be great is to see how to wire it with events so that we can later send the data to the backend ? Not using local storage though.
I have a working example using Vue.Draggable that is great but lacks customisation when it comes to ui.
Here's the rep if you wanna take a look. it uses Laravel as backend.

Trello-clone

Nested draggable components

Hey there, awesome lib, thank you!

I'm having some trouble implementing a hierarchy of draggable components...

Data scheme of component:

[
  {
    id: 1
    title: 'nomination #1',
    travelers: [
      {id:1, name: 'foo'}
    ],
    rooms: [
      {
        id: 1,
        title: 'room #1',
        travelers: [
          {id:2, name:'bar'},
          {id:3, name:'baz'},
        ],
      },
      {
        id: 3,
        title: 'room #3',
        travelers: [],
      }
    ]
  },
  {
    id: 2
    title: 'nomination #2',
    travelers: [
      {id:4, name: 'bax'}
    ],
    rooms: [
      {
        id: 2,
        title: 'room #2',
        travelers: [
          {id:5, name:'bop'},
          {id:6, name:'blop'},
        ],
      }
    ]
  }
]

Component layout:

<Tour>
  - <Nomination>
    - <Traveler>
    - <Traveler>
    - <Room>
      - <Traveler>
      - <Traveler>
  - <Nomination>
    - <Room>

The logic behind is that each tour can have nominated accommodations, each nomination can have travelers without an assigned room and a list of rooms, each of which can have assigned travelers.

The nomination component receives its data via v-model, and is watched and mirrored to an inner variable when changed. This variable is used when constructing v-dragula. If the inner value changes, it emits it with the input name.

This is working so far, drag&drop events manipulate the root data structure, yay! (it is awesome, took me like 30 mins to implement)

Now to my real problem, events get fired twice:

tour.vue:354 travelers:drop {el: div.tour-traveler.gu-transit, container: div.tour-nomination-travelers.sort-target.pull-left, source: div.tour-room-travelers, name: "travelers", service: {…}, …}
tour.vue:354 travelers:drop {el: div.tour-traveler.gu-transit, container: div.tour-nomination-travelers.sort-target.pull-left, source: div.tour-room-travelers, name: "travelers", service: {…}, …}
tour.vue:344 travelers:insertAt {elements: {…}, targetModel: ModelManager, transitModel: {…}, dragIndex: 0, dropIndex: 2, …}dragIndex: 0dropIndex: 2elements: {drop: div.tour-traveler, target: div.tour-nomination-travelers.sort-target.pull-left, source: div.tour-room-travelers}indexes: {source: 0, target: 2}models: {source: ModelManager, target: ModelManager, transit: {…}}name: "travelers"sourceModel: ModelManager {opts: {…}, name: "travelers", drake: {…}, modelRef: Array(0), model: Array(0), …}targetModel: ModelManager {opts: {…}, name: "travelers", drake: {…}, modelRef: Array(2), model: Array(2), …}transitModel: {…}__proto__: Object
tour.vue:344 travelers:insertAt {elements: {…}, targetModel: ModelManager, transitModel: {…}, dragIndex: 0, dropIndex: 2, …}dragIndex: 0dropIndex: 2elements: {drop: div.tour-traveler, target: div.tour-nomination-travelers.sort-target.pull-left, source: div.tour-room-travelers}indexes: {source: 0, target: 2}models: {source: ModelManager, target: ModelManager, transit: {…}}name: "travelers"sourceModel: ModelManager {opts: {…}, name: "travelers", drake: {…}, modelRef: Array(0), model: Array(0), …}targetModel: ModelManager {opts: {…}, name: "travelers", drake: {…}, modelRef: Array(2), model: Array(2), …}transitModel: {…}__proto__: Object
tour.vue:347 travelers:dropModel {target: div.tour-nomination-travelers.sort-target.pull-left, source: div.tour-room-travelers, el: div.tour-traveler.gu-transit, dragIndex: 0, dropIndex: 2, …}
tour.vue:347 travelers:dropModel {target: div.tour-nomination-travelers.sort-target.pull-left, source: div.tour-room-travelers, el: div.tour-traveler.gu-transit, dragIndex: 0, dropIndex: 2, …}

Any help/advice is appreciated!

"accepts" not firing as event

In your dragEffects example, within the service.on block you are registering the 'accepts' event, however vue2-dragula doesn't fire 'accepts' as an event. I turned on full logging and 'accepts' never pops up. In dragula 'accepts' is a configuration option.

I was able to make it work thoughby adding it to the createService call;

...
let service = dragula.createService({
    name: 'effects',
    drake: {
        copy: true,
        accepts: (el, target, source, sibling) => {
            log('accepts: ', el, target)
            return true // target !== document.getElementById(left)
        },
    }
});
...

The library should either start firing 'accepts' as an event, or update the documentation.

Problem receiving data from server

I'm trying to use vue2-dragula, but I'm not having the behavior I expect. I do not know what I'm doing wrong.

I'm using the library like this:

data: () => ({
        images: []
});

I get the data from the server and do this:

axios.get('myURL').then(response => {
                this.images = response.data;
})

And in my template I do this:

<div v-dragula="images" drake="images">
        <div class="ss-img-mini" v-for="(img, i) in images" :key="img.id">
                <img :src="img">
        </div>
</div>

My problem is that when I move an image, the new position is not reflected in the array images. I've identified that if I set the array images statically, everything works fine. But if the data arrives from the server, it does not work well.

Does anyone know what can it be?

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.