Showing preview only (249K chars total). Download the full file or copy to clipboard to get everything.
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:

Add charts to each `chart-stage` HTML element:
```html
<div class="grid-hero">
<div class="hero chart-wrapper">
<div class="chart-title">
Chart Title
</div>
<div class="chart-stage">
<div id="grid-1-1">
<!-- chart goes here! -->
</div>
</div>
<div class="chart-notes">
Notes about this chart (optional)
</div>
</div>
</div>
```
And voilà!

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 ```</body>```.
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
<div class="chart-stage"> <!-- This is where you need to put the id property in! -->
<img data-src="holder.js/100%x650/white">
</div>
```
Now we're going to change those lines so that it looks like this:
```html
<div class="chart-stage" id="chart-01"> <!-- This is where you need to put the id property in! -->
<!-- Get rid of that img tag! -->
</div>
```
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 <dustin@keen.io>",
"Sean Dokko <dokko1230@gmail.com>",
"Ritchie Benevedes <ritchieleeann@gmail.com>"
],
"license": "MIT",
"authors": [
"Dustin Larimer <dustin@keen.io>",
"Taylor Barnett <taylor@keen.io>"
],
"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
================================================
<script>
const client = new Keen({
projectId: "5368fa5436bf5a5623000000",
readKey: "3f324dcb5636316d6865ab0ebbbbc725224c7f8f3e8899c7733439965d6d4a2c7f13bf7765458790bd50ec76b4361687f51cf626314585dc246bb51aeb455c0a1dd6ce77a993d9c953c5fc554d1d3530ca5d17bdc6d1333ef3d8146a990c79435bb2c7d936f259a22647a75407921056"
});
Keen.ready(function () {
// ----------------------------------------
// Sample one
// ----------------------------------------
const pageviews_timeline = client.query('count', {
eventCollection: "pageviews",
interval: "hourly",
groupBy: "user.device_info.browser.family",
timeframe: {
start: "2014-05-04T00:00:00.000Z",
end: "2014-05-05T00:00:00.000Z"
}
});
client.run([pageviews_timeline])
.then(res => {
const chart = new KeenDataviz({
container: '#chart-01',
type: 'area'
})
.data(res)
.render();
})
// ----------------------------------------
// End sample one
// ----------------------------------------
// ----------------------------------------
// Sample two
// ----------------------------------------
const pageviews_static = client.query('count', {
eventCollection: "pageviews",
groupBy: "user.device_info.browser.family",
timeframe: {
start: "2014-05-01T00:00:00.000Z",
end: "2014-05-05T00:00:00.000Z"
}
});
client.run([pageviews_static])
.then(res => {
const chart = new KeenDataviz({
container: '#chart-02',
type: 'pie'
})
.data(res)
.render();
})
// ----------------------------------------
// End sample two
// ----------------------------------------
// ----------------------------------------
// Sample three
// ----------------------------------------
const impressions_timeline = client.query('count', {
eventCollection: "impressions",
groupBy: "ad.advertiser",
interval: "hourly",
timeframe: {
start: "2014-05-04T00:00:00.000Z",
end: "2014-05-05T00:00:00.000Z"
}
});
client.run([impressions_timeline])
.then(res => {
const chart = new KeenDataviz({
container: '#chart-03',
type: 'bar',
stacked: true
})
.data(res)
.render();
});
// ----------------------------------------
// End sample three
// ----------------------------------------
// ----------------------------------------
// Sample four
// ----------------------------------------
const impressions_timeline_by_device = client.query('count', {
eventCollection: "impressions",
groupBy: "user.device_info.device.family",
interval: "hourly",
timeframe: {
start: "2014-05-04T00:00:00.000Z",
end: "2014-05-05T00:00:00.000Z"
}
});
client.run([impressions_timeline_by_device])
.then(res => {
const chart = new KeenDataviz({
container: '#chart-04',
type: 'bar',
stacked: true,
bar: {
width: 100
}
})
.data(res)
.render();
});
// ----------------------------------------
// End sample four
// ----------------------------------------
// ----------------------------------------
// Sample five
// ----------------------------------------
const impressions_timeline_by_country = client.query('count', {
eventCollection: "impressions",
groupBy: "user.geo_info.country",
interval: "hourly",
timeframe: {
start: "2014-05-04T00:00:00.000Z",
end: "2014-05-05T00:00:00.000Z"
}
});
client.run([impressions_timeline_by_country])
.then(res => {
const chart = new KeenDataviz({
container: '#chart-05',
type: 'bar',
stacked: true,
bar: {
width: {
ratio: 0.85
}
}
})
.data(res)
.render();
});
// ----------------------------------------
// End sample five
// ----------------------------------------
});
</script>
================================================
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
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>2048</title>
<link rel="shortcut icon" href="favicon.ico">
<link rel="apple-touch-icon" href="meta/apple-touch-icon.png">
<link rel="apple-touch-startup-image" href="meta/apple-touch-startup-image-640x1096.png"
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)"> <!-- iPhone 5+ -->
<link rel="apple-touch-startup-image" href="meta/apple-touch-startup-image-640x920.png"
media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 2)">
<!-- iPhone, retina -->
<link href="styles/game/main.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" type="text/css" href="../../assets/css/keen-dashboards.css" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="viewport"
content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0, maximum-scale=1, user-scalable=no, minimal-ui">
</head>
<body>
<div class="container">
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="../">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="76.39 132.243 15.35 22" width="20" height="20" fill="#777">
<defs>
<path id="a"
d="M88.31 136.63c.29-.28.43-.63.43-1.05 0-.41-.14-.76-.43-1.04-.08-.09-.78-.78-.86-.87-.29-.28-.64-.43-1.05-.43-.41 0-.76.15-1.05.43-.75.75-6.78 6.77-7.53 7.53-.29.28-.43.63-.43 1.04 0 .42.14.76.43 1.05.75.75 6.78 6.77 7.53 7.53.29.28.64.42 1.05.42.41 0 .76-.14 1.05-.42.08-.09.78-.78.86-.87.29-.29.43-.63.43-1.04 0-.41-.14-.76-.43-1.05-.37-.38-2.24-2.25-5.61-5.61 3.37-3.37 5.24-5.24 5.61-5.62z" />
</defs>
<use xlink:href="#a" />
<use xlink:href="#a" />
</svg>
</a>
<a class="navbar-brand" href="./">Keen 2048</a>
</div>
<div class="navbar-collapse">
<ul class="navbar-nav nav main-nav">
<li><a href="https://keen.io">Home</a></li>
<li><a href="https://keen.io/team">Team</a></li>
<li><a href="https://github.com/keenlabs/dashboards/tree/gh-pages/layouts/hero-sidebar">Source</a>
</li>
<li><a href="https://groups.google.com/forum/#!forum/keen-io-devs">Community</a></li>
<li><a href="http://stackoverflow.com/questions/tagged/keen-io?sort=newest&pageSize=15">Technical
Support</a></li>
</ul>
</div>
</div>
</div>
<div class="container-2048">
<div class="header">
<div class="heading">
<h1 class="title">Keen 2048</h1>
<div class="scores-container">
<div class="score-container">0</div>
<div class="best-container">0</div>
</div>
</div>
<div class="above-game">
<p class="game-intro">Join the numbers and get to the <strong>2048 tile!</strong></p>
<a class="restart-button">New Game</a>
</div>
</div>
<table class="main-container">
<tr>
<td width="50%">
<div class="game-container">
<div class="game-message">
<p></p>
<div class="lower">
<a class="keep-playing-button">Keep going</a>
<a class="retry-button">Try again</a>
</div>
</div>
<div class="grid-container">
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
</div>
<div class="tile-container"></div>
</div>
</td>
<td width="50%">
<div id="moves-container" style="padding-left: 20px;"></div>
<br><br>
<div id="stats-container" style="padding-left: 20px;"></div>
</td>
</tr>
</table>
<p class="game-explanation">
<strong class="important">How to play:</strong> Use your <strong>arrow keys</strong> to move the tiles. When two
tiles with the same number touch, they <strong>merge into one!</strong>
</p>
<hr>
<p>
<strong class="important">Note:</strong> This site is the official version of 2048. You can play it on your phone
via <a href="http://git.io/2048">http://git.io/2048.</a> All other apps or sites are derivatives or fakes, and
should be used with caution.
</p>
<hr>
<p>
Created by <a href="http://gabrielecirulli.com" target="_blank">Gabriele Cirulli.</a> Based on <a
href="https://itunes.apple.com/us/app/1024!/id823499224" target="_blank">1024 by Veewo Studio</a> and
conceptually similar to <a href="http://asherv.com/threes/" target="_blank">Threes by Asher Vollmer.</a>
</p>
</div>
<!-- keen-tracking@4 -->
<script src="https://cdn.jsdelivr.net/npm/keen-tracking@4" type="text/javascript"></script>
<!-- keen-analysis@3 -->
<script src="https://cdn.jsdelivr.net/npm/keen-analysis@3" type="text/javascript"></script>
<!-- keen-dataviz@3 -->
<link href="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.js" type="text/javascript"></script>
<!-- Demo Assets -->
<script type="text/javascript" src="js/game/bind_polyfill.js"></script>
<script type="text/javascript" src="js/game/classlist_polyfill.js"></script>
<script type="text/javascript" src="js/game/animframe_polyfill.js"></script>
<script type="text/javascript" src="js/game/keyboard_input_manager.js"></script>
<script type="text/javascript" src="js/game/html_actuator.js"></script>
<script type="text/javascript" src="js/game/grid.js"></script>
<script type="text/javascript" src="js/game/tile.js"></script>
<script type="text/javascript" src="js/game/local_storage_manager.js"></script>
<script type="text/javascript" src="js/game/game_manager.js"></script>
<script type="text/javascript" src="js/game/application.js"></script>
<!-- Project Analytics -->
<script type="text/javascript" src="../../assets/js/keen-analytics.js"></script>
<script>
function toggleMenu() {
const toggleBtn = document.querySelector('.navbar-toggle');
toggleBtn.addEventListener('click', (e) => {
let menu;
if (e.currentTarget.dataset.target) {
menu = document.querySelector(e.currentTarget.dataset.target);
}
if (menu) menu.classList.toggle('collapsed');
});
}
window.addEventListener('DOMContentLoaded', toggleMenu);
</script>
</body>
</html>
================================================
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('<a href=\'https://keen.io/\'>Custom Analytics by Keen IO</a>');
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
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />
<title>Connected Devices</title>
<!-- Mapbox Dependencies -->
<link href='https://api.tiles.mapbox.com/mapbox.js/v2.0.0/mapbox.css' rel='stylesheet' />
<script type="text/javascript" src='https://api.tiles.mapbox.com/mapbox.js/v2.0.0/mapbox.js'></script>
<script type="text/javascript"
src='https://api.tiles.mapbox.com/mapbox.js/plugins/leaflet-heat/v0.1.0/leaflet-heat.js'></script>
<!-- Demo Dependencies -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery-Knob/1.2.13/jquery.knob.min.js" type="text/javascript">
</script>
<!-- keen-analysis@3 -->
<script src="https://cdn.jsdelivr.net/npm/keen-analysis@3" type="text/javascript"></script>
<!-- keen-dataviz@3 -->
<link href="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.js" type="text/javascript"></script>
<!-- Dashboard -->
<link rel="stylesheet" type="text/css" href="../../assets/css/keen-dashboards.css" />
<link rel="stylesheet" type="text/css" href="connected-devices.css" />
<script src="connected-devices.js" type="text/javascript"></script>
</head>
<body class="keen-dashboard">
<div class="container">
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="../">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="76.39 132.243 15.35 22" width="20" height="20" fill="#777">
<defs>
<path id="a"
d="M88.31 136.63c.29-.28.43-.63.43-1.05 0-.41-.14-.76-.43-1.04-.08-.09-.78-.78-.86-.87-.29-.28-.64-.43-1.05-.43-.41 0-.76.15-1.05.43-.75.75-6.78 6.77-7.53 7.53-.29.28-.43.63-.43 1.04 0 .42.14.76.43 1.05.75.75 6.78 6.77 7.53 7.53.29.28.64.42 1.05.42.41 0 .76-.14 1.05-.42.08-.09.78-.78.86-.87.29-.29.43-.63.43-1.04 0-.41-.14-.76-.43-1.05-.37-.38-2.24-2.25-5.61-5.61 3.37-3.37 5.24-5.24 5.61-5.62z" />
</defs>
<use xlink:href="#a" />
<use xlink:href="#a" />
</svg>
</a>
<a class="navbar-brand" href="./">Connected Devices</a>
</div>
<div class="navbar-collapse">
<ul class="navbar-nav nav main-nav">
<li><a href="https://keen.io">Home</a></li>
<li><a href="https://keen.io/team">Team</a></li>
<li><a href="https://github.com/keenlabs/dashboards/tree/gh-pages/layouts/hero-sidebar">Source</a>
</li>
<li><a href="https://groups.google.com/forum/#!forum/keen-io-devs">Community</a></li>
<li><a href="http://stackoverflow.com/questions/tagged/keen-io?sort=newest&pageSize=15">Technical
Support</a></li>
</ul>
</div>
</div>
</div>
<div class="container grid grid-simple-col-2">
<div class="chart-wrapper">
<div class="chart-title">
Traffic Stats
</div>
<div class="chart-stage">
<div class="grid grid-traffic">
<div class="tabbable tabs-left">
<ul class="nav nav-tabs">
<li class="active">
<a href="#visitors" id="tab-visitors" data-toggle="tab">Visitors</a></li>
<li>
<a href="#browser" id="tab-browsers" data-toggle="tab">Browser</a></li>
<li>
<a href="#geography" id="tab-geography" data-toggle="tab">Geography</a></li>
</ul>
</div>
<div class="tab-content">
<div class="tab-pane active" id="visitors"></div>
<div class="tab-pane" id="browser"></div>
<div class="tab-pane" id="geography"></div>
</div>
</div>
</div>
</div>
<div class="chart-wrapper">
<div class="chart-title">
Stats
</div>
<div class="chart-stage">
<div class="chart-container">
<div class="chart">
<div class="chart-title knob-title">
New Activations Today
</div>
<div class="chart-stage">
<input type="text" class="users" value="0" />
</div>
</div>
<div class="chart">
<div class="chart-title knob-title">
Device Crashes Today
</div>
<div class="chart-stage">
<input type="text" class="errors" value="0" />
</div>
</div>
</div>
</div>
</div>
<div class="chart-wrapper">
<div class="chart-title">
Customer Engagement
</div>
<div class="chart-stage">
<div id="chart-05"></div>
</div>
<div class="chart-notes">
</div>
</div>
<div id="mapwrap" class="chart-wrapper">
<div class="chart-title">
<div class="btn-group-xs">Active Users
<button type="button" id="7days" class="btn btn-default">7 Days</button>
<button type="button" id="14days" class="btn btn-default">14 Days</button>
<button type="button" id="28days" class="btn btn-default">28 Days</button>
</div>
</div>
<div class="chart-stage">
<div id="map"></div>
</div>
<div class="chart-notes">
</div>
</div>
</div>
<!-- Project Analytics -->
<script type="text/javascript" src="../../assets/js/keen-analytics.js"></script>
<script>
function toggleMenu() {
const toggleBtn = document.querySelector('.navbar-toggle');
toggleBtn.addEventListener('click', (e) => {
let menu;
if (e.currentTarget.dataset.target) {
menu = document.querySelector(e.currentTarget.dataset.target);
}
if (menu) menu.classList.toggle('collapsed');
});
}
window.addEventListener('DOMContentLoaded', toggleMenu);
</script>
</body>
</html>
================================================
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
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />
<title>Dashboard User Facing, by Keen IO</title>
<link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
<!-- Demo Dependencies -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery-Knob/1.2.13/jquery.knob.min.js" type="text/javascript">
</script>
<!-- keen-analysis@3 -->
<script src="https://cdn.jsdelivr.net/npm/keen-analysis@3" type="text/javascript"></script>
<!-- keen-dataviz@3 -->
<link href="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.js" type="text/javascript"></script>
<!-- Dashboard -->
<link rel="stylesheet" type="text/css" href="customer-facing.css" />
<script type="text/javascript" src="customer-facing.js"></script>
</head>
<body>
<nav class="menu">
<ul>
<li><a href="../">Back</a></li>
<li><a href="https://keen.io">Home</a></li>
<li><a href="./">Profile</a></li>
<li><a href="#">Friends</a></li>
<li><a href="#">Settings</a></li>
<li class="logout"><a>Logout</a></li>
</ul>
</nav>
<div class="banner">
<div class="container">
<div class="user-photo">
<img src="demo-user.jpg" alt="" class="img-thumbnail" />
</div>
<div class="user-info">
<h1>Abraham Wald</h1>
<p class="lead lead-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 174.239 174.239" width="20" height="20" fill="#fff">
<path
d="M87.12 174.239C39.082 174.239 0 135.157 0 87.12S39.082 0 87.12 0s87.12 39.082 87.12 87.12-39.083 87.119-87.12 87.119zm0-159.304c-39.802 0-72.185 32.383-72.185 72.185s32.383 72.185 72.185 72.185 72.185-32.383 72.185-72.185-32.384-72.185-72.185-72.185z" />
<path
d="M125.32 94.587h-38.2a7.468 7.468 0 0 1-7.467-7.467V40.794a7.467 7.467 0 1 1 14.934 0v38.859h30.733a7.468 7.468 0 0 1 0 14.934z" />
</svg>
<span>Last Login August 14, 2018/span>
</p>
</div>
</div>
</div>
<div class="container">
<div class="content">
<div class="nav">
<ul class="nav nav-pills nav-stacked" role="tablist">
<li class="active"><a href="#">My Activity</a></li>
<li><a href="#">Friend's Rank</a></li>
<li><a href="#">Community</a></li>
<li><a href="#">Device Info</a></li>
</ul>
</div>
<div class="data">
<h3>Heart Rate Throughout the Day</h3>
<div id="chart-01"></div>
<hr />
<div class="knobs">
<div class="knob">
<h3 class="knob-title">Resting Heart Rate</h3>
<input class="heart" value="0" />
</div>
<div class="knob">
<h3 class="knob-title">Body Temp</h3>
<input class="temp" value="97.3" />
</div>
<div class="knob">
<h3 class="knob-title">Battery Level</h3>
<input class="battery" value="0" />
</div>
</div>
</div>
</div>
</div>
<div class="footer">
<div class="container">
<hr>
<p class="small text-muted">Built with ♥ by <a href="https://keen.io">Keen IO</a></p>
</div>
</div>
<!-- Project Analytics -->
<script type="text/javascript" src="../../assets/js/keen-analytics.js"></script>
</body>
</html>
================================================
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('<a href=\'https://keen.io/\'>Custom Analytics by Keen IO</a>');
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
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Dashboard Starter UI, by Keen IO</title>
<!-- Demo Dependencies -->
<link href='https://api.tiles.mapbox.com/mapbox.js/v2.0.1/mapbox.css' rel='stylesheet' />
<script type="text/javascript" src="https://api.tiles.mapbox.com/mapbox.js/v2.0.1/mapbox.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>
<!-- keen-analysis@3 -->
<script src="https://cdn.jsdelivr.net/npm/keen-analysis@3" type="text/javascript"></script>
<!-- keen-dataviz@3 -->
<link href="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.js" type="text/javascript"></script>
<!-- Dashboard -->
<link rel="stylesheet" type="text/css" href="../../assets/css/keen-dashboards.css" />
<link rel="stylesheet" type="text/css" href="geo-explorer.css" />
<script type="text/javascript" src="geo-explorer.js"></script>
</head>
<body class="keen-dashboard">
<div id="app-wrapper">
<div id="app-toolbar">
<form action="" onsubmit="return false;" method="post">
<div class="tools">
<div class="tool radius">
<h5>Query Radius</h5>
<input type="number" id="radius-value" class="form-control" value="">
<select class="form-control" id="radius-suffix">
<option></option>
<option value="mi">mi</option>
<option value="km">km</option>
</select>
</div>
<div class="tool coordinates">
<h5>Latitude</h5>
<input type="number" step="any" id="coordinates-lat" class="form-control" placeholder="Latitude">
</div>
<div class="tool coordinates">
<h5>Longitude</h5>
<input type="number" step="any" id="coordinates-lng" class="form-control" placeholder="Longitude">
</div>
<div class="tool timeframe">
<h5>Start time</h5>
<input type="date" id="timeframe-start" class="form-control" placeholder="mm/dd/yyyy">
</div>
<div class="tool timeframe">
<h5>End time</h5>
<input type="date" id="timeframe-end" class="form-control" placeholder="mm/dd/yyyy">
</div>
<div class="tool">
<h5>Keen IO ♡'s Mapbox</h5>
<button id="refresh" class="btn btn-primary btn-block">Refresh</button>
</div>
</div>
</form>
</div>
<div id="app-maparea"></div>
<div id="app-sidebar">
<div id="chart-01" class="app-sidebar-chart"></div>
<br>
<div id="chart-02" class="app-sidebar-chart"></div>
<br>
<div id="chart-03" class="app-sidebar-chart"></div>
</div>
</div>
<p class="small text-muted">Built with ♥ by <a href="https://keen.io">Keen IO</a></p>
<!-- Project Analytics -->
<script type="text/javascript" src="../../assets/js/keen-analytics.js"></script>
</body>
</html>
================================================
FILE: examples/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Dashboard Examples, by Keen IO</title>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />
<link href="https://cloud.typography.com/737368/747986/css/fonts.css" rel="stylesheet" type="text/css">
<link rel="stylesheet" type="text/css" href="../assets/css/keen-static.css" />
<link rel="stylesheet" type="text/css" href="../assets/css/keen-dashboards.css" />
</head>
<body>
<div class="masthead">
<div class="container">
<h1><a class="return-link" href="../">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="76.39 132.243 15.35 22" width="20" height="20" fill="#fff">
<defs>
<path id="a"
d="M88.31 136.63c.29-.28.43-.63.43-1.05 0-.41-.14-.76-.43-1.04-.08-.09-.78-.78-.86-.87-.29-.28-.64-.43-1.05-.43-.41 0-.76.15-1.05.43-.75.75-6.78 6.77-7.53 7.53-.29.28-.43.63-.43 1.04 0 .42.14.76.43 1.05.75.75 6.78 6.77 7.53 7.53.29.28.64.42 1.05.42.41 0 .76-.14 1.05-.42.08-.09.78-.78.86-.87.29-.29.43-.63.43-1.04 0-.41-.14-.76-.43-1.05-.37-.38-2.24-2.25-5.61-5.61 3.37-3.37 5.24-5.24 5.61-5.62z" />
</defs>
<use xlink:href="#a" />
<use xlink:href="#a" />
</svg>
Examples</a></h1>
</div>
</div>
<div class="content">
<div class="container grid grid-simple-col-3">
<div class="sample-item">
<h2><a href="./geo-explorer/">Geo-Explorer</a></h2>
<a href="./geo-explorer/"><img class="img-responsive img-thumbnail" src="./geo-explorer/preview.png"></a>
<a href="https://github.com/keenlabs/dashboards/tree/gh-pages/examples/geo-explorer">Source Code</a>
</div>
<div class="sample-item">
<h2><a href="./starter-kit/">Starter Kit</a></h2>
<a href="./starter-kit/"><img class="img-responsive img-thumbnail" src="./starter-kit/preview.png"></a>
<a href="https://github.com/keenlabs/dashboards/tree/gh-pages/examples/starter-kit">Source Code</a>
</div>
<div class="sample-item">
<h2><a href="./connected-devices/">Connected Devices</a></h2>
<a href="./connected-devices/"><img class="img-responsive img-thumbnail"
src="./connected-devices/preview.png"></a>
<a href="https://github.com/keenlabs/dashboards/tree/gh-pages/examples/connected-devices">Source Code</a>
</div>
<div class="sample-item">
<h2><a href="./sfmta-parking/">SFMTA Parking</a></h2>
<a href="./sfmta-parking/"><img class="img-responsive img-thumbnail" src="./sfmta-parking/preview.png"></a>
<a href="https://github.com/keenlabs/dashboards/tree/gh-pages/examples/sfmta-parking">Source Code</a>
</div>
</div>
</div>
<div class="footer">
<div class="container">
<div class="love">
<p><a href="https://keen.io">Keen IO</a></p>
</div>
</div>
</div>
<!-- Project Analytics -->
<script type="text/javascript" src="../assets/js/keen-analytics.js"></script>
</body>
</html>
================================================
FILE: examples/military-surplus-flow/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Military Surplus Flow</title>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />
<!-- keen-analysis@3 -->
<script src="https://cdn.jsdelivr.net/npm/keen-analysis@3" type="text/javascript"></script>
<!-- keen-dataviz@3 -->
<link href="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.js" type="text/javascript"></script>
<!-- Dashboard -->
<link rel="stylesheet" type="text/css" href="../../assets/css/keen-dashboards.css" />
<link rel="stylesheet" type="text/css" href="./military-surplus-flow.css" />
<script type="text/javascript" src="military-surplus-flow.js"></script>
</head>
<body class="keen-dashboard">
<div class="container">
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="../">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="76.39 132.243 15.35 22" width="20" height="20" fill="#777">
<defs>
<path id="a"
d="M88.31 136.63c.29-.28.43-.63.43-1.05 0-.41-.14-.76-.43-1.04-.08-.09-.78-.78-.86-.87-.29-.28-.64-.43-1.05-.43-.41 0-.76.15-1.05.43-.75.75-6.78 6.77-7.53 7.53-.29.28-.43.63-.43 1.04 0 .42.14.76.43 1.05.75.75 6.78 6.77 7.53 7.53.29.28.64.42 1.05.42.41 0 .76-.14 1.05-.42.08-.09.78-.78.86-.87.29-.29.43-.63.43-1.04 0-.41-.14-.76-.43-1.05-.37-.38-2.24-2.25-5.61-5.61 3.37-3.37 5.24-5.24 5.61-5.62z" />
</defs>
<use xlink:href="#a" />
<use xlink:href="#a" />
</svg>
</a>
<a class="navbar-brand" href="./">Military Surplus Flow</a>
</div>
<div class="navbar-collapse">
<ul class="navbar-nav nav main-nav">
<li><a href="https://keen.io">Home</a></li>
<li><a href="https://keen.io/team">Team</a></li>
<li><a href="https://github.com/keenlabs/dashboards/tree/gh-pages/layouts/hero-sidebar">Source</a>
</li>
<li><a href="https://groups.google.com/forum/#!forum/keen-io-devs">Community</a></li>
<li><a href="http://stackoverflow.com/questions/tagged/keen-io?sort=newest&pageSize=15">Technical
Support</a></li>
</ul>
</div>
</div>
</div>
<div class="container grid grid-column-vertical">
<div class="column">
<div id="grid-1"></div>
<hr>
<div id="grid-2"></div>
<hr>
<div id="grid-3"></div>
</div>
<div class="column vertical">
<div id="grid-4"></div>
</div>
</div>
<div class="container">
<hr>
<p class="small text-muted">Built with ♥ by <a href="https://keen.io">Keen IO</a></p>
</div>
<!-- Project Analytics -->
<script type="text/javascript" src="../../assets/js/keen-analytics.js"></script>
<script>
function toggleMenu() {
const toggleBtn = document.querySelector('.navbar-toggle');
toggleBtn.addEventListener('click', (e) => {
let menu;
if (e.currentTarget.dataset.target) {
menu = document.querySelector(e.currentTarget.dataset.target);
}
if (menu) menu.classList.toggle('collapsed');
});
}
window.addEventListener('DOMContentLoaded', toggleMenu);
</script>
</body>
</html>
================================================
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
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Parking Data Dashboard, by Keen IO</title>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />
<!-- Demo Dependencies -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>
<!-- Mapbox Dependencies -->
<link href="https://api.tiles.mapbox.com/mapbox.js/v1.6.2/mapbox.css" rel="stylesheet" />
<script src="https://api.tiles.mapbox.com/mapbox.js/v1.6.2/mapbox.js" type="text/javascript"></script>
<!-- keen-analysis@3-->
<script src="https://cdn.jsdelivr.net/npm/keen-analysis@3" type="text/javascript"></script>
<!-- keen-dataviz@3 -->
<link href="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.js" type="text/javascript"></script>
<!-- Dashboard -->
<link href="../../assets/css/keen-dashboards.css" rel="stylesheet" type="text/css" />
<link href="sfmta-demo.css" rel="stylesheet" />
<script type="text/javascript" src="sfmta-demo.js"></script>
</head>
<body class="keen-dashboard">
<div class="container">
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="../">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="76.39 132.243 15.35 22" width="20" height="20" fill="#777">
<defs>
<path id="a"
d="M88.31 136.63c.29-.28.43-.63.43-1.05 0-.41-.14-.76-.43-1.04-.08-.09-.78-.78-.86-.87-.29-.28-.64-.43-1.05-.43-.41 0-.76.15-1.05.43-.75.75-6.78 6.77-7.53 7.53-.29.28-.43.63-.43 1.04 0 .42.14.76.43 1.05.75.75 6.78 6.77 7.53 7.53.29.28.64.42 1.05.42.41 0 .76-.14 1.05-.42.08-.09.78-.78.86-.87.29-.29.43-.63.43-1.04 0-.41-.14-.76-.43-1.05-.37-.38-2.24-2.25-5.61-5.61 3.37-3.37 5.24-5.24 5.61-5.62z" />
</defs>
<use xlink:href="#a" />
<use xlink:href="#a" />
</svg>
</a>
<a class="navbar-brand" href="./">SFMTA <small>Parking Data</small></a>
</div>
<div class="navbar-collapse">
<ul class="navbar-nav nav main-nav">
<li><a href="https://keen.io">Home</a></li>
<li><a href="https://keen.io/team">Team</a></li>
<li><a href="https://github.com/keenlabs/dashboards/tree/gh-pages/layouts/hero-sidebar">Source</a>
</li>
<li><a href="https://groups.google.com/forum/#!forum/keen-io-devs">Community</a></li>
<li><a href="http://stackoverflow.com/questions/tagged/keen-io?sort=newest&pageSize=15">Technical
Support</a></li>
</ul>
</div>
</div>
</div>
<div class="container grid grid-simple-col-2">
<div class="sfmta-charts">
<div id="chart-01" class="chart"></div>
<div id="chart-03" class="chart"></div>
<div id="chart-05" class="chart"></div>
</div>
<div class="sfmta-map">
<div id="mapbox-panel">
<div id="map"></div>
</div>
</div>
</div>
<!-- Project Analytics -->
<script type="text/javascript" src="../../assets/js/keen-analytics.js"></script>
<script>
function toggleMenu() {
const toggleBtn = document.querySelector('.navbar-toggle');
toggleBtn.addEventListener('click', (e) => {
let menu;
if (e.currentTarget.dataset.target) {
menu = document.querySelector(e.currentTarget.dataset.target);
}
if (menu) menu.classList.toggle('collapsed');
});
}
window.addEventListener('DOMContentLoaded', toggleMenu);
</script>
</body>
</html>
================================================
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('<a href=\'https://keen.io/\'>Custom Analytics by Keen IO</a>');
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
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Dashboard Starter UI, by Keen IO</title>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />
<!-- keen-analysis@3 -->
<script src="https://cdn.jsdelivr.net/npm/keen-analysis@3" type="text/javascript"></script>
<!-- keen-dataviz@3 -->
<link href="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.js" type="text/javascript"></script>
<!-- Dashboard -->
<link href="../../assets/css/keen-dashboards.css" rel="stylesheet" type="text/css" />
<link href="starter-kit.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="starter-kit.js"></script>
</head>
<body class="keen-dashboard">
<div class="container">
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="../">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="76.39 132.243 15.35 22" width="20" height="20" fill="#777">
<defs>
<path id="a"
d="M88.31 136.63c.29-.28.43-.63.43-1.05 0-.41-.14-.76-.43-1.04-.08-.09-.78-.78-.86-.87-.29-.28-.64-.43-1.05-.43-.41 0-.76.15-1.05.43-.75.75-6.78 6.77-7.53 7.53-.29.28-.43.63-.43 1.04 0 .42.14.76.43 1.05.75.75 6.78 6.77 7.53 7.53.29.28.64.42 1.05.42.41 0 .76-.14 1.05-.42.08-.09.78-.78.86-.87.29-.29.43-.63.43-1.04 0-.41-.14-.76-.43-1.05-.37-.38-2.24-2.25-5.61-5.61 3.37-3.37 5.24-5.24 5.61-5.62z" />
</defs>
<use xlink:href="#a" />
<use xlink:href="#a" />
</svg>
</a>
<a class="navbar-brand" href="./">Dashboard Starter UI</a>
</div>
<div class="navbar-collapse">
<ul class="navbar-nav nav main-nav">
<li><a href="https://keen.io">Home</a></li>
<li><a href="https://keen.io/team">Team</a></li>
<li><a href="https://github.com/keenlabs/dashboards/tree/gh-pages/layouts/hero-sidebar">Source</a>
</li>
<li><a href="https://groups.google.com/forum/#!forum/keen-io-devs">Community</a></li>
<li><a href="http://stackoverflow.com/questions/tagged/keen-io?sort=newest&pageSize=15">Technical
Support</a></li>
</ul>
</div>
</div>
</div>
<div class="container grid grid-simple-col-3">
<div id="chart-01" class="hero starter-chart"></div>
<div id="chart-02" class="starter-chart"></div>
<div id="chart-03" class="starter-chart"></div>
<div id="chart-04" class="starter-chart"></div>
<div id="chart-05" class="starter-chart"></div>
</div>
<div class="container">
<hr>
<p class="small text-muted">Built with ♥ by <a href="https://keen.io">Keen IO</a></p>
</div>
<!-- Project Analytics -->
<script type="text/javascript" src="../../assets/js/keen-analytics.js"></script>
<script>
function toggleMenu() {
const toggleBtn = document.querySelector('.navbar-toggle');
toggleBtn.addEventListener('click', (e) => {
let menu;
if (e.currentTarget.dataset.target) {
menu = document.querySelector(e.currentTarget.dataset.target);
}
if (menu) menu.classList.toggle('collapsed');
});
}
window.addEventListener('DOMContentLoaded', toggleMenu);
</script>
</body>
</html>
================================================
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
================================================
<!DOCTYPE html>
<html>
<head>
<title>Dashboards by Keen IO</title>
<meta charset="utf-8">
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />
<link href="https://cloud.typography.com/737368/747986/css/fonts.css" rel="stylesheet" type="text/css">
<link href="assets/css/keen-static.css" rel="stylesheet" type='text/css' />
<link href="assets/css/keen-dashboards.css" rel="stylesheet" type='text/css' />
</head>
<body>
<div class="masthead hero">
<div class="container">
<header class="navbar">
<div class="navbar-header">
<button class="navbar-toggle" type="button" data-target=".keen-navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="https://keen.io?s=db_land" class="navbar-brand" target="_blank">
<svg class="keen-logo" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px"
viewBox="0 0 594 128" enable-background="new 0 0 594 128" xml:space="preserve">
<g class="icon">
<polygon class="red" fill="#FFFFFF"
points="59.811,73.135 66.859,85.34 49.275,115.863 140.807,115.863 147.814,128 28.045,128"></polygon>
<polygon class="green" fill="#FFFFFF"
points="59.873,24.297 91.504,79.242 77.413,79.242 59.826,48.781 13.958,128 0,128"></polygon>
<polygon class="blue" fill="#FFFFFF"
points="133.769,103.674 70.364,103.674 77.418,91.432 112.586,91.432 66.915,12.1 73.9,0"></polygon>
</g>
<path class="text" fill="#FFFFFF"
d="M246.12 94.163L216.29 57.373l27.704-27.484c1.199-1.195 1.782-2.475 1.782-3.91 0-2.906-2.496-5.453-5.341-5.453 -1.869 0-3.128 0.887-4.262 1.896l-43.89 44.996V26.094c0-3.07-2.498-5.568-5.568-5.568 -2.96 0-5.461 2.549-5.461 5.568v72.01c0 3.02 2.501 5.57 5.461 5.57 3.07 0 5.568-2.498 5.568-5.57V81.145l16.181-16.178 29.093 36.231c1.15 1.643 2.655 2.477 4.474 2.477 3.021 0 5.573-2.551 5.573-5.57C247.604 96.301 246.896 95.151 246.12 94.163zM309.211 71.702c0-15.379-9.767-30.948-28.434-30.948 -16.456 0-29.348 13.941-29.348 31.743v0.23c0 18.1 13.108 31.748 30.49 31.748 9.265 0 16.353-2.836 22.95-9.162 1.104-0.967 1.713-2.248 1.713-3.609 0-2.631-2.192-4.771-4.887-4.771 -1.466 0-2.503 0.719-3.195 1.293 -4.886 4.5-10.083 6.596-16.354 6.596 -10.378 0-18.167-7.188-19.675-18.006h41.627C306.917 76.815 309.211 74.52 309.211 71.702zM280.55 50.182c11.501 0 16.667 9.162 17.751 18.117h-35.831C263.956 57.574 271.266 50.182 280.55 50.182zM374.018 71.702c0-15.379-9.767-30.948-28.434-30.948 -16.454 0-29.344 13.941-29.344 31.743v0.23c0 18.1 13.107 31.748 30.486 31.748 9.263 0 16.351-2.836 22.95-9.164 1.103-0.965 1.71-2.246 1.71-3.607 0-2.631-2.193-4.771-4.889-4.771 -1.464 0-2.501 0.719-3.193 1.293 -4.886 4.5-10.082 6.596-16.352 6.596 -10.374 0-18.163-7.189-19.677-18.006h41.626C371.723 76.815 374.018 74.52 374.018 71.702zM345.357 50.182c11.497 0 16.662 9.162 17.749 18.117h-35.83C328.762 57.574 336.072 50.182 345.357 50.182zM414.734 40.754c-8.037 0-14.492 3.127-19.233 9.309V46.9c0-3.062-2.396-5.461-5.455-5.461 -2.945 0-5.342 2.449-5.342 5.461v51.319c0 3.059 2.396 5.455 5.455 5.455 3.045 0 5.342-2.346 5.342-5.455V68.271c0-10.312 6.874-17.514 16.717-17.514 9.606 0 15.342 6.291 15.342 16.826v30.635c0 3.059 2.398 5.455 5.461 5.455 2.999 0 5.348-2.396 5.348-5.455V65.414C438.369 50.434 429.092 40.754 414.734 40.754zM491.424 20.525c-2.958 0-5.457 2.549-5.457 5.568v72.01c0 3.02 2.499 5.57 5.457 5.57 3.072 0 5.572-2.498 5.572-5.57v-72.01C496.996 23.023 494.496 20.525 491.424 20.525zM552.307 19.725c-23.504 0-41.916 18.613-41.916 42.373v0.229c0 24.03 17.923 42.149 41.689 42.149 23.507 0 41.92-18.613 41.92-42.377v-0.227C594 37.844 576.076 19.725 552.307 19.725zM582.627 62.098v0.229c0 18.133-13.035 31.807-30.32 31.807 -17.411 0-30.541-13.773-30.541-32.036v-0.227c0-18.131 13.032-31.803 30.314-31.803C569.209 30.068 582.627 44.137 582.627 62.098z">
</path>
</svg>
</a>
</div>
<nav class="navbar-collapse collapse keen-navbar-collapse" role="navigation">
<ul class="navbar-nav nav main-nav">
<li><a href="https://github.com/keen/dashboards" target="_blank">Docs</a></li>
<li><a href="https://keen.io/docs/visualize/common-chart-examples/" target="_blank">Charts</a></li>
<li><a href="https://keen.io/team" target="_blank">Team</a></li>
<li><a href="https://blog.keen.io/" target="_blank">Blog</a></li>
<li><a href="http://slack.keen.io/" target="_blank">Community</a></li>
</ul>
<ul class="navbar-nav nav main-nav align-right">
<li><a href="https://try.keen.io/contact?s=gh-dashboards" class="btn navbar-btn" target="_blank">Get started today</a></li>
</ul>
</nav>
</header>
<h1>Dashboards by Keen IO</h1>
<p class="lead">Responsive dashboard templates for Bootstrap</p>
<a class="btn hero-btn" href="https://github.com/keen/dashboards" target="_blank">Source Code on Github</a>
<iframe src="https://ghbtns.com/github-btn.html?user=keen&repo=dashboards&type=watch&count=true"
allowtransparency="true" frameborder="0" scrolling="0" width="95" height="20"></iframe>
<iframe src="https://ghbtns.com/github-btn.html?user=keen&repo=dashboards&type=fork&count=true"
allowtransparency="true" frameborder="0" scrolling="0" width="95" height="20"></iframe>
</div>
</div>
<div class="content">
<div class="container grid grid-simple-col-2">
<div>
<h2><a href="./examples/">Examples</a></h2>
<a href="./examples/"><img class="img-responsive img-thumbnail" src="./examples/starter-kit/preview.png"></a>
</div>
<div>
<h2><a href="./layouts/">Layouts</a></h2>
<a href="./layouts/"><img class="img-responsive img-thumbnail" src="./layouts/quarter-grid/preview.png"></a>
</div>
</div>
</div>
<div class="footer">
<div class="container">
<div class="love">
<p><a href="https://keen.io?s=db_land">Keen IO</a></p>
</div>
</div>
</div>
<script type="text/javascript" src="assets/js/keen-analytics.js"></script>
<script>
function toggleMenu() {
const toggleBtn = document.querySelector('.navbar-toggle');
toggleBtn.addEventListener('click', (e) => {
let menu;
if (e.currentTarget.dataset.target) {
menu = document.querySelector(e.currentTarget.dataset.target);
}
if (menu) menu.classList.toggle('collapsed');
});
}
window.addEventListener('DOMContentLoaded', toggleMenu);
</script>
</body>
</html>
================================================
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
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Layouts » Hero Sidebar</title>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />
<!-- Demo Dependencies -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/holder/2.9.6/holder.js" type="text/javascript"></script>
<script>
Holder.addTheme("white", {
bg: "#fff",
fg: "#a7a7a7",
size: 10
});
</script>
<!-- keen-analysis@3 -->
<script src="https://cdn.jsdelivr.net/npm/keen-analysis@3/dist/keen-analysis.min.js" type="text/javascript"></script>
<!-- keen-dataviz@3 -->
<link href="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.js" type="text/javascript"></script>
<!-- Dashboard -->
<link rel="stylesheet" type="text/css"
href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css" />
<link rel="stylesheet" type="text/css" href="../../assets/css/keen-dashboards.css" />
</head>
<body class="keen-dashboard">
<div class="container">
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="../">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="76.39 132.243 15.35 22" width="20" height="20" fill="#777">
<defs>
<path id="a"
d="M88.31 136.63c.29-.28.43-.63.43-1.05 0-.41-.14-.76-.43-1.04-.08-.09-.78-.78-.86-.87-.29-.28-.64-.43-1.05-.43-.41 0-.76.15-1.05.43-.75.75-6.78 6.77-7.53 7.53-.29.28-.43.63-.43 1.04 0 .42.14.76.43 1.05.75.75 6.78 6.77 7.53 7.53.29.28.64.42 1.05.42.41 0 .76-.14 1.05-.42.08-.09.78-.78.86-.87.29-.29.43-.63.43-1.04 0-.41-.14-.76-.43-1.05-.37-.38-2.24-2.25-5.61-5.61 3.37-3.37 5.24-5.24 5.61-5.62z" />
</defs>
<use xlink:href="#a" />
<use xlink:href="#a" />
</svg>
</a>
<a class="navbar-brand" href="./">Layouts » Hero Sidebar</a>
</div>
<div class="navbar-collapse">
<ul class="navbar-nav nav main-nav">
<li><a href="https://keen.io">Home</a></li>
<li><a href="https://keen.io/team">Team</a></li>
<li><a href="https://github.com/keenlabs/dashboards/tree/gh-pages/layouts/hero-sidebar">Source</a>
</li>
<li><a href="https://groups.google.com/forum/#!forum/keen-io-devs">Community</a></li>
<li><a href="http://stackoverflow.com/questions/tagged/keen-io?sort=newest&pageSize=15">Technical
Support</a></li>
</ul>
</div>
</div>
</div>
<div class="container grid grid-hero-sidebar">
<div class="sidebar">
<div class="chart-wrapper">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<img data-src="holder.js/100px660?theme=white">
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
</div>
<div class="hero">
<div class="chart-wrapper">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<img data-src="holder.js/100px240?theme=white">
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
</div>
<div class="chart-wrapper">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<img data-src="holder.js/100px120?theme=white">
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
<div class="chart-wrapper">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<img data-src="holder.js/100px120?theme=white">
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
<div class="chart-wrapper">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<img data-src="holder.js/100px120?theme=white">
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
<div class="chart-wrapper">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<img data-src="holder.js/100px120?theme=white">
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
<div class="chart-wrapper">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<img data-src="holder.js/100px120?theme=white">
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
<div class="chart-wrapper">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<img data-src="holder.js/100px120?theme=white">
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
</div>
<div class="container">
<p class="small text-muted">Built with ♥ by <a href="https://keen.io">Keen IO</a></p>
</div>
<!-- Project Analytics -->
<script type="text/javascript" src="../../assets/js/keen-analytics.js"></script>
<script>
function toggleMenu() {
const toggleBtn = document.querySelector('.navbar-toggle');
toggleBtn.addEventListener('click', (e) => {
let menu;
if (e.currentTarget.dataset.target) {
menu = document.querySelector(e.currentTarget.dataset.target);
}
if (menu) menu.classList.toggle('collapsed');
});
}
window.addEventListener('DOMContentLoaded', toggleMenu);
</script>
</body>
</html>
================================================
FILE: layouts/hero-thirds/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Layouts » Hero-Thirds</title>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />
<!-- Demo Dependencies -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/holder/2.9.6/holder.js" type="text/javascript"></script>
<script>
Holder.addTheme("white", {
bg: "#fff",
fg: "#a7a7a7",
size: 10
});
</script>
<!-- keen-analysis@3 -->
<script src="https://cdn.jsdelivr.net/npm/keen-analysis@3/dist/keen-analysis.min.js" type="text/javascript"></script>
<!-- keen-dataviz@3 -->
<link href="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/keen-dataviz@3/dist/keen-dataviz.min.js" type="text/javascript"></script>
<!-- Dashboard -->
<link rel="stylesheet" type="text/css"
href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css" />
<link rel="stylesheet" type="text/css" href="../../assets/css/keen-dashboards.css" />
</head>
<body class="keen-dashboard">
<div class="container">
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="../">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="76.39 132.243 15.35 22" width="20" height="20" fill="#777">
<defs>
<path id="a"
d="M88.31 136.63c.29-.28.43-.63.43-1.05 0-.41-.14-.76-.43-1.04-.08-.09-.78-.78-.86-.87-.29-.28-.64-.43-1.05-.43-.41 0-.76.15-1.05.43-.75.75-6.78 6.77-7.53 7.53-.29.28-.43.63-.43 1.04 0 .42.14.76.43 1.05.75.75 6.78 6.77 7.53 7.53.29.28.64.42 1.05.42.41 0 .76-.14 1.05-.42.08-.09.78-.78.86-.87.29-.29.43-.63.43-1.04 0-.41-.14-.76-.43-1.05-.37-.38-2.24-2.25-5.61-5.61 3.37-3.37 5.24-5.24 5.61-5.62z" />
</defs>
<use xlink:href="#a" />
<use xlink:href="#a" />
</svg>
</a>
<a class="navbar-brand" href="./">Layouts » Hero Thirds</a>
</div>
<div class="navbar-collapse">
<ul class="navbar-nav nav main-nav">
<li><a href="https://keen.io">Home</a></li>
<li><a href="https://keen.io/team">Team</a></li>
<li><a href="https://github.com/keenlabs/dashboards/tree/gh-pages/layouts/hero-sidebar">Source</a>
</li>
<li><a href="https://groups.google.com/forum/#!forum/keen-io-devs">Community</a></li>
<li><a href="http://stackoverflow.com/questions/tagged/keen-io?sort=newest&pageSize=15">Technical
Support</a></li>
</ul>
</div>
</div>
</div>
<div class="container grid grid-hero-thirds">
<div class="grid-hero">
<div class="hero chart-wrapper">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<div id="grid-1-1">
<img data-src="holder.js/100px240?theme=white&&text=#grid-1-1">
</div>
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
<div class="chart-wrapper">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<img data-src="holder.js/100px240?theme=white">
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
</div>
<div class="chart-wrapper">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<img data-src="holder.js/100px120?theme=white">
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
<div class="chart-wrapper">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<img data-src="holder.js/100px120?theme=white">
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
<div class="chart-wrapper">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<img data-src="holder.js/100px120?theme=white">
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
<div class="chart-wrapper" id="1">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<img data-src="holder.js/100px120?theme=white">
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
<div class="chart-wrapper">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<img data-src="holder.js/100px120?theme=white">
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
<div class="chart-wrapper">
<div class="chart-title">
Cell Title
</div>
<div class="chart-stage">
<img data-src="holder.js/100px120?theme=white">
</div>
<div class="chart-notes">
Notes about this chart
</div>
</div>
</div>
<div class="container">
<p class="small text-muted">Built with ♥ by <a href="https://keen.io">Keen IO</a></p>
</div>
<!-- Project Analytics -->
<script type="text/javascript" src="../../assets/js/keen-analytics.js"></script>
<script>
function toggleMenu() {
const toggleBtn = document.querySelector('.navbar-toggle');
toggleBtn.addEventListener('click', (e) => {
let menu;
if (e.currentTarget.dataset.target) {
menu = document.querySelector(e.currentTarget.dataset.target);
}
if (menu) menu.classList.toggle('collapsed');
});
}
window.addEventListener('DOMContentLoaded', toggleMenu);
</script>
</body>
</html>
================================================
FILE: layouts/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Dashboard Layouts, by Keen IO</title>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />
<link href="https://cloud.typography.com/737368/747986/css/fonts.css" rel="stylesheet" type="text/css">
<link rel="stylesheet" type="text/css" href="../assets/css/keen-static.css" />
<link rel="stylesheet" type="text/css" href="../assets/css/keen-dashboards.css" />
</head>
<body>
<div class="masthead">
<div class="container">
<h1><a class="return-link" href="../"><svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http:
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
SYMBOL INDEX (26 symbols across 10 files)
FILE: examples/2048/js/game/classlist_polyfill.js
function DOMTokenList (line 12) | function DOMTokenList(el) {
function defineElementGetter (line 58) | function defineElementGetter(obj, prop, getter) {
FILE: examples/2048/js/game/game_manager.js
function GameManager (line 1) | function GameManager(size, InputManager, Actuator, StorageManager) {
FILE: examples/2048/js/game/grid.js
function Grid (line 1) | function Grid(size, previousState) {
FILE: examples/2048/js/game/html_actuator.js
function HTMLActuator (line 1) | function HTMLActuator() {
FILE: examples/2048/js/game/keyboard_input_manager.js
function KeyboardInputManager (line 1) | function KeyboardInputManager() {
FILE: examples/2048/js/game/local_storage_manager.js
function LocalStorageManager (line 21) | function LocalStorageManager() {
FILE: examples/2048/js/game/tile.js
function Tile (line 1) | function Tile(position, value) {
FILE: examples/connected-devices/connected-devices.js
function setActivePane (line 41) | function setActivePane(e) {
function selectVisitorTab (line 57) | function selectVisitorTab(e) {
function selectBrowserTab (line 67) | function selectBrowserTab(e) {
function selectGeographyTab (line 77) | function selectGeographyTab(e) {
function renderVisitorTab (line 87) | function renderVisitorTab(chart) {
function renderBrowserTab (line 108) | function renderBrowserTab(chart) {
function renderGeographyTab (line 130) | function renderGeographyTab(chart) {
function setActiveButton (line 265) | function setActiveButton(button) {
function runQuery (line 324) | function runQuery() {
function resize (line 344) | function resize(geo) {
FILE: examples/geo-explorer/geo-explorer.js
function init (line 68) | function init() {
function getParams (line 161) | function getParams(selector) {
function updateQuery (line 166) | function updateQuery() {
function setGeoSelection (line 187) | function setGeoSelection() {
function refresh (line 199) | function refresh() {
function adjust (line 204) | function adjust() {
function draw (line 211) | function draw() {
FILE: examples/sfmta-parking/sfmta-demo.js
function adjust (line 14) | function adjust() {
Condensed preview — 66 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (253K chars).
[
{
"path": ".gitignore",
"chars": 22,
"preview": ".DS_Store\n.sass-cache\n"
},
{
"path": ".npmignore",
"chars": 43,
"preview": ".bowerrc\n.DS_Store\n.git*\n*.log\n\nbower.json\n"
},
{
"path": "CONTRIBUTING.md",
"chars": 1221,
"preview": "## Submitting a Pull Request\n\nUse the template below. If certain testing steps are not relevant, specify that in the PR."
},
{
"path": "Dockerfile",
"chars": 114,
"preview": "FROM httpd\n\nRUN apt-get update -y\nWORKDIR /usr/local/apache2/htdocs/\nADD . /usr/local/apache2/htdocs/\n\nEXPOSE 80 \n"
},
{
"path": "LICENSE",
"chars": 1074,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2014 Keen IO\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "README.md",
"chars": 5953,
"preview": "# Dashboards by Keen IO\n\nBuilding an analytics dashboard? Don’t start from scratch. Grab one of our CSS Grid-based templ"
},
{
"path": "assets/css/keen-dashboards.css",
"chars": 11807,
"preview": ":root {\n --grid-gap: 15px;\n}\n\n/* GENERAL */\n\n* {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box"
},
{
"path": "assets/css/keen-static.css",
"chars": 2615,
"preview": "a,\na:focus,\na:hover,\na:active {\n color: #00afd7;\n}\n\nh1,\nh2,\nh3 {\n font-family: \"Gotham Rounded\", \"Helvetica Neue\", Hel"
},
{
"path": "assets/js/keen-analytics.js",
"chars": 2246,
"preview": "! function (name, path, ctx) {\n var latest, prev = name !== 'Keen' && window.Keen ? window.Keen : false;\n ctx[name] = "
},
{
"path": "bower.json",
"chars": 672,
"preview": "{\n \"name\": \"keen-dashboards\",\n \"homepage\": \"https://keenlabs.github.io/dashboards/\",\n \"main\": \"./dist/keen-dashboards"
},
{
"path": "demo-data/sample.html",
"chars": 4339,
"preview": "<script>\n const client = new Keen({\n projectId: \"5368fa5436bf5a5623000000\",\n readKey: \"3f324dcb5636316d6865ab0ebb"
},
{
"path": "dist/keen-dashboards.css",
"chars": 1565,
"preview": ".keen-dashboard {\n background: #f2f2f2;\n font-family: 'Gotham Rounded SSm A', 'Gotham Rounded SSm B', 'Helvetica Neue'"
},
{
"path": "examples/2048/index.html",
"chars": 7994,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <title>2048</title>\n\n <link rel=\"shortcut icon\" href=\"favicon"
},
{
"path": "examples/2048/js/game/animframe_polyfill.js",
"chars": 890,
"preview": "(function () {\n var lastTime = 0;\n var vendors = ['webkit', 'moz'];\n for (var x = 0; x < vendors.length && !window.re"
},
{
"path": "examples/2048/js/game/application.js",
"chars": 1793,
"preview": "window.requestAnimationFrame(function () {\n // Wait till the browser is ready to render the game (avoids glitches)\n\n w"
},
{
"path": "examples/2048/js/game/bind_polyfill.js",
"chars": 220,
"preview": "Function.prototype.bind = Function.prototype.bind || function (target) {\n var self = this;\n return function (args) {\n "
},
{
"path": "examples/2048/js/game/classlist_polyfill.js",
"chars": 1794,
"preview": "(function () {\n if (typeof window.Element === \"undefined\" ||\n \"classList\" in document.documentElement) {\n retur"
},
{
"path": "examples/2048/js/game/game_manager.js",
"chars": 7670,
"preview": "function GameManager(size, InputManager, Actuator, StorageManager) {\n this.size = size; // Size of the grid\n "
},
{
"path": "examples/2048/js/game/grid.js",
"chars": 2526,
"preview": "function Grid(size, previousState) {\n this.size = size;\n this.cells = previousState ? this.fromState(previousState) : "
},
{
"path": "examples/2048/js/game/html_actuator.js",
"chars": 4239,
"preview": "function HTMLActuator() {\n this.tileContainer = document.querySelector(\".tile-container\");\n this.scoreContainer ="
},
{
"path": "examples/2048/js/game/keyboard_input_manager.js",
"chars": 3991,
"preview": "function KeyboardInputManager() {\n this.events = {};\n\n if (window.navigator.msPointerEnabled) {\n //Internet Explore"
},
{
"path": "examples/2048/js/game/local_storage_manager.js",
"chars": 1586,
"preview": "window.fakeStorage = {\n _data: {},\n\n setItem: function (id, val) {\n return this._data[id] = String(val);\n },\n\n ge"
},
{
"path": "examples/2048/js/game/tile.js",
"chars": 594,
"preview": "function Tile(position, value) {\n this.x = position.x;\n this.y = position.y;\n this.valu"
},
{
"path": "examples/2048/styles/game/fonts/clear-sans.css",
"chars": 1078,
"preview": "@font-face {\n font-family: \"Clear Sans\";\n src: url(\"ClearSans-Light-webfont.eot\");\n src: url(\"ClearSans-Light-w"
},
{
"path": "examples/2048/styles/game/helpers.scss",
"chars": 1655,
"preview": "// Exponent\n// From: https://github.com/Team-Sass/Sassy-math/blob/master/sass/math.scss#L36\n\n@function exponent($base, $"
},
{
"path": "examples/2048/styles/game/main.css",
"chars": 19141,
"preview": "@import url(fonts/clear-sans.css);\n.navbar-inverse {\n background-color: #3d4a57;\n border-color: #333; }\n\nhtml, body {\n"
},
{
"path": "examples/2048/styles/game/main.scss",
"chars": 10803,
"preview": "@import \"helpers\";\n@import \"fonts/clear-sans.css\";\n\n$field-width: 500px;\n$grid-spacing: 15px;\n$grid-row-cells: 4;\n$tile-"
},
{
"path": "examples/README.md",
"chars": 38,
"preview": "Dashboard Examples\n==================\n"
},
{
"path": "examples/connected-devices/README.md",
"chars": 0,
"preview": ""
},
{
"path": "examples/connected-devices/connected-devices.css",
"chars": 2836,
"preview": "#mapwrap .chart-title {\n border: medium none;\n}\n\n#mapwrap .chart-stage {\n height: 350px;\n}\n\n#map {\n bottom: 5px;\n le"
},
{
"path": "examples/connected-devices/connected-devices.js",
"chars": 10603,
"preview": "const client = new Keen({\n projectId: '5337e28273f4bb4499000000',\n readKey: '8827959317a6a01257bbadf16c12eff4bc61a1708"
},
{
"path": "examples/connected-devices/index.html",
"chars": 6470,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset='utf-8' />\n <meta name='viewport' content='width=device-width, initial-s"
},
{
"path": "examples/customer-facing/README.md",
"chars": 0,
"preview": ""
},
{
"path": "examples/customer-facing/customer-facing.css",
"chars": 3994,
"preview": "* {\n box-sizing: border-box;\n}\n\nimg {\n max-width: 100%;\n}\n\nbody {\n margin: 0;\n font-family: \"Helvetica Neue\", Helvet"
},
{
"path": "examples/customer-facing/customer-facing.js",
"chars": 3328,
"preview": "const client1 = new Keen({\n projectId: '5368fa5436bf5a5623000000',\n readKey: '3f324dcb5636316d6865ab0ebbbbc725224c7f8f"
},
{
"path": "examples/customer-facing/index.html",
"chars": 3711,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset='utf-8' />\n <meta name='viewport' content='width=device-width, initial-s"
},
{
"path": "examples/geo-explorer/geo-explorer.css",
"chars": 1264,
"preview": "body.application {\n padding: 0;\n margin: 0;\n}\n\n.row {\n margin: 0;\n}\n\n#app-wrapper {\n height: 100%;\n position: abosl"
},
{
"path": "examples/geo-explorer/geo-explorer.js",
"chars": 9149,
"preview": "!(function (undefined) {\n\n let DEFAULTS,\n GEO,\n client,\n circle,\n marker,\n map,\n activeMapData,\n k"
},
{
"path": "examples/geo-explorer/index.html",
"chars": 3104,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <title>Dashboard Starter UI, by Keen IO</title>\n\n <!-- Demo D"
},
{
"path": "examples/index.html",
"chars": 3134,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <title>Dashboard Examples, by Keen IO</title>\n <meta name='vi"
},
{
"path": "examples/military-surplus-flow/index.html",
"chars": 3809,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <title>Military Surplus Flow</title>\n <meta name='viewport' c"
},
{
"path": "examples/military-surplus-flow/military-surplus-flow.css",
"chars": 82,
"preview": ".column .c3-chart {\n height: 240px;\n}\n\n#grid-4 .c3-chart {\n height: 800px;\n}"
},
{
"path": "examples/military-surplus-flow/military-surplus-flow.js",
"chars": 2505,
"preview": "const client = new Keen({\n projectId: '53f3eca97d8cb91b75000000',\n readKey: 'df6ff0ff414bc286b91e2661db4c691c45b6aea8d"
},
{
"path": "examples/sfmta-parking/README.md",
"chars": 22,
"preview": "Geo Mashup\n==========\n"
},
{
"path": "examples/sfmta-parking/index.html",
"chars": 4096,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <title>Parking Data Dashboard, by Keen IO</title>\n <meta name"
},
{
"path": "examples/sfmta-parking/sfmta-demo.css",
"chars": 526,
"preview": "#mapbox-panel {\n position: relative;\n}\n\n#mapbox-panel .chart-title {\n border: medium none;\n}\n\n#mapbox-panel .chart-sta"
},
{
"path": "examples/sfmta-parking/sfmta-demo.js",
"chars": 6974,
"preview": "const client = new KeenAnalysis({\n projectId: '5368fa5436bf5a5623000000',\n readKey: '3f324dcb5636316d6865ab0ebbbbc7252"
},
{
"path": "examples/starter-kit/LICENSE",
"chars": 1073,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2014 Keen IO\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "examples/starter-kit/README.md",
"chars": 44,
"preview": "Dashboard Starter Kit\n=====================\n"
},
{
"path": "examples/starter-kit/index.html",
"chars": 3835,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <title>Dashboard Starter UI, by Keen IO</title>\n <meta name='"
},
{
"path": "examples/starter-kit/starter-kit.css",
"chars": 231,
"preview": ".starter-chart {\n min-width: 0;\n height: 280px;\n margin-bottom: 1rem;\n}\n\n@media (min-width: 768px) {\n .start"
},
{
"path": "examples/starter-kit/starter-kit.js",
"chars": 3293,
"preview": "const client = new Keen({\n projectId: '5368fa5436bf5a5623000000',\n readKey: '3f324dcb5636316d6865ab0ebbbbc725224c7f8f3"
},
{
"path": "index.html",
"chars": 6868,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <title>Dashboards by Keen IO</title>\n <meta charset=\"utf-8\">\n <meta name='viewport' c"
},
{
"path": "layouts/README.md",
"chars": 304,
"preview": "Dashboard Layouts\n=================\n\nBuilding something from scratch? These vanilla dashboard layouts will get you up an"
},
{
"path": "layouts/hero-sidebar/index.html",
"chars": 6233,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <title>Layouts » Hero Sidebar</title>\n <meta name='view"
},
{
"path": "layouts/hero-thirds/index.html",
"chars": 6275,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <title>Layouts » Hero-Thirds</title>\n <meta name='viewp"
},
{
"path": "layouts/index.html",
"chars": 4394,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <title>Dashboard Layouts, by Keen IO</title>\n <meta name='vie"
},
{
"path": "layouts/quarter-grid/index.html",
"chars": 8446,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <title>Layouts » Quarter Grid</title>\n <meta name='view"
},
{
"path": "layouts/split-centered/index.html",
"chars": 7418,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <title>Layouts » Split-Centered</title>\n <meta name='vi"
},
{
"path": "layouts/split-columns/index.html",
"chars": 6249,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <title>Layouts » Split-Columns</title>\n <meta name='vie"
},
{
"path": "layouts/split-rows/index.html",
"chars": 6112,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <title>Layouts » Split-Rows</title>\n <meta name='viewpo"
},
{
"path": "layouts/thirds-grid/index.html",
"chars": 6455,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <title>Layouts » Thirds Grid</title>\n <meta name='viewp"
},
{
"path": "layouts/two-and-one/LICENSE",
"chars": 1073,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2014 Keen IO\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "layouts/two-and-one/README.md",
"chars": 44,
"preview": "Dashboard Starter Kit\n=====================\n"
},
{
"path": "layouts/two-and-one/index.html",
"chars": 4829,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <title>Layouts » Two-and-One</title>\n <meta name='viewp"
},
{
"path": "package.json",
"chars": 701,
"preview": "{\n \"name\": \"keen-dashboards\",\n \"version\": \"3.0.1\",\n \"description\": \"Responsive dashboard templates\",\n \"main\": \"./dis"
}
]
About this extraction
This page contains the full source code of the keen/dashboards GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 66 files (231.6 KB), approximately 71.5k tokens, and a symbol index with 26 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.