GithubHelp home page GithubHelp logo

bytescale / bytescale-javascript-sdk Goto Github PK

View Code? Open in Web Editor NEW
85.0 2.0 7.0 2.15 MB

Official Bytescale JavaScript SDK

Home Page: https://www.bytescale.com/docs/sdks/javascript

License: MIT License

JavaScript 5.67% TypeScript 94.00% Shell 0.33%
javascript file-upload image-upload file-uploader image-uploader upload npm-package file library javascript-library

bytescale-javascript-sdk's Introduction


Twitter URL


Use the Bytescale JavaScript SDK to upload, transform, and serve files at scale.

Full SDK DocumentationUpload WidgetMedia Processing APIsStorageCDN


Bytescale JavaScript SDK Example

Installation

For Node.js:

npm install @bytescale/sdk node-fetch

For Browsers:

npm install @bytescale/sdk

If you'd prefer to use a script tag:

<script src="https://js.bytescale.com/sdk/v3"></script>

Uploading Files — Try on CodePen

This library is isomorphic, meaning you can upload files from Node.js, or the browser, or both.

From Node.js:

import * as Bytescale from "@bytescale/sdk";
import nodeFetch from "node-fetch";

const uploadManager = new Bytescale.UploadManager({
  fetchApi: nodeFetch, // import nodeFetch from "node-fetch"; // Only required for Node.js. TypeScript: 'nodeFetch as any' may be necessary.
  apiKey: "free" // Get API keys from: www.bytescale.com
});

uploadManager
  .upload({
    // Supported types:
    // - String
    // - Blob
    // - ArrayBuffer
    // - Buffer
    // - ReadableStream (Node.js), e.g. fs.createReadStream("file.txt")
    data: "Hello World",

    // ---------
    // Optional:
    // ---------

    // Required if 'data' is a stream.
    // size: 5098, // e.g. fs.statSync("file.txt").size

    // Required if 'data' is a stream, buffer, or string.
    mime: "text/plain",

    // Required if 'data' is a stream, buffer, or string.
    originalFileName: "my_file.txt"

    // Reports progress: bytesTotal, bytesSent, progress.
    // onProgress: ({ progress }) => console.log(progress),

    // Controls multipart upload concurrency. Ignored if 'data' is a stream.
    // maxConcurrentUploadParts: 4,

    // Up to 2KB of arbitrary JSON.
    // metadata: {
    //   productId: 60891
    // },

    // Up to 25 tags per file.
    // tags: [
    //   "example_tag"
    // ],

    // About file paths:
    // - Your API key's "file upload path" is used by default, and can be changed by editing the API key's settings.
    // - You can override the API key's file upload path by specifying a path below.
    // - You may use path variables (e.g. "{UTC_DAY}"): http://localhost:3201/docs/path-variables
    // path: {
    //   folderPath: "/uploads/{UTC_YEAR}/{UTC_MONTH}/{UTC_DAY}",
    //   fileName: "{UTC_TIME_TOKEN_INVERSE}{UNIQUE_DIGITS_2}{ORIGINAL_FILE_EXT}"
    // },

    // Set to 'isCancelled = true' after invoking 'upload' to cancel the upload.
    // cancellationToken: {
    //   isCancelled: false
    // }
  })
  .then(
    ({ fileUrl, filePath }) => {
      // --------------------------------------------
      // File successfully uploaded!
      // --------------------------------------------
      // The 'filePath' uniquely identifies the file,
      // and is what you should save to your DB.
      // --------------------------------------------
      console.log(`File uploaded to: ${fileUrl}`);
    },
    error => console.error(`Error: ${error.message}`, error)
  );

From the Browser:

<html>
  <head>
    <script src="https://js.bytescale.com/sdk/v3"></script>
    <script>
      // import * as Bytescale from "@bytescale/sdk"
      const uploadManager = new Bytescale.UploadManager({
        apiKey: "free" // Get API keys from: www.bytescale.com
      });

      const onFileSelected = async event => {
        const file = event.target.files[0];

        try {
          const { fileUrl, filePath } = await uploadManager.upload({
            // Supported types:
            // - String
            // - Blob
            // - ArrayBuffer
            // - File (i.e. from a DOM file input element)
            data: file

            // ---------
            // Optional:
            // ---------

            // Required if 'data' is a stream. Node.js only. (Not required when uploading files from the browser.)
            // size: 5098, // e.g. fs.statSync("file.txt").size

            // Required if 'data' is a stream, buffer, or string. (Not required for DOM file inputs or blobs.)
            // mime: "application/octet-stream",

            // Required if 'data' is a stream, buffer, or string. (Not required for DOM file inputs or blobs.)
            // originalFileName: "my_file.txt",

            // Reports progress: bytesTotal, bytesSent, progress.
            // onProgress: ({ progress }) => console.log(progress),

            // Controls multipart upload concurrency. Ignored if 'data' is a stream.
            // maxConcurrentUploadParts: 4,

            // Up to 2KB of arbitrary JSON.
            // metadata: {
            //   productId: 60891
            // },

            // Up to 25 tags per file.
            // tags: [
            //   "example_tag"
            // ],

            // About file paths:
            // - Your API key's "file upload path" is used by default, and can be changed by editing the API key's settings.
            // - You can override the API key's file upload path by specifying a path below.
            // - You may use path variables (e.g. "{UTC_DAY}"): http://localhost:3201/docs/path-variables
            // path: {
            //   folderPath: "/uploads/{UTC_YEAR}/{UTC_MONTH}/{UTC_DAY}",
            //   fileName: "{UTC_TIME_TOKEN_INVERSE}{UNIQUE_DIGITS_2}{ORIGINAL_FILE_EXT}"
            // },

            // Set to 'isCancelled = true' after invoking 'upload' to cancel the upload.
            // cancellationToken: {
            //   isCancelled: false
            // }
          });

          // --------------------------------------------
          // File successfully uploaded!
          // --------------------------------------------
          // The 'filePath' uniquely identifies the file,
          // and is what you should save to your API.
          // --------------------------------------------
          alert(`File uploaded:\n${fileUrl}`);
        } catch (e) {
          alert(`Error:\n${e.message}`);
        }
      };
    </script>
  </head>
  <body>
    <input type="file" onchange="onFileSelected(event)" />
  </body>
</html>

Downloading Files

import * as Bytescale from "@bytescale/sdk";
import nodeFetch from "node-fetch"; // Only required for Node.js

const fileApi = new Bytescale.FileApi({
  fetchApi: nodeFetch, // import nodeFetch from "node-fetch"; // Only required for Node.js. TypeScript: 'nodeFetch as any' may be necessary.
  apiKey: "YOUR_API_KEY" // e.g. "secret_xxxxx"
});

fileApi
  .downloadFile({
    accountId: "YOUR_ACCOUNT_ID", // e.g. "W142hJk"
    filePath: "/uploads/2022/12/25/hello_world.txt"
  })
  .then(response => response.text()) // .text() | .json() | .blob() | .stream()
  .then(
    fileContents => console.log(fileContents),
    error => console.error(error)
  );

Use the UrlBuilder to get a URL instead (if you need a file URL instead of a binary stream).

Processing Files

import * as Bytescale from "@bytescale/sdk";
import fetch from "node-fetch"; // Only required for Node.js
import fs from "fs";

const fileApi = new Bytescale.FileApi({
  fetchApi: nodeFetch, // import nodeFetch from "node-fetch"; // Only required for Node.js. TypeScript: 'nodeFetch as any' may be necessary.
  apiKey: "YOUR_API_KEY" // e.g. "secret_xxxxx"
});

fileApi
  .processFile({
    accountId: "YOUR_ACCOUNT_ID", // e.g. "W142hJk"
    filePath: "/uploads/2022/12/25/image.jpg",

    // See: https://www.bytescale.com/docs/image-processing-api
    transformation: "image",
    transformationParams: {
      w: 800,
      h: 600
    }
  })
  .then(response => response.stream()) // .text() | .json() | .blob() | .stream()
  .then(
    imageByteStream =>
      new Promise((resolve, reject) => {
        const writer = fs.createWriteStream("image-thumbnail.jpg");
        writer.on("close", resolve);
        writer.on("error", reject);
        imageByteStream.pipe(writer);
      })
  )
  .then(
    () => console.log("Thumbnail saved to 'image-thumbnail.jpg'"),
    error => console.error(error)
  );

Use the UrlBuilder to get a URL instead (if you need a file URL instead of a binary stream).

Get File Details

import * as Bytescale from "@bytescale/sdk";
import fetch from "node-fetch"; // Only required for Node.js

const fileApi = new Bytescale.FileApi({
  fetchApi: nodeFetch, // import nodeFetch from "node-fetch"; // Only required for Node.js. TypeScript: 'nodeFetch as any' may be necessary.
  apiKey: "YOUR_API_KEY" // e.g. "secret_xxxxx"
});

fileApi
  .getFileDetails({
    accountId: "YOUR_ACCOUNT_ID", // e.g. "W142hJk"
    filePath: "/uploads/2022/12/25/image.jpg"
  })
  .then(
    fileDetails => console.log(fileDetails),
    error => console.error(error)
  );

Listing Folders

import * as Bytescale from "@bytescale/sdk";
import fetch from "node-fetch"; // Only required for Node.js

const folderApi = new Bytescale.FolderApi({
  fetchApi: nodeFetch, // import nodeFetch from "node-fetch"; // Only required for Node.js. TypeScript: 'nodeFetch as any' may be necessary.
  apiKey: "YOUR_API_KEY" // e.g. "secret_xxxxx"
});

folderApi
  .listFolder({
    accountId: "YOUR_ACCOUNT_ID", // e.g. "W142hJk"
    folderPath: "/",
    recursive: false
  })
  .then(
    // Note: operation is paginated, see 'result.cursor' and 'params.cursor'.
    result => console.log(`Items in folder: ${result.items.length}`),
    error => console.error(error)
  );

📙 Bytescale SDK API Reference

For a complete list of operations, please see:

Bytescale JavaScript SDK Docs »

🌐 Media Processing APIs (Image/Video/Audio)

Bytescale provides several real-time Media Processing APIs:

Image Processing API (Original Image)

Here's an example using a photo of Chicago:

https://upcdn.io/W142hJk/raw/example/city-landscape.jpg

Image Processing API (Transformed Image)

Using the Image Processing API, you can produce this image:

https://upcdn.io/W142hJk/image/example/city-landscape.jpg
  ?w=900
  &h=600
  &fit=crop
  &f=webp
  &q=80
  &blur=4
  &text=WATERMARK
  &layer-opacity=80
  &blend=overlay
  &layer-rotate=315
  &font-size=100
  &padding=10
  &font-weight=900
  &color=ffffff
  &repeat=true
  &text=Chicago
  &gravity=bottom
  &padding-x=50
  &padding-bottom=20
  &font=/example/fonts/Lobster.ttf
  &color=ffe400

Authentication

The Bytescale JavaScript SDK supports two types of authentication:

API Keys

The Bytescale JavaScript SDK automatically adds the apiKey from the constructor to the authorization header for all requests made via the SDK.

With API key auth, the requester has access to the resources available to the API key:

  • Secret API keys (secret_***) can perform all API operations.

  • Public API keys (public_***) can perform file uploads and file downloads only. File overwrites, file deletes, and all other destructive operations cannot be performed using public API keys.

Each Public API Key and Secret API Key can have its read/write access limited to a subset of files/folders.

JWTs

JWTs are optional.

With JWTs, the user can download private files directly via the URL, as authentication is performed implicitly via a session cookie or via an authorization header if service workers are enabled (see the serviceWorkerScript param on the AuthManager.beginAuthSession method). This allows the browser to display private files in <img>, <video>, and other elements.

With JWTs, the user can also perform API requests, such as file deletions, as these can be granted by the JWT's payload. The Bytescale JavaScript SDK will automatically inject the user's JWT into the authorization-token request header for all API requests, assuming the AuthManager.beginAuthSession method has been called.

Learn more about the AuthManager and JWTs »

UrlBuilder

Use the UrlBuilder to construct URLs for your uploaded files:

import { UrlBuilder } from "@bytescale/sdk";

Raw Files

To get the URL for the uploaded image /example.jpg in its original form, use the following:

// Returns: "https://upcdn.io/1234abc/raw/example.jpg"
UrlBuilder.url({
  accountId: "1234abc",
  filePath: "/example.jpg"
});

Images

To resize the uploaded image /example.jpg to 800x600, use the following:

// Returns: "https://upcdn.io/1234abc/image/example.jpg?w=800&h=600"
UrlBuilder.url({
  accountId: "1234abc",
  filePath: "/example.jpg",
  options: {
    transformation: "image",
    transformationParams: {
      w: 800,
      h: 600
    }
  }
});

Image Processing API Docs »

Videos

To transcode the uploaded video /example.mov to MP4/H.264 in HD, use the following:

// Returns: "https://upcdn.io/1234abc/video/example.mov?f=mp4-h264&h=1080"
UrlBuilder.url({
  accountId: "1234abc",
  filePath: "/example.mov",
  options: {
    transformation: "video",
    transformationParams: {
      f: "mp4-h264",
      h: 1080
    }
  }
});

Video Processing API Docs »

Audio

To transcode the uploaded audio /example.wav to AAC in 192kbps, use the following:

// Returns: "https://upcdn.io/1234abc/audio/example.wav?f=aac&br=192"
UrlBuilder.url({
  accountId: "1234abc",
  filePath: "/example.wav",
  options: {
    transformation: "audio",
    transformationParams: {
      f: "aac",
      br: 192
    }
  }
});

Audio Processing API Docs »

Archives

To extract the file document.docx from the uploaded ZIP file /example.zip:

// Returns: "https://upcdn.io/1234abc/archive/example.zip?m=extract&artifact=/document.docx"
UrlBuilder.url({
  accountId: "1234abc",
  filePath: "/example.zip",
  options: {
    transformation: "archive",
    transformationParams: {
      m: "extract"
    },
    artifact: "/document.docx"
  }
});

Archive Processing API Docs »

Antivirus

To scan the file example.zip for viruses, use the following:

// Returns: "https://upcdn.io/1234abc/antivirus/example.zip"
UrlBuilder.url({
  accountId: "1234abc",
  filePath: "/example.zip",
  options: {
    transformation: "antivirus"
  }
});

Antivirus API Docs »

🙋 Can I use my own storage?

Bytescale supports AWS S3, Cloudflare R2, Google Storage, DigitalOcean, and Bytescale Storage.

Bytescale Storage Docs »

Bytescale JavaScript SDK Docs »

👋 Create your Bytescale Account

Bytescale is the best way to upload, transform, and serve images, videos, and audio at scale.

Create a Bytescale account »

License

MIT

bytescale-javascript-sdk's People

Contributors

ljwagerfield 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

Watchers

 avatar  avatar

bytescale-javascript-sdk's Issues

upload-js-full SDK throws on import: Uncaught TypeError: Object.defineProperty called on non-object

I'm trying to use the upload-js-full SDK in a Next.js 13 application. ("next": "^13.4.2",)

I'm seeing the following error:

Uncaught TypeError: Object.defineProperty called on non-object
    at Function.defineProperty (<anonymous>)
    at __webpack_require__.r (webpack.js?ts=1686572445609:205:21)
    at eval (main.mjs:1:21)
    at ./node_modules/.pnpm/[email protected]/node_modules/upload-js-full/dist/browser/esm/main.mjs (manage.js?ts=1686572445609:46870:1)
    at options.factory (webpack.js?ts=1686572445609:661:31)
    at __webpack_require__ (webpack.js?ts=1686572445609:37:33)
    at fn (webpack.js?ts=1686572445609:316:21)
    at eval (upload.ts:8:72)
    at ./src/utils/upload.ts (manage.js?ts=1686572445609:46689:1)
    at options.factory (webpack.js?ts=1686572445609:661:31)
    at __webpack_require__ (webpack.js?ts=1686572445609:37:33)
    at fn (webpack.js?ts=1686572445609:316:21)
    at eval (ManageListing.tsx:18:71)
    at ./src/components/pages/owners/listings/ManageListing.tsx (manage.js?ts=1686572445609:46645:1)
    at options.factory (webpack.js?ts=1686572445609:661:31)
    at __webpack_require__ (webpack.js?ts=1686572445609:37:33)
    at fn (webpack.js?ts=1686572445609:316:21)
    at eval (manage.tsx:8:105)
    at ./src/pages/owners/listings/[id]/manage.tsx (manage.js?ts=1686572445609:46678:1)
    at options.factory (webpack.js?ts=1686572445609:661:31)
    at __webpack_require__ (webpack.js?ts=1686572445609:37:33)
    at fn (webpack.js?ts=1686572445609:316:21)
    at eval (?d8e5:5:16)
    at eval (route-loader.js:230:51)

I may be wrong but it appears to be we webpack error that's happening at the point of importing the dependency. This is a complete blocker for using the SDK.

Any ideas what is causing the issue?

For reference, the minimal upload-js SDK does work as expected, however I need the full SDK as I want access to more SDK actions and want use it on both the client and server.

Add example of using `uploadFile`

It would be good to have an example that doesn't require any DOM elements.

If possible, this example could construct a simple text file in memory, and then call uploadFile.

This would provide something usable on RunKit (which NPM links off to on each package's page).

How can I cancel an upload

Hello, I am trying to cancel uploads. From what I read in the documentation, I should be using cancellationToken: { isCancelled: false }, but I can't find a way to make this work.

The issue is that I am trying to pass a dynamic value to the isCancelled parameter, which is not being listened to. Here is an example of what I tried, which is not working.

let shouldBeCancelled = false;
setTimeout(() => {
    shouldBeCancelled = true;
}, 2000);

await uploadManager.upload({
    ...otherProperties,
    cancellationToken: {
        isCancelled: shouldBeCancelled,
    },
})

You can find the non working example in this Codepen.

Is there maybe another way to trigger the cancel of an ongoing upload that I missed in the documentation?

Thanks for your help!

JavaScript SDK integration doesn't work as documented with SvelteKit

Environment: SvelteKit + Vercel

Method 1

This one is from upload-js documentation. Works in development, doesn't work in production

import { Upload } from "upload-js";

const upload = Upload({ apiKey: * });

Error:

file:///var/task/.svelte-kit/output/server/entries/pages/_page.svelte.js:5
import { Upload } from "upload-js";
         ^^^^^^
SyntaxError: Named export 'Upload' not found. The requested module 'upload-js' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'upload-js';
const { Upload } = pkg;

    at ModuleJob._instantiate (node:internal/modules/esm/module_job:124:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:190:5)

Method 2

Works in development, doesn't work in production

import * as UploadJS from 'upload-js';
const { Upload } = UploadJS;

const upload = Upload({ apiKey: * });

Error:

TypeError: Upload is not a function
    at file:///var/task/.svelte-kit/output/server/entries/pages/_page.svelte.js:132:3
    at Object.$$render (file:///var/task/.svelte-kit/output/server/chunks/index2.js:187:18)
    at Object.default (file:///var/task/.svelte-kit/output/server/chunks/internal.js:69:98)
    at file:///var/task/.svelte-kit/output/server/entries/pages/_layout.svelte.js:18:33
    at Object.$$render (file:///var/task/.svelte-kit/output/server/chunks/index2.js:187:18)
    at file:///var/task/.svelte-kit/output/server/chunks/internal.js:58:101
    at $$render (file:///var/task/.svelte-kit/output/server/chunks/index2.js:187:18)
    at Object.render (file:///var/task/.svelte-kit/output/server/chunks/index2.js:195:20)
    at render_response (file:///var/task/.svelte-kit/output/server/index.js:1739:34)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

Method 3

Works in both production and development.

import UploadJS from 'upload-js';
const { Upload } = UploadJS;

const upload = Upload({ apiKey: * });

Error: Failed to upload part (403).

Version

+     "@bytescale/sdk": "^3.31.1",

I am attempting to upload a small png file with this snippet using TypeScript and Node.js version 20

// upload.ts
import * as Bytescale from "@bytescale/sdk";
import * as fs from 'fs'
import nodeFetch from "node-fetch";

const uploadManager = new Bytescale.UploadManager({
  fetchApi: nodeFetch as any,
  apiKey: 'i have tried using my secret key and public key, both give same error'
});

export const uploadFileFromPath = async (filePath: string): Promise<string> => {
    const data = fs.readFileSync(filePath)
    console.log({ data }) // file does exist is and is a buffer. 

    try {
        const res = await uploadManager.upload({
            data,
            mime: 'image/png',
            originalFileName: filePath,
        })

        return res.fileUrl
    } catch (err) {
        console.error(err)
    }
}

const start = async () => {
    const fileUrl = await uploadFileFromPath('./1707182041670-10.png')
    console.log({ fileUrl })
}
start()

node --loader ts-node/esm ./upload.ts

I am getting the following error:

Error: Failed to upload part (403).
    at file:///Users/zact/Projects/scourz/application/node_modules/@bytescale/sdk/dist/node/esm/main.mjs:2222:19
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

I cannot find any documentation about the errors, I am unsure if the 403 means that my API KEY or ACCESS is wrong but the error says 'failed to upload part' so maybe my request body is wrong?

Image cropping support / documentation

Is it possible for Upload.js to crop images?

I've read the documentation but couldn't find anything -- presumably it would start with me prompting the user for some image cropping dimensions, and then associating them with the uploaded image somehow (but I'm just guessing). Does Upload.js / upload.io support it?

Appreciate any help! 🙏

ChainAlert: npm package release (1.48.18) has no matching tag in this repo

Dear upload-js maintainers,
Thank you for your contribution to the open-source community.

This issue was automatically created to inform you a new version (1.48.18) of upload-js was published without a matching tag in this repo.

As part of our efforts to fight software supply chain attacks, we would like to verify this release is known and intended, and not a result of an unauthorized activity.

If you find this behavior legitimate, kindly close and ignore this issue. Read more

badge

400 Bad request when displaying image using JWT

Version

"@bytescale/sdk": "^3.34.0"

Description

I am trying to handle authentication to upload and fetch the files, but the fetching of the file doesn't seem to be working as a I get a 400 bad request when trying to display the image with <img src={url} />

We start by authenticating using the AuthManager

await bytescale.AuthManager.beginAuthSession({
  accountId: "ACCOUNT_ID",
  authUrl: "AUTHENTICATION_URL",
  authHeaders: () => Promise.resolve({}),
});

We then upload an image using the upload manager

const uploadManager = new bytescale.UploadManager({
  apiKey: publicKey,
})

const response = await uploadManager.upload({
  data: file,
  path: {
    folderPath: "/",
    fileName: getFileName(),
  },
});

This response contains the fileUrl, which we directly use to display the image:

function Image({ response }: { response: { fileUrl: string } }) {
  if (!fileUrl) return null;

  return <img src={fileUrl} alt="test" />
}

Current result

The upload works as expected, I can indeed find my image in the Bytescale dashboard. The upload is protected by JWT so I can confirm that the JWT authentication works as expected.

The GET image request does not work, I receive the following response:

321940478-486f44d6-5122-4fda-9879-a402d6309730

Note that we can see the JWT is indeed used to get the image through the cookie.

The response tab displays the following message: "Failed to load response data: No data found for resource with given identifier"

Expected result

The image is displayed after it got uploaded

Am I maybe missing something?

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.