GithubHelp home page GithubHelp logo

kvyatkovskys / kvkcalendar Goto Github PK

View Code? Open in Web Editor NEW
506.0 10.0 109.0 236.67 MB

A most fully customization calendar for Apple platforms 📅

License: MIT License

Swift 99.66% Ruby 0.29% Shell 0.04%
swift ios cocoapods ios-calendar ios-swift calendar kvkcalendar carthage calendar-view timeline swift-package-manager customization-calendar timeline-library datepicker

kvkcalendar's Introduction

 

CI Status Version Carthage SwiftPM compatible Platform License

KVKCalendar

KVKCalendar is a most fully customization calendar. Library consists of five modules for displaying various types of calendar (day, week, month, year, list of events). You can choose any module or use all. It is designed based on a standard iOS calendar, but with additional features. Timeline displays the schedule for the day and the week.

Additional features:

Need Help?

If you have a question about how to use KVKCalendar in your application, ask it on StackOverflow using the KVKCalendar tag.

Please, use Issues only for reporting bugs or requesting a new features in the library.

Requirements

  • iOS 13.0+, iPadOS 13.0+, MacOS 11.0+ (supports Mac Catalyst)
  • Swift 5.0+

Installation

KVKCalendar is available through CocoaPods or Carthage or Swift Package Manager.

CocoaPods

pod 'KVKCalendar'

Adding Pods to an Xcode project

Carthage

github "kvyatkovskys/KVKCalendar"

Adding Frameworks to an Xcode project

Swift Package Manager (Xcode 12 or higher)

  1. In Xcode navigate to FileSwift PackagesAdd Package Dependency...
  2. Select a project
  3. Paste the repository URL (https://github.com/kvyatkovskys/KVKCalendar.git) and click Next.
  4. For Rules, select Version (Up to Next Major) and click Next.
  5. Click Finish.

Adding Package Dependencies to Your App

Usage for UIKit

Import KVKCalendar. Create a subclass view CalendarView and implement CalendarDataSource protocol. Create an array of class [Event] and return the array.

import KVKCalendar

class ViewController: UIViewController {
    var events = [Event]()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let calendar = CalendarView(frame: frame)
        calendar.dataSource = self
        view.addSubview(calendar)
        
        createEvents { (events) in
            self.events = events
            self.calendarView.reloadData()
        }
    }
    
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        // to track changing frame when an user rotates device
        calendarView.reloadFrame(view.frame)
    }
}

extension ViewController {
    func createEvents(completion: ([Event]) -> Void) {
        let models = // Get events from storage / API
        
        let events = models.compactMap({ (item) in
            var event = Event(ID: item.id)
            event.start = item.startDate // start date event
            event.end = item.endDate // end date event
            event.color = item.color
            event.isAllDay = item.allDay
            event.isContainsFile = !item.files.isEmpty
            event.recurringType = // recurring event type - .everyDay, .everyWeek
        
            // Add text event (title, info, location, time)
            if item.allDay {
                event.text = "\(item.title)"
            } else {
                event.text = "\(startTime) - \(endTime)\n\(item.title)"
            }
            return event
        })
        completion(events)
    }
}

extension ViewController: CalendarDataSource {
    func eventsForCalendar(systemEvents: [EKEvent]) -> [Event] {
        // if you want to get events from iOS calendars
        // set calendar names to style.systemCalendars = ["Test"]
        let mappedEvents = systemEvents.compactMap { Event(event: $0) }
        return events + mappedEvents
    }
}

Implement CalendarDelegate to handle user action and control calendar behaviour.

calendar.delegate = self

To use a custom view for specific event or date you need to create a new view of class EventViewGeneral and return the view in function.

class CustomViewEvent: EventViewGeneral {
    override init(style: Style, event: Event, frame: CGRect) {
        super.init(style: style, event: event, frame: frame)
    }
}

// an optional function from CalendarDataSource
func willDisplayEventView(_ event: Event, frame: CGRect, date: Date?) -> EventViewGeneral? {
    guard event.ID == id else { return nil }
    
    return customEventView
}

To use a custom date cell, just subscribe on this optional method from CalendarDataSource (works for Day/Week/Month/Year views).

func dequeueCell<T>(parameter: CellParameter, type: CalendarType, view: T, indexPath: IndexPath) -> KVKCalendarCellProtocol? where T: UIScrollView { 
    switch type {
    case .year:
        let cell = (view as? UICollectionView)?.dequeueCell(indexPath: indexPath) { (cell: CustomYearCell) in
            // configure the cell
        }
        return cell
    case .day, .week, .month:    
        let cell = (view as? UICollectionView)?.dequeueCell(indexPath: indexPath) { (cell: CustomDayCell) in
            // configure the cell
        }
        return cell
    case .list:    
        let cell = (view as? UITableView)?.dequeueCell { (cell: CustomListCell) in
            // configure the cell
        }
        return cell
    }
}

Usage for SwiftUI

Add a new SwiftUI file and import KVKCalendar. Create a struct CalendarViewDisplayable and declare the protocol UIViewRepresentable for connection UIKit with SwiftUI.

import SwiftUI
import KVKCalendar

struct CalendarViewDisplayable: UIViewRepresentable {

    @Binding var events: [Event]
    var selectDate = Date()
    var style = Style()
    
    private var calendar = KVKalendarView(frame: .zero)
        
    func makeUIView(context: UIViewRepresentableContext<CalendarViewDisplayable>) -> KVKCalendarView {
        calendar.dataSource = context.coordinator
        calendar.delegate = context.coordinator
        return calendar
    }
    
    func updateUIView(_ uiView: KVKCalendarView, context: UIViewRepresentableContext<CalendarViewDisplayable>) {
        context.coordinator.events = events
    }
    
    func makeCoordinator() -> CalendarDisplayView.Coordinator {
        Coordinator(self)
    }
    
    public init(events: Binding<[Event]>) {
        _events = events
        calendar = KVKCalendarView(frame: frame, date: selectDate, style: style)
    }
    
    // MARK: Calendar DataSource and Delegate
    class Coordinator: NSObject, CalendarDataSource, CalendarDelegate {
        private let view: CalendarViewDisplayable
        
        var events: [Event] = [] {
            didSet {
                view.events = events
                view.calendar.reloadData()
            }
        }
        
        init(_ view: CalendarViewDisplayable) {
            self.view = view
            super.init()
        }
        
        func eventsForCalendar(systemEvents: [EKEvent]) -> [Event] {
            events
        }
    }
}

Create a new SwiftUI file and add CalendarViewDisplayable to body.

import SwiftUI

struct CalendarContentView: View {
    @State var events: [Event] = []

    var body: some View {
        NavigationStack {
            CalendarViewDisplayable(events: $events)
        }
    }
}

Styles

To customize calendar create an object Style and add to init class CalendarView.

public struct Style {
    public var event = EventStyle()
    public var timeline = TimelineStyle()
    public var week = WeekStyle()
    public var allDay = AllDayStyle()
    public var headerScroll = HeaderScrollStyle()
    public var month = MonthStyle()
    public var year = YearStyle()
    public var list = ListViewStyle()
    public var locale = Locale.current
    public var calendar = Calendar.current
    public var timezone = TimeZone.current
    public var defaultType: CalendarType?
    public var timeHourSystem: TimeHourSystem = .twentyFourHour
    public var startWeekDay: StartDayType = .monday
    public var followInSystemTheme: Bool = false 
    public var systemCalendars: Set<String> = []
}

Author

Sergei Kviatkovskii

License

KVKCalendar is available under the MIT license

kvkcalendar's People

Contributors

azpery avatar chiliec avatar codemnky01 avatar fgeistert avatar kerekson avatar kvyatkovskys avatar mikeyyyzhao avatar mkowalski87 avatar phuanggh avatar pwnsmithers avatar ru5c55an avatar wassup- 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

kvkcalendar's Issues

Does not conform to protocol

extension JobDetailsViewController: CalendarDataSource {
func eventsForCalendar() -> [Event] {
return events
}
}

This is the code for the data source, but I get the 'Does not conform to protocol' error. Any thoughts?

Add event

Can we add event by clicking on particular date range for Day/Week view like we do in Apple default calendar?

Event is not displayed well when date format is changed in iPhone

Hi,

I've been testing the example using this pod. It is really amazing and I didn't find this error until I began to test it in a actual cellphone.
I only tested on iPhone 7, 8 and 11. What happens is that when I have my cellphone configured with 24-hour format, all events are display as follows:
IMG_1435

However, as soon as I change to 12-hour format, an error occurs and the events are displayed as follows:
IMG_1436

I really would appreciate if you could guide me trough this problem.

Month horizontal scroll

I needed the month scroll to be horizontal so I set the scrollDirection to .horizontal in MonthStyle and it works. The problem is that the days now are displayed in vertical.

Schermata 2020-06-19 alle 16 34 36

Also, opening the view the current date is hidden and it appears only after the first scroll.

Example Selected Date

Having an issue in my app so I went to the example. What am I missing in that when you select a date in the Example and then switch from day to month to week, that the selected date keeps changing. Also, TODAY is off by one and I have to add

var today = Date()
today = today.addingTimeInterval(86400)

Using KVKCalendar with SwiftUI

Hi,

I am not that experienced with using libraries yet and I tried implementing KVKCalendar into my app, however I am getting several errors. I followed the steps described in the updated readme for SwiftUI, but when creating the CalendarDisplayView struct I get quite a few errors. I guess I am doing something wrong, but can't seem to figure out what it is I am doing wrong.

Here is a screenshot of errors inside the SwiftUI file I created:

Screen Shot 2019-12-30 at 3 12 47 PM

Thank you very much in advance. :)

Events in UTC - Local

I'm assuming that Events are in UTC right? If I want to create an event for today, Central Time for 8.am ( or 8am for whatever timezone the phone is currently in), what do I use for an event start date object?

Wrong date showing for selected date

Hello,

I have an issue where the selected date shows up as a different date.

This is what I mean:

IMG_0019

So the screenshot shows Monday as the current day, however the actual day is Tuesday. Underneath the days of the week it does show 'Tuesday, 28 January 2020'.
I tried fixing the issue, but haven't been able to find out what causes the issue.

I made a simple project to see if it occurs as well, and it did.
Here is the code:

import SwiftUI
import KVKCalendar

struct CalendarItem {
    var id: Int

    let isAllDay: Bool
    let color: String
    let start: String
    let end: String
    let title: String
}


struct CalendarDisplayView: UIViewRepresentable {
    var selectDate: Date?
    
    public init(selectDate: Date?) {
        self.selectDate = selectDate
    }
    
    private var calendar: CalendarView = {
        var style = Style()
        return CalendarView(frame: UIScreen.main.bounds, style: style)
    }()
        
    func makeUIView(context: UIViewRepresentableContext<CalendarDisplayView>) -> CalendarView {
        calendar.dataSource = context.coordinator
        calendar.delegate = context.coordinator
        calendar.reloadData()
        return calendar
    }
    
    func updateUIView(_ uiView: CalendarView, context: UIViewRepresentableContext<CalendarDisplayView>) {
        
    }
    
    func makeCoordinator() -> CalendarDisplayView.Coordinator {
        Coordinator(self, selectDate: selectDate)
    }
    
    // MARK: Calendar DataSource and Delegate
    class Coordinator: NSObject, CalendarDataSource, CalendarDelegate {
        private var view: CalendarDisplayView
        
        var selectDate: Date?
        private var events = [Event]()
        
        
        init(_ view: CalendarDisplayView, selectDate: Date?) {
            self.view = view
            self.selectDate = selectDate
            super.init()
            
            loadEvents { (events) in
                self.events = events
                self.view.calendar.reloadData()
            }
        }
        
        
        func eventsForCalendar() -> [Event] {
            return events
        }
        
        func didSelectDate(_ date: Date?, type: CalendarType, frame: CGRect?) {
            selectDate = date ?? Date()
            loadEvents { (events) in
                self.events = events
                self.view.calendar.reloadData()
            }
        }
        
        func loadEvents(completion: ([Event]) -> Void) {
            var events = [Event]()
            
            let result = [CalendarItem(id: 0, isAllDay: false, color: "#93c47d", start: "2020-01-25T12:30:00", end: "2020-01-25T16:00:00", title: "Event number 1"), CalendarItem(id: 1, isAllDay: false, color: "#93c47d", start: "2020-01-25T12:30:00+01:00", end: "2020-01-25T16:00:00+01:00", title: "Event number 2"), CalendarItem(id: 2, isAllDay: false, color: "#93c47d", start: "2020-01-18T12:30:00", end: "2020-01-18T16:00:00", title: "Event number 3"), CalendarItem(id: 3, isAllDay: false, color: "#93c47d", start: "2020-01-15T12:30:00", end: "2020-01-15T16:00:00", title: "Event number 3")]
            
            for (index, item) in result.enumerated() {
                print(item.start)
                let startDate = formatter(date: item.start)
                let endDate = formatter(date: item.end)
                let startTime = timeFormatter(date: startDate)
                let endTime = timeFormatter(date: endDate)
                
                var event = Event()
                event.id = index
                event.start = startDate
                event.end = endDate
                event.color = EventColor(UIColor.hexStringToColor(hex: item.color))
                event.isAllDay = item.isAllDay
                event.isContainsFile = false // !item.files.isEmpty
                event.textForMonth = item.title
                
                if event.isAllDay {
                    event.text = "\(item.title)"
                } else {
                    event.text = "\(startTime) - \(endTime)\n\(item.title)"
                }
                events.append(event)
            }
            completion(events)
        }
        
        func formatter(date: String) -> Date {
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
            return formatter.date(from: date) ?? Date()
        }
        
        func timeFormatter(date: Date) -> String {
            let formatter = DateFormatter()
            formatter.dateFormat = "HH:mm"
            return formatter.string(from: date)
        }
    }
}

extension UIColor {
    static func hexStringToColor(hex: String) -> UIColor {
        var cString = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
        
        if cString.hasPrefix("#") {
            cString.remove(at: cString.startIndex)
        }
        
        if cString.count != 6 {
            return UIColor.gray
        }
        var rgbValue: UInt32 = 0
        Scanner(string: cString).scanHexInt32(&rgbValue)
        
        return UIColor(red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
                       green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
                       blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
                       alpha: CGFloat(1.0)
        )
    }
}

struct CalendarDisplayView_Previews: PreviewProvider {
    static var previews: some View {
        CalendarDisplayView(selectDate: Date())
    }
}

Any idea on what's going wrong here?
Thank you in advance! :)

Problem with displaying events in the "Month" section

There is a problem when you want to display several events with different months.
For example, if I create one event on December 20 and another on November 20, in the "Month" section when I scroll around November, it is the first event created that will be displayed, namely the event of December 20.
The date is well referenced but not the rest....
Capture d’écran 2019-12-02 à 15 46 43
Capture d’écran 2019-12-02 à 15 46 38

Add Delegate Method for when user scrolls to a new date range.

When we are using the calendar we don't load all events at once. But only for specific time interval, since we have a monthly view or weekly view.

Would be great if there was a delegate method that let us know when user has scrolled the calendar to a new month so that we could dynamically load events for the new month. Since we do let users scroll infinitely.

Also an option to show titles for events instead of dots on monthly view.

Missing part of day

how can i show all the morning area in case first event fetched in the day start in the afternoon?
Schermata 2019-12-30 alle 19 16 36
thanks

Contraints?

How are constraints dealt with in the calendar? I've been using an iPhone and its working just great. However one of my users tried it on an iPad, and when they switch to month view the view is all messed up. How do we use this the calendar does not take up the entire screen?

Recurring event

Is there a feature for recurring events? I.E. One event that occurs every Monday.

Attempted to read an unowned reference but the object was already deallocated

I use your component in a NavigationView page detail.
When I go back into list and the minute changes, the component still call movingCurrentLineHour throwing 'Fatal error: Attempted to read an unowned reference but the object was already deallocated' in TimelineView:282.
How can I catch this? It is possible to fix or did I do something wrong?

EventPageView width calculation is impractical

When adding a very long event that overlaps with multiple other events the library comes up with very strange widths for the EventPageViews (see attached image).

EventPageView Width

Could you please make this behaviour modifiable or the function that calculates the page width overridable?

Kind regards,
Sjors

How can i load calendar view size dynamic

I create calendar view and set its frame . In that width is 300. On button tap i want to change calendar view width 500.
I reassign new frame with 500 width but calendar view month cell and header view consist with 300 width. I want to make it dynamic width after creating calendar view object
Screenshot 2020-07-25 at 5 11 44 PM

Event view column size

Hi, how can I fix the event width to the full width of the frame, if I don't have any event with the same hours?
I've 3 data and my event width looks like divided with event count.

I want the event size divide by the amount of event with the same hours only, not all events.

Edit: I used v0.2.5

IMG_306FB7CD49EB-1

How to hide Header

Hi @kvyatkovskys ,

Can we hide the header view, if yes where should i be looking to make those changes.
I want only the events part to be visible, as I'm planning to show events for only one day.

Screenshot 2020-07-24 at 3 27 28 PM

How do I get current date

Is it possible to get the current date on the calendar? Like when I am on the week view, can I get at least the starting date? Or can I get the date range? Like March 15-21?

Multiple day event

for example start date from 2020-07-19 at 08:30 and end date 2020-07-21 at 2:52, so how can show 3 days event
{
"all_day": 0,
"border_color": "#FFFFFF",
"color": "#12c745",
"end": "2020-07-21T12:52:00+03:00",
"id": "0",
"start": "2020-07-19T08:30
Simulator Screen Shot - iPhone 11 - 2020-07-22 at 10 46 23
:00+03:00",

        "text_color": "#000000",
        "title": "All day event number 2",
        "files": ["file"]
    }

Simulator Screen Shot - iPhone 11 - 2020-07-22 at 10 46 23

Move Events from Timeline or Week week

Hi, I wanted to know if you had thought or taken into consideration the possibility of moving appointments directly from the timeline or weekview as the original apple calendar. Or if you plan to extend the period directly from the event to the timeline or weekview with the longtap gesture.

Congratulations for the library

Panning/Moving Events

I wanted to move events from one time to other. Is there any way to do so in this library. I have checked your library, but i dont get any proper way. Can you please give me if there is any proper way for the same.

EventPageView

Hi.
Is there a possibility to change EventPageView to custom view ?

Scroll Header View

Hi, I found this problem ....

If I select Sunday, then I scroll the week after some scrolls the dot on the date is not visible and the date is not selected.

This is the piece of code it looks like it comes into else

guard let newMoveDate = cellDays.filter({ $0.day.date?.weekday == date.weekday }).first?.day.date, date != newMoveDate else { return }

ScrollDayHeaderView.swift row 237

Thk's

Events for a date

Can you get all of the events for a selected date (other than iterating through them)?

Customizable EventPageView through new func in protocol

Hi,

First, thank you for your work, it is beautiful and well coded.

Though, I'd like to know if it is possible to add a function in a protocol so we could customize the EventPageViews.

For example I'd like to add labels in the event view. And if I'm right, the protocol you expose does not support those kind of purpose.

In a UITableViewController there is this method : func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell that allows us to return a customizable view.

I can give it a try and make a pull request.

On Monthly view events are not selectable or titled.

Hi,

In the example on a daily view I can select an event, even drag it. However, on monthly view events are not selectable (just shows multi color dots). Tapping on the event dots in monthly view does not trigger delegate callbacks.

Is it possible to update those dots to show actual titles instead, and to do delegate callbacks on selection?
Does this need a code update, or is this functionality already available and I'm just not aware of it?

Thanks,

Chris

How to change orange bg color

I can't seem to find a way to remove the orange background colour that appears from todays date + 10 days. Please help!
Screenshot 2020-09-17 at 15 37 04

Is it possible to make a List View?

First of all thank you for such a great plugin. I need to make a list view like a UITableView tab next to month tab. I will really appreciate the help or at least the guidance on how to do that.

Get events on calendar view

Is it possible to get events that are visible on the calendar? For instance Just what you see on the week view?

Example not working

So if I run the example app and select today, the date is incorrect. If I then select a week or month, the selected date changes to the incorrect date.

Thoughts?

Problem with displaying and loading events in "Week" section

Having an issue in week section to load and display events.
Imagine we have an event on Tuesday February 18 and another on Tuesday February 25. The current day is Tuesday 18, so the first event is well displaying but when i change the week (with the Tuesday 25), the second event isn't displaying and if i click on my view and i begin to slide, the first event (Tuesday 18) is displaying for the Tuesday 25. (same for any weeks)

Only events from current week is displaying. See example (gif)
I followed the example in the readme to write my code.

calendar_issue

Header and Events Animations

Hello, first of all, the library is excellent.

My question is:

In the week view, when I horizontally scroll the calendar view (events), the header (mon, tue, wed ..) moves together with the events (horizontal animation) and changes to the next or previous week. But when I horizontally scroll header, the events don't have animation. Is there an option to move the events when the header is scrolling (same animation when events view is scrolling) ?.

The question is because I have recurring events, so without the animation (when scrolling the header), the events do not make the animation of the week change.

Thank you.

StoryBoard

How do you use this in a story board?

movingCurrentLineHour crashing on xcode 11.3.1

The following line is crashing on a minute update at guard let time =

private func movingCurrentLineHour() {
guard !(timer?.isValid ?? false) else { return }

    let date = Date()
    var comps = style.calendar.dateComponents([.era, .year, .month, .day, .hour, .minute], from: date)
    comps.minute = (comps.minute ?? 0) + 1
    guard let nextMinute = style.calendar.date(from: comps) else { return }
    
    timer = Timer(fire: nextMinute, interval: 60, repeats: true) { [unowned self] _ in
        let nextDate = Date()
        guard let time = self.getTimelineLabel(hour: nextDate.hour) else { return }

ScrollDirection for Month Calendar is limited to vertical.

var scrollDirection: UICollectionView.ScrollDirection = .vertical

scrollDirection inside MonthStyle is limited to vertical. Tried finding ways to set it to Horizontal but failed. the scroll directionVariable is not public so I can't access it.

could we make scrollDirection public?

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.