├── .flowconfig ├── .gitignore ├── .vscode └── settings.json ├── LICENSE.md ├── README.md ├── TODO ├── app.json ├── bin ├── build ├── cleanup └── watch ├── flow-typed └── npm │ ├── babel-cli_vx.x.x.js │ ├── babel-core_vx.x.x.js │ ├── babel-plugin-transform-es2015-modules-amd_vx.x.x.js │ ├── babel-plugin-transform-flow-strip-types_vx.x.x.js │ ├── babel-preset-es2015_vx.x.x.js │ ├── babel-preset-stage-0_vx.x.x.js │ ├── babel_vx.x.x.js │ ├── body-parser_v1.x.x.js │ ├── body-parser_vx.x.x.js │ ├── connect_vx.x.x.js │ ├── express_v4.x.x.js │ ├── flow-bin_v0.x.x.js │ ├── handlebars_vx.x.x.js │ ├── less_vx.x.x.js │ ├── nodeunit_vx.x.x.js │ ├── request_vx.x.x.js │ ├── requirejs_vx.x.x.js │ ├── shelljs_vx.x.x.js │ └── uglify-js_vx.x.x.js ├── img └── example_chart_screenshot.png ├── lib ├── client │ ├── d3.js │ ├── nodeunit.css │ ├── nodeunit.js │ └── require.js └── interfaces │ ├── d3.js │ └── types.js ├── package-lock.json ├── package.json └── src ├── client ├── Actions.js ├── Chart.js ├── ChartData.js ├── ChartLegend.js ├── ChartParameters.js ├── ChartParameters_test.js ├── Editor.js ├── Editor_test.js ├── PageController.js ├── PageData.js ├── PageData_test.js ├── charted.js ├── dom.js ├── templates.js └── tests.js ├── public ├── embed.js ├── example.csv ├── images │ ├── icon-back-white.svg │ ├── icon-back.svg │ ├── icon-color-white.svg │ ├── icon-color.svg │ ├── icon-column-white.svg │ ├── icon-column.svg │ ├── icon-download-white.svg │ ├── icon-download.svg │ ├── icon-embed-white.svg │ ├── icon-embed.svg │ ├── icon-full-screen-white.svg │ ├── icon-full-screen.svg │ ├── icon-line-white.svg │ ├── icon-line.svg │ ├── icon-move-white.svg │ ├── icon-move.svg │ ├── icon-plus-white.svg │ ├── icon-plus.svg │ ├── icon-round-off-white.svg │ ├── icon-round-off.svg │ ├── icon-round-on-white.svg │ ├── icon-round-on.svg │ ├── icon-settings-white.svg │ ├── icon-settings.svg │ ├── icon-split-screen-white.svg │ ├── icon-split-screen.svg │ ├── icon-x.svg │ ├── spinner-dark.svg │ └── spinner.svg └── tests.html ├── server ├── charted.js ├── db.js ├── db_test.js ├── index.js ├── prepare.js └── prepare_test.js ├── shared ├── sha1.js ├── sha1_test.js └── utils.js ├── styles ├── chart-info.less ├── chart-options.less ├── chart-plot.less ├── chart-selection.less ├── chart-y-axis.less ├── charted.less ├── colors.less ├── dimensions.less ├── embed-overlay.less ├── icons.less ├── landing-page.less ├── legend.less ├── mixins.less ├── page-settings.less ├── popover.less ├── reset.less ├── type.less └── utils.less └── templates └── index.html /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/out/.* 3 | .*/node_modules/.* 4 | [include] 5 | 6 | [libs] 7 | lib/interfaces/ 8 | 9 | [options] 10 | suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | .DS_Store 4 | out/ 5 | .charted_db 6 | dump.rdb 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "eslint.autoFixOnSave": true, 4 | "files.insertFinalNewline": true, 5 | "javascript.validate.enable": false, 6 | "typescript.validate.enable": false, 7 | "typescript.disableAutomaticTypeAcquisition": true, 8 | "search.exclude": { 9 | "**/node_modules": true, 10 | "**/out": true 11 | }, 12 | "files.exclude": { 13 | "**/.git": true, 14 | "**/.svn": true, 15 | "**/.hg": true, 16 | "**/CVS": true, 17 | "**/.DS_Store": true, 18 | "**/node_modules": true, 19 | "**/out": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 A Medium Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Charted 2 | Charted is a tool for automatically visualizing data, originally created by 3 | the Product Science team at [Medium](https://medium.com/). Provide the 4 | link to a data file and Charted returns a beautiful, interactive, 5 | and shareable chart of the data. The charts look like this: 6 | 7 | ![Example Chart Screenshot](img/example_chart_screenshot.png?raw=true "Example Chart Screenshot") 8 | 9 | Charted is deliberately sparse in formatting and data transformation options, 10 | and instead gives you a few powerful core features: 11 | * Rendering well on all screen sizes, including monitors 12 | * Re-fetching the data and updating the chart every 30 minutes 13 | * Moving data series into separate charts 14 | * Adjusting the chart type, labels/titles, and background 15 | 16 | ## Supported files 17 | Charted currently supports the following file types: 18 | * .csv files 19 | * .tsv files 20 | * Google Spreadsheets (set to shareable) 21 | * Dropbox share links to supported files 22 | 23 | ## Data structure 24 | Charted treats the first column of the data file as the labels for the 25 | x-axis. All subsequent columns are added as y-series. Charted does not 26 | parse the first column (x-axis), but instead always equally spaces the 27 | data points along the x-axis. 28 | 29 | ## Running Charted 30 | To try Charted out, simply download the repo and run `npm install` 31 | to install dependencies. After that you will be able to run 32 | `npm start`. This will start a server at localhost:3000. 33 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | A list of known issues in no particular order: 2 | 3 | - Code that compiles templates is duplicated in two places 4 | - We re-compile templates on every request without caching 5 | results. Seems wasteful. 6 | - Handlebars plugins are registered for the homepage only. 7 | - We seriously need to optimize how we use methods that 8 | cause DOM reflows. 9 | 10 | Planned/ongoing projects: 11 | 12 | - Add support for dashboards where you can display multiple 13 | charts. 14 | - Replace templates.js with Handlebars 15 | - Introduce u-classes 16 | - Use system fonts 17 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "charted", 3 | "description": "A charting library from Medium", 4 | "repository": "https://github.com/mikesall/charted", 5 | "keywords": ["node", "express", "static", "charts"] 6 | } 7 | -------------------------------------------------------------------------------- /bin/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict" 3 | 4 | const sh = require('shelljs/global') 5 | const babel = require('babel-core') 6 | const requirejs = require('requirejs') 7 | const uglify = require('uglify-js') 8 | const path = require('path') 9 | 10 | const BABEL_PLUGINS = { 11 | client: [ 12 | "babel-plugin-transform-flow-strip-types", 13 | "babel-plugin-transform-es2015-modules-amd" 14 | ], 15 | 16 | server: [ 17 | "babel-plugin-transform-flow-strip-types" 18 | ] 19 | } 20 | 21 | const BABEL_PRESETS = [ 22 | "babel-preset-es2015", 23 | "babel-preset-stage-0" 24 | ] 25 | 26 | const ENV = {dev: true} 27 | 28 | function main(args) { 29 | let cmds = args.slice(2).filter((cmd) => { 30 | if (cmd == '--prod') { 31 | ENV.dev = false 32 | return false 33 | } 34 | 35 | return true 36 | }) 37 | 38 | if (cmds.length) { 39 | cmds.forEach((cmd) => { 40 | if (cmd.endsWith('.less')) { 41 | less('src/styles/charted.less', 'out/client/charted.css') 42 | } 43 | 44 | if (cmd.endsWith('.js')) { 45 | if (cmd.startsWith('client')) { 46 | transpile(`src/${cmd}`, 'client') 47 | } 48 | 49 | if (cmd.startsWith('server')) { 50 | transpile(`src/${cmd}`, 'server') 51 | } 52 | 53 | if (cmd.startsWith('shared')) { 54 | transpile(`src/${cmd}`, 'client') 55 | transpile(`src/${cmd}`, 'server') 56 | } 57 | } 58 | 59 | if (cmd.startsWith('public')) { 60 | let dest = cmd.replace('public/', '') 61 | cp('-rf', `src/${cmd}`, `out/client/${dest}`) 62 | } 63 | }) 64 | 65 | return 66 | } 67 | 68 | // We're recompiling the whole project so we need 69 | // to delete the existing build. 70 | echo('Removing old build') 71 | rm('-r', 'out') 72 | 73 | echo('Creating necessary directories') 74 | mkdir('-p', 'out/server') 75 | mkdir('-p', 'out/shared') 76 | mkdir('-p', 'out/client/scripts') 77 | mkdir('-p', 'out/client/shared') 78 | mkdir('-p', 'out/client/lib') 79 | mkdir('-p', 'out/templates') 80 | 81 | // Copy src/public, lib/client, and src/templates without any transformation. 82 | echo('Copying src/public and lib/client') 83 | cp('-r', 'src/public/*', 'out/client') 84 | cp('-r', 'lib/client/*', 'out/client/lib') 85 | cp('-r', 'src/templates/*', 'out/templates') 86 | 87 | // Compile LESS 88 | echo('Compiling LESS') 89 | less('src/styles/charted.less', 'out/client/charted.css') 90 | 91 | // Compile client-side JavaScript 92 | ls('-R', 'src/client/*.js').forEach((file) => { 93 | echo(`Compiling ${file}`) 94 | transpile(file, 'client') 95 | }) 96 | 97 | // Compile server-side JavaScript 98 | ls('-R', 'src/server/*.js').forEach((file) => { 99 | echo(`Compiling ${file}`) 100 | transpile(file, 'server') 101 | }) 102 | 103 | // Compile shared JavaScript 104 | ls('-R', 'src/shared/*.js').forEach((file) => { 105 | echo(`Compiling ${file}`) 106 | transpile(file, 'client') 107 | transpile(file, 'server', true) 108 | }) 109 | 110 | // Compile JavaScript for production (if needed) 111 | if (!ENV.dev) { 112 | echo(`Optimizing JavaScript`) 113 | uglify.minify([ 114 | 'out/client/lib/d3.js', 115 | ]).code.to('out/client/lib.js') 116 | 117 | requirejs.optimize({ 118 | baseUrl: 'out/client', 119 | name: 'scripts/charted', 120 | out: 'out/client/charted.js', 121 | include: 'requireLib', 122 | paths: { 123 | requireLib: 'lib/require' 124 | } 125 | }) 126 | } 127 | } 128 | 129 | function less(src, dest) { 130 | let lessc = path.join(__dirname, '..', 'node_modules', '.bin', 'lessc') 131 | exec(`${lessc} ${src} ${dest}`) 132 | } 133 | 134 | function transpile(file, type, isShared) { 135 | let name = file 136 | .replace('src/client/', 'scripts/') 137 | .replace('src/server/', '') 138 | .replace('src/', '') 139 | 140 | let code = cat(file) 141 | let dest = type == 'client' ? 'out/client' : isShared ? 'out' : 'out/server' 142 | let options = { 143 | filename: name, 144 | presets: BABEL_PRESETS, 145 | plugins: type == 'client' ? BABEL_PLUGINS.client : BABEL_PLUGINS.server 146 | } 147 | 148 | let res = babel.transform(code, options) 149 | res.code.to(`${dest}/${name}`) 150 | } 151 | 152 | // Get to the root of the project 153 | while (!test('-f', 'package.json')) { cd('..') } 154 | main(process.argv) 155 | -------------------------------------------------------------------------------- /bin/cleanup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | if hash watchman 2> /dev/null; then 4 | watchman trigger-del $PWD build 5 | watchman watch-del-all 6 | watchman shutdown-server 7 | fi 8 | 9 | if hash flow 2> /dev/null; then 10 | flow stop 11 | fi 12 | 13 | rm -r $PWD/out 14 | -------------------------------------------------------------------------------- /bin/watch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # If watchman is installed, use it to watch and re-compile files. 4 | # You can install watchman by running `brew install watchman`. 5 | # See more: https://facebook.github.io/watchman/ 6 | if hash watchman 2> /dev/null; then 7 | watchman watch $PWD 8 | watchman -- trigger $PWD/src build -- npm run-script prestart 9 | fi 10 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-cli_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: ee9f3fe21ed802eb45c7ac68a8433679 2 | // flow-typed version: <>/babel-cli_v6.3.x/flow_v0.37.4 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-cli' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-cli' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-cli/bin/babel-doctor' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'babel-cli/bin/babel-external-helpers' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'babel-cli/bin/babel-node' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'babel-cli/bin/babel' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'babel-cli/lib/_babel-node' { 42 | declare module.exports: any; 43 | } 44 | 45 | declare module 'babel-cli/lib/babel-doctor/index' { 46 | declare module.exports: any; 47 | } 48 | 49 | declare module 'babel-cli/lib/babel-doctor/rules/deduped' { 50 | declare module.exports: any; 51 | } 52 | 53 | declare module 'babel-cli/lib/babel-doctor/rules/has-config' { 54 | declare module.exports: any; 55 | } 56 | 57 | declare module 'babel-cli/lib/babel-doctor/rules/index' { 58 | declare module.exports: any; 59 | } 60 | 61 | declare module 'babel-cli/lib/babel-doctor/rules/latest-packages' { 62 | declare module.exports: any; 63 | } 64 | 65 | declare module 'babel-cli/lib/babel-doctor/rules/npm-3' { 66 | declare module.exports: any; 67 | } 68 | 69 | declare module 'babel-cli/lib/babel-external-helpers' { 70 | declare module.exports: any; 71 | } 72 | 73 | declare module 'babel-cli/lib/babel-node' { 74 | declare module.exports: any; 75 | } 76 | 77 | declare module 'babel-cli/lib/babel/dir' { 78 | declare module.exports: any; 79 | } 80 | 81 | declare module 'babel-cli/lib/babel/file' { 82 | declare module.exports: any; 83 | } 84 | 85 | declare module 'babel-cli/lib/babel/index' { 86 | declare module.exports: any; 87 | } 88 | 89 | declare module 'babel-cli/lib/babel/util' { 90 | declare module.exports: any; 91 | } 92 | 93 | // Filename aliases 94 | declare module 'babel-cli/bin/babel-doctor.js' { 95 | declare module.exports: $Exports<'babel-cli/bin/babel-doctor'>; 96 | } 97 | declare module 'babel-cli/bin/babel-external-helpers.js' { 98 | declare module.exports: $Exports<'babel-cli/bin/babel-external-helpers'>; 99 | } 100 | declare module 'babel-cli/bin/babel-node.js' { 101 | declare module.exports: $Exports<'babel-cli/bin/babel-node'>; 102 | } 103 | declare module 'babel-cli/bin/babel.js' { 104 | declare module.exports: $Exports<'babel-cli/bin/babel'>; 105 | } 106 | declare module 'babel-cli/index' { 107 | declare module.exports: $Exports<'babel-cli'>; 108 | } 109 | declare module 'babel-cli/index.js' { 110 | declare module.exports: $Exports<'babel-cli'>; 111 | } 112 | declare module 'babel-cli/lib/_babel-node.js' { 113 | declare module.exports: $Exports<'babel-cli/lib/_babel-node'>; 114 | } 115 | declare module 'babel-cli/lib/babel-doctor/index.js' { 116 | declare module.exports: $Exports<'babel-cli/lib/babel-doctor/index'>; 117 | } 118 | declare module 'babel-cli/lib/babel-doctor/rules/deduped.js' { 119 | declare module.exports: $Exports<'babel-cli/lib/babel-doctor/rules/deduped'>; 120 | } 121 | declare module 'babel-cli/lib/babel-doctor/rules/has-config.js' { 122 | declare module.exports: $Exports<'babel-cli/lib/babel-doctor/rules/has-config'>; 123 | } 124 | declare module 'babel-cli/lib/babel-doctor/rules/index.js' { 125 | declare module.exports: $Exports<'babel-cli/lib/babel-doctor/rules/index'>; 126 | } 127 | declare module 'babel-cli/lib/babel-doctor/rules/latest-packages.js' { 128 | declare module.exports: $Exports<'babel-cli/lib/babel-doctor/rules/latest-packages'>; 129 | } 130 | declare module 'babel-cli/lib/babel-doctor/rules/npm-3.js' { 131 | declare module.exports: $Exports<'babel-cli/lib/babel-doctor/rules/npm-3'>; 132 | } 133 | declare module 'babel-cli/lib/babel-external-helpers.js' { 134 | declare module.exports: $Exports<'babel-cli/lib/babel-external-helpers'>; 135 | } 136 | declare module 'babel-cli/lib/babel-node.js' { 137 | declare module.exports: $Exports<'babel-cli/lib/babel-node'>; 138 | } 139 | declare module 'babel-cli/lib/babel/dir.js' { 140 | declare module.exports: $Exports<'babel-cli/lib/babel/dir'>; 141 | } 142 | declare module 'babel-cli/lib/babel/file.js' { 143 | declare module.exports: $Exports<'babel-cli/lib/babel/file'>; 144 | } 145 | declare module 'babel-cli/lib/babel/index.js' { 146 | declare module.exports: $Exports<'babel-cli/lib/babel/index'>; 147 | } 148 | declare module 'babel-cli/lib/babel/util.js' { 149 | declare module.exports: $Exports<'babel-cli/lib/babel/util'>; 150 | } 151 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-core_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: e89f957802eabf069b040b120d17edee 2 | // flow-typed version: <>/babel-core_v6.3.x/flow_v0.37.4 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-core' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-core' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-core/lib/api/browser' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'babel-core/lib/api/node' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'babel-core/lib/helpers/merge' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'babel-core/lib/helpers/normalize-ast' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'babel-core/lib/helpers/resolve' { 42 | declare module.exports: any; 43 | } 44 | 45 | declare module 'babel-core/lib/store' { 46 | declare module.exports: any; 47 | } 48 | 49 | declare module 'babel-core/lib/tools/build-external-helpers' { 50 | declare module.exports: any; 51 | } 52 | 53 | declare module 'babel-core/lib/transformation/file/index' { 54 | declare module.exports: any; 55 | } 56 | 57 | declare module 'babel-core/lib/transformation/file/logger' { 58 | declare module.exports: any; 59 | } 60 | 61 | declare module 'babel-core/lib/transformation/file/metadata' { 62 | declare module.exports: any; 63 | } 64 | 65 | declare module 'babel-core/lib/transformation/file/options/config' { 66 | declare module.exports: any; 67 | } 68 | 69 | declare module 'babel-core/lib/transformation/file/options/index' { 70 | declare module.exports: any; 71 | } 72 | 73 | declare module 'babel-core/lib/transformation/file/options/option-manager' { 74 | declare module.exports: any; 75 | } 76 | 77 | declare module 'babel-core/lib/transformation/file/options/parsers' { 78 | declare module.exports: any; 79 | } 80 | 81 | declare module 'babel-core/lib/transformation/internal-plugins/block-hoist' { 82 | declare module.exports: any; 83 | } 84 | 85 | declare module 'babel-core/lib/transformation/internal-plugins/shadow-functions' { 86 | declare module.exports: any; 87 | } 88 | 89 | declare module 'babel-core/lib/transformation/pipeline' { 90 | declare module.exports: any; 91 | } 92 | 93 | declare module 'babel-core/lib/transformation/plugin-pass' { 94 | declare module.exports: any; 95 | } 96 | 97 | declare module 'babel-core/lib/transformation/plugin' { 98 | declare module.exports: any; 99 | } 100 | 101 | declare module 'babel-core/lib/util' { 102 | declare module.exports: any; 103 | } 104 | 105 | declare module 'babel-core/register' { 106 | declare module.exports: any; 107 | } 108 | 109 | // Filename aliases 110 | declare module 'babel-core/index' { 111 | declare module.exports: $Exports<'babel-core'>; 112 | } 113 | declare module 'babel-core/index.js' { 114 | declare module.exports: $Exports<'babel-core'>; 115 | } 116 | declare module 'babel-core/lib/api/browser.js' { 117 | declare module.exports: $Exports<'babel-core/lib/api/browser'>; 118 | } 119 | declare module 'babel-core/lib/api/node.js' { 120 | declare module.exports: $Exports<'babel-core/lib/api/node'>; 121 | } 122 | declare module 'babel-core/lib/helpers/merge.js' { 123 | declare module.exports: $Exports<'babel-core/lib/helpers/merge'>; 124 | } 125 | declare module 'babel-core/lib/helpers/normalize-ast.js' { 126 | declare module.exports: $Exports<'babel-core/lib/helpers/normalize-ast'>; 127 | } 128 | declare module 'babel-core/lib/helpers/resolve.js' { 129 | declare module.exports: $Exports<'babel-core/lib/helpers/resolve'>; 130 | } 131 | declare module 'babel-core/lib/store.js' { 132 | declare module.exports: $Exports<'babel-core/lib/store'>; 133 | } 134 | declare module 'babel-core/lib/tools/build-external-helpers.js' { 135 | declare module.exports: $Exports<'babel-core/lib/tools/build-external-helpers'>; 136 | } 137 | declare module 'babel-core/lib/transformation/file/index.js' { 138 | declare module.exports: $Exports<'babel-core/lib/transformation/file/index'>; 139 | } 140 | declare module 'babel-core/lib/transformation/file/logger.js' { 141 | declare module.exports: $Exports<'babel-core/lib/transformation/file/logger'>; 142 | } 143 | declare module 'babel-core/lib/transformation/file/metadata.js' { 144 | declare module.exports: $Exports<'babel-core/lib/transformation/file/metadata'>; 145 | } 146 | declare module 'babel-core/lib/transformation/file/options/config.js' { 147 | declare module.exports: $Exports<'babel-core/lib/transformation/file/options/config'>; 148 | } 149 | declare module 'babel-core/lib/transformation/file/options/index.js' { 150 | declare module.exports: $Exports<'babel-core/lib/transformation/file/options/index'>; 151 | } 152 | declare module 'babel-core/lib/transformation/file/options/option-manager.js' { 153 | declare module.exports: $Exports<'babel-core/lib/transformation/file/options/option-manager'>; 154 | } 155 | declare module 'babel-core/lib/transformation/file/options/parsers.js' { 156 | declare module.exports: $Exports<'babel-core/lib/transformation/file/options/parsers'>; 157 | } 158 | declare module 'babel-core/lib/transformation/internal-plugins/block-hoist.js' { 159 | declare module.exports: $Exports<'babel-core/lib/transformation/internal-plugins/block-hoist'>; 160 | } 161 | declare module 'babel-core/lib/transformation/internal-plugins/shadow-functions.js' { 162 | declare module.exports: $Exports<'babel-core/lib/transformation/internal-plugins/shadow-functions'>; 163 | } 164 | declare module 'babel-core/lib/transformation/pipeline.js' { 165 | declare module.exports: $Exports<'babel-core/lib/transformation/pipeline'>; 166 | } 167 | declare module 'babel-core/lib/transformation/plugin-pass.js' { 168 | declare module.exports: $Exports<'babel-core/lib/transformation/plugin-pass'>; 169 | } 170 | declare module 'babel-core/lib/transformation/plugin.js' { 171 | declare module.exports: $Exports<'babel-core/lib/transformation/plugin'>; 172 | } 173 | declare module 'babel-core/lib/util.js' { 174 | declare module.exports: $Exports<'babel-core/lib/util'>; 175 | } 176 | declare module 'babel-core/register.js' { 177 | declare module.exports: $Exports<'babel-core/register'>; 178 | } 179 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-plugin-transform-es2015-modules-amd_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 7a980a4d8ec87d8be65be3ce9a987319 2 | // flow-typed version: <>/babel-plugin-transform-es2015-modules-amd_v6.3.x/flow_v0.37.4 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-plugin-transform-es2015-modules-amd' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-plugin-transform-es2015-modules-amd' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-plugin-transform-es2015-modules-amd/lib/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'babel-plugin-transform-es2015-modules-amd/lib/index.js' { 31 | declare module.exports: $Exports<'babel-plugin-transform-es2015-modules-amd/lib/index'>; 32 | } 33 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-plugin-transform-flow-strip-types_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 5c196062d64f14a3da7e32ddec496ed2 2 | // flow-typed version: <>/babel-plugin-transform-flow-strip-types_v6.3.x/flow_v0.37.4 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-plugin-transform-flow-strip-types' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-plugin-transform-flow-strip-types' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-plugin-transform-flow-strip-types/lib/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'babel-plugin-transform-flow-strip-types/lib/index.js' { 31 | declare module.exports: $Exports<'babel-plugin-transform-flow-strip-types/lib/index'>; 32 | } 33 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-preset-es2015_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 9cee4ea5593fe1a042a360d3d5fc5ad1 2 | // flow-typed version: <>/babel-preset-es2015_v6.3.x/flow_v0.37.4 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-preset-es2015' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-preset-es2015' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | 26 | 27 | // Filename aliases 28 | declare module 'babel-preset-es2015/index' { 29 | declare module.exports: $Exports<'babel-preset-es2015'>; 30 | } 31 | declare module 'babel-preset-es2015/index.js' { 32 | declare module.exports: $Exports<'babel-preset-es2015'>; 33 | } 34 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-preset-stage-0_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 9a2617c76122da2097429b91bac6216a 2 | // flow-typed version: <>/babel-preset-stage-0_v6.3.x/flow_v0.37.4 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-preset-stage-0' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-preset-stage-0' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | 26 | 27 | // Filename aliases 28 | declare module 'babel-preset-stage-0/index' { 29 | declare module.exports: $Exports<'babel-preset-stage-0'>; 30 | } 31 | declare module 'babel-preset-stage-0/index.js' { 32 | declare module.exports: $Exports<'babel-preset-stage-0'>; 33 | } 34 | -------------------------------------------------------------------------------- /flow-typed/npm/babel_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 61e90ee26c0b56a560cf25a034b8a844 2 | // flow-typed version: <>/babel_v6.0.x/flow_v0.37.4 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel/cli' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'babel/cli.js' { 31 | declare module.exports: $Exports<'babel/cli'>; 32 | } 33 | declare module 'babel/index' { 34 | declare module.exports: $Exports<'babel'>; 35 | } 36 | declare module 'babel/index.js' { 37 | declare module.exports: $Exports<'babel'>; 38 | } 39 | -------------------------------------------------------------------------------- /flow-typed/npm/body-parser_v1.x.x.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charted-co/charted/fa9364cc71e18150623c86083a829465843b00de/flow-typed/npm/body-parser_v1.x.x.js -------------------------------------------------------------------------------- /flow-typed/npm/body-parser_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 3b3bfe2805ca0f8e12c0ba00139162a5 2 | // flow-typed version: <>/body-parser_v1.14.x 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'body-parser' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'body-parser' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'body-parser/lib/read' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'body-parser/lib/types/json' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'body-parser/lib/types/raw' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'body-parser/lib/types/text' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'body-parser/lib/types/urlencoded' { 42 | declare module.exports: any; 43 | } 44 | 45 | // Filename aliases 46 | declare module 'body-parser/index' { 47 | declare module.exports: $Exports<'body-parser'>; 48 | } 49 | declare module 'body-parser/index.js' { 50 | declare module.exports: $Exports<'body-parser'>; 51 | } 52 | declare module 'body-parser/lib/read.js' { 53 | declare module.exports: $Exports<'body-parser/lib/read'>; 54 | } 55 | declare module 'body-parser/lib/types/json.js' { 56 | declare module.exports: $Exports<'body-parser/lib/types/json'>; 57 | } 58 | declare module 'body-parser/lib/types/raw.js' { 59 | declare module.exports: $Exports<'body-parser/lib/types/raw'>; 60 | } 61 | declare module 'body-parser/lib/types/text.js' { 62 | declare module.exports: $Exports<'body-parser/lib/types/text'>; 63 | } 64 | declare module 'body-parser/lib/types/urlencoded.js' { 65 | declare module.exports: $Exports<'body-parser/lib/types/urlencoded'>; 66 | } 67 | -------------------------------------------------------------------------------- /flow-typed/npm/connect_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: d1514155f636d9288c9cf68d57d0f8e3 2 | // flow-typed version: <>/connect_v^3.3.5/flow_v0.37.4 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'connect' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'connect' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | 26 | 27 | // Filename aliases 28 | declare module 'connect/index' { 29 | declare module.exports: $Exports<'connect'>; 30 | } 31 | declare module 'connect/index.js' { 32 | declare module.exports: $Exports<'connect'>; 33 | } 34 | -------------------------------------------------------------------------------- /flow-typed/npm/express_v4.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 730b5b5f47f89c6e5871c0d9e9cd72d9 2 | // flow-typed version: 8d3bb346ba/express_v4.x.x/flow_>=v0.32.x 3 | 4 | import type { Server } from 'http'; 5 | import type { Socket } from 'net'; 6 | 7 | declare type express$RouterOptions = { 8 | caseSensitive?: boolean, 9 | mergeParams?: boolean, 10 | strict?: boolean 11 | }; 12 | 13 | declare class express$RequestResponseBase { 14 | app: express$Application; 15 | get(field: string): string | void; 16 | } 17 | 18 | declare type express$RequestParams = { 19 | [param: string]: string 20 | } 21 | 22 | declare class express$Request extends http$IncomingMessage mixins express$RequestResponseBase { 23 | baseUrl: string; 24 | body: any; 25 | cookies: {[cookie: string]: string}; 26 | connection: Socket; 27 | fresh: boolean; 28 | hostname: string; 29 | ip: string; 30 | ips: Array; 31 | method: string; 32 | originalUrl: string; 33 | params: express$RequestParams; 34 | path: string; 35 | protocol: 'https' | 'http'; 36 | query: {[name: string]: string | Array}; 37 | route: string; 38 | secure: boolean; 39 | signedCookies: {[signedCookie: string]: string}; 40 | stale: boolean; 41 | subdomains: Array; 42 | xhr: boolean; 43 | accepts(types: string): string | false; 44 | accepts(types: Array): string | false; 45 | acceptsCharsets(...charsets: Array): string | false; 46 | acceptsEncodings(...encoding: Array): string | false; 47 | acceptsLanguages(...lang: Array): string | false; 48 | header(field: string): string | void; 49 | is(type: string): boolean; 50 | param(name: string, defaultValue?: string): string | void; 51 | } 52 | 53 | declare type express$CookieOptions = { 54 | domain?: string, 55 | encode?: (value: string) => string, 56 | expires?: Date, 57 | httpOnly?: boolean, 58 | maxAge?: number, 59 | path?: string, 60 | secure?: boolean, 61 | signed?: boolean 62 | }; 63 | 64 | declare type express$RenderCallback = (err: Error | null, html?: string) => mixed; 65 | 66 | declare type express$SendFileOptions = { 67 | maxAge?: number, 68 | root?: string, 69 | lastModified?: boolean, 70 | headers?: {[name: string]: string}, 71 | dotfiles?: 'allow' | 'deny' | 'ignore' 72 | }; 73 | 74 | declare class express$Response extends http$ServerResponse mixins express$RequestResponseBase { 75 | headersSent: boolean; 76 | locals: {[name: string]: mixed}; 77 | append(field: string, value?: string): this; 78 | attachment(filename?: string): this; 79 | cookie(name: string, value: string, options?: express$CookieOptions): this; 80 | clearCookie(name: string, options?: express$CookieOptions): this; 81 | download(path: string, filename?: string, callback?: (err?: ?Error) => void): this; 82 | format(typesObject: {[type: string]: Function}): this; 83 | json(body?: mixed): this; 84 | jsonp(body?: mixed): this; 85 | links(links: {[name: string]: string}): this; 86 | location(path: string): this; 87 | redirect(url: string, ...args: Array): this; 88 | redirect(status: number, url: string, ...args: Array): this; 89 | render(view: string, locals?: {[name: string]: mixed}, callback?: express$RenderCallback): this; 90 | send(body?: mixed): this; 91 | sendFile(path: string, options?: express$SendFileOptions, callback?: (err?: ?Error) => mixed): this; 92 | sendStatus(statusCode: number): this; 93 | header(field: string, value?: string): this; 94 | header(headers: {[name: string]: string}): this; 95 | set(field: string, value?: string|string[]): this; 96 | set(headers: {[name: string]: string}): this; 97 | status(statusCode: number): this; 98 | type(type: string): this; 99 | vary(field: string): this; 100 | req: express$Request; 101 | } 102 | 103 | declare type express$NextFunction = (err?: ?Error | 'route') => mixed; 104 | declare type express$Middleware = 105 | ((req: $Subtype, res: express$Response, next: express$NextFunction) => mixed) | 106 | ((error: ?Error, req: $Subtype, res: express$Response, next: express$NextFunction) => mixed); 107 | declare interface express$RouteMethodType { 108 | (middleware: express$Middleware): T; 109 | (...middleware: Array): T; 110 | (path: string|RegExp|string[], ...middleware: Array): T; 111 | } 112 | declare class express$Route { 113 | all: express$RouteMethodType; 114 | get: express$RouteMethodType; 115 | post: express$RouteMethodType; 116 | put: express$RouteMethodType; 117 | head: express$RouteMethodType; 118 | delete: express$RouteMethodType; 119 | options: express$RouteMethodType; 120 | trace: express$RouteMethodType; 121 | copy: express$RouteMethodType; 122 | lock: express$RouteMethodType; 123 | mkcol: express$RouteMethodType; 124 | move: express$RouteMethodType; 125 | purge: express$RouteMethodType; 126 | propfind: express$RouteMethodType; 127 | proppatch: express$RouteMethodType; 128 | unlock: express$RouteMethodType; 129 | report: express$RouteMethodType; 130 | mkactivity: express$RouteMethodType; 131 | checkout: express$RouteMethodType; 132 | merge: express$RouteMethodType; 133 | 134 | // @TODO Missing 'm-search' but get flow illegal name error. 135 | 136 | notify: express$RouteMethodType; 137 | subscribe: express$RouteMethodType; 138 | unsubscribe: express$RouteMethodType; 139 | patch: express$RouteMethodType; 140 | search: express$RouteMethodType; 141 | connect: express$RouteMethodType; 142 | } 143 | 144 | declare class express$Router extends express$Route { 145 | constructor(options?: express$RouterOptions): void; 146 | route(path: string): express$Route; 147 | static (options?: express$RouterOptions): express$Router; 148 | use(middleware: express$Middleware): this; 149 | use(...middleware: Array): this; 150 | use(path: string|RegExp|string[], ...middleware: Array): this; 151 | use(path: string, router: express$Router): this; 152 | handle(req: http$IncomingMessage, res: http$ServerResponse, next: express$NextFunction): void; 153 | 154 | // Can't use regular callable signature syntax due to https://github.com/facebook/flow/issues/3084 155 | $call: (req: http$IncomingMessage, res: http$ServerResponse, next?: ?express$NextFunction) => void; 156 | } 157 | 158 | declare class express$Application extends express$Router mixins events$EventEmitter { 159 | constructor(): void; 160 | locals: {[name: string]: mixed}; 161 | mountpath: string; 162 | listen(port: number, hostname?: string, backlog?: number, callback?: (err?: ?Error) => mixed): Server; 163 | listen(port: number, hostname?: string, callback?: (err?: ?Error) => mixed): Server; 164 | listen(port: number, callback?: (err?: ?Error) => mixed): Server; 165 | listen(path: string, callback?: (err?: ?Error) => mixed): Server; 166 | listen(handle: Object, callback?: (err?: ?Error) => mixed): Server; 167 | disable(name: string): void; 168 | disabled(name: string): boolean; 169 | enable(name: string): express$Application; 170 | enabled(name: string): boolean; 171 | engine(name: string, callback: Function): void; 172 | /** 173 | * Mixed will not be taken as a value option. Issue around using the GET http method name and the get for settings. 174 | */ 175 | // get(name: string): mixed; 176 | set(name: string, value: mixed): mixed; 177 | render(name: string, optionsOrFunction: {[name: string]: mixed}, callback: express$RenderCallback): void; 178 | handle(req: http$IncomingMessage, res: http$ServerResponse, next?: ?express$NextFunction): void; 179 | } 180 | 181 | declare module 'express' { 182 | declare export type RouterOptions = express$RouterOptions; 183 | declare export type CookieOptions = express$CookieOptions; 184 | declare export type Middleware = express$Middleware; 185 | declare export type NextFunction = express$NextFunction; 186 | declare export type RequestParams = express$RequestParams; 187 | declare export type $Response = express$Response; 188 | declare export type $Request = express$Request; 189 | declare export type $Application = express$Application; 190 | 191 | declare module.exports: { 192 | (): express$Application, // If you try to call like a function, it will use this signature 193 | static: (root: string, options?: Object) => express$Middleware, // `static` property on the function 194 | Router: typeof express$Router, // `Router` property on the function 195 | }; 196 | } 197 | -------------------------------------------------------------------------------- /flow-typed/npm/flow-bin_v0.x.x.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charted-co/charted/fa9364cc71e18150623c86083a829465843b00de/flow-typed/npm/flow-bin_v0.x.x.js -------------------------------------------------------------------------------- /flow-typed/npm/nodeunit_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 417e5453d28d2c851ef2f2df3c6bfb44 2 | // flow-typed version: <>/nodeunit_v0.9.x/flow_v0.37.4 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'nodeunit' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'nodeunit' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'nodeunit/deps/async' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'nodeunit/deps/console.log' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'nodeunit/deps/ejs/benchmark' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'nodeunit/deps/ejs/ejs' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'nodeunit/deps/ejs/ejs.min' { 42 | declare module.exports: any; 43 | } 44 | 45 | declare module 'nodeunit/deps/ejs/examples/list' { 46 | declare module.exports: any; 47 | } 48 | 49 | declare module 'nodeunit/deps/ejs/index' { 50 | declare module.exports: any; 51 | } 52 | 53 | declare module 'nodeunit/deps/ejs/lib/ejs' { 54 | declare module.exports: any; 55 | } 56 | 57 | declare module 'nodeunit/deps/ejs/lib/filters' { 58 | declare module.exports: any; 59 | } 60 | 61 | declare module 'nodeunit/deps/ejs/lib/utils' { 62 | declare module.exports: any; 63 | } 64 | 65 | declare module 'nodeunit/deps/ejs/support/compile' { 66 | declare module.exports: any; 67 | } 68 | 69 | declare module 'nodeunit/deps/ejs/test/ejs.test' { 70 | declare module.exports: any; 71 | } 72 | 73 | declare module 'nodeunit/deps/json2' { 74 | declare module.exports: any; 75 | } 76 | 77 | declare module 'nodeunit/examples/browser/nodeunit' { 78 | declare module.exports: any; 79 | } 80 | 81 | declare module 'nodeunit/examples/browser/suite1' { 82 | declare module.exports: any; 83 | } 84 | 85 | declare module 'nodeunit/examples/browser/suite2' { 86 | declare module.exports: any; 87 | } 88 | 89 | declare module 'nodeunit/examples/browser/suite3' { 90 | declare module.exports: any; 91 | } 92 | 93 | declare module 'nodeunit/examples/nested/nested_reporter_test.unit' { 94 | declare module.exports: any; 95 | } 96 | 97 | declare module 'nodeunit/lib/assert' { 98 | declare module.exports: any; 99 | } 100 | 101 | declare module 'nodeunit/lib/core' { 102 | declare module.exports: any; 103 | } 104 | 105 | declare module 'nodeunit/lib/nodeunit' { 106 | declare module.exports: any; 107 | } 108 | 109 | declare module 'nodeunit/lib/reporters/browser' { 110 | declare module.exports: any; 111 | } 112 | 113 | declare module 'nodeunit/lib/reporters/default' { 114 | declare module.exports: any; 115 | } 116 | 117 | declare module 'nodeunit/lib/reporters/eclipse' { 118 | declare module.exports: any; 119 | } 120 | 121 | declare module 'nodeunit/lib/reporters/html' { 122 | declare module.exports: any; 123 | } 124 | 125 | declare module 'nodeunit/lib/reporters/index' { 126 | declare module.exports: any; 127 | } 128 | 129 | declare module 'nodeunit/lib/reporters/junit' { 130 | declare module.exports: any; 131 | } 132 | 133 | declare module 'nodeunit/lib/reporters/lcov' { 134 | declare module.exports: any; 135 | } 136 | 137 | declare module 'nodeunit/lib/reporters/machineout' { 138 | declare module.exports: any; 139 | } 140 | 141 | declare module 'nodeunit/lib/reporters/minimal' { 142 | declare module.exports: any; 143 | } 144 | 145 | declare module 'nodeunit/lib/reporters/nested' { 146 | declare module.exports: any; 147 | } 148 | 149 | declare module 'nodeunit/lib/reporters/skip_passed' { 150 | declare module.exports: any; 151 | } 152 | 153 | declare module 'nodeunit/lib/reporters/tap' { 154 | declare module.exports: any; 155 | } 156 | 157 | declare module 'nodeunit/lib/reporters/verbose' { 158 | declare module.exports: any; 159 | } 160 | 161 | declare module 'nodeunit/lib/track' { 162 | declare module.exports: any; 163 | } 164 | 165 | declare module 'nodeunit/lib/types' { 166 | declare module.exports: any; 167 | } 168 | 169 | declare module 'nodeunit/lib/utils' { 170 | declare module.exports: any; 171 | } 172 | 173 | declare module 'nodeunit/share/license' { 174 | declare module.exports: any; 175 | } 176 | 177 | declare module 'nodeunit/test/fixtures/dir/example_test_sub' { 178 | declare module.exports: any; 179 | } 180 | 181 | declare module 'nodeunit/test/fixtures/dir/mock_module3' { 182 | declare module.exports: any; 183 | } 184 | 185 | declare module 'nodeunit/test/fixtures/dir/mock_module4' { 186 | declare module.exports: any; 187 | } 188 | 189 | declare module 'nodeunit/test/fixtures/example_test' { 190 | declare module.exports: any; 191 | } 192 | 193 | declare module 'nodeunit/test/fixtures/mock_module1' { 194 | declare module.exports: any; 195 | } 196 | 197 | declare module 'nodeunit/test/fixtures/mock_module2' { 198 | declare module.exports: any; 199 | } 200 | 201 | declare module 'nodeunit/test/fixtures/raw_jscode1' { 202 | declare module.exports: any; 203 | } 204 | 205 | declare module 'nodeunit/test/fixtures/raw_jscode2' { 206 | declare module.exports: any; 207 | } 208 | 209 | declare module 'nodeunit/test/fixtures/raw_jscode3' { 210 | declare module.exports: any; 211 | } 212 | 213 | declare module 'nodeunit/test/test-base' { 214 | declare module.exports: any; 215 | } 216 | 217 | declare module 'nodeunit/test/test-bettererrors' { 218 | declare module.exports: any; 219 | } 220 | 221 | declare module 'nodeunit/test/test-cli' { 222 | declare module.exports: any; 223 | } 224 | 225 | declare module 'nodeunit/test/test-failing-callbacks' { 226 | declare module.exports: any; 227 | } 228 | 229 | declare module 'nodeunit/test/test-httputil' { 230 | declare module.exports: any; 231 | } 232 | 233 | declare module 'nodeunit/test/test-runfiles' { 234 | declare module.exports: any; 235 | } 236 | 237 | declare module 'nodeunit/test/test-runmodule' { 238 | declare module.exports: any; 239 | } 240 | 241 | declare module 'nodeunit/test/test-runtest' { 242 | declare module.exports: any; 243 | } 244 | 245 | declare module 'nodeunit/test/test-sandbox' { 246 | declare module.exports: any; 247 | } 248 | 249 | declare module 'nodeunit/test/test-testcase-legacy' { 250 | declare module.exports: any; 251 | } 252 | 253 | declare module 'nodeunit/test/test-testcase' { 254 | declare module.exports: any; 255 | } 256 | 257 | // Filename aliases 258 | declare module 'nodeunit/deps/async.js' { 259 | declare module.exports: $Exports<'nodeunit/deps/async'>; 260 | } 261 | declare module 'nodeunit/deps/console.log.js' { 262 | declare module.exports: $Exports<'nodeunit/deps/console.log'>; 263 | } 264 | declare module 'nodeunit/deps/ejs/benchmark.js' { 265 | declare module.exports: $Exports<'nodeunit/deps/ejs/benchmark'>; 266 | } 267 | declare module 'nodeunit/deps/ejs/ejs.js' { 268 | declare module.exports: $Exports<'nodeunit/deps/ejs/ejs'>; 269 | } 270 | declare module 'nodeunit/deps/ejs/ejs.min.js' { 271 | declare module.exports: $Exports<'nodeunit/deps/ejs/ejs.min'>; 272 | } 273 | declare module 'nodeunit/deps/ejs/examples/list.js' { 274 | declare module.exports: $Exports<'nodeunit/deps/ejs/examples/list'>; 275 | } 276 | declare module 'nodeunit/deps/ejs/index.js' { 277 | declare module.exports: $Exports<'nodeunit/deps/ejs/index'>; 278 | } 279 | declare module 'nodeunit/deps/ejs/lib/ejs.js' { 280 | declare module.exports: $Exports<'nodeunit/deps/ejs/lib/ejs'>; 281 | } 282 | declare module 'nodeunit/deps/ejs/lib/filters.js' { 283 | declare module.exports: $Exports<'nodeunit/deps/ejs/lib/filters'>; 284 | } 285 | declare module 'nodeunit/deps/ejs/lib/utils.js' { 286 | declare module.exports: $Exports<'nodeunit/deps/ejs/lib/utils'>; 287 | } 288 | declare module 'nodeunit/deps/ejs/support/compile.js' { 289 | declare module.exports: $Exports<'nodeunit/deps/ejs/support/compile'>; 290 | } 291 | declare module 'nodeunit/deps/ejs/test/ejs.test.js' { 292 | declare module.exports: $Exports<'nodeunit/deps/ejs/test/ejs.test'>; 293 | } 294 | declare module 'nodeunit/deps/json2.js' { 295 | declare module.exports: $Exports<'nodeunit/deps/json2'>; 296 | } 297 | declare module 'nodeunit/examples/browser/nodeunit.js' { 298 | declare module.exports: $Exports<'nodeunit/examples/browser/nodeunit'>; 299 | } 300 | declare module 'nodeunit/examples/browser/suite1.js' { 301 | declare module.exports: $Exports<'nodeunit/examples/browser/suite1'>; 302 | } 303 | declare module 'nodeunit/examples/browser/suite2.js' { 304 | declare module.exports: $Exports<'nodeunit/examples/browser/suite2'>; 305 | } 306 | declare module 'nodeunit/examples/browser/suite3.js' { 307 | declare module.exports: $Exports<'nodeunit/examples/browser/suite3'>; 308 | } 309 | declare module 'nodeunit/examples/nested/nested_reporter_test.unit.js' { 310 | declare module.exports: $Exports<'nodeunit/examples/nested/nested_reporter_test.unit'>; 311 | } 312 | declare module 'nodeunit/index' { 313 | declare module.exports: $Exports<'nodeunit'>; 314 | } 315 | declare module 'nodeunit/index.js' { 316 | declare module.exports: $Exports<'nodeunit'>; 317 | } 318 | declare module 'nodeunit/lib/assert.js' { 319 | declare module.exports: $Exports<'nodeunit/lib/assert'>; 320 | } 321 | declare module 'nodeunit/lib/core.js' { 322 | declare module.exports: $Exports<'nodeunit/lib/core'>; 323 | } 324 | declare module 'nodeunit/lib/nodeunit.js' { 325 | declare module.exports: $Exports<'nodeunit/lib/nodeunit'>; 326 | } 327 | declare module 'nodeunit/lib/reporters/browser.js' { 328 | declare module.exports: $Exports<'nodeunit/lib/reporters/browser'>; 329 | } 330 | declare module 'nodeunit/lib/reporters/default.js' { 331 | declare module.exports: $Exports<'nodeunit/lib/reporters/default'>; 332 | } 333 | declare module 'nodeunit/lib/reporters/eclipse.js' { 334 | declare module.exports: $Exports<'nodeunit/lib/reporters/eclipse'>; 335 | } 336 | declare module 'nodeunit/lib/reporters/html.js' { 337 | declare module.exports: $Exports<'nodeunit/lib/reporters/html'>; 338 | } 339 | declare module 'nodeunit/lib/reporters/index.js' { 340 | declare module.exports: $Exports<'nodeunit/lib/reporters/index'>; 341 | } 342 | declare module 'nodeunit/lib/reporters/junit.js' { 343 | declare module.exports: $Exports<'nodeunit/lib/reporters/junit'>; 344 | } 345 | declare module 'nodeunit/lib/reporters/lcov.js' { 346 | declare module.exports: $Exports<'nodeunit/lib/reporters/lcov'>; 347 | } 348 | declare module 'nodeunit/lib/reporters/machineout.js' { 349 | declare module.exports: $Exports<'nodeunit/lib/reporters/machineout'>; 350 | } 351 | declare module 'nodeunit/lib/reporters/minimal.js' { 352 | declare module.exports: $Exports<'nodeunit/lib/reporters/minimal'>; 353 | } 354 | declare module 'nodeunit/lib/reporters/nested.js' { 355 | declare module.exports: $Exports<'nodeunit/lib/reporters/nested'>; 356 | } 357 | declare module 'nodeunit/lib/reporters/skip_passed.js' { 358 | declare module.exports: $Exports<'nodeunit/lib/reporters/skip_passed'>; 359 | } 360 | declare module 'nodeunit/lib/reporters/tap.js' { 361 | declare module.exports: $Exports<'nodeunit/lib/reporters/tap'>; 362 | } 363 | declare module 'nodeunit/lib/reporters/verbose.js' { 364 | declare module.exports: $Exports<'nodeunit/lib/reporters/verbose'>; 365 | } 366 | declare module 'nodeunit/lib/track.js' { 367 | declare module.exports: $Exports<'nodeunit/lib/track'>; 368 | } 369 | declare module 'nodeunit/lib/types.js' { 370 | declare module.exports: $Exports<'nodeunit/lib/types'>; 371 | } 372 | declare module 'nodeunit/lib/utils.js' { 373 | declare module.exports: $Exports<'nodeunit/lib/utils'>; 374 | } 375 | declare module 'nodeunit/share/license.js' { 376 | declare module.exports: $Exports<'nodeunit/share/license'>; 377 | } 378 | declare module 'nodeunit/test/fixtures/dir/example_test_sub.js' { 379 | declare module.exports: $Exports<'nodeunit/test/fixtures/dir/example_test_sub'>; 380 | } 381 | declare module 'nodeunit/test/fixtures/dir/mock_module3.js' { 382 | declare module.exports: $Exports<'nodeunit/test/fixtures/dir/mock_module3'>; 383 | } 384 | declare module 'nodeunit/test/fixtures/dir/mock_module4.js' { 385 | declare module.exports: $Exports<'nodeunit/test/fixtures/dir/mock_module4'>; 386 | } 387 | declare module 'nodeunit/test/fixtures/example_test.js' { 388 | declare module.exports: $Exports<'nodeunit/test/fixtures/example_test'>; 389 | } 390 | declare module 'nodeunit/test/fixtures/mock_module1.js' { 391 | declare module.exports: $Exports<'nodeunit/test/fixtures/mock_module1'>; 392 | } 393 | declare module 'nodeunit/test/fixtures/mock_module2.js' { 394 | declare module.exports: $Exports<'nodeunit/test/fixtures/mock_module2'>; 395 | } 396 | declare module 'nodeunit/test/fixtures/raw_jscode1.js' { 397 | declare module.exports: $Exports<'nodeunit/test/fixtures/raw_jscode1'>; 398 | } 399 | declare module 'nodeunit/test/fixtures/raw_jscode2.js' { 400 | declare module.exports: $Exports<'nodeunit/test/fixtures/raw_jscode2'>; 401 | } 402 | declare module 'nodeunit/test/fixtures/raw_jscode3.js' { 403 | declare module.exports: $Exports<'nodeunit/test/fixtures/raw_jscode3'>; 404 | } 405 | declare module 'nodeunit/test/test-base.js' { 406 | declare module.exports: $Exports<'nodeunit/test/test-base'>; 407 | } 408 | declare module 'nodeunit/test/test-bettererrors.js' { 409 | declare module.exports: $Exports<'nodeunit/test/test-bettererrors'>; 410 | } 411 | declare module 'nodeunit/test/test-cli.js' { 412 | declare module.exports: $Exports<'nodeunit/test/test-cli'>; 413 | } 414 | declare module 'nodeunit/test/test-failing-callbacks.js' { 415 | declare module.exports: $Exports<'nodeunit/test/test-failing-callbacks'>; 416 | } 417 | declare module 'nodeunit/test/test-httputil.js' { 418 | declare module.exports: $Exports<'nodeunit/test/test-httputil'>; 419 | } 420 | declare module 'nodeunit/test/test-runfiles.js' { 421 | declare module.exports: $Exports<'nodeunit/test/test-runfiles'>; 422 | } 423 | declare module 'nodeunit/test/test-runmodule.js' { 424 | declare module.exports: $Exports<'nodeunit/test/test-runmodule'>; 425 | } 426 | declare module 'nodeunit/test/test-runtest.js' { 427 | declare module.exports: $Exports<'nodeunit/test/test-runtest'>; 428 | } 429 | declare module 'nodeunit/test/test-sandbox.js' { 430 | declare module.exports: $Exports<'nodeunit/test/test-sandbox'>; 431 | } 432 | declare module 'nodeunit/test/test-testcase-legacy.js' { 433 | declare module.exports: $Exports<'nodeunit/test/test-testcase-legacy'>; 434 | } 435 | declare module 'nodeunit/test/test-testcase.js' { 436 | declare module.exports: $Exports<'nodeunit/test/test-testcase'>; 437 | } 438 | -------------------------------------------------------------------------------- /flow-typed/npm/request_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: a59a5deecdeee9a353b420aeb76638e4 2 | // flow-typed version: <>/request_v2.47.x/flow_v0.37.4 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'request' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'request' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'request/lib/cookies' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'request/lib/copy' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'request/lib/debug' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'request/lib/helpers' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'request/request' { 42 | declare module.exports: any; 43 | } 44 | 45 | // Filename aliases 46 | declare module 'request/index' { 47 | declare module.exports: $Exports<'request'>; 48 | } 49 | declare module 'request/index.js' { 50 | declare module.exports: $Exports<'request'>; 51 | } 52 | declare module 'request/lib/cookies.js' { 53 | declare module.exports: $Exports<'request/lib/cookies'>; 54 | } 55 | declare module 'request/lib/copy.js' { 56 | declare module.exports: $Exports<'request/lib/copy'>; 57 | } 58 | declare module 'request/lib/debug.js' { 59 | declare module.exports: $Exports<'request/lib/debug'>; 60 | } 61 | declare module 'request/lib/helpers.js' { 62 | declare module.exports: $Exports<'request/lib/helpers'>; 63 | } 64 | declare module 'request/request.js' { 65 | declare module.exports: $Exports<'request/request'>; 66 | } 67 | -------------------------------------------------------------------------------- /flow-typed/npm/requirejs_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: f2eccad2115478aa5c08385f024dfe50 2 | // flow-typed version: <>/requirejs_v^2.1.22/flow_v0.37.4 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'requirejs' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'requirejs' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'requirejs/bin/r' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'requirejs/require' { 30 | declare module.exports: any; 31 | } 32 | 33 | // Filename aliases 34 | declare module 'requirejs/bin/r.js' { 35 | declare module.exports: $Exports<'requirejs/bin/r'>; 36 | } 37 | declare module 'requirejs/require.js' { 38 | declare module.exports: $Exports<'requirejs/require'>; 39 | } 40 | -------------------------------------------------------------------------------- /flow-typed/npm/shelljs_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: f55652efbbaac1126e8ff998071132c3 2 | // flow-typed version: <>/shelljs_v0.5.x/flow_v0.37.4 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'shelljs' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'shelljs' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'shelljs/global' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'shelljs/make' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'shelljs/scripts/generate-docs' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'shelljs/scripts/run-tests' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'shelljs/shell' { 42 | declare module.exports: any; 43 | } 44 | 45 | declare module 'shelljs/src/cat' { 46 | declare module.exports: any; 47 | } 48 | 49 | declare module 'shelljs/src/cd' { 50 | declare module.exports: any; 51 | } 52 | 53 | declare module 'shelljs/src/chmod' { 54 | declare module.exports: any; 55 | } 56 | 57 | declare module 'shelljs/src/common' { 58 | declare module.exports: any; 59 | } 60 | 61 | declare module 'shelljs/src/cp' { 62 | declare module.exports: any; 63 | } 64 | 65 | declare module 'shelljs/src/dirs' { 66 | declare module.exports: any; 67 | } 68 | 69 | declare module 'shelljs/src/echo' { 70 | declare module.exports: any; 71 | } 72 | 73 | declare module 'shelljs/src/error' { 74 | declare module.exports: any; 75 | } 76 | 77 | declare module 'shelljs/src/exec' { 78 | declare module.exports: any; 79 | } 80 | 81 | declare module 'shelljs/src/find' { 82 | declare module.exports: any; 83 | } 84 | 85 | declare module 'shelljs/src/grep' { 86 | declare module.exports: any; 87 | } 88 | 89 | declare module 'shelljs/src/ln' { 90 | declare module.exports: any; 91 | } 92 | 93 | declare module 'shelljs/src/ls' { 94 | declare module.exports: any; 95 | } 96 | 97 | declare module 'shelljs/src/mkdir' { 98 | declare module.exports: any; 99 | } 100 | 101 | declare module 'shelljs/src/mv' { 102 | declare module.exports: any; 103 | } 104 | 105 | declare module 'shelljs/src/popd' { 106 | declare module.exports: any; 107 | } 108 | 109 | declare module 'shelljs/src/pushd' { 110 | declare module.exports: any; 111 | } 112 | 113 | declare module 'shelljs/src/pwd' { 114 | declare module.exports: any; 115 | } 116 | 117 | declare module 'shelljs/src/rm' { 118 | declare module.exports: any; 119 | } 120 | 121 | declare module 'shelljs/src/sed' { 122 | declare module.exports: any; 123 | } 124 | 125 | declare module 'shelljs/src/tempdir' { 126 | declare module.exports: any; 127 | } 128 | 129 | declare module 'shelljs/src/test' { 130 | declare module.exports: any; 131 | } 132 | 133 | declare module 'shelljs/src/to' { 134 | declare module.exports: any; 135 | } 136 | 137 | declare module 'shelljs/src/toEnd' { 138 | declare module.exports: any; 139 | } 140 | 141 | declare module 'shelljs/src/which' { 142 | declare module.exports: any; 143 | } 144 | 145 | // Filename aliases 146 | declare module 'shelljs/global.js' { 147 | declare module.exports: $Exports<'shelljs/global'>; 148 | } 149 | declare module 'shelljs/make.js' { 150 | declare module.exports: $Exports<'shelljs/make'>; 151 | } 152 | declare module 'shelljs/scripts/generate-docs.js' { 153 | declare module.exports: $Exports<'shelljs/scripts/generate-docs'>; 154 | } 155 | declare module 'shelljs/scripts/run-tests.js' { 156 | declare module.exports: $Exports<'shelljs/scripts/run-tests'>; 157 | } 158 | declare module 'shelljs/shell.js' { 159 | declare module.exports: $Exports<'shelljs/shell'>; 160 | } 161 | declare module 'shelljs/src/cat.js' { 162 | declare module.exports: $Exports<'shelljs/src/cat'>; 163 | } 164 | declare module 'shelljs/src/cd.js' { 165 | declare module.exports: $Exports<'shelljs/src/cd'>; 166 | } 167 | declare module 'shelljs/src/chmod.js' { 168 | declare module.exports: $Exports<'shelljs/src/chmod'>; 169 | } 170 | declare module 'shelljs/src/common.js' { 171 | declare module.exports: $Exports<'shelljs/src/common'>; 172 | } 173 | declare module 'shelljs/src/cp.js' { 174 | declare module.exports: $Exports<'shelljs/src/cp'>; 175 | } 176 | declare module 'shelljs/src/dirs.js' { 177 | declare module.exports: $Exports<'shelljs/src/dirs'>; 178 | } 179 | declare module 'shelljs/src/echo.js' { 180 | declare module.exports: $Exports<'shelljs/src/echo'>; 181 | } 182 | declare module 'shelljs/src/error.js' { 183 | declare module.exports: $Exports<'shelljs/src/error'>; 184 | } 185 | declare module 'shelljs/src/exec.js' { 186 | declare module.exports: $Exports<'shelljs/src/exec'>; 187 | } 188 | declare module 'shelljs/src/find.js' { 189 | declare module.exports: $Exports<'shelljs/src/find'>; 190 | } 191 | declare module 'shelljs/src/grep.js' { 192 | declare module.exports: $Exports<'shelljs/src/grep'>; 193 | } 194 | declare module 'shelljs/src/ln.js' { 195 | declare module.exports: $Exports<'shelljs/src/ln'>; 196 | } 197 | declare module 'shelljs/src/ls.js' { 198 | declare module.exports: $Exports<'shelljs/src/ls'>; 199 | } 200 | declare module 'shelljs/src/mkdir.js' { 201 | declare module.exports: $Exports<'shelljs/src/mkdir'>; 202 | } 203 | declare module 'shelljs/src/mv.js' { 204 | declare module.exports: $Exports<'shelljs/src/mv'>; 205 | } 206 | declare module 'shelljs/src/popd.js' { 207 | declare module.exports: $Exports<'shelljs/src/popd'>; 208 | } 209 | declare module 'shelljs/src/pushd.js' { 210 | declare module.exports: $Exports<'shelljs/src/pushd'>; 211 | } 212 | declare module 'shelljs/src/pwd.js' { 213 | declare module.exports: $Exports<'shelljs/src/pwd'>; 214 | } 215 | declare module 'shelljs/src/rm.js' { 216 | declare module.exports: $Exports<'shelljs/src/rm'>; 217 | } 218 | declare module 'shelljs/src/sed.js' { 219 | declare module.exports: $Exports<'shelljs/src/sed'>; 220 | } 221 | declare module 'shelljs/src/tempdir.js' { 222 | declare module.exports: $Exports<'shelljs/src/tempdir'>; 223 | } 224 | declare module 'shelljs/src/test.js' { 225 | declare module.exports: $Exports<'shelljs/src/test'>; 226 | } 227 | declare module 'shelljs/src/to.js' { 228 | declare module.exports: $Exports<'shelljs/src/to'>; 229 | } 230 | declare module 'shelljs/src/toEnd.js' { 231 | declare module.exports: $Exports<'shelljs/src/toEnd'>; 232 | } 233 | declare module 'shelljs/src/which.js' { 234 | declare module.exports: $Exports<'shelljs/src/which'>; 235 | } 236 | -------------------------------------------------------------------------------- /flow-typed/npm/uglify-js_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 5bf867d77ff345148611914d9e20eb1b 2 | // flow-typed version: <>/uglify-js_v^2.6.1/flow_v0.37.4 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'uglify-js' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'uglify-js' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'uglify-js/bin/extract-props' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'uglify-js/lib/ast' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'uglify-js/lib/compress' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'uglify-js/lib/mozilla-ast' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'uglify-js/lib/output' { 42 | declare module.exports: any; 43 | } 44 | 45 | declare module 'uglify-js/lib/parse' { 46 | declare module.exports: any; 47 | } 48 | 49 | declare module 'uglify-js/lib/propmangle' { 50 | declare module.exports: any; 51 | } 52 | 53 | declare module 'uglify-js/lib/scope' { 54 | declare module.exports: any; 55 | } 56 | 57 | declare module 'uglify-js/lib/sourcemap' { 58 | declare module.exports: any; 59 | } 60 | 61 | declare module 'uglify-js/lib/transform' { 62 | declare module.exports: any; 63 | } 64 | 65 | declare module 'uglify-js/lib/utils' { 66 | declare module.exports: any; 67 | } 68 | 69 | declare module 'uglify-js/tools/exports' { 70 | declare module.exports: any; 71 | } 72 | 73 | declare module 'uglify-js/tools/node' { 74 | declare module.exports: any; 75 | } 76 | 77 | // Filename aliases 78 | declare module 'uglify-js/bin/extract-props.js' { 79 | declare module.exports: $Exports<'uglify-js/bin/extract-props'>; 80 | } 81 | declare module 'uglify-js/lib/ast.js' { 82 | declare module.exports: $Exports<'uglify-js/lib/ast'>; 83 | } 84 | declare module 'uglify-js/lib/compress.js' { 85 | declare module.exports: $Exports<'uglify-js/lib/compress'>; 86 | } 87 | declare module 'uglify-js/lib/mozilla-ast.js' { 88 | declare module.exports: $Exports<'uglify-js/lib/mozilla-ast'>; 89 | } 90 | declare module 'uglify-js/lib/output.js' { 91 | declare module.exports: $Exports<'uglify-js/lib/output'>; 92 | } 93 | declare module 'uglify-js/lib/parse.js' { 94 | declare module.exports: $Exports<'uglify-js/lib/parse'>; 95 | } 96 | declare module 'uglify-js/lib/propmangle.js' { 97 | declare module.exports: $Exports<'uglify-js/lib/propmangle'>; 98 | } 99 | declare module 'uglify-js/lib/scope.js' { 100 | declare module.exports: $Exports<'uglify-js/lib/scope'>; 101 | } 102 | declare module 'uglify-js/lib/sourcemap.js' { 103 | declare module.exports: $Exports<'uglify-js/lib/sourcemap'>; 104 | } 105 | declare module 'uglify-js/lib/transform.js' { 106 | declare module.exports: $Exports<'uglify-js/lib/transform'>; 107 | } 108 | declare module 'uglify-js/lib/utils.js' { 109 | declare module.exports: $Exports<'uglify-js/lib/utils'>; 110 | } 111 | declare module 'uglify-js/tools/exports.js' { 112 | declare module.exports: $Exports<'uglify-js/tools/exports'>; 113 | } 114 | declare module 'uglify-js/tools/node.js' { 115 | declare module.exports: $Exports<'uglify-js/tools/node'>; 116 | } 117 | -------------------------------------------------------------------------------- /img/example_chart_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charted-co/charted/fa9364cc71e18150623c86083a829465843b00de/img/example_chart_screenshot.png -------------------------------------------------------------------------------- /lib/client/nodeunit.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Styles taken from qunit.css 3 | */ 4 | 5 | h1#nodeunit-header, h1.nodeunit-header { 6 | padding: 15px; 7 | font-size: large; 8 | background-color: #06b; 9 | color: white; 10 | font-family: 'trebuchet ms', verdana, arial; 11 | margin: 0; 12 | } 13 | 14 | h1#nodeunit-header a { 15 | color: white; 16 | } 17 | 18 | h2#nodeunit-banner { 19 | height: 2em; 20 | border-bottom: 1px solid white; 21 | background-color: #eee; 22 | margin: 0; 23 | font-family: 'trebuchet ms', verdana, arial; 24 | } 25 | h2#nodeunit-banner.pass { 26 | background-color: green; 27 | } 28 | h2#nodeunit-banner.fail { 29 | background-color: red; 30 | } 31 | 32 | h2#nodeunit-userAgent, h2.nodeunit-userAgent { 33 | padding: 10px; 34 | background-color: #eee; 35 | color: black; 36 | margin: 0; 37 | font-size: small; 38 | font-weight: normal; 39 | font-family: 'trebuchet ms', verdana, arial; 40 | font-size: 10pt; 41 | } 42 | 43 | div#nodeunit-testrunner-toolbar { 44 | background: #eee; 45 | border-top: 1px solid black; 46 | padding: 10px; 47 | font-family: 'trebuchet ms', verdana, arial; 48 | margin: 0; 49 | font-size: 10pt; 50 | } 51 | 52 | ol#nodeunit-tests { 53 | font-family: 'trebuchet ms', verdana, arial; 54 | font-size: 10pt; 55 | } 56 | ol#nodeunit-tests li strong { 57 | cursor:pointer; 58 | } 59 | ol#nodeunit-tests .pass { 60 | color: green; 61 | } 62 | ol#nodeunit-tests .fail { 63 | color: red; 64 | } 65 | 66 | p#nodeunit-testresult { 67 | margin-left: 1em; 68 | font-size: 10pt; 69 | font-family: 'trebuchet ms', verdana, arial; 70 | } 71 | -------------------------------------------------------------------------------- /lib/interfaces/d3.js: -------------------------------------------------------------------------------- 1 | declare class _D3_TSV_CSV { 2 | parseRows(file: string): Array> 3 | } 4 | 5 | declare class _D3_SCALE { 6 | linear(): Object 7 | } 8 | 9 | declare class _D3_SVG { 10 | line(): Object; 11 | } 12 | 13 | declare class _D3_XHR { 14 | header(key: string, value: string): _D3_XHR; 15 | post(data: string, callback: Function): _D3_XHR; 16 | } 17 | 18 | declare class _D3 { 19 | tsv: _D3_TSV_CSV; 20 | csv: _D3_TSV_CSV; 21 | scale: _D3_SCALE; 22 | svg: _D3_SVG; 23 | 24 | ascending(a: any, b: any): number; 25 | json(url: string, callback: Function): void; 26 | xhr(url: string): _D3_XHR; 27 | range(range: number): Array; 28 | extent(list: Array, callback: (item: T) => any): Array; 29 | max(list: Array): T; 30 | select(selector: string|Object): Object; 31 | } 32 | 33 | declare var d3: _D3; 34 | -------------------------------------------------------------------------------- /lib/interfaces/types.js: -------------------------------------------------------------------------------- 1 | declare type t_SERIES = {label: string, seriesIndex: number} 2 | declare type t_FIELD = { 3 | x: number, 4 | y: number, 5 | xLabel: string, 6 | yRaw: string, 7 | ySeries?: number, 8 | y0?: number, 9 | y1?: number, 10 | columnEl?: Element 11 | } 12 | declare type t_CHART_PARAM = { 13 | dataUrl: string; 14 | charts?: Array; 15 | seriesColors?: {[key: number]: string}; 16 | seriesNames?: {[key: number]: string}; 17 | grid?: string; 18 | color?: string; 19 | } 20 | declare type t_ENV = { 21 | dev: boolean; 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chartedjs", 3 | "version": "3.2.3", 4 | "description": "A charting library originally created by the Product Science team at Medium", 5 | "main": "out/server/charted.js", 6 | "scripts": { 7 | "prestart": "bin/build", 8 | "start": "bin/watch && node out/server/index.js", 9 | "pretest": "bin/build", 10 | "test": "nodeunit out/server/*_test.js", 11 | "build-prod": "bin/build --prod", 12 | "start-prod": "bin/build --prod && node out/server/index.js", 13 | "prepublishOnly": "bin/build --prod", 14 | "flow": "flow" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git@github.com:mikesall/charted.git" 19 | }, 20 | "keywords": [ 21 | "charts" 22 | ], 23 | "author": "Anton Kovalyov ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/mikesall/charted/issues" 27 | }, 28 | "dependencies": { 29 | "body-parser": "1.14.x", 30 | "connect": "^3.3.5", 31 | "express": "4.10.x", 32 | "handlebars": "^4.0.5", 33 | "request": "2.47.x" 34 | }, 35 | "devDependencies": { 36 | "babel": "6.0.x", 37 | "babel-cli": "6.3.x", 38 | "babel-core": "6.3.x", 39 | "babel-plugin-transform-es2015-modules-amd": "6.3.x", 40 | "babel-plugin-transform-flow-strip-types": "6.3.x", 41 | "babel-preset-es2015": "6.3.x", 42 | "babel-preset-stage-0": "6.3.x", 43 | "less": "1.7.x", 44 | "nodeunit": "0.9.x", 45 | "requirejs": "^2.1.22", 46 | "shelljs": "0.5.x", 47 | "uglify-js": "^2.6.1", 48 | "flow-bin": "^0.37.4" 49 | }, 50 | "engines": { 51 | "node": ">=4.1.1" 52 | }, 53 | "homepage": "http://www.charted.co/" 54 | } 55 | -------------------------------------------------------------------------------- /src/client/Actions.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | type ListenerFunction = (el: Element, name: string, ev: Event) => void; 4 | type Listeners = {[name: string]: ListenerFunction[]} 5 | 6 | export default class Actions { 7 | rootElement: Element; 8 | listeners: Listeners; 9 | boundListener: Function; 10 | 11 | constructor(el: ?Element) { 12 | if (!el) { 13 | throw new Error('root element is not initialized yet') 14 | } 15 | 16 | this.rootElement = el 17 | this.listeners = {} 18 | } 19 | 20 | activate() { 21 | this.boundListener = this.handleClick.bind(this) 22 | this.rootElement.addEventListener('click', this.boundListener) 23 | } 24 | 25 | deactivate() { 26 | if (this.boundListener) { 27 | this.rootElement.removeEventListener('click', this.boundListener) 28 | } 29 | 30 | delete this.boundListener 31 | delete this.rootElement 32 | this.listeners = {} 33 | } 34 | 35 | add(name: string, listener: ListenerFunction, thisObj: T): Actions { 36 | if (!this.listeners[name]) { 37 | this.listeners[name] = [] 38 | } 39 | 40 | this.listeners[name].push(listener.bind(thisObj)) 41 | return this 42 | } 43 | 44 | handleClick(ev: Event) { 45 | let target = ev.target 46 | 47 | if (!(target instanceof Element)) { 48 | return 49 | } 50 | 51 | if (target instanceof HTMLElement) { 52 | if (target.nodeName == 'BUTTON' && target.getAttribute('type') == 'submit') { 53 | return 54 | } 55 | } 56 | 57 | let root = this.rootElement.parentNode 58 | while (target && target != root && target != document) { 59 | if (target instanceof HTMLElement) { 60 | let name = target.getAttribute('data-click') 61 | 62 | // If the element doesn't have a data-click property but is something 63 | // you'd want to click on (like a text field), return without firing. 64 | if (this.isClickable(target) && !name) { 65 | return 66 | } 67 | 68 | if (name) { 69 | this.fire(name, target, ev) 70 | return 71 | } 72 | } 73 | 74 | target = target.parentNode 75 | } 76 | } 77 | 78 | isClickable(el: HTMLElement) { 79 | switch (el.nodeName) { 80 | case 'A': 81 | case 'BUTTON': 82 | case 'INPUT': 83 | case 'TEXTAREA': 84 | return true 85 | default: 86 | if (el.getAttribute('contenteditable')) { 87 | return true 88 | } 89 | } 90 | 91 | return false 92 | } 93 | 94 | fire(name: string, target: Element, ev: Event) { 95 | if (!this.listeners[name]) { 96 | return 97 | } 98 | 99 | this.listeners[name].forEach((fn: ListenerFunction) => { 100 | fn(target, name, ev) 101 | }) 102 | 103 | ev.stopPropagation() 104 | ev.preventDefault() 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/client/ChartData.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import {getTrimmedExtent} from "../shared/utils" 4 | import PageData from "./PageData" 5 | 6 | export default class ChartData { 7 | _data: Array>; 8 | _serieses: Array; 9 | _indices: Array; 10 | 11 | constructor(pageData: PageData, seriesIndicesToUse: Array) { 12 | this._data = pageData.data.filter((series, i) => seriesIndicesToUse.indexOf(i) > -1) 13 | this._serieses = pageData.serieses.filter((series, i) => seriesIndicesToUse.indexOf(i) > -1) 14 | this._indices = pageData.indices 15 | this.formatData() 16 | } 17 | 18 | formatData(): void { 19 | // add stackedPosition 20 | this._data[0].forEach((row, i) => { 21 | var negY0 = 0 22 | var posY0 = 0 23 | 24 | this._data.forEach((series, j) => { 25 | var datum = this._data[j][i] 26 | datum.ySeries = j 27 | 28 | if (datum.y < 0) { 29 | negY0 = negY0 + datum.y 30 | datum.y0 = negY0 31 | datum.y1 = datum.y0 - datum.y 32 | } else { 33 | datum.y0 = posY0 34 | datum.y1 = datum.y0 + datum.y 35 | posY0 = posY0 + datum.y 36 | } 37 | }) 38 | }) 39 | } 40 | 41 | getFlattenedData(): Array { 42 | return this._data.reduce((a, b) => a.concat(b)) 43 | } 44 | 45 | getSerieses(): Array { 46 | return this._serieses 47 | } 48 | 49 | getSeries(i: number): Object { 50 | return this._serieses[i] 51 | } 52 | 53 | getSeriesByIndex(index: number): ?t_SERIES { 54 | for (let i = 0; i < this._serieses.length; i++) { 55 | if (this._serieses[i].seriesIndex == index) { 56 | return this._serieses[i] 57 | } 58 | } 59 | } 60 | 61 | getSeriesPositionByIndex(index: number): number { 62 | for (let i = 0; i < this._serieses.length; i++) { 63 | if (this._serieses[i].seriesIndex == index) { 64 | return i 65 | } 66 | } 67 | 68 | return -1 69 | } 70 | 71 | getDatum(seriesIndex: number, index: number): t_FIELD { 72 | return this._data[seriesIndex][index] 73 | } 74 | 75 | getSeriesLabels(): Array { 76 | return this._serieses.map(function (series) { return series.label }) 77 | } 78 | 79 | getSeriesIndices(): Array { 80 | return this._serieses.map(function (series) { return series.seriesIndex }) 81 | } 82 | 83 | getSeriesCount(): number { 84 | return this._serieses.length 85 | } 86 | 87 | getUnstackedValuesAtIndex(i: number): Array { 88 | return this._data.map(function (series) { 89 | return series[i].y 90 | }) 91 | } 92 | 93 | getValuesForSeries(seriesIndex: number): any { 94 | var seriesExtent = this.getSeriesExtent(seriesIndex) 95 | return this._data[seriesIndex].slice(seriesExtent[0], seriesExtent[1] + 1) 96 | } 97 | 98 | getFirstDatum(seriesIndex: number): any { 99 | var firstPointIndex = this.getSeriesExtent(seriesIndex)[0] 100 | return this._data[seriesIndex][firstPointIndex] 101 | } 102 | 103 | getLastDatum(seriesIndex: number): any { 104 | var lastPointIndex = this.getSeriesExtent(seriesIndex)[1] 105 | return this._data[seriesIndex][lastPointIndex] 106 | } 107 | 108 | getSeriesExtent(seriesIndex: number): Array { 109 | let yRawValues = this._data[seriesIndex].map((item) => item.yRaw) 110 | return getTrimmedExtent(yRawValues) 111 | } 112 | 113 | getIndices(): Array { 114 | return this._indices 115 | } 116 | 117 | getIndexCount(): number { 118 | return this._indices.length 119 | } 120 | 121 | getStackedExtent(): Array { 122 | return d3.extent(this.getFlattenedData(), function (datum) { 123 | return datum.y < 0 ? datum.y0 : datum.y1 124 | }) 125 | } 126 | 127 | getStackedExtentForIndex(index: number): Array { 128 | var extent = [0, 0] 129 | this._data.forEach(function (series) { 130 | var minOrMax = series[index].y < 0 ? 0 : 1 131 | extent[minOrMax] += series[index].y 132 | }) 133 | 134 | return extent 135 | } 136 | 137 | getUnstackedExtent(): Array { 138 | return d3.extent(this.getFlattenedData(), function (datum) { 139 | return datum.y 140 | }) 141 | } 142 | 143 | getIndexExtent(): Array { 144 | return [this._indices[0], this._indices[this._indices.length - 1]] 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/client/ChartLegend.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Actions from "./Actions" 4 | import Chart from "./Chart" 5 | import ChartData from "./ChartData" 6 | import {PageController} from "./PageController" 7 | import Editor from "./Editor" 8 | import * as templates from "./templates" 9 | import dom from "./dom" 10 | 11 | export default class ChartLegend { 12 | actions: Actions; 13 | chart: Chart; 14 | controller: PageController; 15 | data: ChartData; 16 | chartIndex: number; 17 | series: Array; 18 | 19 | constructor(controller: PageController, data: ChartData, chart: Chart) { 20 | this.chart = chart 21 | this.controller = controller 22 | this.data = data 23 | this.chartIndex = this.chart.getChartIndex() 24 | this.series = this.chart.getChartSeries() 25 | this.actions = new Actions(this.chart.container) 26 | } 27 | 28 | activate() { 29 | this.actions 30 | .add('open-color-input', this.openColorInput, this) 31 | .add('open-move-chart', this.openMoveChart, this) 32 | .add('move-to-chart', this.moveToChart, this) 33 | .activate() 34 | } 35 | 36 | deactivate() { 37 | this.actions.deactivate() 38 | delete this.actions 39 | } 40 | 41 | getLegendElement(index: number): Element { 42 | let legend = dom.get(`js-legendItem[data-series-index="${index}"]`) 43 | if (legend) return legend 44 | throw `Legend item with index ${index} not found` 45 | } 46 | 47 | update(): void { 48 | if (this.data.getSeriesCount() === 1 && this.controller.getOtherCharts(this.chartIndex).length === 0) { 49 | let legend = dom.get('js-legend', this.chart.container) 50 | if (legend) { 51 | legend.innerHTML = '' 52 | } 53 | return 54 | } 55 | 56 | let legend = document.createDocumentFragment() 57 | let serieses = this.data.getSerieses() 58 | 59 | for (let i = serieses.length - 1; i >= 0; i--) { 60 | let series = serieses[i] 61 | let label = this.controller.getSeriesName(this.series[i]) 62 | let fragment = dom.renderFragment(templates.legendItem({ 63 | label: label, 64 | color: this.chart.getSeriesColor(series.seriesIndex), 65 | editable: this.controller.getEditability(), 66 | seriesIndex: series.seriesIndex 67 | })) 68 | 69 | legend.appendChild(fragment) 70 | } 71 | 72 | let container = dom.get('js-legend', this.chart.container) 73 | if (container) { 74 | container.innerHTML = '' 75 | container.appendChild(legend) 76 | dom.classlist.remove(container, 'hidden') 77 | } 78 | 79 | let seriesNames = this.controller.params.seriesNames 80 | if (this.controller.getEditability()) { 81 | this.data.getSerieses().forEach((series) => { 82 | let label = dom.get('js-legendLabel', this.getLegendElement(series.seriesIndex)) 83 | if (!label) throw `Legend label for legend ${series.seriesIndex} not found` 84 | 85 | let ed = new Editor(label) 86 | ed.onChange((content) => { 87 | if (!content === '' || content === series.label) { 88 | ed.setContent(series.label) 89 | delete seriesNames[series.seriesIndex] 90 | } else { 91 | seriesNames[series.seriesIndex] = content 92 | } 93 | 94 | this.controller.updateURL() 95 | }) 96 | }) 97 | } 98 | } 99 | 100 | openColorInput(target: Element) { 101 | this.controller.removePopovers() 102 | let index = Number(target.getAttribute('data-series-index')) 103 | let el = this.getLegendElement(index) 104 | let colorHex = this.chart.getSeriesColor(index).replace(/^#/, '') 105 | 106 | dom.classlist.add(el, 'active-color-input') 107 | let fragment = dom.renderFragment(templates.changeSeriesColor({ 108 | colorHex: colorHex, 109 | seriesIndex: index 110 | })) 111 | el.appendChild(fragment) 112 | 113 | // TODO: Replace all this with Editor 114 | let input = dom.assert(dom.get('js-colorEditor', el)) 115 | input.addEventListener('focusout', () => { 116 | let seriesColors = this.controller.params.seriesColors 117 | let newColorHex = '' 118 | if (input.innerText) { 119 | newColorHex = '#' + input.innerText.replace(/^#/, '').trim() 120 | } 121 | 122 | let defaultColorHex = this.chart.getDefaulSeriesColor(index) 123 | let isValidHex = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(newColorHex) 124 | 125 | if (newColorHex === defaultColorHex ||!isValidHex ) { 126 | input.innerHTML = defaultColorHex 127 | delete seriesColors[index] 128 | } else { 129 | seriesColors[index] = newColorHex 130 | } 131 | this.chart.render() 132 | this.controller.updateURL() 133 | }) 134 | } 135 | 136 | openMoveChart(target: Element) { 137 | this.controller.removePopovers() 138 | let index = Number(target.getAttribute('data-series-index')) 139 | let series = this.data.getSeriesByIndex(index) 140 | if (!series) throw `Series ${index} not found` 141 | 142 | let position = this.data.getSeriesPositionByIndex(index) 143 | if (position < 0) throw `Series ${index} not found` 144 | 145 | let otherCharts = this.controller.getOtherCharts(this.chartIndex) 146 | 147 | // current number of charts = other charts + current chart 148 | var newChartIndex = otherCharts.length + 1 149 | 150 | if (otherCharts.length === 0) { 151 | // if no other charts, move series to a new chart 152 | this.controller.moveToChart(this.series[position], this.chartIndex, newChartIndex) 153 | 154 | } else if (otherCharts.length === 1 && this.series.length === 1) { 155 | // if only one series and only one other chart, move series back into that chart 156 | this.controller.moveToChart(this.series[position], this.chartIndex, otherCharts[0].chartIndex) 157 | 158 | } else { 159 | // else, show all the options in a popover 160 | let el = this.getLegendElement(series.seriesIndex) 161 | dom.classlist.add(el, 'active') 162 | 163 | el.appendChild(dom.renderFragment(templates.moveChart({ 164 | position: position, 165 | otherCharts: otherCharts, 166 | series: this.series, 167 | newChartIndex: newChartIndex 168 | }))) 169 | } 170 | } 171 | 172 | moveToChart(target: Element) { 173 | let src = this.chartIndex 174 | let dest = Number(target.getAttribute('data-dest')) 175 | let series = this.series[Number(target.getAttribute('data-position'))] 176 | this.controller.moveToChart(series, src, dest) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/client/ChartParameters.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as utils from "../shared/utils" 4 | import sha1 from "../shared/sha1" 5 | 6 | // TODO(anton): These should be in shared/constants 7 | const COLOR_DARK = 'dark' 8 | const COLOR_LIGHT = 'light' 9 | const GRID_FULL = 'full' 10 | const GRID_SPLIT = 'split' 11 | const OPTIONS = { 12 | // Default values are first 13 | type: ['column', 'line'], 14 | rounding: ['on', 'off'] 15 | } 16 | 17 | export default class ChartParameters { 18 | url: string; 19 | charts: Array; 20 | seriesColors: {[key: number]: string}; 21 | seriesNames: {[key: number]: string}; 22 | 23 | _grid: string; 24 | _color: string; 25 | _getDefaultTitle: (i: number) => string; 26 | 27 | constructor(url: string) { 28 | this.url = url 29 | this.charts = [{}] 30 | this.seriesColors = {} 31 | this.seriesNames = {} 32 | this._color = COLOR_LIGHT 33 | this._grid = GRID_SPLIT 34 | this._getDefaultTitle = (i) => '' // no-op 35 | } 36 | 37 | static fromJSON(data: Object): ChartParameters { 38 | let params = new ChartParameters(data.dataUrl) 39 | if (data.charts) params.charts = data.charts 40 | if (data.seriesNames) params.seriesNames = data.seriesNames 41 | if (data.seriesColors) params.seriesColors = data.seriesColors 42 | if (data.grid) params._grid = data.grid 43 | if (data.color) params._color = data.color 44 | 45 | return params 46 | } 47 | 48 | static fromQueryString(qs: string): ?ChartParameters { 49 | let urlParams = utils.parseQueryString(qs) 50 | let data = urlParams.data 51 | if (!data) { 52 | return null 53 | } 54 | 55 | let url = data.csvUrl || data.dataUrl 56 | if (!url) { 57 | return null 58 | } 59 | 60 | let params = new ChartParameters(url) 61 | if (data.charts) params.charts = data.charts 62 | if (data.seriesNames) params.seriesNames = data.seriesNames 63 | if (data.seriesColors) params.seriesColors = data.seriesColors 64 | if (data.grid) params._grid = data.grid 65 | if (data.color) params._color = data.color 66 | 67 | return params 68 | } 69 | 70 | withDefaultTitle(fn: (i: number) => string): ChartParameters { 71 | this._getDefaultTitle = fn 72 | return this 73 | } 74 | 75 | isLight(): boolean { 76 | return this._color == COLOR_LIGHT; 77 | } 78 | 79 | toggleColor(): void { 80 | this._color = this.isLight() ? COLOR_DARK : COLOR_LIGHT; 81 | } 82 | 83 | isFull(): boolean { 84 | return this._grid == GRID_FULL; 85 | } 86 | 87 | toggleGrid(): void { 88 | this._grid = this.isFull() ? GRID_SPLIT : GRID_FULL 89 | } 90 | 91 | getSeriesColor(index: number): ?string { 92 | return this.seriesColors[index] 93 | } 94 | 95 | getSeriesName(index: number): ?string { 96 | return this.seriesNames[index] 97 | } 98 | 99 | compress(): t_CHART_PARAM { 100 | let params: t_CHART_PARAM = {dataUrl: this.url} 101 | 102 | // Add seriesNames, if applicable. 103 | if (Object.keys(this.seriesNames).length) { 104 | params.seriesNames = this.seriesNames 105 | } 106 | 107 | // Add seriesColors, if applicable. 108 | if (Object.keys(this.seriesColors).length) { 109 | params.seriesColors = this.seriesColors 110 | } 111 | 112 | // Add color, if applicable. 113 | if (!this.isLight()) { 114 | params.color = this._color 115 | } 116 | 117 | // Add grid, if applicable. 118 | if (this.isFull()) { 119 | params.grid = this._grid 120 | } 121 | 122 | // Add applicable chart parameters. 123 | params.charts = this.charts.map((chart, i) => { 124 | let compressed = {} 125 | 126 | // Add applicable chart options. 127 | Object.keys(OPTIONS).forEach((option) => { 128 | if (chart[option] && chart[option] !== OPTIONS[option][0]) { 129 | compressed[option] = chart[option] 130 | } 131 | }) 132 | 133 | // Add applicable title. 134 | if (chart.title && chart.title !== this._getDefaultTitle(i)) { 135 | compressed.title = chart.title 136 | } 137 | 138 | // Add applicable note. 139 | if (chart.note) { 140 | compressed.note = chart.note 141 | } 142 | 143 | // Add applicable series. 144 | if (i > 0 && chart.series) { 145 | compressed.series = chart.series 146 | } 147 | 148 | return compressed 149 | }) 150 | 151 | // Delete charts if empty. 152 | if (params.charts.length === 1 && !Object.keys(params.charts[0]).length) { 153 | delete params.charts 154 | } 155 | 156 | return params 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/client/ChartParameters_test.js: -------------------------------------------------------------------------------- 1 | import ChartParameters from "./ChartParameters" 2 | 3 | export function testDefaultParameters(test) { 4 | let params = new ChartParameters('http://charted.co/data.csv') 5 | test.equal(params.url, 'http://charted.co/data.csv') 6 | test.ok(params.isLight()) 7 | test.ok(!params.isFull()) 8 | test.equal(1, params.charts.length) 9 | test.done() 10 | } 11 | 12 | export function testFromQueryString(test) { 13 | // This is URL for our demo chart 14 | let qs = 15 | '?%7B"dataUrl"%3A"https%3A%2F%2Fdocs.google.com%2Fspreadsheets%2Fd%2F1PSaAXtklG4UyFm2' + 16 | 'lui5d4k_UNEO1laAMpzMWVh0FMTU%2Fexport%3Fgid%3D0%26format%3Dcsv"%2C"charts"%3A%5B%7B"' + 17 | 'title"%3A"An%20Example%20Chart"%2C"note"%3A"This%20is%20an%20example%20chart%20visua' + 18 | 'lizing%20some%20fake%20data"%7D%5D%7D' 19 | 20 | let params = ChartParameters.fromQueryString(qs) 21 | test.equal(params.url, 'https://docs.google.com/spreadsheets/d/1PSaAXtklG4UyFm2lui5d4k_UNEO1laAMpzMWVh0FMTU/export?gid=0&format=csv') 22 | test.equal(1, params.charts.length) 23 | test.equal('This is an example chart visualizing some fake data', params.charts[0].note) 24 | test.equal('An Example Chart', params.charts[0].title) 25 | test.done() 26 | } 27 | 28 | export function testToggleColor(test) { 29 | let params = new ChartParameters('http://charted.co') 30 | test.ok(params.isLight()) 31 | params.toggleColor() 32 | test.ok(!params.isLight()) 33 | test.done() 34 | } 35 | 36 | export function testToggleGrid(test) { 37 | let params = new ChartParameters('http://charted.co') 38 | test.ok(!params.isFull()) 39 | params.toggleGrid() 40 | test.ok(params.isFull()) 41 | test.done() 42 | } 43 | 44 | export function testGetSeriesColor(test) { 45 | let params = new ChartParameters('http://charted.co') 46 | params.seriesColors[3] = '#fff' 47 | test.equal('#fff', params.getSeriesColor(3)) 48 | test.done() 49 | } 50 | 51 | export function testGetSeriesName(test) { 52 | let params = new ChartParameters('http://charted.co') 53 | params.seriesNames[3] = 'test name' 54 | test.equal('test name', params.getSeriesName(3)) 55 | test.done() 56 | } 57 | 58 | export function testCompressParams(test) { 59 | // We start with a completely empty instance and then gradually 60 | // add bits to it one by one. 61 | let params = new ChartParameters('http://charted.co') 62 | 63 | test.deepEqual(params.compress(), { 64 | dataUrl: 'http://charted.co' 65 | }) 66 | 67 | params.seriesNames[1] = 'test name' 68 | test.deepEqual(params.compress(), { 69 | dataUrl: 'http://charted.co', 70 | seriesNames: {1: 'test name'} 71 | }) 72 | 73 | params.seriesColors[1] = '#fff' 74 | test.deepEqual(params.compress(), { 75 | dataUrl: 'http://charted.co', 76 | seriesNames: {1: 'test name'}, 77 | seriesColors: {1: '#fff'} 78 | }) 79 | 80 | params.toggleColor() 81 | test.deepEqual(params.compress(), { 82 | dataUrl: 'http://charted.co', 83 | seriesNames: {1: 'test name'}, 84 | seriesColors: {1: '#fff'}, 85 | color: 'dark' 86 | }) 87 | 88 | params.toggleGrid() 89 | test.deepEqual(params.compress(), { 90 | dataUrl: 'http://charted.co', 91 | seriesNames: {1: 'test name'}, 92 | seriesColors: {1: '#fff'}, 93 | color: 'dark', 94 | grid: 'full' 95 | }) 96 | 97 | params.charts = [{title: 'my title', note: 'my note'}] 98 | test.deepEqual(params.compress(), { 99 | dataUrl: 'http://charted.co', 100 | seriesNames: {1: 'test name'}, 101 | seriesColors: {1: '#fff'}, 102 | color: 'dark', 103 | grid: 'full', 104 | charts: [{title: 'my title', note: 'my note'}] 105 | }) 106 | 107 | params.charts = [ 108 | {title: 'chart 1', type: 'column', rounding: 'on'}, 109 | {title: 'chart 2', type: 'line', rounding: 'off', series: []} 110 | ] 111 | test.deepEqual(params.compress(), { 112 | dataUrl: 'http://charted.co', 113 | seriesNames: {1: 'test name'}, 114 | seriesColors: {1: '#fff'}, 115 | color: 'dark', 116 | grid: 'full', 117 | charts: [ 118 | {title: 'chart 1'}, 119 | {title: 'chart 2', type: 'line', rounding: 'off', series: ''} 120 | ] 121 | }) 122 | 123 | test.done() 124 | } 125 | 126 | export function testDefaultTitle(test) { 127 | // We start with a completely empty instance and then gradually 128 | // add bits to it one by one. 129 | let params = new ChartParameters('http://charted.co') 130 | params.withDefaultTitle((i) => `title ${i+1}`) 131 | params.charts = [ 132 | {title: 'title 1'}, 133 | {title: 'title 2'}, 134 | {title: 'title X'} 135 | ] 136 | test.deepEqual(params.compress(), { 137 | dataUrl: 'http://charted.co', 138 | charts: [{}, {}, {title: 'title X'}] 139 | }) 140 | 141 | test.done() 142 | } 143 | -------------------------------------------------------------------------------- /src/client/Editor.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export default class Editor { 4 | rootElement: Element; 5 | listener: Function; 6 | 7 | constructor(el: Element) { 8 | this.rootElement = el 9 | this.listener = () => {} 10 | this.setContent(this.getContent()) 11 | 12 | this.rootElement.addEventListener('focusout', () => { 13 | let content = this.getContent() 14 | 15 | this.setContent(content) 16 | this.listener(content) 17 | }) 18 | } 19 | 20 | onChange(fn: Function) { 21 | this.listener = fn 22 | } 23 | 24 | getContent(): string { 25 | let content = this.rootElement.innerText 26 | return content ? content.trim() : '' 27 | } 28 | 29 | setContent(text: string): void { 30 | if (!text) { 31 | this.rootElement.innerHTML = '' 32 | this.rootElement.classList.add('empty') 33 | return 34 | } 35 | 36 | let sandbox = document.createElement('div') 37 | sandbox.innerHTML = text 38 | 39 | let sanitizedText = sandbox.innerText 40 | let sanitizedHTML = (sanitizedText || '') 41 | .split('\n') 42 | .map((line) => `
${line}
`) 43 | .join('') 44 | 45 | this.rootElement.innerHTML = sanitizedHTML 46 | this.rootElement.classList.remove('empty') 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/client/Editor_test.js: -------------------------------------------------------------------------------- 1 | import Editor from "./Editor" 2 | 3 | export function testEditor(test) { 4 | // We need to append this sandbox to the DOM because 5 | // otherwise innerText omits newlines. 6 | let sandbox = document.createElement('div') 7 | document.body.appendChild(sandbox) 8 | 9 | let editor = new Editor(sandbox) 10 | test.equal('', editor.getContent()) 11 | test.ok(editor.rootElement.classList.contains('empty')) 12 | 13 | editor.setContent('Hello\nWorld') 14 | test.equal('Hello\nWorld', editor.getContent()) 15 | test.ok(!editor.rootElement.classList.contains('empty')) 16 | 17 | sandbox.innerHTML = 'Hello
World
' 18 | test.equal('Hello\nWorld', editor.getContent()) 19 | test.ok(!editor.rootElement.classList.contains('empty')) 20 | 21 | document.body.removeChild(sandbox) 22 | test.done() 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/client/PageData.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as utils from "../shared/utils" 4 | 5 | export default class PageData { 6 | indices: Array; 7 | serieses: Array; 8 | data: Array>; 9 | 10 | static fromJSON(url: string, data: string) { 11 | let ext = utils.getFileExtension(url) 12 | let rows = ext == 'tsv' ? d3.tsv.parseRows(data) : d3.csv.parseRows(data) 13 | return new PageData(rows) 14 | } 15 | 16 | constructor(rows: Array>) { 17 | // Extract field names and build an array of row objects 18 | // with field names as keys. 19 | let fieldNames = rows.shift() 20 | let fields = rows.map((row) => { 21 | return fieldNames.reduce((memo, name, i) => { 22 | memo[name] = row[i] 23 | return memo 24 | }, {}) 25 | }) 26 | 27 | // Build a list of indices. 28 | if (fieldNames.length != 1) { 29 | let indexField = fieldNames.shift() 30 | this.indices = fields.map((row) => row[indexField]) 31 | } else { 32 | this.indices = fields.map((row, i) => `Row ${i + 1}`) 33 | } 34 | 35 | // Build a list of serieses. 36 | this.serieses = fieldNames.map((label, i) => { 37 | return {label: label, seriesIndex: i} 38 | }) 39 | 40 | // Build a list of lists per each column. 41 | this.data = fieldNames.map((label) => { 42 | return fields.map((row, i) => { 43 | return { 44 | x: i, 45 | y: utils.stringToNumber(row[label]), 46 | xLabel: this.indices[i], 47 | yRaw: row[label] 48 | } 49 | }) 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/client/PageData_test.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | import PageData from "./PageData" 4 | 5 | const DATA = [ 6 | ["month","example_one","example_two"], 7 | ["2013-07","2023","5247"], 8 | ["2013-08","3343","2357"] 9 | ] 10 | 11 | const DATA_ONE_COLUMN = [ 12 | ["month"], 13 | ["2013-07"], 14 | ["2013-08"] 15 | ] 16 | 17 | export function testConstructor(test) { 18 | let data = new PageData(DATA) 19 | 20 | test.equal(data.serieses[0].label, "example_one") 21 | test.equal(data.serieses[0].seriesIndex, 0) 22 | test.equal(data.serieses[1].label, "example_two") 23 | test.equal(data.serieses[1].seriesIndex, 1) 24 | 25 | test.equal(data.indices[0], "2013-07") 26 | test.equal(data.indices[1], "2013-08") 27 | 28 | test.equal(data.data.length, 2) 29 | 30 | test.equal(data.data[0].length, 2) 31 | test.equal(data.data[0][0].x, 0) 32 | test.equal(data.data[0][0].xLabel, "2013-07") 33 | test.equal(data.data[0][0].y, 2023) 34 | test.equal(data.data[0][0].yRaw, "2023") 35 | test.equal(data.data[0][1].x, 1) 36 | test.equal(data.data[0][1].xLabel, "2013-08") 37 | test.equal(data.data[0][1].y, 3343) 38 | test.equal(data.data[0][1].yRaw, "3343") 39 | 40 | test.equal(data.data[1].length, 2) 41 | test.equal(data.data[1][0].x, 0) 42 | test.equal(data.data[1][0].xLabel, "2013-07") 43 | test.equal(data.data[1][0].y, 5247) 44 | test.equal(data.data[1][0].yRaw, "5247") 45 | test.equal(data.data[1][1].x, 1) 46 | test.equal(data.data[1][1].xLabel, "2013-08") 47 | test.equal(data.data[1][1].y, 2357) 48 | test.equal(data.data[1][1].yRaw, "2357") 49 | 50 | test.done() 51 | } 52 | 53 | export function testIndices_oneColumn(test) { 54 | let data = new PageData(DATA_ONE_COLUMN) 55 | 56 | test.equal(data.indices[0], "Row 1") 57 | test.equal(data.indices[1], "Row 2") 58 | 59 | test.done() 60 | } 61 | -------------------------------------------------------------------------------- /src/client/charted.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import {PageController} from "./PageController" 4 | 5 | window.__charted = new PageController() 6 | window.__charted.activate() 7 | -------------------------------------------------------------------------------- /src/client/dom.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | // Simple wrappers around DOM API that keep Flow and us happy. 4 | 5 | class Classlist { 6 | add(el: ?Element, name: string) { 7 | if (el) el.classList.add(name) 8 | } 9 | 10 | remove(el: ?Element, name: string) { 11 | if (el) el.classList.remove(name) 12 | } 13 | 14 | enable(el: ?Element, name: string, cond: bool) { 15 | cond ? this.add(el, name) : this.remove(el, name) 16 | } 17 | 18 | toggle(el: ?Element, name: string) { 19 | if (el) { 20 | el.classList.contains(name) ? this.remove(el, name) : this.add(el, name) 21 | } 22 | } 23 | } 24 | 25 | function assert(el: ?Element): Element { 26 | if (!el) throw 'Assertion error' 27 | return el 28 | } 29 | 30 | function remove(el: ?Element) { 31 | if (!el || !el.parentNode) return 32 | el.parentNode.removeChild(el) 33 | } 34 | 35 | function renderFragment(html: string): DocumentFragment { 36 | let fragment = document.createDocumentFragment() 37 | let temp = document.createElement('div') 38 | temp.innerHTML = html 39 | 40 | while (temp.firstChild) { 41 | fragment.appendChild(temp.removeChild(temp.firstChild)) 42 | } 43 | 44 | return fragment 45 | } 46 | 47 | // Like querySelector and querySelectorAll but only for 48 | // js classes. Eventually we shouldn't use any other way 49 | // of getting elements. 50 | 51 | function get(selector: string, root: ?Element): ?Element { 52 | root = root || document.body 53 | 54 | if (root && /^js\-/.test(selector)) { 55 | return root.querySelector('.' + selector) 56 | } 57 | 58 | return null 59 | } 60 | 61 | function getAll(selector: string, root: ?Element): Element[] { 62 | root = root || document.body 63 | 64 | if (root && /^js\-/.test(selector)) { 65 | return Array.prototype.slice.call(root.querySelectorAll('.' + selector)) 66 | } 67 | 68 | return [] 69 | } 70 | 71 | function queryAll(selector: string, root: ?Element): Element[] { 72 | root = root || document.body 73 | 74 | if (root) { 75 | return Array.prototype.slice.call(root.querySelectorAll(selector)) 76 | } 77 | 78 | return [] 79 | } 80 | 81 | function rect(el: Element): ClientRect { 82 | return el.getBoundingClientRect() 83 | } 84 | 85 | export default { 86 | assert: assert, 87 | get: get, 88 | getAll: getAll, 89 | queryAll: queryAll, 90 | rect: rect, 91 | renderFragment: renderFragment, 92 | remove: remove, 93 | classlist: new Classlist() 94 | } 95 | -------------------------------------------------------------------------------- /src/client/templates.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export { 4 | pageSettings, 5 | embedOverlay, 6 | gridSettingsFull, 7 | gridSettingsSplit, 8 | yAxisLabel, 9 | chart, 10 | changeSeriesColor, 11 | legendItem, 12 | moveChart 13 | } 14 | 15 | function pageSettings(): string { 16 | return ` 17 |
18 | 21 | 22 |
23 |
24 | 25 | Download data 26 | 27 | 28 | 31 | 32 |
33 | 34 | 37 | 38 |
39 | 40 |
41 | 42 | 43 |
44 |
45 |
46 | 47 | 48 | Charted home 49 | 50 |
51 |
52 | ` 53 | } 54 | 55 | function embedOverlay(chartId: string): string { 56 | var script = `` 57 | 58 | return ` 59 |
60 |
61 |

Embed this Charted page

62 |

63 | You can add this embed to your website by copying and pasting the HTML code below. 64 |

65 | 66 | 67 |
${script}
68 |
69 |
70 |
71 | ` 72 | } 73 | 74 | function gridSettingsFull(): string { 75 | return ` 76 | 79 | ` 80 | } 81 | 82 | function gridSettingsSplit(): string { 83 | return ` 84 | 87 | ` 88 | } 89 | 90 | function yAxisLabel(interval: {top: string, display: string}): string { 91 | return ` 92 |
${interval.display}
93 | ` 94 | } 95 | 96 | function chart(params: {editable: boolean}): string { 97 | var editableAttribute = '' 98 | var editableButtons = '' 99 | 100 | if (params.editable) { 101 | editableAttribute = 'contenteditable="true"' 102 | editableButtons = ` 103 | 114 | ` 115 | } 116 | 117 | return ` 118 |
119 |
120 |

121 |
122 |
123 | 124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 | 140 | 144 |
145 | ` 146 | } 147 | 148 | function changeSeriesColor(params: {seriesIndex: number, colorHex: string}): string { 149 | return ` 150 |
151 |

Change color:

152 |

153 | ${params.colorHex} 154 |

155 | 156 |
157 | ` 158 | } 159 | 160 | function legendItem(label: {editable: boolean, label: string, color: string, seriesIndex: number}): string { 161 | var editableAttribute = '' 162 | var editableButtons = '' 163 | 164 | if (label.editable) { 165 | editableAttribute = 'contenteditable="true"' 166 | editableButtons = ` 167 | ` 170 | } 171 | 172 | return ` 173 |
  • 174 |
    175 | ${label.label} 176 |
    177 | 180 | ${editableButtons} 181 |
  • 182 | ` 183 | } 184 | 185 | function moveChart(params: {otherCharts: Array, series: Array, newChartIndex: number, position: number}): string { 186 | var chartList = params.otherCharts.map(function (chart) { 187 | return ` 188 | 189 | ${chart.title} 190 | 191 | ` 192 | }).join('\n') 193 | 194 | var newChartButton = '' 195 | if (params.series.length > 1) { 196 | newChartButton = ` 197 | 198 | New chart 199 | 200 | ` 201 | } 202 | 203 | return ` 204 |
    205 |

    Move to:

    206 | ${chartList} 207 | ${newChartButton} 208 | 209 |
    210 | ` 211 | } 212 | -------------------------------------------------------------------------------- /src/client/tests.js: -------------------------------------------------------------------------------- 1 | import * as tests_PageData from "./PageData_test" 2 | import * as tests_ChartParameters from "./ChartParameters_test" 3 | import * as tests_sha1 from "../shared/sha1_test" 4 | import * as tests_Editor from "./Editor_test"; 5 | 6 | nodeunit.run({ 7 | 'PageData': tests_PageData, 8 | 'ChartParameters': tests_ChartParameters, 9 | 'sha1': tests_sha1, 10 | "Editor": tests_Editor 11 | }) 12 | -------------------------------------------------------------------------------- /src/public/embed.js: -------------------------------------------------------------------------------- 1 | var CHARTED 2 | 3 | (function () { 4 | if (CHARTED && CHARTED.createIframes && CHARTED.onMessage) { 5 | CHARTED.createIframes() 6 | return 7 | } 8 | 9 | CHARTED = { 10 | createIframes: function () { 11 | var scripts = document.getElementsByTagName('script') 12 | var i, chartId, iframe, link 13 | 14 | for (var i = 0; i < scripts.length; i++) { 15 | script = scripts[i] 16 | 17 | if (script.getAttribute('data-charted-processed')) { 18 | continue 19 | } 20 | 21 | chartId = script.getAttribute('data-charted') 22 | if (!chartId) { 23 | continue 24 | } 25 | 26 | iframe = document.createElement('iframe') 27 | iframe.setAttribute('id', 'charted-' + chartId) 28 | iframe.setAttribute('height', '600px') 29 | iframe.setAttribute('width', '100%') 30 | iframe.setAttribute('scrolling', 'yes') 31 | iframe.style.border = 'solid 1px #ccc' 32 | 33 | link = document.createElement('a') 34 | link.setAttribute('href', script.getAttribute('src')) 35 | iframe.setAttribute('src', link.origin + '/embed/' + chartId) 36 | 37 | script.parentNode.insertBefore(iframe, script.nextSibling) 38 | script.setAttribute('data-charted-processed', 'yes') 39 | } 40 | }, 41 | 42 | onMessage: function (ev) { 43 | 44 | // Use the standardized version of postMessage iframe.resize. 45 | var data 46 | try { 47 | data = JSON.parse(ev.data) 48 | } catch (e) { 49 | return 50 | } 51 | 52 | if (data.context !== 'iframe.resize') { 53 | return 54 | } 55 | 56 | var iframe = document.querySelector('#charted-' + data.chartId) 57 | 58 | if (!iframe) { 59 | return 60 | } 61 | 62 | iframe.style.transition = 'height 200ms' 63 | iframe.style.MozTransition = 'height 200ms' 64 | iframe.style.WebkitTransition = 'height 200ms' 65 | iframe.style.height = data.height + 'px' 66 | } 67 | } 68 | 69 | CHARTED.createIframes() 70 | if (window.postMessage && window.addEventListener) { 71 | window.addEventListener('message', CHARTED.onMessage) 72 | } 73 | }()) 74 | -------------------------------------------------------------------------------- /src/public/example.csv: -------------------------------------------------------------------------------- 1 | "month","example_one","example_two" 2 | "2013-07","2023","5247" 3 | "2013-08","3343","2357" 4 | "2013-09","1222","4395" 5 | "2013-10","1827","2586" 6 | "2013-11","2110","4179" 7 | "2013-12","3344","4450" 8 | "2014-01","18026","4428" 9 | "2014-02","12388","11877" 10 | "2014-03","4374","5214" 11 | "2014-04","3713","4522" 12 | "2014-05","3412","5542" 13 | "2014-06","4847","4832" 14 | "2014-07","7181","6812" 15 | "2014-08","2124","5681" 16 | "2014-09","2165","2332" 17 | "2014-10","1691","1062" 18 | "2014-11","1239","6015" 19 | "2014-12","7706","1421" 20 | "2015-01","5228","4402" 21 | "2015-02","2918","8130" 22 | "2015-03","5067","3160" 23 | "2015-04","8682","4728" -------------------------------------------------------------------------------- /src/public/images/icon-back-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /src/public/images/icon-back.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /src/public/images/icon-color-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /src/public/images/icon-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /src/public/images/icon-column-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/public/images/icon-column.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/public/images/icon-download-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/public/images/icon-download.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/public/images/icon-embed-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /src/public/images/icon-embed.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /src/public/images/icon-full-screen-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/public/images/icon-full-screen.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/public/images/icon-line-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /src/public/images/icon-line.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /src/public/images/icon-move-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/public/images/icon-move.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/public/images/icon-plus-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/public/images/icon-plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/public/images/icon-round-off-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/public/images/icon-round-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/public/images/icon-round-on-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/public/images/icon-round-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/public/images/icon-settings-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 39 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/public/images/icon-settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 39 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/public/images/icon-split-screen-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /src/public/images/icon-split-screen.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /src/public/images/icon-x.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /src/public/images/spinner-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /src/public/images/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /src/public/tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Charted Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

    Charted Tests

    19 | 20 | 21 | -------------------------------------------------------------------------------- /src/server/charted.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | "use strict" 4 | 5 | import url from "url" 6 | import path from "path" 7 | import request from "request" 8 | import express from "express" 9 | import fs from 'fs' 10 | import handlebars from "handlebars" 11 | import bodyParser from "body-parser" 12 | import prepare from "./prepare" 13 | import FileDb from "./db.js" 14 | import sha1 from "../shared/sha1" 15 | import * as utils from "../shared/utils" 16 | 17 | type TemplateParams = { 18 | ENV?: Object, 19 | links?: Array, 20 | dataUrl?: string 21 | } 22 | 23 | export default class ChartedServer { 24 | address: any; 25 | staticRoot: string; 26 | store: FileDb; 27 | app: express$Application; 28 | 29 | env = {dev: false}; 30 | port = Number(process.env.PORT) || 3000; 31 | links = [ 32 | {text: 'GitHub', href: 'https://github.com/charted-co/charted'}, 33 | ]; 34 | 35 | static start(port: number, staticRoot: string, db: FileDb) { 36 | return new ChartedServer() 37 | .withPort(port) 38 | .withStaticRoot(staticRoot) 39 | .withStore(db) 40 | .start() 41 | } 42 | 43 | constructor() { 44 | this.app = express() 45 | } 46 | 47 | withPort(port: number): ChartedServer { 48 | this.port = port 49 | return this 50 | } 51 | 52 | withStaticRoot(path: string): ChartedServer { 53 | this.staticRoot = path 54 | return this 55 | } 56 | 57 | withForceSSL(): ChartedServer { 58 | this.app.all('*', (req: express$Request, res: express$Response, next: express$NextFunction) => { 59 | if (req.hostname != 'localhost' && req.protocol == 'http') { 60 | res.redirect(`https://${req.headers.host}${req.originalUrl}`); 61 | return; 62 | } 63 | 64 | return next() 65 | }) 66 | 67 | return this 68 | } 69 | 70 | withLinks(links: Array): ChartedServer { 71 | this.links = links 72 | return this 73 | } 74 | 75 | withApp(path: string, app: express$Application): ChartedServer { 76 | this.app.use(path, app) 77 | return this 78 | } 79 | 80 | withStore(store: FileDb): ChartedServer { 81 | this.store = store 82 | return this 83 | } 84 | 85 | start() { 86 | if (!this.staticRoot) throw new Error('You have to set static root') 87 | if (!this.store) throw new Error('You have to specify a store') 88 | 89 | return new Promise((resolve) => { 90 | const app = this.app 91 | 92 | app.enable('trust proxy') 93 | app.use(bodyParser.json()) 94 | app.use(express.static(this.staticRoot)) 95 | 96 | app.get('/', this.getHome.bind(this)) 97 | app.get('/c/:id', this.getChart.bind(this)) 98 | app.get('/embed/:id', this.getChart.bind(this)) 99 | app.post('/c/:id', this.saveChart.bind(this)) 100 | app.get('/load', this.loadChart.bind(this)) 101 | app.get('/oembed', this.getOembed.bind(this)) 102 | 103 | let server = app.listen(this.port, () => { 104 | this.address = server.address() 105 | resolve(this) 106 | }) 107 | }) 108 | } 109 | 110 | render(name: string, options: ?TemplateParams): Promise { 111 | return new Promise((resolve, reject) => { 112 | const fp = path.join(__dirname, '..', 'templates', name) 113 | 114 | fs.readFile(fp, 'utf8', (err, data) => { 115 | if (err) { 116 | reject(err) 117 | return 118 | } 119 | 120 | options = options || {} 121 | options.ENV = this.env 122 | const html = handlebars.compile(data)(options) 123 | resolve(html) 124 | }) 125 | }) 126 | } 127 | 128 | getHome(req: express$Request, res: express$Response) { 129 | this.render('index.html', {links: this.links}) 130 | .then((html) => { 131 | res.status(200).send(html) 132 | }) 133 | } 134 | 135 | getChart(req: express$Request, res: express$Response) { 136 | this.store.get(req.params.id).then((params) => { 137 | if (!params) { 138 | this.notFound(res, `chart ${req.params.id} was not found.`) 139 | return 140 | } 141 | 142 | this.render('index.html', {dataUrl: params.dataUrl,}) 143 | .then((html) => { 144 | res.status(200).send(html) 145 | }) 146 | }) 147 | } 148 | 149 | loadChart(req: express$Request, res: express$Response) { 150 | if (req.query.url && typeof req.query.url == 'string') { 151 | let parsed = url.parse(req.query.url, /* parse query string */ true) 152 | let chartUrl = url.format(prepare(parsed)) 153 | let params = {dataUrl: chartUrl} 154 | 155 | this.store.set(utils.getChartId(params), params) 156 | .then(() => this.respondWithChart(res, params)) 157 | 158 | return 159 | } 160 | 161 | if (req.query.id && typeof req.query.id == 'string') { 162 | const id:string = req.query.id 163 | this.store.get(id) 164 | .then((params) => { 165 | if (!params) { 166 | this.notFound(res, `chart ${id} was not found.`) 167 | return 168 | } 169 | 170 | this.respondWithChart(res, params) 171 | }) 172 | 173 | return 174 | } 175 | 176 | this.badRequest(res, 'either url or id is required') 177 | return 178 | } 179 | 180 | saveChart(req: express$Request, res: express$Response) { 181 | let id = req.params.id 182 | if (utils.getChartId(req.body) != id) { 183 | this.badRequest(res, 'id and params are out of sync.') 184 | return 185 | } 186 | 187 | this.store.set(id, req.body) 188 | res.setHeader('Content-Type', 'application/json') 189 | res.status(200) 190 | res.end(JSON.stringify({status: 'ok'})) 191 | } 192 | 193 | getOembed(req: express$Request, res: express$Response) { 194 | // oEmbed requires a URL. 195 | if (!req.query.url) { 196 | this.badRequest(res, 'URL Required.') 197 | return 198 | } 199 | 200 | // Grab the id from the url. 201 | const id = utils.parseChartId(req.query.url) 202 | 203 | if (!id) { 204 | this.badRequest(res, 'Could not parse ID from url') 205 | return 206 | } 207 | 208 | // get the chart. 209 | this.store.get(id) 210 | .then((params) => { 211 | if (!params) { 212 | this.notFound(res, `chart ${id} was not found.`) 213 | return 214 | } 215 | 216 | res.setHeader('Content-Type', 'application/json') 217 | res.status(200) 218 | 219 | res.end(JSON.stringify({ 220 | type: 'rich', 221 | version: '1.0', 222 | width: 1280, 223 | height: 600, 224 | title: "Charted", 225 | html: `` 226 | })) 227 | }) 228 | } 229 | 230 | respondWithChart(res: express$Response, params: t_CHART_PARAM) { 231 | request(params.dataUrl, (err, resp, body) => { 232 | if (err) { 233 | this.badRequest(res, err) 234 | return 235 | } 236 | 237 | if (resp.statusCode != 200) { 238 | this.badRequest(res, `Received HTTP-${resp.statusCode} status code from ${params.dataUrl}`) 239 | return 240 | } 241 | 242 | res.setHeader('Content-Type', 'application/json') 243 | res.status(200) 244 | res.end(JSON.stringify({params: params, data: body})) 245 | }) 246 | } 247 | 248 | notFound(res: express$Response, message: string) { 249 | res.status(404) 250 | res.end(`Not Found: ${message}`) 251 | } 252 | 253 | badRequest(res: express$Response, message: string) { 254 | res.status(400) 255 | res.end(`Bad Request: ${message}`) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/server/db.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | "use strict" 4 | 5 | import fs from "fs" 6 | 7 | export default class FileDb { 8 | path: string; 9 | 10 | constructor(path: string) { 11 | try { 12 | fs.accessSync(path) 13 | } catch (err) { 14 | if (err.code != 'ENOENT') { 15 | throw err 16 | } 17 | 18 | fs.writeFileSync(path, '{}', 'utf8') 19 | } 20 | 21 | this.path = path 22 | } 23 | 24 | getAll(): Promise { 25 | return new Promise((resolve, reject) => { 26 | fs.readFile(this.path, 'utf8', (err, data) => { 27 | if (err) reject(err) 28 | resolve(JSON.parse(data)) 29 | }) 30 | }) 31 | } 32 | 33 | get(key: string): Promise { 34 | return this.getAll().then((data) => data[key]) 35 | } 36 | 37 | set(key: string, value: any): Promise { 38 | return this.getAll().then((data) => { 39 | data[key] = value 40 | 41 | return new Promise((resolve, reject) => { 42 | fs.writeFile(this.path, JSON.stringify(data), 'utf8', (err) => { 43 | if (err) reject(err) 44 | resolve() 45 | }) 46 | }) 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/server/db_test.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | "use strict" 4 | 5 | import fs from "fs" 6 | import path from "path" 7 | import FileDb from "./db.js" 8 | 9 | const PATH = path.join(__dirname, '..', '..', '.charted_test_db') 10 | 11 | export function tearDown(callback) { 12 | fs.unlinkSync(PATH) 13 | callback() 14 | } 15 | 16 | export function testInitialize(test) { 17 | let db = new FileDb(PATH) 18 | db.getAll() 19 | .then((data) => { 20 | test.deepEqual(data, {}) 21 | test.done() 22 | }) 23 | } 24 | 25 | export function testAccess(test) { 26 | let db = new FileDb(PATH) 27 | db.set('12345', {name: 'Charted'}) 28 | .then(() => db.get('12345')) 29 | .then((data) => { 30 | test.equal(data.name, 'Charted') 31 | test.done() 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /src/server/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | 'use strict' 4 | 5 | import path from "path" 6 | import express from "express" 7 | import ChartedServer from './charted' 8 | import FileDb from "./db" 9 | 10 | const demo = express() 11 | demo.get('/', (req: express$Request, res: express$Response) => { 12 | res.send('Hello, World!') 13 | }) 14 | 15 | new ChartedServer() 16 | .withStaticRoot(path.join(__dirname, '..', 'client')) 17 | .withStore(new FileDb(path.join(__dirname, '..', '..', '.charted_db'))) 18 | .withApp('/demo', demo) 19 | .start() 20 | .then((server: ChartedServer) => { 21 | server.env.dev = true 22 | let address = server.address 23 | console.log(`Running at ${address.address}:${address.port}`) 24 | }) 25 | -------------------------------------------------------------------------------- /src/server/prepare.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | "use strict" 4 | 5 | import url from "url" 6 | 7 | function googledocs(uri) { 8 | // the gid is the specific sheet within the document 9 | var hash = uri.hash || '' 10 | var path = uri.pathname || '' 11 | var gid = hash.indexOf('gid=') > -1 ? hash.substring(hash.indexOf('gid=') + 4) : 0 12 | var doc = path.match(/^\/spreadsheets\/d\/(.*)\//) 13 | 14 | if (doc) doc = doc[1] 15 | else return uri // we couldn't extract document id 16 | 17 | uri.pathname = '/spreadsheets/d/' + doc + '/export' 18 | uri.query = uri.query || {} 19 | uri.query.gid = gid 20 | uri.query.format = 'csv' 21 | uri.hash = '' 22 | 23 | // url.format ignores 'query' if 'search' is present 24 | // (see https://nodejs.org/api/url.html) 25 | delete uri.search 26 | 27 | return uri 28 | } 29 | 30 | function dropbox(uri) { 31 | uri.query = uri.query || {} 32 | 33 | if (uri.query.dl === '0') { 34 | uri.query.dl = '1' 35 | } 36 | 37 | if (!uri.query.dl) { 38 | uri.query.raw = 1 39 | } 40 | 41 | // url.format ignores 'query' if 'search' is present 42 | // (see https://nodejs.org/api/url.html) 43 | delete uri.search 44 | 45 | return uri 46 | } 47 | 48 | export default function(uri: any): any { 49 | if (uri.host == 'docs.google.com' && uri.pathname && uri.pathname.startsWith('/spreadsheets/d/')) { 50 | return googledocs(uri) 51 | } 52 | 53 | if (uri.host == 'dropbox.com' || uri.host == 'www.dropbox.com') { 54 | return dropbox(uri) 55 | } 56 | 57 | return uri 58 | } 59 | -------------------------------------------------------------------------------- /src/server/prepare_test.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | "use strict" 4 | 5 | import url from "url" 6 | import prepare from "./prepare.js" 7 | 8 | export function testBasic(test) { 9 | var address = 'http://charted.co/' 10 | var parsed = url.parse(address) 11 | 12 | test.equal(url.format(prepare(parsed)), address) 13 | test.done(); 14 | } 15 | 16 | export function testDropbox(test) { 17 | test.equal(parse('http://dropbox.com/s/abcdef/my.csv'), 18 | 'http://dropbox.com/s/abcdef/my.csv?raw=1') 19 | 20 | test.equal(parse('http://www.dropbox.com/s/abcdef/my.csv'), 21 | 'http://www.dropbox.com/s/abcdef/my.csv?raw=1') 22 | 23 | test.equal(parse('https://www.dropbox.com/s/pbrtd50jpwlx5n8/charted-sample.csv?dl=0'), 24 | 'https://www.dropbox.com/s/pbrtd50jpwlx5n8/charted-sample.csv?dl=1') 25 | 26 | test.done() 27 | } 28 | 29 | export function testGoogleSpreadsheets(test) { 30 | test.equal( 31 | parse('https://docs.google.com/spreadsheets/d/1N9Vpl941bR-yN_ZlMHvlc4soDrCxswsORpvjDTbKaiw/edit#gid=2090366728'), 32 | 'https://docs.google.com/spreadsheets/d/1N9Vpl941bR-yN_ZlMHvlc4soDrCxswsORpvjDTbKaiw/export?gid=2090366728&format=csv') 33 | 34 | test.equal( 35 | parse('https://docs.google.com/spreadsheets/d/1N9Vpl941bR-yN_ZlMHvlc4soDrCxswsORpvjDTbKaiw/edit'), 36 | 'https://docs.google.com/spreadsheets/d/1N9Vpl941bR-yN_ZlMHvlc4soDrCxswsORpvjDTbKaiw/export?gid=0&format=csv') 37 | 38 | // https://github.com/mikesall/charted/issues/77 39 | test.equal( 40 | parse('https://docs.google.com/spreadsheets/d/1i5INeh718Hj2AiKbiRq5eRL5qiekHR9Lzvs4NjCNmtc/edit?usp=sharing'), 41 | 'https://docs.google.com/spreadsheets/d/1i5INeh718Hj2AiKbiRq5eRL5qiekHR9Lzvs4NjCNmtc/export?usp=sharing&gid=0&format=csv' 42 | ) 43 | 44 | test.done() 45 | } 46 | 47 | function parse(uri) { 48 | return url.format(prepare(url.parse(uri, true))) 49 | } 50 | -------------------------------------------------------------------------------- /src/shared/sha1.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /** 4 | * SHA-1 implementation. 5 | * 6 | * Adopted from SHA-1 implementation in JavaScript by Chris Veness. 7 | * (c) Chris Veness 2002-2014 / MIT Licence 8 | * 9 | * See: 10 | * http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html 11 | * http://csrc.nist.gov/groups/ST/toolkit/examples.html 12 | */ 13 | 14 | /** 15 | * Generates SHA-1 hash of string. 16 | */ 17 | export default function sha1(msg: string, short: boolean = false): string { 18 | // convert string to UTF-8, as SHA only deals with byte-streams 19 | msg = utf8Encode(msg); 20 | 21 | // constants [§4.2.1] 22 | var K = [ 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 ]; 23 | 24 | // PREPROCESSING 25 | 26 | msg += String.fromCharCode(0x80); // add trailing '1' bit (+ 0's padding) to string [§5.1.1] 27 | 28 | // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1] 29 | var l = msg.length/4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length 30 | var N = Math.ceil(l/16); // number of 16-integer-blocks required to hold 'l' ints 31 | var M = new Array(N); 32 | 33 | for (var i=0; i>> 32, but since JS converts 42 | // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators 43 | M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14]); 44 | M[N-1][15] = ((msg.length-1)*8) & 0xffffffff; 45 | 46 | // set initial hash value [§5.3.1] 47 | var H0 = 0x67452301; 48 | var H1 = 0xefcdab89; 49 | var H2 = 0x98badcfe; 50 | var H3 = 0x10325476; 51 | var H4 = 0xc3d2e1f0; 52 | 53 | // HASH COMPUTATION [§6.1.2] 54 | 55 | var W = new Array(80); var a, b, c, d, e; 56 | for (var i=0; i>>(32-n)); 107 | }; 108 | 109 | 110 | /** 111 | * Hexadecimal representation of a number. 112 | */ 113 | function toHexStr(n: number): string { 114 | // note can't use toString(16) as it is implementation-dependant, 115 | // and in IE returns signed numbers when used on full words 116 | var s="", v; 117 | for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); } 118 | return s; 119 | }; 120 | 121 | 122 | /* 123 | * Encode utf8 multi-byte string to utf8 124 | */ 125 | function utf8Encode(str: string): string { 126 | // $FlowFixMe: See http://monsur.hossa.in/2012/07/20/utf-8-in-javascript.html 127 | return unescape(encodeURIComponent(str)); 128 | } 129 | -------------------------------------------------------------------------------- /src/shared/sha1_test.js: -------------------------------------------------------------------------------- 1 | import sha1 from "./sha1" 2 | 3 | export function testSHA1(test) { 4 | test.equal('a9993e364706816aba3e25717850c26c9cd0d89d', sha1('abc')) 5 | test.equal('d271a54bb67c1af0ad791924e986cb2ec431f556', sha1('charted')) 6 | test.equal('a9993e3', sha1('abc', /* short */ true)) 7 | test.equal('d271a54', sha1('charted', /* short */ true)) 8 | test.done() 9 | } 10 | -------------------------------------------------------------------------------- /src/shared/utils.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import sha1 from "./sha1" 4 | 5 | export { 6 | getChartId, 7 | parseChartId, 8 | parseQueryString, 9 | camelToHyphen, 10 | stringToNumber, 11 | getRoundedValue, 12 | getNiceIntervals, 13 | getTrimmedExtent, 14 | getFileExtension 15 | } 16 | 17 | function getChartId(params: any): string { 18 | return sha1(JSON.stringify(params), /* short */ true) 19 | } 20 | 21 | /* Returns the Chart ID from a given URL */ 22 | function parseChartId(url: string|Array): ?string { 23 | if (typeof url != 'string') url = url[0] 24 | const match = (/\/(?:c|embed)\/(\w+)(?:|\?.*)?/).exec(url) 25 | return match ? match[1] : null 26 | } 27 | 28 | function log10Floor(val: number): number { 29 | return Math.floor(Math.log(val) / Math.LN10) 30 | } 31 | 32 | function parseQueryString(qs: string): Object { 33 | var string = qs.slice(1) 34 | if (!string) return {} 35 | 36 | var queries = string.split("&") 37 | var params = {} 38 | queries.forEach(function (query) { 39 | var pair = query.split("=") 40 | if (pair.length === 1) { 41 | params.data = JSON.parse(decodeURIComponent(pair[0])) 42 | } else { 43 | params[pair[0]] = pair[1] 44 | } 45 | }) 46 | 47 | return params 48 | } 49 | 50 | function camelToHyphen(str: string): string { 51 | return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() 52 | } 53 | 54 | function stringToNumber(str: string): number { 55 | return Number(String(str).replace(/[^0-9\.\-]/g, '') || 0) 56 | } 57 | 58 | function addCommaSeparator(val: string): string { 59 | return val.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') 60 | } 61 | 62 | function getRoundedValue(val: number, extent: Array): string { 63 | // round to the same decimal if all values are within 2 orders of magnitude (e.g., 10-1,000) 64 | var maxOrdersDiff = 2 65 | 66 | // always show 3 digits 67 | var digitsVisible = 3 68 | 69 | // find how many orders apart the max/min are to determine how much to round 70 | var ordersLow = log10Floor(Math.abs(extent[1])) 71 | var ordersHigh = log10Floor(Math.abs(extent[0])) 72 | var ordersDiff = Math.abs(ordersLow - ordersHigh) 73 | var ordersMax = Math.max(ordersLow, ordersHigh) 74 | var ordersToUse = ordersDiff <= maxOrdersDiff ? ordersMax : log10Floor(Math.abs(val)) 75 | 76 | return roundToDecimalOrder(val, ordersToUse, digitsVisible) 77 | } 78 | 79 | function roundToDecimalOrder(val: number, decimalOrder: number, digitsVisible: number): string { 80 | if (decimalOrder < -3) { 81 | // when the val is below 0.001, just show the full value 82 | return val.toString() 83 | } else if (decimalOrder < 3) { 84 | // when the val is from 0.001 to 99, don't show extra decimals 85 | var roundToDigits = Math.max(digitsVisible - decimalOrder - 1, 0) 86 | return val.toFixed(roundToDigits) 87 | } 88 | var units = ['K', 'M', 'B', 'T'] 89 | var commasToUse = Math.min(Math.floor(decimalOrder / 3), units.length) 90 | var divisor = Math.pow(1000, commasToUse) 91 | var decimals = Math.max(0, (commasToUse * 3) - decimalOrder + digitsVisible - 1) 92 | var thisUnit = units[commasToUse - 1] 93 | var thisVal = addCommaSeparator((val / divisor).toFixed(decimals)) 94 | return thisVal + thisUnit 95 | } 96 | 97 | function getNiceIntervals(range: Array, height: number): Array { 98 | var rangeWithZero = [Math.min(0, range[0]), Math.max(0, range[1])] 99 | var fullRange = Math.max(rangeWithZero[1] - rangeWithZero[0]) 100 | 101 | // include no more than 5 ticks spaced at least 50px apart 102 | var minDistance = 40 103 | var maxTicks = 5 104 | var maxPotentialTicks = Math.floor(Math.min(height / minDistance, maxTicks)) 105 | 106 | // get the smallest nice interval value that produces no more than the max potential ticks 107 | var minInterval = fullRange/maxPotentialTicks 108 | var minMultipleOf10 = Math.pow(10, log10Floor(minInterval) + 1) 109 | var interval = minMultipleOf10; 110 | 111 | [2, 4, 5, 10].forEach(function (divisor) { 112 | var thisInterval = minMultipleOf10 / divisor 113 | interval = thisInterval >= minInterval ? thisInterval : interval 114 | }) 115 | 116 | // get the appropriate digits to round the labels 117 | var intervalOrders = log10Floor(Math.abs(interval)) 118 | var maxOrders = log10Floor(Math.max(Math.abs(rangeWithZero[0]), Math.abs(rangeWithZero[1]))) 119 | var extraDigit = interval / Math.pow(10, intervalOrders) === 2.5 ? 1 : 0 // need extra digit if it's a quarter interval 120 | var digitsToUse = 1 + maxOrders - intervalOrders + extraDigit 121 | 122 | // get the intervals 123 | var niceIntervals = [] 124 | var firstInterval = Math.ceil(rangeWithZero[0] / interval) * interval 125 | var currentInterval = firstInterval 126 | while (currentInterval < (range[1] + interval)) { 127 | var intervalObject = { 128 | value: currentInterval, 129 | displayString: currentInterval === 0 ? '0' : roundToDecimalOrder(currentInterval, maxOrders, digitsToUse), 130 | rawString: currentInterval === 0 ? '0' : roundToDecimalOrder(currentInterval, Math.min(0, maxOrders), 0) 131 | } 132 | currentInterval += interval 133 | niceIntervals.push(intervalObject) 134 | } 135 | 136 | return niceIntervals 137 | } 138 | 139 | function getTrimmedExtent(array: Array): Array { 140 | var firstNonEmptyItem = 0 141 | var lastNonEmptyItem = 0 142 | 143 | array.forEach(function (value, i) { 144 | var isEmpty = !value || value.toLowerCase() === 'null' 145 | 146 | if (isEmpty && i === firstNonEmptyItem) { 147 | firstNonEmptyItem = i + 1 148 | } else if (!isEmpty) { 149 | lastNonEmptyItem = i 150 | } 151 | }) 152 | 153 | return [firstNonEmptyItem, lastNonEmptyItem] 154 | } 155 | 156 | function getFileExtension(fileString: string): string { 157 | // remove any url parameters 158 | var fileStringWithoutParams = fileString.substring(0, fileString.indexOf('?')) 159 | var fileExtention = fileStringWithoutParams.split('.').pop() 160 | return fileExtention.toLowerCase() 161 | } 162 | -------------------------------------------------------------------------------- /src/styles/chart-info.less: -------------------------------------------------------------------------------- 1 | .chart-info{ 2 | padding: 12px; 3 | position: relative; 4 | width: 100%; 5 | } 6 | .chart-description{ 7 | font-size: @fontSize-smaller; 8 | line-height: @lineHeight-tight; 9 | padding: 20px; 10 | text-align: center; 11 | } 12 | .info-input{ 13 | transition: background-color 0.3s ease, opacity 0.2s ease; 14 | border-radius: 4px; 15 | overflow: hidden; 16 | } 17 | .info-input:hover, 18 | .info-input:focus{ 19 | background-color: @gray-lightest; 20 | } 21 | .dark .info-input:hover, 22 | .dark .info-input:focus{ 23 | background-color: @gray-darker; 24 | } 25 | .embed .info-input:hover, 26 | .embed .info-input:focus, 27 | .dark.embed .info-input:hover, 28 | .dark.embed .info-input:focus{ 29 | background-color: transparent; 30 | } 31 | .title{ 32 | color: @gray-darker; 33 | font-size: @fontSize-large; 34 | padding: 8px; 35 | } 36 | .dark .title{ 37 | color: white; 38 | } 39 | .note{ 40 | color: @gray; 41 | padding: 8px; 42 | } 43 | .dark .note{ 44 | color: @gray-dark; 45 | } 46 | .empty.note{ 47 | display: none; 48 | opacity: 0; 49 | } 50 | .active .empty.note{ 51 | opacity: 1; 52 | } 53 | .note::before{ 54 | color: @gray; 55 | content: "add a note"; 56 | display: none; 57 | opacity: 0.5; 58 | width: 100%; 59 | } 60 | .empty.note::before{ 61 | display: block; 62 | } 63 | .note:focus::before{ 64 | display: none; 65 | } 66 | 67 | 68 | @media screen and (min-width: @width-tablet) { 69 | .chart-info{ 70 | height: 100%; 71 | right: 0; 72 | padding: 0; 73 | position: absolute; 74 | top: 0; 75 | width: @sidebar-width-small; 76 | } 77 | .chart-description{ 78 | padding: 16px 10px 8px; 79 | position: absolute; 80 | right: 0; 81 | text-align: left; 82 | top: 0; 83 | width: @sidebar-width-small; 84 | z-index: 2; 85 | } 86 | .empty.note{ 87 | display: block; 88 | } 89 | .embed .empty.note{ 90 | display: none; 91 | } 92 | } 93 | 94 | 95 | @media screen and (min-width: @width-netbook) { 96 | .chart-info{ 97 | width: @sidebar-width-large; 98 | } 99 | .chart-description{ 100 | font-size: @fontSize-base; 101 | width: @sidebar-width-large; 102 | } 103 | .title{ 104 | font-size: @fontSize-larger; 105 | } 106 | } 107 | 108 | @media screen and (min-width: @width-netbook) and (max-width: @width-desktop){ 109 | .chart-grid .chart-info{ 110 | width: @sidebar-width-small; 111 | } 112 | .chart-grid .chart-description{ 113 | font-size: @fontSize-smaller; 114 | width: @sidebar-width-small; 115 | } 116 | .chart-grid .title{ 117 | font-size: @fontSize-large; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/styles/chart-options.less: -------------------------------------------------------------------------------- 1 | .chart-options{ 2 | font-size: 0; 3 | margin: 0 auto 10px; 4 | text-align: center; 5 | width: 100%; 6 | } 7 | 8 | @media screen and (min-width: @width-tablet) { 9 | .chart-options{ 10 | transition: all 1s ease; 11 | bottom: 0; 12 | opacity: 0; 13 | margin: 0; 14 | position: absolute; 15 | text-align: right; 16 | right: 4px; 17 | } 18 | .active .chart-options{ 19 | transition: all 0.2s ease; 20 | opacity: 1; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/styles/chart-plot.less: -------------------------------------------------------------------------------- 1 | // chart containers 2 | .pre-load .chart{ 3 | display: none; 4 | } 5 | .chart-wrapper{ 6 | float: left; 7 | overflow: hidden; 8 | position: relative; 9 | } 10 | .chart-wrapper ~ .chart-wrapper{ 11 | border-top: 4px solid @gray-lightest; 12 | } 13 | .chart-grid .chart-wrapper:nth-child(2n+1){ 14 | border-right: 4px solid @gray-lightest; 15 | } 16 | .chart-grid .chart-wrapper{ 17 | border-bottom: 4px solid @gray-lightest; 18 | } 19 | .dark .chart-wrapper ~ .chart-wrapper{ 20 | border-top: 4px solid @gray-darkest; 21 | } 22 | .dark.chart-grid .chart-wrapper:nth-child(2n+1){ 23 | border-right: 4px solid @gray-darkest; 24 | } 25 | .dark.chart-grid .chart-wrapper{ 26 | border-bottom: 4px solid @gray-darkest; 27 | } 28 | .chart-grid .chart-wrapper.bottom-row, 29 | .dark.chart-grid .chart-wrapper.bottom-row{ 30 | border-bottom: none; 31 | } 32 | .chart-grid .chart-wrapper ~ .chart-wrapper, 33 | .dark.chart-grid .chart-wrapper ~ .chart-wrapper{ 34 | border-top: none; 35 | } 36 | .chart-plot-outer-container{ 37 | height: 360px; 38 | font-size: 0; 39 | overflow: hidden; 40 | position: relative; 41 | width: 100%; 42 | } 43 | .chart-plot-inner-container{ 44 | bottom: 0; 45 | left: @y-axis-w; 46 | position: absolute; 47 | right: 0; 48 | top: 0; 49 | } 50 | .chart-height{ 51 | bottom: @x-axis-h; 52 | position: absolute; 53 | top: @selected-value-h-small; 54 | } 55 | .chart-plot{ 56 | width: 100%; 57 | z-index: 2; 58 | } 59 | .zero-line-container{ 60 | left: 0; 61 | width: 100%; 62 | } 63 | .zero-line{ 64 | border-top: 1px solid @gray-lighter; 65 | left: 0; 66 | margin-top: -1px; 67 | position: absolute; 68 | width: 100%; 69 | } 70 | .zero-line.hidden{ 71 | display: none; 72 | } 73 | .dark .zero-line{ 74 | border-top: 1px solid @gray-darker; 75 | } 76 | .x-axis{ 77 | border-top: 1px solid @gray-lighter; 78 | bottom: 0; 79 | color: @gray-dark; 80 | font-size: @fontSize-smallest; 81 | height: @x-axis-h; 82 | left: 0; 83 | padding: 15px 8px 0; 84 | position: absolute; 85 | width: 100%; 86 | z-index: 2; 87 | } 88 | .dark .x-axis{ 89 | border-top: 1px solid @gray-darker; 90 | color: @gray; 91 | } 92 | .x-beginning{ 93 | transition: all 0.4s ease; 94 | float: left; 95 | opacity: 1; 96 | padding: 0 30px 0 0; 97 | } 98 | .x-end{ 99 | transition: all 0.4s ease; 100 | float: right; 101 | opacity: 1; 102 | padding: 0 0 0 30px; 103 | } 104 | .hidden.x-beginning, 105 | .hidden.x-end{ 106 | transition: all 0.2s ease; 107 | opacity: 0; 108 | } 109 | 110 | 111 | // SVG elements 112 | .line{ 113 | fill: none; 114 | opacity: 0.9; 115 | stroke-width: 1.5px; 116 | } 117 | .line.focused{ 118 | opacity: 1; 119 | stroke-width: 2.5px; 120 | } 121 | .column, 122 | .selected-column, 123 | .show-columns .line, 124 | .show-columns .end-dot, 125 | .show-columns .selected-dot{ 126 | display: none; 127 | } 128 | .column{ 129 | opacity: 0.9; 130 | } 131 | .column.selected{ 132 | opacity: 1; 133 | } 134 | .show-columns .column{ 135 | display: inline; 136 | } 137 | 138 | 139 | @media screen and (min-width: @width-mobilePortrait) { 140 | .chart-plot-outer-container{ 141 | height: 420px; 142 | } 143 | .x-axis{ 144 | font-size: @fontSize-smaller; 145 | } 146 | .line{ 147 | stroke-width: 2px; 148 | } 149 | .line.focused{ 150 | stroke-width: 3px; 151 | } 152 | } 153 | 154 | 155 | @media screen and (min-width: @width-tablet) { 156 | .chart{ 157 | position: absolute; 158 | top: 0; 159 | right: 0; 160 | bottom: 0; 161 | left: 0; 162 | padding-right: @sidebar-width-small; 163 | } 164 | .chart::after{ 165 | border-top: 1px solid @gray-lighter; 166 | bottom: @x-axis-h; 167 | content: ""; 168 | display:block; 169 | left: 0; 170 | position: absolute; 171 | width: 100%; 172 | } 173 | .dark .chart::after{ 174 | border-top: 1px solid @gray-darker; 175 | } 176 | .chart-plot-outer-container{ 177 | border: none; 178 | height: 100%; 179 | } 180 | .x-axis, 181 | .dark .x-axis{ 182 | border-top: none; 183 | } 184 | } 185 | 186 | 187 | @media screen and (min-width: @width-netbook) { 188 | .chart{ 189 | padding-right: @sidebar-width-large; 190 | } 191 | .chart-height{ 192 | top: @selected-value-h-large; 193 | } 194 | .line{ 195 | stroke-width: 2.5px; 196 | } 197 | .line.focused{ 198 | stroke-width: 3.5px; 199 | } 200 | } 201 | 202 | @media screen and (min-width: @width-netbook) and (max-width: @width-desktop){ 203 | .chart-grid .chart{ 204 | padding-right: @sidebar-width-small; 205 | } 206 | .chart-grid .chart-height{ 207 | top: @selected-value-h-small; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/styles/chart-selection.less: -------------------------------------------------------------------------------- 1 | .selection{ 2 | background-color: @gray-light; 3 | bottom: 0; 4 | position: absolute; 5 | right: 0; 6 | top: 0; 7 | width: 1px; 8 | } 9 | .dark .selection{ 10 | background-color: @gray-darker; 11 | } 12 | .selection-info{ 13 | transition: all 0.4s ease; 14 | height: 100%; 15 | margin: 0 0 0 -200px; 16 | position: absolute; 17 | text-align: right; 18 | top: 0; 19 | width: 200px; 20 | } 21 | .on-right .selection-info{ 22 | text-align: left; 23 | margin: 0 0 0 100%; 24 | } 25 | .selection-xlabel, 26 | .selection-ylabel{ 27 | color: @gray-dark; 28 | font-size: @fontSize-smaller; 29 | padding: 0 14px; 30 | width: 100%; 31 | } 32 | .dark .selection-xlabel, 33 | .dark .selection-ylabel{ 34 | color: @gray; 35 | } 36 | .selection-value{ 37 | .m-fontSans400(); 38 | font-size: @fontSize-number-base; 39 | line-height: @lineHeight-tighter; 40 | margin: 16px 0 0 0; 41 | padding: 0 14px; 42 | } 43 | .selection-xlabel{ 44 | bottom: 0; 45 | height: @x-axis-h; 46 | padding: 16px 14px 0; 47 | position: absolute; 48 | left: 0; 49 | } 50 | 51 | 52 | @media screen and (min-width: @width-tablet) { 53 | .selection-info{ 54 | margin: 0 0 0 -300px; 55 | width: 300px; 56 | } 57 | .selection-value{ 58 | font-size: @fontSize-number-larger; 59 | line-height: @lineHeight-tightest; 60 | } 61 | .half-height .selection-value{ 62 | font-size: @fontSize-number-large; 63 | } 64 | .selection-xlabel{ 65 | padding-top: 15px; 66 | } 67 | .selection-ylabel{ 68 | font-size: @fontSize-small; 69 | } 70 | } 71 | 72 | 73 | @media screen and (min-width: @width-tablet) and (min-height: @height-desktop) { 74 | .half-height .selection-value,{ 75 | font-size: @fontSize-number-larger; 76 | } 77 | } 78 | 79 | 80 | @media screen and (min-width: @width-netbook) { 81 | .chart-grid .selection-value{ 82 | font-size: @fontSize-number-large; 83 | } 84 | .selection-ylabel{ 85 | font-size: @fontSize-base; 86 | } 87 | } 88 | 89 | 90 | @media screen and (min-width: @width-netbook) and (min-height: @height-desktop) { 91 | .selection-value{ 92 | font-size: @fontSize-number-largest; 93 | } 94 | .chart-grid .selection-value{ 95 | font-size: @fontSize-number-larger; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/styles/chart-y-axis.less: -------------------------------------------------------------------------------- 1 | .y-axis-container{ 2 | bottom: 0; 3 | border-right: 1px solid @gray-lighter; 4 | left: -@y-axis-w; 5 | position: absolute; 6 | top: 0; 7 | width: @y-axis-w; 8 | } 9 | .y-axis{ 10 | left: 0; 11 | z-index: 2; 12 | width: 100%; 13 | } 14 | .y-axis-label{ 15 | background-color: white; 16 | color: @gray; 17 | font-size: @fontSize-smaller; 18 | right: 0px; 19 | margin-top: -6px; 20 | padding: 0 8px; 21 | position: absolute; 22 | text-align: right; 23 | } 24 | .y-axis-label:after{ 25 | background-color: @gray-lighter; 26 | content: ""; 27 | display: block; 28 | height: 1px; 29 | position: absolute; 30 | right: 0; 31 | width: 3px; 32 | top: 5px; 33 | } 34 | .dark .y-axis-container{ 35 | border-right: 1px solid @gray-darker; 36 | } 37 | .dark .y-axis-label{ 38 | color: @gray-dark; 39 | background-color: black; 40 | } 41 | .dark .y-axis-label:after{ 42 | background-color: @gray-darker; 43 | } 44 | -------------------------------------------------------------------------------- /src/styles/charted.less: -------------------------------------------------------------------------------- 1 | // Variables and Mixins 2 | @import "colors"; 3 | @import "dimensions"; 4 | @import "mixins"; 5 | @import "type"; 6 | 7 | // Low Level Styles 8 | @import "reset"; 9 | @import "utils"; 10 | 11 | // UI Components 12 | @import "chart-info"; 13 | @import "chart-options"; 14 | @import "chart-plot"; 15 | @import "chart-selection"; 16 | @import "chart-y-axis"; 17 | @import "embed-overlay"; 18 | @import "icons"; 19 | @import "legend"; 20 | @import "landing-page"; 21 | @import "page-settings"; 22 | @import "popover"; 23 | -------------------------------------------------------------------------------- /src/styles/colors.less: -------------------------------------------------------------------------------- 1 | // Grays 2 | // ----------------------------------------------------------------------- 3 | @gray-lightest: rgb(249, 249, 247); 4 | @gray-lighter: rgb(242, 242, 240); 5 | @gray-light: rgb(222, 222, 220); 6 | @gray: rgb(179, 179, 177); 7 | @gray-dark: rgb(102, 102, 101); 8 | @gray-darker: rgb(51, 51, 50); 9 | @gray-darkest: rgb(35, 35, 34); 10 | 11 | 12 | // Colors 13 | // ----------------------------------------------------------------------- 14 | @red: #ff8150; 15 | -------------------------------------------------------------------------------- /src/styles/dimensions.less: -------------------------------------------------------------------------------- 1 | // Responsive width sizes 2 | // -------------------------------------------------- 3 | @width-mobilePortrait: 400px; 4 | @width-tablet: 800px; 5 | @width-netbook: 1200px; 6 | @width-desktop: 1800px; 7 | 8 | 9 | // Responsive height sizes 10 | // -------------------------------------------------- 11 | @height-tablet: 400px; 12 | @height-desktop: 1000px; 13 | 14 | 15 | // Chart info sidebar width sizes 16 | // -------------------------------------------------- 17 | @sidebar-width-small: 200px; 18 | @sidebar-width-large: 300px; 19 | 20 | 21 | // Chart elements 22 | // -------------------------------------------------- 23 | @x-axis-h: 44px; 24 | @y-axis-w: 44px; 25 | @selected-value-h-small: 100px; 26 | @selected-value-h-large: 120px; 27 | -------------------------------------------------------------------------------- /src/styles/embed-overlay.less: -------------------------------------------------------------------------------- 1 | .overlay-container{ 2 | background-color: rgba(255, 255, 255, 0.96); 3 | bottom: 0; 4 | left: 0; 5 | overflow-y: scroll; 6 | position: fixed; 7 | right: 0; 8 | text-align: center; 9 | top: 0; 10 | z-index: 10; 11 | } 12 | .overlay-content{ 13 | margin: 0 auto; 14 | max-width: 760px; 15 | padding: 40px 10px; 16 | width: 100%; 17 | } 18 | .overlay-title{ 19 | color: @gray-darker; 20 | font-size: @fontSize-larger; 21 | margin: 0 auto 20px; 22 | max-width: 500px; 23 | } 24 | .overlay-description{ 25 | color: @gray; 26 | font-size: @fontSize-base; 27 | line-height: @lineHeight-tight; 28 | margin: 0 auto; 29 | max-width: 500px; 30 | } 31 | .overlay-close{ 32 | transition: all 0.2s ease; 33 | cursor: pointer; 34 | opacity: 0.6; 35 | position: absolute; 36 | right: 12px; 37 | top: 12px; 38 | } 39 | .overlay-close:hover{ 40 | opacity: 1; 41 | } 42 | .overlay-close .icon{ 43 | display: inline-block; 44 | height: 26px; 45 | width: 26px; 46 | } 47 | .iframe-container{ 48 | background-color: white; 49 | } 50 | .embed-link{ 51 | .m-fontSans400(); 52 | border-radius: 4px; 53 | background-color: white; 54 | border: 1px solid @gray-light; 55 | color: @gray-dark; 56 | font-size: @fontSize-base; 57 | margin: 40px 0 30px; 58 | overflow: hidden; 59 | padding: 10px 0; 60 | text-align: center; 61 | resize: none; 62 | width: 100%; 63 | } 64 | 65 | @media screen and (min-width: @width-tablet) { 66 | .overlay-content{ 67 | padding: 80px 10px; 68 | } 69 | .overlay-close{ 70 | right: 20px; 71 | top: 20px; 72 | } 73 | .overlay-close .icon{ 74 | height: 40px; 75 | width: 40px; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/styles/icons.less: -------------------------------------------------------------------------------- 1 | .option-item{ 2 | border-radius: 100%; 3 | transition: all 0.2s ease; 4 | border: 1px solid @gray-light; 5 | display: inline-block; 6 | font-size: 0; 7 | height: 30px; 8 | margin: 7px 3px; 9 | position: relative; 10 | width: 30px; 11 | } 12 | .option-item.settings{ 13 | height: 34px; 14 | margin: 0px; 15 | width: 34px; 16 | } 17 | .option-item .icon{ 18 | transition: all 0.2s ease; 19 | display: inline-block; 20 | height: 100%; 21 | opacity: 0.4; 22 | width: 100%; 23 | } 24 | .option-item:hover{ 25 | border: 1px solid @gray-dark; 26 | } 27 | .option-item:hover .icon{ 28 | opacity: 0.8; 29 | } 30 | .dark .option-item{ 31 | border: 1px solid @gray-dark; 32 | } 33 | .dark .option-item .icon{ 34 | opacity: 0.6; 35 | } 36 | .dark .option-item:hover{ 37 | border: 1px solid @gray; 38 | } 39 | .dark .option-item:hover .icon{ 40 | opacity: 1; 41 | } 42 | .option-item.none{ 43 | display: none; 44 | } 45 | .option-item .icon-line, 46 | .option-item .icon-round-on, 47 | .show-columns .icon-column, 48 | .rounding-off .icon-round-off{ 49 | display: none; 50 | } 51 | .show-columns .option-item .icon-line, 52 | .rounding-off .option-item .icon-round-on{ 53 | display: inline-block; 54 | } 55 | 56 | 57 | // Icon background images 58 | .icon-column{ background-image: url(./images/icon-column.svg) } 59 | .dark .icon-column{ background-image: url(./images/icon-column-white.svg) } 60 | .icon-color{ background-image: url(./images/icon-color.svg) } 61 | .dark .icon-color{ background-image: url(./images/icon-color-white.svg) } 62 | .icon-download{ background-image: url(./images/icon-download.svg) } 63 | .dark .icon-download{ background-image: url(./images/icon-download-white.svg) } 64 | .icon-line{ background-image: url(./images/icon-line.svg) } 65 | .dark .icon-line{ background-image: url(./images/icon-line-white.svg) } 66 | .icon-round-on{ background-image: url(./images/icon-round-on.svg) } 67 | .dark .icon-round-on{ background-image: url(./images/icon-round-on-white.svg) } 68 | .icon-round-off{ background-image: url(./images/icon-round-off.svg) } 69 | .dark .icon-round-off{ background-image: url(./images/icon-round-off-white.svg) } 70 | .icon-settings{ background-image: url(./images/icon-settings.svg) } 71 | .dark .icon-settings{ background-image: url(./images/icon-settings-white.svg) } 72 | .icon-move{ background-image: url(./images/icon-move.svg) } 73 | .dark .icon-move{ background-image: url(./images/icon-move-white.svg) } 74 | .icon-plus{ background-image: url(./images/icon-plus.svg) } 75 | .dark .icon-plus{ background-image: url(./images/icon-plus-white.svg) } 76 | .icon-back{ background-image: url(./images/icon-back.svg) } 77 | .dark .icon-back{ background-image: url(./images/icon-back-white.svg) } 78 | .icon-embed{ background-image: url(./images/icon-embed.svg) } 79 | .dark .icon-embed{ background-image: url(./images/icon-embed-white.svg) } 80 | .icon-x{ background-image: url(./images/icon-x.svg) } 81 | .dark .icon-x{ background-image: url(./images/icon-x-white.svg) } 82 | .icon-full-screen{ background-image: url(./images/icon-full-screen.svg) } 83 | .dark .icon-full-screen{ background-image: url(./images/icon-full-screen-white.svg) } 84 | .icon-split-screen{ background-image: url(./images/icon-split-screen.svg) } 85 | .dark .icon-split-screen{ background-image: url(./images/icon-split-screen-white.svg) } 86 | -------------------------------------------------------------------------------- /src/styles/landing-page.less: -------------------------------------------------------------------------------- 1 | .loading-embed-spinner, 2 | .pre-load .page-title, 3 | .pre-load .page-subtitle, 4 | .pre-load:not(.is-embed) .load-data-form, 5 | .pre-load:not(.is-embed) footer{ 6 | display: block; 7 | } 8 | .page-title{ 9 | .m-fontSans900(); 10 | color: @gray-darker; 11 | display: none; 12 | font-size: @fontSize-base; 13 | letter-spacing: @letterSpacing-wider; 14 | margin: 45px 0 6px; 15 | text-align: center; 16 | text-transform: uppercase; 17 | } 18 | .page-subtitle{ 19 | .m-fontSans200(); 20 | color: @gray-dark; 21 | display: none; 22 | font-size: @fontSize-base; 23 | text-align: center; 24 | } 25 | .load-data-form{ 26 | display: none; 27 | margin: 90px 5% 100px; 28 | padding: 0 62px 0 0; 29 | position: relative; 30 | width: 90%; 31 | } 32 | .data-file-input{ 33 | .m-fontSans200(); 34 | border-radius: 4px 0 0 4px; 35 | background-color: transparent; 36 | border: 1px solid @gray; 37 | border-width: 1px 0 1px 1px; 38 | color: @gray-darker; 39 | font-size: @fontSize-large; 40 | height: 60px; 41 | padding: 0 0 0 16px; 42 | width: 100%; 43 | } 44 | .data-file-input::-webkit-input-placeholder{ 45 | color: @gray-light; 46 | } 47 | .data-file-input:-moz-placeholder{ 48 | color: @gray-light; 49 | } 50 | .data-file-input::-moz-placeholder{ 51 | color: @gray-light; 52 | } 53 | .data-file-input:-ms-input-placeholder { 54 | color: @gray-light; 55 | } 56 | .data-file-input::light { 57 | color: @gray-darker; 58 | } 59 | .submit{ 60 | .m-fontSans900(); 61 | background-color: transparent; 62 | border: 1px solid @gray; 63 | color: @gray-darker; 64 | display: block; 65 | font-size: @fontSize-large; 66 | letter-spacing: @letterSpacing-wide; 67 | text-align: center; 68 | text-transform: uppercase; 69 | } 70 | .submit:hover{ 71 | border: 1px solid @gray-darkest; 72 | background-color: @gray-darkest; 73 | color: white; 74 | } 75 | .submit, 76 | .loading-spinner{ 77 | border-radius: 0 4px 4px 0; 78 | height: 60px; 79 | position: absolute; 80 | right: 0; 81 | top: 0; 82 | width: 62px; 83 | } 84 | .loading-spinner{ 85 | transition: all 0.4s ease; 86 | background-color: @gray-darkest; 87 | display: none; 88 | } 89 | .loading-spinner-img{ 90 | .spin(); 91 | display: block; 92 | margin: 6px auto 0; 93 | width: 50px; 94 | } 95 | .loading .loading-spinner{ 96 | display: block; 97 | } 98 | .loading-embed-spinner { 99 | display: none; 100 | } 101 | .loading.is-embed .loading-embed-spinner { 102 | display: block; 103 | } 104 | .loading.is-embed .loading-spinner-img { 105 | margin: 50px auto; 106 | } 107 | .error .error-message{ 108 | color: @red; 109 | font-size: @fontSize-base; 110 | left: 0; 111 | line-height: @lineHeight-tight; 112 | margin: 14px 5%; 113 | position: absolute; 114 | top: 100%; 115 | text-align: center; 116 | width: 90%; 117 | } 118 | footer{ 119 | display: none; 120 | font-size: @fontSize-small; 121 | padding: 0 0 35px; 122 | text-align: center; 123 | width: 100%; 124 | } 125 | footer a{ 126 | color: @gray-dark; 127 | text-decoration: none; 128 | } 129 | footer a ~ a:before{ 130 | color: @gray; 131 | content: "\2022"; 132 | margin: 0 10px; 133 | } 134 | footer a:hover{ 135 | color: @gray-darker; 136 | } 137 | 138 | 139 | @media screen and (min-width: @width-tablet) { 140 | .page-title{ 141 | font-size: @fontSize-large; 142 | margin: 60px 0 10px; 143 | } 144 | .page-subtitle{ 145 | font-size: @fontSize-large; 146 | } 147 | .load-data-form{ 148 | margin-left: 8%; 149 | margin-right: 8%; 150 | padding: 0 92px 0 0; 151 | width: 84%; 152 | } 153 | .data-file-input{ 154 | font-size: @fontSize-largest; 155 | height: 90px; 156 | padding: 0 0 0 24px; 157 | } 158 | .submit, 159 | .loading-spinner{ 160 | font-size: @fontSize-larger; 161 | height: 90px; 162 | width: 92px; 163 | } 164 | .loading-spinner-img{ 165 | margin: 14px auto 0; 166 | width: 66px; 167 | } 168 | 169 | footer{ 170 | padding: 0 0 50px; 171 | } 172 | footer a ~ footer a:before{ 173 | content: "\2219"; 174 | margin: 0 6px; 175 | } 176 | footer a{ 177 | color: @gray-dark; 178 | } 179 | footer a:hover{ 180 | color: @gray-darkest; 181 | } 182 | .error .error-message{ 183 | font-size: @fontSize-large; 184 | margin: 20px 10%; 185 | width: 80%; 186 | } 187 | } 188 | 189 | @media screen and (min-height: @height-tablet) { 190 | .load-data-form{ 191 | bottom: 50%; 192 | left: 0; 193 | margin-bottom: -30px; 194 | margin-top: 0; 195 | position: absolute; 196 | } 197 | footer{ 198 | bottom: 0; 199 | left: 0; 200 | position: absolute; 201 | } 202 | } 203 | 204 | @media screen and (min-width: @width-tablet) and (min-height: @height-tablet) { 205 | .load-data-form{ 206 | margin-bottom: -55px; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/styles/legend.less: -------------------------------------------------------------------------------- 1 | .legend{ 2 | font-size: @fontSize-smaller; 3 | padding: 8px 0 20px; 4 | width: 100%; 5 | } 6 | .legend.hidden{ 7 | display: none; 8 | } 9 | .legend-item{ 10 | padding: 0 42px 0 26px; 11 | position: relative; 12 | } 13 | .legend-label{ 14 | position: relative; 15 | } 16 | .legend-input{ 17 | transition: all 0.3s ease; 18 | border-radius: 4px; 19 | display: block; 20 | overflow: hidden; 21 | padding: 5px 0 5px 3px; 22 | width: 100%; 23 | } 24 | .legend-color{ 25 | border-radius: 100%; 26 | height: 19px; 27 | left: 3px; 28 | position: absolute; 29 | top: 2px; 30 | width: 19px; 31 | } 32 | .legend-dot{ 33 | border-radius: 100%; 34 | height: 11px; 35 | left: 4px; 36 | display: inline-block; 37 | position: absolute; 38 | top: 4px; 39 | width: 11px; 40 | } 41 | .legend-color:hover, 42 | .active-color-input .legend-color{ 43 | background-color: @gray-lighter; 44 | } 45 | .dark .legend-color:hover, 46 | .dark .active-color-input .legend-color{ 47 | background-color: @gray-darkest; 48 | } 49 | .change-series-color.popover{ 50 | bottom: 30px; 51 | left: -9px; 52 | width: auto; 53 | } 54 | .change-series-color.popover p{ 55 | padding: 6px 8px 10px; 56 | } 57 | .color-hex-input{ 58 | border-radius: 4px; 59 | background-color: @gray-lightest; 60 | color: @gray-darker; 61 | font-size: @fontSize-small; 62 | padding: 4px 8px; 63 | } 64 | .color-hex-input::before{ 65 | color: @gray; 66 | content:"#"; 67 | margin: 0 4px 0 0; 68 | } 69 | .move-chart{ 70 | transition: all 0.3s ease; 71 | background-color: transparent; 72 | opacity: 0; 73 | position: absolute; 74 | right: 9px; 75 | top: -2px; 76 | } 77 | .move-chart .icon{ 78 | display: inline-block; 79 | height: 30px; 80 | width: 30px; 81 | } 82 | .legend-item.active .legend-input, 83 | .legend-item:hover .legend-input{ 84 | background-color: @gray-lightest; 85 | } 86 | .dark .legend-item.active .legend-input, 87 | .dark .legend-item:hover .legend-input{ 88 | background-color: @gray-darkest; 89 | } 90 | .legend-item:hover .move-chart{ 91 | opacity: 0.15; 92 | } 93 | .legend-item.active .move-chart, 94 | .legend-item:hover .move-chart:hover{ 95 | opacity: 0.8; 96 | } 97 | .dark .legend-item:hover .move-chart{ 98 | opacity: 0.4; 99 | } 100 | .dark .legend-item.active .move-chart, 101 | .darl .legend-item:hover .move-chart:hover{ 102 | opacity: 1; 103 | } 104 | .move-chart-options.popover{ 105 | bottom: 40px; 106 | right: 2px; 107 | } 108 | .move-chart-option{ 109 | transition: all 0.3s ease; 110 | border-radius: 4px; 111 | background-color: none; 112 | color: @gray-darker; 113 | display: block; 114 | font-size: @fontSize-small; 115 | padding: 8px; 116 | position: relative; 117 | text-decoration: none; 118 | } 119 | .move-chart-option:hover{ 120 | background-color: @gray-lightest; 121 | } 122 | .dark .move-chart-option{ 123 | color: @gray-light; 124 | } 125 | .dark .move-chart-option:hover{ 126 | background-color: @gray-darker; 127 | } 128 | .move-chart-option .icon{ 129 | display: block; 130 | height: 26px; 131 | left: 3px; 132 | opacity: 0.8; 133 | position: absolute; 134 | top: 2px; 135 | width: 26px; 136 | } 137 | .embed .legend-item.active .legend-input, 138 | .embed .legend-item:hover .legend-input, 139 | .embed .dark .legend-item.active .legend-input, 140 | .embed .dark .legend-item:hover .legend-input, 141 | .embed .legend-color:hover, 142 | .embed .active-color-input .legend-color, 143 | .embed .dark .legend-color:hover, 144 | .embed .dark .active-color-input .legend-color{ 145 | background-color: transparent; 146 | cursor: auto; 147 | } 148 | 149 | 150 | @media screen and (min-width: @width-tablet) { 151 | .legend, 152 | .dark .legend{ 153 | bottom: 40px; 154 | left: 0; 155 | padding: 6px 10px 24px; 156 | position: absolute; 157 | } 158 | } 159 | 160 | 161 | @media screen and (min-width: @width-netbook) { 162 | .legend{ 163 | font-size: @fontSize-base; 164 | } 165 | .legend-input{ 166 | min-height: 30px; 167 | padding: 6px 0 6px 4px; 168 | } 169 | .legend-color{ 170 | height: 21px; 171 | top: 5px; 172 | width: 21px; 173 | } 174 | .legend-dot{ 175 | height: 13px; 176 | width: 13px; 177 | } 178 | .move-chart{ 179 | top: -1px; 180 | } 181 | } 182 | 183 | @media screen and (min-width: @width-netbook) and (max-width: @width-desktop){ 184 | .chart-grid .legend{ 185 | font-size: @fontSize-smaller; 186 | } 187 | .chart-grid .legend-input{ 188 | min-height: 0px; 189 | padding: 5px 0 5px 3px; 190 | } 191 | .chart-grid .legend-color{ 192 | height: 19px; 193 | top: 2px; 194 | width: 19px; 195 | } 196 | .chart-grid .legend-dot{ 197 | height: 11px; 198 | width: 11px; 199 | } 200 | .chart-grid .move-chart{ 201 | top: -2px; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/styles/mixins.less: -------------------------------------------------------------------------------- 1 | // Spin 2 | // ----------------------------------------------------------------------- 3 | .spin { 4 | animation-name: spin; 5 | animation-duration: 1500ms; 6 | animation-iteration-count: infinite; 7 | animation-timing-function: linear; 8 | } 9 | 10 | @keyframes spin { 11 | from { 12 | transform: rotate(0deg); 13 | } 14 | to { 15 | transform: rotate(360deg); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/styles/page-settings.less: -------------------------------------------------------------------------------- 1 | .page-settings{ 2 | left: 5px; 3 | position: fixed; 4 | top: 7px; 5 | z-index: 3; 6 | } 7 | .settings{ 8 | background-color: white; 9 | } 10 | .dark .settings{ 11 | background-color: black; 12 | } 13 | .settings-popover.popover{ 14 | display: none; 15 | left: 4px; 16 | padding-bottom: 8px; 17 | top: 4px; 18 | } 19 | .open .settings{ 20 | display: none; 21 | } 22 | .page-options{ 23 | padding: 0 0 6px; 24 | } 25 | .open .settings-popover{ 26 | display: block; 27 | } 28 | .page-options{ 29 | padding: 0 0 12px; 30 | } 31 | .page-option-item{ 32 | .m-fontSans400(); 33 | transition: all 0.2s ease; 34 | color: @gray; 35 | display: block; 36 | font-size: @fontSize-small; 37 | padding: 5px 0; 38 | text-decoration: none; 39 | } 40 | .page-option-item:hover{ 41 | color: @gray-dark; 42 | } 43 | .page-option-item .icon{ 44 | transition: all 0.2s ease; 45 | display: inline-block; 46 | height: 30px; 47 | margin: 0 2px -5px 0; 48 | opacity: 0.4; 49 | padding: 3px 0; 50 | vertical-align: bottom; 51 | width: 30px; 52 | } 53 | .page-option-item:hover .icon{ 54 | opacity: 0.8; 55 | } 56 | .page-data-source{ 57 | border-top: 1px solid @gray-lightest; 58 | margin: 10px 0 0; 59 | padding: 16px 0 0; 60 | } 61 | .page-data-source label{ 62 | color: @gray; 63 | font-size: @fontSize-smallest; 64 | display: block; 65 | padding: 0 0 3px 5px; 66 | } 67 | .page-data-source-form{ 68 | width: 100%; 69 | padding: 0 40px 0 0; 70 | position: relative; 71 | } 72 | .page-data-source-form input{ 73 | .m-fontSans400(); 74 | transition: all 0.2s ease; 75 | border-radius: 3px 0 0 3px; 76 | border: 1px solid @gray-lighter; 77 | color: @gray-dark; 78 | display: block; 79 | font-size: @fontSize-small; 80 | height: 36px; 81 | padding: 5px 0 5px 5px; 82 | width: 100%; 83 | } 84 | .page-data-source-form button{ 85 | .m-fontSans900(); 86 | border-radius: 0 3px 3px 0; 87 | background-color: white; 88 | border: 1px solid @gray-lighter; 89 | border-width: 1px 1px 1px 0; 90 | font-size: @fontSize-smaller; 91 | height: 36px; 92 | letter-spacing: @letterSpacing-wide; 93 | position: absolute; 94 | right: 0; 95 | text-transform: uppercase; 96 | top: 0; 97 | width: 40px; 98 | } 99 | .page-data-source-form button:hover{ 100 | border: 1px solid @gray-darkest; 101 | background-color: @gray-darkest; 102 | color: white; 103 | } 104 | .dark .page-option-item:hover{ 105 | color: @gray-lightest; 106 | } 107 | .dark .page-option-item .icon{ 108 | opacity: 0.6; 109 | } 110 | .dark .page-option-item:hover .icon{ 111 | opacity: 1; 112 | } 113 | .dark .page-data-source{ 114 | border-top: 1px solid @gray-dark; 115 | } 116 | 117 | 118 | @media screen and (min-width: @width-tablet) { 119 | .page-settings{ 120 | transition: all 1s ease; 121 | opacity: 0; 122 | } 123 | .page-active .page-settings{ 124 | transition: all 0.2s ease; 125 | opacity: 1; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/styles/popover.less: -------------------------------------------------------------------------------- 1 | .popover{ 2 | box-shadow: 0 1px 1px rgba(0,0,0,.05); 3 | border-radius: 4px; 4 | background-color: white; 5 | border: 1px solid @gray-light; 6 | padding: 8px; 7 | position: absolute; 8 | width: 240px; 9 | z-index: 2; 10 | } 11 | .popover p { 12 | color: @gray; 13 | font-size: @fontSize-smaller; 14 | padding: 10px 8px 8px; 15 | } 16 | .dark .popover p{ 17 | color: @gray-dark; 18 | } 19 | .popover .arrow-bottom-right, 20 | .popover .arrow-bottom-left { 21 | bottom: -14px; 22 | clip: rect(0px 18px 18px -4px); 23 | position: absolute; 24 | } 25 | .popover .arrow-bottom-right{ 26 | right: 20px; 27 | margin: 0px -7px 0 0; 28 | } 29 | .popover .arrow-bottom-left{ 30 | left: 20px; 31 | margin: 0px 0 0 -7px; 32 | } 33 | .popover .arrow-bottom-right:after, 34 | .popover .arrow-bottom-left:after { 35 | transform: rotate(45deg) translate(-6px, -6px); 36 | box-shadow: 0 1px 1px rgba(0,0,0,.05); 37 | border: 1px solid @gray-light; 38 | background-color: white; 39 | content: ''; 40 | display: block; 41 | height: 14px; 42 | width: 14px; 43 | } 44 | .dark .popover .arrow-bottom-right:after, 45 | .dark .popover .arrow-bottom-left:after, 46 | .dark .popover{ 47 | background-color: black; 48 | border: 1px solid @gray-dark; 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/styles/reset.less: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | html, body, div, span, applet, object, iframe, 6 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 7 | a, abbr, acronym, address, big, cite, code, 8 | del, dfn, em, img, ins, kbd, q, s, samp, 9 | small, strike, strong, sub, sup, tt, var, 10 | b, u, i, center, 11 | dl, dt, dd, ol, ul, li, 12 | fieldset, form, label, legend, 13 | table, caption, tbody, tfoot, thead, tr, th, td, 14 | article, aside, canvas, details, embed, 15 | figure, figcaption, footer, header, hgroup, 16 | menu, nav, output, ruby, section, summary, 17 | time, mark, audio, video { 18 | margin: 0; 19 | padding: 0; 20 | border: 0; 21 | font-size: 100%; 22 | font: inherit; 23 | vertical-align: baseline; 24 | } 25 | /* HTML5 display-role reset for older browsers */ 26 | article, aside, details, figcaption, figure, 27 | footer, header, hgroup, menu, nav, section { 28 | display: block; 29 | } 30 | body { 31 | line-height: 1; 32 | } 33 | ol, ul { 34 | list-style: none; 35 | } 36 | blockquote, q { 37 | quotes: none; 38 | } 39 | blockquote:before, blockquote:after, 40 | q:before, q:after { 41 | content: ''; 42 | content: none; 43 | } 44 | table { 45 | border-collapse: collapse; 46 | border-spacing: 0; 47 | } 48 | 49 | /* Resets specifically for Charted */ 50 | *, 51 | *:before, 52 | *:after { 53 | box-sizing: border-box; 54 | } 55 | body{ 56 | .m-fontSans400(); 57 | color: @gray-darker; 58 | } 59 | body.dark{ 60 | background-color: black; 61 | color: @gray; 62 | } 63 | button { 64 | -webkit-appearance: none; 65 | background: transparent; 66 | border: none; 67 | cursor: pointer; 68 | margin: 0; 69 | outline: none; 70 | padding: 0; 71 | } 72 | /* mobile browser input styling */ 73 | input { 74 | -webkit-appearance: none; 75 | border-radius: 0; 76 | border: none; 77 | margin: 0; 78 | padding: 0; 79 | resize: none; 80 | outline: none; 81 | } 82 | -------------------------------------------------------------------------------- /src/styles/type.less: -------------------------------------------------------------------------------- 1 | // Type 2 | // -------------------------------------------------- 3 | @fontSize-smallest: 12px; 4 | @fontSize-smaller: 14px; 5 | @fontSize-small: 16px; 6 | @fontSize-base: 18px; 7 | @fontSize-large: 24px; 8 | @fontSize-larger: 32px; 9 | @fontSize-largest: 44px; 10 | @fontSize-number-base: 54px; 11 | @fontSize-number-large: 60px; 12 | @fontSize-number-larger: 72px; 13 | @fontSize-number-largest: 100px; 14 | 15 | // Line heights 16 | // -------------------------------------------------- 17 | @lineHeight-tightest: 0.9; 18 | @lineHeight-tighter: 1.1; 19 | @lineHeight-tight: 1.2; 20 | @lineHeight-base: 1.4; 21 | 22 | // Letter spacing 23 | // -------------------------------------------------- 24 | @letterSpacing-normal: 0; 25 | @letterSpacing-wide: 0.05rem; 26 | @letterSpacing-wider: 0.3rem; 27 | 28 | // Font Family 29 | // ------------------------------------------------------------------------- 30 | @fontFamily-sans: "source-sans-pro", "Helvetica Neue", "Helvetica", "Arial", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; 31 | 32 | // Mixins for fonts 33 | // ------------------------------------------------------------------------- 34 | .m-fontSans200() { 35 | font-family: @fontFamily-sans; 36 | letter-spacing: @letterSpacing-normal; 37 | font-weight: 200; 38 | font-style: normal; 39 | } 40 | .m-fontSans400() { 41 | font-family: @fontFamily-sans; 42 | letter-spacing: @letterSpacing-normal; 43 | font-weight: 400; 44 | font-style: normal; 45 | } 46 | .m-fontSans900() { 47 | font-family: @fontFamily-sans; 48 | letter-spacing: @letterSpacing-normal; 49 | font-weight: 900; 50 | font-style: normal; 51 | } 52 | 53 | // Hide WebFont loading -- Adobe Edge Web Fonts API 54 | // ------------------------------------------------------------------------- 55 | .wf-loading { 56 | visibility: hidden; 57 | } 58 | -------------------------------------------------------------------------------- /src/styles/utils.less: -------------------------------------------------------------------------------- 1 | .u-paddingLeft30 { 2 | padding-left: 30px !important; 3 | } 4 | -------------------------------------------------------------------------------- /src/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Charted 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {{#if ENV.dev}} 14 | 15 | {{else}} 16 | 17 | 18 | {{/if}} 19 | 20 | 21 | 22 |

    23 | {{#if ENV.dev}} 24 | Charted Dev 25 | {{else}} 26 | Charted 27 | {{/if}} 28 |

    29 |

    Beautiful, automatic charts

    30 | 31 |
    32 | 33 |
    34 | 35 |
    36 | 39 | 40 | 41 |
    42 |
    43 |
    44 | 45 |
    46 | 47 | 52 | 53 | {{#if ENV.dev}} 54 | 55 | {{else}} 56 | 62 | {{/if}} 63 | 64 | 65 | --------------------------------------------------------------------------------