├── api ├── .python-version ├── __config.json ├── .gitignore ├── Dockerfile.custom ├── __app.yaml ├── package.json ├── test │ ├── app.test.js │ └── cat.json ├── views │ └── index.pug ├── __openapi-appengine.yaml ├── README.md ├── app.js └── category-count-dict.json ├── images ├── demo.png └── header.png ├── .distilla ├── demo ├── static │ ├── font │ │ ├── handwritten01.otf │ │ ├── handwritten02.otf │ │ └── handwritten03.otf │ ├── images │ │ ├── share-embed.jpg │ │ ├── ham.svg │ │ ├── ic_refresh_black_24dp.svg │ │ ├── share.svg │ │ ├── github.svg │ │ └── play.svg │ ├── js │ │ ├── demo.js │ │ ├── categories-count-dict.js │ │ └── categories.js │ └── style.css └── index.html ├── wct.conf.json ├── index.html ├── .gitignore ├── test ├── index.html └── quickdraw-component_test.html ├── .gcloudignore ├── package.json ├── polymer.json ├── CONTRIBUTING.md ├── README.md ├── LICENSE.txt ├── categories.js └── quickdraw-component.js /api/.python-version: -------------------------------------------------------------------------------- 1 | 2.7.13 2 | -------------------------------------------------------------------------------- /api/__config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectId": "YOUR_PROJECT_ID", 3 | "bucketId": "YOUR_BUCKET_ID" 4 | } -------------------------------------------------------------------------------- /images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecreativelab/quickdraw-component/HEAD/images/demo.png -------------------------------------------------------------------------------- /.distilla: -------------------------------------------------------------------------------- 1 | preview: true 2 | 3 | tasks: 4 | npm run build: 5 | - build/es5-unbundled/ 6 | - ./ 7 | -------------------------------------------------------------------------------- /images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecreativelab/quickdraw-component/HEAD/images/header.png -------------------------------------------------------------------------------- /demo/static/font/handwritten01.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecreativelab/quickdraw-component/HEAD/demo/static/font/handwritten01.otf -------------------------------------------------------------------------------- /demo/static/font/handwritten02.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecreativelab/quickdraw-component/HEAD/demo/static/font/handwritten02.otf -------------------------------------------------------------------------------- /demo/static/font/handwritten03.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecreativelab/quickdraw-component/HEAD/demo/static/font/handwritten03.otf -------------------------------------------------------------------------------- /demo/static/images/share-embed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecreativelab/quickdraw-component/HEAD/demo/static/images/share-embed.jpg -------------------------------------------------------------------------------- /wct.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "plugins": { 4 | "local": { 5 | "browsers": ["chrome", "firefox"] 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | QuickDrawFiles-ac0a06cd19d2.json 2 | quickdrawfiletest-814462328b90.json 3 | node_modules/ 4 | gcs-utils/ 5 | config.json 6 | app.yaml 7 | openapi-appengine.yaml 8 | -------------------------------------------------------------------------------- /demo/static/images/ham.svg: -------------------------------------------------------------------------------- 1 | Asset 4 -------------------------------------------------------------------------------- /demo/static/images/ic_refresh_black_24dp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | quickdraw-component 7 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | bower_components 3 | node_modules 4 | 5 | # compiled output 6 | dist 7 | 8 | # IDEs 9 | .idea 10 | .vscode 11 | 12 | # misc 13 | .DS_Store 14 | npm-debug.log 15 | 16 | # Analyzer output used in the docs 17 | analysis.json 18 | 19 | # NPM artifact 20 | polymer-polymer-*.tgz 21 | 22 | # Typings are generated upon publish to NPM, except for one. 23 | *.d.ts 24 | !interfaces.d.ts 25 | 26 | .DS_Store.DS_Store 27 | 28 | demo/static/fonts 29 | 30 | /build -------------------------------------------------------------------------------- /demo/static/images/share.svg: -------------------------------------------------------------------------------- 1 | Asset 6 2 | -------------------------------------------------------------------------------- /api/Dockerfile.custom: -------------------------------------------------------------------------------- 1 | # The Google App Engine Flexible Environment base Docker image can 2 | # also be used on Google Container Engine, or any other Docker host. 3 | # This image is based on Debian Jessie and includes nodejs and npm 4 | # installed from nodejs.org. The source is located in 5 | # https://github.com/GoogleCloudPlatform/nodejs-docker 6 | FROM gcr.io/google_appengine/nodejs 7 | 8 | ADD . /app 9 | WORKDIR /app 10 | 11 | RUN npm install 12 | ENV PORT=8080 13 | ENTRYPOINT ["npm", "start"] 14 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.gcloudignore: -------------------------------------------------------------------------------- 1 | # This file specifies files that are *not* uploaded to Google Cloud Platform 2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of 3 | # "#!include" directives (which insert the entries of the given .gitignore-style 4 | # file at that point). 5 | # 6 | # For more information, run: 7 | # $ gcloud topic gcloudignore 8 | # 9 | .gcloudignore 10 | # If you would like to upload your .git directory, .gitignore file or files 11 | # from your .gitignore file, remove the corresponding line 12 | # below: 13 | .git 14 | .gitignore 15 | 16 | # Node.js dependencies: 17 | node_modules/ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quickdraw-component", 3 | "description": "A web component & API for quickly drawing QuickDraw drawings", 4 | "main": "quickdraw-component.js", 5 | "scripts": { 6 | "start": "polymer serve --open ./ --port 8080 --module-resolution='node'", 7 | "build": "polymer build", 8 | "test": "polymer test" 9 | }, 10 | "dependencies": { 11 | "@polymer/lit-element": "^0.6.1", 12 | "@polymer/polymer": "^3.0.0-pre.12" 13 | }, 14 | "devDependencies": { 15 | "@babel/core": "^7.0.0-beta.42", 16 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0-beta.42", 17 | "@babel/preset-env": "^7.0.0-beta.42", 18 | "@webcomponents/webcomponentsjs": "^1.0.0", 19 | "wct-browser-legacy": "^1.0.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /demo/static/images/github.svg: -------------------------------------------------------------------------------- 1 | Asset 1 -------------------------------------------------------------------------------- /polymer.json: -------------------------------------------------------------------------------- 1 | { 2 | "npm": true, 3 | "entrypoint": "demo/index.html", 4 | "extraDependencies": [ 5 | "categories.js", 6 | "index.html", 7 | "node_modules/@webcomponents/webcomponentsjs/*.js", 8 | "!node_modules/@webcomponents/webcomponentsjs/gulpfile.js", 9 | "node_modules/@webcomponents/webcomponentsjs/bundles/*.js", 10 | "demo/static/**/*" 11 | ], 12 | "builds": [ 13 | { 14 | "name": "es5-unbundled", 15 | "js": { 16 | "minify": false, 17 | "compile": "es5", 18 | "transformModulesToAmd": true 19 | }, 20 | "css": { 21 | "minify": false 22 | }, 23 | "html": { 24 | "minify": false 25 | }, 26 | "bundle": false, 27 | "addServiceWorker": false, 28 | "addPushManifest": false, 29 | "preset": "es5-bundled" 30 | } 31 | ], 32 | "moduleResolution": "node", 33 | "lint": { 34 | "rules": [ 35 | "polymer-3" 36 | ] 37 | } 38 | } -------------------------------------------------------------------------------- /api/__app.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2016, Google, Inc. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | runtime: nodejs 15 | env: flex 16 | 17 | # [START configuration] 18 | endpoints_api_service: 19 | # The following values are to be replaced by information from the output of 20 | # 'gcloud endpoints services deploy openapi-appengine.yaml' command. 21 | name: YOUR_PROJECT_ID.appspot.com 22 | rollout_strategy: managed 23 | # [END configuration] 24 | -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quickdraw-data-api", 3 | "description": "An API middlelayer for Quick, Draw! Files stored in GCS", 4 | "version": "0.0.1", 5 | "private": true, 6 | "license": "Apache-2.0", 7 | "author": "Google Inc.", 8 | "repository": { 9 | "type": "git", 10 | "url": "" 11 | }, 12 | "engines": { 13 | "node": ">=4.3.2" 14 | }, 15 | "scripts": { 16 | "start": "node app.js", 17 | "lint": "repo-tools lint", 18 | "pretest": "npm run lint", 19 | "test": "repo-tools test run --cmd ava -- -T 20s --verbose test/*.test.js", 20 | "deploy": "gcloud app deploy", 21 | "deploy-spec": "gcloud endpoints services deploy openapi-appengine.yaml" 22 | }, 23 | "dependencies": { 24 | "@google-cloud/storage": "^1.7.0", 25 | "body-parser": "1.18.2", 26 | "cors": "^2.8.4", 27 | "express": "4.16.2", 28 | "pug": "^2.0.3" 29 | }, 30 | "devDependencies": { 31 | "@google-cloud/nodejs-repo-tools": "^2.3.0", 32 | "ava": "0.25.0", 33 | "proxyquire": "2.0.0", 34 | "sinon": "4.4.2", 35 | "supertest": "3.0.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows 28 | [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/). -------------------------------------------------------------------------------- /demo/static/images/play.svg: -------------------------------------------------------------------------------- 1 | Asset 2 -------------------------------------------------------------------------------- /api/test/app.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | 18 | const express = require('express'); 19 | const path = require('path'); 20 | const proxyquire = require('proxyquire').noPreserveCache(); 21 | const request = require('supertest'); 22 | const sinon = require('sinon'); 23 | const test = require('ava'); 24 | const tools = require('@google-cloud/nodejs-repo-tools'); 25 | const sampleCat = require('./cat.json'); 26 | 27 | const SAMPLE_PATH = path.join(__dirname, '../app.js'); 28 | 29 | function getSample () { 30 | const testApp = express(); 31 | sinon.stub(testApp, 'listen').callsArg(1); 32 | const expressMock = sinon.stub().returns(testApp); 33 | 34 | const app = proxyquire(SAMPLE_PATH, { 35 | express: expressMock 36 | }); 37 | return { 38 | app: app, 39 | mocks: { 40 | express: expressMock 41 | } 42 | }; 43 | } 44 | 45 | test.beforeEach(tools.stubConsole); 46 | test.afterEach.always(tools.restoreConsole); 47 | 48 | test('lookup number of apples', async t => { 49 | t.plan(2); 50 | const res = await request(getSample().app) 51 | .get('/drawing/apple/count'); 52 | 53 | t.is(res.status, 200); 54 | t.is(res.text, '139902'); 55 | }); 56 | 57 | test('lookup non-existent category', async t => { 58 | t.plan(1); 59 | const res = await request(getSample().app) 60 | .get('/drawing/frankenstein/count'); 61 | 62 | t.is(res.status, 404); 63 | }); 64 | 65 | test('test isAnimated=true query parameter for drawing data', async t => { 66 | t.plan(1); 67 | const res = await request(getSample().app) 68 | .get('/drawing/apple?isAnimated=true'); 69 | 70 | t.is(JSON.parse(res.text).drawing[0].length, 3); 71 | }); 72 | 73 | test('test isAnimated=false query parameter for drawing data', async t => { 74 | t.plan(1); 75 | const res = await request(getSample().app) 76 | .get('/drawing/apple?isAnimated=false'); 77 | 78 | t.is(JSON.parse(res.text).drawing[0].length, 2); 79 | }); 80 | 81 | test('test specific cat ID=9 data', async t => { 82 | t.plan(1); 83 | const res = await request(getSample().app) 84 | .get('/drawing/cat?format=json&isAnimated=true&id=9'); 85 | t.deepEqual(JSON.parse(res.text), sampleCat); 86 | }); 87 | -------------------------------------------------------------------------------- /api/views/index.pug: -------------------------------------------------------------------------------- 1 | html 2 | 3 | head 4 | title= title 5 | 6 | body 7 | 8 | canvas(id='canvas', width='800', height='800') 9 | 10 | script. 11 | 12 | let canvasEl = document.getElementById('canvas') 13 | let ctx = canvasEl.getContext('2d'); 14 | let strokeWidth = 4; 15 | 16 | //- get properties of image given points 17 | function getDrawingProperties(points){ 18 | let x_range = {min: 99999, max: 0}, 19 | y_range = {min: 99999, max: 0}; 20 | for(let i = 0; i < points.length; i++){ 21 | let stroke_len = points[i][0].length; 22 | for(let j = 0; j < stroke_len; j++){ 23 | let this_x = points[i][0][j], 24 | this_y = points[i][1][j]; 25 | if(this_x < x_range.min) x_range.min = this_x; 26 | else if(this_x > x_range.max) x_range.max = this_x; 27 | if(this_y < y_range.min) y_range.min = this_y; 28 | else if(this_y > y_range.max) y_range.max = this_y; 29 | } 30 | } 31 | return { 32 | x_range: x_range, 33 | y_range: y_range, 34 | width: x_range.max - x_range.min + (strokeWidth * 2), 35 | height: y_range.max - y_range.min + (strokeWidth * 2), 36 | x: x_range.min, 37 | y: y_range.min 38 | }; 39 | } 40 | 41 | function transformPoint(x, y){ 42 | x = (x - drawingProperties.x) * 1 + strokeWidth; 43 | y = (y - drawingProperties.y) * 1 + strokeWidth; 44 | return {x: x, y: y}; 45 | } 46 | 47 | 48 | //- access drawing data that's passed in from server 49 | let data = !{JSON.stringify(data).replace(/<\//g, '<\\/')} 50 | let drawingProperties = getDrawingProperties(data.drawing); 51 | 52 | canvasEl.width = drawingProperties.width; 53 | canvasEl.height = drawingProperties.height; 54 | 55 | //- set up stroke properties 56 | ctx.lineJoin = ctx.lineCap = 'round'; 57 | ctx.lineWidth = 4; 58 | ctx.strokeStyle = '#000000'; 59 | ctx.beginPath(); 60 | 61 | //- move to start position 62 | var {x, y} = transformPoint(data.drawing[0][0], data.drawing[0][1]); 63 | ctx.moveTo(x, y); 64 | //- loop through and draw points 65 | for(var i = 0; i < data.drawing.length; i++){ 66 | var len = data.drawing[i][0].length; 67 | for(var j = 0; j < len; j++){ 68 | 69 | const this_x = data.drawing[i][0][j]; 70 | const this_y = data.drawing[i][1][j]; 71 | const {x, y} = transformPoint(this_x, this_y); 72 | 73 | if(j === 0){ 74 | ctx.moveTo(x, y); 75 | } 76 | if(j > 0){ 77 | ctx.lineTo(x, y); 78 | } 79 | } 80 | ctx.stroke(); 81 | } 82 | 83 | -------------------------------------------------------------------------------- /api/__openapi-appengine.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | description: "A Data API for easy access to Quick Draw data" 4 | title: "Quick, Draw! Data API" 5 | version: "1.0" 6 | host: "YOUR_PROJECT_ID.appspot.com" 7 | basePath: "/" 8 | # [END swagger] 9 | consumes: 10 | - "application/json" 11 | produces: 12 | - "application/json" 13 | schemes: 14 | - "https" 15 | parameters: 16 | categoryParam: 17 | in: path 18 | name: category 19 | required: true 20 | type: string 21 | description: The category ID for a QuickDraw drawing 22 | isAnimatedParam: 23 | in: query 24 | name: isAnimated 25 | description: If drawing data points are to be drawn out in a sequence over time, or a static drawing 26 | required: false 27 | type: boolean 28 | formatParam: 29 | in: query 30 | name: format 31 | description: The format of the response, either in JSON or HTML (Canvas) 32 | required: false 33 | type: string 34 | enum: [json, html, canvas] 35 | idParam: 36 | in: query 37 | name: id 38 | description: The id for an individual drawing within a category (typically the number index that is also the filename) 39 | required: false 40 | type: integer 41 | paths: 42 | "/drawing/{category}/count": 43 | get: 44 | operationId: "get_category_count" 45 | parameters: 46 | - $ref: "#/parameters/categoryParam" 47 | produces: 48 | - application/json 49 | responses: 50 | '200': 51 | description: The total count of drawings within a certain category 52 | schema: 53 | $ref: '#/definitions/CategoryModel' 54 | "/drawing/{category}": 55 | get: 56 | x-google-quota: 57 | metricCosts: 58 | drawingDataRequests: 1 59 | operationId: "get_random" 60 | parameters: 61 | - $ref: "#/parameters/categoryParam" 62 | - $ref: "#/parameters/isAnimatedParam" 63 | - $ref: "#/parameters/formatParam" 64 | - $ref: "#/parameters/idParam" 65 | produces: 66 | - application/json 67 | responses: 68 | '200': 69 | description: Definition generated from Swagger Inspector 70 | schema: 71 | $ref: '#/definitions/CategoryModel' 72 | definitions: 73 | CategoryModel: 74 | properties: 75 | count: 76 | type: string 77 | # This section requires all requests to any path to require an API key. 78 | security: 79 | - api_key: [] 80 | securityDefinitions: 81 | # This section configures basic authentication with an API key. 82 | api_key: 83 | type: "apiKey" 84 | name: "key" 85 | in: "query" 86 | x-google-management: 87 | metrics: 88 | - name: "drawingDataRequests" 89 | displayName: "DrawingDataRequests" 90 | valueType: INT64 91 | metricKind: DELTA 92 | quota: 93 | limits: 94 | - name: "drawing-data-request-limit" 95 | metric: "drawingDataRequests" 96 | unit: "1/min/{project}" 97 | values: 98 | STANDARD: 120 -------------------------------------------------------------------------------- /test/quickdraw-component_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | quickdraw-component test 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /api/README.md: -------------------------------------------------------------------------------- 1 | # Quick, Draw Data API 2 | 3 | In order to self-host, it's easiest to use Google Cloud Platform. [Create a GCP project](https://console.cloud.google.com/cloud-resource-manager) if do not have one already for your projct. The following instructions assume you have your GCP Project ID handy. 4 | 5 | ## Copy the Quick, Draw! data 6 | 7 | The bucket contains 191.63 GB of doodles. Assuming multiregional storage, and at the time of writing this, your cost would be ($0.026 * 191.63) = $4.98 per month. Storage and transferring between GCS buckets may vary based on your bucket location (it's free to transfer in the same region, bucket to bucket). [You can find more details here](https://cloud.google.com/storage/pricing#network-pricing). 8 | 9 | 1. In your GCP Project, go to [Storage > Browser](https://pantheon.corp.google.com/storage/browser) and create a bucket with a unique id of your choosing (leave all other default settings) 10 | 2. Go to [Storage > Transfer](https://pantheon.corp.google.com/storage/transfer) 11 | 3. Select "Google Cloud Storage bucket" as the source, and enter ```gs://quickdraw-data-complete``` and the Cloud Storage bucket source. Press continue. 12 | 4. Enter ```gs://YOUR_BUCKET_ID``` where ```YOUR_BUCKET_ID``` is the unique id you chose in the first step. Leave all other default settings and press continue. 13 | 5. Leave the default transfer settings to run now, and press "Create". The files will now transfer over. 14 | 15 | ## Running the API locally 16 | 17 | 1. Run ```npm install``` in this directory 18 | 2. In the root of this API directory, create a ```config.json``` file replacing the values below (```GCS_BUCKET_ID``` is the bucket you created when copying the data over in the above step) : 19 | 20 | { 21 | "projectId": "YOUR_PROJECT_ID", 22 | "bucketId": "GCS_BUCKET_ID" 23 | } 24 | 25 | 26 | **NOTE:** *If you secure your bucket to certain roles, you can add your service account credential key in this config file by adding:* ```"keyFilename": "./PATH_TO_SERVICE_ACCOUNT_KEY"``` 27 | 28 | 29 | You can now run ```npm start``` and the server should run locally on your machine at ```http://localhost:8080```. You can then try sending a request using the instructions below. 30 | 31 | 32 | ## Send a request 33 | 34 | Choose your local or production server: 35 | 36 | ``` 37 | # If you're running locally, you won't need an API key. 38 | $ export ENDPOINTS_HOST=http://localhost:8080 39 | 40 | $ export ENDPOINTS_HOST=https://YOUR_PROJECT_ID.appspot.com 41 | $ export ENDPOINTS_KEY=AIza... 42 | ``` 43 | 44 | Send the request: 45 | 46 | ``` 47 | $ curl -vv -H 'Content-Type: application/json' "${ENDPOINTS_HOST}/drawing/cat/count?key=${ENDPOINTS_KEY}" 48 | ``` 49 | 50 | If you're running locally, you won't need an API key. 51 | 52 | ## Deploying the API 53 | 54 | 1. You will need to make sure you have Python 2.7.x installed as well as the [Google Cloud SDK](https://cloud.google.com/sdk/install) on your local machine. 55 | 56 | 1. Change ```__app.yaml``` to ```app.yaml``` and inside the file, update ```name: YOUR_PROJECT_ID.appspot.com``` replacing ```YOUR_PROJECT_ID``` with the ID of the project you created in GCP where the files live. 57 | 58 | 2. Change ```__openapi-appengine.yaml``` to ```openapi-appengine.yaml``` and inside the file, update ```host: "YOUR_PROJECT_ID.appspot.com"``` replacing ```YOUR_PROJECT_ID``` with the same project ID. 59 | 60 | 3. Run ```npm run deploy-spec``` to deploy your API definition file 61 | 62 | 4. Run ```npm run deploy``` to deploy your application code 63 | 64 | ## License 65 | 66 | The component & API fall under the Apache 2.0 license. 67 | 68 | This data is made available by Google, Inc. under the [Creative Commons Attribution 4.0 International license.](https://creativecommons.org/licenses/by/4.0/) -------------------------------------------------------------------------------- /demo/static/js/demo.js: -------------------------------------------------------------------------------- 1 | // set defaults 2 | var count = 100, 3 | currentCategory = 'cat', 4 | elems = []; 5 | 6 | (function(){ 7 | // reference to quick draw component 8 | var qdEl = document.querySelector('quick-draw'); 9 | 10 | // show the current index of the drawing loaded *when* it is loaded 11 | qdEl.addEventListener('drawingData', function(res){ 12 | document.getElementById('drawing-id').innerHTML = numberWithCommas(res.detail.index) + ' / ' + numberWithCommas(categoriesDict[currentCategory]); 13 | }) 14 | 15 | // populate the select drop down with all the categories 16 | var selectEl = document.getElementById('select-category'); 17 | for(var i = 0; i < categories.length; i++){ 18 | var option = document.createElement('option'); 19 | var text = document.createTextNode(categories[i].category); 20 | option.appendChild(text); 21 | option.setAttribute('value', categories[i].category); 22 | selectEl.appendChild(option); 23 | } 24 | selectEl.value = 'cat'; 25 | selectEl.addEventListener('change', onSelectCategory); 26 | 27 | document.querySelector('.refresh').addEventListener('click', function(){ 28 | qdEl.index = -1; 29 | qdEl.refresh(); 30 | }) 31 | 32 | var colors = document.querySelectorAll('.colors li'); 33 | [].forEach.call(colors, function(elem){ 34 | elem.addEventListener('click', onSelectColor); 35 | }) 36 | 37 | var timingOptions = document.querySelectorAll('.timing-options .timing-option'); 38 | [].forEach.call(timingOptions, function(elem){ 39 | elem.addEventListener('click', onSelectTiming); 40 | }) 41 | })() 42 | 43 | function onSelectTiming(e){ 44 | var timingId = e.target.dataset.id; 45 | var timingOptions = document.querySelectorAll('.timing-options .timing-option'); 46 | [].forEach.call(timingOptions, function(elem){ 47 | elem.classList.remove('selected'); 48 | if(elem.dataset.id === timingId){ 49 | elem.classList.add('selected'); 50 | } 51 | }) 52 | var html = ''; 53 | var qdEl = document.querySelector('quick-draw'); 54 | switch(timingId){ 55 | case 'one-second': 56 | html = ' time="1000" animate'; 57 | qdEl.animated = true; 58 | qdEl.time = 1000; 59 | break; 60 | 61 | case 'five-seconds': 62 | html = ' time="5000" animate'; 63 | qdEl.animated = true; 64 | qdEl.time = 5000; 65 | break; 66 | 67 | case 'original': 68 | qdEl.animated = true; 69 | html = ' animate'; 70 | qdEl.time = null; 71 | break; 72 | 73 | case 'no-animate': 74 | qdEl.animated = false; 75 | qdEl.time = null; 76 | break; 77 | } 78 | document.getElementById('timing').innerHTML = html; 79 | } 80 | 81 | function onSelectColor(e){ 82 | var newColor = e.target.dataset.color; 83 | var colors = document.querySelectorAll('.colors li'); 84 | [].forEach.call(colors, function(elem){ 85 | elem.classList.remove('selected'); 86 | if(elem.dataset.color === newColor){ 87 | elem.classList.add('selected'); 88 | } 89 | }) 90 | document.getElementById('stroke-color').innerHTML = newColor === '#000' ? '' : ` strokecolor="${newColor}"`; 91 | var qdEl = document.querySelector('quick-draw'); 92 | qdEl.strokeColor = newColor; 93 | } 94 | 95 | function onSelectCategory(e){ 96 | var selectEl = document.getElementById('select-category'); 97 | var exampleValueEl = document.getElementById('example-value'); 98 | var qdEl = document.querySelector('quick-draw'); 99 | var newCat = selectEl.value; 100 | currentCategory = newCat; 101 | exampleValueEl.innerHTML = newCat; 102 | // qdEl.setAttribute('category', newCat); 103 | qdEl.category = newCat; 104 | // qdEl.setAttribute('category', newCat); 105 | // qdEl.fetchImageData(newCat); 106 | 107 | } 108 | 109 | function numberWithCommas(x){ 110 | return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 111 | } 112 | -------------------------------------------------------------------------------- /api/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017, Google, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | 18 | const express = require('express'); 19 | const bodyParser = require('body-parser'); 20 | const categories = require('./category-count-dict.json'); 21 | const cors = require('cors'); 22 | const app = express(); 23 | 24 | app.set('case sensitive routing', true); 25 | app.set('view engine', 'pug'); 26 | app.use(bodyParser.json()); 27 | app.use(cors()); 28 | 29 | const asyncMiddleware = fn => 30 | (req, res, next) => { 31 | Promise.resolve(fn(req, res, next)) 32 | .catch(next); 33 | }; 34 | 35 | // check the config file 36 | const config = require('./config.json'); 37 | if (config && typeof config.projectId !== 'undefined') { 38 | console.log('Project ID: ' + config.projectId); 39 | } else { 40 | throw new Error('Error in config. Make sure to create a config.json file with { "projectId": "YOUR_PROJECT_ID", "bucketId": "YOUR_BUCKET_ID" }'); 41 | } 42 | 43 | // Imports the Google Cloud client library 44 | const Storage = require('@google-cloud/storage'); 45 | const storage = new Storage(config); 46 | 47 | /* Note: "/drawing" in path because requests on the root path are currently rejected by the Extensible Service Proxy (ESP) 48 | 49 | See more here: 50 | https://cloud.google.com/endpoints/docs/openapi/openapi-limitations */ 51 | 52 | /** 53 | * '/drawing/:category/count' 54 | * Method: GET 55 | * Description: Get the number of drawings for a provided category 56 | * @param: {String} category [the name of the Quick, Draw! category (i.e. "cat")] 57 | */ 58 | app.get('/drawing/:category/count', asyncMiddleware(async (req, res, next) => { 59 | let category = req.params.category; 60 | if (typeof categories[category] !== 'undefined') { 61 | res.status(200).json(categories[category]).end(); 62 | } else { 63 | res.sendStatus(404); 64 | } 65 | })); 66 | 67 | /** 68 | * '/drawing/:category' 69 | * Method: GET 70 | * Description: Gets the data for a random drawing 71 | * @param: {String} category [the name of the Quick, Draw! category (i.e. "cat")] 72 | * @queryParam: {String} id [the numerical id of a drawing] 73 | * @queryParam: {Boolean} isAnimated [if the data includes time-based information for animating] 74 | * @queryParam: {String} format ["json" by default, can be "html" to return a rendering of the drawing on canvas] 75 | */ 76 | app.get('/drawing/:category', asyncMiddleware(async (req, res, next) => { 77 | const category = req.params.category; 78 | const isAnimated = req.query['isAnimated'] === 'true'; 79 | const bucketDirectory = (isAnimated) ? 'drawings_raw_complete' : 'drawings_simplified_complete'; 80 | let format = req.query['format'] || 'json'; 81 | let id = req.query['id']; 82 | // if isn't animated, grab from bucket with simplified data (without time data) 83 | 84 | if (typeof categories[category] !== 'undefined') { 85 | // parse the ID query parameter 86 | // if not provided, give random number 87 | if (id && typeof id !== 'undefined') { 88 | id = parseInt(id, 10); 89 | if (isNaN(id)) { 90 | res.sendStatus(404); 91 | } 92 | } else { 93 | // ID not passed, assign random index 94 | let totalCount = categories[category]; 95 | id = Math.floor(Math.random() * totalCount); 96 | } 97 | 98 | // download the file from GCS 99 | storage 100 | .bucket(config.bucketId) 101 | .file(`${bucketDirectory}/${category}/${id}.json`) 102 | .download() 103 | .then(results => { 104 | let obj = JSON.parse(results[0].toString()); 105 | obj.index = id; 106 | obj.totalCategoryCount = categories[category]; 107 | if (format.toLowerCase() === 'canvas' || format.toLowerCase() === 'html') { 108 | // send canvas drawing 109 | res.render('index', { title: 'Quick, Draw! ' + category, data: obj }); 110 | } else { 111 | res.json(obj); 112 | } 113 | }) 114 | .catch(err => { 115 | res.send(err); 116 | }); 117 | } else { 118 | res.sendStatus(404); 119 | } 120 | })); 121 | 122 | if (module === require.main) { 123 | // [START listen] 124 | const PORT = process.env.PORT || 8080; 125 | app.listen(PORT, () => { 126 | console.log(`App listening on port ${PORT}`); 127 | console.log('Press Ctrl+C to quit.'); 128 | }); 129 | // [END listen] 130 | } 131 | 132 | module.exports = app; 133 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Quick, Draw! Component & API 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 64 | 65 |
66 |
67 |
68 |
69 | 70 |

How does it work?

71 |

With just one line, you can add one of over 46 million Quick, Draw! doodles to your project.

72 |
73 | Draw a: Refresh 74 |
75 | 76 |
77 | Original speed 78 | 1 second 79 | 5 seconds 80 | Don't animate 81 |
82 | 83 |
84 |
    85 |
  • 86 |
  • 87 |
  • 88 |
89 |
90 | 91 |
92 | <quick-draw category="cat" animate></quick-draw> 93 |
94 | 95 | 96 | 97 |

#

98 |
99 |
100 |
101 |
102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /api/test/cat.json: -------------------------------------------------------------------------------- 1 | { 2 | "word": "cat", 3 | "countrycode": "US", 4 | "timestamp": "2017-03-03 17:58:50.53529 UTC", 5 | "recognized": true, 6 | "key_id": "5321656484495360", 7 | "drawing": [ 8 | [ 9 | [ 10 | 562, 11 | 553, 12 | 545, 13 | 537, 14 | 524, 15 | 507, 16 | 489, 17 | 469, 18 | 446, 19 | 428, 20 | 411, 21 | 395, 22 | 383, 23 | 375, 24 | 369, 25 | 366, 26 | 365, 27 | 365, 28 | 366, 29 | 374, 30 | 384, 31 | 397, 32 | 413, 33 | 433, 34 | 456, 35 | 482, 36 | 511, 37 | 541, 38 | 570, 39 | 599, 40 | 626, 41 | 646, 42 | 664, 43 | 677, 44 | 687, 45 | 694, 46 | 699, 47 | 699, 48 | 698, 49 | 691, 50 | 683, 51 | 673, 52 | 662, 53 | 650, 54 | 640, 55 | 629, 56 | 620, 57 | 610, 58 | 601, 59 | 592, 60 | 584, 61 | 577, 62 | 572, 63 | 572 64 | ], 65 | [ 66 | 131, 67 | 123, 68 | 120, 69 | 118, 70 | 118, 71 | 121, 72 | 130, 73 | 140, 74 | 153, 75 | 168, 76 | 185, 77 | 207, 78 | 228, 79 | 250, 80 | 272, 81 | 296, 82 | 321, 83 | 346, 84 | 371, 85 | 394, 86 | 416, 87 | 433, 88 | 447, 89 | 457, 90 | 464, 91 | 469, 92 | 469, 93 | 467, 94 | 456, 95 | 441, 96 | 422, 97 | 403, 98 | 381, 99 | 358, 100 | 337, 101 | 312, 102 | 283, 103 | 255, 104 | 233, 105 | 213, 106 | 197, 107 | 186, 108 | 176, 109 | 169, 110 | 162, 111 | 154, 112 | 147, 113 | 141, 114 | 136, 115 | 133, 116 | 131, 117 | 131, 118 | 131, 119 | 131 120 | ], 121 | [ 122 | 0, 123 | 9, 124 | 23, 125 | 39, 126 | 56, 127 | 72, 128 | 90, 129 | 107, 130 | 123, 131 | 139, 132 | 158, 133 | 172, 134 | 190, 135 | 207, 136 | 224, 137 | 243, 138 | 255, 139 | 273, 140 | 289, 141 | 306, 142 | 323, 143 | 339, 144 | 357, 145 | 373, 146 | 389, 147 | 406, 148 | 423, 149 | 440, 150 | 456, 151 | 474, 152 | 489, 153 | 506, 154 | 523, 155 | 539, 156 | 555, 157 | 573, 158 | 589, 159 | 607, 160 | 623, 161 | 640, 162 | 657, 163 | 672, 164 | 690, 165 | 706, 166 | 723, 167 | 739, 168 | 756, 169 | 773, 170 | 789, 171 | 806, 172 | 824, 173 | 840, 174 | 857, 175 | 859 176 | ] 177 | ], 178 | [ 179 | [ 180 | 640, 181 | 650, 182 | 665, 183 | 684, 184 | 707, 185 | 729, 186 | 749, 187 | 769, 188 | 786, 189 | 797, 190 | 803, 191 | 806, 192 | 806, 193 | 800, 194 | 791, 195 | 780, 196 | 768, 197 | 753, 198 | 739, 199 | 727, 200 | 718, 201 | 713, 202 | 711, 203 | 711 204 | ], 205 | [ 206 | 139, 207 | 130, 208 | 119, 209 | 107, 210 | 92, 211 | 79, 212 | 67, 213 | 55, 214 | 45, 215 | 40, 216 | 38, 217 | 45, 218 | 55, 219 | 70, 220 | 88, 221 | 109, 222 | 132, 223 | 155, 224 | 178, 225 | 197, 226 | 211, 227 | 219, 228 | 224, 229 | 224 230 | ], 231 | [ 232 | 1251, 233 | 1264, 234 | 1273, 235 | 1289, 236 | 1307, 237 | 1323, 238 | 1340, 239 | 1357, 240 | 1373, 241 | 1390, 242 | 1406, 243 | 1439, 244 | 1455, 245 | 1474, 246 | 1489, 247 | 1505, 248 | 1523, 249 | 1539, 250 | 1556, 251 | 1574, 252 | 1589, 253 | 1605, 254 | 1623, 255 | 1632 256 | ] 257 | ], 258 | [ 259 | [ 260 | 450, 261 | 442, 262 | 437, 263 | 431, 264 | 422, 265 | 411, 266 | 396, 267 | 379, 268 | 362, 269 | 344, 270 | 331, 271 | 319, 272 | 312, 273 | 307, 274 | 307, 275 | 307, 276 | 312, 277 | 319, 278 | 327, 279 | 336, 280 | 348, 281 | 359, 282 | 370, 283 | 370 284 | ], 285 | [ 286 | 148, 287 | 139, 288 | 135, 289 | 129, 290 | 121, 291 | 112, 292 | 104, 293 | 97, 294 | 93, 295 | 90, 296 | 88, 297 | 86, 298 | 84, 299 | 84, 300 | 89, 301 | 101, 302 | 117, 303 | 137, 304 | 159, 305 | 183, 306 | 202, 307 | 217, 308 | 229, 309 | 229 310 | ], 311 | [ 312 | 2105, 313 | 2123, 314 | 2139, 315 | 2156, 316 | 2172, 317 | 2188, 318 | 2206, 319 | 2224, 320 | 2239, 321 | 2256, 322 | 2274, 323 | 2289, 324 | 2306, 325 | 2339, 326 | 2356, 327 | 2372, 328 | 2390, 329 | 2405, 330 | 2422, 331 | 2439, 332 | 2456, 333 | 2473, 334 | 2490, 335 | 2497 336 | ] 337 | ], 338 | [ 339 | [ 340 | 509, 341 | 510, 342 | 509, 343 | 507, 344 | 505, 345 | 508, 346 | 511, 347 | 512, 348 | 520, 349 | 526, 350 | 532, 351 | 537, 352 | 541, 353 | 533, 354 | 524, 355 | 513, 356 | 501, 357 | 493, 358 | 498, 359 | 498 360 | ], 361 | [ 362 | 307, 363 | 313, 364 | 320, 365 | 326, 366 | 331, 367 | 317, 368 | 306, 369 | 298, 370 | 301, 371 | 313, 372 | 326, 373 | 337, 374 | 345, 375 | 347, 376 | 345, 377 | 344, 378 | 343, 379 | 341, 380 | 337, 381 | 337 382 | ], 383 | [ 384 | 3094, 385 | 3175, 386 | 3192, 387 | 3207, 388 | 3249, 389 | 3289, 390 | 3306, 391 | 3323, 392 | 3422, 393 | 3439, 394 | 3455, 395 | 3472, 396 | 3489, 397 | 3572, 398 | 3589, 399 | 3605, 400 | 3622, 401 | 3639, 402 | 3705, 403 | 3707 404 | ] 405 | ], 406 | [ 407 | [ 408 | 596, 409 | 586, 410 | 593, 411 | 605, 412 | 618, 413 | 629, 414 | 638, 415 | 646, 416 | 651, 417 | 655, 418 | 656, 419 | 655, 420 | 644, 421 | 626, 422 | 605, 423 | 587, 424 | 576, 425 | 571, 426 | 571, 427 | 582, 428 | 589, 429 | 589 430 | ], 431 | [ 432 | 251, 433 | 245, 434 | 231, 435 | 220, 436 | 213, 437 | 212, 438 | 214, 439 | 224, 440 | 238, 441 | 251, 442 | 262, 443 | 272, 444 | 278, 445 | 281, 446 | 281, 447 | 279, 448 | 273, 449 | 266, 450 | 257, 451 | 247, 452 | 243, 453 | 243 454 | ], 455 | [ 456 | 4297, 457 | 4341, 458 | 4372, 459 | 4389, 460 | 4406, 461 | 4424, 462 | 4441, 463 | 4456, 464 | 4472, 465 | 4489, 466 | 4507, 467 | 4523, 468 | 4541, 469 | 4556, 470 | 4573, 471 | 4589, 472 | 4607, 473 | 4622, 474 | 4641, 475 | 4656, 476 | 4673, 477 | 4675 478 | ] 479 | ], 480 | [ 481 | [ 482 | 424, 483 | 431, 484 | 446, 485 | 460, 486 | 475, 487 | 486, 488 | 494, 489 | 498, 490 | 498, 491 | 493, 492 | 482, 493 | 467, 494 | 449, 495 | 433, 496 | 419, 497 | 410, 498 | 406, 499 | 406, 500 | 410, 501 | 423, 502 | 432, 503 | 432 504 | ], 505 | [ 506 | 233, 507 | 220, 508 | 213, 509 | 211, 510 | 211, 511 | 214, 512 | 221, 513 | 231, 514 | 241, 515 | 252, 516 | 262, 517 | 271, 518 | 278, 519 | 281, 520 | 281, 521 | 275, 522 | 260, 523 | 243, 524 | 228, 525 | 218, 526 | 214, 527 | 214 528 | ], 529 | [ 530 | 5024, 531 | 5033, 532 | 5057, 533 | 5073, 534 | 5089, 535 | 5106, 536 | 5122, 537 | 5139, 538 | 5156, 539 | 5173, 540 | 5190, 541 | 5205, 542 | 5222, 543 | 5239, 544 | 5256, 545 | 5273, 546 | 5289, 547 | 5306, 548 | 5323, 549 | 5339, 550 | 5357, 551 | 5359 552 | ] 553 | ], 554 | [ 555 | [ 556 | 499, 557 | 503, 558 | 503, 559 | 503, 560 | 498, 561 | 490, 562 | 480, 563 | 469, 564 | 460, 565 | 454, 566 | 453, 567 | 453 568 | ], 569 | [ 570 | 377, 571 | 382, 572 | 395, 573 | 412, 574 | 427, 575 | 435, 576 | 436, 577 | 435, 578 | 430, 579 | 424, 580 | 415, 581 | 415 582 | ], 583 | [ 584 | 6127, 585 | 6186, 586 | 6191, 587 | 6216, 588 | 6228, 589 | 6242, 590 | 6256, 591 | 6273, 592 | 6290, 593 | 6307, 594 | 6322, 595 | 6332 596 | ] 597 | ], 598 | [ 599 | [ 600 | 512, 601 | 517, 602 | 527, 603 | 539, 604 | 552, 605 | 558, 606 | 558 607 | ], 608 | [ 609 | 384, 610 | 397, 611 | 410, 612 | 421, 613 | 427, 614 | 427, 615 | 427 616 | ], 617 | [ 618 | 6451, 619 | 6522, 620 | 6539, 621 | 6555, 622 | 6572, 623 | 6589, 624 | 6590 625 | ] 626 | ], 627 | [ 628 | [ 629 | 586, 630 | 588, 631 | 593, 632 | 618, 633 | 652, 634 | 689, 635 | 724 636 | ], 637 | [ 638 | 333, 639 | 328, 640 | 326, 641 | 317, 642 | 304, 643 | 294, 644 | 286 645 | ], 646 | [ 647 | 6783, 648 | 6814, 649 | 6826, 650 | 6839, 651 | 6855, 652 | 6872, 653 | 6889 654 | ] 655 | ] 656 | ], 657 | "index": 9, 658 | "totalCategoryCount": 103031 659 | } -------------------------------------------------------------------------------- /demo/static/style.css: -------------------------------------------------------------------------------- 1 | #frontpage { 2 | max-width: 896px; 3 | margin: 0 auto; } 4 | #frontpage #intro { 5 | text-align: center; 6 | padding: 0 25px; 7 | max-width: 990px; 8 | margin: 0 auto; 9 | padding-top: 50px; 10 | box-sizing: border-box; } 11 | #frontpage #intro h1 { 12 | font-family: Handwrite1; 13 | font-size: 2.5em; 14 | letter-spacing: 2px; } 15 | #frontpage #intro #introduction { 16 | line-height: 1.8em; } 17 | #frontpage #intro #introduction a { 18 | color: #FFD139; } 19 | #frontpage #intro #intro-inner { 20 | max-width: 840px; 21 | display: inline-block; } 22 | #frontpage #intro #select_drawing { 23 | display: block; 24 | margin-top: 30px; 25 | font-family: Handwrite2; 26 | font-size: 1.3em; } 27 | #frontpage #intro #select_drawing_arrow { 28 | margin-top: 10px; 29 | margin-bottom: 10px; 30 | height: 30px; } 31 | #frontpage #canvas_wrapper { 32 | z-index: -50; 33 | position: absolute; 34 | top: 0; 35 | bottom: 0px; 36 | left: 0px; 37 | right: 0px; 38 | text-align: center; } 39 | #frontpage #scroller { 40 | z-index: 1; 41 | position: absolute; 42 | top: 0; 43 | bottom: 0px; 44 | left: 0px; 45 | right: 0px; 46 | text-align: center; 47 | overflow-y: auto; 48 | overflow-x: hidden; } 49 | #frontpage #scroller-fake-height { 50 | max-width: 990px; 51 | margin: 0 auto; 52 | position: relative; } 53 | @media only screen and (min-width: 720px) { 54 | #frontpage .tooltip-box:hover { 55 | background-color: rgba(255, 255, 255, 0.4) !important; } 56 | #frontpage .tooltip-box:hover .tooltip { 57 | display: block !important; } } 58 | 59 | 60 | #hamburger, #mobile-nav { 61 | display: none; } 62 | 63 | #share { 64 | display: flex; 65 | align-items: center; 66 | justify-content: center; 67 | height: 100%; } 68 | #share a { 69 | text-decoration: none; } 70 | #share div { 71 | text-align: center; } 72 | #share h1 { 73 | font-family: Handwrite2; 74 | font-weight: normal; } 75 | 76 | * { 77 | -webkit-box-sizing: border-box; 78 | /* Safari/Chrome, other WebKit */ 79 | -moz-box-sizing: border-box; 80 | /* Firefox, other Gecko */ 81 | box-sizing: border-box; 82 | /* Opera/IE 8+ */ } 83 | 84 | body { 85 | font-family: 'Roboto Mono', sans-serif; 86 | padding: 0; 87 | margin: 0; 88 | overflow: hidden; } 89 | 90 | h1 { 91 | font-weight: 600; } 92 | 93 | h2 { 94 | font-weight: 400; } 95 | 96 | a { 97 | color: #444; } 98 | 99 | .quick-draw-container { 100 | max-width: 100%; 101 | margin: 50px auto; } 102 | 103 | .section-header { 104 | font-family: Handwrite1; 105 | font-size: 3.0em; 106 | font-weight: 400; 107 | margin: 20px 0; } 108 | .section-header.sub { 109 | font-size: 2.0em; } 110 | 111 | .desc { 112 | max-width: 550px; 113 | margin: 0 auto; 114 | line-height: 1.6em; 115 | color: #666; } 116 | 117 | .category-selector { 118 | margin: 20px 0; 119 | color: black; } 120 | 121 | .code-example { 122 | margin: 18px auto; 123 | position: relative; } 124 | .code-example code { 125 | font-size: 1.4em; 126 | padding: 30px; 127 | border-radius: 10px; 128 | line-height: 30px; } 129 | 130 | .timing-options { 131 | margin: 18px auto; } 132 | .timing-options .timing-option { 133 | padding: 9px 15px; 134 | border: 1px solid #666; 135 | border-radius: 10px; 136 | text-decoration: none; 137 | display: inline-block; 138 | margin: 5px 0; } 139 | .timing-options .timing-option.selected { 140 | background: #FFD139; } 141 | 142 | .options .colors { 143 | margin: 10px 0; 144 | padding: 0; 145 | display: inline-block; 146 | position: relative; 147 | height: 50px; } 148 | .options .colors li { 149 | margin: 0; 150 | padding: 0; 151 | display: inline-block; 152 | position: relative; 153 | width: 50px; 154 | margin-right: 15px; 155 | height: 50px; 156 | cursor: pointer; 157 | border-radius: 50%; } 158 | .options .colors li .bg { 159 | position: absolute; 160 | width: 50px; 161 | height: 50px; 162 | background: red; 163 | border-radius: 50%; 164 | pointer-events: none; } 165 | .options .colors li.selected .bg { 166 | border: 6px solid #CCC; } 167 | .options .colors li:nth-child(1) .bg { 168 | background: #000; } 169 | .options .colors li:nth-child(2) .bg { 170 | background: #ffd139; } 171 | .options .colors li:nth-child(3) .bg { 172 | background: #ee7aaa; } 173 | 174 | 175 | quick-draw { 176 | display: inline-block; 177 | margin: 10px; } 178 | 179 | .refresh { 180 | position: relative; 181 | top: 4px; 182 | left: 22px; 183 | opacity: 0.5; } 184 | .refresh:hover { 185 | opacity: 1; } 186 | .refresh img { 187 | transform: scale(1.2); } 188 | 189 | .footer { 190 | margin-top: 100px; 191 | position: fixed; 192 | bottom: 30px; 193 | width: 100%; } 194 | 195 | @font-face { 196 | font-family: Handwrite1; 197 | src: url("font/handwritten01.otf") format("opentype"); } 198 | 199 | @font-face { 200 | font-family: Handwrite2; 201 | src: url("font/handwritten02.otf") format("opentype"); } 202 | 203 | @font-face { 204 | font-family: Handwrite3; 205 | src: url("font/handwritten03.otf") format("opentype"); } 206 | 207 | #header { 208 | height: 60px; 209 | background-color: #FFD139; } 210 | #header.header-shadow { 211 | box-shadow: 0px 8px rgba(0, 0, 0, 0.1); } 212 | #header .header-escl { 213 | font-family: Handwrite2; 214 | padding-left: 3px; 215 | font-size: 1.15em; 216 | line-height: 0; 217 | display: inline-block; 218 | transform: translateY(4px); } 219 | #header img { 220 | padding: 20px; 221 | padding-left: 30px; 222 | padding-right: 0px; 223 | height: 22px; } 224 | #header h1 { 225 | font-family: Handwrite1; 226 | margin: 0; 227 | padding: 20px; 228 | font-size: 28px; 229 | display: inline-block; 230 | position: absolute; 231 | top: 0; 232 | letter-spacing: 2px; } 233 | #header #rightnav { 234 | position: absolute; 235 | right: 20px; 236 | top: 20px; } 237 | #header #rightnav a { 238 | padding: 0 1vw; 239 | opacity: 0.6; } 240 | #header #rightnav a img { 241 | display: inline-block; 242 | height: 15px; 243 | padding: 0; 244 | margin: 0; 245 | top: 2px; 246 | position: relative; } 247 | #header #rightnav a:hover { 248 | opacity: 0.8; } 249 | #header a { 250 | text-decoration: none; } 251 | 252 | #content { 253 | padding: 0 0 0 0; 254 | position: absolute; 255 | top: 60px; 256 | bottom: 0px; 257 | left: 0px; 258 | right: 0px; } 259 | 260 | #header h1.logo { 261 | font-size: 1.5em; 262 | color: #000; 263 | letter-spacing: 1px; } 264 | 265 | select { 266 | text-rendering: auto; 267 | color: initial; 268 | letter-spacing: normal; 269 | word-spacing: normal; 270 | text-transform: none; 271 | text-indent: 0px; 272 | text-shadow: none; 273 | display: inline-block; 274 | text-align: start; 275 | margin: 0em; 276 | font: 400 11px system-ui; 277 | -webkit-appearance: menulist; 278 | box-sizing: border-box; 279 | align-items: center; 280 | white-space: pre; 281 | -webkit-rtl-ordering: logical; 282 | color: black; 283 | background-color: white; 284 | cursor: default; 285 | border-width: 1px; 286 | border-style: solid; 287 | border-color: initial; 288 | border-image: initial; 289 | overflow: visible !important; 290 | border: 0px; 291 | background: transparent; 292 | font-family: 'Roboto Mono'; 293 | font-size: 18px; 294 | text-decoration: underline; } 295 | -------------------------------------------------------------------------------- /api/category-count-dict.json: -------------------------------------------------------------------------------- 1 | { 2 | "aircraft carrier": 116504, 3 | "airplane": 151623, 4 | "alarm clock": 123399, 5 | "ambulance": 148004, 6 | "angel": 149736, 7 | "animal migration": 137847, 8 | "ant": 124612, 9 | "anvil": 126231, 10 | "apple": 144722, 11 | "arm": 120951, 12 | "asparagus": 168102, 13 | "axe": 124122, 14 | "backpack": 125801, 15 | "banana": 307936, 16 | "bandage": 147614, 17 | "barn": 151139, 18 | "baseball": 135375, 19 | "baseball bat": 123809, 20 | "basket": 118458, 21 | "basketball": 133793, 22 | "bat": 118114, 23 | "bathtub": 174336, 24 | "beach": 124938, 25 | "bear": 134762, 26 | "beard": 165202, 27 | "bed": 113862, 28 | "bee": 120890, 29 | "belt": 191119, 30 | "bench": 128695, 31 | "bicycle": 126527, 32 | "binoculars": 124190, 33 | "bird": 133572, 34 | "birthday cake": 144982, 35 | "blackberry": 128153, 36 | "blueberry": 127878, 37 | "book": 119364, 38 | "boomerang": 142682, 39 | "bottlecap": 153207, 40 | "bowtie": 130283, 41 | "bracelet": 119416, 42 | "brain": 143033, 43 | "bread": 120570, 44 | "bridge": 133010, 45 | "broccoli": 132826, 46 | "broom": 116927, 47 | "bucket": 124064, 48 | "bulldozer": 187409, 49 | "bus": 166208, 50 | "bush": 120520, 51 | "butterfly": 117999, 52 | "cactus": 131676, 53 | "cake": 124905, 54 | "calculator": 128375, 55 | "calendar": 321981, 56 | "camel": 121399, 57 | "camera": 128772, 58 | "camouflage": 172710, 59 | "campfire": 133395, 60 | "candle": 141545, 61 | "cannon": 141394, 62 | "canoe": 123767, 63 | "car": 182764, 64 | "carrot": 132459, 65 | "castle": 122534, 66 | "cat": 123202, 67 | "ceiling fan": 115413, 68 | "cello": 149725, 69 | "cell phone": 121130, 70 | "chair": 222706, 71 | "chandelier": 167502, 72 | "church": 164225, 73 | "circle": 122876, 74 | "clarinet": 126214, 75 | "clock": 120536, 76 | "cloud": 120265, 77 | "coffee cup": 183432, 78 | "compass": 127609, 79 | "computer": 123885, 80 | "cookie": 131353, 81 | "cooler": 271444, 82 | "couch": 119662, 83 | "cow": 123083, 84 | "crab": 126930, 85 | "crayon": 129953, 86 | "crocodile": 127932, 87 | "crown": 134089, 88 | "cruise ship": 123410, 89 | "cup": 130721, 90 | "diamond": 131587, 91 | "dishwasher": 169759, 92 | "diving board": 290239, 93 | "dog": 152159, 94 | "dolphin": 121613, 95 | "donut": 140751, 96 | "door": 120230, 97 | "dragon": 124362, 98 | "dresser": 123395, 99 | "drill": 136786, 100 | "drums": 137299, 101 | "duck": 135480, 102 | "dumbbell": 157975, 103 | "ear": 122897, 104 | "elbow": 126253, 105 | "elephant": 126969, 106 | "envelope": 134863, 107 | "eraser": 118339, 108 | "eye": 125888, 109 | "eyeglasses": 225762, 110 | "face": 161666, 111 | "fan": 136158, 112 | "feather": 119910, 113 | "fence": 129426, 114 | "finger": 167957, 115 | "fire hydrant": 137242, 116 | "fireplace": 155570, 117 | "firetruck": 220695, 118 | "fish": 134150, 119 | "flamingo": 124569, 120 | "flashlight": 239763, 121 | "flip flops": 121518, 122 | "floor lamp": 166355, 123 | "flower": 144818, 124 | "flying saucer": 151966, 125 | "foot": 203086, 126 | "fork": 126077, 127 | "frog": 159047, 128 | "frying pan": 123824, 129 | "garden": 158527, 130 | "garden hose": 121843, 131 | "giraffe": 127182, 132 | "goatee": 190002, 133 | "golf club": 194843, 134 | "grapes": 155305, 135 | "grass": 123071, 136 | "guitar": 120451, 137 | "hamburger": 129672, 138 | "hammer": 119012, 139 | "hand": 291773, 140 | "harp": 285403, 141 | "hat": 222610, 142 | "headphones": 118906, 143 | "hedgehog": 120527, 144 | "helicopter": 159938, 145 | "helmet": 121899, 146 | "hexagon": 142435, 147 | "hockey puck": 203301, 148 | "hockey stick": 130110, 149 | "horse": 178286, 150 | "hospital": 167448, 151 | "hot air balloon": 126350, 152 | "hot dog": 181999, 153 | "hot tub": 120279, 154 | "hourglass": 135957, 155 | "house": 135420, 156 | "house plant": 122996, 157 | "hurricane": 136245, 158 | "ice cream": 123133, 159 | "jacket": 214124, 160 | "jail": 120131, 161 | "kangaroo": 174470, 162 | "key": 160965, 163 | "keyboard": 187766, 164 | "knee": 267540, 165 | "knife": 172656, 166 | "ladder": 125389, 167 | "lantern": 149912, 168 | "laptop": 261501, 169 | "leaf": 125571, 170 | "leg": 116804, 171 | "light bulb": 120879, 172 | "lighter": 121260, 173 | "lighthouse": 160903, 174 | "lightning": 151560, 175 | "line": 143549, 176 | "lion": 120949, 177 | "lipstick": 127623, 178 | "lobster": 140175, 179 | "lollipop": 128849, 180 | "mailbox": 130053, 181 | "map": 120629, 182 | "marker": 319136, 183 | "matches": 143969, 184 | "megaphone": 137334, 185 | "mermaid": 180304, 186 | "microphone": 120570, 187 | "microwave": 130533, 188 | "monkey": 127633, 189 | "moon": 121661, 190 | "mosquito": 123029, 191 | "motorbike": 169931, 192 | "mountain": 128540, 193 | "mouse": 178826, 194 | "moustache": 179924, 195 | "mouth": 134135, 196 | "mug": 152918, 197 | "mushroom": 142167, 198 | "nail": 158593, 199 | "necklace": 120580, 200 | "nose": 197573, 201 | "ocean": 131493, 202 | "octagon": 159474, 203 | "octopus": 150152, 204 | "onion": 132297, 205 | "oven": 206910, 206 | "owl": 169632, 207 | "paintbrush": 187002, 208 | "paint can": 123446, 209 | "palm tree": 121959, 210 | "panda": 113613, 211 | "pants": 144264, 212 | "paper clip": 127129, 213 | "parachute": 127319, 214 | "parrot": 185530, 215 | "passport": 150265, 216 | "peanut": 126626, 217 | "pear": 116904, 218 | "peas": 161656, 219 | "pencil": 122001, 220 | "penguin": 253791, 221 | "piano": 116870, 222 | "pickup truck": 130740, 223 | "picture frame": 122371, 224 | "pig": 186770, 225 | "pillow": 118753, 226 | "pineapple": 125071, 227 | "pizza": 130371, 228 | "pliers": 172549, 229 | "police car": 130024, 230 | "pond": 121620, 231 | "pool": 133439, 232 | "popsicle": 126707, 233 | "postcard": 125706, 234 | "potato": 329204, 235 | "power outlet": 169462, 236 | "purse": 123320, 237 | "rabbit": 155288, 238 | "raccoon": 119588, 239 | "radio": 135728, 240 | "rain": 134680, 241 | "rainbow": 126845, 242 | "rake": 154639, 243 | "remote control": 119644, 244 | "rhinoceros": 188484, 245 | "rifle": 172444, 246 | "river": 133271, 247 | "roller coaster": 143570, 248 | "rollerskates": 119772, 249 | "sailboat": 136506, 250 | "sandwich": 131732, 251 | "saw": 121256, 252 | "saxophone": 118107, 253 | "school bus": 122041, 254 | "scissors": 123390, 255 | "scorpion": 165689, 256 | "screwdriver": 116313, 257 | "sea turtle": 119876, 258 | "see saw": 131936, 259 | "shark": 126050, 260 | "sheep": 126121, 261 | "shoe": 120231, 262 | "shorts": 124970, 263 | "shovel": 117194, 264 | "sink": 208410, 265 | "skateboard": 128733, 266 | "skull": 126174, 267 | "skyscraper": 183709, 268 | "sleeping bag": 119691, 269 | "smiley face": 124386, 270 | "snail": 133757, 271 | "snake": 122273, 272 | "snorkel": 154533, 273 | "snowflake": 116685, 274 | "snowman": 340029, 275 | "soccer ball": 125349, 276 | "sock": 205715, 277 | "speedboat": 188580, 278 | "spider": 209447, 279 | "spoon": 125028, 280 | "spreadsheet": 170200, 281 | "square": 125145, 282 | "squiggle": 118441, 283 | "squirrel": 156883, 284 | "stairs": 128981, 285 | "star": 137619, 286 | "steak": 122042, 287 | "stereo": 122444, 288 | "stethoscope": 153794, 289 | "stitches": 125192, 290 | "stop sign": 119814, 291 | "stove": 116535, 292 | "strawberry": 122301, 293 | "streetlight": 123280, 294 | "string bean": 119083, 295 | "submarine": 124362, 296 | "suitcase": 126442, 297 | "sun": 133781, 298 | "swan": 152088, 299 | "sweater": 120184, 300 | "swing set": 119357, 301 | "sword": 123802, 302 | "syringe": 135823, 303 | "table": 128021, 304 | "teapot": 126804, 305 | "teddy-bear": 179568, 306 | "telephone": 127885, 307 | "television": 123137, 308 | "tennis racquet": 231151, 309 | "tent": 131527, 310 | "The Eiffel Tower": 134801, 311 | "The Great Wall of China": 193015, 312 | "The Mona Lisa": 121383, 313 | "tiger": 121067, 314 | "toaster": 122434, 315 | "toe": 153652, 316 | "toilet": 129888, 317 | "tooth": 125064, 318 | "toothbrush": 124828, 319 | "toothpaste": 131037, 320 | "tornado": 143271, 321 | "tractor": 116677, 322 | "traffic light": 125321, 323 | "train": 127948, 324 | "tree": 144721, 325 | "triangle": 123170, 326 | "trombone": 184759, 327 | "truck": 131354, 328 | "trumpet": 169547, 329 | "t-shirt": 125233, 330 | "umbrella": 124084, 331 | "underwear": 124548, 332 | "van": 165909, 333 | "vase": 126475, 334 | "violin": 217260, 335 | "washing machine": 120851, 336 | "watermelon": 132939, 337 | "waterslide": 185364, 338 | "whale": 116502, 339 | "wheel": 136659, 340 | "windmill": 120644, 341 | "wine bottle": 126373, 342 | "wine glass": 132302, 343 | "wristwatch": 162645, 344 | "yoga": 280442, 345 | "zebra": 144608, 346 | "zigzag": 120072 347 | } -------------------------------------------------------------------------------- /demo/static/js/categories-count-dict.js: -------------------------------------------------------------------------------- 1 | window.categoriesDict = { 2 | "aircraft carrier": 116504, 3 | "airplane": 151623, 4 | "alarm clock": 123399, 5 | "ambulance": 148004, 6 | "angel": 149736, 7 | "animal migration": 137847, 8 | "ant": 124612, 9 | "anvil": 126231, 10 | "apple": 144722, 11 | "arm": 120951, 12 | "asparagus": 168102, 13 | "axe": 124122, 14 | "backpack": 125801, 15 | "banana": 307936, 16 | "bandage": 147614, 17 | "barn": 151139, 18 | "baseball": 135375, 19 | "baseball bat": 123809, 20 | "basket": 118458, 21 | "basketball": 133793, 22 | "bat": 118114, 23 | "bathtub": 174336, 24 | "beach": 124938, 25 | "bear": 134762, 26 | "beard": 165202, 27 | "bed": 113862, 28 | "bee": 120890, 29 | "belt": 191119, 30 | "bench": 128695, 31 | "bicycle": 126527, 32 | "binoculars": 124190, 33 | "bird": 133572, 34 | "birthday cake": 144982, 35 | "blackberry": 128153, 36 | "blueberry": 127878, 37 | "book": 119364, 38 | "boomerang": 142682, 39 | "bottlecap": 153207, 40 | "bowtie": 130283, 41 | "bracelet": 119416, 42 | "brain": 143033, 43 | "bread": 120570, 44 | "bridge": 133010, 45 | "broccoli": 132826, 46 | "broom": 116927, 47 | "bucket": 124064, 48 | "bulldozer": 187409, 49 | "bus": 166208, 50 | "bush": 120520, 51 | "butterfly": 117999, 52 | "cactus": 131676, 53 | "cake": 124905, 54 | "calculator": 128375, 55 | "calendar": 321981, 56 | "camel": 121399, 57 | "camera": 128772, 58 | "camouflage": 172710, 59 | "campfire": 133395, 60 | "candle": 141545, 61 | "cannon": 141394, 62 | "canoe": 123767, 63 | "car": 182764, 64 | "carrot": 132459, 65 | "castle": 122534, 66 | "cat": 123202, 67 | "ceiling fan": 115413, 68 | "cello": 149725, 69 | "cell phone": 121130, 70 | "chair": 222706, 71 | "chandelier": 167502, 72 | "church": 164225, 73 | "circle": 122876, 74 | "clarinet": 126214, 75 | "clock": 120536, 76 | "cloud": 120265, 77 | "coffee cup": 183432, 78 | "compass": 127609, 79 | "computer": 123885, 80 | "cookie": 131353, 81 | "cooler": 271444, 82 | "couch": 119662, 83 | "cow": 123083, 84 | "crab": 126930, 85 | "crayon": 129953, 86 | "crocodile": 127932, 87 | "crown": 134089, 88 | "cruise ship": 123410, 89 | "cup": 130721, 90 | "diamond": 131587, 91 | "dishwasher": 169759, 92 | "diving board": 290239, 93 | "dog": 152159, 94 | "dolphin": 121613, 95 | "donut": 140751, 96 | "door": 120230, 97 | "dragon": 124362, 98 | "dresser": 123395, 99 | "drill": 136786, 100 | "drums": 137299, 101 | "duck": 135480, 102 | "dumbbell": 157975, 103 | "ear": 122897, 104 | "elbow": 126253, 105 | "elephant": 126969, 106 | "envelope": 134863, 107 | "eraser": 118339, 108 | "eye": 125888, 109 | "eyeglasses": 225762, 110 | "face": 161666, 111 | "fan": 136158, 112 | "feather": 119910, 113 | "fence": 129426, 114 | "finger": 167957, 115 | "fire hydrant": 137242, 116 | "fireplace": 155570, 117 | "firetruck": 220695, 118 | "fish": 134150, 119 | "flamingo": 124569, 120 | "flashlight": 239763, 121 | "flip flops": 121518, 122 | "floor lamp": 166355, 123 | "flower": 144818, 124 | "flying saucer": 151966, 125 | "foot": 203086, 126 | "fork": 126077, 127 | "frog": 159047, 128 | "frying pan": 123824, 129 | "garden": 158527, 130 | "garden hose": 121843, 131 | "giraffe": 127182, 132 | "goatee": 190002, 133 | "golf club": 194843, 134 | "grapes": 155305, 135 | "grass": 123071, 136 | "guitar": 120451, 137 | "hamburger": 129672, 138 | "hammer": 119012, 139 | "hand": 291773, 140 | "harp": 285403, 141 | "hat": 222610, 142 | "headphones": 118906, 143 | "hedgehog": 120527, 144 | "helicopter": 159938, 145 | "helmet": 121899, 146 | "hexagon": 142435, 147 | "hockey puck": 203301, 148 | "hockey stick": 130110, 149 | "horse": 178286, 150 | "hospital": 167448, 151 | "hot air balloon": 126350, 152 | "hot dog": 181999, 153 | "hot tub": 120279, 154 | "hourglass": 135957, 155 | "house": 135420, 156 | "house plant": 122996, 157 | "hurricane": 136245, 158 | "ice cream": 123133, 159 | "jacket": 214124, 160 | "jail": 120131, 161 | "kangaroo": 174470, 162 | "key": 160965, 163 | "keyboard": 187766, 164 | "knee": 267540, 165 | "knife": 172656, 166 | "ladder": 125389, 167 | "lantern": 149912, 168 | "laptop": 261501, 169 | "leaf": 125571, 170 | "leg": 116804, 171 | "light bulb": 120879, 172 | "lighter": 121260, 173 | "lighthouse": 160903, 174 | "lightning": 151560, 175 | "line": 143549, 176 | "lion": 120949, 177 | "lipstick": 127623, 178 | "lobster": 140175, 179 | "lollipop": 128849, 180 | "mailbox": 130053, 181 | "map": 120629, 182 | "marker": 319136, 183 | "matches": 143969, 184 | "megaphone": 137334, 185 | "mermaid": 180304, 186 | "microphone": 120570, 187 | "microwave": 130533, 188 | "monkey": 127633, 189 | "moon": 121661, 190 | "mosquito": 123029, 191 | "motorbike": 169931, 192 | "mountain": 128540, 193 | "mouse": 178826, 194 | "moustache": 179924, 195 | "mouth": 134135, 196 | "mug": 152918, 197 | "mushroom": 142167, 198 | "nail": 158593, 199 | "necklace": 120580, 200 | "nose": 197573, 201 | "ocean": 131493, 202 | "octagon": 159474, 203 | "octopus": 150152, 204 | "onion": 132297, 205 | "oven": 206910, 206 | "owl": 169632, 207 | "paintbrush": 187002, 208 | "paint can": 123446, 209 | "palm tree": 121959, 210 | "panda": 113613, 211 | "pants": 144264, 212 | "paper clip": 127129, 213 | "parachute": 127319, 214 | "parrot": 185530, 215 | "passport": 150265, 216 | "peanut": 126626, 217 | "pear": 116904, 218 | "peas": 161656, 219 | "pencil": 122001, 220 | "penguin": 253791, 221 | "piano": 116870, 222 | "pickup truck": 130740, 223 | "picture frame": 122371, 224 | "pig": 186770, 225 | "pillow": 118753, 226 | "pineapple": 125071, 227 | "pizza": 130371, 228 | "pliers": 172549, 229 | "police car": 130024, 230 | "pond": 121620, 231 | "pool": 133439, 232 | "popsicle": 126707, 233 | "postcard": 125706, 234 | "potato": 329204, 235 | "power outlet": 169462, 236 | "purse": 123320, 237 | "rabbit": 155288, 238 | "raccoon": 119588, 239 | "radio": 135728, 240 | "rain": 134680, 241 | "rainbow": 126845, 242 | "rake": 154639, 243 | "remote control": 119644, 244 | "rhinoceros": 188484, 245 | "rifle": 172444, 246 | "river": 133271, 247 | "roller coaster": 143570, 248 | "rollerskates": 119772, 249 | "sailboat": 136506, 250 | "sandwich": 131732, 251 | "saw": 121256, 252 | "saxophone": 118107, 253 | "school bus": 122041, 254 | "scissors": 123390, 255 | "scorpion": 165689, 256 | "screwdriver": 116313, 257 | "sea turtle": 119876, 258 | "see saw": 131936, 259 | "shark": 126050, 260 | "sheep": 126121, 261 | "shoe": 120231, 262 | "shorts": 124970, 263 | "shovel": 117194, 264 | "sink": 208410, 265 | "skateboard": 128733, 266 | "skull": 126174, 267 | "skyscraper": 183709, 268 | "sleeping bag": 119691, 269 | "smiley face": 124386, 270 | "snail": 133757, 271 | "snake": 122273, 272 | "snorkel": 154533, 273 | "snowflake": 116685, 274 | "snowman": 340029, 275 | "soccer ball": 125349, 276 | "sock": 205715, 277 | "speedboat": 188580, 278 | "spider": 209447, 279 | "spoon": 125028, 280 | "spreadsheet": 170200, 281 | "square": 125145, 282 | "squiggle": 118441, 283 | "squirrel": 156883, 284 | "stairs": 128981, 285 | "star": 137619, 286 | "steak": 122042, 287 | "stereo": 122444, 288 | "stethoscope": 153794, 289 | "stitches": 125192, 290 | "stop sign": 119814, 291 | "stove": 116535, 292 | "strawberry": 122301, 293 | "streetlight": 123280, 294 | "string bean": 119083, 295 | "submarine": 124362, 296 | "suitcase": 126442, 297 | "sun": 133781, 298 | "swan": 152088, 299 | "sweater": 120184, 300 | "swing set": 119357, 301 | "sword": 123802, 302 | "syringe": 135823, 303 | "table": 128021, 304 | "teapot": 126804, 305 | "teddy-bear": 179568, 306 | "telephone": 127885, 307 | "television": 123137, 308 | "tennis racquet": 231151, 309 | "tent": 131527, 310 | "The Eiffel Tower": 134801, 311 | "The Great Wall of China": 193015, 312 | "The Mona Lisa": 121383, 313 | "tiger": 121067, 314 | "toaster": 122434, 315 | "toe": 153652, 316 | "toilet": 129888, 317 | "tooth": 125064, 318 | "toothbrush": 124828, 319 | "toothpaste": 131037, 320 | "tornado": 143271, 321 | "tractor": 116677, 322 | "traffic light": 125321, 323 | "train": 127948, 324 | "tree": 144721, 325 | "triangle": 123170, 326 | "trombone": 184759, 327 | "truck": 131354, 328 | "trumpet": 169547, 329 | "t-shirt": 125233, 330 | "umbrella": 124084, 331 | "underwear": 124548, 332 | "van": 165909, 333 | "vase": 126475, 334 | "violin": 217260, 335 | "washing machine": 120851, 336 | "watermelon": 132939, 337 | "waterslide": 185364, 338 | "whale": 116502, 339 | "wheel": 136659, 340 | "windmill": 120644, 341 | "wine bottle": 126373, 342 | "wine glass": 132302, 343 | "wristwatch": 162645, 344 | "yoga": 280442, 345 | "zebra": 144608, 346 | "zigzag": 120072 347 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 |
4 | This project is no longer actively maintained by the Google Creative Lab but remains here in a read-only Archive mode so that it can continue to assist developers that may find the examples helpful. We aren’t able to address all pull requests or bug reports but outstanding issues will remain in read-only mode for reference purposes. Also, please note that some of the dependencies may not be up to date and there hasn’t been any QA done in a while so your mileage may vary. 5 |

6 | For more details on how Archiving affects Github repositories see this documentation . 7 |

8 | We welcome users to fork this repository should there be more useful, community-driven efforts that can help continue what this project began. 9 |
12 | 13 | ![logos for Quick, Draw! and the Polymer Project and Google Cloud](https://github.com/googlecreativelab/quickdraw-component/raw/master/images/header.png "Quick, Draw! + Polymer Project + Google Cloud") 14 | ## Quick, Draw! Polymer Component & API 15 | 16 | Embed [Quick, Draw!](https://quickdraw.withgoogle.com/) drawings into your project using a web component, as easily as: 17 | 18 | `````` 19 | 20 | This component is built using [Polymer3](https://www.polymer-project.org/) and is coupled with an API layer for accessing individual drawing data from the [open sourced data set](https://github.com/googlecreativelab/quickdraw-dataset). The drawings are drawn out to a canvas element, and provides you with options to customize it for your own web project. [Here's a demo of how it can be used in different ways.](https://googlecreativelab.github.io/quickdraw-component/demo/) 21 | 22 | ### Background 23 | 24 | In October 2016 we released [Quick, Draw!](https://quickdraw.withgoogle.com), a game to test your ability to doodle and have a neural net guess what you're drawing. You can help teach it by adding your drawings to the world’s largest doodling data set, shared publicly to help with machine learning research. 25 | 26 | Since the release, we've collected over 1 billion drawings across 345 categories. We've [released the data](https://github.com/googlecreativelab/quickdraw-dataset) in the form of [very large ndjson files](https://github.com/googlecreativelab/quickdraw-dataset/blob/master/examples/nodejs/ndjson.md) of moderated doodles. Now we are releasing a data API, a Polymer web component, and an option to self-host the 50 million files. 27 | 28 | ## Run example 29 | 30 | With npm installed, in the root of this repo: 31 | 32 | ``` 33 | npm install 34 | npm start 35 | ``` 36 | 37 | When this works, you'll see this simple demo of the component: 38 | 39 | ![demo image](https://github.com/googlecreativelab/quickdraw-component/raw/master/images/demo.png) 40 | 41 | 42 | ## Usage 43 | 44 | Install as an NPM dependency to your project: 45 | 46 | ``` 47 | npm install --save quickdraw-component 48 | ``` 49 | 50 | Import it: 51 | 52 | ``` 53 | import 'quickdraw-component/quickdraw-component.js'; 54 | ``` 55 | 56 | *You need to make sure that your app is using a tool like [Webpack](https://webpack.js.org/concepts/configuration/) or [Rollup](https://rollupjs.org/guide/en) to transpile and bundle the component into your app.* 57 | 58 | The most basic usage of the component's properties is to include a static, random drawing of a category with an API Key: 59 | 60 | `````` 61 | 62 | A list of all the available categories is [here](./categories.js). 63 | 64 | ### Getting a demo API Key 65 | 66 | It is *highly encouraged* for any large project to not use the demo endpoint as a dependency - see [Self-Hosting](#self-hosting) below. In order to get an API key for the demo endpoint, you need to: 67 | 68 | 1. [Join the Quick Draw API Google Group](https://groups.google.com/forum/#!forum/quick-draw-data-api). 69 | 70 | 2. In your project on Google Cloud Platform, go to [APIs & Services > Library](https://console.developers.google.com/apis/library/quickdrawfiles.appspot.com) and search for "Quick, Draw! API" 71 | 72 | 3. Click into Quick, Draw! Data API and press "Enable" 73 | 74 | 4. If you haven't created an API Key yet for your project, go into [APIs & Services > Credentials](https://console.developers.google.com/apis/credentials) and create one. This is what you'll use in the `key` property of: `` 75 | 76 | 77 | ### Options 78 | 79 | | Name | Description | Type | Default | Options / Examples| 80 | | :----------- | :-----------:| :-----------:| :-----------:|---------:| 81 | | `category` **required* | One of the [available categories](./categories.js) | String | null | `````` 82 | | `key` **required* | API Key from Google Cloud Platform | String | null | `````` 83 | | `host` | The host of your API | String | https://quickdrawfiles.appspot.com | `````` 84 | | `index` | The drawing id within a category *(random unless specified)* | Number | -1 | `````` 85 | | `animate` | Animate the drawing in the same time frame it was originally drawn| Boolean | *false* |`` 86 | | `time` | Sets the total time for the animation, preserving time proportions for each path (in milliseconds)| Number | null |`` 87 | | `paused` | When set to true, the component will only load the image data but not draw it. Listen for the data loaded event ```drawingData``` and then call ```drawImage()``` to control it manually| Number | null |`` 88 | | `strokeColor` | A hex value for the stroke color | String | #000000 |`` 89 | | `strokeWidth` | The width/thickness of the stroke | Number | 4|`` 90 | | `width` | The px width of the drawing. If provided, the drawing will scale proportionally to fit the space. | String | 'auto'|`` 91 | | `height` | The px height of the drawing. If provided, the drawing will scale proportionally to fit the space. | String | 'auto'|`` 92 | | `debug` | For testing purposes, will log output | Boolean | false|`` 93 | 94 | 95 | ### Events 96 | 97 | You can listen for the following custom events from the component: 98 | 99 | | Name | Description | Return | 100 | | ----------- | :-----------:| :-----------:| 101 | | `drawingData` | When drawing data is received | `{detail: {index, category, data}}` 102 | | `drawingComplete` | When drawing is completed | `{detail: {index, category, data}}` 103 | 104 | An example of how to access the element to listen for events: 105 | 106 | ``` 107 | function ready() { 108 | var el = document.querySelector('quick-draw'); 109 | el.addEventListener('drawingData', function(){ 110 | console.log('Drawing data loaded...'); 111 | }); 112 | el.addEventListener('drawingComplete', function(){ 113 | console.log('Drawing complete!'); 114 | }); 115 | } 116 | 117 | document.addEventListener("DOMContentLoaded", ready); 118 | ``` 119 | 120 | ### Methods 121 | 122 | You can manually call the following methods on your element: 123 | 124 | #### fetchImageData(category, index) 125 | This method will load image data, where "category" is one of [these available categories](./categories.js) like "cat", or "apple", and index is the number of the drawing. If index isn't passed, then it will load in a random image. To find out exactly how many indices are available for a given category, use the ```/{category}/count``` API call below. 126 | 127 | #### drawImage() 128 | This method will simply draw the image out on the canvas with its current properties. This is especially useful when using the ```paused``` property (when you want to control exactly when it's drawn out). 129 | 130 | 131 | ## API / Middleware 132 | 133 | This component has a server-side dependency, which can be reached from this endpoint: 134 | 135 | ```https://quickdrawfiles.appspot.com/drawing/{category}?id={id}&key={key}&isAnimated={isAnimated}&format={format}``` 136 | 137 | | Name | Description | Type | Default | 138 | | :----------- | :-----------:| :-----------:| -----------:| 139 | | `category` **required* | One of the [available categories](./categories.js) | String | null 140 | | `id` **required* | ID (number) or "random" | String | null 141 | | `key` **required* | API Key ([see above for instructions](#getting-a-demo-api-key)) | String | null 142 | | `isAnimated` | Will return raw time-based data if true, otherwise simplified data | Boolean | false 143 | | `format` | JSON or canvas drawing | String | "json" 144 | 145 | You can also retrieve the total count of drawings within a category using: 146 | 147 | ```https://quickdrawfiles.appspot.com/drawing/{category}/count?key={key}``` 148 | 149 | 150 | ## Self-Hosting 151 | 152 | It's highly encouraged that you self-host for larger, more serious projects as the quota limits are subject to change with this one (and it's not guaranteed to be supported / maintained forever). View the [README.md](./api/README.md) in the API directory to learn more about how to self-host the files and the API. 153 | 154 | ## Contributing 155 | 1. Fork it! 156 | 2. Create your feature branch: `git checkout -b my-new-feature` 157 | 3. Commit your changes: `git commit -am 'Add some feature'` 158 | 4. Push to the branch: `git push origin my-new-feature` 159 | 5. Submit a pull request :D 160 | 161 | ## License 162 | 163 | The component & API fall under the Apache 2.0 license. 164 | 165 | 166 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /demo/static/js/categories.js: -------------------------------------------------------------------------------- 1 | window.categories = [{"category":"aircraft carrier","count":116504},{"category":"airplane","count":151623},{"category":"alarm clock","count":123399},{"category":"ambulance","count":148004},{"category":"angel","count":149736},{"category":"animal migration","count":137847},{"category":"ant","count":124612},{"category":"anvil","count":126231},{"category":"apple","count":144722},{"category":"arm","count":120951},{"category":"asparagus","count":168102},{"category":"axe","count":124122},{"category":"backpack","count":125801},{"category":"banana","count":307936},{"category":"bandage","count":147614},{"category":"barn","count":151139},{"category":"baseball","count":135375},{"category":"baseball bat","count":123809},{"category":"basket","count":118458},{"category":"basketball","count":133793},{"category":"bat","count":118114},{"category":"bathtub","count":174336},{"category":"beach","count":124938},{"category":"bear","count":134762},{"category":"beard","count":165202},{"category":"bed","count":113862},{"category":"bee","count":120890},{"category":"belt","count":191119},{"category":"bench","count":128695},{"category":"bicycle","count":126527},{"category":"binoculars","count":124190},{"category":"bird","count":133572},{"category":"birthday cake","count":144982},{"category":"blackberry","count":128153},{"category":"blueberry","count":127878},{"category":"book","count":119364},{"category":"boomerang","count":142682},{"category":"bottlecap","count":153207},{"category":"bowtie","count":130283},{"category":"bracelet","count":119416},{"category":"brain","count":143033},{"category":"bread","count":120570},{"category":"bridge","count":133010},{"category":"broccoli","count":132826},{"category":"broom","count":116927},{"category":"bucket","count":124064},{"category":"bulldozer","count":187409},{"category":"bus","count":166208},{"category":"bush","count":120520},{"category":"butterfly","count":117999},{"category":"cactus","count":131676},{"category":"cake","count":124905},{"category":"calculator","count":128375},{"category":"calendar","count":321981},{"category":"camel","count":121399},{"category":"camera","count":128772},{"category":"camouflage","count":172710},{"category":"campfire","count":133395},{"category":"candle","count":141545},{"category":"cannon","count":141394},{"category":"canoe","count":123767},{"category":"car","count":182764},{"category":"carrot","count":132459},{"category":"castle","count":122534},{"category":"cat","count":123202},{"category":"ceiling fan","count":115413},{"category":"cello","count":149725},{"category":"cell phone","count":121130},{"category":"chair","count":222706},{"category":"chandelier","count":167502},{"category":"church","count":164225},{"category":"circle","count":122876},{"category":"clarinet","count":126214},{"category":"clock","count":120536},{"category":"cloud","count":120265},{"category":"coffee cup","count":183432},{"category":"compass","count":127609},{"category":"computer","count":123885},{"category":"cookie","count":131353},{"category":"cooler","count":271444},{"category":"couch","count":119662},{"category":"cow","count":123083},{"category":"crab","count":126930},{"category":"crayon","count":129953},{"category":"crocodile","count":127932},{"category":"crown","count":134089},{"category":"cruise ship","count":123410},{"category":"cup","count":130721},{"category":"diamond","count":131587},{"category":"dishwasher","count":169759},{"category":"diving board","count":290239},{"category":"dog","count":152159},{"category":"dolphin","count":121613},{"category":"donut","count":140751},{"category":"door","count":120230},{"category":"dragon","count":124362},{"category":"dresser","count":123395},{"category":"drill","count":136786},{"category":"drums","count":137299},{"category":"duck","count":135480},{"category":"dumbbell","count":157975},{"category":"ear","count":122897},{"category":"elbow","count":126253},{"category":"elephant","count":126969},{"category":"envelope","count":134863},{"category":"eraser","count":118339},{"category":"eye","count":125888},{"category":"eyeglasses","count":225762},{"category":"face","count":161666},{"category":"fan","count":136158},{"category":"feather","count":119910},{"category":"fence","count":129426},{"category":"finger","count":167957},{"category":"fire hydrant","count":137242},{"category":"fireplace","count":155570},{"category":"firetruck","count":220695},{"category":"fish","count":134150},{"category":"flamingo","count":124569},{"category":"flashlight","count":239763},{"category":"flip flops","count":121518},{"category":"floor lamp","count":166355},{"category":"flower","count":144818},{"category":"flying saucer","count":151966},{"category":"foot","count":203086},{"category":"fork","count":126077},{"category":"frog","count":159047},{"category":"frying pan","count":123824},{"category":"garden","count":158527},{"category":"garden hose","count":121843},{"category":"giraffe","count":127182},{"category":"goatee","count":190002},{"category":"golf club","count":194843},{"category":"grapes","count":155305},{"category":"grass","count":123071},{"category":"guitar","count":120451},{"category":"hamburger","count":129672},{"category":"hammer","count":119012},{"category":"hand","count":291773},{"category":"harp","count":285403},{"category":"hat","count":222610},{"category":"headphones","count":118906},{"category":"hedgehog","count":120527},{"category":"helicopter","count":159938},{"category":"helmet","count":121899},{"category":"hexagon","count":142435},{"category":"hockey puck","count":203301},{"category":"hockey stick","count":130110},{"category":"horse","count":178286},{"category":"hospital","count":167448},{"category":"hot air balloon","count":126350},{"category":"hot dog","count":181999},{"category":"hot tub","count":120279},{"category":"hourglass","count":135957},{"category":"house","count":135420},{"category":"house plant","count":122996},{"category":"hurricane","count":136245},{"category":"ice cream","count":123133},{"category":"jacket","count":214124},{"category":"jail","count":120131},{"category":"kangaroo","count":174470},{"category":"key","count":160965},{"category":"keyboard","count":187766},{"category":"knee","count":267540},{"category":"knife","count":172656},{"category":"ladder","count":125389},{"category":"lantern","count":149912},{"category":"laptop","count":261501},{"category":"leaf","count":125571},{"category":"leg","count":116804},{"category":"light bulb","count":120879},{"category":"lighter","count":121260},{"category":"lighthouse","count":160903},{"category":"lightning","count":151560},{"category":"line","count":143549},{"category":"lion","count":120949},{"category":"lipstick","count":127623},{"category":"lobster","count":140175},{"category":"lollipop","count":128849},{"category":"mailbox","count":130053},{"category":"map","count":120629},{"category":"marker","count":319136},{"category":"matches","count":143969},{"category":"megaphone","count":137334},{"category":"mermaid","count":180304},{"category":"microphone","count":120570},{"category":"microwave","count":130533},{"category":"monkey","count":127633},{"category":"moon","count":121661},{"category":"mosquito","count":123029},{"category":"motorbike","count":169931},{"category":"mountain","count":128540},{"category":"mouse","count":178826},{"category":"moustache","count":179924},{"category":"mouth","count":134135},{"category":"mug","count":152918},{"category":"mushroom","count":142167},{"category":"nail","count":158593},{"category":"necklace","count":120580},{"category":"nose","count":197573},{"category":"ocean","count":131493},{"category":"octagon","count":159474},{"category":"octopus","count":150152},{"category":"onion","count":132297},{"category":"oven","count":206910},{"category":"owl","count":169632},{"category":"paintbrush","count":187002},{"category":"paint can","count":123446},{"category":"palm tree","count":121959},{"category":"panda","count":113613},{"category":"pants","count":144264},{"category":"paper clip","count":127129},{"category":"parachute","count":127319},{"category":"parrot","count":185530},{"category":"passport","count":150265},{"category":"peanut","count":126626},{"category":"pear","count":116904},{"category":"peas","count":161656},{"category":"pencil","count":122001},{"category":"penguin","count":253791},{"category":"piano","count":116870},{"category":"pickup truck","count":130740},{"category":"picture frame","count":122371},{"category":"pig","count":186770},{"category":"pillow","count":118753},{"category":"pineapple","count":125071},{"category":"pizza","count":130371},{"category":"pliers","count":172549},{"category":"police car","count":130024},{"category":"pond","count":121620},{"category":"pool","count":133439},{"category":"popsicle","count":126707},{"category":"postcard","count":125706},{"category":"potato","count":329204},{"category":"power outlet","count":169462},{"category":"purse","count":123320},{"category":"rabbit","count":155288},{"category":"raccoon","count":119588},{"category":"radio","count":135728},{"category":"rain","count":134680},{"category":"rainbow","count":126845},{"category":"rake","count":154639},{"category":"remote control","count":119644},{"category":"rhinoceros","count":188484},{"category":"rifle","count":172444},{"category":"river","count":133271},{"category":"roller coaster","count":143570},{"category":"rollerskates","count":119772},{"category":"sailboat","count":136506},{"category":"sandwich","count":131732},{"category":"saw","count":121256},{"category":"saxophone","count":118107},{"category":"school bus","count":122041},{"category":"scissors","count":123390},{"category":"scorpion","count":165689},{"category":"screwdriver","count":116313},{"category":"sea turtle","count":119876},{"category":"see saw","count":131936},{"category":"shark","count":126050},{"category":"sheep","count":126121},{"category":"shoe","count":120231},{"category":"shorts","count":124970},{"category":"shovel","count":117194},{"category":"sink","count":208410},{"category":"skateboard","count":128733},{"category":"skull","count":126174},{"category":"skyscraper","count":183709},{"category":"sleeping bag","count":119691},{"category":"smiley face","count":124386},{"category":"snail","count":133757},{"category":"snake","count":122273},{"category":"snorkel","count":154533},{"category":"snowflake","count":116685},{"category":"snowman","count":340029},{"category":"soccer ball","count":125349},{"category":"sock","count":205715},{"category":"speedboat","count":188580},{"category":"spider","count":209447},{"category":"spoon","count":125028},{"category":"spreadsheet","count":170200},{"category":"square","count":125145},{"category":"squiggle","count":118441},{"category":"squirrel","count":156883},{"category":"stairs","count":128981},{"category":"star","count":137619},{"category":"steak","count":122042},{"category":"stereo","count":122444},{"category":"stethoscope","count":153794},{"category":"stitches","count":125192},{"category":"stop sign","count":119814},{"category":"stove","count":116535},{"category":"strawberry","count":122301},{"category":"streetlight","count":123280},{"category":"string bean","count":119083},{"category":"submarine","count":124362},{"category":"suitcase","count":126442},{"category":"sun","count":133781},{"category":"swan","count":152088},{"category":"sweater","count":120184},{"category":"swing set","count":119357},{"category":"sword","count":123802},{"category":"syringe","count":135823},{"category":"table","count":128021},{"category":"teapot","count":126804},{"category":"teddy-bear","count":179568},{"category":"telephone","count":127885},{"category":"television","count":123137},{"category":"tennis racquet","count":231151},{"category":"tent","count":131527},{"category":"The Eiffel Tower","count":134801},{"category":"The Great Wall of China","count":193015},{"category":"The Mona Lisa","count":121383},{"category":"tiger","count":121067},{"category":"toaster","count":122434},{"category":"toe","count":153652},{"category":"toilet","count":129888},{"category":"tooth","count":125064},{"category":"toothbrush","count":124828},{"category":"toothpaste","count":131037},{"category":"tornado","count":143271},{"category":"tractor","count":116677},{"category":"traffic light","count":125321},{"category":"train","count":127948},{"category":"tree","count":144721},{"category":"triangle","count":123170},{"category":"trombone","count":184759},{"category":"truck","count":131354},{"category":"trumpet","count":169547},{"category":"t-shirt","count":125233},{"category":"umbrella","count":124084},{"category":"underwear","count":124548},{"category":"van","count":165909},{"category":"vase","count":126475},{"category":"violin","count":217260},{"category":"washing machine","count":120851},{"category":"watermelon","count":132939},{"category":"waterslide","count":185364},{"category":"whale","count":116502},{"category":"wheel","count":136659},{"category":"windmill","count":120644},{"category":"wine bottle","count":126373},{"category":"wine glass","count":132302},{"category":"wristwatch","count":162645},{"category":"yoga","count":280442},{"category":"zebra","count":144608},{"category":"zigzag","count":120072}] -------------------------------------------------------------------------------- /categories.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export const categories = [{"category":"aircraft carrier","count":116504},{"category":"airplane","count":151623},{"category":"alarm clock","count":123399},{"category":"ambulance","count":148004},{"category":"angel","count":149736},{"category":"animal migration","count":137847},{"category":"ant","count":124612},{"category":"anvil","count":126231},{"category":"apple","count":144722},{"category":"arm","count":120951},{"category":"asparagus","count":168102},{"category":"axe","count":124122},{"category":"backpack","count":125801},{"category":"banana","count":307936},{"category":"bandage","count":147614},{"category":"barn","count":151139},{"category":"baseball","count":135375},{"category":"baseball bat","count":123809},{"category":"basket","count":118458},{"category":"basketball","count":133793},{"category":"bat","count":118114},{"category":"bathtub","count":174336},{"category":"beach","count":124938},{"category":"bear","count":134762},{"category":"beard","count":165202},{"category":"bed","count":113862},{"category":"bee","count":120890},{"category":"belt","count":191119},{"category":"bench","count":128695},{"category":"bicycle","count":126527},{"category":"binoculars","count":124190},{"category":"bird","count":133572},{"category":"birthday cake","count":144982},{"category":"blackberry","count":128153},{"category":"blueberry","count":127878},{"category":"book","count":119364},{"category":"boomerang","count":142682},{"category":"bottlecap","count":153207},{"category":"bowtie","count":130283},{"category":"bracelet","count":119416},{"category":"brain","count":143033},{"category":"bread","count":120570},{"category":"bridge","count":133010},{"category":"broccoli","count":132826},{"category":"broom","count":116927},{"category":"bucket","count":124064},{"category":"bulldozer","count":187409},{"category":"bus","count":166208},{"category":"bush","count":120520},{"category":"butterfly","count":117999},{"category":"cactus","count":131676},{"category":"cake","count":124905},{"category":"calculator","count":128375},{"category":"calendar","count":321981},{"category":"camel","count":121399},{"category":"camera","count":128772},{"category":"camouflage","count":172710},{"category":"campfire","count":133395},{"category":"candle","count":141545},{"category":"cannon","count":141394},{"category":"canoe","count":123767},{"category":"car","count":182764},{"category":"carrot","count":132459},{"category":"castle","count":122534},{"category":"cat","count":123202},{"category":"ceiling fan","count":115413},{"category":"cello","count":149725},{"category":"cell phone","count":121130},{"category":"chair","count":222706},{"category":"chandelier","count":167502},{"category":"church","count":164225},{"category":"circle","count":122876},{"category":"clarinet","count":126214},{"category":"clock","count":120536},{"category":"cloud","count":120265},{"category":"coffee cup","count":183432},{"category":"compass","count":127609},{"category":"computer","count":123885},{"category":"cookie","count":131353},{"category":"cooler","count":271444},{"category":"couch","count":119662},{"category":"cow","count":123083},{"category":"crab","count":126930},{"category":"crayon","count":129953},{"category":"crocodile","count":127932},{"category":"crown","count":134089},{"category":"cruise ship","count":123410},{"category":"cup","count":130721},{"category":"diamond","count":131587},{"category":"dishwasher","count":169759},{"category":"diving board","count":290239},{"category":"dog","count":152159},{"category":"dolphin","count":121613},{"category":"donut","count":140751},{"category":"door","count":120230},{"category":"dragon","count":124362},{"category":"dresser","count":123395},{"category":"drill","count":136786},{"category":"drums","count":137299},{"category":"duck","count":135480},{"category":"dumbbell","count":157975},{"category":"ear","count":122897},{"category":"elbow","count":126253},{"category":"elephant","count":126969},{"category":"envelope","count":134863},{"category":"eraser","count":118339},{"category":"eye","count":125888},{"category":"eyeglasses","count":225762},{"category":"face","count":161666},{"category":"fan","count":136158},{"category":"feather","count":119910},{"category":"fence","count":129426},{"category":"finger","count":167957},{"category":"fire hydrant","count":137242},{"category":"fireplace","count":155570},{"category":"firetruck","count":220695},{"category":"fish","count":134150},{"category":"flamingo","count":124569},{"category":"flashlight","count":239763},{"category":"flip flops","count":121518},{"category":"floor lamp","count":166355},{"category":"flower","count":144818},{"category":"flying saucer","count":151966},{"category":"foot","count":203086},{"category":"fork","count":126077},{"category":"frog","count":159047},{"category":"frying pan","count":123824},{"category":"garden","count":158527},{"category":"garden hose","count":121843},{"category":"giraffe","count":127182},{"category":"goatee","count":190002},{"category":"golf club","count":194843},{"category":"grapes","count":155305},{"category":"grass","count":123071},{"category":"guitar","count":120451},{"category":"hamburger","count":129672},{"category":"hammer","count":119012},{"category":"hand","count":291773},{"category":"harp","count":285403},{"category":"hat","count":222610},{"category":"headphones","count":118906},{"category":"hedgehog","count":120527},{"category":"helicopter","count":159938},{"category":"helmet","count":121899},{"category":"hexagon","count":142435},{"category":"hockey puck","count":203301},{"category":"hockey stick","count":130110},{"category":"horse","count":178286},{"category":"hospital","count":167448},{"category":"hot air balloon","count":126350},{"category":"hot dog","count":181999},{"category":"hot tub","count":120279},{"category":"hourglass","count":135957},{"category":"house","count":135420},{"category":"house plant","count":122996},{"category":"hurricane","count":136245},{"category":"ice cream","count":123133},{"category":"jacket","count":214124},{"category":"jail","count":120131},{"category":"kangaroo","count":174470},{"category":"key","count":160965},{"category":"keyboard","count":187766},{"category":"knee","count":267540},{"category":"knife","count":172656},{"category":"ladder","count":125389},{"category":"lantern","count":149912},{"category":"laptop","count":261501},{"category":"leaf","count":125571},{"category":"leg","count":116804},{"category":"light bulb","count":120879},{"category":"lighter","count":121260},{"category":"lighthouse","count":160903},{"category":"lightning","count":151560},{"category":"line","count":143549},{"category":"lion","count":120949},{"category":"lipstick","count":127623},{"category":"lobster","count":140175},{"category":"lollipop","count":128849},{"category":"mailbox","count":130053},{"category":"map","count":120629},{"category":"marker","count":319136},{"category":"matches","count":143969},{"category":"megaphone","count":137334},{"category":"mermaid","count":180304},{"category":"microphone","count":120570},{"category":"microwave","count":130533},{"category":"monkey","count":127633},{"category":"moon","count":121661},{"category":"mosquito","count":123029},{"category":"motorbike","count":169931},{"category":"mountain","count":128540},{"category":"mouse","count":178826},{"category":"moustache","count":179924},{"category":"mouth","count":134135},{"category":"mug","count":152918},{"category":"mushroom","count":142167},{"category":"nail","count":158593},{"category":"necklace","count":120580},{"category":"nose","count":197573},{"category":"ocean","count":131493},{"category":"octagon","count":159474},{"category":"octopus","count":150152},{"category":"onion","count":132297},{"category":"oven","count":206910},{"category":"owl","count":169632},{"category":"paintbrush","count":187002},{"category":"paint can","count":123446},{"category":"palm tree","count":121959},{"category":"panda","count":113613},{"category":"pants","count":144264},{"category":"paper clip","count":127129},{"category":"parachute","count":127319},{"category":"parrot","count":185530},{"category":"passport","count":150265},{"category":"peanut","count":126626},{"category":"pear","count":116904},{"category":"peas","count":161656},{"category":"pencil","count":122001},{"category":"penguin","count":253791},{"category":"piano","count":116870},{"category":"pickup truck","count":130740},{"category":"picture frame","count":122371},{"category":"pig","count":186770},{"category":"pillow","count":118753},{"category":"pineapple","count":125071},{"category":"pizza","count":130371},{"category":"pliers","count":172549},{"category":"police car","count":130024},{"category":"pond","count":121620},{"category":"pool","count":133439},{"category":"popsicle","count":126707},{"category":"postcard","count":125706},{"category":"potato","count":329204},{"category":"power outlet","count":169462},{"category":"purse","count":123320},{"category":"rabbit","count":155288},{"category":"raccoon","count":119588},{"category":"radio","count":135728},{"category":"rain","count":134680},{"category":"rainbow","count":126845},{"category":"rake","count":154639},{"category":"remote control","count":119644},{"category":"rhinoceros","count":188484},{"category":"rifle","count":172444},{"category":"river","count":133271},{"category":"roller coaster","count":143570},{"category":"rollerskates","count":119772},{"category":"sailboat","count":136506},{"category":"sandwich","count":131732},{"category":"saw","count":121256},{"category":"saxophone","count":118107},{"category":"school bus","count":122041},{"category":"scissors","count":123390},{"category":"scorpion","count":165689},{"category":"screwdriver","count":116313},{"category":"sea turtle","count":119876},{"category":"see saw","count":131936},{"category":"shark","count":126050},{"category":"sheep","count":126121},{"category":"shoe","count":120231},{"category":"shorts","count":124970},{"category":"shovel","count":117194},{"category":"sink","count":208410},{"category":"skateboard","count":128733},{"category":"skull","count":126174},{"category":"skyscraper","count":183709},{"category":"sleeping bag","count":119691},{"category":"smiley face","count":124386},{"category":"snail","count":133757},{"category":"snake","count":122273},{"category":"snorkel","count":154533},{"category":"snowflake","count":116685},{"category":"snowman","count":340029},{"category":"soccer ball","count":125349},{"category":"sock","count":205715},{"category":"speedboat","count":188580},{"category":"spider","count":209447},{"category":"spoon","count":125028},{"category":"spreadsheet","count":170200},{"category":"square","count":125145},{"category":"squiggle","count":118441},{"category":"squirrel","count":156883},{"category":"stairs","count":128981},{"category":"star","count":137619},{"category":"steak","count":122042},{"category":"stereo","count":122444},{"category":"stethoscope","count":153794},{"category":"stitches","count":125192},{"category":"stop sign","count":119814},{"category":"stove","count":116535},{"category":"strawberry","count":122301},{"category":"streetlight","count":123280},{"category":"string bean","count":119083},{"category":"submarine","count":124362},{"category":"suitcase","count":126442},{"category":"sun","count":133781},{"category":"swan","count":152088},{"category":"sweater","count":120184},{"category":"swing set","count":119357},{"category":"sword","count":123802},{"category":"syringe","count":135823},{"category":"table","count":128021},{"category":"teapot","count":126804},{"category":"teddy-bear","count":179568},{"category":"telephone","count":127885},{"category":"television","count":123137},{"category":"tennis racquet","count":231151},{"category":"tent","count":131527},{"category":"The Eiffel Tower","count":134801},{"category":"The Great Wall of China","count":193015},{"category":"The Mona Lisa","count":121383},{"category":"tiger","count":121067},{"category":"toaster","count":122434},{"category":"toe","count":153652},{"category":"toilet","count":129888},{"category":"tooth","count":125064},{"category":"toothbrush","count":124828},{"category":"toothpaste","count":131037},{"category":"tornado","count":143271},{"category":"tractor","count":116677},{"category":"traffic light","count":125321},{"category":"train","count":127948},{"category":"tree","count":144721},{"category":"triangle","count":123170},{"category":"trombone","count":184759},{"category":"truck","count":131354},{"category":"trumpet","count":169547},{"category":"t-shirt","count":125233},{"category":"umbrella","count":124084},{"category":"underwear","count":124548},{"category":"van","count":165909},{"category":"vase","count":126475},{"category":"violin","count":217260},{"category":"washing machine","count":120851},{"category":"watermelon","count":132939},{"category":"waterslide","count":185364},{"category":"whale","count":116502},{"category":"wheel","count":136659},{"category":"windmill","count":120644},{"category":"wine bottle","count":126373},{"category":"wine glass","count":132302},{"category":"wristwatch","count":162645},{"category":"yoga","count":280442},{"category":"zebra","count":144608},{"category":"zigzag","count":120072}] -------------------------------------------------------------------------------- /quickdraw-component.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | import {LitElement, html} from '@polymer/lit-element'; 15 | import { categories } from './categories'; 16 | 17 | // temporary deferring update until connectedCallback 18 | // see more: https://github.com/Polymer/lit-element/issues/258 19 | const DeferUntilConnected = superClass => class extends superClass { 20 | _invalidate(){ 21 | if (this._hasConnected){ 22 | super._invalidate(); 23 | } 24 | } 25 | connectedCallback() { 26 | this._hasConnected = true; 27 | super.connectedCallback(); 28 | this.requestUpdate(); 29 | } 30 | } 31 | 32 | const QuickDrawElement = DeferUntilConnected(class extends LitElement { 33 | 34 | // Public property API that triggers re-render (synced with attributes) 35 | static get properties() { 36 | return { 37 | category: {type: String}, 38 | speed: {type: Number}, 39 | strokeWidth: {type: Number}, 40 | strokeColor: {type: String}, 41 | width: {type: String}, 42 | height: {type: String}, 43 | key: {type: String}, 44 | host: {type: String}, 45 | index: {type: Number}, 46 | animated: {type: Boolean}, 47 | debug: {type: Boolean}, 48 | time: {type: Number}, 49 | paused: {type: Boolean} 50 | } 51 | } 52 | 53 | // set default properties 54 | constructor() { 55 | super(); 56 | this.category = null; 57 | this.key = null; 58 | this.index = -1; 59 | this.width = 'auto'; 60 | this.height = 'auto'; 61 | this.strokeWidth = 4; 62 | this.strokeColor = '#000000'; 63 | this.speed = 0.5; 64 | this.host = 'https://quickdrawfiles.appspot.com'; 65 | this.imageData = null; 66 | this.animated = false; 67 | this.debug = false; 68 | this.time = null; // ms 69 | this.paused = false; 70 | this._isFetching = false; 71 | } 72 | 73 | // runs only once after first render() 74 | firstUpdated(){ 75 | // get references 76 | this.canvas = this.shadowRoot.querySelector('#canvas'); 77 | this.ctx = this.canvas.getContext('2d'); 78 | } 79 | 80 | // when properties are updated 81 | updated(changedProperties){ 82 | // do property checks 83 | if(!this.key){ 84 | console.error('No API Key provided.') 85 | return; 86 | } 87 | if(!this.category){ 88 | console.error('No category provided.') 89 | return; 90 | } 91 | if(this._isFetching){ 92 | console.warn('Tried to change property while fetch is still in flight.'); 93 | return; 94 | } 95 | // draw to canvas, fetching new data if necessary 96 | this._init(changedProperties.has('category') || changedProperties.has('index') || changedProperties.has('animated')); 97 | } 98 | 99 | async _init(isNewFetch){ 100 | // cancel anything that is currently going on 101 | if(this.cancelDrawing){ 102 | if(this.reqFrame){ 103 | window.cancelAnimationFrame(this.reqFrame); 104 | } 105 | this.cancelDrawing(); 106 | this.cancelDrawing = null; 107 | } 108 | // clear canvas 109 | this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 110 | // load image data 111 | if(isNewFetch){ 112 | try{ 113 | this._log('Fetching new data...'); 114 | this._isFetching = true; 115 | this.imageData = await this.fetchImageData(this.category, this.index); 116 | this._isFetching = false; 117 | }catch(err){ 118 | console.error(err); 119 | return; 120 | } 121 | } 122 | // draw new image 123 | if(!this.paused){ 124 | try{ 125 | await this.drawImage(); 126 | }catch(err){ 127 | this._log('Drawing stopped short.'); 128 | } 129 | this.lastIndex = 0; 130 | } 131 | } 132 | 133 | refresh(){ 134 | this._init(true); 135 | } 136 | 137 | async fetchImageData(category, index){ 138 | let filepath = this.host + `/drawing/${category}?isAnimated=${this.animated}&key=${this.key}` 139 | if(typeof index != 'undefined' && parseInt(index, 10) >= 0){ 140 | filepath += `&id=${index}`; 141 | } 142 | // fetch local file 143 | try{ 144 | const res = await fetch(filepath); 145 | if(res.status == 404){ 146 | this._log('Could not find file: ' + filepath); 147 | }else{ 148 | try{ 149 | const resJson = await res.json() 150 | this._log('id -> ' + resJson.index); 151 | const data = {detail: {index: resJson.index, category: this.category, data: resJson}}; 152 | this.imageData = data.detail.data; 153 | this.dispatchEvent(new CustomEvent('drawingData', data)) 154 | return resJson; 155 | }catch(err){ 156 | throw err; 157 | } 158 | } 159 | }catch(err){ 160 | throw err; 161 | } 162 | } 163 | 164 | async drawImage(){ 165 | if(!this.imageData){ 166 | console.error('drawImage() called before drawing data has loaded.'); 167 | return; 168 | } 169 | const imageData = this.imageData.drawing; 170 | // make sure canvas is cleared 171 | this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 172 | // call this method when canceling before finished 173 | this.cancelDrawing = () => { 174 | if(this.promisePathCancel){ 175 | this.promisePathCancel(); 176 | this.promisePathCancel = null; 177 | } 178 | } 179 | // get image properties 180 | // {x_range, y_range, width, height, x, y} 181 | this.currentImageProperties = this._getDrawingProperties(imageData); 182 | // account for stroke width to the image properties 183 | this.currentImageProperties.width += this.strokeWidth * 2; 184 | this.currentImageProperties.height += this.strokeWidth * 2; 185 | this.currentImageScaleRatio = 1; 186 | // default, automatic width and height based on original drawing 187 | if(this.width == 'auto' && this.height == 'auto'){ 188 | this.canvas.width = this.currentImageProperties.width; 189 | this.canvas.height = this.currentImageProperties.height; 190 | } 191 | // user gave height value, width is auto 192 | else if(this.width == 'auto' && this.height != 'auto'){ 193 | this.canvas.height = parseInt(this.height, 10); 194 | let ratio = this.height / this.currentImageProperties.height; 195 | this.currentImageScaleRatio = ratio; 196 | this.canvas.width = this.currentImageProperties.width * ratio; 197 | } 198 | // user gave width value, height is auto 199 | else if(this.width != 'auto' && this.height == 'auto'){ 200 | this.canvas.width = parseInt(this.width, 10); 201 | let ratio = this.width / this.currentImageProperties.width; 202 | this.currentImageScaleRatio = ratio; 203 | this.canvas.height = this.currentImageProperties.height * ratio; 204 | } 205 | // user provided both width and height 206 | else{ 207 | this.canvas.width = this.width; 208 | this.canvas.height = this.height; 209 | // let's best fit the drawing in the user-defined canvas dimensions 210 | const destWidth = this.width; 211 | const destHeight = this.height; 212 | const sourceWidth = this.currentImageProperties.width; 213 | const sourceHeight = this.currentImageProperties.height; 214 | // Determine the appropriate scale 215 | const scaleX = destWidth / sourceWidth; 216 | const scaleY = destHeight / sourceHeight; 217 | this.currentImageScaleRatio = scaleX < scaleY ? scaleX : scaleY; 218 | } 219 | 220 | // lose decimal precision, using floor & 90% of original value to give slight padding 221 | // eg: 0.3463203463203463 -> 0.31 222 | this.currentImageScaleRatio = Math.floor((this.currentImageScaleRatio * 0.9) * 100) / 100; 223 | 224 | // need to get time ratios for each path, used when user sets time 225 | // first get total time for drawing 226 | if(this.animated){ 227 | let total = imageData[imageData.length - 1][2][imageData[imageData.length - 1][2].length - 1]; 228 | // add ratios to path data 229 | for(let i = 0; i < imageData.length; i++){ 230 | const ttc = imageData[i][2][imageData[i][2].length - 1]; 231 | imageData[i].push(ttc / total); 232 | } 233 | this._log(`Original time: ${total}ms`, '#9f8029'); 234 | this._log(`New time: ${this.time}ms`, '#9f8029'); 235 | } 236 | // loop through each segment and draw 237 | let timeToCompleteThisPath = 0, 238 | timeToCompleteLastPath = 0; 239 | for(let i = 0; i < imageData.length; i++){ 240 | if(this.animated){ 241 | if(this.time){ 242 | timeToCompleteThisPath = imageData[i][3] * this.time; 243 | }else{ 244 | timeToCompleteThisPath = imageData[i][2][imageData[i][2].length - 1]; 245 | } 246 | if(i > 0){ 247 | if(this.time){ 248 | timeToCompleteLastPath = imageData[i - 1][3] * this.time; 249 | }else{ 250 | timeToCompleteLastPath = imageData[i - 1][2][imageData[i - 1][2].length - 1]; 251 | } 252 | timeToCompleteThisPath -= timeToCompleteLastPath; 253 | } 254 | } 255 | this._log(`Path ${i + 1}/${imageData.length} time: ${timeToCompleteThisPath}ms`, '#e2b537'); 256 | try{ 257 | await this._drawSegment(imageData[i], timeToCompleteThisPath); 258 | }catch(err){ 259 | throw 'Segment drawing cut short.' 260 | } 261 | this.promisePathCancel = null; 262 | this.lastIndex = 0; 263 | } 264 | this.dispatchEvent(new CustomEvent('drawingComplete', {detail: {index: this.imageData.index, category: this.category, data: this.imageData}})) 265 | } 266 | 267 | _drawSegment(points, timeToComplete){ 268 | return new Promise((resolve, reject) => { 269 | if(this.animated){ 270 | // for canceling the drawing of segment before finished 271 | this.promisePathCancel = () => { 272 | reject(); 273 | } 274 | // assuming 60 FPS 275 | let totalFrames = Math.floor((timeToComplete / 1000) * 60); 276 | this._refreshLoop(points, totalFrames, 0, resolve); 277 | }else{ 278 | this.lastIndex = this._drawPath(points, (this.lastIndex || 0), 1); 279 | resolve(); 280 | } 281 | }) 282 | } 283 | 284 | _refreshLoop(points, totalFrames, counter, resolve) { 285 | let that = this; 286 | this.reqFrame = window.requestAnimationFrame(() => { 287 | let ratioComplete = counter / totalFrames; 288 | that.lastIndex = that._drawPath(points, (that.lastIndex || 0), ratioComplete); 289 | if(counter >= totalFrames){ 290 | window.cancelAnimationFrame(that.reqFrame); 291 | resolve(); 292 | }else{ 293 | counter++; 294 | that._refreshLoop(points, totalFrames, counter, resolve); 295 | } 296 | }); 297 | } 298 | 299 | 300 | _drawPath(points, startIndex, ratioComplete){ 301 | let x_points = points[0], 302 | y_points = points[1], 303 | stroke_len = x_points.length, // number of x & y points in stroke 304 | ratio_complete_len = Math.floor(ratioComplete * stroke_len), 305 | len = Math.min(ratio_complete_len, x_points.length); 306 | 307 | this.ctx.beginPath(); 308 | this.ctx.lineWidth = this.strokeWidth; 309 | this.ctx.strokeStyle = this.strokeColor; 310 | // gives smoother rendering 311 | // https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors#A_lineJoin_example 312 | this.ctx.lineJoin = this.ctx.lineCap = 'round'; 313 | 314 | let {x, y} = this._transformPoint(x_points[startIndex], y_points[startIndex]); 315 | this.ctx.moveTo(x, y); 316 | for(let j = startIndex; j < len; j++){ 317 | let {x, y} = this._transformPoint(x_points[j], y_points[j]); 318 | 319 | if(j > 0){ 320 | // Note: tried using quadraticCurveTo as a smoothing effect but found 321 | // normal lineTo with a round line cap yielded smoother results 322 | this.ctx.lineTo(x, y); 323 | } 324 | } 325 | this.ctx.stroke(); 326 | return len - 1; 327 | } 328 | 329 | _transformPoint(x, y){ 330 | x = (x - this.currentImageProperties.x) * this.currentImageScaleRatio + this.strokeWidth; 331 | y = (y - this.currentImageProperties.y) * this.currentImageScaleRatio + this.strokeWidth; 332 | return {x: x, y: y}; 333 | } 334 | 335 | _getDrawingProperties(points){ 336 | let x_range = {min: 99999, max: 0}, 337 | y_range = {min: 99999, max: 0}; 338 | for(let i = 0; i < points.length; i++){ 339 | let stroke_len = points[i][0].length; 340 | for(let j = 0; j < stroke_len; j++){ 341 | let this_x = points[i][0][j], 342 | this_y = points[i][1][j]; 343 | if(this_x < x_range.min) x_range.min = this_x; 344 | else if(this_x > x_range.max) x_range.max = this_x; 345 | if(this_y < y_range.min) y_range.min = this_y; 346 | else if(this_y > y_range.max) y_range.max = this_y; 347 | } 348 | } 349 | return { 350 | x_range: x_range, 351 | y_range: y_range, 352 | width: x_range.max - x_range.min, 353 | height: y_range.max - y_range.min, 354 | x: x_range.min, 355 | y: y_range.min 356 | }; 357 | } 358 | 359 | _logProperties(){ 360 | if(this.debug){ 361 | console.log('Properties:::'); 362 | console.log( 363 | '\tthis.category:',this.category,'\n', 364 | '\tthis.key:',this.key,'\n', 365 | '\tthis.index:',this.index,'\n', 366 | '\tthis.width:',this.width,'\n', 367 | '\tthis.height:',this.height,'\n', 368 | '\tthis.strokeWidth:',this.strokeWidth,'\n', 369 | '\tthis.strokeColor:',this.strokeColor,'\n', 370 | '\tthis.speed:',this.speed,'\n', 371 | '\tthis.host:',this.host,'\n', 372 | '\tthis.imageData:',this.imageData,'\n', 373 | '\tthis.animated:',this.animated,'\n', 374 | '\tthis.debug:',this.debug,'\n', 375 | '\tthis.time:',this.time,'\n', 376 | '\tthis.pause:',this.paused,'\n' 377 | ) 378 | } 379 | } 380 | 381 | _log(s, color){ 382 | if(this.debug){ 383 | console.log('%c Quick, Draw! Component ::: ' + '%c' + s, "color:#ffd139;", `color:${color || '#000'};`); 384 | } 385 | } 386 | 387 | // Render method should return a `TemplateResult` using the provided lit-html `html` tag function 388 | render() { 389 | 390 | return html` 391 | 399 | ${this.category} 400 | `; 401 | } 402 | }); 403 | 404 | customElements.define('quick-draw', QuickDrawElement); --------------------------------------------------------------------------------