├── .gitignore ├── README.md ├── build ├── asset-manifest.json ├── favicon.ico ├── index.html ├── manifest.json ├── precache-manifest.9c2a8c84f78df0983cb5568c89146179.js ├── service-worker.js └── static │ ├── css │ ├── main.6a0345f5.css │ └── main.6a0345f5.css.map │ └── js │ ├── main.0e3a9095.js.map │ └── main.js ├── dist ├── Figure.js ├── Widget.css ├── Widget.js ├── Widget.test.js ├── hooks │ └── useDatasetLoader.js ├── index.css ├── index.js └── serviceWorker.js ├── example ├── index.html └── script.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── scripts └── build-non-split.js ├── src ├── Figure.js ├── Widget.css ├── Widget.js ├── Widget.test.js ├── hooks │ └── useDatasetLoader.js ├── index.css ├── index.js └── serviceWorker.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **dashboard-js** is a lightweight javascript library built for quickly creating dashboards using data packaged data sources. If you need to visualize and present data in a convenient way and you need to do that fast then **dashboard-js** is a library for you. There are many advantages to dashboard-js: 2 | 3 | * It requires only basic knowledge of HTML, CSS 4 | * It's modular and extendable through the use of widgets 5 | * It's lightweight, fast to include and to begin with 6 | 7 | 8 | ## Getting started 9 | 10 | ### Installation 11 | 12 | Simply import bundled **dashboard-js** code inside of your HTML file: 13 | 14 | ```javascript 15 | 16 | ``` 17 | 18 | ### Setup your Dashboard 19 | 20 | Your dashboard will consist of one or more widgets. You'll need to have placeholder element for each widget and reference it by `id` in the `config`: 21 | 22 | ```html 23 |
24 | ``` 25 | 26 | ### Widget configuration 27 | 28 | To start working with widgets you need to set up `config` global variable available from `window.config` where you can specify how the dashboard should be rendered: 29 | 30 | ```js 31 | var config = { 32 | widgets: [...], 33 | datasets: [...] 34 | } 35 | ``` 36 | 37 | * `widgets` - a list of objects where each object contains information about where a widget should be injected and how it should look like. 38 | * `datasets` - a list of dataset URLs. 39 | 40 | A standard widget object should have the following structure: 41 | 42 | ``` 43 | { 44 | "elementId": "testWidget", 45 | "view": { 46 | "title": "", 47 | "legend": "", 48 | "footer": "", 49 | "resources": [ 50 | { 51 | "datasetId": "", 52 | "name": "", 53 | "transform": [] 54 | } 55 | ], 56 | "specType": "", 57 | "spec": { 58 | 59 | } 60 | } 61 | } 62 | ``` 63 | 64 | where: 65 | 66 | * `elementId` - is "id" of the attribute you want to use as a container of your widget. 67 | * `view` - descriptor of a view (widget). 68 | * `title`, `legend`, `footer` - these are optional metadata. 69 | * `resources` - a list of resources needed for a widget and required manipulations (transforms). 70 | * `datasetId` - the id (name) of the dataset from which the resource is extracted. 71 | * `name` - name of the resource. 72 | * `transform` - transformations required for a resource. 73 | * `specType` - type of a widget, e.g., `vega` or `figure`. 74 | * `spec` - specification for selected widget type. 75 | 76 | #### Figure widget 77 | 78 | A specification for "Figure widget" would have the following structure: 79 | 80 | ``` 81 | { 82 | "fieldName": "", 83 | "suffix": "", 84 | "prefix": "" 85 | } 86 | ``` 87 | 88 | where "suffix" and "prefix" attributes are optional. The "fieldName" attribute will be used to extract specific value from a row. In this example we extract the latest data and display it as a "Figure" widget - https://github.com/datopian/dashboard-js/tree/master/example. 89 | 90 | #### Vega widget 91 | 92 | You can find information about Vega here: https://datahub.io/docs/features/views#vega-graphs 93 | 94 | ### Example 95 | 96 | You can find basic example of how to use **dashboard-js** here - https://github.com/datopian/dashboard-js/tree/master/example 97 | 98 | ### More complex example 99 | 100 | One of the good examples of projects built using dashboard-js is the [London - City Dashboard](https://london.datahub.io/) a free and open data-sharing portal where anyone can access data related to London. See the code here - https://github.com/datahq/city-indicators. 101 | 102 | ![](https://i.imgur.com/JoGMc3W.png) 103 | 104 | ## How it works 105 | 106 | ```mermaid 107 | sequenceDiagram 108 | 109 | Browser->>DashboardJS: config 110 | DashboardJS->>DataJS: identifier (URL to dataset) 111 | DataJS->>DashboardJS: compiled data package 112 | DashboardJS->>DatapackageRender: view + compiled data package 113 | DatapackageRender->>DashboardJS: compiled view 114 | DashboardJS-->>DashboardJS: render Vega or Figure component 115 | DashboardJS->>Browser: Figure or Vega chart 116 | ``` 117 | ![](https://i.imgur.com/ZJ971sb.png) 118 | 119 | ## Datasets, packages, resources, Oh My! 120 | 121 | "A data package can contain multiple resources" == 122 | "A dataset can contain multiple files" 123 | 124 | ## Noteable Dependencies 125 | Dashboard-js is built on the following core dependencies: 126 | 127 | * data.js - https://github.com/datopian/data.js 128 | * datapackage-render - https://github.com/frictionlessdata/datapackage-render-js 129 | * transforms - https://github.com/frictionlessdata/datapackage-render-js/blob/master/lib/transform.js 130 | 131 | ## Maintenance 132 | 133 | *This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).* 134 | 135 | First of all, install dependencies: 136 | 137 | `npm install` or `yarn` 138 | 139 | In the project directory, you can run: 140 | 141 | ### `npm start` 142 | 143 | Runs the app in the development mode.
144 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 145 | 146 | ### `npm test` 147 | 148 | Launches the test runner in the interactive watch mode. 149 | 150 | ### `npm run build` 151 | 152 | Build the app for production into a single file `/build/static/js/main.js`. 153 | -------------------------------------------------------------------------------- /build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "main.css": "/static/css/main.6a0345f5.css", 3 | "main.js": "/static/js/main.0e3a9095.js", 4 | "main.js.map": "/static/js/main.0e3a9095.js.map", 5 | "index.html": "/index.html", 6 | "precache-manifest.9c2a8c84f78df0983cb5568c89146179.js": "/precache-manifest.9c2a8c84f78df0983cb5568c89146179.js", 7 | "service-worker.js": "/service-worker.js", 8 | "static/css/main.6a0345f5.css.map": "/static/css/main.6a0345f5.css.map" 9 | } -------------------------------------------------------------------------------- /build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datopian/dashboard-js/faf32416cb7a59e729de6971a6a59de65175d25c/build/favicon.ico -------------------------------------------------------------------------------- /build/index.html: -------------------------------------------------------------------------------- 1 | London City Dashboard
-------------------------------------------------------------------------------- /build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /build/precache-manifest.9c2a8c84f78df0983cb5568c89146179.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = [ 2 | { 3 | "revision": "0e3a90950df69d0d4ffb", 4 | "url": "/static/css/main.6a0345f5.css" 5 | }, 6 | { 7 | "revision": "0e3a90950df69d0d4ffb", 8 | "url": "/static/js/main.0e3a9095.js" 9 | }, 10 | { 11 | "revision": "2378730a2fef1f8340d9955983d03a18", 12 | "url": "/index.html" 13 | } 14 | ]; -------------------------------------------------------------------------------- /build/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js"); 15 | 16 | importScripts( 17 | "/precache-manifest.9c2a8c84f78df0983cb5568c89146179.js" 18 | ); 19 | 20 | workbox.clientsClaim(); 21 | 22 | /** 23 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 24 | * requests for URLs in the manifest. 25 | * See https://goo.gl/S9QRab 26 | */ 27 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 28 | workbox.precaching.suppressWarnings(); 29 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 30 | 31 | workbox.routing.registerNavigationRoute("/index.html", { 32 | 33 | blacklist: [/^\/_/,/\/[^/]+\.[^/]+$/], 34 | }); 35 | -------------------------------------------------------------------------------- /build/static/css/main.6a0345f5.css: -------------------------------------------------------------------------------- 1 | body{margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}@-webkit-keyframes scale{0%{-webkit-transform:scale(1);transform:scale(1);opacity:1}45%{-webkit-transform:scale(.1);transform:scale(.1);opacity:.7}80%{-webkit-transform:scale(1);transform:scale(1);opacity:1}}.ball-pulse>div:nth-child(0){-webkit-animation:scale .75s cubic-bezier(.2,.68,.18,1.08) -.36s infinite;animation:scale .75s cubic-bezier(.2,.68,.18,1.08) -.36s infinite}.ball-pulse>div:first-child{-webkit-animation:scale .75s cubic-bezier(.2,.68,.18,1.08) -.24s infinite;animation:scale .75s cubic-bezier(.2,.68,.18,1.08) -.24s infinite}.ball-pulse>div:nth-child(2){-webkit-animation:scale .75s cubic-bezier(.2,.68,.18,1.08) -.12s infinite;animation:scale .75s cubic-bezier(.2,.68,.18,1.08) -.12s infinite}.ball-pulse>div:nth-child(3){-webkit-animation:scale .75s cubic-bezier(.2,.68,.18,1.08) 0s infinite;animation:scale .75s cubic-bezier(.2,.68,.18,1.08) 0s infinite}.ball-pulse>div{background-color:#fff;width:15px;height:15px;border-radius:100%;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;display:inline-block}@-webkit-keyframes ball-pulse-sync{33%{-webkit-transform:translateY(10px);transform:translateY(10px)}66%{-webkit-transform:translateY(-10px);transform:translateY(-10px)}to{-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes ball-pulse-sync{33%{-webkit-transform:translateY(10px);transform:translateY(10px)}66%{-webkit-transform:translateY(-10px);transform:translateY(-10px)}to{-webkit-transform:translateY(0);transform:translateY(0)}}.ball-pulse-sync>div:nth-child(0){-webkit-animation:ball-pulse-sync .6s ease-in-out -.21s infinite;animation:ball-pulse-sync .6s ease-in-out -.21s infinite}.ball-pulse-sync>div:first-child{-webkit-animation:ball-pulse-sync .6s ease-in-out -.14s infinite;animation:ball-pulse-sync .6s ease-in-out -.14s infinite}.ball-pulse-sync>div:nth-child(2){-webkit-animation:ball-pulse-sync .6s ease-in-out -.07s infinite;animation:ball-pulse-sync .6s ease-in-out -.07s infinite}.ball-pulse-sync>div:nth-child(3){-webkit-animation:ball-pulse-sync .6s ease-in-out 0s infinite;animation:ball-pulse-sync .6s ease-in-out 0s infinite}.ball-pulse-sync>div{background-color:#fff;width:15px;height:15px;border-radius:100%;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;display:inline-block}@-webkit-keyframes ball-scale{0%{-webkit-transform:scale(0);transform:scale(0)}to{-webkit-transform:scale(1);transform:scale(1);opacity:0}}@keyframes ball-scale{0%{-webkit-transform:scale(0);transform:scale(0)}to{-webkit-transform:scale(1);transform:scale(1);opacity:0}}.ball-scale>div{background-color:#fff;width:15px;height:15px;border-radius:100%;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;display:inline-block;height:60px;width:60px;-webkit-animation:ball-scale 1s ease-in-out 0s infinite;animation:ball-scale 1s ease-in-out 0s infinite}.ball-scale-random{width:37px;height:40px}.ball-scale-random>div{background-color:#fff;width:15px;height:15px;border-radius:100%;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;position:absolute;display:inline-block;height:30px;width:30px;-webkit-animation:ball-scale 1s ease-in-out 0s infinite;animation:ball-scale 1s ease-in-out 0s infinite}.ball-scale-random>div:first-child{margin-left:-7px;-webkit-animation:ball-scale 1s ease-in-out .2s infinite;animation:ball-scale 1s ease-in-out .2s infinite}.ball-scale-random>div:nth-child(3){margin-left:-2px;margin-top:9px;-webkit-animation:ball-scale 1s ease-in-out .5s infinite;animation:ball-scale 1s ease-in-out .5s infinite}@-webkit-keyframes rotate{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}50%{-webkit-transform:rotate(180deg);transform:rotate(180deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.ball-rotate,.ball-rotate>div{position:relative}.ball-rotate>div{background-color:#fff;width:15px;height:15px;border-radius:100%;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both}.ball-rotate>div:first-child{-webkit-animation:rotate 1s cubic-bezier(.7,-.13,.22,.86) 0s infinite;animation:rotate 1s cubic-bezier(.7,-.13,.22,.86) 0s infinite}.ball-rotate>div:after,.ball-rotate>div:before{background-color:#fff;width:15px;height:15px;border-radius:100%;margin:2px;content:"";position:absolute;opacity:.8}.ball-rotate>div:before{top:0;left:-28px}.ball-rotate>div:after{top:0;left:25px}.ball-clip-rotate>div{background-color:#fff;width:15px;height:15px;border-radius:100%;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;border:2px solid;border-color:#fff #fff transparent;height:25px;width:25px;background:transparent!important;display:inline-block;-webkit-animation:rotate .75s linear 0s infinite;animation:rotate .75s linear 0s infinite}@keyframes scale{30%{-webkit-transform:scale(.3);transform:scale(.3)}to{-webkit-transform:scale(1);transform:scale(1)}}.ball-clip-rotate-pulse{position:relative;-webkit-transform:translateY(-15px);transform:translateY(-15px)}.ball-clip-rotate-pulse>div{-webkit-animation-fill-mode:both;animation-fill-mode:both;position:absolute;top:0;left:0;border-radius:100%}.ball-clip-rotate-pulse>div:first-child{background:#fff;height:16px;width:16px;top:7px;left:-7px;-webkit-animation:scale 1s cubic-bezier(.09,.57,.49,.9) 0s infinite;animation:scale 1s cubic-bezier(.09,.57,.49,.9) 0s infinite}.ball-clip-rotate-pulse>div:last-child{position:absolute;width:30px;height:30px;left:-16px;top:-2px;background:transparent;border:2px solid;border-color:#fff transparent;border-style:solid;border-width:2px;-webkit-animation:rotate 1s cubic-bezier(.09,.57,.49,.9) 0s infinite;animation:rotate 1s cubic-bezier(.09,.57,.49,.9) 0s infinite;-webkit-animation-duration:1s;animation-duration:1s}@keyframes rotate{0%{-webkit-transform:rotate(0deg) scale(1);transform:rotate(0deg) scale(1)}50%{-webkit-transform:rotate(180deg) scale(.6);transform:rotate(180deg) scale(.6)}to{-webkit-transform:rotate(1turn) scale(1);transform:rotate(1turn) scale(1)}}.ball-clip-rotate-multiple{position:relative}.ball-clip-rotate-multiple>div{-webkit-animation-fill-mode:both;animation-fill-mode:both;position:absolute;left:-20px;top:-20px;border-color:transparent #fff;border-style:solid;border-width:2px;border-radius:100%;height:35px;width:35px;-webkit-animation:rotate 1s ease-in-out 0s infinite;animation:rotate 1s ease-in-out 0s infinite}.ball-clip-rotate-multiple>div:last-child{display:inline-block;top:-10px;left:-10px;width:15px;height:15px;-webkit-animation-duration:.5s;animation-duration:.5s;border-color:#fff transparent;-webkit-animation-direction:reverse;animation-direction:reverse}@-webkit-keyframes ball-scale-ripple{0%{-webkit-transform:scale(.1);transform:scale(.1);opacity:1}70%{-webkit-transform:scale(1);transform:scale(1);opacity:.7}to{opacity:0}}@keyframes ball-scale-ripple{0%{-webkit-transform:scale(.1);transform:scale(.1);opacity:1}70%{-webkit-transform:scale(1);transform:scale(1);opacity:.7}to{opacity:0}}.ball-scale-ripple>div{-webkit-animation-fill-mode:both;animation-fill-mode:both;height:50px;width:50px;border-radius:100%;border:2px solid #fff;-webkit-animation:ball-scale-ripple 1s cubic-bezier(.21,.53,.56,.8) 0s infinite;animation:ball-scale-ripple 1s cubic-bezier(.21,.53,.56,.8) 0s infinite}@-webkit-keyframes ball-scale-ripple-multiple{0%{-webkit-transform:scale(.1);transform:scale(.1);opacity:1}70%{-webkit-transform:scale(1);transform:scale(1);opacity:.7}to{opacity:0}}@keyframes ball-scale-ripple-multiple{0%{-webkit-transform:scale(.1);transform:scale(.1);opacity:1}70%{-webkit-transform:scale(1);transform:scale(1);opacity:.7}to{opacity:0}}.ball-scale-ripple-multiple{position:relative;-webkit-transform:translateY(-25px);transform:translateY(-25px)}.ball-scale-ripple-multiple>div:nth-child(0){-webkit-animation-delay:-.8s;animation-delay:-.8s}.ball-scale-ripple-multiple>div:first-child{-webkit-animation-delay:-.6s;animation-delay:-.6s}.ball-scale-ripple-multiple>div:nth-child(2){-webkit-animation-delay:-.4s;animation-delay:-.4s}.ball-scale-ripple-multiple>div:nth-child(3){-webkit-animation-delay:-.2s;animation-delay:-.2s}.ball-scale-ripple-multiple>div{-webkit-animation-fill-mode:both;animation-fill-mode:both;position:absolute;top:-2px;left:-26px;width:50px;height:50px;border-radius:100%;border:2px solid #fff;-webkit-animation:ball-scale-ripple-multiple 1.25s cubic-bezier(.21,.53,.56,.8) 0s infinite;animation:ball-scale-ripple-multiple 1.25s cubic-bezier(.21,.53,.56,.8) 0s infinite}@-webkit-keyframes ball-beat{50%{opacity:.2;-webkit-transform:scale(.75);transform:scale(.75)}to{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes ball-beat{50%{opacity:.2;-webkit-transform:scale(.75);transform:scale(.75)}to{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}.ball-beat>div{background-color:#fff;width:15px;height:15px;border-radius:100%;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;display:inline-block;-webkit-animation:ball-beat .7s linear 0s infinite;animation:ball-beat .7s linear 0s infinite}.ball-beat>div:nth-child(2n-1){-webkit-animation-delay:-.35s!important;animation-delay:-.35s!important}@-webkit-keyframes ball-scale-multiple{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}5%{opacity:1}to{-webkit-transform:scale(1);transform:scale(1);opacity:0}}@keyframes ball-scale-multiple{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}5%{opacity:1}to{-webkit-transform:scale(1);transform:scale(1);opacity:0}}.ball-scale-multiple{position:relative;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.ball-scale-multiple>div:nth-child(2){-webkit-animation-delay:-.4s;animation-delay:-.4s}.ball-scale-multiple>div:nth-child(3){-webkit-animation-delay:-.2s;animation-delay:-.2s}.ball-scale-multiple>div{background-color:#fff;width:15px;height:15px;border-radius:100%;-webkit-animation-fill-mode:both;animation-fill-mode:both;position:absolute;left:-30px;top:0;opacity:0;margin:0;width:60px;height:60px;-webkit-animation:ball-scale-multiple 1s linear 0s infinite;animation:ball-scale-multiple 1s linear 0s infinite}@-webkit-keyframes ball-triangle-path-1{33%{-webkit-transform:translate(25px,-50px);transform:translate(25px,-50px)}66%{-webkit-transform:translate(50px);transform:translate(50px)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes ball-triangle-path-1{33%{-webkit-transform:translate(25px,-50px);transform:translate(25px,-50px)}66%{-webkit-transform:translate(50px);transform:translate(50px)}to{-webkit-transform:translate(0);transform:translate(0)}}@-webkit-keyframes ball-triangle-path-2{33%{-webkit-transform:translate(25px,50px);transform:translate(25px,50px)}66%{-webkit-transform:translate(-25px,50px);transform:translate(-25px,50px)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes ball-triangle-path-2{33%{-webkit-transform:translate(25px,50px);transform:translate(25px,50px)}66%{-webkit-transform:translate(-25px,50px);transform:translate(-25px,50px)}to{-webkit-transform:translate(0);transform:translate(0)}}@-webkit-keyframes ball-triangle-path-3{33%{-webkit-transform:translate(-50px);transform:translate(-50px)}66%{-webkit-transform:translate(-25px,-50px);transform:translate(-25px,-50px)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes ball-triangle-path-3{33%{-webkit-transform:translate(-50px);transform:translate(-50px)}66%{-webkit-transform:translate(-25px,-50px);transform:translate(-25px,-50px)}to{-webkit-transform:translate(0);transform:translate(0)}}.ball-triangle-path{position:relative;-webkit-transform:translate(-29.994px,-37.50938px);transform:translate(-29.994px,-37.50938px)}.ball-triangle-path>div:first-child{-webkit-animation-name:ball-triangle-path-1;animation-name:ball-triangle-path-1;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.ball-triangle-path>div:first-child,.ball-triangle-path>div:nth-child(2){-webkit-animation-delay:0;animation-delay:0;-webkit-animation-duration:2s;animation-duration:2s}.ball-triangle-path>div:nth-child(2){-webkit-animation-name:ball-triangle-path-2;animation-name:ball-triangle-path-2;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.ball-triangle-path>div:nth-child(3){-webkit-animation-name:ball-triangle-path-3;animation-name:ball-triangle-path-3;-webkit-animation-delay:0;animation-delay:0;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.ball-triangle-path>div{-webkit-animation-fill-mode:both;animation-fill-mode:both;position:absolute;width:10px;height:10px;border-radius:100%;border:1px solid #fff}.ball-triangle-path>div:first-of-type{top:50px}.ball-triangle-path>div:nth-of-type(2){left:25px}.ball-triangle-path>div:nth-of-type(3){top:50px;left:50px}@-webkit-keyframes ball-pulse-rise-even{0%{-webkit-transform:scale(1.1);transform:scale(1.1)}25%{-webkit-transform:translateY(-30px);transform:translateY(-30px)}50%{-webkit-transform:scale(.4);transform:scale(.4)}75%{-webkit-transform:translateY(30px);transform:translateY(30px)}to{-webkit-transform:translateY(0);transform:translateY(0);-webkit-transform:scale(1);transform:scale(1)}}@keyframes ball-pulse-rise-even{0%{-webkit-transform:scale(1.1);transform:scale(1.1)}25%{-webkit-transform:translateY(-30px);transform:translateY(-30px)}50%{-webkit-transform:scale(.4);transform:scale(.4)}75%{-webkit-transform:translateY(30px);transform:translateY(30px)}to{-webkit-transform:translateY(0);transform:translateY(0);-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes ball-pulse-rise-odd{0%{-webkit-transform:scale(.4);transform:scale(.4)}25%{-webkit-transform:translateY(30px);transform:translateY(30px)}50%{-webkit-transform:scale(1.1);transform:scale(1.1)}75%{-webkit-transform:translateY(-30px);transform:translateY(-30px)}to{-webkit-transform:translateY(0);transform:translateY(0);-webkit-transform:scale(.75);transform:scale(.75)}}@keyframes ball-pulse-rise-odd{0%{-webkit-transform:scale(.4);transform:scale(.4)}25%{-webkit-transform:translateY(30px);transform:translateY(30px)}50%{-webkit-transform:scale(1.1);transform:scale(1.1)}75%{-webkit-transform:translateY(-30px);transform:translateY(-30px)}to{-webkit-transform:translateY(0);transform:translateY(0);-webkit-transform:scale(.75);transform:scale(.75)}}.ball-pulse-rise>div{background-color:#fff;width:15px;height:15px;border-radius:100%;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;display:inline-block;-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-timing-function:cubic-bezier(.15,.46,.9,.6);animation-timing-function:cubic-bezier(.15,.46,.9,.6);-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-delay:0;animation-delay:0}.ball-pulse-rise>div:nth-child(2n){-webkit-animation-name:ball-pulse-rise-even;animation-name:ball-pulse-rise-even}.ball-pulse-rise>div:nth-child(2n-1){-webkit-animation-name:ball-pulse-rise-odd;animation-name:ball-pulse-rise-odd}@-webkit-keyframes ball-grid-beat{50%{opacity:.7}to{opacity:1}}@keyframes ball-grid-beat{50%{opacity:.7}to{opacity:1}}.ball-grid-beat{width:57px}.ball-grid-beat>div:first-child{-webkit-animation-delay:.44s;animation-delay:.44s;-webkit-animation-duration:1.27s;animation-duration:1.27s}.ball-grid-beat>div:nth-child(2){-webkit-animation-delay:.2s;animation-delay:.2s;-webkit-animation-duration:1.52s;animation-duration:1.52s}.ball-grid-beat>div:nth-child(3){-webkit-animation-delay:.14s;animation-delay:.14s;-webkit-animation-duration:.61s;animation-duration:.61s}.ball-grid-beat>div:nth-child(4){-webkit-animation-delay:.15s;animation-delay:.15s;-webkit-animation-duration:.82s;animation-duration:.82s}.ball-grid-beat>div:nth-child(5){-webkit-animation-delay:-.01s;animation-delay:-.01s;-webkit-animation-duration:1.24s;animation-duration:1.24s}.ball-grid-beat>div:nth-child(6){-webkit-animation-delay:-.07s;animation-delay:-.07s;-webkit-animation-duration:1.35s;animation-duration:1.35s}.ball-grid-beat>div:nth-child(7){-webkit-animation-delay:.29s;animation-delay:.29s;-webkit-animation-duration:1.44s;animation-duration:1.44s}.ball-grid-beat>div:nth-child(8){-webkit-animation-delay:.63s;animation-delay:.63s;-webkit-animation-duration:1.19s;animation-duration:1.19s}.ball-grid-beat>div:nth-child(9){-webkit-animation-delay:-.18s;animation-delay:-.18s;-webkit-animation-duration:1.48s;animation-duration:1.48s}.ball-grid-beat>div{background-color:#fff;width:15px;height:15px;border-radius:100%;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;display:inline-block;float:left;-webkit-animation-name:ball-grid-beat;animation-name:ball-grid-beat;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-delay:0;animation-delay:0}@-webkit-keyframes ball-grid-pulse{0%{-webkit-transform:scale(1);transform:scale(1)}50%{-webkit-transform:scale(.5);transform:scale(.5);opacity:.7}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes ball-grid-pulse{0%{-webkit-transform:scale(1);transform:scale(1)}50%{-webkit-transform:scale(.5);transform:scale(.5);opacity:.7}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}.ball-grid-pulse{width:57px}.ball-grid-pulse>div:first-child{-webkit-animation-delay:.58s;animation-delay:.58s;-webkit-animation-duration:.9s;animation-duration:.9s}.ball-grid-pulse>div:nth-child(2){-webkit-animation-delay:.01s;animation-delay:.01s;-webkit-animation-duration:.94s;animation-duration:.94s}.ball-grid-pulse>div:nth-child(3){-webkit-animation-delay:.25s;animation-delay:.25s;-webkit-animation-duration:1.43s;animation-duration:1.43s}.ball-grid-pulse>div:nth-child(4){-webkit-animation-delay:-.03s;animation-delay:-.03s;-webkit-animation-duration:.74s;animation-duration:.74s}.ball-grid-pulse>div:nth-child(5){-webkit-animation-delay:.21s;animation-delay:.21s;-webkit-animation-duration:.68s;animation-duration:.68s}.ball-grid-pulse>div:nth-child(6){-webkit-animation-delay:.25s;animation-delay:.25s;-webkit-animation-duration:1.17s;animation-duration:1.17s}.ball-grid-pulse>div:nth-child(7){-webkit-animation-delay:.46s;animation-delay:.46s;-webkit-animation-duration:1.41s;animation-duration:1.41s}.ball-grid-pulse>div:nth-child(8){-webkit-animation-delay:.02s;animation-delay:.02s;-webkit-animation-duration:1.56s;animation-duration:1.56s}.ball-grid-pulse>div:nth-child(9){-webkit-animation-delay:.13s;animation-delay:.13s;-webkit-animation-duration:.78s;animation-duration:.78s}.ball-grid-pulse>div{background-color:#fff;width:15px;height:15px;border-radius:100%;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;display:inline-block;float:left;-webkit-animation-name:ball-grid-pulse;animation-name:ball-grid-pulse;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-delay:0;animation-delay:0}@-webkit-keyframes ball-spin-fade-loader{50%{opacity:.3;-webkit-transform:scale(.4);transform:scale(.4)}to{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes ball-spin-fade-loader{50%{opacity:.3;-webkit-transform:scale(.4);transform:scale(.4)}to{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}.ball-spin-fade-loader{position:relative;top:-10px;left:-10px}.ball-spin-fade-loader>div:first-child{top:25px;left:0;-webkit-animation:ball-spin-fade-loader 1s linear -.96s infinite;animation:ball-spin-fade-loader 1s linear -.96s infinite}.ball-spin-fade-loader>div:nth-child(2){top:17.04545px;left:17.04545px;-webkit-animation:ball-spin-fade-loader 1s linear -.84s infinite;animation:ball-spin-fade-loader 1s linear -.84s infinite}.ball-spin-fade-loader>div:nth-child(3){top:0;left:25px;-webkit-animation:ball-spin-fade-loader 1s linear -.72s infinite;animation:ball-spin-fade-loader 1s linear -.72s infinite}.ball-spin-fade-loader>div:nth-child(4){top:-17.04545px;left:17.04545px;-webkit-animation:ball-spin-fade-loader 1s linear -.6s infinite;animation:ball-spin-fade-loader 1s linear -.6s infinite}.ball-spin-fade-loader>div:nth-child(5){top:-25px;left:0;-webkit-animation:ball-spin-fade-loader 1s linear -.48s infinite;animation:ball-spin-fade-loader 1s linear -.48s infinite}.ball-spin-fade-loader>div:nth-child(6){top:-17.04545px;left:-17.04545px;-webkit-animation:ball-spin-fade-loader 1s linear -.36s infinite;animation:ball-spin-fade-loader 1s linear -.36s infinite}.ball-spin-fade-loader>div:nth-child(7){top:0;left:-25px;-webkit-animation:ball-spin-fade-loader 1s linear -.24s infinite;animation:ball-spin-fade-loader 1s linear -.24s infinite}.ball-spin-fade-loader>div:nth-child(8){top:17.04545px;left:-17.04545px;-webkit-animation:ball-spin-fade-loader 1s linear -.12s infinite;animation:ball-spin-fade-loader 1s linear -.12s infinite}.ball-spin-fade-loader>div{background-color:#fff;width:15px;height:15px;border-radius:100%;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;position:absolute}@-webkit-keyframes ball-spin-loader{75%{opacity:.2}to{opacity:1}}@keyframes ball-spin-loader{75%{opacity:.2}to{opacity:1}}.ball-spin-loader{position:relative}.ball-spin-loader>span:first-child{top:45px;left:0;-webkit-animation:ball-spin-loader 2s linear .9s infinite;animation:ball-spin-loader 2s linear .9s infinite}.ball-spin-loader>span:nth-child(2){top:30.68182px;left:30.68182px;-webkit-animation:ball-spin-loader 2s linear 1.8s infinite;animation:ball-spin-loader 2s linear 1.8s infinite}.ball-spin-loader>span:nth-child(3){top:0;left:45px;-webkit-animation:ball-spin-loader 2s linear 2.7s infinite;animation:ball-spin-loader 2s linear 2.7s infinite}.ball-spin-loader>span:nth-child(4){top:-30.68182px;left:30.68182px;-webkit-animation:ball-spin-loader 2s linear 3.6s infinite;animation:ball-spin-loader 2s linear 3.6s infinite}.ball-spin-loader>span:nth-child(5){top:-45px;left:0;-webkit-animation:ball-spin-loader 2s linear 4.5s infinite;animation:ball-spin-loader 2s linear 4.5s infinite}.ball-spin-loader>span:nth-child(6){top:-30.68182px;left:-30.68182px;-webkit-animation:ball-spin-loader 2s linear 5.4s infinite;animation:ball-spin-loader 2s linear 5.4s infinite}.ball-spin-loader>span:nth-child(7){top:0;left:-45px;-webkit-animation:ball-spin-loader 2s linear 6.3s infinite;animation:ball-spin-loader 2s linear 6.3s infinite}.ball-spin-loader>span:nth-child(8){top:30.68182px;left:-30.68182px;-webkit-animation:ball-spin-loader 2s linear 7.2s infinite;animation:ball-spin-loader 2s linear 7.2s infinite}.ball-spin-loader>div{-webkit-animation-fill-mode:both;animation-fill-mode:both;position:absolute;width:15px;height:15px;border-radius:100%;background:green}@-webkit-keyframes ball-zig{33%{-webkit-transform:translate(-15px,-30px);transform:translate(-15px,-30px)}66%{-webkit-transform:translate(15px,-30px);transform:translate(15px,-30px)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes ball-zig{33%{-webkit-transform:translate(-15px,-30px);transform:translate(-15px,-30px)}66%{-webkit-transform:translate(15px,-30px);transform:translate(15px,-30px)}to{-webkit-transform:translate(0);transform:translate(0)}}@-webkit-keyframes ball-zag{33%{-webkit-transform:translate(15px,30px);transform:translate(15px,30px)}66%{-webkit-transform:translate(-15px,30px);transform:translate(-15px,30px)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes ball-zag{33%{-webkit-transform:translate(15px,30px);transform:translate(15px,30px)}66%{-webkit-transform:translate(-15px,30px);transform:translate(-15px,30px)}to{-webkit-transform:translate(0);transform:translate(0)}}.ball-zig-zag{position:relative;-webkit-transform:translate(-15px,-15px);transform:translate(-15px,-15px)}.ball-zig-zag>div{background-color:#fff;width:15px;height:15px;border-radius:100%;-webkit-animation-fill-mode:both;animation-fill-mode:both;position:absolute;margin:2px 2px 2px 15px;top:4px;left:-7px}.ball-zig-zag>div:first-child{-webkit-animation:ball-zig .7s linear 0s infinite;animation:ball-zig .7s linear 0s infinite}.ball-zig-zag>div:last-child{-webkit-animation:ball-zag .7s linear 0s infinite;animation:ball-zag .7s linear 0s infinite}@-webkit-keyframes ball-zig-deflect{17%{-webkit-transform:translate(-15px,-30px);transform:translate(-15px,-30px)}34%{-webkit-transform:translate(15px,-30px);transform:translate(15px,-30px)}50%{-webkit-transform:translate(0);transform:translate(0)}67%{-webkit-transform:translate(15px,-30px);transform:translate(15px,-30px)}84%{-webkit-transform:translate(-15px,-30px);transform:translate(-15px,-30px)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes ball-zig-deflect{17%{-webkit-transform:translate(-15px,-30px);transform:translate(-15px,-30px)}34%{-webkit-transform:translate(15px,-30px);transform:translate(15px,-30px)}50%{-webkit-transform:translate(0);transform:translate(0)}67%{-webkit-transform:translate(15px,-30px);transform:translate(15px,-30px)}84%{-webkit-transform:translate(-15px,-30px);transform:translate(-15px,-30px)}to{-webkit-transform:translate(0);transform:translate(0)}}@-webkit-keyframes ball-zag-deflect{17%{-webkit-transform:translate(15px,30px);transform:translate(15px,30px)}34%{-webkit-transform:translate(-15px,30px);transform:translate(-15px,30px)}50%{-webkit-transform:translate(0);transform:translate(0)}67%{-webkit-transform:translate(-15px,30px);transform:translate(-15px,30px)}84%{-webkit-transform:translate(15px,30px);transform:translate(15px,30px)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes ball-zag-deflect{17%{-webkit-transform:translate(15px,30px);transform:translate(15px,30px)}34%{-webkit-transform:translate(-15px,30px);transform:translate(-15px,30px)}50%{-webkit-transform:translate(0);transform:translate(0)}67%{-webkit-transform:translate(-15px,30px);transform:translate(-15px,30px)}84%{-webkit-transform:translate(15px,30px);transform:translate(15px,30px)}to{-webkit-transform:translate(0);transform:translate(0)}}.ball-zig-zag-deflect{position:relative;-webkit-transform:translate(-15px,-15px);transform:translate(-15px,-15px)}.ball-zig-zag-deflect>div{background-color:#fff;width:15px;height:15px;border-radius:100%;-webkit-animation-fill-mode:both;animation-fill-mode:both;position:absolute;margin:2px 2px 2px 15px;top:4px;left:-7px}.ball-zig-zag-deflect>div:first-child{-webkit-animation:ball-zig-deflect 1.5s linear 0s infinite;animation:ball-zig-deflect 1.5s linear 0s infinite}.ball-zig-zag-deflect>div:last-child{-webkit-animation:ball-zag-deflect 1.5s linear 0s infinite;animation:ball-zag-deflect 1.5s linear 0s infinite}@-webkit-keyframes line-scale{0%{-webkit-transform:scaley(1);transform:scaley(1)}50%{-webkit-transform:scaley(.4);transform:scaley(.4)}to{-webkit-transform:scaley(1);transform:scaley(1)}}@keyframes line-scale{0%{-webkit-transform:scaley(1);transform:scaley(1)}50%{-webkit-transform:scaley(.4);transform:scaley(.4)}to{-webkit-transform:scaley(1);transform:scaley(1)}}.line-scale>div:first-child{-webkit-animation:line-scale 1s cubic-bezier(.2,.68,.18,1.08) -.4s infinite;animation:line-scale 1s cubic-bezier(.2,.68,.18,1.08) -.4s infinite}.line-scale>div:nth-child(2){-webkit-animation:line-scale 1s cubic-bezier(.2,.68,.18,1.08) -.3s infinite;animation:line-scale 1s cubic-bezier(.2,.68,.18,1.08) -.3s infinite}.line-scale>div:nth-child(3){-webkit-animation:line-scale 1s cubic-bezier(.2,.68,.18,1.08) -.2s infinite;animation:line-scale 1s cubic-bezier(.2,.68,.18,1.08) -.2s infinite}.line-scale>div:nth-child(4){-webkit-animation:line-scale 1s cubic-bezier(.2,.68,.18,1.08) -.1s infinite;animation:line-scale 1s cubic-bezier(.2,.68,.18,1.08) -.1s infinite}.line-scale>div:nth-child(5){-webkit-animation:line-scale 1s cubic-bezier(.2,.68,.18,1.08) 0s infinite;animation:line-scale 1s cubic-bezier(.2,.68,.18,1.08) 0s infinite}.line-scale>div{background-color:#fff;width:4px;height:35px;border-radius:2px;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;display:inline-block}@-webkit-keyframes line-scale-party{0%{-webkit-transform:scale(1);transform:scale(1)}50%{-webkit-transform:scale(.5);transform:scale(.5)}to{-webkit-transform:scale(1);transform:scale(1)}}@keyframes line-scale-party{0%{-webkit-transform:scale(1);transform:scale(1)}50%{-webkit-transform:scale(.5);transform:scale(.5)}to{-webkit-transform:scale(1);transform:scale(1)}}.line-scale-party>div:first-child{-webkit-animation-delay:-.09s;animation-delay:-.09s;-webkit-animation-duration:.83s;animation-duration:.83s}.line-scale-party>div:nth-child(2){-webkit-animation-delay:.33s;animation-delay:.33s;-webkit-animation-duration:.64s;animation-duration:.64s}.line-scale-party>div:nth-child(3){-webkit-animation-delay:.32s;animation-delay:.32s;-webkit-animation-duration:.39s;animation-duration:.39s}.line-scale-party>div:nth-child(4){-webkit-animation-delay:.47s;animation-delay:.47s;-webkit-animation-duration:.52s;animation-duration:.52s}.line-scale-party>div{background-color:#fff;width:4px;height:35px;border-radius:2px;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;display:inline-block;-webkit-animation-name:line-scale-party;animation-name:line-scale-party;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-delay:0;animation-delay:0}@-webkit-keyframes line-scale-pulse-out{0%{-webkit-transform:scaley(1);transform:scaley(1)}50%{-webkit-transform:scaley(.4);transform:scaley(.4)}to{-webkit-transform:scaley(1);transform:scaley(1)}}@keyframes line-scale-pulse-out{0%{-webkit-transform:scaley(1);transform:scaley(1)}50%{-webkit-transform:scaley(.4);transform:scaley(.4)}to{-webkit-transform:scaley(1);transform:scaley(1)}}.line-scale-pulse-out>div{background-color:#fff;width:4px;height:35px;border-radius:2px;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;display:inline-block;-webkit-animation:line-scale-pulse-out .9s cubic-bezier(.85,.25,.37,.85) -.6s infinite;animation:line-scale-pulse-out .9s cubic-bezier(.85,.25,.37,.85) -.6s infinite}.line-scale-pulse-out>div:nth-child(2),.line-scale-pulse-out>div:nth-child(4){-webkit-animation-delay:-.4s!important;animation-delay:-.4s!important}.line-scale-pulse-out>div:first-child,.line-scale-pulse-out>div:nth-child(5){-webkit-animation-delay:-.2s!important;animation-delay:-.2s!important}@-webkit-keyframes line-scale-pulse-out-rapid{0%{-webkit-transform:scaley(1);transform:scaley(1)}80%{-webkit-transform:scaley(.3);transform:scaley(.3)}90%{-webkit-transform:scaley(1);transform:scaley(1)}}@keyframes line-scale-pulse-out-rapid{0%{-webkit-transform:scaley(1);transform:scaley(1)}80%{-webkit-transform:scaley(.3);transform:scaley(.3)}90%{-webkit-transform:scaley(1);transform:scaley(1)}}.line-scale-pulse-out-rapid>div{background-color:#fff;width:4px;height:35px;border-radius:2px;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;display:inline-block;-webkit-animation:line-scale-pulse-out-rapid .9s cubic-bezier(.11,.49,.38,.78) -.5s infinite;animation:line-scale-pulse-out-rapid .9s cubic-bezier(.11,.49,.38,.78) -.5s infinite}.line-scale-pulse-out-rapid>div:nth-child(2),.line-scale-pulse-out-rapid>div:nth-child(4){-webkit-animation-delay:-.25s!important;animation-delay:-.25s!important}.line-scale-pulse-out-rapid>div:first-child,.line-scale-pulse-out-rapid>div:nth-child(5){-webkit-animation-delay:0s!important;animation-delay:0s!important}@-webkit-keyframes line-spin-fade-loader{50%{opacity:.3}to{opacity:1}}@keyframes line-spin-fade-loader{50%{opacity:.3}to{opacity:1}}.line-spin-fade-loader{position:relative;top:-10px;left:-4px}.line-spin-fade-loader>div:first-child{top:20px;left:0;-webkit-animation:line-spin-fade-loader 1.2s ease-in-out -.84s infinite;animation:line-spin-fade-loader 1.2s ease-in-out -.84s infinite}.line-spin-fade-loader>div:nth-child(2){top:13.63636px;left:13.63636px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-animation:line-spin-fade-loader 1.2s ease-in-out -.72s infinite;animation:line-spin-fade-loader 1.2s ease-in-out -.72s infinite}.line-spin-fade-loader>div:nth-child(3){top:0;left:20px;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-animation:line-spin-fade-loader 1.2s ease-in-out -.6s infinite;animation:line-spin-fade-loader 1.2s ease-in-out -.6s infinite}.line-spin-fade-loader>div:nth-child(4){top:-13.63636px;left:13.63636px;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-animation:line-spin-fade-loader 1.2s ease-in-out -.48s infinite;animation:line-spin-fade-loader 1.2s ease-in-out -.48s infinite}.line-spin-fade-loader>div:nth-child(5){top:-20px;left:0;-webkit-animation:line-spin-fade-loader 1.2s ease-in-out -.36s infinite;animation:line-spin-fade-loader 1.2s ease-in-out -.36s infinite}.line-spin-fade-loader>div:nth-child(6){top:-13.63636px;left:-13.63636px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-animation:line-spin-fade-loader 1.2s ease-in-out -.24s infinite;animation:line-spin-fade-loader 1.2s ease-in-out -.24s infinite}.line-spin-fade-loader>div:nth-child(7){top:0;left:-20px;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-animation:line-spin-fade-loader 1.2s ease-in-out -.12s infinite;animation:line-spin-fade-loader 1.2s ease-in-out -.12s infinite}.line-spin-fade-loader>div:nth-child(8){top:13.63636px;left:-13.63636px;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-animation:line-spin-fade-loader 1.2s ease-in-out 0s infinite;animation:line-spin-fade-loader 1.2s ease-in-out 0s infinite}.line-spin-fade-loader>div{background-color:#fff;width:4px;height:35px;border-radius:2px;margin:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;position:absolute;width:5px;height:15px}@-webkit-keyframes triangle-skew-spin{25%{-webkit-transform:perspective(100px) rotateX(180deg) rotateY(0);transform:perspective(100px) rotateX(180deg) rotateY(0)}50%{-webkit-transform:perspective(100px) rotateX(180deg) rotateY(180deg);transform:perspective(100px) rotateX(180deg) rotateY(180deg)}75%{-webkit-transform:perspective(100px) rotateX(0) rotateY(180deg);transform:perspective(100px) rotateX(0) rotateY(180deg)}to{-webkit-transform:perspective(100px) rotateX(0) rotateY(0);transform:perspective(100px) rotateX(0) rotateY(0)}}@keyframes triangle-skew-spin{25%{-webkit-transform:perspective(100px) rotateX(180deg) rotateY(0);transform:perspective(100px) rotateX(180deg) rotateY(0)}50%{-webkit-transform:perspective(100px) rotateX(180deg) rotateY(180deg);transform:perspective(100px) rotateX(180deg) rotateY(180deg)}75%{-webkit-transform:perspective(100px) rotateX(0) rotateY(180deg);transform:perspective(100px) rotateX(0) rotateY(180deg)}to{-webkit-transform:perspective(100px) rotateX(0) rotateY(0);transform:perspective(100px) rotateX(0) rotateY(0)}}.triangle-skew-spin>div{-webkit-animation-fill-mode:both;animation-fill-mode:both;width:0;height:0;border-left:20px solid transparent;border-right:20px solid transparent;border-bottom:20px solid #fff;-webkit-animation:triangle-skew-spin 3s cubic-bezier(.09,.57,.49,.9) 0s infinite;animation:triangle-skew-spin 3s cubic-bezier(.09,.57,.49,.9) 0s infinite}@-webkit-keyframes square-spin{25%{-webkit-transform:perspective(100px) rotateX(180deg) rotateY(0);transform:perspective(100px) rotateX(180deg) rotateY(0)}50%{-webkit-transform:perspective(100px) rotateX(180deg) rotateY(180deg);transform:perspective(100px) rotateX(180deg) rotateY(180deg)}75%{-webkit-transform:perspective(100px) rotateX(0) rotateY(180deg);transform:perspective(100px) rotateX(0) rotateY(180deg)}to{-webkit-transform:perspective(100px) rotateX(0) rotateY(0);transform:perspective(100px) rotateX(0) rotateY(0)}}@keyframes square-spin{25%{-webkit-transform:perspective(100px) rotateX(180deg) rotateY(0);transform:perspective(100px) rotateX(180deg) rotateY(0)}50%{-webkit-transform:perspective(100px) rotateX(180deg) rotateY(180deg);transform:perspective(100px) rotateX(180deg) rotateY(180deg)}75%{-webkit-transform:perspective(100px) rotateX(0) rotateY(180deg);transform:perspective(100px) rotateX(0) rotateY(180deg)}to{-webkit-transform:perspective(100px) rotateX(0) rotateY(0);transform:perspective(100px) rotateX(0) rotateY(0)}}.square-spin>div{-webkit-animation-fill-mode:both;animation-fill-mode:both;width:50px;height:50px;background:#fff;border:1px solid red;-webkit-animation:square-spin 3s cubic-bezier(.09,.57,.49,.9) 0s infinite;animation:square-spin 3s cubic-bezier(.09,.57,.49,.9) 0s infinite}@-webkit-keyframes rotate_pacman_half_up{0%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}50%{-webkit-transform:rotate(1turn);transform:rotate(1turn)}to{-webkit-transform:rotate(270deg);transform:rotate(270deg)}}@keyframes rotate_pacman_half_up{0%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}50%{-webkit-transform:rotate(1turn);transform:rotate(1turn)}to{-webkit-transform:rotate(270deg);transform:rotate(270deg)}}@-webkit-keyframes rotate_pacman_half_down{0%{-webkit-transform:rotate(90deg);transform:rotate(90deg)}50%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(90deg);transform:rotate(90deg)}}@keyframes rotate_pacman_half_down{0%{-webkit-transform:rotate(90deg);transform:rotate(90deg)}50%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(90deg);transform:rotate(90deg)}}@-webkit-keyframes pacman-balls{75%{opacity:.7}to{-webkit-transform:translate(-100px,-6.25px);transform:translate(-100px,-6.25px)}}@keyframes pacman-balls{75%{opacity:.7}to{-webkit-transform:translate(-100px,-6.25px);transform:translate(-100px,-6.25px)}}.pacman{position:relative}.pacman>div:nth-child(2){-webkit-animation:pacman-balls 1s linear -.99s infinite;animation:pacman-balls 1s linear -.99s infinite}.pacman>div:nth-child(3){-webkit-animation:pacman-balls 1s linear -.66s infinite;animation:pacman-balls 1s linear -.66s infinite}.pacman>div:nth-child(4){-webkit-animation:pacman-balls 1s linear -.33s infinite;animation:pacman-balls 1s linear -.33s infinite}.pacman>div:nth-child(5){-webkit-animation:pacman-balls 1s linear 0s infinite;animation:pacman-balls 1s linear 0s infinite}.pacman>div:first-of-type{-webkit-animation:rotate_pacman_half_up .5s 0s infinite;animation:rotate_pacman_half_up .5s 0s infinite}.pacman>div:first-of-type,.pacman>div:nth-child(2){width:0;height:0;border:25px solid #fff;border-right-color:transparent;border-radius:25px;position:relative;left:-30px}.pacman>div:nth-child(2){-webkit-animation:rotate_pacman_half_down .5s 0s infinite;animation:rotate_pacman_half_down .5s 0s infinite;margin-top:-50px}.pacman>div:nth-child(3),.pacman>div:nth-child(4),.pacman>div:nth-child(5),.pacman>div:nth-child(6){background-color:#fff;width:15px;height:15px;border-radius:100%;margin:2px;width:10px;height:10px;position:absolute;-webkit-transform:translateY(-6.25px);transform:translateY(-6.25px);top:25px;left:70px}@-webkit-keyframes cube-transition{25%{-webkit-transform:translateX(50px) scale(.5) rotate(-90deg);transform:translateX(50px) scale(.5) rotate(-90deg)}50%{-webkit-transform:translate(50px,50px) rotate(-180deg);transform:translate(50px,50px) rotate(-180deg)}75%{-webkit-transform:translateY(50px) scale(.5) rotate(-270deg);transform:translateY(50px) scale(.5) rotate(-270deg)}to{-webkit-transform:rotate(-1turn);transform:rotate(-1turn)}}@keyframes cube-transition{25%{-webkit-transform:translateX(50px) scale(.5) rotate(-90deg);transform:translateX(50px) scale(.5) rotate(-90deg)}50%{-webkit-transform:translate(50px,50px) rotate(-180deg);transform:translate(50px,50px) rotate(-180deg)}75%{-webkit-transform:translateY(50px) scale(.5) rotate(-270deg);transform:translateY(50px) scale(.5) rotate(-270deg)}to{-webkit-transform:rotate(-1turn);transform:rotate(-1turn)}}.cube-transition{position:relative;-webkit-transform:translate(-25px,-25px);transform:translate(-25px,-25px)}.cube-transition>div{-webkit-animation-fill-mode:both;animation-fill-mode:both;width:10px;height:10px;position:absolute;top:-5px;left:-5px;background-color:#fff;-webkit-animation:cube-transition 1.6s ease-in-out 0s infinite;animation:cube-transition 1.6s ease-in-out 0s infinite}.cube-transition>div:last-child{-webkit-animation-delay:-.8s;animation-delay:-.8s}@-webkit-keyframes spin-rotate{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}50%{-webkit-transform:rotate(180deg);transform:rotate(180deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes spin-rotate{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}50%{-webkit-transform:rotate(180deg);transform:rotate(180deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.semi-circle-spin{position:relative;width:35px;height:35px;overflow:hidden}.semi-circle-spin>div{position:absolute;border-width:0;border-radius:100%;-webkit-animation:spin-rotate .6s linear 0s infinite;animation:spin-rotate .6s linear 0s infinite;background-image:-webkit-linear-gradient(transparent,transparent 70%,#fff 0,#fff);background-image:linear-gradient(transparent,transparent 70%,#fff 0,#fff);width:100%;height:100%}@-webkit-keyframes bar-progress{0%{-webkit-transform:scaleY(20%);transform:scaleY(20%);opacity:1}25%{-webkit-transform:translateX(6%) scaleY(10%);transform:translateX(6%) scaleY(10%);opacity:.7}50%{-webkit-transform:translateX(20%) scaleY(20%);transform:translateX(20%) scaleY(20%);opacity:1}75%{-webkit-transform:translateX(6%) scaleY(10%);transform:translateX(6%) scaleY(10%);opacity:.7}to{-webkit-transform:scaleY(20%);transform:scaleY(20%);opacity:1}}@keyframes bar-progress{0%{-webkit-transform:scaleY(20%);transform:scaleY(20%);opacity:1}25%{-webkit-transform:translateX(6%) scaleY(10%);transform:translateX(6%) scaleY(10%);opacity:.7}50%{-webkit-transform:translateX(20%) scaleY(20%);transform:translateX(20%) scaleY(20%);opacity:1}75%{-webkit-transform:translateX(6%) scaleY(10%);transform:translateX(6%) scaleY(10%);opacity:.7}to{-webkit-transform:scaleY(20%);transform:scaleY(20%);opacity:1}}.bar-progress{width:30%;height:12px}.bar-progress>div{position:relative;width:20%;height:12px;border-radius:10px;background-color:#fff;-webkit-animation:bar-progress 3s cubic-bezier(.57,.1,.44,.93) infinite;animation:bar-progress 3s cubic-bezier(.57,.1,.44,.93) infinite;opacity:1}@-webkit-keyframes bar-swing{0%{left:0}50%{left:70%}to{left:0}}@keyframes bar-swing{0%{left:0}50%{left:70%}to{left:0}}.bar-swing,.bar-swing>div{width:30%;height:8px}.bar-swing>div{position:relative;border-radius:10px;background-color:#fff;-webkit-animation:bar-swing 1.5s infinite;animation:bar-swing 1.5s infinite}@-webkit-keyframes bar-swing-container{0%{left:0;-webkit-transform:translateX(0);transform:translateX(0)}50%{left:70%;-webkit-transform:translateX(-4px);transform:translateX(-4px)}to{left:0;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes bar-swing-container{0%{left:0;-webkit-transform:translateX(0);transform:translateX(0)}50%{left:70%;-webkit-transform:translateX(-4px);transform:translateX(-4px)}to{left:0;-webkit-transform:translateX(0);transform:translateX(0)}}.bar-swing-container{width:20%;height:8px;position:relative}.bar-swing-container div:first-child{position:absolute;width:100%;background-color:hsla(0,0%,100%,.2);height:12px;border-radius:10px}.bar-swing-container div:nth-child(2){position:absolute;width:30%;height:8px;border-radius:10px;background-color:#fff;-webkit-animation:bar-swing-container 2s cubic-bezier(.91,.35,.12,.6) infinite;animation:bar-swing-container 2s cubic-bezier(.91,.35,.12,.6) infinite;margin:2px 2px 0}.sk-spinner{color:#333}.sk-spinner>div{background-color:currentColor}.ball-scale-ripple-multiple>div,.ball-scale-ripple>div,.ball-triangle-path>div{background-color:initial;border-color:currentColor}.ball-clip-rotate>div{background-color:initial;border-top-color:initial;border-right-color:initial;border-left-color:initial}.ball-clip-rotate-pulse>div:first-child{background-color:currentColor}.ball-clip-rotate-pulse>div:last-child{background-color:initial;border-top-color:initial;border-bottom-color:initial}.ball-clip-rotate-multiple>div:first-child{background-color:initial;border-right-color:initial;border-left-color:initial}.ball-clip-rotate-multiple>div:last-child{border-top-color:initial}.ball-clip-rotate-multiple>div:last-child,.pacman>div:first-child,.pacman>div:nth-child(2),.triangle-skew-spin>div{background-color:initial;border-bottom-color:initial}.pacman>div:first-child,.pacman>div:nth-child(2){border-top-color:initial;border-left-color:initial}.pacman>div:nth-child(3),.pacman>div:nth-child(4),.pacman>div:nth-child(5){background-color:currentColor}@-webkit-keyframes sk-fade-in{0%{opacity:0}50%{opacity:0}to{opacity:1}}@keyframes sk-fade-in{0%{opacity:0}50%{opacity:0}to{opacity:1}}.sk-fade-in{-webkit-animation:sk-fade-in 2s;animation:sk-fade-in 2s}.sk-fade-in-half-second{-webkit-animation:sk-fade-in 1s;animation:sk-fade-in 1s}.sk-fade-in-quarter-second{-webkit-animation:sk-fade-in .5s;animation:sk-fade-in .5s}.sk-chasing-dots{width:27px;height:27px;position:relative;-webkit-animation:sk-rotate 2s linear infinite;animation:sk-rotate 2s linear infinite}.sk-chasing-dots>div{width:60%;height:60%;display:inline-block;position:absolute;top:0;background-color:currentColor;border-radius:100%;-webkit-animation:sk-bounce 2s ease-in-out infinite;animation:sk-bounce 2s ease-in-out infinite}.sk-chasing-dots>div:last-child{top:auto;bottom:0;-webkit-animation-delay:-1s;animation-delay:-1s}@-webkit-keyframes sk-rotate{to{-webkit-transform:rotate(1turn)}}@keyframes sk-rotate{to{transform:rotate(1turn);-webkit-transform:rotate(1turn)}}.sk-circle{width:22px;height:22px;position:relative}.sk-circle>div{background-color:initial;width:100%;height:100%;position:absolute;left:0;top:0}.sk-circle>div:before{content:"";display:block;margin:0 auto;width:20%;height:20%;background-color:currentColor;border-radius:100%;-webkit-animation:sk-bouncedelay 1.2s ease-in-out infinite;animation:sk-bouncedelay 1.2s ease-in-out infinite;-webkit-animation-fill-mode:both;animation-fill-mode:both}.sk-circle>div:nth-child(2){-webkit-transform:rotate(30deg);transform:rotate(30deg)}.sk-circle>div:nth-child(3){-webkit-transform:rotate(60deg);transform:rotate(60deg)}.sk-circle>div:nth-child(4){-webkit-transform:rotate(90deg);transform:rotate(90deg)}.sk-circle>div:nth-child(5){-webkit-transform:rotate(120deg);transform:rotate(120deg)}.sk-circle>div:nth-child(6){-webkit-transform:rotate(150deg);transform:rotate(150deg)}.sk-circle>div:nth-child(7){-webkit-transform:rotate(180deg);transform:rotate(180deg)}.sk-circle>div:nth-child(8){-webkit-transform:rotate(210deg);transform:rotate(210deg)}.sk-circle>div:nth-child(9){-webkit-transform:rotate(240deg);transform:rotate(240deg)}.sk-circle>div:nth-child(10){-webkit-transform:rotate(270deg);transform:rotate(270deg)}.sk-circle>div:nth-child(11){-webkit-transform:rotate(300deg);transform:rotate(300deg)}.sk-circle>div:nth-child(12){-webkit-transform:rotate(330deg);transform:rotate(330deg)}.sk-circle>div:nth-child(2):before{-webkit-animation-delay:-1.1s;animation-delay:-1.1s}.sk-circle>div:nth-child(3):before{-webkit-animation-delay:-1s;animation-delay:-1s}.sk-circle>div:nth-child(4):before{-webkit-animation-delay:-.9s;animation-delay:-.9s}.sk-circle>div:nth-child(5):before{-webkit-animation-delay:-.8s;animation-delay:-.8s}.sk-circle>div:nth-child(6):before{-webkit-animation-delay:-.7s;animation-delay:-.7s}.sk-circle>div:nth-child(7):before{-webkit-animation-delay:-.6s;animation-delay:-.6s}.sk-circle>div:nth-child(8):before{-webkit-animation-delay:-.5s;animation-delay:-.5s}.sk-circle>div:nth-child(9):before{-webkit-animation-delay:-.4s;animation-delay:-.4s}.sk-circle>div:nth-child(10):before{-webkit-animation-delay:-.3s;animation-delay:-.3s}.sk-circle>div:nth-child(11):before{-webkit-animation-delay:-.2s;animation-delay:-.2s}.sk-circle>div:nth-child(12):before{-webkit-animation-delay:-.1s;animation-delay:-.1s}.sk-cube-grid{width:27px;height:27px}.sk-cube-grid>div{width:33%;height:33%;background-color:currentColor;float:left;-webkit-animation:sk-scaleDelay 1.3s ease-in-out infinite;animation:sk-scaleDelay 1.3s ease-in-out infinite}.sk-cube-grid>div:first-child{-webkit-animation-delay:.2s;animation-delay:.2s}.sk-cube-grid>div:nth-child(2){-webkit-animation-delay:.3s;animation-delay:.3s}.sk-cube-grid>div:nth-child(3){-webkit-animation-delay:.4s;animation-delay:.4s}.sk-cube-grid>div:nth-child(4){-webkit-animation-delay:.1s;animation-delay:.1s}.sk-cube-grid>div:nth-child(5){-webkit-animation-delay:.2s;animation-delay:.2s}.sk-cube-grid>div:nth-child(6){-webkit-animation-delay:.3s;animation-delay:.3s}.sk-cube-grid>div:nth-child(7){-webkit-animation-delay:0s;animation-delay:0s}.sk-cube-grid>div:nth-child(8){-webkit-animation-delay:.1s;animation-delay:.1s}.sk-cube-grid>div:nth-child(9){-webkit-animation-delay:.2s;animation-delay:.2s}@-webkit-keyframes sk-scaleDelay{0%,70%,to{-webkit-transform:scaleX(1)}35%{-webkit-transform:scale3D(0,0,1)}}@keyframes sk-scaleDelay{0%,70%,to{-webkit-transform:scaleX(1);transform:scaleX(1)}35%{-webkit-transform:scaleX(1);transform:scale3D(0,0,1)}}.sk-double-bounce{width:27px;height:27px;position:relative}.sk-double-bounce>div{width:100%;height:100%;border-radius:50%;background-color:currentColor;opacity:.6;position:absolute;top:0;left:0;-webkit-animation:sk-bounce 2s ease-in-out infinite;animation:sk-bounce 2s ease-in-out infinite}.sk-double-bounce>div:last-child{-webkit-animation-delay:-1s;animation-delay:-1s}@-webkit-keyframes sk-bounce{0%,to{-webkit-transform:scale(0)}50%{-webkit-transform:scale(1)}}@keyframes sk-bounce{0%,to{transform:scale(0);-webkit-transform:scale(0)}50%{transform:scale(1);-webkit-transform:scale(1)}}.sk-folding-cube{width:27px;height:27px;position:relative;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.sk-folding-cube>div{background-color:initial;float:left;width:50%;height:50%;position:relative;-webkit-transform:scale(1.1);transform:scale(1.1)}.sk-folding-cube>div:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:currentColor;-webkit-animation:sk-foldCubeAngle 2.4s linear infinite both;animation:sk-foldCubeAngle 2.4s linear infinite both;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}.sk-folding-cube>div:nth-child(2){-webkit-transform:scale(1.1) rotate(90deg);transform:scale(1.1) rotate(90deg)}.sk-folding-cube>div:nth-child(4){-webkit-transform:scale(1.1) rotate(180deg);transform:scale(1.1) rotate(180deg)}.sk-folding-cube>div:nth-child(3){-webkit-transform:scale(1.1) rotate(270deg);transform:scale(1.1) rotate(270deg)}.sk-folding-cube>div:nth-child(2):before{-webkit-animation-delay:.3s;animation-delay:.3s}.sk-folding-cube>div:nth-child(4):before{-webkit-animation-delay:.6s;animation-delay:.6s}.sk-folding-cube>div:nth-child(3):before{-webkit-animation-delay:.9s;animation-delay:.9s}@-webkit-keyframes sk-foldCubeAngle{0%,10%{-webkit-transform:perspective(140px) rotateX(-180deg);transform:perspective(140px) rotateX(-180deg);opacity:0}25%,75%{-webkit-transform:perspective(140px) rotateX(0deg);transform:perspective(140px) rotateX(0deg);opacity:1}90%,to{-webkit-transform:perspective(140px) rotateY(180deg);transform:perspective(140px) rotateY(180deg);opacity:0}}@keyframes sk-foldCubeAngle{0%,10%{-webkit-transform:perspective(140px) rotateX(-180deg);transform:perspective(140px) rotateX(-180deg);opacity:0}25%,75%{-webkit-transform:perspective(140px) rotateX(0deg);transform:perspective(140px) rotateX(0deg);opacity:1}90%,to{-webkit-transform:perspective(140px) rotateY(180deg);transform:perspective(140px) rotateY(180deg);opacity:0}}.sk-pulse>div{width:27px;height:27px;background-color:currentColor;border-radius:100%;-webkit-animation:sk-scaleout 1s ease-in-out infinite;animation:sk-scaleout 1s ease-in-out infinite}@-webkit-keyframes sk-scaleout{0%{-webkit-transform:scale(0)}to{-webkit-transform:scale(1);opacity:0}}@keyframes sk-scaleout{0%{transform:scale(0);-webkit-transform:scale(0)}to{transform:scale(1);-webkit-transform:scale(1);opacity:0}}.sk-rotating-plane>div{width:27px;height:27px;background-color:currentColor;-webkit-animation:sk-rotateplane 1.2s ease-in-out infinite;animation:sk-rotateplane 1.2s ease-in-out infinite}@-webkit-keyframes sk-rotateplane{0%{-webkit-transform:perspective(120px)}50%{-webkit-transform:perspective(120px) rotateY(180deg)}to{-webkit-transform:perspective(120px) rotateY(180deg) rotateX(180deg)}}@keyframes sk-rotateplane{0%{transform:perspective(120px) rotateX(0deg) rotateY(0deg);-webkit-transform:perspective(120px) rotateX(0deg) rotateY(0deg)}50%{transform:perspective(120px) rotateX(-180.1deg) rotateY(0deg);-webkit-transform:perspective(120px) rotateX(-180.1deg) rotateY(0deg)}to{transform:perspective(120px) rotateX(-180deg) rotateY(-179.9deg);-webkit-transform:perspective(120px) rotateX(-180deg) rotateY(-179.9deg)}}.sk-three-bounce{height:18px}.sk-three-bounce>div{width:18px;height:18px;background-color:currentColor;border-radius:100%;display:inline-block;-webkit-animation:sk-bouncedelay 1.4s ease-in-out infinite;animation:sk-bouncedelay 1.4s ease-in-out infinite;-webkit-animation-fill-mode:both;animation-fill-mode:both}.sk-three-bounce>div:first-child{-webkit-animation-delay:-.32s;animation-delay:-.32s}.sk-three-bounce>div:nth-child(2){-webkit-animation-delay:-.16s;animation-delay:-.16s}@-webkit-keyframes sk-bouncedelay{0%,80%,to{-webkit-transform:scale(0)}40%{-webkit-transform:scale(1)}}@keyframes sk-bouncedelay{0%,80%,to{transform:scale(0);-webkit-transform:scale(0)}40%{transform:scale(1);-webkit-transform:scale(1)}}.sk-wandering-cubes{width:52px;height:52px;position:relative}.sk-wandering-cubes>div{background-color:currentColor;width:10px;height:10px;position:absolute;top:0;left:0;-webkit-animation:sk-cubemove 1.8s ease-in-out infinite;animation:sk-cubemove 1.8s ease-in-out infinite}.sk-wandering-cubes>div:last-child{-webkit-animation-delay:-.9s;animation-delay:-.9s}@-webkit-keyframes sk-cubemove{25%{-webkit-transform:translateX(42px) rotate(-90deg) scale(.5)}50%{-webkit-transform:translateX(42px) translateY(42px) rotate(-180deg)}75%{-webkit-transform:translateX(0) translateY(42px) rotate(-270deg) scale(.5)}to{-webkit-transform:rotate(-1turn)}}@keyframes sk-cubemove{25%{transform:translateX(42px) rotate(-90deg) scale(.5);-webkit-transform:translateX(42px) rotate(-90deg) scale(.5)}50%{transform:translateX(42px) translateY(42px) rotate(-179deg);-webkit-transform:translateX(42px) translateY(42px) rotate(-179deg)}50.1%{transform:translateX(42px) translateY(42px) rotate(-180deg);-webkit-transform:translateX(42px) translateY(42px) rotate(-180deg)}75%{transform:translateX(0) translateY(42px) rotate(-270deg) scale(.5);-webkit-transform:translateX(0) translateY(42px) rotate(-270deg) scale(.5)}to{transform:rotate(-1turn);-webkit-transform:rotate(-1turn)}}.sk-wave{width:30px;height:27px}.sk-wave>div{background-color:currentColor;height:100%;width:6px;display:inline-block;-webkit-animation:sk-stretchdelay 1.2s ease-in-out infinite;animation:sk-stretchdelay 1.2s ease-in-out infinite}.sk-wave>div:nth-child(2){-webkit-animation-delay:-1.1s;animation-delay:-1.1s}.sk-wave>div:nth-child(3){-webkit-animation-delay:-1s;animation-delay:-1s}.sk-wave>div:nth-child(4){-webkit-animation-delay:-.9s;animation-delay:-.9s}.sk-wave>div:nth-child(5){-webkit-animation-delay:-.8s;animation-delay:-.8s}@-webkit-keyframes sk-stretchdelay{0%,40%,to{-webkit-transform:scaleY(.4)}20%{-webkit-transform:scaleY(1)}}@keyframes sk-stretchdelay{0%,40%,to{transform:scaleY(.4);-webkit-transform:scaleY(.4)}20%{transform:scaleY(1);-webkit-transform:scaleY(1)}}.sk-wordpress>div{width:27px;height:27px;background-color:currentColor;display:inline-block;border-radius:27px;position:relative;-webkit-animation:sk-inner-circle 1s linear infinite;animation:sk-inner-circle 1s linear infinite}.sk-wordpress>div:after{content:"";display:block;background-color:#fff;width:8px;height:8px;position:absolute;border-radius:8px;top:5px;left:5px}@-webkit-keyframes sk-inner-circle{0%{-webkit-transform:rotate(0)}to{-webkit-transform:rotate(1turn)}}@keyframes sk-inner-circle{0%{transform:rotate(0);-webkit-transform:rotate(0)}to{transform:rotate(1turn);-webkit-transform:rotate(1turn)}}.Widget{text-align:center}.Widget-logo{-webkit-animation:Widget-logo-spin 20s linear infinite;animation:Widget-logo-spin 20s linear infinite;height:40vmin;pointer-events:none}.Widget-header{background-color:#282c34;min-height:100vh;display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center;font-size:calc(10px + 2vmin);color:#fff}.Widget-link{color:#61dafb}@-webkit-keyframes Widget-logo-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes Widget-logo-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}} 2 | /*# sourceMappingURL=main.6a0345f5.css.map */ -------------------------------------------------------------------------------- /dist/Figure.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 11 | 12 | function Figure(props) { 13 | var fieldName = props.compiledView.spec.fieldName; 14 | var output = '-'; 15 | 16 | if (props.compiledView.resources[0]._values[0]) { 17 | // We expect that after transformation, the `_values` array will have single row: 18 | var values = props.compiledView.resources[0]._values[0]; // Flatten the values: 19 | 20 | Object.keys(values).forEach(function (key) { 21 | var value = values[key]; 22 | 23 | if (value.constructor.name === 'Object') { 24 | Object.keys(value).forEach(function (anotherKey) { 25 | values[anotherKey] = value[anotherKey]; 26 | }); 27 | } 28 | }); // Take the value we need: 29 | 30 | output = values[fieldName]; // If it's a number, use only 1 decimal place: 31 | 32 | if (!isNaN(output)) { 33 | output = parseFloat(output).toFixed(1).toString(); 34 | } 35 | } 36 | 37 | var prefix = props.compiledView.spec.prefix || ''; 38 | var suffix = props.compiledView.spec.suffix || ''; 39 | return _react.default.createElement(_react.default.Fragment, null, _react.default.createElement("h2", null, props.compiledView.title), _react.default.createElement("div", { 40 | className: "info" 41 | }, _react.default.createElement("span", null, prefix + output + suffix), _react.default.createElement("br", null), _react.default.createElement("h4", null, props.compiledView.legend)), _react.default.createElement("p", null, props.compiledView.footer)); 42 | } 43 | 44 | var _default = Figure; 45 | exports.default = _default; -------------------------------------------------------------------------------- /dist/Widget.css: -------------------------------------------------------------------------------- 1 | .Widget { 2 | text-align: center; 3 | } 4 | 5 | .Widget-logo { 6 | animation: Widget-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .Widget-header { 12 | background-color: #282c34; 13 | min-height: 100vh; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: calc(10px + 2vmin); 19 | color: white; 20 | } 21 | 22 | .Widget-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes Widget-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /dist/Widget.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | var _reactVega = _interopRequireDefault(require("react-vega")); 11 | 12 | var _reactSpinkit = _interopRequireDefault(require("react-spinkit")); 13 | 14 | var dprender = _interopRequireWildcard(require("datapackage-render")); 15 | 16 | var _useDatasetLoader = _interopRequireDefault(require("./hooks/useDatasetLoader")); 17 | 18 | var _Figure = _interopRequireDefault(require("./Figure.js")); 19 | 20 | require("./Widget.css"); 21 | 22 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } 23 | 24 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 25 | 26 | function Widget(props) { 27 | var datasets = (0, _useDatasetLoader.default)(props.widget.view.resources[0].datasetId); 28 | var dataset = datasets.find(function (dataset) { 29 | return dataset.descriptor.name === props.widget.view.resources[0].datasetId; 30 | }); 31 | 32 | if (dataset) { 33 | var compiledView = dprender.compileView(props.widget.view, dataset.descriptor); 34 | 35 | if (props.widget.view.specType === 'vega') { 36 | var vegaSpec = dprender.vegaToVega(compiledView); 37 | 38 | if (vegaSpec) { 39 | return _react.default.createElement(_reactVega.default, { 40 | spec: vegaSpec 41 | }); 42 | } 43 | } else if (props.widget.view.specType === 'figure') { 44 | return _react.default.createElement(_Figure.default, { 45 | compiledView: compiledView 46 | }); 47 | } 48 | } 49 | 50 | return _react.default.createElement(_reactSpinkit.default, { 51 | name: "ball-beat" 52 | }); 53 | } 54 | 55 | var _default = Widget; 56 | exports.default = _default; -------------------------------------------------------------------------------- /dist/Widget.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _react = _interopRequireDefault(require("react")); 4 | 5 | var _reactDom = _interopRequireDefault(require("react-dom")); 6 | 7 | var _Widget = _interopRequireDefault(require("./Widget")); 8 | 9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 10 | 11 | it('renders without crashing', function () { 12 | var div = document.createElement('div'); 13 | 14 | _reactDom.default.render(_react.default.createElement(_Widget.default, null), div); 15 | 16 | _reactDom.default.unmountComponentAtNode(div); 17 | }); -------------------------------------------------------------------------------- /dist/hooks/useDatasetLoader.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | var _react = require("react"); 9 | 10 | function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } 11 | 12 | function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } 13 | 14 | function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } 15 | 16 | function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } } 17 | 18 | function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } 19 | 20 | function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } 21 | 22 | function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } 23 | 24 | function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } 25 | 26 | function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } 27 | 28 | function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } 29 | 30 | var _require = require('data.js'), 31 | Dataset = _require.Dataset; 32 | 33 | var toArray = require('stream-to-array'); 34 | 35 | function useDatasetLoader(datasetId) { 36 | var _useState = (0, _react.useState)([]), 37 | _useState2 = _slicedToArray(_useState, 2), 38 | currentState = _useState2[0], 39 | addDataset = _useState2[1]; 40 | 41 | var config = window.config; 42 | var identifier = config.datasets.find(function (dataset) { 43 | var urlParts = dataset.split('/'); 44 | 45 | if (urlParts[urlParts.length - 1] === datasetId) { 46 | return dataset; 47 | } 48 | }); // Load datasets 49 | // Check if this dataset is already loaded: 50 | 51 | if (!currentState.some(function (dataset) { 52 | return dataset.identifier.original === identifier; 53 | })) { 54 | // Load the dataset: 55 | Dataset.load(identifier).then( 56 | /*#__PURE__*/ 57 | function () { 58 | var _ref = _asyncToGenerator( 59 | /*#__PURE__*/ 60 | regeneratorRuntime.mark(function _callee2(newDataset) { 61 | return regeneratorRuntime.wrap(function _callee2$(_context2) { 62 | while (1) { 63 | switch (_context2.prev = _context2.next) { 64 | case 0: 65 | _context2.next = 2; 66 | return Promise.all(newDataset.resources.map( 67 | /*#__PURE__*/ 68 | function () { 69 | var _ref2 = _asyncToGenerator( 70 | /*#__PURE__*/ 71 | regeneratorRuntime.mark(function _callee(resource) { 72 | var knownTabularFormats, rowStream; 73 | return regeneratorRuntime.wrap(function _callee$(_context) { 74 | while (1) { 75 | switch (_context.prev = _context.next) { 76 | case 0: 77 | // Load it here 78 | knownTabularFormats = ['csv', 'tsv', 'dsv']; 79 | 80 | if (!(!resource.descriptor._values && knownTabularFormats.indexOf(resource.descriptor.format) !== -1 && resource.descriptor.datahub && resource.descriptor.datahub.type === 'source/tabular')) { 81 | _context.next = 8; 82 | break; 83 | } 84 | 85 | _context.next = 4; 86 | return resource.rows({ 87 | keyed: true 88 | }); 89 | 90 | case 4: 91 | rowStream = _context.sent; 92 | _context.next = 7; 93 | return toArray(rowStream); 94 | 95 | case 7: 96 | resource.descriptor._values = _context.sent; 97 | 98 | case 8: 99 | case "end": 100 | return _context.stop(); 101 | } 102 | } 103 | }, _callee); 104 | })); 105 | 106 | return function (_x2) { 107 | return _ref2.apply(this, arguments); 108 | }; 109 | }())); 110 | 111 | case 2: 112 | addDataset(function (v) { 113 | return [].concat(_toConsumableArray(v), [newDataset]); 114 | }); 115 | 116 | case 3: 117 | case "end": 118 | return _context2.stop(); 119 | } 120 | } 121 | }, _callee2); 122 | })); 123 | 124 | return function (_x) { 125 | return _ref.apply(this, arguments); 126 | }; 127 | }()); 128 | } 129 | 130 | return currentState; 131 | } 132 | 133 | var _default = useDatasetLoader; 134 | exports.default = _default; -------------------------------------------------------------------------------- /dist/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _react = _interopRequireDefault(require("react")); 4 | 5 | var _reactDom = _interopRequireDefault(require("react-dom")); 6 | 7 | require("./index.css"); 8 | 9 | var _Widget = _interopRequireDefault(require("./Widget")); 10 | 11 | var serviceWorker = _interopRequireWildcard(require("./serviceWorker")); 12 | 13 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | // Read 'config' from global variable: 18 | var config = window.config; 19 | 20 | if (config) { 21 | // Render widgets: 22 | config.widgets.forEach(function (widget) { 23 | _reactDom.default.render(_react.default.createElement(_Widget.default, { 24 | widget: widget 25 | }), document.getElementById(widget.elementId)); 26 | }); 27 | } else { 28 | _reactDom.default.render(_react.default.createElement("div", null, "'Missing \"config\" global variable. Please, refer to README.'"), document.getElementById('root')); 29 | } // If you want your app to work offline and load faster, you can change 30 | // unregister() to register() below. Note this comes with some pitfalls. 31 | // Learn more about service workers: http://bit.ly/CRA-PWA 32 | 33 | 34 | serviceWorker.unregister(); -------------------------------------------------------------------------------- /dist/serviceWorker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.register = register; 7 | exports.unregister = unregister; 8 | // This optional code is used to register a service worker. 9 | // register() is not called by default. 10 | // This lets the app load faster on subsequent visits in production, and gives 11 | // it offline capabilities. However, it also means that developers (and users) 12 | // will only see deployed updates on subsequent visits to a page, after all the 13 | // existing tabs open on the page have been closed, since previously cached 14 | // resources are updated in the background. 15 | // To learn more about the benefits of this model and instructions on how to 16 | // opt-in, read http://bit.ly/CRA-PWA 17 | var isLocalhost = Boolean(window.location.hostname === 'localhost' || // [::1] is the IPv6 localhost address. 18 | window.location.hostname === '[::1]' || // 127.0.0.1/8 is considered localhost for IPv4. 19 | window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)); 20 | 21 | function register(config) { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | var publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 25 | 26 | if (publicUrl.origin !== window.location.origin) { 27 | // Our service worker won't work if PUBLIC_URL is on a different origin 28 | // from what our page is served on. This might happen if a CDN is used to 29 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 30 | return; 31 | } 32 | 33 | window.addEventListener('load', function () { 34 | var swUrl = "".concat(process.env.PUBLIC_URL, "/service-worker.js"); 35 | 36 | if (isLocalhost) { 37 | // This is running on localhost. Let's check if a service worker still exists or not. 38 | checkValidServiceWorker(swUrl, config); // Add some additional logging to localhost, pointing developers to the 39 | // service worker/PWA documentation. 40 | 41 | navigator.serviceWorker.ready.then(function () { 42 | console.log('This web app is being served cache-first by a service ' + 'worker. To learn more, visit http://bit.ly/CRA-PWA'); 43 | }); 44 | } else { 45 | // Is not localhost. Just register service worker 46 | registerValidSW(swUrl, config); 47 | } 48 | }); 49 | } 50 | } 51 | 52 | function registerValidSW(swUrl, config) { 53 | navigator.serviceWorker.register(swUrl).then(function (registration) { 54 | registration.onupdatefound = function () { 55 | var installingWorker = registration.installing; 56 | 57 | if (installingWorker == null) { 58 | return; 59 | } 60 | 61 | installingWorker.onstatechange = function () { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the updated precached content has been fetched, 65 | // but the previous service worker will still serve the older 66 | // content until all client tabs are closed. 67 | console.log('New content is available and will be used when all ' + 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'); // Execute callback 68 | 69 | if (config && config.onUpdate) { 70 | config.onUpdate(registration); 71 | } 72 | } else { 73 | // At this point, everything has been precached. 74 | // It's the perfect time to display a 75 | // "Content is cached for offline use." message. 76 | console.log('Content is cached for offline use.'); // Execute callback 77 | 78 | if (config && config.onSuccess) { 79 | config.onSuccess(registration); 80 | } 81 | } 82 | } 83 | }; 84 | }; 85 | }).catch(function (error) { 86 | console.error('Error during service worker registration:', error); 87 | }); 88 | } 89 | 90 | function checkValidServiceWorker(swUrl, config) { 91 | // Check if the service worker can be found. If it can't reload the page. 92 | fetch(swUrl).then(function (response) { 93 | // Ensure service worker exists, and that we really are getting a JS file. 94 | var contentType = response.headers.get('content-type'); 95 | 96 | if (response.status === 404 || contentType != null && contentType.indexOf('javascript') === -1) { 97 | // No service worker found. Probably a different app. Reload the page. 98 | navigator.serviceWorker.ready.then(function (registration) { 99 | registration.unregister().then(function () { 100 | window.location.reload(); 101 | }); 102 | }); 103 | } else { 104 | // Service worker found. Proceed as normal. 105 | registerValidSW(swUrl, config); 106 | } 107 | }).catch(function () { 108 | console.log('No internet connection found. App is running in offline mode.'); 109 | }); 110 | } 111 | 112 | function unregister() { 113 | if ('serviceWorker' in navigator) { 114 | navigator.serviceWorker.ready.then(function (registration) { 115 | registration.unregister(); 116 | }); 117 | } 118 | } -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example/script.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | widgets: [ 3 | { 4 | "elementId": "testWidget", 5 | "view": { 6 | "title": "Economy", 7 | "legend": "Recent unemployment rate", 8 | "resources": [ 9 | { 10 | "datasetId": "unemployment", 11 | "name": "unemployment-rate", 12 | "transform": [ 13 | { 14 | "type": "aggregate", 15 | "fields": [ 16 | "date" 17 | ], 18 | "operations": [ 19 | "argmax" 20 | ] 21 | } 22 | ] 23 | } 24 | ], 25 | "specType": "figure", 26 | "spec": { 27 | "fieldName": "unemployment_rate", 28 | "suffix": "%" 29 | } 30 | } 31 | } 32 | ], 33 | datasets: ["https://datahub.io/london/unemployment"] 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@datopian/dashboard", 3 | "main": "/dist/index.js", 4 | "module": "/dist/index.js", 5 | "version": "0.1.5", 6 | "private": false, 7 | "babel": { 8 | "presets": [ 9 | "@babel/preset-env", 10 | "@babel/preset-react" 11 | ], 12 | "plugins": [ 13 | "@babel/plugin-proposal-class-properties" 14 | ] 15 | }, 16 | "devDependencies": { 17 | "@babel/cli": "^7.5.5", 18 | "@babel/core": "^7.5.5", 19 | "@babel/plugin-proposal-class-properties": "^7.5.5", 20 | "@babel/preset-env": "^7.5.5", 21 | "@babel/preset-react": "^7.0.0", 22 | "react-scripts": "2.1.5", 23 | "rewire": "^4.0.1", 24 | "source-map-explorer": "^1.6.0" 25 | }, 26 | "dependencies": { 27 | "data.js": "^0.12.9", 28 | "datapackage-render": "git+https://github.com/frictionlessdata/datapackage-render-js.git", 29 | "react": "^16.8.1", 30 | "react-dom": "^16.8.1", 31 | "react-spinkit": "^3.0.0", 32 | "react-vega": "^4.0.2", 33 | "vega-lib": "^4.4.0" 34 | }, 35 | "scripts": { 36 | "start": "react-scripts start", 37 | "build": "node ./scripts/build-non-split.js && mv build/static/js/*.js build/static/js/main.js", 38 | "build-optimized": "react-scripts build", 39 | "build:package": "rm -rf dist/ && mkdir dist && NODE_ENV=production npx babel src/ -d dist/ --copy-files", 40 | "test": "react-scripts test", 41 | "eject": "react-scripts eject", 42 | "analyze": "source-map-explorer build/static/js/main.*" 43 | }, 44 | "eslintConfig": { 45 | "extends": "react-app" 46 | }, 47 | "browserslist": [ 48 | ">0.2%", 49 | "not dead", 50 | "not ie <= 11", 51 | "not op_mini all" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datopian/dashboard-js/faf32416cb7a59e729de6971a6a59de65175d25c/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | London City Dashboard 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 20 | 21 | 30 | 31 | 32 | 33 | 34 | 35 |
36 | 37 |
38 | 39 |
40 | 41 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /scripts/build-non-split.js: -------------------------------------------------------------------------------- 1 | const rewire = require('rewire'); 2 | const defaults = rewire('react-scripts/scripts/build.js'); 3 | let config = defaults.__get__('config'); 4 | 5 | config.optimization.splitChunks = { 6 | cacheGroups: { 7 | default: false, 8 | }, 9 | }; 10 | 11 | config.optimization.runtimeChunk = false; 12 | -------------------------------------------------------------------------------- /src/Figure.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | 4 | function Figure(props) { 5 | let fieldName = props.compiledView.spec.fieldName 6 | let output = '-' 7 | if (props.compiledView.resources[0]._values[0]) { 8 | // We expect that after transformation, the `_values` array will have single row: 9 | let values = props.compiledView.resources[0]._values[0] 10 | // Flatten the values: 11 | Object.keys(values).forEach(key => { 12 | let value = values[key] 13 | if (value.constructor.name === 'Object') { 14 | Object.keys(value).forEach(anotherKey => { 15 | values[anotherKey] = value[anotherKey] 16 | }) 17 | } 18 | }) 19 | // Take the value we need: 20 | output = values[fieldName] 21 | // If it's a number, use only 1 decimal place: 22 | if (!isNaN(output)) { 23 | output = parseFloat(output).toFixed(1).toString() 24 | } 25 | } 26 | 27 | const prefix = props.compiledView.spec.prefix || '' 28 | const suffix = props.compiledView.spec.suffix || '' 29 | 30 | return ( 31 | <> 32 |

{props.compiledView.title}

33 |
34 | 35 | {prefix + output + suffix} 36 | 37 |
38 |

{props.compiledView.legend}

39 |
40 | 41 |

{props.compiledView.footer}

42 | 43 | ) 44 | } 45 | 46 | export default Figure; 47 | -------------------------------------------------------------------------------- /src/Widget.css: -------------------------------------------------------------------------------- 1 | .Widget { 2 | text-align: center; 3 | } 4 | 5 | .Widget-logo { 6 | animation: Widget-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .Widget-header { 12 | background-color: #282c34; 13 | min-height: 100vh; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: calc(10px + 2vmin); 19 | color: white; 20 | } 21 | 22 | .Widget-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes Widget-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Widget.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Vega from 'react-vega'; 3 | import Spinner from 'react-spinkit'; 4 | import * as dprender from 'datapackage-render' 5 | 6 | import useDatasetLoader from './hooks/useDatasetLoader'; 7 | import Figure from './Figure.js' 8 | import './Widget.css'; 9 | 10 | 11 | function Widget(props) { 12 | const datasets = useDatasetLoader(props.widget.view.resources[0].datasetId) 13 | const dataset = datasets.find(dataset => dataset.descriptor.name === props.widget.view.resources[0].datasetId) 14 | if (dataset) { 15 | let compiledView = dprender.compileView(props.widget.view, dataset.descriptor) 16 | if (props.widget.view.specType === 'vega') { 17 | let vegaSpec = dprender.vegaToVega(compiledView) 18 | if (vegaSpec) { 19 | return ( 20 | 21 | ) 22 | } 23 | } else if (props.widget.view.specType === 'figure') { 24 | return ( 25 |
26 | ) 27 | } 28 | } 29 | return ( 30 | 31 | ) 32 | } 33 | 34 | export default Widget; 35 | -------------------------------------------------------------------------------- /src/Widget.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Widget from './Widget'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/hooks/useDatasetLoader.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | const {Dataset} = require('data.js'); 3 | const toArray = require('stream-to-array') 4 | 5 | 6 | function useDatasetLoader(datasetId) { 7 | const [currentState, addDataset] = useState([]) 8 | const config = window.config 9 | const identifier = config.datasets.find(dataset => { 10 | const urlParts = dataset.split('/') 11 | if (urlParts[urlParts.length - 1] === datasetId) { 12 | return dataset 13 | } 14 | }) 15 | // Load datasets 16 | // Check if this dataset is already loaded: 17 | if (!(currentState.some(dataset => dataset.identifier.original === identifier))) { 18 | // Load the dataset: 19 | Dataset.load(identifier).then(async newDataset => { 20 | // Load/compile resources: 21 | await Promise.all(newDataset.resources.map(async resource => { 22 | // Load it here 23 | const knownTabularFormats = ['csv', 'tsv', 'dsv'] 24 | if ( 25 | !resource.descriptor._values && 26 | knownTabularFormats.indexOf(resource.descriptor.format) !== -1 27 | ) { 28 | const rowStream = await resource.rows({keyed: true}) 29 | resource.descriptor._values = await toArray(rowStream) 30 | } 31 | })) 32 | addDataset(v => [...v, newDataset]) 33 | }) 34 | } 35 | 36 | return currentState 37 | } 38 | 39 | export default useDatasetLoader; 40 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import Widget from './Widget'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | // Read 'config' from global variable: 8 | const config = window.config 9 | 10 | if (config) { 11 | // Render widgets: 12 | config.widgets.forEach(widget => { 13 | ReactDOM.render(, document.getElementById(widget.elementId)); 14 | }) 15 | } else { 16 | ReactDOM.render( 17 |
'Missing "config" global variable. Please, refer to README.'
, 18 | document.getElementById('root') 19 | ); 20 | } 21 | 22 | // If you want your app to work offline and load faster, you can change 23 | // unregister() to register() below. Note this comes with some pitfalls. 24 | // Learn more about service workers: http://bit.ly/CRA-PWA 25 | serviceWorker.unregister(); 26 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | --------------------------------------------------------------------------------