Giter Club home page Giter Club logo

ruby-klaviyo's Introduction

ruby-klaviyo - RETIRED

Deprecation Notice

This SDK and its associated rubygem package are set to be deprecated on 2024-06-30 and will not receive further updates.

We recommend migrating over to our newest SDK.

You can read more about our SDK release history and support here

For a comparison between our old and new APIs, check out this guide.

Migration Instructions

NOTE: this change is not backwards compatible; migrating to the new SDK requires completing the following steps:

Install New SDK

gem install klaviyo_sdk

Update Import

From:

require 'klaviyo'

To:

require 'klaviyo_sdk'

Update Client Instantiation

from:

Klaviyo.private_api_key = 'YOUR_PRIVATE_API_KEY'

to:

Client.configure do |config|
  config.api_key['ApiKeyAuth'] = 'YOUR_PRIVATE_API_KEY'
end

Important

The api_key is set at the global level: this means that if you manage multiple stores, you will need to run the code for each store in a different environment.

Updating API Operations

The new SDK has many name changes to both namespace and parameters (types+format). Please reference this section for a comprehensive list of examples.

What is Klaviyo?

Klaviyo is a real-time service for understanding your customers by aggregating all your customer data, identifying important groups of customers and then taking action. http://www.klaviyo.com/

What does this Gem do?

  • Track customers and events directly from your backend.
  • Track customers and events via JavaScript using a Rack middleware.
  • Access historical metric data.
  • Manage lists and profiles in Klaviyo.

How to install?

gem install klaviyo

API Examples

Require the Klaviyo module in the file, and then set your Public and Private API keys:

# require the klaviyo module
require 'klaviyo'

# set your 6 digit Public API key
Klaviyo.public_api_key = 'YOUR_PUBLIC_API_KEY'

# set your Private API key
Klaviyo.private_api_key = 'YOUR_PRIVATE_API_KEY'

Public:

# used to track events
Klaviyo::Public.track(
  'Filled out profile',
  email: '[email protected]',
  properties: {
    'Added social accounts' : false
  }
)

# using a phone number to track events
Klaviyo::Public.track(
  'TestedSMSContact',
  phone_number: '+15555555555',
  properties: {
    'Added social accounts' : false
  }
)

# used for identifying customers and managing profile properties
Klaviyo::Public.identify(
  email: '[email protected]',
  properties: {
    '$first_name': 'Thomas',
    '$last_name': 'Jefferson',
    'Plan': 'Premium'
  }
)

# using a POST request to track events
Klaviyo::Public.track(
  'Filled out profile',
  method: 'post',
  email: '[email protected]',
  properties: {
    'Added social accounts': false
  }
)

Thread Safety - Public API

For Public API requests, you can set the Public API key per-request by using the 'token' keyword argument. This allows you to send track or identify requests to different Klaviyo accounts.

# sending a track request to account xXyYzZ
Klaviyo::Public.track(
  'Filled out profile',
  token: 'PUBLIC_API_TOKEN',
  email: '[email protected]',
  properties: {
    'Added social accounts' : false
  }
)

# sending an identify request to account xXyYzZ
Klaviyo::Public.identify(
  email: '[email protected]',
  token: 'PUBLIC_API_TOKEN',
  properties: {
    '$first_name': 'Thomas',
    '$last_name': 'Jefferson',
    'Plan': 'Premium'
  }
)

Thread Safety - Private APIs

For all of the APIs listed below, each request will accept an optional 'api_key' keyword argument that can be used to set the Private API key for that request. Examples are provided below. All of the following methods will accept the 'api_key' keyword argument.

Lists:

# to add a new list
Klaviyo::Lists.create_list('NEW_LIST_NAME')

# add a new list using a different api key
Klaviyo::Lists.create_list('NEW_LIST_NAME', api_key: 'pk_EXAMPLE_API_KEY')

# to get all lists
Klaviyo::Lists.get_lists()

# to get all lists using a different api key
Klaviyo::Lists.get_lists(api_key: 'pk_EXAMPLE_API_KEY')

# to get list details
Klaviyo::Lists.get_list_details('LIST_ID')

# to update a list name
Klaviyo::Lists.update_list_details('LIST_ID', 'LIST_NAME_UPDATED')

# to delete a list
Klaviyo::Lists.delete_list('LIST_ID')

# to check email address subscription status to a list
Klaviyo::Lists.check_list_subscriptions(
  'LIST_ID',
  emails: ['[email protected]'],
  phone_numbers: ['5555555555'],
  push_tokens: ['PUSH_TOKEN']
)

# check email address subscription status to a list using a different api key
Klaviyo::Lists.check_list_subscriptions(
  'LIST_ID',
  api_key: 'pk_EXAMPLE_API_KEY',
  emails: ['[email protected]'],
  phone_numbers: ['5555555555'],
  push_tokens: ['PUSH_TOKEN']
)

# to add subscribers to a list, this will follow the lists double opt in settings
Klaviyo::Lists.add_subscribers_to_list(
  'LIST_ID',
  profiles: [
    {
      email: '[email protected]'
    },
    {
      phone_number: '5555555555'
    }
  ]
)

# to unsubscribe and remove profile from a list and suppress a profile
Klaviyo::Lists.unsubscribe_from_list(
  'LIST_ID',
  emails: ['[email protected]']
)

# to add members to a list, this doesn't care about the list double opt in setting
Klaviyo::Lists.add_to_list(
  'LIST_ID',
  profiles: [
    {
      email: '[email protected]'
    },
    {
      email: '[email protected]'
    }
  ]
)

# to check email profiles if they're in a list
Klaviyo::Lists.check_list_memberships(
  'LIST_ID',
  emails: ['[email protected]'],
  phone_numbers: ['5555555555'],
  push_tokens: ['PUSH_TOKEN']
)

# to remove profiles from a list
Klaviyo::Lists.remove_from_list(
  'LIST_ID',
  emails: ['[email protected]'],
  phone_numbers: ['5555555555'],
  push_tokens: ['PUSH_TOKEN']
)

# to get exclusion emails from a list - marker is used for paginating
Klaviyo::Lists.get_list_exclusions('LIST_ID', marker: 'EXAMPLE_MARKER')

# to get exclusion emails from a list - marker is used for paginating using a different api key
Klaviyo::Lists.get_list_exclusions(
  'LIST_ID',
  marker: 'EXAMPLE_MARKER',
  api_key: 'pk_EXAMPLE_API_KEY'
)

# to get all members in a group or list
Klaviyo::Lists.get_group_members('LIST_ID')

# to get the next batch of records in a group or list
Klaviyo::Lists.get_group_members(
  'LIST_ID',
  marker: 'EXAMPLE_MARKER'
)

Profiles:

# get profile id by email
Klaviyo::Profiles.get_profile_id_by_email('EMAIL')

# get profile id by email using a different api key
Klaviyo::Profiles.get_profile_id_by_email('EMAIL', api_key: 'pk_EXAMPLE_API_KEY')

# get profile by profile_id
Klaviyo::Profiles.get_person_attributes('PROFILE_ID')

# update a profile
Klaviyo::Profiles.update_person_attributes(
  'PROFILE_ID',
  PropertyName1: 'value',
  PropertyName2: 'value'
)

# get all metrics for a profile with the default kwargs
Klaviyo::Profiles.get_person_metrics_timeline(
  'PROFILE_ID',
  since: nil,
  count: 100,
  sort: 'desc'
)

# get all metrics for a profile with the default kwargs using a different api key
Klaviyo::Profiles.get_person_metrics_timeline(
  'PROFILE_ID',
  since: nil,
  count: 100,
  sort: 'desc',
  api_key: 'pk_EXAMPLE_API_KEY'
)


# get all events of a metric for a profile with the default kwargs
Klaviyo::Profiles.get_person_metric_timeline(
  'PROFILE_ID',
  'METRIC_ID',
  since: nil,
  count: 100,
  sort: 'desc'
)

Metrics:

# get all metrics with the default kwargs
Klaviyo::Metrics.get_metrics(page: 0, count: 100)

# get all metrics with the default kwargs using a different api key
Klaviyo::Metrics.get_metrics(page: 0, count: 100, api_key: 'pk_EXAMPLE_API_KEY')

# get a batched timeline of all metrics with the default kwargs
Klaviyo::Metrics.get_metrics_timeline(
  since: nil,
  count: 100,
  sort: 'desc'
)

# get a batched timeline of a single metric with the default kwargs
Klaviyo::Metrics.get_metric_timeline(
  'METRIC_ID',
  since: nil,
  count: 100,
  sort: 'desc'
)

# get a batched timeline of a single metric with the default kwargs using a different api key
Klaviyo::Metrics.get_metric_timeline(
  'METRIC_ID',
  since: nil,
  count: 100,
  sort: 'desc',
  api_key: 'pk_EXAMPLE_API_KEY'
)

# export data for a single metric
Klaviyo::Metrics.get_metric_export(
  'METRIC_ID',
  start_date: nil,
  end_date: nil,
  unit: nil,
  measurement: nil,
  where: nil,
  by: nil,
  count: nil
)

Campaigns:

# get Campaigns
Klaviyo::Campaigns.get_campaigns()

# get Campaigns using a different api key
Klaviyo::Campaigns.get_campaigns(api_key: 'pk_EXAMPLE_API_KEY')

# get specific Campaign details
Klaviyo::Campaigns.get_campaign_details('CAMPAIGN_ID')

# send Campaign
Klaviyo::Campaigns.send_campaign('CAMPAIGN_ID')

# cancel Campaign
Klaviyo::Campaigns.cancel_campaign('CAMPAIGN_ID')

# cancel Campaign using a different API key
Klaviyo::Campaigns.cancel_campaign('CAMPAIGN_ID', api_key: 'pk_EXAMPLE_API_KEY')

Email Templates:

# get templates
Klaviyo::EmailTemplates.get_templates()

# get templates using a different api key
Klaviyo::EmailTemplates.get_templates(api_key: 'pk_EXAMPLE_API_KEY')

# create a new template
Klaviyo::EmailTemplates.create_template(name: 'TEMPLATE_NAME', html: 'TEMPLATE_HTML')

# update template
# does not update drag & drop templates at this time
Klaviyo::EmailTemplates.update_template(
  'TEMPLATE_ID',
  name: 'UPDATED_TEMPLATE_NAME',
  html: 'UPDATED_TEMPLATE_HTML'
)

# delete template
Klaviyo::EmailTemplates.delete_template('TEMPLATE_ID')

# delete template using a different API key
Klaviyo::EmailTemplates.delete_template('TEMPLATE_ID', api_key: 'pk_EXAMPLE_API_KEY')

# clone a template with a new name
Klaviyo::EmailTemplates.clone_template('TEMPLATE_ID', 'NEW_TEMPLATE_NAME')

# render template - returns html and text versions of template
Klaviyo::EmailTemplates.render_template(
  'TEMPLATE_ID',
  context: {
    name: 'RECIPIENT_NAME',
    email: 'RECIPIENT_EMAIL_ADDRESS'
  }

# render template - returns html and text versions of template using a different api key
Klaviyo::EmailTemplates.render_template(
  'TEMPLATE_ID',
  api_key: 'pk_EXAMPLE_API_KEY',
  context: {
    name: 'RECIPIENT_NAME',
    email: 'RECIPIENT_EMAIL_ADDRESS'
  }
)

# send template
Klaviyo::EmailTemplates.send_template(
  'TEMPLATE_ID',
  from_email: 'FROM_EMAIL_ADDRESS',
  from_name: 'FROM_EMAIL_NAME',
  subject: 'EMAIL_SUBJECT',
  to: 'RECIPIENT_EMAIL_ADDRESS',
  context: {
    name: 'RECIPIENT_NAME',
    email: 'RECIPIENT_EMAIL_ADDRESS'
  }
)

Data Privacy:

# delete profile by email
Klaviyo::DataPrivacy.request_profile_deletion('email','EMAIL')

# delete profile by email using a different api key
Klaviyo::DataPrivacy.request_profile_deletion('email','EMAIL', api_key: 'pk_EXAMPLE_API_KEY')

# delete profile by phone number
Klaviyo::DataPrivacy.request_profile_deletion('phone_number','PHONE_NUMBER')

# delete profile by phone number using a different api key
Klaviyo::DataPrivacy.request_profile_deletion('phone_number','PHONE_NUMBER', api_key: 'pk_EXAMPLE_API_KEY')

# delete profile by person id
Klaviyo::DataPrivacy.request_profile_deletion('person_id','PERSON_ID')

# delete profile by person id using a different api key
Klaviyo::DataPrivacy.request_profile_deletion('person_id','PERSON_ID', api_key: 'pk_EXAMPLE_API_KEY')

How to use it with a Rails application?

To automatically insert the Klaviyo script in your Rails app, add this to your environment config file or create a new initializer for it:

require 'rack/klaviyo'
config.middleware.use "Klaviyo::Client::Middleware", "YOUR_PUBLIC_KLAVIYO_API_TOKEN"

This will automatically insert the Klaviyo script at the bottom on your HTML page, right before the closing body tag.

To record customer actions directly from your backend, in your application_controller class add a method to initialize set your public and private API tokens.

require 'klaviyo'

class ApplicationController < ActionController::Base
  Klaviyo.public_api_key = 'YOUR_PUBLIC_API_KEY'
  Klaviyo.private_api_key = 'YOUR_PRIVATE_API_KEY'
end

Then in your controllers where you'd like to record an event:

Klaviyo::Public.track('Did something important',
  email: '[email protected]',
  properties:
  {
    key: 'value'
  }
)

ruby-klaviyo's People

Contributors

bialecki avatar cpcurtis1218 avatar dano-klaviyo avatar hundredwatt avatar klaviyojad avatar markglenn avatar nnande avatar pierrea avatar smostovoy avatar ymek 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

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

ruby-klaviyo's Issues

Email requirement

According to documentation for the track and identify APIs, email or phone_number key can be supplied:

Custom information about the person who did this event. You must identify the person by their email using a $email key (or by their phone number using a $phone_number key if you have SMS-only contacts). Other than that, you can include any data you want and it can then be used to create segments of people. For example, if you wanted to create a list of people on trial plans, include a person's plan type in this hash so you can use that information later.

Should the email or id check be changed accordingly?
I am working on a product where customers can sign up with a phone number only, optionally setting their email...

def self.check_email_or_id_exists(kwargs)

How can I send the email?

I saw this still uses https://a.klaviyo.com/api/v1/email-template/{template_id}/send which was deprecated.

# send template
Klaviyo::EmailTemplates.send_template(
  'TEMPLATE_ID',
  from_email: 'FROM_EMAIL_ADDRESS',
  from_name: 'FROM_EMAIL_NAME',
  subject: 'EMAIL_SUBJECT',
  to: 'RECIPIENT_EMAIL_ADDRESS',
  context: {
    name: 'RECIPIENT_NAME',
    email: 'RECIPIENT_EMAIL_ADDRESS'
  }
)

What is the solution to send Klaviyo email using the API?

check_list_subscriptions uses the incorrect endpoint

There appears to be a bug in this ruby gem on the check_list_subscriptions method. This method is supposed to check if a user is on a list AND they aren't unsubscribed.

See documentation here: https://apidocs.klaviyo.com/reference/lists-segments#get-list-subscriptions

That indicates that this method should hit https://a.klaviyo.com/api/v2/list/LIST_ID/get-list-subscriptions

But in the sourcecode here: https://github.com/klaviyo/ruby-klaviyo/blob/master/lib/klaviyo/apis/lists.rb

you can see that it is hitting "#{LIST}/#{list_id}/#{SUBSCRIBE}" and SUBSCRIBE is set to 'subscribe'.

GET on that endpoint is not described in the documentation, but it appears to check list membership without checking against unsubscribes.

The fix is to correct that URL to be "#{LIST}/#{list_id}/get-list-subscriptions", although you will want to use a constant to follow the existing pattern.

Errno::ENAMETOOLONG: File name too long @ rb_sysopen

Attempting to send a tracking payload I'm hitting this error with a request looking like:

http://a.klaviyo.com/crm/api/track?data=eyJ0b2tlbiI6InBrXzA3MTIwNjEyODliZTQ2NjM2ODRjMGRjOWZhNjcyMDg1NWQiLCJldmVudCI6IlBsYWNlZCBPcmRlciIsInByb3BlcnRpZXMiOnsiT3JkZXJOdW1iZXIiOiJSNTQ1ODAwMDU1IiwiQ2F0ZWdvcmllcyI6WyJTb2Z0d2FyZSIsIkZYIl0sIkl0ZW1OYW1lcyI6WyJSaWZ0IEZpbHRlciBMaXRlIC0gTWFzdGVyIl0sIkRpc2NvdW50Q29kZSI6IiIsIkRpc2NvdW50VmFsdWUiOiItNDkuMCIsIkl0ZW1zIjpbeyJQcm9kdWN0SUQiOjgsIlNLVSI6Ik1BLVNGLTAwMiIsIlByb2R1Y3ROYW1lIjoiUmlmdCBGaWx0ZXIgTGl0ZSAtIE1hc3RlciIsIlF1YW50aXR5IjoxLCJJdGVtUHJpY2UiOiI0OS4wIiwiUm93VG90YWwiOiI0OS4wIiwiUHJvZHVjdFVSTCI6Imh0dHBzOi8vbG9jYWxob3N0OjMwMDAvcHJvZHVjdHMvcmlmdC1maWx0ZXItbGl0ZSIsIkltYWdlVVJMIjoiLy9zMy11cy1lYXN0LTIuYW1hem9uYXdzLmNvbS9taW5pbWFsLWF1ZGlvLXN0YWdpbmcvVXNlcnMvamFjb2JwZW5uL2phY29icGVubi9EZXZlbG9wbWVudC9NaW5pbWFsL1dlYnNpdGUvbWluaW1hbGF1ZGlvL3B1YmxpYy9zcHJlZS9wcm9kdWN0cy8xNS9wcm9kdWN0L3JpZnQtZmlsdGVyLWxpdGUtMS5wbmc/MTYzMjM0Mjc2OSIsIlByb2R1Y3RDYXRlZ29yaWVzIjpbIlNvZnR3YXJlIiwiRlgiXX1dLCJCaWxsaW5nQWRkcmVzcyI6eyJGdWxsTmFtZSI6Ik1pbmltYWwgQXVkaW8iLCJGaXJzdE5hbWUiOiJNaW5pbWFsIiwiTGFzdE5hbWUiOiJBdWRpbyIsIkNvbXBhbnkiOm51bGwsIkFkZHJlc3MxIjoiMTAyMDEgV2F5emF0YSBCbHZkIiwiQWRkcmVzczIiOiJTdWl0ZSAyNTAiLCJDaXR5IjoiSG9wa2lucyIsIlJlZ2lvbiI6Ik1OIiwiUmVnaW9uQ29kZSI6Ik1OIiwiQ291bnRyeSI6IlVuaXRlZCBTdGF0ZXMiLCJDb3VudHJ5Q29kZSI6IlVTIiwiWmlwIjoiNTUzMDUiLCJQaG9uZSI6bnVsbH0sIlNoaXBwaW5nQWRkcmVzcyI6eyJGdWxsTmFtZSI6Ik1pbmltYWwgQXVkaW8iLCJGaXJzdE5hbWUiOiJNaW5pbWFsIiwiTGFzdE5hbWUiOiJBdWRpbyIsIkNvbXBhbnkiOm51bGwsIkFkZHJlc3MxIjoiMTAyMDEgV2F5emF0YSBCbHZkIiwiQWRkcmVzczIiOiJTdWl0ZSAyNTAiLCJDaXR5IjoiSG9wa2lucyIsIlJlZ2lvbiI6Ik1OIiwiUmVnaW9uQ29kZSI6Ik1OIiwiQ291bnRyeSI6IlVuaXRlZCBTdGF0ZXMiLCJDb3VudHJ5Q29kZSI6IlVTIiwiWmlwIjoiNTUzMDUiLCJQaG9uZSI6bnVsbH0sIk9yZGVyVVJMIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6MzAwMC9vcmRlcnMvUjU0NTgwMDA1NSIsIlRvdGFsIjoiMC4wIiwiSXRlbVRvdGFsIjoiNDkuMCIsIkFkanVzdG1lbnRUb3RhbCI6Ii00OS4wIiwiUGF5bWVudFRvdGFsIjoiMC4wIiwiU2hpcG1lbnRUb3RhbCI6IjAuMCIsIkFkZGl0aW9uYWxUYXhUb3RhbCI6IjAuMCIsIlByb21vVG90YWwiOiItNDkuMCIsIkluY2x1ZGVkVGF4VG90YWwiOiIwLjAiLCJTaGlwbWVudHMiOltdLCJQYXltZW50cyI6W10sIiRldmVudF9pZCI6IlI1NDU4MDAwNTUiLCIkdmFsdWUiOiIwLjAifSwiY3VzdG9tZXJfcHJvcGVydGllcyI6eyJFbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwiJGVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20iLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIn0sImlwIjoiIiwidGltZSI6MTYzMjM0NzU2MH0=

Is this simply too much data for event properties?

2.0.5 breaking change

Just FYI, 2.0.5 broke for me because the track API previously accepted a Hash for the kwargs but now requires actual keyword arguments (due to changes in request). Not sure if this should be considered a major version change as a result?

Better support for rate limiting

I am in a situation where in some (rare) circumstances I will need to update a lot of users on Klaviyo at bulk. Unfortunately I am not sure how I can implement rate limiting with this gem as it does not give a lot of info if the request was successful when I use the identify endpoint.

If the identify endpoint was POST, I would get the status code and could implement some logic based on that, unfortunately the Gem uses the (deprecated according to docs) GET endpoint and is implemented in such a way that instead of returning the response, it returns simply 0 or 1. It is not clear to me from documentation if I would get a 0 response code if I was rate limited (ref).

Also in general unfortunately the docs do not seem to provide any good info on what the rate limits actually are that I could implement some staggering logic myself that would work. If e.g. I knew it was 10 items / second or something, I could simply implement it in such a way that it would always be safely below that for bulk update operations.

Middleware-generated script tags are HTML-escaped

In its current form, when using this gem in a Rails 6 app, when I add middleware configuration the script tag that is appended to the bottom of every HTML page is escaped, so that it renders as a string ("<script...") instead of a script tag.

Klaviyo client not thread-safe

I have a client with two separate subscriptions to Klaviyo. We'd like to be able to use this Ruby library to send events for both subscriptions, each of which has a separate api key.

v1.0 of the ruby-klaviyo library was thread-safe, since I could instantiate the Klaviyo::Client with the api key and make the subsequent requests based on that. v2.0 breaks that thread-safety, since it's a singleton assignment and assumes all requests are going to use the same api key.

Will there be a way going forward to specify api keys for particular requests?

Problem with request_profile_deletion

Hi, I'm trying to delete profiles as follows:

Klaviyo::DataPrivacy.request_profile_deletion('email', '[email protected]')

but I'm getting the following ArgumentError:

ArgumentError (wrong number of arguments (given 3, expected 2))

am I missing something here? I've tried it with profile_id & phone_number with the same result

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.