Repository: keen/dashboards Branch: master Commit: 348b37de6070 Files: 66 Total size: 231.6 KB Directory structure: gitextract_9ghuq6_o/ ├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── assets/ │ ├── css/ │ │ ├── keen-dashboards.css │ │ └── keen-static.css │ └── js/ │ └── keen-analytics.js ├── bower.json ├── demo-data/ │ └── sample.html ├── dist/ │ └── keen-dashboards.css ├── examples/ │ ├── 2048/ │ │ ├── index.html │ │ ├── js/ │ │ │ └── game/ │ │ │ ├── animframe_polyfill.js │ │ │ ├── application.js │ │ │ ├── bind_polyfill.js │ │ │ ├── classlist_polyfill.js │ │ │ ├── game_manager.js │ │ │ ├── grid.js │ │ │ ├── html_actuator.js │ │ │ ├── keyboard_input_manager.js │ │ │ ├── local_storage_manager.js │ │ │ └── tile.js │ │ └── styles/ │ │ └── game/ │ │ ├── fonts/ │ │ │ └── clear-sans.css │ │ ├── helpers.scss │ │ ├── main.css │ │ └── main.scss │ ├── README.md │ ├── connected-devices/ │ │ ├── README.md │ │ ├── connected-devices.css │ │ ├── connected-devices.js │ │ └── index.html │ ├── customer-facing/ │ │ ├── README.md │ │ ├── customer-facing.css │ │ ├── customer-facing.js │ │ └── index.html │ ├── geo-explorer/ │ │ ├── geo-explorer.css │ │ ├── geo-explorer.js │ │ └── index.html │ ├── index.html │ ├── military-surplus-flow/ │ │ ├── index.html │ │ ├── military-surplus-flow.css │ │ └── military-surplus-flow.js │ ├── sfmta-parking/ │ │ ├── README.md │ │ ├── index.html │ │ ├── sfmta-demo.css │ │ └── sfmta-demo.js │ └── starter-kit/ │ ├── LICENSE │ ├── README.md │ ├── index.html │ ├── starter-kit.css │ └── starter-kit.js ├── index.html ├── layouts/ │ ├── README.md │ ├── hero-sidebar/ │ │ └── index.html │ ├── hero-thirds/ │ │ └── index.html │ ├── index.html │ ├── quarter-grid/ │ │ └── index.html │ ├── split-centered/ │ │ └── index.html │ ├── split-columns/ │ │ └── index.html │ ├── split-rows/ │ │ └── index.html │ ├── thirds-grid/ │ │ └── index.html │ └── two-and-one/ │ ├── LICENSE │ ├── README.md │ └── index.html └── package.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store .sass-cache ================================================ FILE: .npmignore ================================================ .bowerrc .DS_Store .git* *.log bower.json ================================================ FILE: CONTRIBUTING.md ================================================ ## Submitting a Pull Request Use the template below. If certain testing steps are not relevant, specify that in the PR. If additional checks are needed, add 'em! Please run through all testing steps before asking for a review. ``` ## What does this PR do? How does it affect users? ## How should this be tested? Step through the code line by line. Things to keep in mind as you review: - Are there any edge cases not covered by this code? - Does this code follow conventions (naming, formatting, modularization, etc) where applicable? Fetch the branch and/or deploy to staging to test the following: - [ ] Does the code compile without warnings (check shell, console)? - [ ] Do all tests pass? - [ ] Does the UI, pixel by pixel, look exactly as expected (check various screen sizes, including mobile)? - [ ] If the feature makes requests from the browser, inspect them in the Web Inspector. Do they look as expected (parameters, headers, etc)? - [ ] If the feature sends data to Keen, is the data visible in the project if you run an extraction (include link to collection/query)? - [ ] If the feature saves data to a database, can you confirm the data is indeed created in the database? ## Related tickets? ``` ================================================ FILE: Dockerfile ================================================ FROM httpd RUN apt-get update -y WORKDIR /usr/local/apache2/htdocs/ ADD . /usr/local/apache2/htdocs/ EXPOSE 80 ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Keen IO Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Dashboards by Keen IO Building an analytics dashboard? Don’t start from scratch. Grab one of our CSS Grid-based templates and admire your data in minutes. **UPDATE: All examples in this repo have been updated to use [keen-dataviz.js](https://github.com/keen/keen-dataviz.js) and [keen-analysis.js](https://github.com/keen/keen-analysis.js), as well as CDN versions of all dependencies.** When producing charts with [keen-dataviz.js](https://github.com/keen/keen-dataviz.js), the HTML wrapper for each chart (`.chart-wrapper`, described below) is rendered automatically. Begin with a layout: ![Hero Thirds Example](http://cl.ly/image/3v2H180U0k0Q/Screen%20Shot%202014-10-29%20at%203.12.24%20AM.png) Add charts to each `chart-stage` HTML element: ```html
Chart Title
Notes about this chart (optional)
``` And voilà! ![Sample Dashboard](http://cl.ly/image/1T3a0X402r0W/Screen%20Shot%202014-10-29%20at%203.35.04%20AM.png) An attractive, custom analytics dashboard that's ready to be shown to your team or your customers. No hours lost tweaking CSS or testing responsiveness on eight different mobile devices. ## The Templates These layout templates are composed of a minimal set of custom styles. They cover the most common use cases and layout configurations we've encountered so far. * [Layouts](http://keen.github.io/dashboards/layouts/) for pre-built, responsive dashboard views * [Examples](http://keen.github.io/dashboards/examples/) for specific domains, data models and popular integrations ## Integrations These templates can work with any data source or charting library, but they're particularly streamlined to work with Keen IO's [Dataviz SDK](https://github.com/keen/keen-dataviz.js). You can add some charts to your dashboard with just a few lines of code. [Talk to our team](https://try.keen.io/contact) to get started today. ## Usage Ready to use one of these awesome layouts? Here's how to get started. 1. Download a copy of this repository as a zip file, using [this link](https://github.com/keen/dashboards/archive/gh-pages.zip). You can also type `git clone keen/dashboards` in your terminal. 2. Check out the various [layouts](http://keen.github.io/dashboards/layouts/) and pick the one that best suits your needs. Find the template in the repository you downloaded at `folder/layouts/(name-of-template)`. 3. Start editing! In the destination folder will exist an `.html` file. Open it in your favorite text editor. There are three things you need to do to edit your dashboard: 1. Setup: If you're a registered Keen IO user, navigate to [your keen project](http://keen.io/login?s=gh-dashboards) or if you don't have a user at first, you can simply use some demo data that we've prepared for you. You can access those by going to the repository and navigating to demo-data. There, you will see some javascript files with some code in them. We will simply paste those in the .html file. 2. Some copypasta. When you navigate to the bottom of the .html file, you can see that there are a bunch of script tags. Just before the end of the body tag, we're going to add in the code from sample.html. Simply copy and paste the code just before you see ``````. 3. Once you've done that we need to hook up the specific items within the template to the code that we've just pasted in to our file. Each KeenDataviz instance has `container` property, which is a node selector required by *query*. That means that this *query* will try to find inside the html file a specified node. Please bear in mind that you have to set a height of this node in your stylesheet or using inline CSS. In these templates, you will see lines of that resemble something like: ```html
``` Now we're going to change those lines so that it looks like this: ```html
``` You're finished! Congratulations on setting up your first chart! Repeat step three with the rest of the items in the template to complete your dashboard! ## Docker Clone the repository. ``` $ git clone https://github.com/keen/dashboards.git ``` Access the repository and build your Docker image. ``` $ cd dashboards $ docker build -t keen/dashboards . ``` Run the Docker container. ``` $ docker run -d -p 80:80 keen/dashboards ``` ## Contributing Contributions are 11,000,000% welcome! That's a lot! Please file issues for any bugs you find or features you'd like to see. And if you're up for it, send in a pull request. To develop, you'll need to first install dependencies using [Bower](http://bower.io/): ``` $ npm install -g bower $ bower install ``` Note: Updates to the site backed by the **gh-pages** branch go live immediately once pull requests are reviewed and approved. Note #2: This project is moving fast, so make sure and stay up to date. Here's what we suggest. Fork this repo, clone the fork, and add the original repo as a remote called `upstream`: ``` $ git clone https://github.com/keen/dashboards.git $ cd dashboards $ git remote add upstream https://github.com/keen/dashboards.git ``` Pull from `upstream` frequently to keep your local copy up to date: ``` $ git pull upstream gh-pages ``` ## Support Need a hand with something? Send us an email to [contact@keen.io](mailto:contact@keen.io) and we'll get back to you right away! For technical questions, use the [`keen-io`](https://stackoverflow.com/questions/tagged/keen-io) tag on Stack Overflow. ================================================ FILE: assets/css/keen-dashboards.css ================================================ :root { --grid-gap: 15px; } /* GENERAL */ * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } :before, :after { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } body { margin: 0; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.42857143; } a { color: #428bca; text-decoration: none; } hr { margin-top: 20px; margin-bottom: 20px; border: 0; border-top: 1px solid #eee; } h1, h2, h3 { margin-top: 20px; margin-bottom: 10px; } h4, h5, h6 { margin-top: 10px; margin-bottom: 10px; } h1 { font-size: 36px; } h2 { font-size: 30px; } h3 { font-size: 24px; } h4 { font-size: 18px; } h5 { font-size: 14px; } h6 { font-size: 12px; } .text-muted { color: #777; } small, .small { font-size: 85%; } .lead { margin-bottom: 20px; font-size: 16px; font-weight: 300; line-height: 1.4; } @media (min-width: 768px) { .lead { font-size: 21px; } } .container { padding-left: 15px; padding-right: 15px; margin-left: auto; margin-right: auto; } @media (min-width: 1200px) { .container { width: 1170px; } } /* NAV */ .nav { padding-left: 0; margin-bottom: 0; list-style: none; } .nav>li>a { position: relative; display: block; padding: 10px 15px; } .nav-tabs>li>a { margin-right: 2px; line-height: 1.42857143; border: 1px solid transparent; border-radius: 4px 4px 0 0; } .nav>li>a:hover, .nav>li>a:focus { text-decoration: none; } .nav-tabs>li.active>a, .nav-tabs>li.active>a:hover, .nav-tabs>li.active>a:focus { color: #555; cursor: default; background-color: #fff; border: 1px solid #ddd; border-bottom-color: transparent; } .nav-list { padding-left: 0; list-style: none; } .nav-list>li { display: block; } .nav-list>li>a { display: block; padding: 10px 15px; line-height: 20px; color: #777777; text-decoration: none; } .nav-list>li>a:hover { color: #ffffff; } .nav-brand { padding: 15px; font-size: 18px; line-height: 20px; text-decoration: none; color: #777777; } .nav-toggle { position: relative; padding: 15px 10px; margin: 8px 15px; background-color: transparent; border: 1px solid #333; border-radius: 3px; } .nav-toggle:hover, .nav-toggle:focus { background-color: #333; } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; } .nav-toggle .icon-bar { position: relative; } .nav-toggle .icon-bar, .nav-toggle .icon-bar::before, .nav-toggle .icon-bar::after { display: block; width: 22px; height: 2px; border-radius: 1px; background-color: #fff; } .nav-toggle .icon-bar::before, .nav-toggle .icon-bar::after { content: ''; } .nav-toggle .icon-bar::before { position: absolute; top: 6px; } .nav-toggle .icon-bar::after { position: absolute; bottom: 6px; } .nav-header { display: flex; flex-wrap: flex; justify-content: space-between; } .navbar { position: relative; min-height: 50px; margin-bottom: 20px; border: 1px solid transparent; } .navbar-toggle { margin: 8px 15px 8px 0; padding: 9px 10px; height: 34px; background: rgba(255, 255, 255, 0.25); border: 1px solid transparent; border-radius: 4px; display: flex; justify-content: space-between; flex-direction: column; } .navbar-toggle:focus { outline: none; } .navbar-toggle .icon-bar { display: block; width: 22px; height: 2px; border-radius: 1px; background: #ffffff; } .navbar-header { display: flex; align-items: center; } .navbar-header .navbar-toggle { order: 2; margin-left: auto; } .navbar-collapse { margin-top: 0; height: 0; overflow: hidden; will-change: height; border-top: 1px solid transparent; transition: all 0.3s ease; } .navbar-collapse .nav { margin-top: 0; } .navbar-collapse.collapsed { height: 200px; overflow-y: auto; } .navbar-inverse .navbar-collapse { border-color: #101010; box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); } .navbar-inverse .navbar-toggle:hover, .navbar-inverse .navbar-toggle:focus { background-color: #333; } .navbar-inverse .navbar-brand:hover, .navbar-inverse .navbar-brand:focus { color: #fff; background-color: transparent; } .navbar-inverse .navbar-brand:hover svg, .navbar-inverse .navbar-brand:focus svg { fill: #fff; } .navbar-inverse .navbar-nav>li>a:hover, .navbar-inverse .navbar-nav>li>a:focus { color: #fff; background-color: transparent; } .navbar-brand { min-height: 50px; padding: 15px 15px; line-height: 20px; display: flex; align-items: center; font-size: 18px; } .navbar-inverse { background-color: #222; border-color: #080808; } .navbar-inverse .navbar-brand, .navbar-inverse .navbar-nav>li>a { color: #777; } .navbar-inverse .navbar-toggle { background: transparent; border-color: #333; } .navbar-fixed-top { top: 0; border-width: 0 0 1px; } .navbar-fixed-bottom { bottom: 0; margin-bottom: 0; border-width: 1px 0 0 } .navbar-fixed-top, .navbar-fixed-bottom { position: fixed; right: 0; left: 0; z-index: 1030; transform: translate3d(0, 0, 0); } @media (min-width: 768px) { .navbar-header .navbar-toggle { display: none; } .navbar { display: flex; align-items: center; } .navbar-collapse, .navbar-collapse.collapsed { height: auto; } .navbar-collapse { flex: 1; display: flex; justify-content: flex-start; } .navbar-collapse .align-right { margin-left: auto; } .navbar-collapse .nav { height: auto; display: flex; align-items: center; justify-content: space-between; } .navbar-collapse .nav .right { margin-left: auto; } .navbar-inverse .navbar-collapse { box-shadow: none; border: none; } } /* IMG */ .img-thumbnail { display: inline-block; width: 100% \9; max-width: 100%; height: auto; padding: 4px; line-height: 1.42857143; background-color: #fff; border: 1px solid #ddd; border-radius: 4px; } .img-responsive { display: block; max-width: 100%; height: auto; } /* FORM_CONTROL */ .form-control { display: block; width: 100%; height: 34px; padding: 6px 12px; font-size: 14px; line-height: 1.42857143; color: #555; background-color: #fff; background-image: none; border: 1px solid #ccc; border-radius: 4px; box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); } /* BUTTON */ button { overflow: visible; } .btn { display: inline-block; padding: 6px 12px; margin-bottom: 0; font-size: 14px; font-weight: 400; line-height: 1.42857143; text-align: center; white-space: nowrap; vertical-align: middle; cursor: pointer; user-select: none; background-image: none; border: 1px solid transparent; border-radius: 4px; } .btn:hover, .btn:focus { color: #333; text-decoration: none; } .btn-primary { color: #fff; background-color: #428bca; border-color: #357ebd; } .btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active { color: #fff; background-color: #3071a9; border-color: #285e8e; } .btn-default { color: #333; background-color: #fff; border-color: #ccc; } .btn-default:hover, .btn-default:focus, .btn-default:active, .btn-default.active { color: #333; background-color: #e6e6e6; border-color: #adadad; } .btn-block { display: block; width: 100%; } /* GRID */ .grid { display: grid; grid-auto-flow: row; align-items: start; } .grid-traffic { grid-template-columns: 1fr 3fr; } @media (min-width: 768px) { .grid { grid-gap: var(--grid-gap); } .grid-hero-thirds { grid-template-columns: repeat(2, 1fr); } .grid-hero-thirds .grid-hero { display: grid; grid-gap: var(--grid-gap); grid-column: 1 / -1; grid-template-columns: [col-start] 2fr 1fr; grid-auto-flow: column; } .grid-hero-thirds .grid-hero .hero { order: 1; grid-column: col-start / span 1; } .grid-thirds, .grid-quarter { grid-template-columns: repeat(2, 1fr); } .grid-split-centered { grid-template-columns: repeat(3, 1fr); } .grid-split-centered .grid-split { display: grid; grid-gap: var(--grid-gap); grid-column: 1 / -1; grid-template-columns: 1fr 2fr 1fr; } .grid-split-centered .grid-split-column { display: flex; flex-direction: column; justify-content: space-between; } .grid-split-columns { grid-template-columns: repeat(2, 1fr); } .grid-split-columns .grid-split { display: grid; grid-gap: var(--grid-gap); grid-column: 1 / -1; grid-template-columns: repeat(2, 1fr); } .grid-split-rows { grid-template-columns: 1fr 3fr; } .grid-hero-sidebar { grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(3, auto); } .grid-hero-sidebar .sidebar { grid-column: 1 / span 1; grid-row: 1 / -1; } .grid-hero-sidebar .hero { grid-column: 2 / -1; grid-row: 1 / span 1; } .grid-two-and-one { grid-template-columns: repeat(2, 1fr); } .grid-two-and-one .two { grid-column: 1 / -1; } .grid-simple-col-3 { grid-template-columns: repeat(3, 1fr); } .grid-simple-col-2 { grid-template-columns: repeat(2, 1fr); } .grid-column-vertical { grid-template-columns: 2fr 1fr; grid-template-rows: auto; } .grid-column-vertical .column { min-width: 0; } .grid-column-vertical .vertical { grid-column: 2 / -1; } } @media (min-width: 992px) { .grid-hero-thirds, .grid-hero-thirds .grid-hero { grid-template-columns: repeat(3, 1fr); } .grid-hero-thirds .grid-hero .hero { grid-column: 1 / 3; } .grid-thirds { grid-template-columns: repeat(3, 1fr); } .grid-quarter { grid-template-columns: repeat(4, 1fr); } .grid-split-columns { grid-template-columns: repeat(4, 1fr); } } /* KEEN */ .keen-dashboard { padding-top: 80px; background: #f2f2f2; font-family: 'Gotham Rounded SSm A', 'Gotham Rounded SSm B', 'Helvetica Neue', Helvetica, Arial, sans-serif; } .keen-dataviz { background: #fff; border: 1px solid #e7e7e7; border-radius: 2px; box-sizing: border-box; } .keen-dataviz-title { border-bottom: 1px solid #e7e7e7; border-radius: 2px 2px 0 0; font-size: 13px; padding: 2px 10px 0; text-transform: uppercase; } .keen-dataviz-stage { padding: 10px; } .keen-dataviz-notes { background: #fbfbfb; border-radius: 0 0 2px 2px; border-top: 1px solid #e7e7e7; font-family: 'Helvetica Neue', Helvetica, sans-serif; font-size: 11px; padding: 0 10px; } .keen-dataviz .keen-dataviz-metric { border-radius: 2px; } .keen-dataviz .keen-spinner-indicator { border-top-color: rgba(0, 187, 222, .4); } .keen-dashboard .chart-wrapper { background: #fff; border: 1px solid #e2e2e2; border-radius: 3px; margin-bottom: 10px; } .keen-dashboard .chart-wrapper .chart-title { border-bottom: 1px solid #d7d7d7; color: #666; font-size: 14px; font-weight: 200; padding: 7px 10px 4px; } .keen-dashboard .chart-wrapper .chart-stage { overflow: hidden; padding: 5px 10px; position: relative; } .keen-dashboard .chart-wrapper .chart-notes { background: #fbfbfb; border-top: 1px solid #e2e2e2; color: #808080; font-size: 12px; padding: 8px 10px 5px; } .keen-dashboard .chart-wrapper .keen-dataviz, .keen-dashboard .chart-wrapper .keen-dataviz-title, .keen-dashboard .chart-stage .chart-title { border: medium none; } .chart-container { display: flex; } .chart-container .chart { padding-left: 15px; padding-right: 15px; flex: 1; min-width: 0; } @media (min-width: 768px) { .keen-dashboard .chart-wrapper { margin-bottom: 0; } } ================================================ FILE: assets/css/keen-static.css ================================================ a, a:focus, a:hover, a:active { color: #00afd7; } h1, h2, h3 { font-family: "Gotham Rounded", "Helvetica Neue", Helvetica, Arial, sans-serif; margin: 12px 0; } h1 { font-size: 32px; font-weight: 100; letter-spacing: .02em; line-height: 48px; margin: 12px 0; } h2 { color: #2a333c; font-weight: 200; font-size: 21px; } h3 { color: rgb(84, 102, 120); font-size: 21px; font-weight: 500; letter-spacing: -0.28px; line-height: 29.39px; } .btn { background: transparent; border: 1px solid white; } .keen-logo { height: 38px; margin: 0 15px 0 0; width: 150px; } .navbar-toggle { background-color: rgba(255, 255, 255, .25); } .navbar-toggle .icon-bar { background: #fff; } .navbar-nav { margin: 5px 0 0; } .navbar-nav>li>a { font-size: 15px; font-weight: 200; letter-spacing: 0.03em; padding-top: 19px; text-shadow: 0 0 2px rgba(0, 0, 0, .1); } .navbar-nav>li>a:focus, .navbar-nav>li>a:hover { background: transparent none; } .navbar-nav>li>a.navbar-btn { background-color: rgba(255, 255, 255, .25); border: medium none; padding: 10px 15px; } .navbar-nav>li>a.navbar-btn:focus, .navbar-nav>li>a.navbar-btn:hover { background-color: rgba(255, 255, 255, .35); } .navbar-collapse { box-shadow: none; } .masthead { background-color: #00afd7; background-image: url("../img/bg-bars.png"); background-position: 0 -290px; background-repeat: repeat-x; color: #fff; margin: 0 0 24px; padding: 20px 0; } .masthead h1 { margin: 0; } .masthead small, .masthead a, .masthead a:focus, .masthead a:hover, .masthead a:active { color: #fff; } .masthead p { color: #b3e7f3; font-weight: 100; letter-spacing: .05em; } .hero { background-position: 50% 100%; min-height: 450px; text-align: center; } .hero h1 { font-size: 48px; margin: 120px 0 0; } .hero .lead { margin-top: 0; margin-bottom: 32px; } .hero a.hero-btn { border: 2px solid #fff; display: block; font-family: "Gotham Rounded", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 24px; font-weight: 200; margin: 0 auto 12px; max-width: 320px; padding: 12px 0 6px; } .hero a.hero-btn:focus, .hero a.hero-btn:hover { border-color: transparent; background-color: #fff; color: #00afd7; } .sample-item { margin-bottom: 24px; } .signup { float: left; display: inline-block; vertical-align: middle; margin-top: -6px; margin-right: 10px; } .love { border-top: 1px solid #d7d7d7; color: #546678; margin: 24px 0 0; padding: 15px 0; text-align: center; } .love p { margin-bottom: 0; margin-top: 0; } ================================================ FILE: assets/js/keen-analytics.js ================================================ ! function (name, path, ctx) { var latest, prev = name !== 'Keen' && window.Keen ? window.Keen : false; ctx[name] = ctx[name] || { ready: function (fn) { var h = document.getElementsByTagName('head')[0], s = document.createElement('script'), w = window, loaded; s.onload = s.onerror = s.onreadystatechange = function () { if ((s.readyState && !(/^c|loade/.test(s.readyState))) || loaded) { return } s.onload = s.onreadystatechange = null; loaded = 1; latest = w.Keen; if (prev) { w.Keen = prev } else { try { delete w.Keen } catch (e) { w.Keen = void 0 } } ctx[name] = latest; ctx[name].ready(fn) }; s.async = 1; s.src = path; h.parentNode.insertBefore(s, h) } } }('KeenTracking', 'https://cdn.jsdelivr.net/npm/keen-tracking@4', this); KeenTracking.ready(function () { var meta = new KeenTracking({ projectId: '5368fa5436bf5a5623000000', writeKey: '725f3a571824d9c29f6e4d1c39af349a114d9034f8e493f95d39802529e2ccbb174033077bdcf10b541dbb50c20105922c59bbe1fe7741cb4b632dd0bc84fe98c0b591e17da3d429ef867cc674be0ad20ad768a5210662d2d18ff5492f37a1f91ce697a5489064bb3fa95c827b6cb394' }); meta.recordEvent('visits', { page: { title: document.title, host: document.location.host, href: document.location.href, path: document.location.pathname, protocol: document.location.protocol.replace(/:/g, ''), query: document.location.search }, visitor: { referrer: document.referrer, ip_address: '${keen.ip}', // tech: {} //^ created by ip_to_geo add-on user_agent: '${keen.user_agent}' // visitor: {} //^ created by ua_parser add-on }, keen: { timestamp: new Date().toISOString(), addons: [{ name: 'keen:ip_to_geo', input: { ip: 'visitor.ip_address' }, output: 'visitor.geo' }, { name: 'keen:ua_parser', input: { ua_string: 'visitor.user_agent' }, output: 'visitor.tech' } ] } }); }); ================================================ FILE: bower.json ================================================ { "name": "keen-dashboards", "homepage": "https://keenlabs.github.io/dashboards/", "main": "./dist/keen-dashboards.css", "authors": [ "Dustin Larimer ", "Sean Dokko ", "Ritchie Benevedes " ], "license": "MIT", "authors": [ "Dustin Larimer ", "Taylor Barnett " ], "keywords": [ "keen io", "dashboards", "dataviz", "visualization" ], "license": "MIT", "ignore": [ "**/.*", "node_modules", "bower_components", "test", "tests", "assets/img", "assets/js", "assets/css/keen-static.css" ] } ================================================ FILE: demo-data/sample.html ================================================ ================================================ FILE: dist/keen-dashboards.css ================================================ .keen-dashboard { background: #f2f2f2; font-family: 'Gotham Rounded SSm A', 'Gotham Rounded SSm B', 'Helvetica Neue', Helvetica, Arial, sans-serif; } .keen-dataviz { background: #fff; border: 1px solid #e7e7e7; border-radius: 2px; box-sizing: border-box; } .keen-dataviz-title { border-bottom: 1px solid #e7e7e7; border-radius: 2px 2px 0 0; font-size: 13px; padding: 2px 10px 0; text-transform: uppercase; } .keen-dataviz-stage { padding: 10px; } .keen-dataviz-notes { background: #fbfbfb; border-radius: 0 0 2px 2px; border-top: 1px solid #e7e7e7; font-family: 'Helvetica Neue', Helvetica, sans-serif; font-size: 11px; padding: 0 10px; } .keen-dataviz .keen-dataviz-metric { border-radius: 2px; } .keen-dataviz .keen-spinner-indicator { border-top-color: rgba(0, 187, 222, .4); } .keen-dashboard .chart-wrapper { background: #fff; border: 1px solid #e2e2e2; border-radius: 3px; margin-bottom: 10px; } .keen-dashboard .chart-wrapper .chart-title { border-bottom: 1px solid #d7d7d7; color: #666; font-size: 14px; font-weight: 200; padding: 7px 10px 4px; } .keen-dashboard .chart-wrapper .chart-stage { overflow: hidden; padding: 5px 10px; position: relative; } .keen-dashboard .chart-wrapper .chart-notes { background: #fbfbfb; border-top: 1px solid #e2e2e2; color: #808080; font-size: 12px; padding: 8px 10px 5px; } .keen-dashboard .chart-wrapper .keen-dataviz, .keen-dashboard .chart-wrapper .keen-dataviz-title, .keen-dashboard .chart-stage .chart-title { border: medium none; } ================================================ FILE: examples/2048/index.html ================================================ 2048

Keen 2048

0
0

Join the numbers and get to the 2048 tile!

New Game


How to play: Use your arrow keys to move the tiles. When two tiles with the same number touch, they merge into one!


Note: This site is the official version of 2048. You can play it on your phone via http://git.io/2048. All other apps or sites are derivatives or fakes, and should be used with caution.


Created by Gabriele Cirulli. Based on 1024 by Veewo Studio and conceptually similar to Threes by Asher Vollmer.

================================================ FILE: examples/2048/js/game/animframe_polyfill.js ================================================ (function () { var lastTime = 0; var vendors = ['webkit', 'moz']; for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']; } if (!window.requestAnimationFrame) { window.requestAnimationFrame = function (callback) { var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = window.setTimeout(function () { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; } if (!window.cancelAnimationFrame) { window.cancelAnimationFrame = function (id) { clearTimeout(id); }; } }()); ================================================ FILE: examples/2048/js/game/application.js ================================================ window.requestAnimationFrame(function () { // Wait till the browser is ready to render the game (avoids glitches) window.client = new Keen({ projectId: '53e3da37e8617022dd000000', writeKey: '1b94c236f839c60775698ea90cef000906eb6f5b6928180320464d3ce61c334775ea0554bb1b5e96cb91bd8e3a2658959a438e85e429702e0e7618be2261a8ac824445301cd3b7e4a59407c0b6876b3c4054bcc63ad7a30fc837513a8516b7b357d3e7931d503a01273209d3db57c301', readKey: 'de02be24f019e60415325d5e34408a752403cdf5170a686d4d3dbdf24b43ad0abafc61ad2e0a5dd9df3d608af8fd243a212416ac407c59e9279db85a3e3f43fbba6c44bb98e2dd3d28d0f3531e1fae763cd87e5b75132ebb23d36632f81b84480dc14c36517efc75e04e1b521b9aca6e' }); Keen.ready(function(){ // Define visuals var chart_total_moves = new Keen.Dataviz() .el('#moves-container') .height(200) .title('Total Moves') .type('metric') .prepare(); var chart_best_scores = new Keen.Dataviz() .el('#stats-container') .height(200) .title('High Score') .type('metric') .chartOptions({ prettyNumber: false }) .prepare(); // Run queries and render results client .query('count', { event_collection: 'moves' }) .then(function(res) { chart_total_moves.data(res).render(); }) .catch(function(err) { chart_total_moves.message(err.message); }); client .query('maximum', { event_collection: 'new_high_score2', target_property: 'best_score' }) .then(function(res) { chart_best_scores.data(res).render(); }) .catch(function(err) { chart_best_scores.message(err.message); }); }); new GameManager(4, KeyboardInputManager, HTMLActuator, LocalStorageManager); }); ================================================ FILE: examples/2048/js/game/bind_polyfill.js ================================================ Function.prototype.bind = Function.prototype.bind || function (target) { var self = this; return function (args) { if (!(args instanceof Array)) { args = [args]; } self.apply(target, args); }; }; ================================================ FILE: examples/2048/js/game/classlist_polyfill.js ================================================ (function () { if (typeof window.Element === "undefined" || "classList" in document.documentElement) { return; } var prototype = Array.prototype, push = prototype.push, splice = prototype.splice, join = prototype.join; function DOMTokenList(el) { this.el = el; // The className needs to be trimmed and split on whitespace // to retrieve a list of classes. var classes = el.className.replace(/^\s+|\s+$/g, '').split(/\s+/); for (var i = 0; i < classes.length; i++) { push.call(this, classes[i]); } } DOMTokenList.prototype = { add: function (token) { if (this.contains(token)) return; push.call(this, token); this.el.className = this.toString(); }, contains: function (token) { return this.el.className.indexOf(token) != -1; }, item: function (index) { return this[index] || null; }, remove: function (token) { if (!this.contains(token)) return; for (var i = 0; i < this.length; i++) { if (this[i] == token) break; } splice.call(this, i, 1); this.el.className = this.toString(); }, toString: function () { return join.call(this, ' '); }, toggle: function (token) { if (!this.contains(token)) { this.add(token); } else { this.remove(token); } return this.contains(token); } }; window.DOMTokenList = DOMTokenList; function defineElementGetter(obj, prop, getter) { if (Object.defineProperty) { Object.defineProperty(obj, prop, { get: getter }); } else { obj.__defineGetter__(prop, getter); } } defineElementGetter(HTMLElement.prototype, 'classList', function () { return new DOMTokenList(this); }); })(); ================================================ FILE: examples/2048/js/game/game_manager.js ================================================ function GameManager(size, InputManager, Actuator, StorageManager) { this.size = size; // Size of the grid this.inputManager = new InputManager; this.storageManager = new StorageManager; this.actuator = new Actuator; this.startTiles = 2; this.inputManager.on("move", this.move.bind(this)); this.inputManager.on("restart", this.restart.bind(this)); this.inputManager.on("keepPlaying", this.keepPlaying.bind(this)); this.moves = 0; this.setup(); } // Restart the game GameManager.prototype.restart = function () { this.storageManager.clearGameState(); this.actuator.continueGame(); // Clear the game won/lost message this.setup(); }; // Keep playing after winning (allows going over 2048) GameManager.prototype.keepPlaying = function () { this.keepPlaying = true; this.actuator.continueGame(); // Clear the game won/lost message }; // Return true if the game is lost, or has won and the user hasn't kept playing GameManager.prototype.isGameTerminated = function () { return this.over || (this.won && !this.keepPlaying); }; // Set up the game GameManager.prototype.setup = function () { var previousState = this.storageManager.getGameState(); // Reload the game from a previous game if present if (previousState) { this.grid = new Grid(previousState.grid.size, previousState.grid.cells); // Reload grid this.score = previousState.score; this.over = previousState.over; this.won = previousState.won; this.keepPlaying = previousState.keepPlaying; } else { this.grid = new Grid(this.size); this.score = 0; this.over = false; this.won = false; this.keepPlaying = false; // Add the initial tiles this.addStartTiles(); } // Update the actuator this.actuate(); }; // Set up the initial tiles to start the game with GameManager.prototype.addStartTiles = function () { for (var i = 0; i < this.startTiles; i++) { this.addRandomTile(); } }; // Adds a tile in a random position GameManager.prototype.addRandomTile = function () { if (this.grid.cellsAvailable()) { var value = Math.random() < 0.9 ? 2 : 4; var tile = new Tile(this.grid.randomAvailableCell(), value); this.grid.insertTile(tile); } }; // Sends the updated grid to the actuator GameManager.prototype.actuate = function () { if (this.storageManager.getBestScore() < this.score) { this.storageManager.setBestScore(this.score); } // Clear the state when the game is over (game over only, not win) if (this.over) { this.storageManager.clearGameState(); } else { this.storageManager.setGameState(this.serialize()); } this.actuator.actuate(this.grid, { score: this.score, over: this.over, won: this.won, bestScore: this.storageManager.getBestScore(), terminated: this.isGameTerminated() }); }; // Represent the current game as an object GameManager.prototype.serialize = function () { return { grid: this.grid.serialize(), score: this.score, over: this.over, won: this.won, keepPlaying: this.keepPlaying }; }; // Save all tile positions and remove merger info GameManager.prototype.prepareTiles = function () { this.grid.eachCell(function (x, y, tile) { if (tile) { tile.mergedFrom = null; tile.savePosition(); } }); }; // Move a tile and its representation GameManager.prototype.moveTile = function (tile, cell) { this.grid.cells[tile.x][tile.y] = null; this.grid.cells[cell.x][cell.y] = tile; tile.updatePosition(cell); }; // Move tiles on the grid in the specified direction GameManager.prototype.move = function (direction) { // 0: up, 1: right, 2: down, 3: left var self = this; this.actuator.moves++; if (this.isGameTerminated()) return; // Don't do anything if the game's over var cell, tile; var vector = this.getVector(direction); var traversals = this.buildTraversals(vector); var moved = false; // Save the current tile positions and remove merger information this.prepareTiles(); // Traverse the grid in the right direction and move tiles traversals.x.forEach(function (x) { traversals.y.forEach(function (y) { cell = { x: x, y: y }; tile = self.grid.cellContent(cell); if (tile) { var positions = self.findFarthestPosition(cell, vector); var next = self.grid.cellContent(positions.next); // Only one merger per row traversal? if (next && next.value === tile.value && !next.mergedFrom) { var merged = new Tile(positions.next, tile.value * 2); merged.mergedFrom = [tile, next]; self.grid.insertTile(merged); self.grid.removeTile(tile); // Converge the two tiles' positions tile.updatePosition(positions.next); // Update the score self.score += merged.value; // The mighty 2048 tile if (merged.value === 2048) self.won = true; } else { self.moveTile(tile, positions.farthest); } if (!self.positionsEqual(cell, tile)) { moved = true; // The tile moved from its original cell! } } }); }); if (moved) { this.addRandomTile(); if (!this.movesAvailable()) { this.over = true; // Game over! } this.actuate(); } }; // Get the vector representing the chosen direction GameManager.prototype.getVector = function (direction) { // Vectors representing tile movement var map = { 0: { x: 0, y: -1 }, // Up 1: { x: 1, y: 0 }, // Right 2: { x: 0, y: 1 }, // Down 3: { x: -1, y: 0 } // Left }; return map[direction]; }; // Build a list of positions to traverse in the right order GameManager.prototype.buildTraversals = function (vector) { var traversals = { x: [], y: [] }; for (var pos = 0; pos < this.size; pos++) { traversals.x.push(pos); traversals.y.push(pos); } // Always traverse from the farthest cell in the chosen direction if (vector.x === 1) traversals.x = traversals.x.reverse(); if (vector.y === 1) traversals.y = traversals.y.reverse(); return traversals; }; GameManager.prototype.findFarthestPosition = function (cell, vector) { var previous; // Progress towards the vector direction until an obstacle is found do { previous = cell; cell = { x: previous.x + vector.x, y: previous.y + vector.y }; } while (this.grid.withinBounds(cell) && this.grid.cellAvailable(cell)); return { farthest: previous, next: cell // Used to check if a merge is required }; }; GameManager.prototype.movesAvailable = function () { return this.grid.cellsAvailable() || this.tileMatchesAvailable(); }; // Check for available matches between tiles (more expensive check) GameManager.prototype.tileMatchesAvailable = function () { var self = this; var tile; for (var x = 0; x < this.size; x++) { for (var y = 0; y < this.size; y++) { tile = this.grid.cellContent({ x: x, y: y }); if (tile) { for (var direction = 0; direction < 4; direction++) { var vector = self.getVector(direction); var cell = { x: x + vector.x, y: y + vector.y }; var other = self.grid.cellContent(cell); if (other && other.value === tile.value) { return true; // These two tiles can be merged } } } } } return false; }; GameManager.prototype.positionsEqual = function (first, second) { return first.x === second.x && first.y === second.y; }; ================================================ FILE: examples/2048/js/game/grid.js ================================================ function Grid(size, previousState) { this.size = size; this.cells = previousState ? this.fromState(previousState) : this.empty(); } // Build a grid of the specified size Grid.prototype.empty = function () { var cells = []; for (var x = 0; x < this.size; x++) { var row = cells[x] = []; for (var y = 0; y < this.size; y++) { row.push(null); } } return cells; }; Grid.prototype.fromState = function (state) { var cells = []; for (var x = 0; x < this.size; x++) { var row = cells[x] = []; for (var y = 0; y < this.size; y++) { var tile = state[x][y]; row.push(tile ? new Tile(tile.position, tile.value) : null); } } return cells; }; // Find the first available random position Grid.prototype.randomAvailableCell = function () { var cells = this.availableCells(); if (cells.length) { return cells[Math.floor(Math.random() * cells.length)]; } }; Grid.prototype.availableCells = function () { var cells = []; this.eachCell(function (x, y, tile) { if (!tile) { cells.push({ x: x, y: y }); } }); return cells; }; // Call callback for every cell Grid.prototype.eachCell = function (callback) { for (var x = 0; x < this.size; x++) { for (var y = 0; y < this.size; y++) { callback(x, y, this.cells[x][y]); } } }; // Check if there are any cells available Grid.prototype.cellsAvailable = function () { return !!this.availableCells().length; }; // Check if the specified cell is taken Grid.prototype.cellAvailable = function (cell) { return !this.cellOccupied(cell); }; Grid.prototype.cellOccupied = function (cell) { return !!this.cellContent(cell); }; Grid.prototype.cellContent = function (cell) { if (this.withinBounds(cell)) { return this.cells[cell.x][cell.y]; } else { return null; } }; // Inserts a tile at its position Grid.prototype.insertTile = function (tile) { this.cells[tile.x][tile.y] = tile; }; Grid.prototype.removeTile = function (tile) { this.cells[tile.x][tile.y] = null; }; Grid.prototype.withinBounds = function (position) { return position.x >= 0 && position.x < this.size && position.y >= 0 && position.y < this.size; }; Grid.prototype.serialize = function () { var cellState = []; for (var x = 0; x < this.size; x++) { var row = cellState[x] = []; for (var y = 0; y < this.size; y++) { row.push(this.cells[x][y] ? this.cells[x][y].serialize() : null); } } return { size: this.size, cells: cellState }; }; ================================================ FILE: examples/2048/js/game/html_actuator.js ================================================ function HTMLActuator() { this.tileContainer = document.querySelector(".tile-container"); this.scoreContainer = document.querySelector(".score-container"); this.bestContainer = document.querySelector(".best-container"); this.messageContainer = document.querySelector(".game-message"); this.moves = 0; this.score = 0; } HTMLActuator.prototype.actuate = function (grid, metadata) { var self = this; window.requestAnimationFrame(function () { self.clearContainer(self.tileContainer); grid.cells.forEach(function (column) { column.forEach(function (cell) { if (cell) { self.addTile(cell); } }); }); self.updateScore(metadata.score); self.updateBestScore(metadata.bestScore); if (metadata.terminated) { if (metadata.over) { self.message(false); // You lose } else if (metadata.won) { self.message(true); // You win! } } }); }; // Continues the game (both restart and keep playing) HTMLActuator.prototype.continueGame = function () { this.clearMessage(); }; HTMLActuator.prototype.clearContainer = function (container) { while (container.firstChild) { container.removeChild(container.firstChild); } }; HTMLActuator.prototype.addTile = function (tile) { var self = this; var wrapper = document.createElement("div"); var inner = document.createElement("div"); var position = tile.previousPosition || { x: tile.x, y: tile.y }; var positionClass = this.positionClass(position); // We can't use classlist because it somehow glitches when replacing classes var classes = ["tile", "tile-" + tile.value, positionClass]; if (tile.value > 2048) classes.push("tile-super"); this.applyClasses(wrapper, classes); inner.classList.add("tile-inner"); inner.textContent = tile.value; if (tile.previousPosition) { // Make sure that the tile gets rendered in the previous position first window.requestAnimationFrame(function () { classes[2] = self.positionClass({ x: tile.x, y: tile.y }); self.applyClasses(wrapper, classes); // Update the position }); } else if (tile.mergedFrom) { classes.push("tile-merged"); this.applyClasses(wrapper, classes); // Render the tiles that merged tile.mergedFrom.forEach(function (merged) { self.addTile(merged); }); } else { classes.push("tile-new"); this.applyClasses(wrapper, classes); } // Add the inner part of the tile to the wrapper wrapper.appendChild(inner); // Put the tile on the board this.tileContainer.appendChild(wrapper); }; HTMLActuator.prototype.applyClasses = function (element, classes) { element.setAttribute("class", classes.join(" ")); }; HTMLActuator.prototype.normalizePosition = function (position) { return { x: position.x + 1, y: position.y + 1 }; }; HTMLActuator.prototype.positionClass = function (position) { position = this.normalizePosition(position); return "tile-position-" + position.x + "-" + position.y; }; HTMLActuator.prototype.updateScore = function (score) { this.clearContainer(this.scoreContainer); var difference = score - this.score; this.score = score; this.scoreContainer.textContent = this.score; if (difference > 0) { var addition = document.createElement("div"); addition.classList.add("score-addition"); addition.textContent = "+" + difference; this.scoreContainer.appendChild(addition); } }; HTMLActuator.prototype.updateBestScore = function (bestScore) { //TODO: ADD KEEN's best scores this.bestContainer.textContent = bestScore; }; HTMLActuator.prototype.message = function (won) { var type = won ? "game-won" : "game-over"; var message = won ? "You win!" : "Game over!"; this.messageContainer.classList.add(type); this.messageContainer.getElementsByTagName("p")[0].textContent = message; window.client.addEvent("new_high_score2", { "best_score": this.score }); window.client.addEvent("moves", { "moves": this.moves }); }; HTMLActuator.prototype.clearMessage = function () { // IE only takes one value to remove at a time. this.messageContainer.classList.remove("game-won"); this.messageContainer.classList.remove("game-over"); }; ================================================ FILE: examples/2048/js/game/keyboard_input_manager.js ================================================ function KeyboardInputManager() { this.events = {}; if (window.navigator.msPointerEnabled) { //Internet Explorer 10 style this.eventTouchstart = "MSPointerDown"; this.eventTouchmove = "MSPointerMove"; this.eventTouchend = "MSPointerUp"; } else { this.eventTouchstart = "touchstart"; this.eventTouchmove = "touchmove"; this.eventTouchend = "touchend"; } this.listen(); } KeyboardInputManager.prototype.on = function (event, callback) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(callback); }; KeyboardInputManager.prototype.emit = function (event, data) { var callbacks = this.events[event]; if (callbacks) { callbacks.forEach(function (callback) { callback(data); }); } }; KeyboardInputManager.prototype.listen = function () { var self = this; var map = { 38: 0, // Up 39: 1, // Right 40: 2, // Down 37: 3, // Left 75: 0, // Vim up 76: 1, // Vim right 74: 2, // Vim down 72: 3, // Vim left 87: 0, // W 68: 1, // D 83: 2, // S 65: 3 // A }; // Respond to direction keys document.addEventListener("keydown", function (event) { var modifiers = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; var mapped = map[event.which]; if (!modifiers) { if (mapped !== undefined) { event.preventDefault(); self.emit("move", mapped); } } // R key restarts the game if (!modifiers && event.which === 82) { self.restart.call(self, event); } }); // Respond to button presses this.bindButtonPress(".retry-button", this.restart); this.bindButtonPress(".restart-button", this.restart); this.bindButtonPress(".keep-playing-button", this.keepPlaying); // Respond to swipe events var touchStartClientX, touchStartClientY; var gameContainer = document.getElementsByClassName("game-container")[0]; gameContainer.addEventListener(this.eventTouchstart, function (event) { if ((!window.navigator.msPointerEnabled && event.touches.length > 1) || event.targetTouches > 1) { return; // Ignore if touching with more than 1 finger } if (window.navigator.msPointerEnabled) { touchStartClientX = event.pageX; touchStartClientY = event.pageY; } else { touchStartClientX = event.touches[0].clientX; touchStartClientY = event.touches[0].clientY; } event.preventDefault(); }); gameContainer.addEventListener(this.eventTouchmove, function (event) { event.preventDefault(); }); gameContainer.addEventListener(this.eventTouchend, function (event) { if ((!window.navigator.msPointerEnabled && event.touches.length > 0) || event.targetTouches > 0) { return; // Ignore if still touching with one or more fingers } var touchEndClientX, touchEndClientY; if (window.navigator.msPointerEnabled) { touchEndClientX = event.pageX; touchEndClientY = event.pageY; } else { touchEndClientX = event.changedTouches[0].clientX; touchEndClientY = event.changedTouches[0].clientY; } var dx = touchEndClientX - touchStartClientX; var absDx = Math.abs(dx); var dy = touchEndClientY - touchStartClientY; var absDy = Math.abs(dy); if (Math.max(absDx, absDy) > 10) { // (right : left) : (down : up) self.emit("move", absDx > absDy ? (dx > 0 ? 1 : 3) : (dy > 0 ? 2 : 0)); } }); }; KeyboardInputManager.prototype.restart = function (event) { event.preventDefault(); this.emit("restart"); }; KeyboardInputManager.prototype.keepPlaying = function (event) { event.preventDefault(); this.emit("keepPlaying"); }; KeyboardInputManager.prototype.bindButtonPress = function (selector, fn) { var button = document.querySelector(selector); button.addEventListener("click", fn.bind(this)); button.addEventListener(this.eventTouchend, fn.bind(this)); }; ================================================ FILE: examples/2048/js/game/local_storage_manager.js ================================================ window.fakeStorage = { _data: {}, setItem: function (id, val) { return this._data[id] = String(val); }, getItem: function (id) { return this._data.hasOwnProperty(id) ? this._data[id] : undefined; }, removeItem: function (id) { return delete this._data[id]; }, clear: function () { return this._data = {}; } }; function LocalStorageManager() { this.bestScoreKey = "bestScore"; this.gameStateKey = "gameState"; var supported = this.localStorageSupported(); this.storage = supported ? window.localStorage : window.fakeStorage; } LocalStorageManager.prototype.localStorageSupported = function () { var testKey = "test"; var storage = window.localStorage; try { storage.setItem(testKey, "1"); storage.removeItem(testKey); return true; } catch (error) { return false; } }; // Best score getters/setters LocalStorageManager.prototype.getBestScore = function () { return this.storage.getItem(this.bestScoreKey) || 0; }; LocalStorageManager.prototype.setBestScore = function (score) { this.storage.setItem(this.bestScoreKey, score); }; // Game state getters/setters and clearing LocalStorageManager.prototype.getGameState = function () { var stateJSON = this.storage.getItem(this.gameStateKey); return stateJSON ? JSON.parse(stateJSON) : null; }; LocalStorageManager.prototype.setGameState = function (gameState) { this.storage.setItem(this.gameStateKey, JSON.stringify(gameState)); }; LocalStorageManager.prototype.clearGameState = function () { this.storage.removeItem(this.gameStateKey); }; ================================================ FILE: examples/2048/js/game/tile.js ================================================ function Tile(position, value) { this.x = position.x; this.y = position.y; this.value = value || 2; this.previousPosition = null; this.mergedFrom = null; // Tracks tiles that merged together } Tile.prototype.savePosition = function () { this.previousPosition = { x: this.x, y: this.y }; }; Tile.prototype.updatePosition = function (position) { this.x = position.x; this.y = position.y; }; Tile.prototype.serialize = function () { return { position: { x: this.x, y: this.y }, value: this.value }; }; ================================================ FILE: examples/2048/styles/game/fonts/clear-sans.css ================================================ @font-face { font-family: "Clear Sans"; src: url("ClearSans-Light-webfont.eot"); src: url("ClearSans-Light-webfont.eot?#iefix") format("embedded-opentype"), url("ClearSans-Light-webfont.svg#clear_sans_lightregular") format("svg"), url("ClearSans-Light-webfont.woff") format("woff"); font-weight: 200; font-style: normal; } @font-face { font-family: "Clear Sans"; src: url("ClearSans-Regular-webfont.eot"); src: url("ClearSans-Regular-webfont.eot?#iefix") format("embedded-opentype"), url("ClearSans-Regular-webfont.svg#clear_sansregular") format("svg"), url("ClearSans-Regular-webfont.woff") format("woff"); font-weight: normal; font-style: normal; } @font-face { font-family: "Clear Sans"; src: url("ClearSans-Bold-webfont.eot"); src: url("ClearSans-Bold-webfont.eot?#iefix") format("embedded-opentype"), url("ClearSans-Bold-webfont.svg#clear_sansbold") format("svg"), url("ClearSans-Bold-webfont.woff") format("woff"); font-weight: 700; font-style: normal; } ================================================ FILE: examples/2048/styles/game/helpers.scss ================================================ // Exponent // From: https://github.com/Team-Sass/Sassy-math/blob/master/sass/math.scss#L36 @function exponent($base, $exponent) { // reset value $value: $base; // positive intergers get multiplied @if $exponent > 1 { @for $i from 2 through $exponent { $value: $value * $base; } } // negitive intergers get divided. A number divided by itself is 1 @if $exponent < 1 { @for $i from 0 through -$exponent { $value: $value / $base; } } // return the last value written @return $value; } @function pow($base, $exponent) { @return exponent($base, $exponent); } // Transition mixins @mixin transition($args...) { -webkit-transition: $args; -moz-transition: $args; transition: $args; } @mixin transition-property($args...) { -webkit-transition-property: $args; -moz-transition-property: $args; transition-property: $args; } @mixin animation($args...) { -webkit-animation: $args; -moz-animation: $args; animation: $args; } @mixin animation-fill-mode($args...) { -webkit-animation-fill-mode: $args; -moz-animation-fill-mode: $args; animation-fill-mode: $args; } @mixin transform($args...) { -webkit-transform: $args; -moz-transform: $args; transform: $args; } // Keyframe animations @mixin keyframes($animation-name) { @-webkit-keyframes $animation-name { @content; } @-moz-keyframes $animation-name { @content; } @keyframes $animation-name { @content; } } // Media queries @mixin smaller($width) { @media screen and (max-width: $width) { @content; } } // Clearfix @mixin clearfix { &:after { content: ""; display: block; clear: both; } } ================================================ FILE: examples/2048/styles/game/main.css ================================================ @import url(fonts/clear-sans.css); .navbar-inverse { background-color: #3d4a57; border-color: #333; } html, body { margin: 0; padding: 0; background: #4d4d4d; color: #776e65; font-family: "Clear Sans", "Helvetica Neue", Arial, sans-serif; font-size: 18px; } .header { background-color: #00afd7; padding: 50px; color: white; } .heading:after { content: ""; display: block; clear: both; } h1.title { font-size: 80px; font-weight: bold; margin: 0; display: block; float: left; } @-webkit-keyframes move-up { 0% { top: 25px; opacity: 1; } 100% { top: -50px; opacity: 0; } } @-moz-keyframes move-up { 0% { top: 25px; opacity: 1; } 100% { top: -50px; opacity: 0; } } @keyframes move-up { 0% { top: 25px; opacity: 1; } 100% { top: -50px; opacity: 0; } } .scores-container { float: right; text-align: right; } .score-container, .best-container { position: relative; display: inline-block; background: #bbada0; padding: 15px 25px; font-size: 25px; height: 60px; line-height: 47px; font-weight: bold; border-radius: 3px; color: white; margin-top: 8px; text-align: center; } .score-container:after, .best-container:after { position: absolute; width: 100%; top: 10px; left: 0; text-transform: uppercase; font-size: 13px; line-height: 13px; text-align: center; color: #eee4da; } .score-container .score-addition, .best-container .score-addition { position: absolute; right: 30px; color: red; font-size: 25px; line-height: 25px; font-weight: bold; color: rgba(119, 110, 101, 0.9); z-index: 100; -webkit-animation: move-up 600ms ease-in; -moz-animation: move-up 600ms ease-in; animation: move-up 600ms ease-in; -webkit-animation-fill-mode: both; -moz-animation-fill-mode: both; animation-fill-mode: both; } .score-container:after { content: "Score"; } .best-container:after { content: "Best"; } p { margin-top: 0; margin-bottom: 10px; line-height: 1.65; } a { color: #776e65; font-weight: bold; text-decoration: underline; cursor: pointer; } strong.important { text-transform: uppercase; } hr { border: none; border-bottom: 1px solid #d8d4d0; margin-top: 20px; margin-bottom: 30px; } .container-2048 { width: 960px; background-color: white; padding: 0 50px 50px 50px; margin: 50px auto; } .main-container { margin-top: 40px; } @-webkit-keyframes fade-in { 0% { opacity: 0; } 100% { opacity: 1; } } @-moz-keyframes fade-in { 0% { opacity: 0; } 100% { opacity: 1; } } @keyframes fade-in { 0% { opacity: 0; } 100% { opacity: 1; } } .game-container { margin-top: 0px; position: relative; padding: 15px; cursor: default; -webkit-touch-callout: none; -ms-touch-callout: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -ms-touch-action: none; touch-action: none; background: #bbada0; border-radius: 6px; width: 500px; height: 500px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .game-container .game-message { display: none; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: rgba(238, 228, 218, 0.5); z-index: 100; text-align: center; -webkit-animation: fade-in 800ms ease 1200ms; -moz-animation: fade-in 800ms ease 1200ms; animation: fade-in 800ms ease 1200ms; -webkit-animation-fill-mode: both; -moz-animation-fill-mode: both; animation-fill-mode: both; } .game-container .game-message p { font-size: 60px; font-weight: bold; height: 60px; line-height: 60px; margin-top: 222px; } .game-container .game-message .lower { display: block; margin-top: 59px; } .game-container .game-message a { display: inline-block; background: #8f7a66; border-radius: 3px; padding: 0 20px; text-decoration: none; color: #f9f6f2; height: 40px; line-height: 42px; margin-left: 9px; } .game-container .game-message a.keep-playing-button { display: none; } .game-container .game-message.game-won { background: rgba(237, 194, 46, 0.5); color: #f9f6f2; } .game-container .game-message.game-won a.keep-playing-button { display: inline-block; } .game-container .game-message.game-won, .game-container .game-message.game-over { display: block; } .grid-container { position: absolute; z-index: 1; } .grid-row { margin-bottom: 15px; } .grid-row:last-child { margin-bottom: 0; } .grid-row:after { content: ""; display: block; clear: both; } .grid-cell { width: 106.25px; height: 106.25px; margin-right: 15px; float: left; border-radius: 3px; background: rgba(238, 228, 218, 0.35); } .grid-cell:last-child { margin-right: 0; } .tile-container { position: absolute; z-index: 2; } .tile, .tile .tile-inner { width: 107px; height: 107px; line-height: 116.25px; } .tile.tile-position-1-1 { -webkit-transform: translate(0px, 0px); -moz-transform: translate(0px, 0px); transform: translate(0px, 0px); } .tile.tile-position-1-2 { -webkit-transform: translate(0px, 121px); -moz-transform: translate(0px, 121px); transform: translate(0px, 121px); } .tile.tile-position-1-3 { -webkit-transform: translate(0px, 242px); -moz-transform: translate(0px, 242px); transform: translate(0px, 242px); } .tile.tile-position-1-4 { -webkit-transform: translate(0px, 363px); -moz-transform: translate(0px, 363px); transform: translate(0px, 363px); } .tile.tile-position-2-1 { -webkit-transform: translate(121px, 0px); -moz-transform: translate(121px, 0px); transform: translate(121px, 0px); } .tile.tile-position-2-2 { -webkit-transform: translate(121px, 121px); -moz-transform: translate(121px, 121px); transform: translate(121px, 121px); } .tile.tile-position-2-3 { -webkit-transform: translate(121px, 242px); -moz-transform: translate(121px, 242px); transform: translate(121px, 242px); } .tile.tile-position-2-4 { -webkit-transform: translate(121px, 363px); -moz-transform: translate(121px, 363px); transform: translate(121px, 363px); } .tile.tile-position-3-1 { -webkit-transform: translate(242px, 0px); -moz-transform: translate(242px, 0px); transform: translate(242px, 0px); } .tile.tile-position-3-2 { -webkit-transform: translate(242px, 121px); -moz-transform: translate(242px, 121px); transform: translate(242px, 121px); } .tile.tile-position-3-3 { -webkit-transform: translate(242px, 242px); -moz-transform: translate(242px, 242px); transform: translate(242px, 242px); } .tile.tile-position-3-4 { -webkit-transform: translate(242px, 363px); -moz-transform: translate(242px, 363px); transform: translate(242px, 363px); } .tile.tile-position-4-1 { -webkit-transform: translate(363px, 0px); -moz-transform: translate(363px, 0px); transform: translate(363px, 0px); } .tile.tile-position-4-2 { -webkit-transform: translate(363px, 121px); -moz-transform: translate(363px, 121px); transform: translate(363px, 121px); } .tile.tile-position-4-3 { -webkit-transform: translate(363px, 242px); -moz-transform: translate(363px, 242px); transform: translate(363px, 242px); } .tile.tile-position-4-4 { -webkit-transform: translate(363px, 363px); -moz-transform: translate(363px, 363px); transform: translate(363px, 363px); } .tile { position: absolute; -webkit-transition: 100ms ease-in-out; -moz-transition: 100ms ease-in-out; transition: 100ms ease-in-out; -webkit-transition-property: -webkit-transform; -moz-transition-property: -moz-transform; transition-property: transform; } .tile .tile-inner { border-radius: 3px; background: #eee4da; text-align: center; font-weight: bold; z-index: 10; font-size: 55px; } .tile.tile-2 .tile-inner { background: #eee4da; box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); } .tile.tile-4 .tile-inner { background: #ede0c8; box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); } .tile.tile-8 .tile-inner { color: #f9f6f2; background: #f2b179; } .tile.tile-16 .tile-inner { color: #f9f6f2; background: #f59563; } .tile.tile-32 .tile-inner { color: #f9f6f2; background: #f67c5f; } .tile.tile-64 .tile-inner { color: #f9f6f2; background: #f65e3b; } .tile.tile-128 .tile-inner { color: #f9f6f2; background: #edcf72; box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.2381), inset 0 0 0 1px rgba(255, 255, 255, 0.14286); font-size: 45px; } @media screen and (max-width: 520px) { .tile.tile-128 .tile-inner { font-size: 25px; } } .tile.tile-256 .tile-inner { color: #f9f6f2; background: #edcc61; box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.31746), inset 0 0 0 1px rgba(255, 255, 255, 0.19048); font-size: 45px; } @media screen and (max-width: 520px) { .tile.tile-256 .tile-inner { font-size: 25px; } } .tile.tile-512 .tile-inner { color: #f9f6f2; background: #edc850; box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.39683), inset 0 0 0 1px rgba(255, 255, 255, 0.2381); font-size: 45px; } @media screen and (max-width: 520px) { .tile.tile-512 .tile-inner { font-size: 25px; } } .tile.tile-1024 .tile-inner { color: #f9f6f2; background: #edc53f; box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.47619), inset 0 0 0 1px rgba(255, 255, 255, 0.28571); font-size: 35px; } @media screen and (max-width: 520px) { .tile.tile-1024 .tile-inner { font-size: 15px; } } .tile.tile-2048 .tile-inner { color: #f9f6f2; background: #edc22e; box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.55556), inset 0 0 0 1px rgba(255, 255, 255, 0.33333); font-size: 35px; } @media screen and (max-width: 520px) { .tile.tile-2048 .tile-inner { font-size: 15px; } } .tile.tile-super .tile-inner { color: #f9f6f2; background: #3c3a32; font-size: 30px; } @media screen and (max-width: 520px) { .tile.tile-super .tile-inner { font-size: 10px; } } @-webkit-keyframes appear { 0% { opacity: 0; -webkit-transform: scale(0); -moz-transform: scale(0); transform: scale(0); } 100% { opacity: 1; -webkit-transform: scale(1); -moz-transform: scale(1); transform: scale(1); } } @-moz-keyframes appear { 0% { opacity: 0; -webkit-transform: scale(0); -moz-transform: scale(0); transform: scale(0); } 100% { opacity: 1; -webkit-transform: scale(1); -moz-transform: scale(1); transform: scale(1); } } @keyframes appear { 0% { opacity: 0; -webkit-transform: scale(0); -moz-transform: scale(0); transform: scale(0); } 100% { opacity: 1; -webkit-transform: scale(1); -moz-transform: scale(1); transform: scale(1); } } .tile-new .tile-inner { -webkit-animation: appear 200ms ease 100ms; -moz-animation: appear 200ms ease 100ms; animation: appear 200ms ease 100ms; -webkit-animation-fill-mode: backwards; -moz-animation-fill-mode: backwards; animation-fill-mode: backwards; } @-webkit-keyframes pop { 0% { -webkit-transform: scale(0); -moz-transform: scale(0); transform: scale(0); } 50% { -webkit-transform: scale(1.2); -moz-transform: scale(1.2); transform: scale(1.2); } 100% { -webkit-transform: scale(1); -moz-transform: scale(1); transform: scale(1); } } @-moz-keyframes pop { 0% { -webkit-transform: scale(0); -moz-transform: scale(0); transform: scale(0); } 50% { -webkit-transform: scale(1.2); -moz-transform: scale(1.2); transform: scale(1.2); } 100% { -webkit-transform: scale(1); -moz-transform: scale(1); transform: scale(1); } } @keyframes pop { 0% { -webkit-transform: scale(0); -moz-transform: scale(0); transform: scale(0); } 50% { -webkit-transform: scale(1.2); -moz-transform: scale(1.2); transform: scale(1.2); } 100% { -webkit-transform: scale(1); -moz-transform: scale(1); transform: scale(1); } } .tile-merged .tile-inner { z-index: 20; -webkit-animation: pop 200ms ease 100ms; -moz-animation: pop 200ms ease 100ms; animation: pop 200ms ease 100ms; -webkit-animation-fill-mode: backwards; -moz-animation-fill-mode: backwards; animation-fill-mode: backwards; } .above-game { margin-top: 5px; } .above-game:after { content: ""; display: block; clear: both; } .game-intro { float: left; line-height: 42px; margin-bottom: 0; } .restart-button { display: inline-block; background: #8f7a66; border-radius: 3px; padding: 0 20px; text-decoration: none; color: #f9f6f2; height: 40px; line-height: 42px; display: block; text-align: center; float: right; } .game-explanation { margin-top: 50px; } @media screen and (max-width: 520px) { html, body { font-size: 15px; } body { margin: 20px 0; padding: 0 20px; } h1.title { font-size: 27px; margin-top: 15px; } .container { width: 280px; margin: 0 auto; } .score-container, .best-container { margin-top: 0; padding: 15px 10px; min-width: 40px; } .heading { margin-bottom: 10px; } .game-intro { width: 55%; display: block; box-sizing: border-box; line-height: 1.65; } .restart-button { width: 42%; padding: 0; display: block; box-sizing: border-box; margin-top: 2px; } .game-container { margin-top: 17px; position: relative; padding: 10px; cursor: default; -webkit-touch-callout: none; -ms-touch-callout: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -ms-touch-action: none; touch-action: none; background: #bbada0; border-radius: 6px; width: 280px; height: 280px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .game-container .game-message { display: none; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: rgba(238, 228, 218, 0.5); z-index: 100; text-align: center; -webkit-animation: fade-in 800ms ease 1200ms; -moz-animation: fade-in 800ms ease 1200ms; animation: fade-in 800ms ease 1200ms; -webkit-animation-fill-mode: both; -moz-animation-fill-mode: both; animation-fill-mode: both; } .game-container .game-message p { font-size: 60px; font-weight: bold; height: 60px; line-height: 60px; margin-top: 222px; } .game-container .game-message .lower { display: block; margin-top: 59px; } .game-container .game-message a { display: inline-block; background: #8f7a66; border-radius: 3px; padding: 0 20px; text-decoration: none; color: #f9f6f2; height: 40px; line-height: 42px; margin-left: 9px; } .game-container .game-message a.keep-playing-button { display: none; } .game-container .game-message.game-won { background: rgba(237, 194, 46, 0.5); color: #f9f6f2; } .game-container .game-message.game-won a.keep-playing-button { display: inline-block; } .game-container .game-message.game-won, .game-container .game-message.game-over { display: block; } .grid-container { position: absolute; z-index: 1; } .grid-row { margin-bottom: 10px; } .grid-row:last-child { margin-bottom: 0; } .grid-row:after { content: ""; display: block; clear: both; } .grid-cell { width: 57.5px; height: 57.5px; margin-right: 10px; float: left; border-radius: 3px; background: rgba(238, 228, 218, 0.35); } .grid-cell:last-child { margin-right: 0; } .tile-container { position: absolute; z-index: 2; } .tile, .tile .tile-inner { width: 58px; height: 58px; line-height: 67.5px; } .tile.tile-position-1-1 { -webkit-transform: translate(0px, 0px); -moz-transform: translate(0px, 0px); transform: translate(0px, 0px); } .tile.tile-position-1-2 { -webkit-transform: translate(0px, 67px); -moz-transform: translate(0px, 67px); transform: translate(0px, 67px); } .tile.tile-position-1-3 { -webkit-transform: translate(0px, 135px); -moz-transform: translate(0px, 135px); transform: translate(0px, 135px); } .tile.tile-position-1-4 { -webkit-transform: translate(0px, 202px); -moz-transform: translate(0px, 202px); transform: translate(0px, 202px); } .tile.tile-position-2-1 { -webkit-transform: translate(67px, 0px); -moz-transform: translate(67px, 0px); transform: translate(67px, 0px); } .tile.tile-position-2-2 { -webkit-transform: translate(67px, 67px); -moz-transform: translate(67px, 67px); transform: translate(67px, 67px); } .tile.tile-position-2-3 { -webkit-transform: translate(67px, 135px); -moz-transform: translate(67px, 135px); transform: translate(67px, 135px); } .tile.tile-position-2-4 { -webkit-transform: translate(67px, 202px); -moz-transform: translate(67px, 202px); transform: translate(67px, 202px); } .tile.tile-position-3-1 { -webkit-transform: translate(135px, 0px); -moz-transform: translate(135px, 0px); transform: translate(135px, 0px); } .tile.tile-position-3-2 { -webkit-transform: translate(135px, 67px); -moz-transform: translate(135px, 67px); transform: translate(135px, 67px); } .tile.tile-position-3-3 { -webkit-transform: translate(135px, 135px); -moz-transform: translate(135px, 135px); transform: translate(135px, 135px); } .tile.tile-position-3-4 { -webkit-transform: translate(135px, 202px); -moz-transform: translate(135px, 202px); transform: translate(135px, 202px); } .tile.tile-position-4-1 { -webkit-transform: translate(202px, 0px); -moz-transform: translate(202px, 0px); transform: translate(202px, 0px); } .tile.tile-position-4-2 { -webkit-transform: translate(202px, 67px); -moz-transform: translate(202px, 67px); transform: translate(202px, 67px); } .tile.tile-position-4-3 { -webkit-transform: translate(202px, 135px); -moz-transform: translate(202px, 135px); transform: translate(202px, 135px); } .tile.tile-position-4-4 { -webkit-transform: translate(202px, 202px); -moz-transform: translate(202px, 202px); transform: translate(202px, 202px); } .tile .tile-inner { font-size: 35px; } .game-message p { font-size: 30px !important; height: 30px !important; line-height: 30px !important; margin-top: 90px !important; } .game-message .lower { margin-top: 30px !important; } } ================================================ FILE: examples/2048/styles/game/main.scss ================================================ @import "helpers"; @import "fonts/clear-sans.css"; $field-width: 500px; $grid-spacing: 15px; $grid-row-cells: 4; $tile-size: ($field-width - $grid-spacing * ($grid-row-cells + 1)) / $grid-row-cells; $tile-border-radius: 3px; $mobile-threshold: $field-width + 20px; $text-color: #776E65; $bright-text-color: #f9f6f2; $tile-color: #eee4da; $tile-gold-color: #edc22e; $tile-gold-glow-color: lighten($tile-gold-color, 15%); $game-container-margin-top: 0px; $game-container-background: #bbada0; $transition-speed: 100ms; // BOOTSTRAP OVERRIDES .navbar-inverse { background-color: #3d4a57; border-color: #333; } html, body { margin: 0; padding: 0; background: rgb(77,77,77); color: $text-color; font-family: "Clear Sans", "Helvetica Neue", Arial, sans-serif; font-size: 18px; } .header { background-color: rgb(0, 175, 215); padding: 50px; color: white; } .heading { @include clearfix; } h1.title { font-size: 80px; font-weight: bold; margin: 0; display: block; float: left; } @include keyframes(move-up) { 0% { top: 25px; opacity: 1; } 100% { top: -50px; opacity: 0; } } .scores-container { float: right; text-align: right; } .score-container, .best-container { $height: 25px; position: relative; display: inline-block; background: $game-container-background; padding: 15px 25px; font-size: $height; height: 60px; line-height: $height + 22px; font-weight: bold; border-radius: 3px; color: white; margin-top: 8px; text-align: center; &:after { position: absolute; width: 100%; top: 10px; left: 0; text-transform: uppercase; font-size: 13px; line-height: 13px; text-align: center; color: $tile-color; } .score-addition { position: absolute; right: 30px; color: red; font-size: $height; line-height: $height; font-weight: bold; color: rgba($text-color, .9); z-index: 100; @include animation(move-up 600ms ease-in); @include animation-fill-mode(both); } } .score-container:after { content: "Score"; } .best-container:after { content: "Best" } p { margin-top: 0; margin-bottom: 10px; line-height: 1.65; } a { color: $text-color; font-weight: bold; text-decoration: underline; cursor: pointer; } strong { &.important { text-transform: uppercase; } } hr { border: none; border-bottom: 1px solid lighten($text-color, 40%); margin-top: 20px; margin-bottom: 30px; } .container-2048 { width: 960px; background-color: white; padding: 0 50px 50px 50px; margin: 50px auto; } .main-container { margin-top: 40px; } @include keyframes(fade-in) { 0% { opacity: 0; } 100% { opacity: 1; } } // Styles for buttons @mixin button { display: inline-block; background: darken($game-container-background, 20%); border-radius: 3px; padding: 0 20px; text-decoration: none; color: $bright-text-color; height: 40px; line-height: 42px; } // Game field mixin used to render CSS at different width @mixin game-field { .game-container { margin-top: $game-container-margin-top; position: relative; padding: $grid-spacing; cursor: default; -webkit-touch-callout: none; -ms-touch-callout: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -ms-touch-action: none; touch-action: none; background: $game-container-background; border-radius: $tile-border-radius * 2; width: $field-width; height: $field-width; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; .game-message { display: none; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: rgba($tile-color, .5); z-index: 100; text-align: center; p { font-size: 60px; font-weight: bold; height: 60px; line-height: 60px; margin-top: 222px; // height: $field-width; // line-height: $field-width; } .lower { display: block; margin-top: 59px; } a { @include button; margin-left: 9px; // margin-top: 59px; &.keep-playing-button { display: none; } } @include animation(fade-in 800ms ease $transition-speed * 12); @include animation-fill-mode(both); &.game-won { background: rgba($tile-gold-color, .5); color: $bright-text-color; a.keep-playing-button { display: inline-block; } } &.game-won, &.game-over { display: block; } } } .grid-container { position: absolute; z-index: 1; } .grid-row { margin-bottom: $grid-spacing; &:last-child { margin-bottom: 0; } &:after { content: ""; display: block; clear: both; } } .grid-cell { width: $tile-size; height: $tile-size; margin-right: $grid-spacing; float: left; border-radius: $tile-border-radius; background: rgba($tile-color, .35); &:last-child { margin-right: 0; } } .tile-container { position: absolute; z-index: 2; } .tile { &, .tile-inner { width: ceil($tile-size); height: ceil($tile-size); line-height: $tile-size + 10px; } // Build position classes @for $x from 1 through $grid-row-cells { @for $y from 1 through $grid-row-cells { &.tile-position-#{$x}-#{$y} { $xPos: floor(($tile-size + $grid-spacing) * ($x - 1)); $yPos: floor(($tile-size + $grid-spacing) * ($y - 1)); @include transform(translate($xPos, $yPos)); } } } } } @mixin stats-field { #stats-container { // background-color: purple; // width: 500px; } } @include stats-field; // End of game-field mixin @include game-field; .tile { position: absolute; // Makes transforms relative to the top-left corner .tile-inner { border-radius: $tile-border-radius; background: $tile-color; text-align: center; font-weight: bold; z-index: 10; font-size: 55px; } // Movement transition @include transition($transition-speed ease-in-out); -webkit-transition-property: -webkit-transform; -moz-transition-property: -moz-transform; transition-property: transform; $base: 2; $exponent: 1; $limit: 11; // Colors for all 11 states, false = no special color $special-colors: false false, // 2 false false, // 4 #f78e48 true, // 8 #fc5e2e true, // 16 #ff3333 true, // 32 #ff0000 true, // 64 false true, // 128 false true, // 256 false true, // 512 false true, // 1024 false true; // 2048 // Build tile colors @while $exponent <= $limit { $power: pow($base, $exponent); &.tile-#{$power} .tile-inner { // Calculate base background color $gold-percent: ($exponent - 1) / ($limit - 1) * 100; $mixed-background: mix($tile-gold-color, $tile-color, $gold-percent); $nth-color: nth($special-colors, $exponent); $special-background: nth($nth-color, 1); $bright-color: nth($nth-color, 2); @if $special-background { $mixed-background: mix($special-background, $mixed-background, 55%); } @if $bright-color { color: $bright-text-color; } // Set background background: $mixed-background; // Add glow $glow-opacity: max($exponent - 4, 0) / ($limit - 4); @if not $special-background { box-shadow: 0 0 30px 10px rgba($tile-gold-glow-color, $glow-opacity / 1.8), inset 0 0 0 1px rgba(white, $glow-opacity / 3); } // Adjust font size for bigger numbers @if $power >= 100 and $power < 1000 { font-size: 45px; // Media queries placed here to avoid carrying over the rest of the logic @include smaller($mobile-threshold) { font-size: 25px; } } @else if $power >= 1000 { font-size: 35px; @include smaller($mobile-threshold) { font-size: 15px; } } } $exponent: $exponent + 1; } // Super tiles (above 2048) &.tile-super .tile-inner { color: $bright-text-color; background: mix(#333, $tile-gold-color, 95%); font-size: 30px; @include smaller($mobile-threshold) { font-size: 10px; } } } @include keyframes(appear) { 0% { opacity: 0; @include transform(scale(0)); } 100% { opacity: 1; @include transform(scale(1)); } } .tile-new .tile-inner { @include animation(appear 200ms ease $transition-speed); @include animation-fill-mode(backwards); } @include keyframes(pop) { 0% { @include transform(scale(0)); } 50% { @include transform(scale(1.2)); } 100% { @include transform(scale(1)); } } .tile-merged .tile-inner { z-index: 20; @include animation(pop 200ms ease $transition-speed); @include animation-fill-mode(backwards); } .above-game { @include clearfix; margin-top: 5px; } .game-intro { float: left; line-height: 42px; margin-bottom: 0; } .restart-button { @include button; display: block; text-align: center; float: right; } .game-explanation { margin-top: 50px; } @include smaller($mobile-threshold) { // Redefine variables for smaller screens $field-width: 280px; $grid-spacing: 10px; $grid-row-cells: 4; $tile-size: ($field-width - $grid-spacing * ($grid-row-cells + 1)) / $grid-row-cells; $tile-border-radius: 3px; $game-container-margin-top: 17px; html, body { font-size: 15px; } body { margin: 20px 0; padding: 0 20px; } h1.title { font-size: 27px; margin-top: 15px; } .container { width: $field-width; margin: 0 auto; } .score-container, .best-container { margin-top: 0; padding: 15px 10px; min-width: 40px; } .heading { margin-bottom: 10px; } // Show intro and restart button side by side .game-intro { width: 55%; display: block; box-sizing: border-box; line-height: 1.65; } .restart-button { width: 42%; padding: 0; display: block; box-sizing: border-box; margin-top: 2px; } // Render the game field at the right width @include game-field; // Rest of the font-size adjustments in the tile class .tile .tile-inner { font-size: 35px; } .game-message { p { font-size: 30px !important; height: 30px !important; line-height: 30px !important; margin-top: 90px !important; } .lower { margin-top: 30px !important; } } } ================================================ FILE: examples/README.md ================================================ Dashboard Examples ================== ================================================ FILE: examples/connected-devices/README.md ================================================ ================================================ FILE: examples/connected-devices/connected-devices.css ================================================ #mapwrap .chart-title { border: medium none; } #mapwrap .chart-stage { height: 350px; } #map { bottom: 5px; left: 5px; position: absolute; top: 0px; right: 5px; } .chart-wrapper { min-height: 360px; } .chart-stage .knob-title { border-bottom: medium none; font-size: 18px; text-align: center; } #chart-05 { height: 340px; } .btn-group-xs>.btn { padding: 1px 5px; font-size: 12px; line-height: 1.5; border-radius: 3px; } /* custom inclusion of right, left and below tabs */ .nav-tabs { padding-top: 10px; padding-left: 10px; } .tab-content { min-width: 0; } .tab-content .tab-pane { height: 300px; } .tabs-below>.nav-tabs, .tabs-right>.nav-tabs, .tabs-left>.nav-tabs { border-bottom: 0; } .tab-content>.tab-pane, .pill-content>.pill-pane { display: none; } .tab-content>.active, .pill-content>.active { display: block; } .tabs-below>.nav-tabs { border-top: 1px solid #ddd; } .tabs-below>.nav-tabs>li { margin-top: -1px; margin-bottom: 0; } .tabs-below>.nav-tabs>li>a { -webkit-border-radius: 0 0 4px 4px; -moz-border-radius: 0 0 4px 4px; border-radius: 0 0 4px 4px; } .tabs-below>.nav-tabs>li>a:hover, .tabs-below>.nav-tabs>li>a:focus { border-top-color: #ddd; border-bottom-color: transparent; } .tabs-below>.nav-tabs>.active>a, .tabs-below>.nav-tabs>.active>a:hover, .tabs-below>.nav-tabs>.active>a:focus { border-color: transparent #ddd #ddd #ddd; } .tabs-left>.nav-tabs>li, .tabs-right>.nav-tabs>li { float: none; } .tabs-left>.nav-tabs>li>a, .tabs-right>.nav-tabs>li>a { min-width: 74px; margin-right: 0; margin-bottom: 3px; } .tabs-left>.nav-tabs { float: left; border-right: 1px solid transparent; } @media (min-width: 1200px) { .tabs-left>.nav-tabs { border-right-color: #ddd; } } .tabs-left>.nav-tabs>li>a { margin-right: -1px; color: #428bca; -webkit-border-radius: 4px 0 0 4px; -moz-border-radius: 4px 0 0 4px; border-radius: 4px 0 0 4px; } .tabs-left>.nav-tabs>li>a:hover, .tabs-left>.nav-tabs>li>a:focus { border-color: #eeeeee #dddddd #eeeeee #eeeeee; } .tabs-left>.nav-tabs .active>a, .tabs-left>.nav-tabs .active>a:hover, .tabs-left>.nav-tabs .active>a:focus { border-color: #ddd transparent #ddd #ddd; border-right-color: #ffffff; } .tabs-right>.nav-tabs { float: right; margin-left: 19px; border-left: 1px solid #ddd; } .tabs-right>.nav-tabs>li>a { margin-left: -1px; -webkit-border-radius: 0 4px 4px 0; -moz-border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0; } .tabs-right>.nav-tabs>li>a:hover, .tabs-right>.nav-tabs>li>a:focus { border-color: #eeeeee #eeeeee #eeeeee #dddddd; } .tabs-right>.nav-tabs .active>a, .tabs-right>.nav-tabs .active>a:hover, .tabs-right>.nav-tabs .active>a:focus { border-color: #ddd #ddd #ddd transparent; *border-left-color: #ffffff; } ================================================ FILE: examples/connected-devices/connected-devices.js ================================================ const client = new Keen({ projectId: '5337e28273f4bb4499000000', readKey: '8827959317a6a01257bbadf16c12eff4bc61a170863ca1dadf9b3718f56bece1ced94552c6f6fcda073de70bf860c622ed5937fcca82d57cff93b432803faed4108d2bca310ca9922d5ef6ea9381267a5bd6fd35895caec69a7e414349257ef43a29ebb764677040d4a80853e11b8a3f' }); const geoProject = new Keen({ projectId: '53eab6e12481962467000000', readKey: 'd1b97982ce67ad4b411af30e53dd75be6cf610213c35f3bd3dd2ef62eaeac14632164890413e2cc2df2e489da88e87430af43628b0c9e0b2870d0a70580d5f5fe8d9ba2a6d56f9448a3b6f62a5e6cdd1be435c227253fbe3fab27beb0d14f91b710d9a6e657ecf47775281abc17ec455' }); Keen.ready(function () { const navTabs = document.querySelector('.nav-tabs'); const tabVisitors = document.getElementById('tab-visitors'); const tabBrowsers = document.getElementById('tab-browsers'); const tabGeography = document.getElementById('tab-geography'); let activeRequest; const chart_visitors = new KeenDataviz({ container: '#visitors', title: 'Monthly Visits', type: 'area' }); const chart_browsers = new KeenDataviz({ container: '#browser', type: 'line' }); const chart_geographies = new KeenDataviz({ container: '#geography', type: 'horizontal-bar' }); navTabs.onclick = setActivePane; tabVisitors.onclick = selectVisitorTab; tabBrowsers.onclick = selectBrowserTab; tabGeography.onclick = selectGeographyTab; selectVisitorTab(); function setActivePane(e) { const tabs = this.querySelectorAll('li'); const tabContent = document.querySelector('.tab-content'); const activePane = document.querySelector(e.target.hash); tabs.forEach(tab => { tab.classList.remove('active') }); tabContent.querySelectorAll('.tab-pane').forEach(pane => { pane.classList.remove('active'); }); e.target.parentNode.classList.add('active'); activePane.classList.add('active'); } function selectVisitorTab(e) { if (e && e.preventDefault) { e.preventDefault(); } if (!chart_visitors.view._rendered) { activeRequest = renderVisitorTab(chart_visitors); } } function selectBrowserTab(e) { if (e && e.preventDefault) { e.preventDefault(); } if (!chart_browsers.view._rendered) { activeRequest = renderBrowserTab(chart_browsers); } } function selectGeographyTab(e) { if (e && e.preventDefault) { e.preventDefault(); } if (!chart_geographies.view._rendered) { activeRequest = renderGeographyTab(chart_geographies); } } function renderVisitorTab(chart) { return geoProject .query('count', { event_collection: 'activations', interval: 'monthly', timeframe: { start: '2014-01-01', end: '2014-12-01' } }) .then(res => { chart .data(res) .render(); }) .catch(err => { chart .message(err.message); }); } function renderBrowserTab(chart) { return geoProject .query('count', { event_collection: 'activations', group_by: 'device_model_name', interval: 'monthly', timeframe: { start: '2014-01-01', end: '2014-12-01' } }) .then(res => { chart .data(res) .render(); }) .catch(err => { chart .message(err.message); }); } function renderGeographyTab(chart) { return client .query('count', { event_collection: 'visit', group_by: 'visitor.geo.province', // interval: 'monthly', timeframe: { start: '2014-01-01', end: '2014-12-01' } }) .then(res => { chart .data(res) .labelMapping({ 'New Jersey': 'NJ', 'Virginia': 'VA', 'California': 'CA', 'Washington': 'WA', 'Utah': 'UT', 'Oregon': 'OR', 'null': 'Other' }) .sortGroups('desc') .render(); }) .catch(err => { chart .message(err.message); }); } // ---------------------------------------- // New Activations // ---------------------------------------- $('.users').knob({ angleArc: 250, angleOffset: -125, readOnly: true, min: 0, max: 500, fgColor: '#00bbde', height: 290, width: '95%' }); geoProject .query('count_unique', { event_collection: 'activations', target_property: 'user.id' }) .then(res => { $('.users').val(res.result).trigger('change'); }) .catch(err => { alert('An error occurred fetching New Activations metric'); }); // ---------------------------------------- // Errors Detected // ---------------------------------------- $('.errors').knob({ angleArc: 250, angleOffset: -125, readOnly: true, min: 0, max: 100, fgColor: '#fe6672', height: 290, width: '95%' }); geoProject .query('count', { event_collection: 'user_action', filters: [{ property_name: 'error_detected', operator: 'eq', property_value: true }] }) .then(res => { $('.errors').val(res.result).trigger('change'); }) .catch(err => { alert('An error occurred fetching Device Crashes metric'); }); // ---------------------------------------- // Funnel // ---------------------------------------- /* This funnel is built from mock data */ const sample_funnel = new KeenDataviz({ container: '#chart-05', type: 'bar', colors: ['#00cfbb'], labels: ['Purchased Device', 'Activated Device', 'First Session', 'Second Session', 'Invited Friend'] }) .data({ result: [3250, 3000, 2432, 1504, 321] }) .render(); // ---------------------------------------- // Mapbox - Active Users // ---------------------------------------- const tframe = { start: '2014-01-01', end: '2014-02-01' }; const DEFAULTS = { coordinates: { lat: 37.77350, lng: -122.41104 }, zoom: 11 }; let initialize, map, markerStart = DEFAULTS.coordinates; let activeMapData, heat; function setActiveButton(button) { const classButtonNormal = 'btn btn-default'; const classButtonSelected = 'btn btn-primary'; switch (button) { default: case '7days': document.getElementById('7days').className = classButtonSelected; document.getElementById('14days').className = classButtonNormal; document.getElementById('28days').className = classButtonNormal; break; case '14days': document.getElementById('7days').className = classButtonNormal; document.getElementById('14days').className = classButtonSelected; document.getElementById('28days').className = classButtonNormal; break; case '28days': document.getElementById('7days').className = classButtonNormal; document.getElementById('14days').className = classButtonNormal; document.getElementById('28days').className = classButtonSelected; break; } } initialize = () => { setActiveButton('7days'); L.mapbox.accessToken = 'pk.eyJ1Ijoia2Vlbi1pbyIsImEiOiIza0xnNXBZIn0.PgzKlxBmYkOq6jBGErpqOg'; map = L.mapbox.map('map', 'keen-io.kae20cg0', { attributionControl: true, center: [markerStart.lat, markerStart.lng], zoom: DEFAULTS.zoom }); heat = L.heatLayer([], { maxZoom: 14 }); activeMapData = L.layerGroup().addTo(map); map.attributionControl.addAttribution('Custom Analytics by Keen IO'); let geoFilter = []; geoFilter.push({ property_name: 'keen.location.coordinates', operator: 'within', property_value: { coordinates: [-122.41104, 37.77350], max_distance_miles: 10 } }); const scoped_events = new Keen.Query('select_unique', { event_collection: 'user_action', target_property: 'keen.location.coordinates', timeframe: tframe, filters: geoFilter }); function runQuery() { geoProject.run(scoped_events, (err, res) => { activeMapData.clearLayers(); Keen.utils.each(res.result, coord => { const em = L.marker(new L.LatLng(coord[1], coord[0]), { icon: L.mapbox.marker.icon() }).addTo(activeMapData); }); activeMapData.eachLayer(l => { heat.addTo(map).addLatLng(l.getLatLng()); }); activeMapData.clearLayers(); }); } let newgeoFilter = []; function resize(geo) { geo = []; heat.setLatLngs([]); const center = map.getCenter(); const zoom = map.getZoom(); z = zoom - 1; if (zoom === 0) { radius = false; } else { radius = 10000 / Math.pow(2, z); } geo.push({ property_name: 'keen.location.coordinates', operator: 'within', property_value: { coordinates: [center.lng, center.lat], max_distance_miles: radius } }); return geo; } map.on('zoomend', e => { newgeoFilter = resize(newgeoFilter); scoped_events.set({ filters: newgeoFilter }); runQuery(); }); map.on('dragend', e => { newgeoFilter = resize(newgeoFilter); scoped_events.set({ filters: newgeoFilter }); runQuery(); }); document.getElementById('14days').addEventListener('click', () => { setActiveButton('14days'); newgeoFilter = resize(newgeoFilter); scoped_events.set({ filters: newgeoFilter, timeframe: { start: '2014-01-01', end: '2014-04-01' } }); runQuery(); }); document.getElementById('28days').addEventListener('click', () => { setActiveButton('28days'); newgeoFilter = resize(newgeoFilter); scoped_events.set({ filters: newgeoFilter, timeframe: { start: '2014-01-01', end: '2014-12-01' } }); runQuery(); }); document.getElementById('7days').addEventListener('click', () => { setActiveButton('7days'); newgeoFilter = resize(newgeoFilter); scoped_events.set({ filters: newgeoFilter, timeframe: { start: '2014-01-01', end: '2014-02-01' } }); runQuery(); }); }; initialize(); }); ================================================ FILE: examples/connected-devices/index.html ================================================ Connected Devices
Traffic Stats
Stats
New Activations Today
Device Crashes Today
Customer Engagement
Active Users
================================================ FILE: examples/customer-facing/README.md ================================================ ================================================ FILE: examples/customer-facing/customer-facing.css ================================================ * { box-sizing: border-box; } img { max-width: 100%; } body { margin: 0; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.42857143; color: #333; background-color: #fff; } h1 { font-size: 36px; } h3 { margin-top: 20px; margin-bottom: 10px; font-size: 24px; } h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { font-family: inherit; font-weight: 500; line-height: 1.1; color: inherit; } a { color: #428bca; text-decoration: none; } .menu { display: none; } .img-thumbnail { display: inline-block; max-width: 100%; height: auto; padding: 4px; line-height: 1.42857143; background-color: #fff; border: 1px solid #ddd; border-radius: 4px; } .nav { padding-left: 0; margin-bottom: 0; list-style: none } .nav>li { position: relative; display: block } .nav>li>a { position: relative; display: block; padding: 10px 15px } .nav>li>a:hover, .nav>li>a:focus { text-decoration: none; background-color: #eee } .nav .open>a, .nav .open>a:hover, .nav .open>a:focus { background-color: #eee; border-color: #428bca } .nav-tabs { border-bottom: 1px solid #ddd } .nav-tabs>li { float: left; margin-bottom: -1px } .nav-tabs>li>a { margin-right: 2px; line-height: 1.42857143; border: 1px solid transparent; border-radius: 4px 4px 0 0 } .nav-tabs>li>a:hover { border-color: #eee #eee #ddd } .nav-tabs>li.active>a, .nav-tabs>li.active>a:hover, .nav-tabs>li.active>a:focus { color: #555; cursor: default; background-color: #fff; border: 1px solid #ddd; border-bottom-color: transparent } .nav-pills>li { float: left } .nav-pills>li>a { border-radius: 4px } .nav-pills>li+li { margin-left: 2px } .nav-pills>li.active>a, .nav-pills>li.active>a:hover, .nav-pills>li.active>a:focus { color: #fff; background-color: #428bca } .nav-stacked>li { float: none } .nav-stacked>li+li { margin-top: 2px; margin-left: 0 } .container { padding-left: 15px; padding-right: 15px; } .knobs { display: flex; flex-direction: column; align-items: center; } @media (min-width: 768px) { .content { display: grid; grid-gap: 15px; grid-template-columns: 1fr 3fr; } .content .data { min-width: 0; } .knobs { display: flex; flex-direction: row; flex-wrap: wrap; justify-content: center; align-items: center; } .knob { min-width: 0; padding-left: 15px; padding-right: 15px; flex: 1; } } @media (min-width: 1200px) { .container { width: 1170px; margin-left: auto; margin-right: auto; } } .banner { background: #2a99d1; color: #fff; padding: 20px 0; } .banner .container { min-height: 120px; display: flex; } .banner h1 { margin: 35px 0 5px; } .banner p.lead { font-size: 16px; margin-top: 0; margin-bottom: 0; } .banner .lead-icon { display: flex; align-items: center; } .banner .lead-icon span { margin-left: 0.5rem; } .banner .user-photo img { min-width: 100px; max-height: 100%; } .banner .user-info { margin-left: 1rem; } .content { clear: both; padding-top: 15px; } .content .nav-pills { margin-top: 5px; } .sidebar-container { border-radius: 30px; } .sidebar-container .sidebar-navs { margin-top: 20px; } .sidebar-container .img-thumbnail { width: 100%; height: 100%; margin-left: -10px; } .main-container .content-wrapper { padding: 1px 20px 18px 20px; border-bottom: 1px solid #d7d7d7; margin-top: 25px; } .main-container .content-wrapper .main-content { margin-top: 10px; } .knob-title { text-align: center; } .navbar-collapse { background: #2a99d1; padding-left: 40px; padding-right: 50px; } .navbar-nav>li>a, .navbar a.navbar-brand { color: #fbfbfb; } .tabs-left>.nav-pills .active>a, .tabs-left>.nav-pills .active>a:hover, .tabs-left>.nav-pills .active>a:focus { background-color: #2a99d1; border-color: #2a99d1; } #chart-01 .c3-chart { height: 175px; } ================================================ FILE: examples/customer-facing/customer-facing.js ================================================ const client1 = new Keen({ projectId: '5368fa5436bf5a5623000000', readKey: '3f324dcb5636316d6865ab0ebbbbc725224c7f8f3e8899c7733439965d6d4a2c7f13bf7765458790bd50ec76b4361687f51cf626314585dc246bb51aeb455c0a1dd6ce77a993d9c953c5fc554d1d3530ca5d17bdc6d1333ef3d8146a990c79435bb2c7d936f259a22647a75407921056' }); const client2 = new Keen({ projectId: '53eab6e12481962467000000', readKey: 'd1b97982ce67ad4b411af30e53dd75be6cf610213c35f3bd3dd2ef62eaeac14632164890413e2cc2df2e489da88e87430af43628b0c9e0b2870d0a70580d5f5fe8d9ba2a6d56f9448a3b6f62a5e6cdd1be435c227253fbe3fab27beb0d14f91b710d9a6e657ecf47775281abc17ec455' }); Keen.ready(function () { // ---------------------------------------- // Impressions timeline // ---------------------------------------- const impressions_timeline = new KeenDataviz({ container: '#chart-01', type: 'area', stacked: true }); client1 .query('count', { event_collection: 'impressions', interval: 'hourly', timeframe: { start: '2014-05-04T00:00:00.000Z', end: '2014-05-05T00:00:00.000Z' } }) .then(res => { impressions_timeline.data(res).render(); }) .catch(err => { impressions_timeline.message(err.message); }); // ---------------------------------------- // Heart Rate // ---------------------------------------- $('.heart').knob({ angleArc: 250, angleOffset: -125, readOnly: true, min: 50, max: 80, fgColor: '#00bbde', width: '100%' }); client2 .query('median', { event_collection: 'user_action', target_property: 'bio_sensors.heart_rate', filters: [{ property_name: 'user.id', operator: 'eq', property_value: '02846154-1520-5F67-A892-6C0F21408069' }] }) .then(res => { $('.heart').val(res.result).trigger('change'); }) .catch(err => { alert('Error fetching user heart rate metric'); }); // ---------------------------------------- // Temperature // ---------------------------------------- $('.temp').knob({ angleArc: 250, angleOffset: -125, readOnly: true, min: 90, max: 105, fgColor: '#fe6672', width: '100%' }); client2 .query('median', { event_collection: 'user_action', target_property: 'enviro_sensors.temp', filters: [{ property_name: 'user.id', operator: 'eq', property_value: '02846154-1520-5F67-A892-6C0F21408069' }] }) .then(res => { $('.temp').val(98).trigger('change'); }) .catch(() => { alert('Error fetching user temperature metric'); }); // ---------------------------------------- // Battery // ---------------------------------------- $('.battery').knob({ angleArc: 250, angleOffset: -125, readOnly: true, fgColor: '#00cfbb', width: '100%' }); client2 .query('median', { event_collection: 'user_action', target_property: 'battery_level', filters: [{ property_name: 'user.id', operator: 'eq', property_value: '02846154-1520-5F67-A892-6C0F21408069' }] }) .then(res => { $('.battery').val((res.result) * 100).trigger('change'); }) .catch(() => { alert('Error fetching user battery metric'); }); }); ================================================ FILE: examples/customer-facing/index.html ================================================ Dashboard User Facing, by Keen IO

Heart Rate Throughout the Day


Resting Heart Rate

Body Temp

Battery Level

================================================ FILE: examples/geo-explorer/geo-explorer.css ================================================ body.application { padding: 0; margin: 0; } .row { margin: 0; } #app-wrapper { height: 100%; position: aboslute; width: 100%; } #app-toolbar { background: rgba(0, 0, 0, .75); box-shadow: 0 1px 5px rgba(0, 0, 0, .1); color: #fff; position: fixed; top: 0px; width: 100%; z-index: 1; } #app-toolbar .btn-primary { background-color: #00afd7; border: medium none; } #app-toolbar .btn-primary:focus, #app-toolbar .btn-primary:hover { background-color: #0098BB; } #app-maparea { border-right: 1px solid #d7d7d7; position: fixed; top: 0px; width: 67%; z-index: 0; } #app-sidebar { padding: 10px; position: absolute; right: 0; top: 95px; width: 33%; } #app-sidebar .chart-wrapper { border: medium none; box-shadow: 0 1px 3px rgba(0, 0, 0, .1); } .app-sidebar-chart { height: 300px; } .tools { min-height: 75px; padding: 10px 15px; display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; grid-column-gap: 15px; } .tool input, .tool select { background: rgba(0, 0, 0, .5); border: medium none; box-shadow: none; color: #e7e7e7; float: left; margin: 0 1%; } .radius input, .radius select { width: 48%; } .tool input, .tool label, .tool h5 { text-shadow: 0 0 1px #000; } ================================================ FILE: examples/geo-explorer/geo-explorer.js ================================================ !(function (undefined) { let DEFAULTS, GEO, client, circle, marker, map, activeMapData, keenMapData; // DOM Elements let appWrapperNode, appMapAreaNode, latNode, lngNode, radiusValueNode, radiusUnitsNode, timeframeStartNode, timeframeEndNode, refreshButton; client = new KeenAnalysis({ projectId: '53eab6e12481962467000000', readKey: 'd1b97982ce67ad4b411af30e53dd75be6cf610213c35f3bd3dd2ef62eaeac14632164890413e2cc2df2e489da88e87430af43628b0c9e0b2870d0a70580d5f5fe8d9ba2a6d56f9448a3b6f62a5e6cdd1be435c227253fbe3fab27beb0d14f91b710d9a6e657ecf47775281abc17ec455' }); DEFAULTS = { timeframe: { start: '2014-08-01', end: '2014-08-15' }, lat: 37.77350, lng: -122.41104, radius: 10, units: 'km', zoom: 12 }; GEO = { meters: 0, miles: 0, lat: DEFAULTS.lat, lng: DEFAULTS.lng, center: [DEFAULTS.lat, DEFAULTS.lng], radius: DEFAULTS.radius, units: DEFAULTS.units, zoom: DEFAULTS.zoom }; Keen.ready(function () { // DOM is ready appWrapperNode = document.getElementById('app-wrapper'); appMapAreaNode = document.getElementById('app-maparea'); latNode = document.getElementById('coordinates-lat'); lngNode = document.getElementById('coordinates-lng'); radiusValueNode = document.getElementById('radius-value'); radiusUnitsNode = document.getElementById('radius-suffix'); timeframeStartNode = document.getElementById('timeframe-start'); timeframeEndNode = document.getElementById('timeframe-end'); refreshButton = document.getElementById('refresh'); adjust(); init(); }); function init() { const params = getParams(); // Get params if (params.center) { GEO.center = params.center.split(','); } if (params.latitude && params.longitude) { GEO.lat = parseFloat(params.latitude); GEO.lng = parseFloat(params.longitude); } if (params.units) { GEO.units = params.units; radiusUnitsNode.value = GEO.units; } if (params.meters) { GEO.meters = parseFloat(params.meters); if (GEO.units === 'km') radiusValueNode.value = parseInt(GEO.meters) / 1000; } if (params.miles) { GEO.miles = parseFloat(params.miles); if (GEO.units === 'mi') radiusValueNode.value = GEO.miles; } if (params.zoom) { GEO.zoom = parseFloat(params.zoom); } // Prefill input fields latNode.value = GEO.lat; lngNode.value = GEO.lng; timeframeStartNode.value = DEFAULTS.timeframe['start']; timeframeEndNode.value = DEFAULTS.timeframe['end']; // Create map instance L.mapbox.accessToken = 'pk.eyJ1Ijoia2Vlbi1pbyIsImEiOiIza0xnNXBZIn0.PgzKlxBmYkOq6jBGErpqOg'; map = L.mapbox.map('app-maparea', 'keen-io.kae20cg0', { attributionControl: true, center: GEO.center, zoom: GEO.zoom }); map.on('dragend', updateQuery); map.on('zoomend', function (e) { GEO.zoom = e.target._zoom; updateQuery(); }); // Contains query result markers activeMapData = L.layerGroup().addTo(map); // Create primary marker marker = L.marker(new L.LatLng(GEO.lat, GEO.lng), { icon: L.mapbox.marker.icon({ 'marker-color': 'ff8888' }), draggable: true, zIndexOffset: 9999 }); marker.addTo(map); marker.on('dragend', function (e) { const newCoords = e.target.getLatLng(); const newLat = newCoords.lat.toPrecision(8); const newLng = newCoords.lng.toPrecision(8); circle.setLatLng({ lat: newLat, lng: newLng }); latNode.value = GEO.lat = newLat; lngNode.value = GEO.lng = newLng; refresh(); }); circle = L.circle([GEO.lat, GEO.lng], 1000); circle.addTo(map); setGeoSelection(); map.attributionControl.addAttribution('Custom Analytics by Keen IO'); keenMapData = L.layerGroup().addTo(map); // Listen for input changes radiusValueNode.onchange = setGeoSelection; radiusUnitsNode.onchange = setGeoSelection; // Listen for refresh events refreshButton.onclick = refresh; // Listen for resize events window.onresize = adjust; // Go! refresh(); } function getParams(selector) { const params = Keen.utils.parseParams(document.location.search); return (selector) ? params[selector] : params; } function updateQuery() { let params, str; setGeoSelection(); params = { start: timeframeStartNode.value, end: timeframeEndNode.value, latitude: latNode.value, longitude: lngNode.value, miles: GEO.miles, meters: GEO.meters, units: GEO.units, zoom: GEO.zoom, center: GEO.center }; str = '?'; Keen.utils.each(params, function (value, key) { str += key + '=' + value + '&'; }); history.pushState(null, null, str); } function setGeoSelection() { GEO.radius = radiusValueNode.value || 10; GEO.units = radiusUnitsNode.value || 'km'; GEO.meters = GEO.radius * ((GEO.units === 'mi') ? 1609.34 : 1000); GEO.miles = GEO.meters / 1609.34; GEO.center[0] = map.getCenter().lat; GEO.center[1] = map.getCenter().lng; GEO.lat = latNode.value; GEO.lng = lngNode.value; circle.setRadius(GEO.meters); } function refresh() { updateQuery(); draw(); } function adjust() { appWrapperNode.style.height = window.innerHeight + 'px'; appMapAreaNode.style.height = window.innerHeight + 'px'; } // Keen.utils.each(queries, function(q){}); function draw() { const options = { start: timeframeStartNode.value, end: timeframeEndNode.value, latitude: latNode.value, longitude: lngNode.value, radius: GEO.miles, zoom: GEO.zoom }; const end = (options['end']) ? new Date(Date.parse(options['end'])) : new Date(); const start = (options['start']) ? new Date(Date.parse(options['start'])) : new Date(end.getFullYear(), end.getMonth(), end.getDate() - 14); const rad = (options['radius']) ? parseFloat(options['radius']) : false; const lat = (options['latitude']) ? parseFloat(options['latitude']) : false; const lng = (options['longitude']) ? parseFloat(options['longitude']) : false; let geoFilter = []; if (lat && lng && rad) { geoFilter.push({ property_name: 'keen.location.coordinates', operator: 'within', property_value: { coordinates: [parseFloat(options['longitude']), parseFloat(options['latitude'])], max_distance_miles: parseFloat(options['radius']) } }); }; const baseParams = { timeframe: { start: start.toISOString(), end: end.toISOString() }, filters: geoFilter }; // Fetch events within geo target const scoped_events = client.query('select_unique', { event_collection: 'user_action', target_property: 'keen.location.coordinates', timeframe: baseParams.timeframe, filters: baseParams.filters }); client.run(scoped_events, (err, res) => { activeMapData.clearLayers(); Keen.utils.each(res.result, coord => { const em = L.marker(new L.LatLng(coord[1], coord[0]), { icon: L.mapbox.marker.icon({ 'marker-color': '#00bbde' }) }).addTo(activeMapData);; }); }); // Sample queries // groupBy not supported for Geo Filters const hearts = client.query('median', { event_collection: 'user_action', interval: 'daily', target_property: 'bio_sensors.heart_rate', timeframe: baseParams.timeframe, filters: baseParams.filters }); const daily_median_heartrate = new KeenDataviz({ container: '#chart-01', title: 'Daily Median Heart Rate', type: 'area', colors: ['#fe6672'], }) client .run(hearts) .then(res => { daily_median_heartrate .data(res) .render() }); const activations = client.query('count', { event_collection: 'activations', interval: 'daily', target_property: 'bio_sensors.heart_rate', timeframe: baseParams.timeframe, filters: baseParams.filters }); const daily_activations = new KeenDataviz({ container: '#chart-02', title: 'Daily Activations', type: 'area', colors: ['#fe6672'], }); client .run(activations) .then(res => { daily_activations .data(res) .render() }); const purchases = client.query('sum', { event_collection: 'purchases', target_property: 'order_price', interval: 'daily', timeframe: baseParams.timeframe, filters: baseParams.filters }); const daily_purchases = new KeenDataviz({ container: '#chart-03', title: 'Daily Purchases', type: 'area', colors: ['#eeb058'], }); client .run(purchases) .then(res => { daily_purchases .data(res) .render() }); } })(); ================================================ FILE: examples/geo-explorer/index.html ================================================ Dashboard Starter UI, by Keen IO
Query Radius
Latitude
Longitude
Start time
End time
Keen IO ♡'s Mapbox


Built with ♥ by Keen IO

================================================ FILE: examples/index.html ================================================ Dashboard Examples, by Keen IO ================================================ FILE: examples/military-surplus-flow/index.html ================================================ Military Surplus Flow



Built with ♥ by Keen IO

================================================ FILE: examples/military-surplus-flow/military-surplus-flow.css ================================================ .column .c3-chart { height: 240px; } #grid-4 .c3-chart { height: 800px; } ================================================ FILE: examples/military-surplus-flow/military-surplus-flow.js ================================================ const client = new Keen({ projectId: '53f3eca97d8cb91b75000000', readKey: 'df6ff0ff414bc286b91e2661db4c691c45b6aea8d2c8cf2393169e9b9ef36a3d77e59c57b540febc8f328bf1f605782d9035c4a7072dc86c4f96ddbcce7dfe0b088ae51dd2ea36ad022290d1f3580e2d1ea202845ae7f79e7db6634ee627a26197dadf7eb2e5a46b16f04a4cae55955e' }); Keen.ready(function () { const chart01 = new KeenDataviz({ container: '#grid-1', title: 'Total Acquisitions, by State', notes: 'Notes about this chart', type: 'area', stacked: true }); const chart02 = new KeenDataviz({ container: '#grid-2', title: 'Total Acquisition Cost and by State', notes: 'Notes about this chart', type: 'area', stacked: true }) const chart03 = new KeenDataviz({ container: '#grid-3', title: 'Total Acquisition Cost in Missouri', notes: 'Notes about this chart', type: 'area', stacked: true }) const chart04 = new KeenDataviz({ container: '#grid-4', title: 'Quantity Purchased by State', type: 'horizontal-bar' }); client .query('count', { event_collection: 'purchases', timeframe: { start: '2012-01-01', end: '2014-05-01' }, interval: 'monthly' }) .then(res => { chart01.data(res).render(); }) .catch(err => { chart01.message(err.message); }); client .query('sum', { event_collection: 'purchases', target_property: 'Acquisition Cost', timeframe: { start: '2012-01-01', end: '2014-05-01' }, interval: 'monthly' }) .then(res => { chart02.data(res).render(); }) .catch(err => { chart02.message(err.message); }); client .query('sum', { event_collection: 'purchases', target_property: 'Acquisition Cost', filters: [{ property_name: 'State', operator: 'eq', property_value: 'MO' }], timeframe: { start: '2012-01-01', end: '2014-05-01' }, interval: 'monthly' }) .then(res => { chart03.data(res).render(); }) .catch(err => { chart03.message(err.message); }); client .query('sum', { event_collection: 'purchases', timeframe: { start: '2012-01-01', end: '2014-05-01' }, target_property: 'Quantity', group_by: 'State' }) .then(res => { chart04.data(res).render(); }) .catch(err => { chart04.message(err.message); }); }); ================================================ FILE: examples/sfmta-parking/README.md ================================================ Geo Mashup ========== ================================================ FILE: examples/sfmta-parking/index.html ================================================ Parking Data Dashboard, by Keen IO
================================================ FILE: examples/sfmta-parking/sfmta-demo.css ================================================ #mapbox-panel { position: relative; } #mapbox-panel .chart-title { border: medium none; } #mapbox-panel .chart-stage { height: 600px; } #map { bottom: 0; height: 100%; left: 0; position: absolute; right: 0px; top: 0; width: 100%; } .sfmta-charts .chart { margin-bottom: 1rem; } .sfmta-charts .c3-chart { height: 250px; } .sfmta-charts .keen-dataviz-rendering { padding: 10px; } @media (min-width: 768px) { .sfmta-map { position: -webkit-sticky; position: sticky; top: 50px; } } ================================================ FILE: examples/sfmta-parking/sfmta-demo.js ================================================ const client = new KeenAnalysis({ projectId: '5368fa5436bf5a5623000000', readKey: '3f324dcb5636316d6865ab0ebbbbc725224c7f8f3e8899c7733439965d6d4a2c7f13bf7765458790bd50ec76b4361687f51cf626314585dc246bb51aeb455c0a1dd6ce77a993d9c953c5fc554d1d3530ca5d17bdc6d1333ef3d8146a990c79435bb2c7d936f259a22647a75407921056' }); const geoProject = new KeenAnalysis({ projectId: '53eab6e12481962467000000', readKey: 'd1b97982ce67ad4b411af30e53dd75be6cf610213c35f3bd3dd2ef62eaeac14632164890413e2cc2df2e489da88e87430af43628b0c9e0b2870d0a70580d5f5fe8d9ba2a6d56f9448a3b6f62a5e6cdd1be435c227253fbe3fab27beb0d14f91b710d9a6e657ecf47775281abc17ec455' }); $(function () { $(window).resize(adjust); function adjust() { var offset = $(window).height() - 50; $('#mapbox-panel').height(offset); } adjust(); }); Keen.ready(function () { // ---------------------------------------- // Mapbox Demo // ---------------------------------------- const DEFAULTS = { coordinates: { lat: 37.77350, lng: -122.41104 }, zoom: 15 }; let map, markerStart = DEFAULTS.coordinates, activeMapData; L.mapbox.accessToken = 'pk.eyJ1Ijoia2Vlbi1pbyIsImEiOiIza0xnNXBZIn0.PgzKlxBmYkOq6jBGErpqOg'; map = L.mapbox.map('map', 'keen-io.kae20cg0', { attributionControl: true, center: [markerStart.lat, markerStart.lng], zoom: DEFAULTS.zoom }); let center = map.getCenter(); let zoom = map.getZoom(); z = zoom - 1; if (zoom = 0) { radius = false; } else { radius = 10000 / Math.pow(2, z); } let geoFilter = []; geoFilter.push({ property_name: 'user.age', operator: 'lt', property_value: '50' }); geoFilter.push({ property_name: 'keen.location.coordinates', operator: 'within', property_value: { coordinates: [center.lng, center.lat], max_distance_miles: radius } }); let geoFilter2 = []; geoFilter2.push({ property_name: 'user.age', operator: 'gt', property_value: '50' }); geoFilter2.push({ property_name: 'keen.location.coordinates', operator: 'within', property_value: { coordinates: [center.lng, center.lat], max_distance_miles: radius } }); activeMapData = L.layerGroup().addTo(map); map.attributionControl.addAttribution('Custom Analytics by Keen IO'); const scoped_events = geoProject.query('select_unique', { event_collection: 'status_update', target_property: 'keen.location.coordinates', filters: geoFilter }); geoProject.run(scoped_events, (err, res) => { Keen.utils.each(res.result, coord => { var em = L.marker(new L.LatLng(coord[1], coord[0]), { icon: L.mapbox.marker.icon({ 'marker-color': '#00bbde' }) }).addTo(activeMapData); }); }); const scoped_events_2 = geoProject.query('select_unique', { event_collection: 'status_update', target_property: 'keen.location.coordinates', filters: geoFilter2 }); geoProject.run(scoped_events_2, (err, res) => { Keen.utils.each(res.result, coord => { const em = L.marker(new L.LatLng(coord[1], coord[0]), { icon: L.mapbox.marker.icon({ 'marker-color': '#fe6672' }) }).addTo(activeMapData); }); }); map.on('zoomend', function (e) { resize(); }); map.on('dragend', function (e) { ; resize(); }); var resize = function () { activeMapData.clearLayers(); center = map.getCenter(), zoom = map.getZoom(); z = zoom - 1; if (zoom = 0) { radius = false; } else { radius = 10000 / Math.pow(2, z); } geoFilter.pop(); geoFilter.push({ property_name: 'keen.location.coordinates', operator: 'within', property_value: { coordinates: [center.lng, center.lat], max_distance_miles: radius } }); geoFilter2.pop(); geoFilter2.push({ property_name: 'keen.location.coordinates', operator: 'within', property_value: { coordinates: [center.lng, center.lat], max_distance_miles: radius } }); const scoped_events_3 = geoProject.query('select_unique', { event_collection: 'status_update', target_property: 'keen.location.coordinates', filters: geoFilter }); geoProject.run(scoped_events_3, (err, res) => { Keen.utils.each(res.result, coord => { const em = L.marker(new L.LatLng(coord[1], coord[0]), { icon: L.mapbox.marker.icon({ 'marker-color': '#00bbde' }) }).addTo(activeMapData); }); }); const scoped_events_4 = geoProject.query('select_unique', { event_collection: 'status_update', target_property: 'keen.location.coordinates', filters: geoFilter2 }); geoProject.run(scoped_events_4, (err, res) => { Keen.utils.each(res.result, coord => { const em = L.marker(new L.LatLng(coord[1], coord[0]), { icon: L.mapbox.marker.icon({ 'marker-color': '#fe6672' }) }).addTo(activeMapData); }); }); }; // ---------------------------------------- // Violations line chart // ---------------------------------------- const chart01 = new KeenDataviz({ container: '#chart-01', title: 'Violations: Hourly Average', type: 'line' }); client .query('count', { event_collection: 'pageviews', interval: 'hourly', group_by: 'user.device_info.browser.family', timeframe: { start: '2014-05-04T00:00:00.000Z', end: '2014-05-05T00:00:00.000Z' } }) .then(res => { chart01.data(res).render(); }) .catch(err => { chart01.message(err.message); }); // ---------------------------------------- // Hourly Actions // ---------------------------------------- const chart02 = new KeenDataviz({ container: '#chart-03', title: 'Hourly Actions', type: 'bar', stacked: true }) client .query('count', { event_collection: 'impressions', group_by: 'ad.advertiser', interval: 'hourly', timeframe: { start: '2014-05-04T00:00:00.000Z', end: '2014-05-05T00:00:00.000Z' } }) .then(res => { chart02.data(res).render(); }) .catch(err => { chart02.message(err.message); }); // ---------------------------------------- // Violations by Officer // ---------------------------------------- const chart03 = new KeenDataviz({ container: '#chart-05', title: 'Actions by Officer', type: 'bar', stacked: true }) client .query('count', { event_collection: 'pageviews', interval: 'hourly', timeframe: { start: '2014-04-30T12:00:00.000Z', end: '2014-05-05T00:00:00.000Z' } }) .then(res => { chart03.data(res).render(); }) .catch(err => { chart03.message(err.message); }); }); ================================================ FILE: examples/starter-kit/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Keen IO Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: examples/starter-kit/README.md ================================================ Dashboard Starter Kit ===================== ================================================ FILE: examples/starter-kit/index.html ================================================ Dashboard Starter UI, by Keen IO

Built with ♥ by Keen IO

================================================ FILE: examples/starter-kit/starter-kit.css ================================================ .starter-chart { min-width: 0; height: 280px; margin-bottom: 1rem; } @media (min-width: 768px) { .starter-chart { margin-bottom: 0; } .hero.starter-chart { grid-column: 1 / span 2; } } ================================================ FILE: examples/starter-kit/starter-kit.js ================================================ const client = new Keen({ projectId: '5368fa5436bf5a5623000000', readKey: '3f324dcb5636316d6865ab0ebbbbc725224c7f8f3e8899c7733439965d6d4a2c7f13bf7765458790bd50ec76b4361687f51cf626314585dc246bb51aeb455c0a1dd6ce77a993d9c953c5fc554d1d3530ca5d17bdc6d1333ef3d8146a990c79435bb2c7d936f259a22647a75407921056' }); Keen.ready(function () { // Pageviews by browser const pageviews_timeline = new KeenDataviz({ container: '#chart-01', title: 'Pageviews by browser', type: 'area', stacked: true, sortGroups: 'desc' }); client .query('count', { event_collection: 'pageviews', interval: 'hourly', group_by: 'user.device_info.browser.family', timeframe: { start: '2014-05-04T00:00:00.000Z', end: '2014-05-05T00:00:00.000Z' } }) .then(res => { pageviews_timeline .data(res) .render(); }) .catch(err => { pageviews_timeline.message(err.message) }); // Pageviews by browser (pie) const pageviews_pie = new KeenDataviz({ container: '#chart-02', type: 'pie', title: 'Pageviews by browser', sortGroups: 'desc' }); client .query({ savedQueryName: 'chart-02', }) .then(function(results){ pageviews_pie .render(results); }) .catch(function(error){ pageviews_pie .message(error.message); }); // Impressions timeline const impressions_timeline = new KeenDataviz({ container: '#chart-03', title: 'Impressions by advertiser', type: 'bar', stacked: true, sortGroups: 'desc' }); client .query('count', { event_collection: 'impressions', group_by: 'ad.advertiser', interval: 'hourly', timeframe: { start: '2014-05-04T00:00:00.000Z', end: '2014-05-05T00:00:00.000Z' } }) .then(res => { impressions_timeline .data(res) .render(); }) .catch(err => { impressions_timeline.message(err.message) }); // Impressions by device const impressions_by_device = new KeenDataviz({ container: '#chart-04', title: 'Impressions by device', type: 'bar', stacked: true, sortGroups: 'desc' }); client .query('count', { event_collection: 'impressions', group_by: 'user.device_info.device.family', interval: 'hourly', timeframe: { start: '2014-05-04T00:00:00.000Z', end: '2014-05-05T00:00:00.000Z' } }) .then(res => { impressions_by_device .data(res) .render(); }) .catch(err => { impressions_by_device.message(err.message) }); // Impressions by country const impressions_by_country = new KeenDataviz({ container: '#chart-05', title: 'Impressions by country', type: 'bar', stacked: true, sortGroups: 'desc' }); client .query('count', { event_collection: 'impressions', group_by: 'user.geo_info.country', interval: 'hourly', timeframe: { start: '2014-05-04T00:00:00.000Z', end: '2014-05-05T00:00:00.000Z' } }) .then(res => { impressions_by_country .data(res) .render(); }) .catch(err => { impressions_by_country.message(err.message) }); }); ================================================ FILE: index.html ================================================ Dashboards by Keen IO

Dashboards by Keen IO

Responsive dashboard templates for Bootstrap

Source Code on Github
================================================ FILE: layouts/README.md ================================================ Dashboard Layouts ================= Building something from scratch? These vanilla dashboard layouts will get you up and running in no time. **Note:** we've used [holder.js](https://github.com/imsky/holder) for labeled cell placeholders, which can be safely removed as you make these layouts your own. ================================================ FILE: layouts/hero-sidebar/index.html ================================================ Layouts » Hero Sidebar
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart

Built with ♥ by Keen IO

================================================ FILE: layouts/hero-thirds/index.html ================================================ Layouts » Hero-Thirds
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart

Built with ♥ by Keen IO

================================================ FILE: layouts/index.html ================================================ Dashboard Layouts, by Keen IO ================================================ FILE: layouts/quarter-grid/index.html ================================================ Layouts » Quarter Grid
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart

Built with ♥ by Keen IO

================================================ FILE: layouts/split-centered/index.html ================================================ Layouts » Split-Centered
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart

Built with ♥ by Keen IO

================================================ FILE: layouts/split-columns/index.html ================================================ Layouts » Split-Columns
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart

Built with ♥ by Keen IO

================================================ FILE: layouts/split-rows/index.html ================================================ Layouts » Split-Rows
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart

Built with ♥ by Keen IO

================================================ FILE: layouts/thirds-grid/index.html ================================================ Layouts » Thirds Grid
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart
Cell Title
Notes about this chart

Built with ♥ by Keen IO

================================================ FILE: layouts/two-and-one/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Keen IO Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: layouts/two-and-one/README.md ================================================ Dashboard Starter Kit ===================== ================================================ FILE: layouts/two-and-one/index.html ================================================ Layouts » Two-and-One
Pageviews by browser (past 24 hours)
This is a sample text region to describe this chart.
Pageviews by browser (past 5 days)
Notes go down here
Impressions by advertiser
Notes go down here

Built with ♥ by Keen IO

================================================ FILE: package.json ================================================ { "name": "keen-dashboards", "version": "3.0.1", "description": "Responsive dashboard templates", "main": "./dist/keen-dashboards.css", "repository": { "type": "git", "url": "git+https://github.com/keen/dashboards.git" }, "keywords": [ "keen io", "dashboards", "dataviz", "visualization" ], "author": "Dustin Larimer ", "contributors": [ "Dustin Larimer ", "Taylor Barnett ", "Artur Pryka (https://github.com/apryka)" ], "license": "MIT", "bugs": { "url": "https://github.com/keen/dashboards/issues" }, "homepage": "https://github.com/keen/dashboards#readme" }