├── .bowerrc
├── .gitignore
├── Gruntfile.js
├── README.md
├── bower.json
├── env
├── dev
│ ├── clj
│ │ └── asterion
│ │ │ └── dev.clj
│ └── cljs
│ │ └── asterion
│ │ └── dev.cljs
└── prod
│ └── cljs
│ └── asterion
│ └── prod.cljs
├── externs
├── dagre-d3.ext.js
└── misc.js
├── less
├── colors.less
├── font-awesome-4.1.0.less
├── graph.less
├── layout.less
├── loader.less
├── main.less
├── normalize-3.0.1.less
├── overlay.less
└── util.less
├── package.json
├── project.clj
├── resources
└── public
│ ├── app.js
│ ├── css
│ └── main.css
│ ├── example.config.json
│ ├── fonts
│ ├── DroidSansMono.woff2
│ ├── FontAwesome.otf
│ ├── OpenSans-Light.woff
│ ├── OpenSans-Semibold.woff
│ ├── OpenSans.woff
│ ├── RobotoSlab-Regular.woff2
│ ├── fontawesome-webfont.svg
│ ├── fontawesome-webfont.ttf
│ └── fontawesome-webfont.woff
│ ├── img
│ ├── clojure.svg
│ ├── logo.icns
│ ├── logo.ico
│ └── logo_96x96.png
│ ├── index.html
│ ├── js
│ └── vendor
│ │ ├── dagre-d3.min.js
│ │ ├── ga.js
│ │ └── palette.js
│ ├── package.json
│ └── prod.config.json
├── scripts
├── build-windows-exe.nsi
├── dmg
│ ├── TestBkg.png
│ └── TestBkg@2x.png
├── setup.bat
└── setup.sh
└── src
├── clj
└── asterion
│ ├── deps.clj
│ ├── deps
│ ├── clojure.clj
│ └── javascript.clj
│ └── server.clj
├── cljc
└── graph
│ └── util.cljc
└── cljs
├── asterion
├── analytics.cljs
├── click.cljs
├── components.cljs
├── core.cljs
├── d3.cljs
├── deps.cljs
├── dir.cljs
├── project.cljs
├── ring.js
└── tree.js
└── cljs
└── node
└── io.cljs
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory" : "app/components"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /target
3 | /classes
4 | /resources/public/cached/
5 | tmp
6 | /checkouts
7 | pom.xml
8 | pom.xml.asc
9 | figwheel_server.log
10 | *.jar
11 | *.class
12 | *.min.css
13 | /.lein-*
14 | /.nrepl-
15 | /app/js/p
16 | /resources/public/js/p
17 | /app/projects
18 | /.repl
19 | /out
20 | /node_modules
21 | /electron
22 | /app/config.json
23 | /app/*.log
24 | /builds
25 | /app/components
26 | .nrepl-port
27 | /spec/static/test.js
28 | /.repl*
29 | *-init.clj
30 | /.idea
31 | *.iml
32 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 | 'use strict';
3 |
4 | var moment = require('moment'),
5 | path = require('path'),
6 | fs = require('fs-plus'),
7 | asar = require('asar');
8 |
9 | var os = (function(){
10 | var platform = process.platform;
11 | if (/^win/.test(platform)) { return "windows"; }
12 | if (/^darwin/.test(platform)) { return "mac"; }
13 | if (/^linux/.test(platform)) { return "linux"; }
14 | return null;
15 | })();
16 |
17 | var exe = {
18 | windows: "electron.exe",
19 | mac: "Electron.app/Contents/MacOS/Electron",
20 | linux: "electron"
21 | };
22 |
23 | var electron_path = "electron";
24 |
25 | //------------------------------------------------------------------------------
26 | // ShellJS
27 | //------------------------------------------------------------------------------
28 |
29 | require('shelljs/global');
30 | // shelljs/global makes the following imports:
31 | // cwd, pwd, ls, find, cp, rm, mv, mkdir, test, cat,
32 | // str.to, str.toEnd, sed, grep, which, echo,
33 | // pushd, popd, dirs, ln, exit, env, exec, chmod,
34 | // tempdir, error
35 |
36 | var shellconfig = require('shelljs').config;
37 | shellconfig.silent = false; // hide shell cmd output?
38 | shellconfig.fatal = false; // stop if cmd failed?
39 |
40 | //------------------------------------------------------------------------------
41 | // Grunt Config
42 | //------------------------------------------------------------------------------
43 |
44 |
45 | grunt.initConfig({
46 |
47 | less: {
48 | options: {
49 | compress: true
50 | },
51 |
52 | default: {
53 | files: {
54 | 'resources/public/css/main.min.css': 'less/main.less'
55 | }
56 | }
57 | },
58 |
59 | watch: {
60 | options: {
61 | atBegin: true
62 | },
63 |
64 | less: {
65 | files: "less/*.less",
66 | tasks: "less:default"
67 | }
68 | },
69 |
70 | 'download-electron': {
71 | version: '0.31.0',
72 | outputDir: 'electron'
73 | }
74 |
75 | });
76 |
77 | //------------------------------------------------------------------------------
78 | // Third-party tasks
79 | //------------------------------------------------------------------------------
80 |
81 |
82 | grunt.loadNpmTasks('grunt-contrib-less');
83 | grunt.loadNpmTasks('grunt-contrib-watch');
84 | grunt.loadNpmTasks('grunt-download-electron');
85 | if (os === "mac") {
86 | grunt.loadNpmTasks('grunt-appdmg');
87 | }
88 | grunt.loadNpmTasks('winresourcer');
89 |
90 | //------------------------------------------------------------------------------
91 | // Setup Tasks
92 | //------------------------------------------------------------------------------
93 |
94 | grunt.registerTask('setup', [
95 | 'download-electron',
96 | 'ensure-config-exists',
97 | 'run-app-bower'
98 | ]);
99 |
100 | grunt.registerTask('ensure-config-exists', function() {
101 | pushd("app");
102 | if (!test("-f", "config.json")) {
103 | grunt.log.writeln("Creating default config.json...");
104 | cp("example.config.json", "config.json");
105 | }
106 | popd();
107 | });
108 |
109 | grunt.registerTask('run-app-bower', function() {
110 | exec("bower install");
111 | });
112 |
113 | grunt.registerTask('cljsbuild-prod', function() {
114 | grunt.log.writeln("\nCleaning and building ClojureScript production files...");
115 | exec("lein do clean, with-profile production cljsbuild once");
116 | });
117 |
118 | grunt.registerTask('launch', function(async) {
119 | var IsAsync = (async == "true");
120 | grunt.log.writeln("\nLaunching development version...");
121 | var local_exe = exe[os];
122 | exec(path.join(electron_path, local_exe) + " app", {async:IsAsync});
123 | });
124 |
125 | grunt.registerTask('check-old', function() {
126 | grunt.log.writeln("\nChecking clojure dependencies");
127 | exec("lein ancient :all", {silent:false});
128 | grunt.log.writeln("\nChecking npm dependencies");
129 | exec("npm outdated", {silent:false});
130 | grunt.log.writeln("\nChecking bower dependencies");
131 | exec("bower list", {silent:false});
132 | });
133 |
134 | //------------------------------------------------------------------------------
135 | // Release
136 | //------------------------------------------------------------------------------
137 |
138 | grunt.registerTask('release', function() {
139 |
140 | var build = getBuildMeta();
141 | var paths = getReleasePaths(build);
142 | var done = this.async();
143 |
144 | prepRelease( build, paths);
145 | copyElectronAndBuildToRelease( build, paths);
146 | setReleaseConfig( build, paths);
147 | installNodeDepsToRelease( build, paths);
148 | stampRelease( build, paths);
149 | makeAsarRelease( build, paths, done);
150 |
151 | });
152 |
153 | grunt.registerTask('fresh-release', ['cljsbuild-prod', 'release']);
154 |
155 | //------------------------------------------------------------------------------
156 | // Release - config
157 | //------------------------------------------------------------------------------
158 |
159 | var electronShell = {
160 | windows: {
161 | exeToRename: "electron.exe",
162 | renamedExe: "asterion.exe",
163 | resources: "resources",
164 | installExt: "exe"
165 | },
166 | mac: {
167 | exeToRename: "Electron.app",
168 | renamedExe: "asterion.app",
169 | plist: "Electron.app/Contents/Info.plist",
170 | resources: "Electron.app/Contents/Resources",
171 | installExt: "dmg"
172 | },
173 | linux: {
174 | exeToRename: "electron",
175 | renamedExe: "asterion",
176 | resources: "resources"
177 | }
178 | }[os];
179 |
180 | function getBuildMeta() {
181 | grunt.log.writeln("Getting project metadata...");
182 | var tokens = cat("project.clj").split(" ");
183 | var build = {
184 | name: tokens[1],
185 | version: tokens[2].replace(/"/g, "").trim(),
186 | date: moment().format("YYYY-MM-DD"),
187 | commit: exec("git rev-list HEAD --count", {silent:true}).output.trim()
188 | };
189 | build.releaseName = build.name + "-v" + build.version + "-" + os;
190 | grunt.log.writeln("name: "+build.name.cyan);
191 | grunt.log.writeln("version: "+build.version.cyan);
192 | grunt.log.writeln("date: "+build.date.cyan);
193 | grunt.log.writeln("commit: "+build.commit.cyan);
194 | grunt.log.writeln("release: "+build.releaseName.cyan);
195 | return build;
196 | }
197 |
198 | function getReleasePaths(build) {
199 | var paths = {
200 | electron: "electron",
201 | builds: "builds",
202 | devApp: "app",
203 | rootPkg: "package.json"
204 | };
205 | paths.release = path.join(paths.builds, build.releaseName);
206 | paths.resources = path.join(paths.release, electronShell.resources);
207 | paths.install = paths.release + "." + electronShell.installExt;
208 | paths.releaseApp = paths.resources + path.sep + paths.devApp;
209 | paths.devPkg = paths.devApp + "/package.json";
210 | paths.prodCfg = paths.devApp + "/prod.config.json";
211 | paths.releasePkg = paths.releaseApp + "/package.json";
212 | paths.releaseCfg = paths.releaseApp + "/config.json";
213 | paths.exeToRename = path.join(paths.release, electronShell.exeToRename);
214 | paths.renamedExe = path.join(paths.release, electronShell.renamedExe);
215 | paths.releaseResources = path.join(paths.resources, paths.devApp, "components");
216 | return paths;
217 | }
218 |
219 | //------------------------------------------------------------------------------
220 | // Release - subtasks
221 | //------------------------------------------------------------------------------
222 |
223 | function prepRelease(build, paths) {
224 | grunt.log.writeln("\nCleaning previous release...");
225 | mkdir('-p', paths.builds);
226 | rm('-rf', paths.install, paths.release);
227 | }
228 |
229 | function copyElectronAndBuildToRelease(build, paths) {
230 | grunt.log.writeln("\nCopying Electron and asterion to release folder...");
231 | grunt.log.writeln(paths.electron + " ==> " + paths.release.cyan);
232 | grunt.log.writeln(paths.devApp + " ==> " + paths.resources.cyan);
233 | cp('-r', paths.electron + "/", paths.release);
234 | cp('-r', paths.devApp, paths.resources);
235 |
236 | //delete extra resources
237 |
238 | rm('-rf', path.join(paths.releaseApp, "js", "p", "out"));
239 |
240 | }
241 |
242 | function setReleaseConfig(build, paths) {
243 | grunt.log.writeln("\nRemoving config to force default release settings...");
244 | rm('-f', paths.releaseCfg);
245 | cp(paths.prodCfg, paths.releaseCfg);
246 | }
247 |
248 | function installNodeDepsToRelease(build, paths) {
249 | grunt.log.writeln("\nCopying node dependencies to release...");
250 | cp('-f', paths.rootPkg, paths.releaseApp);
251 | pushd(paths.releaseApp);
252 | exec('npm install --no-optional --production');
253 | popd();
254 | cp('-f', paths.devPkg, paths.releaseApp);
255 | }
256 |
257 | function makeAsarRelease(build, paths, done) {
258 | var new_path = path.join(paths.releaseApp, "..", "app.asar");
259 | grunt.log.writeln("Release: " + paths.releaseApp + ", Asar: " + new_path);
260 | asar.createPackage(paths.releaseApp, new_path , function(err) {
261 | grunt.log.writeln("Asar file created.");
262 | cp(path.join(paths.releaseApp, "img", "logo.icns"), paths.resources);
263 | rm('-rf', path.join(paths.releaseApp));
264 | switch (os) {
265 | case "mac": finalizeMacRelease( build, paths); break;
266 | case "linux": finalizeLinuxRelease( build, paths); break;
267 | case "windows": finalizeWindowsRelease( build, paths); break;
268 | }
269 | done(err);
270 | });
271 | }
272 |
273 | function stampRelease(build, paths) {
274 | grunt.log.writeln("\nStamping release with build metadata...");
275 | var pkg = grunt.file.readJSON(paths.releasePkg);
276 | pkg.version = build.version;
277 | pkg["build-commit"] = build.commit;
278 | pkg["build-date"] = build.date;
279 | JSON.stringify(pkg, null, " ").to(paths.releasePkg);
280 | }
281 |
282 | function updateVersionInReadme(build, paths) {
283 | grunt.log.writeln("\nUpdating version and download links in readme...");
284 | sed('-i', /v\d+\.\d+/g, "v"+build.version, "README.md");
285 | }
286 |
287 | //------------------------------------------------------------------------------
288 | // Release - finalization
289 | //------------------------------------------------------------------------------
290 |
291 | function finalizeMacRelease(build, paths) {
292 |
293 | grunt.log.writeln("\nChanging Electron app icon and bundle name...");
294 | var plist = path.join(__dirname, paths.release, electronShell.plist);
295 | exec("defaults write " + plist + " CFBundleIconFile logo.icns");
296 | exec("defaults write " + plist + " CFBundleDisplayName asterion");
297 | exec("defaults write " + plist + " CFBundleName asterion");
298 | exec("defaults write " + plist + " CFBundleIdentifier com.example");
299 | mv(paths.exeToRename, paths.renamedExe);
300 | var app = paths.renamedExe;
301 |
302 | grunt.log.writeln("\nCreating dmg image...");
303 | grunt.config.set("appdmg", {
304 | options: {
305 | "title": "asterion",
306 | "background": "scripts/dmg/TestBkg.png",
307 | "icon-size": 80,
308 | "contents": [
309 | { "x": 448, "y": 344, "type": "link", "path": "/Applications" },
310 | { "x": 192, "y": 344, "type": "file", "path": app }
311 | ]
312 | },
313 | target: {
314 | dest: paths.install
315 | }
316 | });
317 | grunt.task.run("appdmg");
318 | }
319 |
320 | function finalizeLinuxRelease(build, paths) {
321 | mv(paths.exeToRename, paths.renamedExe);
322 | }
323 |
324 | function finalizeWindowsRelease(build, paths) {
325 | grunt.log.writeln("\nChanging electron app icon and bundle name...");
326 | mv(paths.exeToRename, paths.renamedExe);
327 | var app = paths.renamedExe;
328 | grunt.config.set("winresourcer", {
329 | main: {
330 | operation: "Update",
331 | exeFile: app,
332 | resourceType: "Icongroup",
333 | resourceName: "1",
334 | resourceFile: "app/img/logo.ico"
335 | }
336 | });
337 | grunt.task.run("winresourcer");
338 |
339 | grunt.config.set("makensis", {
340 | version: build.version,
341 | releaseDir: paths.release,
342 | outFile: paths.install
343 | });
344 | grunt.task.run("makensis");
345 | }
346 |
347 | grunt.registerTask('makensis', function() {
348 | grunt.log.writeln("\nCreating installer...");
349 | var config = grunt.config.get("makensis");
350 | exec(["makensis",
351 | "/DPRODUCT_VERSION=" + config.version,
352 | "/DRELEASE_DIR=../" + config.releaseDir,
353 | "/DOUTFILE=../" + config.outFile,
354 | "scripts/build-windows-exe.nsi"].join(" "));
355 | });
356 |
357 | //------------------------------------------------------------------------------
358 | // Test Tasks
359 | //------------------------------------------------------------------------------
360 |
361 |
362 | //------------------------------------------------------------------------------
363 | // Default Task
364 | //------------------------------------------------------------------------------
365 |
366 | grunt.registerTask('default', ['setup']);
367 |
368 | // end module.exports
369 | };
370 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Asterion
2 |
3 | Make and explore dependency graphs for Clojure(Script) projects.
4 |
5 | > La casa es del tamaño del mundo; mejor dicho, es el mundo.
6 |
7 | Asterion will run `tools.namespace` against your project sources,
8 | generate a dependency graph, and then graph it using something similar
9 | to [graphviz](https://github.com/cpettitt/dagre-d3). To get your
10 | project sources, it will try to parse your `project.clj` and if that
11 | works, the graph is displayed, you can navigate it (zoom),
12 | filter out namespaces by their names, and highlight namespaces that
13 | contain a certain word in their file (full text search with grep).
14 |
15 | Here is an initial [demo](https://www.youtube.com/watch?v=NOJLmkIyS2A).
16 | and
17 |
18 | ## Usage
19 |
20 | Asterion is currently structured as a webapp, we'll see how it can be
21 | packaged in the future. You can try it [here](http://asterion-dev.elasticbeanstalk.com/index.html)
22 |
23 | To use it locally:
24 |
25 | ```sh
26 | # Compile ClojureScript
27 | lein cljsbuild once production
28 | # Start the server
29 | lein ring server
30 | ```
31 |
32 | ## Developing
33 |
34 | ### Special Requirements
35 |
36 | First install an experimental version of `tools.namespace`:
37 |
38 | ```sh
39 | https://github.com/bensu/tools.namespace
40 | cd tools.namespace
41 | git checkout node
42 | mvn install
43 | ```
44 |
45 | If it worked, you should have a `0.3.19-SNAPSHOT` folder under:
46 |
47 | ```sh
48 | ls ~/.m2/repository/org/clojure/tools.namespace/
49 | ```
50 |
51 | With `tools.namespace` installed you can start the Electron app as
52 | usual.
53 |
54 | ## Development mode
55 |
56 | Start the figwheel server:
57 |
58 | ```
59 | lein figwheel
60 | ```
61 |
62 | If you are on OSX/Linux and have `rlwrap` installed, you can start the figwheel server with:
63 |
64 | ```
65 | rlwrap lein figwheel
66 | ```
67 |
68 | This will give better readline support.
69 |
70 | More about [figwheel](https://github.com/bhauman/lein-figwheel) here.
71 |
72 |
73 | ### Dependencies
74 |
75 | Node dependencies are in `package.json` file. Bower dependencies are in `bower.json` file. Clojure/ClojureScript dependencies are in `project.clj`.
76 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "draft",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | "bootstrap": "3.3.5"
6 | },
7 | "license": "MIT",
8 | "private": true,
9 | "ignore": [
10 | "**/.*",
11 | "node_modules",
12 | "bower_components",
13 | "test",
14 | "tests",
15 | "spec"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/env/dev/clj/asterion/dev.clj:
--------------------------------------------------------------------------------
1 | (ns asterion.dev
2 | (:require [clojure.tools.namespace.repl :refer (refresh refresh-all)]
3 | [com.stuartsierra.component :as component]
4 | [asterion.server :as server]))
5 |
6 | ;; ======================================================================
7 | ;; Reloaded Workflow
8 |
9 | (def system nil)
10 |
11 | (defn init
12 | "Constructs the current development system."
13 | []
14 | (alter-var-root #'system (constantly server/new-system)))
15 |
16 | (defn start
17 | "Starts the current development system."
18 | []
19 | (alter-var-root #'system component/start))
20 |
21 | (defn stop
22 | "Shuts down and destroys the current development system."
23 | []
24 | (alter-var-root #'system
25 | (fn [s] (when s (component/stop s)))))
26 |
27 | (defn go
28 | "Initializes the current development system and starts it running."
29 | []
30 | (init)
31 | (start))
32 |
33 | (defn reset []
34 | (stop)
35 | (refresh :after 'asterion.dev/go))
36 |
--------------------------------------------------------------------------------
/env/dev/cljs/asterion/dev.cljs:
--------------------------------------------------------------------------------
1 | (ns asterion.dev
2 | (:require [asterion.core :as core]))
3 |
4 | (core/init!)
5 |
--------------------------------------------------------------------------------
/env/prod/cljs/asterion/prod.cljs:
--------------------------------------------------------------------------------
1 | (ns ^:figwheel-no-load asterion.prod
2 | (:require [asterion.core :as core]))
3 |
4 | (core/init!)
5 |
--------------------------------------------------------------------------------
/externs/dagre-d3.ext.js:
--------------------------------------------------------------------------------
1 | var dagreD3 = {
2 | "render": function() {},
3 | "graphlib": {
4 | "Graph": function() {}
5 | }
6 | };
7 |
8 | dagreD3.graphlib.Graph.prototype = function() {};
9 | dagreD3.graphlib.Graph.prototype = {
10 | "setGraph": function() {}
11 | };
12 |
13 | dagreD3.graphlib.Graph.prototype.setGraph.prototype = function() {};
14 | dagreD3.graphlib.Graph.prototype.setGraph.prototype = {
15 | "node": function() {},
16 | "setNode": function() {},
17 | "setEdge": function() {}
18 | };
19 |
20 | dagreD3.graphlib.Graph.prototype.setGraph.prototype.node.prototype = function() {};
21 |
22 | dagreD3.graphlib.Graph.prototype.setGraph.prototype.node.prototype = {
23 | "name": {},
24 | "rx": {},
25 | "ry": {}
26 | };
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/externs/misc.js:
--------------------------------------------------------------------------------
1 | // Node.JS externs: https://github.com/dcodeIO/node.js-closure-compiler-externs
2 |
3 | var ipc = {};
4 | ipc.on = function() {};
5 | ipc.send = function() {};
6 |
7 |
8 | var fs = {};
9 | fs.listTreeSync = function() {};
10 |
11 |
12 | var remote = {};
13 | remote.require = function() {};
14 | remote.buildFromTemplate = function() {};
15 | remote.popup = function() {};
16 | remote.getCurrentWindow = function() {};
17 | remote.showErrorBox = function() {};
18 | remote.setTitle = function() {};
19 | remote.setRepresentedFilename = function() {};
20 | remote.showMessageBox = function() {};
21 |
22 | var process = {
23 | platform: {}
24 | };
25 |
--------------------------------------------------------------------------------
/less/colors.less:
--------------------------------------------------------------------------------
1 | // cljs info colors
2 |
3 | @whiteGhost: #f9fbfb;
4 | @blueHavelock: #607ebe;
5 | @greenAtlantis: #99cb54;
6 | @greenOrinoco: #ccdfac;
7 | @greenJuniper: #6e9393;
8 | @indianRed: #BE6060;
--------------------------------------------------------------------------------
/less/font-awesome-4.1.0.less:
--------------------------------------------------------------------------------
1 | // Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome
2 | // License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
3 |
4 | /* FONT PATH
5 | * -------------------------- */
6 | @font-face {
7 | font-family: 'FontAwesome';
8 | src: url('../fonts/fontawesome-webfont.eot?v=4.1.0');
9 | src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg');
10 | font-weight: normal;
11 | font-style: normal;
12 | }
13 | .fa {
14 | display: inline-block;
15 | font-family: FontAwesome;
16 | font-style: normal;
17 | font-weight: normal;
18 | line-height: 1;
19 | -webkit-font-smoothing: antialiased;
20 | -moz-osx-font-smoothing: grayscale;
21 | }
22 | /* makes the font 33% larger relative to the icon container */
23 | .fa-lg {
24 | font-size: 1.33333333em;
25 | line-height: 0.75em;
26 | vertical-align: -15%;
27 | }
28 | .fa-2x {
29 | font-size: 2em;
30 | }
31 | .fa-3x {
32 | font-size: 3em;
33 | }
34 | .fa-4x {
35 | font-size: 4em;
36 | }
37 | .fa-5x {
38 | font-size: 5em;
39 | }
40 | .fa-fw {
41 | width: 1.28571429em;
42 | text-align: center;
43 | }
44 | .fa-ul {
45 | padding-left: 0;
46 | margin-left: 2.14285714em;
47 | list-style-type: none;
48 | }
49 | .fa-ul > li {
50 | position: relative;
51 | }
52 | .fa-li {
53 | position: absolute;
54 | left: -2.14285714em;
55 | width: 2.14285714em;
56 | top: 0.14285714em;
57 | text-align: center;
58 | }
59 | .fa-li.fa-lg {
60 | left: -1.85714286em;
61 | }
62 | .fa-border {
63 | padding: .2em .25em .15em;
64 | border: solid 0.08em #eeeeee;
65 | border-radius: .1em;
66 | }
67 | .pull-right {
68 | float: right;
69 | }
70 | .pull-left {
71 | float: left;
72 | }
73 | .fa.pull-left {
74 | margin-right: .3em;
75 | }
76 | .fa.pull-right {
77 | margin-left: .3em;
78 | }
79 | .fa-spin {
80 | -webkit-animation: spin 2s infinite linear;
81 | -moz-animation: spin 2s infinite linear;
82 | -o-animation: spin 2s infinite linear;
83 | animation: spin 2s infinite linear;
84 | }
85 | @-moz-keyframes spin {
86 | 0% {
87 | -moz-transform: rotate(0deg);
88 | }
89 | 100% {
90 | -moz-transform: rotate(359deg);
91 | }
92 | }
93 | @-webkit-keyframes spin {
94 | 0% {
95 | -webkit-transform: rotate(0deg);
96 | }
97 | 100% {
98 | -webkit-transform: rotate(359deg);
99 | }
100 | }
101 | @-o-keyframes spin {
102 | 0% {
103 | -o-transform: rotate(0deg);
104 | }
105 | 100% {
106 | -o-transform: rotate(359deg);
107 | }
108 | }
109 | @keyframes spin {
110 | 0% {
111 | -webkit-transform: rotate(0deg);
112 | transform: rotate(0deg);
113 | }
114 | 100% {
115 | -webkit-transform: rotate(359deg);
116 | transform: rotate(359deg);
117 | }
118 | }
119 | .fa-rotate-90 {
120 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
121 | -webkit-transform: rotate(90deg);
122 | -moz-transform: rotate(90deg);
123 | -ms-transform: rotate(90deg);
124 | -o-transform: rotate(90deg);
125 | transform: rotate(90deg);
126 | }
127 | .fa-rotate-180 {
128 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
129 | -webkit-transform: rotate(180deg);
130 | -moz-transform: rotate(180deg);
131 | -ms-transform: rotate(180deg);
132 | -o-transform: rotate(180deg);
133 | transform: rotate(180deg);
134 | }
135 | .fa-rotate-270 {
136 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
137 | -webkit-transform: rotate(270deg);
138 | -moz-transform: rotate(270deg);
139 | -ms-transform: rotate(270deg);
140 | -o-transform: rotate(270deg);
141 | transform: rotate(270deg);
142 | }
143 | .fa-flip-horizontal {
144 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);
145 | -webkit-transform: scale(-1, 1);
146 | -moz-transform: scale(-1, 1);
147 | -ms-transform: scale(-1, 1);
148 | -o-transform: scale(-1, 1);
149 | transform: scale(-1, 1);
150 | }
151 | .fa-flip-vertical {
152 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);
153 | -webkit-transform: scale(1, -1);
154 | -moz-transform: scale(1, -1);
155 | -ms-transform: scale(1, -1);
156 | -o-transform: scale(1, -1);
157 | transform: scale(1, -1);
158 | }
159 | .fa-stack {
160 | position: relative;
161 | display: inline-block;
162 | width: 2em;
163 | height: 2em;
164 | line-height: 2em;
165 | vertical-align: middle;
166 | }
167 | .fa-stack-1x,
168 | .fa-stack-2x {
169 | position: absolute;
170 | left: 0;
171 | width: 100%;
172 | text-align: center;
173 | }
174 | .fa-stack-1x {
175 | line-height: inherit;
176 | }
177 | .fa-stack-2x {
178 | font-size: 2em;
179 | }
180 | .fa-inverse {
181 | color: #ffffff;
182 | }
183 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
184 | readers do not read off random characters that represent icons */
185 | .fa-glass:before {
186 | content: "\f000";
187 | }
188 | .fa-music:before {
189 | content: "\f001";
190 | }
191 | .fa-search:before {
192 | content: "\f002";
193 | }
194 | .fa-envelope-o:before {
195 | content: "\f003";
196 | }
197 | .fa-heart:before {
198 | content: "\f004";
199 | }
200 | .fa-star:before {
201 | content: "\f005";
202 | }
203 | .fa-star-o:before {
204 | content: "\f006";
205 | }
206 | .fa-user:before {
207 | content: "\f007";
208 | }
209 | .fa-film:before {
210 | content: "\f008";
211 | }
212 | .fa-th-large:before {
213 | content: "\f009";
214 | }
215 | .fa-th:before {
216 | content: "\f00a";
217 | }
218 | .fa-th-list:before {
219 | content: "\f00b";
220 | }
221 | .fa-check:before {
222 | content: "\f00c";
223 | }
224 | .fa-times:before {
225 | content: "\f00d";
226 | }
227 | .fa-search-plus:before {
228 | content: "\f00e";
229 | }
230 | .fa-search-minus:before {
231 | content: "\f010";
232 | }
233 | .fa-power-off:before {
234 | content: "\f011";
235 | }
236 | .fa-signal:before {
237 | content: "\f012";
238 | }
239 | .fa-gear:before,
240 | .fa-cog:before {
241 | content: "\f013";
242 | }
243 | .fa-trash-o:before {
244 | content: "\f014";
245 | }
246 | .fa-home:before {
247 | content: "\f015";
248 | }
249 | .fa-file-o:before {
250 | content: "\f016";
251 | }
252 | .fa-clock-o:before {
253 | content: "\f017";
254 | }
255 | .fa-road:before {
256 | content: "\f018";
257 | }
258 | .fa-download:before {
259 | content: "\f019";
260 | }
261 | .fa-arrow-circle-o-down:before {
262 | content: "\f01a";
263 | }
264 | .fa-arrow-circle-o-up:before {
265 | content: "\f01b";
266 | }
267 | .fa-inbox:before {
268 | content: "\f01c";
269 | }
270 | .fa-play-circle-o:before {
271 | content: "\f01d";
272 | }
273 | .fa-rotate-right:before,
274 | .fa-repeat:before {
275 | content: "\f01e";
276 | }
277 | .fa-refresh:before {
278 | content: "\f021";
279 | }
280 | .fa-list-alt:before {
281 | content: "\f022";
282 | }
283 | .fa-lock:before {
284 | content: "\f023";
285 | }
286 | .fa-flag:before {
287 | content: "\f024";
288 | }
289 | .fa-headphones:before {
290 | content: "\f025";
291 | }
292 | .fa-volume-off:before {
293 | content: "\f026";
294 | }
295 | .fa-volume-down:before {
296 | content: "\f027";
297 | }
298 | .fa-volume-up:before {
299 | content: "\f028";
300 | }
301 | .fa-qrcode:before {
302 | content: "\f029";
303 | }
304 | .fa-barcode:before {
305 | content: "\f02a";
306 | }
307 | .fa-tag:before {
308 | content: "\f02b";
309 | }
310 | .fa-tags:before {
311 | content: "\f02c";
312 | }
313 | .fa-book:before {
314 | content: "\f02d";
315 | }
316 | .fa-bookmark:before {
317 | content: "\f02e";
318 | }
319 | .fa-print:before {
320 | content: "\f02f";
321 | }
322 | .fa-camera:before {
323 | content: "\f030";
324 | }
325 | .fa-font:before {
326 | content: "\f031";
327 | }
328 | .fa-bold:before {
329 | content: "\f032";
330 | }
331 | .fa-italic:before {
332 | content: "\f033";
333 | }
334 | .fa-text-height:before {
335 | content: "\f034";
336 | }
337 | .fa-text-width:before {
338 | content: "\f035";
339 | }
340 | .fa-align-left:before {
341 | content: "\f036";
342 | }
343 | .fa-align-center:before {
344 | content: "\f037";
345 | }
346 | .fa-align-right:before {
347 | content: "\f038";
348 | }
349 | .fa-align-justify:before {
350 | content: "\f039";
351 | }
352 | .fa-list:before {
353 | content: "\f03a";
354 | }
355 | .fa-dedent:before,
356 | .fa-outdent:before {
357 | content: "\f03b";
358 | }
359 | .fa-indent:before {
360 | content: "\f03c";
361 | }
362 | .fa-video-camera:before {
363 | content: "\f03d";
364 | }
365 | .fa-photo:before,
366 | .fa-image:before,
367 | .fa-picture-o:before {
368 | content: "\f03e";
369 | }
370 | .fa-pencil:before {
371 | content: "\f040";
372 | }
373 | .fa-map-marker:before {
374 | content: "\f041";
375 | }
376 | .fa-adjust:before {
377 | content: "\f042";
378 | }
379 | .fa-tint:before {
380 | content: "\f043";
381 | }
382 | .fa-edit:before,
383 | .fa-pencil-square-o:before {
384 | content: "\f044";
385 | }
386 | .fa-share-square-o:before {
387 | content: "\f045";
388 | }
389 | .fa-check-square-o:before {
390 | content: "\f046";
391 | }
392 | .fa-arrows:before {
393 | content: "\f047";
394 | }
395 | .fa-step-backward:before {
396 | content: "\f048";
397 | }
398 | .fa-fast-backward:before {
399 | content: "\f049";
400 | }
401 | .fa-backward:before {
402 | content: "\f04a";
403 | }
404 | .fa-play:before {
405 | content: "\f04b";
406 | }
407 | .fa-pause:before {
408 | content: "\f04c";
409 | }
410 | .fa-stop:before {
411 | content: "\f04d";
412 | }
413 | .fa-forward:before {
414 | content: "\f04e";
415 | }
416 | .fa-fast-forward:before {
417 | content: "\f050";
418 | }
419 | .fa-step-forward:before {
420 | content: "\f051";
421 | }
422 | .fa-eject:before {
423 | content: "\f052";
424 | }
425 | .fa-chevron-left:before {
426 | content: "\f053";
427 | }
428 | .fa-chevron-right:before {
429 | content: "\f054";
430 | }
431 | .fa-plus-circle:before {
432 | content: "\f055";
433 | }
434 | .fa-minus-circle:before {
435 | content: "\f056";
436 | }
437 | .fa-times-circle:before {
438 | content: "\f057";
439 | }
440 | .fa-check-circle:before {
441 | content: "\f058";
442 | }
443 | .fa-question-circle:before {
444 | content: "\f059";
445 | }
446 | .fa-info-circle:before {
447 | content: "\f05a";
448 | }
449 | .fa-crosshairs:before {
450 | content: "\f05b";
451 | }
452 | .fa-times-circle-o:before {
453 | content: "\f05c";
454 | }
455 | .fa-check-circle-o:before {
456 | content: "\f05d";
457 | }
458 | .fa-ban:before {
459 | content: "\f05e";
460 | }
461 | .fa-arrow-left:before {
462 | content: "\f060";
463 | }
464 | .fa-arrow-right:before {
465 | content: "\f061";
466 | }
467 | .fa-arrow-up:before {
468 | content: "\f062";
469 | }
470 | .fa-arrow-down:before {
471 | content: "\f063";
472 | }
473 | .fa-mail-forward:before,
474 | .fa-share:before {
475 | content: "\f064";
476 | }
477 | .fa-expand:before {
478 | content: "\f065";
479 | }
480 | .fa-compress:before {
481 | content: "\f066";
482 | }
483 | .fa-plus:before {
484 | content: "\f067";
485 | }
486 | .fa-minus:before {
487 | content: "\f068";
488 | }
489 | .fa-asterisk:before {
490 | content: "\f069";
491 | }
492 | .fa-exclamation-circle:before {
493 | content: "\f06a";
494 | }
495 | .fa-gift:before {
496 | content: "\f06b";
497 | }
498 | .fa-leaf:before {
499 | content: "\f06c";
500 | }
501 | .fa-fire:before {
502 | content: "\f06d";
503 | }
504 | .fa-eye:before {
505 | content: "\f06e";
506 | }
507 | .fa-eye-slash:before {
508 | content: "\f070";
509 | }
510 | .fa-warning:before,
511 | .fa-exclamation-triangle:before {
512 | content: "\f071";
513 | }
514 | .fa-plane:before {
515 | content: "\f072";
516 | }
517 | .fa-calendar:before {
518 | content: "\f073";
519 | }
520 | .fa-random:before {
521 | content: "\f074";
522 | }
523 | .fa-comment:before {
524 | content: "\f075";
525 | }
526 | .fa-magnet:before {
527 | content: "\f076";
528 | }
529 | .fa-chevron-up:before {
530 | content: "\f077";
531 | }
532 | .fa-chevron-down:before {
533 | content: "\f078";
534 | }
535 | .fa-retweet:before {
536 | content: "\f079";
537 | }
538 | .fa-shopping-cart:before {
539 | content: "\f07a";
540 | }
541 | .fa-folder:before {
542 | content: "\f07b";
543 | }
544 | .fa-folder-open:before {
545 | content: "\f07c";
546 | }
547 | .fa-arrows-v:before {
548 | content: "\f07d";
549 | }
550 | .fa-arrows-h:before {
551 | content: "\f07e";
552 | }
553 | .fa-bar-chart-o:before {
554 | content: "\f080";
555 | }
556 | .fa-twitter-square:before {
557 | content: "\f081";
558 | }
559 | .fa-facebook-square:before {
560 | content: "\f082";
561 | }
562 | .fa-camera-retro:before {
563 | content: "\f083";
564 | }
565 | .fa-key:before {
566 | content: "\f084";
567 | }
568 | .fa-gears:before,
569 | .fa-cogs:before {
570 | content: "\f085";
571 | }
572 | .fa-comments:before {
573 | content: "\f086";
574 | }
575 | .fa-thumbs-o-up:before {
576 | content: "\f087";
577 | }
578 | .fa-thumbs-o-down:before {
579 | content: "\f088";
580 | }
581 | .fa-star-half:before {
582 | content: "\f089";
583 | }
584 | .fa-heart-o:before {
585 | content: "\f08a";
586 | }
587 | .fa-sign-out:before {
588 | content: "\f08b";
589 | }
590 | .fa-linkedin-square:before {
591 | content: "\f08c";
592 | }
593 | .fa-thumb-tack:before {
594 | content: "\f08d";
595 | }
596 | .fa-external-link:before {
597 | content: "\f08e";
598 | }
599 | .fa-sign-in:before {
600 | content: "\f090";
601 | }
602 | .fa-trophy:before {
603 | content: "\f091";
604 | }
605 | .fa-github-square:before {
606 | content: "\f092";
607 | }
608 | .fa-upload:before {
609 | content: "\f093";
610 | }
611 | .fa-lemon-o:before {
612 | content: "\f094";
613 | }
614 | .fa-phone:before {
615 | content: "\f095";
616 | }
617 | .fa-square-o:before {
618 | content: "\f096";
619 | }
620 | .fa-bookmark-o:before {
621 | content: "\f097";
622 | }
623 | .fa-phone-square:before {
624 | content: "\f098";
625 | }
626 | .fa-twitter:before {
627 | content: "\f099";
628 | }
629 | .fa-facebook:before {
630 | content: "\f09a";
631 | }
632 | .fa-github:before {
633 | content: "\f09b";
634 | }
635 | .fa-unlock:before {
636 | content: "\f09c";
637 | }
638 | .fa-credit-card:before {
639 | content: "\f09d";
640 | }
641 | .fa-rss:before {
642 | content: "\f09e";
643 | }
644 | .fa-hdd-o:before {
645 | content: "\f0a0";
646 | }
647 | .fa-bullhorn:before {
648 | content: "\f0a1";
649 | }
650 | .fa-bell:before {
651 | content: "\f0f3";
652 | }
653 | .fa-certificate:before {
654 | content: "\f0a3";
655 | }
656 | .fa-hand-o-right:before {
657 | content: "\f0a4";
658 | }
659 | .fa-hand-o-left:before {
660 | content: "\f0a5";
661 | }
662 | .fa-hand-o-up:before {
663 | content: "\f0a6";
664 | }
665 | .fa-hand-o-down:before {
666 | content: "\f0a7";
667 | }
668 | .fa-arrow-circle-left:before {
669 | content: "\f0a8";
670 | }
671 | .fa-arrow-circle-right:before {
672 | content: "\f0a9";
673 | }
674 | .fa-arrow-circle-up:before {
675 | content: "\f0aa";
676 | }
677 | .fa-arrow-circle-down:before {
678 | content: "\f0ab";
679 | }
680 | .fa-globe:before {
681 | content: "\f0ac";
682 | }
683 | .fa-wrench:before {
684 | content: "\f0ad";
685 | }
686 | .fa-tasks:before {
687 | content: "\f0ae";
688 | }
689 | .fa-filter:before {
690 | content: "\f0b0";
691 | }
692 | .fa-briefcase:before {
693 | content: "\f0b1";
694 | }
695 | .fa-arrows-alt:before {
696 | content: "\f0b2";
697 | }
698 | .fa-group:before,
699 | .fa-users:before {
700 | content: "\f0c0";
701 | }
702 | .fa-chain:before,
703 | .fa-link:before {
704 | content: "\f0c1";
705 | }
706 | .fa-cloud:before {
707 | content: "\f0c2";
708 | }
709 | .fa-flask:before {
710 | content: "\f0c3";
711 | }
712 | .fa-cut:before,
713 | .fa-scissors:before {
714 | content: "\f0c4";
715 | }
716 | .fa-copy:before,
717 | .fa-files-o:before {
718 | content: "\f0c5";
719 | }
720 | .fa-paperclip:before {
721 | content: "\f0c6";
722 | }
723 | .fa-save:before,
724 | .fa-floppy-o:before {
725 | content: "\f0c7";
726 | }
727 | .fa-square:before {
728 | content: "\f0c8";
729 | }
730 | .fa-navicon:before,
731 | .fa-reorder:before,
732 | .fa-bars:before {
733 | content: "\f0c9";
734 | }
735 | .fa-list-ul:before {
736 | content: "\f0ca";
737 | }
738 | .fa-list-ol:before {
739 | content: "\f0cb";
740 | }
741 | .fa-strikethrough:before {
742 | content: "\f0cc";
743 | }
744 | .fa-underline:before {
745 | content: "\f0cd";
746 | }
747 | .fa-table:before {
748 | content: "\f0ce";
749 | }
750 | .fa-magic:before {
751 | content: "\f0d0";
752 | }
753 | .fa-truck:before {
754 | content: "\f0d1";
755 | }
756 | .fa-pinterest:before {
757 | content: "\f0d2";
758 | }
759 | .fa-pinterest-square:before {
760 | content: "\f0d3";
761 | }
762 | .fa-google-plus-square:before {
763 | content: "\f0d4";
764 | }
765 | .fa-google-plus:before {
766 | content: "\f0d5";
767 | }
768 | .fa-money:before {
769 | content: "\f0d6";
770 | }
771 | .fa-caret-down:before {
772 | content: "\f0d7";
773 | }
774 | .fa-caret-up:before {
775 | content: "\f0d8";
776 | }
777 | .fa-caret-left:before {
778 | content: "\f0d9";
779 | }
780 | .fa-caret-right:before {
781 | content: "\f0da";
782 | }
783 | .fa-columns:before {
784 | content: "\f0db";
785 | }
786 | .fa-unsorted:before,
787 | .fa-sort:before {
788 | content: "\f0dc";
789 | }
790 | .fa-sort-down:before,
791 | .fa-sort-desc:before {
792 | content: "\f0dd";
793 | }
794 | .fa-sort-up:before,
795 | .fa-sort-asc:before {
796 | content: "\f0de";
797 | }
798 | .fa-envelope:before {
799 | content: "\f0e0";
800 | }
801 | .fa-linkedin:before {
802 | content: "\f0e1";
803 | }
804 | .fa-rotate-left:before,
805 | .fa-undo:before {
806 | content: "\f0e2";
807 | }
808 | .fa-legal:before,
809 | .fa-gavel:before {
810 | content: "\f0e3";
811 | }
812 | .fa-dashboard:before,
813 | .fa-tachometer:before {
814 | content: "\f0e4";
815 | }
816 | .fa-comment-o:before {
817 | content: "\f0e5";
818 | }
819 | .fa-comments-o:before {
820 | content: "\f0e6";
821 | }
822 | .fa-flash:before,
823 | .fa-bolt:before {
824 | content: "\f0e7";
825 | }
826 | .fa-sitemap:before {
827 | content: "\f0e8";
828 | }
829 | .fa-umbrella:before {
830 | content: "\f0e9";
831 | }
832 | .fa-paste:before,
833 | .fa-clipboard:before {
834 | content: "\f0ea";
835 | }
836 | .fa-lightbulb-o:before {
837 | content: "\f0eb";
838 | }
839 | .fa-exchange:before {
840 | content: "\f0ec";
841 | }
842 | .fa-cloud-download:before {
843 | content: "\f0ed";
844 | }
845 | .fa-cloud-upload:before {
846 | content: "\f0ee";
847 | }
848 | .fa-user-md:before {
849 | content: "\f0f0";
850 | }
851 | .fa-stethoscope:before {
852 | content: "\f0f1";
853 | }
854 | .fa-suitcase:before {
855 | content: "\f0f2";
856 | }
857 | .fa-bell-o:before {
858 | content: "\f0a2";
859 | }
860 | .fa-coffee:before {
861 | content: "\f0f4";
862 | }
863 | .fa-cutlery:before {
864 | content: "\f0f5";
865 | }
866 | .fa-file-text-o:before {
867 | content: "\f0f6";
868 | }
869 | .fa-building-o:before {
870 | content: "\f0f7";
871 | }
872 | .fa-hospital-o:before {
873 | content: "\f0f8";
874 | }
875 | .fa-ambulance:before {
876 | content: "\f0f9";
877 | }
878 | .fa-medkit:before {
879 | content: "\f0fa";
880 | }
881 | .fa-fighter-jet:before {
882 | content: "\f0fb";
883 | }
884 | .fa-beer:before {
885 | content: "\f0fc";
886 | }
887 | .fa-h-square:before {
888 | content: "\f0fd";
889 | }
890 | .fa-plus-square:before {
891 | content: "\f0fe";
892 | }
893 | .fa-angle-double-left:before {
894 | content: "\f100";
895 | }
896 | .fa-angle-double-right:before {
897 | content: "\f101";
898 | }
899 | .fa-angle-double-up:before {
900 | content: "\f102";
901 | }
902 | .fa-angle-double-down:before {
903 | content: "\f103";
904 | }
905 | .fa-angle-left:before {
906 | content: "\f104";
907 | }
908 | .fa-angle-right:before {
909 | content: "\f105";
910 | }
911 | .fa-angle-up:before {
912 | content: "\f106";
913 | }
914 | .fa-angle-down:before {
915 | content: "\f107";
916 | }
917 | .fa-desktop:before {
918 | content: "\f108";
919 | }
920 | .fa-laptop:before {
921 | content: "\f109";
922 | }
923 | .fa-tablet:before {
924 | content: "\f10a";
925 | }
926 | .fa-mobile-phone:before,
927 | .fa-mobile:before {
928 | content: "\f10b";
929 | }
930 | .fa-circle-o:before {
931 | content: "\f10c";
932 | }
933 | .fa-quote-left:before {
934 | content: "\f10d";
935 | }
936 | .fa-quote-right:before {
937 | content: "\f10e";
938 | }
939 | .fa-spinner:before {
940 | content: "\f110";
941 | }
942 | .fa-circle:before {
943 | content: "\f111";
944 | }
945 | .fa-mail-reply:before,
946 | .fa-reply:before {
947 | content: "\f112";
948 | }
949 | .fa-github-alt:before {
950 | content: "\f113";
951 | }
952 | .fa-folder-o:before {
953 | content: "\f114";
954 | }
955 | .fa-folder-open-o:before {
956 | content: "\f115";
957 | }
958 | .fa-smile-o:before {
959 | content: "\f118";
960 | }
961 | .fa-frown-o:before {
962 | content: "\f119";
963 | }
964 | .fa-meh-o:before {
965 | content: "\f11a";
966 | }
967 | .fa-gamepad:before {
968 | content: "\f11b";
969 | }
970 | .fa-keyboard-o:before {
971 | content: "\f11c";
972 | }
973 | .fa-flag-o:before {
974 | content: "\f11d";
975 | }
976 | .fa-flag-checkered:before {
977 | content: "\f11e";
978 | }
979 | .fa-terminal:before {
980 | content: "\f120";
981 | }
982 | .fa-code:before {
983 | content: "\f121";
984 | }
985 | .fa-mail-reply-all:before,
986 | .fa-reply-all:before {
987 | content: "\f122";
988 | }
989 | .fa-star-half-empty:before,
990 | .fa-star-half-full:before,
991 | .fa-star-half-o:before {
992 | content: "\f123";
993 | }
994 | .fa-location-arrow:before {
995 | content: "\f124";
996 | }
997 | .fa-crop:before {
998 | content: "\f125";
999 | }
1000 | .fa-code-fork:before {
1001 | content: "\f126";
1002 | }
1003 | .fa-unlink:before,
1004 | .fa-chain-broken:before {
1005 | content: "\f127";
1006 | }
1007 | .fa-question:before {
1008 | content: "\f128";
1009 | }
1010 | .fa-info:before {
1011 | content: "\f129";
1012 | }
1013 | .fa-exclamation:before {
1014 | content: "\f12a";
1015 | }
1016 | .fa-superscript:before {
1017 | content: "\f12b";
1018 | }
1019 | .fa-subscript:before {
1020 | content: "\f12c";
1021 | }
1022 | .fa-eraser:before {
1023 | content: "\f12d";
1024 | }
1025 | .fa-puzzle-piece:before {
1026 | content: "\f12e";
1027 | }
1028 | .fa-microphone:before {
1029 | content: "\f130";
1030 | }
1031 | .fa-microphone-slash:before {
1032 | content: "\f131";
1033 | }
1034 | .fa-shield:before {
1035 | content: "\f132";
1036 | }
1037 | .fa-calendar-o:before {
1038 | content: "\f133";
1039 | }
1040 | .fa-fire-extinguisher:before {
1041 | content: "\f134";
1042 | }
1043 | .fa-rocket:before {
1044 | content: "\f135";
1045 | }
1046 | .fa-maxcdn:before {
1047 | content: "\f136";
1048 | }
1049 | .fa-chevron-circle-left:before {
1050 | content: "\f137";
1051 | }
1052 | .fa-chevron-circle-right:before {
1053 | content: "\f138";
1054 | }
1055 | .fa-chevron-circle-up:before {
1056 | content: "\f139";
1057 | }
1058 | .fa-chevron-circle-down:before {
1059 | content: "\f13a";
1060 | }
1061 | .fa-html5:before {
1062 | content: "\f13b";
1063 | }
1064 | .fa-css3:before {
1065 | content: "\f13c";
1066 | }
1067 | .fa-anchor:before {
1068 | content: "\f13d";
1069 | }
1070 | .fa-unlock-alt:before {
1071 | content: "\f13e";
1072 | }
1073 | .fa-bullseye:before {
1074 | content: "\f140";
1075 | }
1076 | .fa-ellipsis-h:before {
1077 | content: "\f141";
1078 | }
1079 | .fa-ellipsis-v:before {
1080 | content: "\f142";
1081 | }
1082 | .fa-rss-square:before {
1083 | content: "\f143";
1084 | }
1085 | .fa-play-circle:before {
1086 | content: "\f144";
1087 | }
1088 | .fa-ticket:before {
1089 | content: "\f145";
1090 | }
1091 | .fa-minus-square:before {
1092 | content: "\f146";
1093 | }
1094 | .fa-minus-square-o:before {
1095 | content: "\f147";
1096 | }
1097 | .fa-level-up:before {
1098 | content: "\f148";
1099 | }
1100 | .fa-level-down:before {
1101 | content: "\f149";
1102 | }
1103 | .fa-check-square:before {
1104 | content: "\f14a";
1105 | }
1106 | .fa-pencil-square:before {
1107 | content: "\f14b";
1108 | }
1109 | .fa-external-link-square:before {
1110 | content: "\f14c";
1111 | }
1112 | .fa-share-square:before {
1113 | content: "\f14d";
1114 | }
1115 | .fa-compass:before {
1116 | content: "\f14e";
1117 | }
1118 | .fa-toggle-down:before,
1119 | .fa-caret-square-o-down:before {
1120 | content: "\f150";
1121 | }
1122 | .fa-toggle-up:before,
1123 | .fa-caret-square-o-up:before {
1124 | content: "\f151";
1125 | }
1126 | .fa-toggle-right:before,
1127 | .fa-caret-square-o-right:before {
1128 | content: "\f152";
1129 | }
1130 | .fa-euro:before,
1131 | .fa-eur:before {
1132 | content: "\f153";
1133 | }
1134 | .fa-gbp:before {
1135 | content: "\f154";
1136 | }
1137 | .fa-dollar:before,
1138 | .fa-usd:before {
1139 | content: "\f155";
1140 | }
1141 | .fa-rupee:before,
1142 | .fa-inr:before {
1143 | content: "\f156";
1144 | }
1145 | .fa-cny:before,
1146 | .fa-rmb:before,
1147 | .fa-yen:before,
1148 | .fa-jpy:before {
1149 | content: "\f157";
1150 | }
1151 | .fa-ruble:before,
1152 | .fa-rouble:before,
1153 | .fa-rub:before {
1154 | content: "\f158";
1155 | }
1156 | .fa-won:before,
1157 | .fa-krw:before {
1158 | content: "\f159";
1159 | }
1160 | .fa-bitcoin:before,
1161 | .fa-btc:before {
1162 | content: "\f15a";
1163 | }
1164 | .fa-file:before {
1165 | content: "\f15b";
1166 | }
1167 | .fa-file-text:before {
1168 | content: "\f15c";
1169 | }
1170 | .fa-sort-alpha-asc:before {
1171 | content: "\f15d";
1172 | }
1173 | .fa-sort-alpha-desc:before {
1174 | content: "\f15e";
1175 | }
1176 | .fa-sort-amount-asc:before {
1177 | content: "\f160";
1178 | }
1179 | .fa-sort-amount-desc:before {
1180 | content: "\f161";
1181 | }
1182 | .fa-sort-numeric-asc:before {
1183 | content: "\f162";
1184 | }
1185 | .fa-sort-numeric-desc:before {
1186 | content: "\f163";
1187 | }
1188 | .fa-thumbs-up:before {
1189 | content: "\f164";
1190 | }
1191 | .fa-thumbs-down:before {
1192 | content: "\f165";
1193 | }
1194 | .fa-youtube-square:before {
1195 | content: "\f166";
1196 | }
1197 | .fa-youtube:before {
1198 | content: "\f167";
1199 | }
1200 | .fa-xing:before {
1201 | content: "\f168";
1202 | }
1203 | .fa-xing-square:before {
1204 | content: "\f169";
1205 | }
1206 | .fa-youtube-play:before {
1207 | content: "\f16a";
1208 | }
1209 | .fa-dropbox:before {
1210 | content: "\f16b";
1211 | }
1212 | .fa-stack-overflow:before {
1213 | content: "\f16c";
1214 | }
1215 | .fa-instagram:before {
1216 | content: "\f16d";
1217 | }
1218 | .fa-flickr:before {
1219 | content: "\f16e";
1220 | }
1221 | .fa-adn:before {
1222 | content: "\f170";
1223 | }
1224 | .fa-bitbucket:before {
1225 | content: "\f171";
1226 | }
1227 | .fa-bitbucket-square:before {
1228 | content: "\f172";
1229 | }
1230 | .fa-tumblr:before {
1231 | content: "\f173";
1232 | }
1233 | .fa-tumblr-square:before {
1234 | content: "\f174";
1235 | }
1236 | .fa-long-arrow-down:before {
1237 | content: "\f175";
1238 | }
1239 | .fa-long-arrow-up:before {
1240 | content: "\f176";
1241 | }
1242 | .fa-long-arrow-left:before {
1243 | content: "\f177";
1244 | }
1245 | .fa-long-arrow-right:before {
1246 | content: "\f178";
1247 | }
1248 | .fa-apple:before {
1249 | content: "\f179";
1250 | }
1251 | .fa-windows:before {
1252 | content: "\f17a";
1253 | }
1254 | .fa-android:before {
1255 | content: "\f17b";
1256 | }
1257 | .fa-linux:before {
1258 | content: "\f17c";
1259 | }
1260 | .fa-dribbble:before {
1261 | content: "\f17d";
1262 | }
1263 | .fa-skype:before {
1264 | content: "\f17e";
1265 | }
1266 | .fa-foursquare:before {
1267 | content: "\f180";
1268 | }
1269 | .fa-trello:before {
1270 | content: "\f181";
1271 | }
1272 | .fa-female:before {
1273 | content: "\f182";
1274 | }
1275 | .fa-male:before {
1276 | content: "\f183";
1277 | }
1278 | .fa-gittip:before {
1279 | content: "\f184";
1280 | }
1281 | .fa-sun-o:before {
1282 | content: "\f185";
1283 | }
1284 | .fa-moon-o:before {
1285 | content: "\f186";
1286 | }
1287 | .fa-archive:before {
1288 | content: "\f187";
1289 | }
1290 | .fa-bug:before {
1291 | content: "\f188";
1292 | }
1293 | .fa-vk:before {
1294 | content: "\f189";
1295 | }
1296 | .fa-weibo:before {
1297 | content: "\f18a";
1298 | }
1299 | .fa-renren:before {
1300 | content: "\f18b";
1301 | }
1302 | .fa-pagelines:before {
1303 | content: "\f18c";
1304 | }
1305 | .fa-stack-exchange:before {
1306 | content: "\f18d";
1307 | }
1308 | .fa-arrow-circle-o-right:before {
1309 | content: "\f18e";
1310 | }
1311 | .fa-arrow-circle-o-left:before {
1312 | content: "\f190";
1313 | }
1314 | .fa-toggle-left:before,
1315 | .fa-caret-square-o-left:before {
1316 | content: "\f191";
1317 | }
1318 | .fa-dot-circle-o:before {
1319 | content: "\f192";
1320 | }
1321 | .fa-wheelchair:before {
1322 | content: "\f193";
1323 | }
1324 | .fa-vimeo-square:before {
1325 | content: "\f194";
1326 | }
1327 | .fa-turkish-lira:before,
1328 | .fa-try:before {
1329 | content: "\f195";
1330 | }
1331 | .fa-plus-square-o:before {
1332 | content: "\f196";
1333 | }
1334 | .fa-space-shuttle:before {
1335 | content: "\f197";
1336 | }
1337 | .fa-slack:before {
1338 | content: "\f198";
1339 | }
1340 | .fa-envelope-square:before {
1341 | content: "\f199";
1342 | }
1343 | .fa-wordpress:before {
1344 | content: "\f19a";
1345 | }
1346 | .fa-openid:before {
1347 | content: "\f19b";
1348 | }
1349 | .fa-institution:before,
1350 | .fa-bank:before,
1351 | .fa-university:before {
1352 | content: "\f19c";
1353 | }
1354 | .fa-mortar-board:before,
1355 | .fa-graduation-cap:before {
1356 | content: "\f19d";
1357 | }
1358 | .fa-yahoo:before {
1359 | content: "\f19e";
1360 | }
1361 | .fa-google:before {
1362 | content: "\f1a0";
1363 | }
1364 | .fa-reddit:before {
1365 | content: "\f1a1";
1366 | }
1367 | .fa-reddit-square:before {
1368 | content: "\f1a2";
1369 | }
1370 | .fa-stumbleupon-circle:before {
1371 | content: "\f1a3";
1372 | }
1373 | .fa-stumbleupon:before {
1374 | content: "\f1a4";
1375 | }
1376 | .fa-delicious:before {
1377 | content: "\f1a5";
1378 | }
1379 | .fa-digg:before {
1380 | content: "\f1a6";
1381 | }
1382 | .fa-pied-piper-square:before,
1383 | .fa-pied-piper:before {
1384 | content: "\f1a7";
1385 | }
1386 | .fa-pied-piper-alt:before {
1387 | content: "\f1a8";
1388 | }
1389 | .fa-drupal:before {
1390 | content: "\f1a9";
1391 | }
1392 | .fa-joomla:before {
1393 | content: "\f1aa";
1394 | }
1395 | .fa-language:before {
1396 | content: "\f1ab";
1397 | }
1398 | .fa-fax:before {
1399 | content: "\f1ac";
1400 | }
1401 | .fa-building:before {
1402 | content: "\f1ad";
1403 | }
1404 | .fa-child:before {
1405 | content: "\f1ae";
1406 | }
1407 | .fa-paw:before {
1408 | content: "\f1b0";
1409 | }
1410 | .fa-spoon:before {
1411 | content: "\f1b1";
1412 | }
1413 | .fa-cube:before {
1414 | content: "\f1b2";
1415 | }
1416 | .fa-cubes:before {
1417 | content: "\f1b3";
1418 | }
1419 | .fa-behance:before {
1420 | content: "\f1b4";
1421 | }
1422 | .fa-behance-square:before {
1423 | content: "\f1b5";
1424 | }
1425 | .fa-steam:before {
1426 | content: "\f1b6";
1427 | }
1428 | .fa-steam-square:before {
1429 | content: "\f1b7";
1430 | }
1431 | .fa-recycle:before {
1432 | content: "\f1b8";
1433 | }
1434 | .fa-automobile:before,
1435 | .fa-car:before {
1436 | content: "\f1b9";
1437 | }
1438 | .fa-cab:before,
1439 | .fa-taxi:before {
1440 | content: "\f1ba";
1441 | }
1442 | .fa-tree:before {
1443 | content: "\f1bb";
1444 | }
1445 | .fa-spotify:before {
1446 | content: "\f1bc";
1447 | }
1448 | .fa-deviantart:before {
1449 | content: "\f1bd";
1450 | }
1451 | .fa-soundcloud:before {
1452 | content: "\f1be";
1453 | }
1454 | .fa-database:before {
1455 | content: "\f1c0";
1456 | }
1457 | .fa-file-pdf-o:before {
1458 | content: "\f1c1";
1459 | }
1460 | .fa-file-word-o:before {
1461 | content: "\f1c2";
1462 | }
1463 | .fa-file-excel-o:before {
1464 | content: "\f1c3";
1465 | }
1466 | .fa-file-powerpoint-o:before {
1467 | content: "\f1c4";
1468 | }
1469 | .fa-file-photo-o:before,
1470 | .fa-file-picture-o:before,
1471 | .fa-file-image-o:before {
1472 | content: "\f1c5";
1473 | }
1474 | .fa-file-zip-o:before,
1475 | .fa-file-archive-o:before {
1476 | content: "\f1c6";
1477 | }
1478 | .fa-file-sound-o:before,
1479 | .fa-file-audio-o:before {
1480 | content: "\f1c7";
1481 | }
1482 | .fa-file-movie-o:before,
1483 | .fa-file-video-o:before {
1484 | content: "\f1c8";
1485 | }
1486 | .fa-file-code-o:before {
1487 | content: "\f1c9";
1488 | }
1489 | .fa-vine:before {
1490 | content: "\f1ca";
1491 | }
1492 | .fa-codepen:before {
1493 | content: "\f1cb";
1494 | }
1495 | .fa-jsfiddle:before {
1496 | content: "\f1cc";
1497 | }
1498 | .fa-life-bouy:before,
1499 | .fa-life-saver:before,
1500 | .fa-support:before,
1501 | .fa-life-ring:before {
1502 | content: "\f1cd";
1503 | }
1504 | .fa-circle-o-notch:before {
1505 | content: "\f1ce";
1506 | }
1507 | .fa-ra:before,
1508 | .fa-rebel:before {
1509 | content: "\f1d0";
1510 | }
1511 | .fa-ge:before,
1512 | .fa-empire:before {
1513 | content: "\f1d1";
1514 | }
1515 | .fa-git-square:before {
1516 | content: "\f1d2";
1517 | }
1518 | .fa-git:before {
1519 | content: "\f1d3";
1520 | }
1521 | .fa-hacker-news:before {
1522 | content: "\f1d4";
1523 | }
1524 | .fa-tencent-weibo:before {
1525 | content: "\f1d5";
1526 | }
1527 | .fa-qq:before {
1528 | content: "\f1d6";
1529 | }
1530 | .fa-wechat:before,
1531 | .fa-weixin:before {
1532 | content: "\f1d7";
1533 | }
1534 | .fa-send:before,
1535 | .fa-paper-plane:before {
1536 | content: "\f1d8";
1537 | }
1538 | .fa-send-o:before,
1539 | .fa-paper-plane-o:before {
1540 | content: "\f1d9";
1541 | }
1542 | .fa-history:before {
1543 | content: "\f1da";
1544 | }
1545 | .fa-circle-thin:before {
1546 | content: "\f1db";
1547 | }
1548 | .fa-header:before {
1549 | content: "\f1dc";
1550 | }
1551 | .fa-paragraph:before {
1552 | content: "\f1dd";
1553 | }
1554 | .fa-sliders:before {
1555 | content: "\f1de";
1556 | }
1557 | .fa-share-alt:before {
1558 | content: "\f1e0";
1559 | }
1560 | .fa-share-alt-square:before {
1561 | content: "\f1e1";
1562 | }
1563 | .fa-bomb:before {
1564 | content: "\f1e2";
1565 | }
1566 |
--------------------------------------------------------------------------------
/less/graph.less:
--------------------------------------------------------------------------------
1 | circle {
2 | stroke-width: 1.5px;
3 | }
4 |
5 | line {
6 | stroke: #999;
7 | }
8 |
9 | .edgePath {
10 | stroke: black;
11 | }
12 |
--------------------------------------------------------------------------------
/less/layout.less:
--------------------------------------------------------------------------------
1 | @import "util.less";
2 |
3 | html, body {
4 | background: @whiteGhost;
5 | height: 100%;
6 | font-family: 'Open Sans Light';
7 | }
8 |
9 | #app {
10 | height: 100%;
11 | }
12 |
13 | .whole-page {
14 | height: 100vh;
15 | }
16 |
17 | .center-container {
18 | display: flex;
19 | align-items: center;
20 | justify-content: center;
21 | flex-direction: column;
22 | }
23 |
24 | .page {
25 | height: inherit;
26 | }
27 |
28 | @topMargin: 20px;
29 |
30 | .nav {
31 | width: 250px;
32 | margin-left: @topMargin;
33 | margin-top: @topMargin;
34 | z-index: 1;
35 | }
36 |
37 | .box {
38 | padding: 12px 12px 12px 18px;
39 | border-radius: 3px;
40 | }
41 |
42 | @boxWidth: 400px;
43 |
44 | .center {
45 | flex: none;
46 | max-width: 50%;
47 | }
48 |
49 | .float-right-corner {
50 | position: absolute;
51 | top: 10px;
52 | right: 10px;
53 | }
54 |
55 | .float-bottom-corner {
56 | position: absolute;
57 | bottom: 15px;
58 | right: 15px;
59 | }
60 |
61 | .float-box {
62 | .box;
63 | .BoxShadowHelper(2);
64 | width: @boxWidth;
65 | position: relative;
66 | margin-bottom: 8px;
67 | }
68 |
69 | .float-box--side {
70 | .box;
71 | position: absolute;
72 | }
73 |
74 | // Style
75 |
76 | .blue {
77 | font-family: 'Open Sans Light';
78 | color: @whiteGhost;
79 | background: @blueHavelock;
80 | }
81 |
82 | .blue-box {
83 | // font-size: larger;
84 | .blue;
85 | }
86 |
87 | .top-break {
88 | margin-top: 12px;
89 | }
90 |
91 | .blue-input {
92 | margin-bottom: 10px;
93 | border: 0;
94 | outline: 0;
95 | border-bottom: 1px solid @whiteGhost;
96 | width: 90%;
97 | .blue;
98 | }
99 |
100 | input::-webkit-input-placeholder {
101 | color: fade(@whiteGhost,50%);
102 | }
103 |
104 | input::-moz-input-placeholder {
105 | color: fade(@whiteGhost,50%);
106 | }
107 |
108 | .error-card {
109 | z-index: 2;
110 | // font-size: larger;
111 | color: @whiteGhost;
112 | background: @indianRed;
113 | > p {
114 | margin-bottom: 8px;
115 | }
116 | }
117 |
118 | .notification {
119 | position: absolute;
120 | left: 0;
121 | right: 0;
122 | margin: 20px auto;
123 | }
124 |
125 | .Title {
126 | font-family: 'Open Sans Semibold';
127 | text-transform: uppercase;
128 | }
129 |
130 | .box__title {
131 | margin-top: 8px;
132 | margin-bottom: 12px;
133 | }
134 |
135 | .blue-box__title {
136 | .box__title;
137 | font-family: 'Open Sans Light';
138 | font-weight: 300;
139 | }
140 |
141 | .blue-box__subtitle {
142 | .box__title;
143 | .Title;
144 | }
145 |
146 | .project-name {
147 | .blue-box__title;
148 | text-transform: lowercase;
149 | margin-bottom: 0px;
150 | }
151 |
152 | .build-title {
153 | margin-top: 3px;
154 | }
155 |
156 | .build-name {
157 | cursor: pointer;
158 | font-style: italic;
159 | font-size: medium;
160 | display: inline-block;
161 | vertical-align: middle;
162 | }
163 |
164 | @iconOpacity: 0.7;
165 |
166 | .menu-icon {
167 | margin-right: 3px;
168 | opacity: @iconOpacity;
169 | }
170 |
171 | .build-item {
172 | margin: 0;
173 | padding: 0;
174 | }
175 |
176 | .builds {
177 | margin-bottom: 0px;
178 | margin-top: 0px;
179 | padding-left: 10px;
180 | list-style-type: none;
181 | }
182 |
183 | .icon-btn {
184 | cursor: pointer;
185 | margin-left: 8px;
186 | font-size: 16px;
187 |
188 | }
189 |
190 | .clear-btn {
191 | .icon-btn;
192 | opacity: @iconOpacity;
193 | color: @whiteGhost;
194 | }
195 |
196 | .help-btn {
197 | .icon-btn;
198 | color: @blueHavelock;
199 | font-size: 30px;
200 | }
201 |
202 | .clear-btn:hover {
203 | text-decoration: none;
204 | opacity: 1;
205 | }
206 |
207 | .last-btns {
208 | margin-bottom: 6px;
209 | }
210 |
211 | .btn-container--center {
212 | .last-btns;
213 | text-align: right;
214 | }
215 |
216 | .btn-center {
217 | margin: 0 6px 0 auto;
218 | }
219 |
220 | // Button
221 |
222 | .btn {
223 | .BoxShadowHelper(1);
224 | .box;
225 | cursor: pointer;
226 | cursor: hand;
227 | display: inline-block;
228 | text-align: center;
229 | min-width: 50px;
230 | margin-top: 8px;
231 | margin-bottom: 8px;
232 | padding: 5px;
233 | padding-right: 10px;
234 | padding-left: 10px;
235 | .Title;
236 | }
237 |
238 | .btn--green {
239 | .btn;
240 | background: @greenAtlantis;
241 | }
242 |
243 |
244 | .btn--green--active {
245 | .btn;
246 | background: darken(@greenAtlantis,15%);
247 | }
248 |
249 | .divider {
250 | margin-top: 2px;
251 | display: block;
252 | height: 1px;
253 | background-color: fade(@whiteGhost,50%);
254 | }
255 |
256 | // Platform Buttons
257 |
258 | .platform--btns {
259 | display: flex;
260 | flex-direction: row;
261 | justify-content: space-around;
262 | .last-btns;
263 | }
264 |
265 | // Folder List
266 |
267 | .div-explorer {
268 | width: 0.90 * @boxWidth;
269 | }
270 |
271 | .folder-list {
272 | list-style: none;
273 | padding-left: 10px;
274 | }
275 |
276 | .folder-list__item {
277 | margin-right: 10px;
278 | }
279 |
280 | .file--activate {
281 | color: white;
282 | font-weight: bold;
283 | }
284 |
285 | .clickable {
286 | cursor: pointer;
287 | }
288 |
289 | .file-item {
290 | margin-top: 10px;
291 | }
292 |
293 | .file-item__text {
294 | .file-item;
295 | color: white;
296 | text-decoration: none;
297 | .clickable;
298 | // &:hover {
299 | // .file--activate;
300 | // }
301 | margin-right: 7px;
302 | }
303 |
304 | .file-item__text--activated {
305 | .file-item__text;
306 | .file--activate;
307 | &:hover {
308 | font-weight: initial;
309 | }
310 | }
311 |
312 | .expand-activate {
313 | color: @whiteGhost;
314 | }
315 | .expand-icon {
316 | color: fade(@whiteGhost,70%);
317 | font-size: 0.75em;
318 | &:hover {
319 | .expand-activate;
320 | }
321 | }
322 |
323 | .expand-icon--active {
324 | .expand-icon;
325 | .expand-activate;
326 | }
--------------------------------------------------------------------------------
/less/loader.less:
--------------------------------------------------------------------------------
1 | @loaderHeight: 1px;
2 |
3 | .loader {
4 | height: @loaderHeight;
5 | width: 90%;
6 | position: relative;
7 | overflow: hidden;
8 | background-color: @blueHavelock; // #ddd;
9 | }
10 |
11 | .loader:before{
12 | display: block;
13 | position: absolute;
14 | content: "";
15 | left: -200px;
16 | width: 200px;
17 | height: @loaderHeight;
18 | background-color: @whiteGhost; // @blueHavelock; // #2980b9;
19 | animation: loading 3s linear infinite;
20 | }
21 |
22 | @keyframes loading {
23 | from {left: -200px; width: 30%;}
24 | 50% {width: 30%;}
25 | 70% {width: 50%;}
26 | 80% {left: 50%;}
27 | 90% {left: 105%;}
28 | to {left: 100%;}
29 | }
--------------------------------------------------------------------------------
/less/main.less:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | // Imports
3 | //------------------------------------------------------------------------------
4 |
5 | @import "normalize-3.0.1.less";
6 | @import "font-awesome-4.1.0.less";
7 | @import "colors.less";
8 | @import "graph.less";
9 | @import "layout.less";
10 | @import "loader.less";
11 | @import "overlay.less";
12 |
13 | //------------------------------------------------------------------------------
14 | // Fonts
15 | //------------------------------------------------------------------------------
16 |
17 | @font-face {
18 | font-family: 'Open Sans Light';
19 | font-style: normal;
20 | font-weight: 300;
21 | src: local('Open Sans Light'), local('OpenSans-Light'),
22 | url('../fonts/OpenSans-Light.woff') format('woff');
23 | }
24 |
25 | @font-face {
26 | font-family: 'Open Sans';
27 | font-style: normal;
28 | font-weight: 400;
29 | src: local('Open Sans'), local('OpenSans'),
30 | url('../fonts/OpenSans.woff') format('woff');
31 | }
32 |
33 | @font-face {
34 | font-family: 'Open Sans';
35 | font-style: normal;
36 | font-weight: 600;
37 | src: local('Open Sans Semibold'), local('OpenSans-Semibold'),
38 | url('../fonts/OpenSans-Semibold.woff') format('woff');
39 | }
40 |
41 | @font-face {
42 | font-family: 'Roboto Slab';
43 | font-style: normal;
44 | font-weight: 400;
45 | src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'),
46 | url('../fonts/RobotoSlab-Regular.woff2') format('woff2');
47 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
48 | }
49 |
50 | @font-face {
51 | font-family: 'Droid Sans Mono';
52 | font-style: normal;
53 | font-weight: 400;
54 | src: local('Droid Sans Mono'), local('DroidSansMono'),
55 | url('../fonts/DroidSansMono.woff2') format('woff2');
56 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
57 | }
58 |
59 | .font-light(@size) {
60 | font-family: 'Open Sans Light';
61 | font-size: @size;
62 | font-weight: 300;
63 | }
64 |
65 | .font-normal(@size) {
66 | font-family: 'Open Sans';
67 | font-size: @size;
68 | font-weight: 400;
69 | }
70 |
71 | .font-bold(@size) {
72 | font-family: 'Open Sans';
73 | font-size: @size;
74 | font-weight: 600;
75 | }
76 |
77 | //------------------------------------------------------------------------------
78 | // Variables and Mixins
79 | //------------------------------------------------------------------------------
80 |
81 | @activeBlue: #0075ea;
82 | @borderGrey: #e1e1e1;
83 | @buttonBorderColor: #d7d7d7;
84 | @errorRed: #e44545;
85 | @inactiveGrey: #666;
86 | @warningOrange: #c47f00;
87 | @primaryGreen: #309630;
88 | @tooltipBackgroundColor: #333;
89 | @tooltipZ: 10;
90 |
91 | @modalZ: 1000;
92 |
93 | .border-radius(@topright: 0, @bottomright: 0, @bottomleft: 0, @topleft: 0) {
94 | background-clip: padding-box;
95 | border-top-right-radius: @topright;
96 | border-bottom-right-radius: @bottomright;
97 | border-bottom-left-radius: @bottomleft;
98 | border-top-left-radius: @topleft;
99 | }
100 |
101 | //------------------------------------------------------------------------------
102 | // Tags
103 | //------------------------------------------------------------------------------
104 |
105 | body {
106 | box-sizing: border-box;
107 | color: #444;
108 | }
109 |
110 | // http://stackoverflow.com/a/3397158/2137320
111 | *:focus {
112 | outline: 0;
113 | }
114 |
115 | //------------------------------------------------------------------------------
116 | // Snowflake Classes
117 | //------------------------------------------------------------------------------
118 |
119 | .clr-737fa {
120 | clear: both;
121 | }
122 |
123 | .app-ca3cd {
124 | min-width: 800px;
125 | padding: 20px;
126 | }
127 |
128 | .title-8749a {
129 | font-family: 'Roboto Slab', serif;
130 | font-size: 32px;
131 | float: left;
132 | }
133 |
134 | .version-8838a {
135 | color: #888;
136 | display: inline-block;
137 | .font-normal(14px);
138 | padding-left: 5px;
139 | }
140 |
141 | .logo-0a166 {
142 | display: block;
143 | float: left;
144 | margin: 6px 5px 0 0;
145 | width: 35px;
146 | }
147 |
148 | .settings-link-c1709 {
149 | border: 1px solid @borderGrey;
150 | border-radius: 4px;
151 | cursor: pointer;
152 | float: right;
153 | padding: 10px 20px;
154 | }
155 |
156 | .title-links-42b06 {
157 | float: right;
158 | }
159 |
160 | .link-3d3ad {
161 | color: @inactiveGrey;
162 | cursor: pointer;
163 | display: inline-block;
164 | .font-normal(18px);
165 | margin: 15px 0 0 30px;
166 |
167 | .fa {
168 | margin-right: 5px;
169 | }
170 | }
171 |
172 | .link-3d3ad:hover {
173 | color: @activeBlue;
174 | text-decoration: underline;
175 | }
176 |
177 | .header-a4c14 {
178 | border-bottom: 1px solid @borderGrey;
179 | margin-bottom: 25px;
180 | padding-bottom: 20px;
181 | position: relative;
182 | }
183 |
184 | .btn-da85d {
185 | background: #e6e6e6;
186 | border: 1px solid @buttonBorderColor;
187 | border-radius: 4px;
188 | color: #000;
189 | cursor: pointer;
190 | display: inline-block;
191 | .font-normal(14px);
192 | margin-left: 10px;
193 | padding: 8px 12px;
194 | }
195 |
196 | .count-cfa27 {
197 | color: #555;
198 | display: inline-block;
199 | .font-light(12px);
200 | margin-left: 5px;
201 | }
202 |
203 | .disabled-btn-1884b {
204 | background: #e6e6e6;
205 | border: 0;
206 | border-radius: 4px;
207 | color: #555;
208 | cursor: auto;
209 | display: inline-block;
210 | .font-normal(14px);
211 | margin-right: 10px;
212 | padding: 8px 12px;
213 | }
214 |
215 | .project-1b83a {
216 | margin-bottom: 50px;
217 | }
218 |
219 | .wrapper-714e4 {
220 | height: 40px;
221 | margin-bottom: 20px;
222 | position: relative;
223 | }
224 |
225 | .left-ba9e7 {
226 | bottom: 0;
227 | .font-bold(28px);
228 | left: 0;
229 | position: absolute;
230 | }
231 |
232 | .right-f5656 {
233 | bottom: 0;
234 | position: absolute;
235 | right: 0;
236 | }
237 |
238 | .error-56f38 {
239 | background: #ffffcc;
240 | margin-bottom: 2px;
241 | padding: 10px;
242 | }
243 |
244 | .success-5c065 {
245 | color: green;
246 | }
247 |
248 | .with-warnings-4b105 {
249 | color: @warningOrange;
250 | }
251 |
252 | .errors-2718a {
253 | color: @errorRed;
254 | }
255 |
256 | .status-b8614 {
257 | color: @inactiveGrey;
258 | display: inline-block;
259 | .font-light(18px);
260 | margin-left: 8px;
261 | }
262 |
263 | .edit-c0ba4 {
264 | color: @inactiveGrey;
265 | cursor: pointer;
266 | display: inline-block;
267 | .font-light(18px);
268 | margin-left: 8px;
269 | }
270 |
271 | .edit-c0ba4:hover {
272 | color: @activeBlue;
273 | text-decoration: underline;
274 | }
275 |
276 | .tbl-bdf39 {
277 | border: 1px solid @borderGrey;
278 | width: 100%;
279 |
280 | .fa {
281 | margin-right: 5px;
282 | }
283 | }
284 |
285 | .header-row-50e32 {
286 | background: #f2f6ff;
287 | border-bottom: 1px solid @borderGrey;
288 | }
289 |
290 | .th-92ca4 {
291 | color: #8e8e8e;
292 | .font-bold(11px);
293 | padding: 12px;
294 | text-align: left;
295 | text-transform: uppercase;
296 |
297 | .fa {
298 | color: #444;
299 | font-size: 14px;
300 | width: 14px;
301 | }
302 | }
303 |
304 | .small-check-7b3d7 {
305 | font-size: 12px !important;
306 | }
307 |
308 | .build-row-fdd97 {
309 |
310 | }
311 |
312 | .odd-c27e6 {
313 | background-color: #fafafa;
314 | }
315 |
316 | .even-158a9 {
317 | background-color: #fff;
318 | }
319 |
320 | // NOTE: if you apply opacity to the tr directly you'll get a border bug
321 | .not-active-a8d35 {
322 | td {
323 | opacity: 0.50;
324 | }
325 | }
326 |
327 | .warning-row-097c8 {
328 | background-color: rgba(255, 255, 100, 0.5);
329 | }
330 |
331 | .warning-cell-b9f12 {
332 | color: #000;
333 | .font-normal(14px);
334 | padding: 12px;
335 | }
336 |
337 | .error-row-b3028 {
338 | background-color: @errorRed;
339 | }
340 |
341 | .error-cell-1ccea {
342 | color: #fff;
343 | .font-normal(14px);
344 | padding: 12px;
345 | }
346 |
347 | .cell-9ad24 {
348 | .font-normal(14px);
349 | padding: 12px;
350 | }
351 |
352 | .time-fc085 {
353 | font-family: Verdana;
354 | font-size: 12px;
355 | }
356 |
357 | .menu-b4b27 {
358 | background: #fff;
359 | border: 1px solid #e1e1e1;
360 | .font-normal(14px);
361 | margin-top: 10px;
362 | min-width: 140px;
363 | padding: 10px;
364 | position: absolute;
365 | right: 0;
366 | z-index: 10;
367 | }
368 |
369 | .menu-b4b27:after, .menu-b4b27:before {
370 | bottom: 100%;
371 | border: solid transparent;
372 | content: " ";
373 | height: 0;
374 | width: 0;
375 | position: absolute;
376 | pointer-events: none;
377 | }
378 |
379 | .menu-b4b27:after {
380 | border-color: rgba(255, 255, 255, 0);
381 | border-bottom-color: #fff;
382 | border-width: 8px;
383 | margin-left: -8px;
384 | right: 8px;
385 | }
386 |
387 | .menu-b4b27:before {
388 | border-color: rgba(225, 225, 225, 0);
389 | border-bottom-color: #e1e1e1;
390 | border-width: 9px;
391 | margin-left: -9px;
392 | right: 7px;
393 | }
394 |
395 | .label-ec878 {
396 | cursor: pointer;
397 | display: block;
398 | padding: 5px;
399 |
400 | .fa {
401 | width: 20px;
402 | }
403 | }
404 |
405 | .bld-e7c4d {
406 | cursor: pointer;
407 | padding: 5px;
408 |
409 | .fa {
410 | width: 20px;
411 | }
412 | }
413 |
414 | .label-ec878:hover, .bld-e7c4d:hover {
415 | background: #d9edf7;
416 | }
417 |
418 | .small-cffc5 {
419 | color: #8e8e8e;
420 | display: block;
421 | .font-bold(11px);
422 | padding: 5px;
423 | text-transform: uppercase;
424 | }
425 |
426 | .spacer-685b6 {
427 | height: 10px;
428 | }
429 |
430 | .compile-btn-17a78 {
431 | background: #e6e6e6;
432 | border: 1px solid @buttonBorderColor;
433 | .border-radius(0, 0, 4px, 4px);
434 | color: #000;
435 | cursor: pointer;
436 | display: inline-block;
437 | .font-normal(14px);
438 | padding: 8px 12px;
439 | }
440 |
441 | .menu-btn-550bf {
442 | background: #e6e6e6;
443 | border: 1px solid @buttonBorderColor;
444 | border-left: 0;
445 | color: #000;
446 | cursor: pointer;
447 | display: inline-block;
448 | .font-normal(14px);
449 | padding: 8px 12px;
450 | .border-radius(4px, 4px, 0, 0);
451 | }
452 |
453 | .status-984ee {
454 | .font-light(20px);
455 | display: inline-block;
456 | vertical-align: bottom;
457 | }
458 |
459 | .status-d941c {
460 | .font-light(20px);
461 | display: inline-block;
462 | vertical-align: bottom;
463 |
464 | .fa {
465 | font-size: 17px;
466 | margin-right: 5px;
467 | }
468 | }
469 |
470 | .project-icons-dd1bb {
471 | font-size: 16px;
472 | margin-left: 10px;
473 | vertical-align: middle;
474 | }
475 |
476 | .project-icon-1711d {
477 | cursor: pointer;
478 | margin-left: 8px;
479 | color: #888;
480 | }
481 |
482 | .project-icon-1711d:hover {
483 | color: @activeBlue;
484 | text-decoration: none;
485 | }
486 |
487 | .loading-6792e {
488 | .font-light(42px);
489 | left: 50%;
490 | position: absolute;
491 | top: 25%;
492 | transform: translate(-50%, 0%);
493 |
494 | .fa {
495 | font-size: 36px;
496 | margin-right: 10px;
497 | }
498 | }
499 |
500 | .jre-5d930 {
501 | .font-light(32px);
502 | color: #666;
503 | position: absolute;
504 | top: 25%;
505 | text-align: center;
506 | width: 100%;
507 |
508 | a {
509 | color: @activeBlue;
510 | cursor: pointer;
511 | }
512 | }
513 |
514 | .outer-f80bb {
515 | position: relative;
516 | }
517 |
518 | .inner-aa3fc {
519 | left: 50%;
520 | position: absolute;
521 | top: 80px;
522 | transform: translate(-50%, -50%);
523 |
524 | h4 {
525 | font-size: 24px;
526 | padding: 0;
527 | margin: 0 0 8px 0;
528 | }
529 |
530 | p {
531 | .font-light(18px);
532 | margin: 0;
533 | padding: 0;
534 | }
535 | }
536 |
537 | .link-e7e58 {
538 | color: @activeBlue;
539 | cursor: pointer;
540 | text-decoration: none;
541 | }
542 |
543 | .link-e7e58:hover {
544 | text-decoration: underline;
545 | }
546 |
547 | .modal-overlay-120d3 {
548 | background-color: #3d464d;
549 | height: 100%;
550 | left: 0;
551 | opacity: .6;
552 | position: fixed;
553 | top: 0;
554 | width: 100%;
555 | z-index: (@modalZ - 1);
556 | }
557 |
558 | .Modal() {
559 | box-sizing: border-box;
560 | background: #f8f9fa;
561 | border: 1px solid #e3e3e3;
562 | border-radius: 3px;
563 | left: 50%;
564 | min-width: 500px;
565 | position: absolute;
566 | top: 15%;
567 | transform: translate(-50%, 0%);
568 | z-index: @modalZ;
569 | }
570 |
571 | .modal-body-fe4db {
572 | .Modal();
573 | padding: 30px 0 0 0;
574 |
575 | code {
576 | font-family: 'Droid Sans Mono';
577 | }
578 | }
579 |
580 | .modal-body-8c212 {
581 | .Modal();
582 | padding: 20px;
583 | }
584 |
585 | .big-btn-a5d18 {
586 | background: @primaryGreen;
587 | border: 0;
588 | border-radius: 2px;
589 | color: #fff;
590 | display: inline-block;
591 | .font-normal(16px);
592 | padding: 10px 20px;
593 | }
594 |
595 | .label-b0246 {
596 | display: block;
597 | .font-light(18px);
598 | margin: 0 0 10px 0;
599 | padding: 0;
600 | }
601 |
602 | .modal-chunk-2041a {
603 | margin: 0 0 30px 0;
604 | padding: 0 20px;
605 | }
606 |
607 | .modal-bottom-050c3 {
608 | background: #eee;
609 | padding: 20px;
610 |
611 | button {
612 | display: inline-block;
613 | margin-right: 12px;
614 | padding: 5px 10px;
615 | }
616 | }
617 |
618 | .icon-e70fb {
619 | margin-right: 8px;
620 | }
621 |
622 | .small-info-b72e9 {
623 | .font-light(16px);
624 | margin: 10px 0 0 0;
625 | padding: 0;
626 | }
627 |
628 | .success-hdr-0f1c6 {
629 | .font-bold(20px);
630 | margin: 0;
631 | padding: 0;
632 | }
633 |
634 | .text-input-4800e {
635 | box-sizing: border-box;
636 | font-size: 24px;
637 | padding: 6px;
638 | width: 100%;
639 | }
640 |
641 | .creating-6d31a {
642 | .font-normal(24px);
643 | }
644 |
645 | .error-cdef1 {
646 | background: @errorRed;
647 | color: #fff;
648 | font-size: 16px;
649 | padding: 15px 10px;
650 | margin: 0 10px;
651 |
652 | .fa {
653 | margin-right: 6px;
654 | }
655 | }
656 |
657 | .primary-d0cd0 {
658 | background: @primaryGreen;
659 | border: 0;
660 | border-radius: 2px;
661 | color: #fff;
662 | display: inline-block;
663 | font-size: 14px;
664 | padding: 6px 12px;
665 | }
666 |
667 | .bottom-link-7d8d7 {
668 | color: @activeBlue;
669 | cursor: pointer;
670 | .font-normal(14px);
671 | text-decoration: none;
672 | }
673 |
674 | .bottom-link-7d8d7:hover {
675 | text-decoration: underline;
676 | }
677 |
678 | .modal-title-8e35b {
679 | border-bottom: 1px solid #e1e1e1;
680 | .font-light(28px);
681 | margin: 0 0 20px 0;
682 | padding: 0 0 10px 0;
683 | }
684 |
685 | .settings-label-7daea {
686 | cursor: pointer;
687 | display: block;
688 | margin-bottom: 10px;
689 |
690 | .fa {
691 | width: 20px;
692 | }
693 | }
694 |
695 | .spacer-586a6 {
696 | height: 15px;
697 | }
698 |
699 | .no-builds-2295f {
700 | .font-normal(14px);
701 | background-color: #fafafa;
702 | padding: 12px;
703 | opacity: 0.50;
704 | }
705 |
706 | .load-builds-1b35d {
707 | .font-normal(14px);
708 | background-color: #fafafa;
709 | padding: 12px;
710 | opacity: 0.50;
711 | }
712 |
713 | .info-bar-b38b4 {
714 | background: #ff6;
715 | color: #000;
716 | .font-normal(16px);
717 | padding: 15px 20px;
718 |
719 | .fa {
720 | margin-right: 8px;
721 | }
722 | }
723 |
724 | .show-me-btn-16a12 {
725 | background: @primaryGreen;
726 | border: 0;
727 | border-radius: 2px;
728 | color: #fff;
729 | display: inline-block;
730 | margin-left: 10px;
731 | padding: 6px 12px;
732 | }
733 |
734 | .ignore-link-ff917 {
735 | color: @activeBlue;
736 | color: #20c;
737 | cursor: pointer;
738 | display: inline-block;
739 | margin-left: 10px;
740 | }
741 |
742 | .tooltip-aca75 {
743 | background: @tooltipBackgroundColor;
744 | border-radius: 4px;
745 | color: #fff;
746 | left: 135px;
747 | padding: 10px;
748 | position: absolute;
749 | top: 44px;
750 | z-index: @tooltipZ;
751 |
752 | td {
753 | height: 20px;
754 | }
755 | }
756 |
757 | .tooltip-aca75:after {
758 | bottom: 100%;
759 | left: 17px;
760 | border: solid transparent;
761 | content: " ";
762 | height: 0;
763 | width: 0;
764 | position: absolute;
765 | pointer-events: none;
766 | border-color: rgba(51, 51, 51, 0);
767 | border-bottom-color: #333;
768 | border-width: 5px;
769 | margin-left: -5px;
770 | }
771 |
772 | .label-be32b {
773 | color: #999;
774 | .font-bold(11px);
775 | text-transform: uppercase;
776 | }
777 |
778 | .value-aee48 {
779 | padding-left: 6px;
780 | font-size: 14px;
781 | }
782 |
783 | .mono-cd368 {
784 | font-family: "Droid Sans Mono";
785 | font-size: 14px;
786 | }
787 |
--------------------------------------------------------------------------------
/less/normalize-3.0.1.less:
--------------------------------------------------------------------------------
1 | // normalize.css v3.0.1 | MIT License | git.io/normalize
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS text size adjust after orientation change, without disabling
6 | * user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 and Firefox.
29 | * Correct `block` display not defined for `main` in IE 11.
30 | */
31 |
32 | article,
33 | aside,
34 | details,
35 | figcaption,
36 | figure,
37 | footer,
38 | header,
39 | hgroup,
40 | main,
41 | nav,
42 | section,
43 | summary {
44 | display: block;
45 | }
46 |
47 | /**
48 | * 1. Correct `inline-block` display not defined in IE 8/9.
49 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
50 | */
51 |
52 | audio,
53 | canvas,
54 | progress,
55 | video {
56 | display: inline-block; /* 1 */
57 | vertical-align: baseline; /* 2 */
58 | }
59 |
60 | /**
61 | * Prevent modern browsers from displaying `audio` without controls.
62 | * Remove excess height in iOS 5 devices.
63 | */
64 |
65 | audio:not([controls]) {
66 | display: none;
67 | height: 0;
68 | }
69 |
70 | /**
71 | * Address `[hidden]` styling not present in IE 8/9/10.
72 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
73 | */
74 |
75 | [hidden],
76 | template {
77 | display: none;
78 | }
79 |
80 | /* Links
81 | ========================================================================== */
82 |
83 | /**
84 | * Remove the gray background color from active links in IE 10.
85 | */
86 |
87 | a {
88 | background: transparent;
89 | }
90 |
91 | /**
92 | * Improve readability when focused and also mouse hovered in all browsers.
93 | */
94 |
95 | a:active,
96 | a:hover {
97 | outline: 0;
98 | }
99 |
100 | /* Text-level semantics
101 | ========================================================================== */
102 |
103 | /**
104 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
105 | */
106 |
107 | abbr[title] {
108 | border-bottom: 1px dotted;
109 | }
110 |
111 | /**
112 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
113 | */
114 |
115 | b,
116 | strong {
117 | font-weight: bold;
118 | }
119 |
120 | /**
121 | * Address styling not present in Safari and Chrome.
122 | */
123 |
124 | dfn {
125 | font-style: italic;
126 | }
127 |
128 | /**
129 | * Address variable `h1` font-size and margin within `section` and `article`
130 | * contexts in Firefox 4+, Safari, and Chrome.
131 | */
132 |
133 | h1 {
134 | font-size: 2em;
135 | margin: 0.67em 0;
136 | }
137 |
138 | /**
139 | * Address styling not present in IE 8/9.
140 | */
141 |
142 | mark {
143 | background: #ff0;
144 | color: #000;
145 | }
146 |
147 | /**
148 | * Address inconsistent and variable font size in all browsers.
149 | */
150 |
151 | small {
152 | font-size: 80%;
153 | }
154 |
155 | /**
156 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
157 | */
158 |
159 | sub,
160 | sup {
161 | font-size: 75%;
162 | line-height: 0;
163 | position: relative;
164 | vertical-align: baseline;
165 | }
166 |
167 | sup {
168 | top: -0.5em;
169 | }
170 |
171 | sub {
172 | bottom: -0.25em;
173 | }
174 |
175 | /* Embedded content
176 | ========================================================================== */
177 |
178 | /**
179 | * Remove border when inside `a` element in IE 8/9/10.
180 | */
181 |
182 | img {
183 | border: 0;
184 | }
185 |
186 | /**
187 | * Correct overflow not hidden in IE 9/10/11.
188 | */
189 |
190 | svg:not(:root) {
191 | overflow: hidden;
192 | }
193 |
194 | /* Grouping content
195 | ========================================================================== */
196 |
197 | /**
198 | * Address margin not present in IE 8/9 and Safari.
199 | */
200 |
201 | figure {
202 | margin: 1em 40px;
203 | }
204 |
205 | /**
206 | * Address differences between Firefox and other browsers.
207 | */
208 |
209 | hr {
210 | -moz-box-sizing: content-box;
211 | box-sizing: content-box;
212 | height: 0;
213 | }
214 |
215 | /**
216 | * Contain overflow in all browsers.
217 | */
218 |
219 | pre {
220 | overflow: auto;
221 | }
222 |
223 | /**
224 | * Address odd `em`-unit font size rendering in all browsers.
225 | */
226 |
227 | code,
228 | kbd,
229 | pre,
230 | samp {
231 | font-family: monospace, monospace;
232 | font-size: 1em;
233 | }
234 |
235 | /* Forms
236 | ========================================================================== */
237 |
238 | /**
239 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
240 | * styling of `select`, unless a `border` property is set.
241 | */
242 |
243 | /**
244 | * 1. Correct color not being inherited.
245 | * Known issue: affects color of disabled elements.
246 | * 2. Correct font properties not being inherited.
247 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
248 | */
249 |
250 | button,
251 | input,
252 | optgroup,
253 | select,
254 | textarea {
255 | color: inherit; /* 1 */
256 | font: inherit; /* 2 */
257 | margin: 0; /* 3 */
258 | }
259 |
260 | /**
261 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
262 | */
263 |
264 | button {
265 | overflow: visible;
266 | }
267 |
268 | /**
269 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
270 | * All other form control elements do not inherit `text-transform` values.
271 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
272 | * Correct `select` style inheritance in Firefox.
273 | */
274 |
275 | button,
276 | select {
277 | text-transform: none;
278 | }
279 |
280 | /**
281 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
282 | * and `video` controls.
283 | * 2. Correct inability to style clickable `input` types in iOS.
284 | * 3. Improve usability and consistency of cursor style between image-type
285 | * `input` and others.
286 | */
287 |
288 | button,
289 | html input[type="button"], /* 1 */
290 | input[type="reset"],
291 | input[type="submit"] {
292 | -webkit-appearance: button; /* 2 */
293 | cursor: pointer; /* 3 */
294 | }
295 |
296 | /**
297 | * Re-set default cursor for disabled elements.
298 | */
299 |
300 | button[disabled],
301 | html input[disabled] {
302 | cursor: default;
303 | }
304 |
305 | /**
306 | * Remove inner padding and border in Firefox 4+.
307 | */
308 |
309 | button::-moz-focus-inner,
310 | input::-moz-focus-inner {
311 | border: 0;
312 | padding: 0;
313 | }
314 |
315 | /**
316 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
317 | * the UA stylesheet.
318 | */
319 |
320 | input {
321 | line-height: normal;
322 | }
323 |
324 | /**
325 | * It's recommended that you don't attempt to style these elements.
326 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
327 | *
328 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
329 | * 2. Remove excess padding in IE 8/9/10.
330 | */
331 |
332 | input[type="checkbox"],
333 | input[type="radio"] {
334 | box-sizing: border-box; /* 1 */
335 | padding: 0; /* 2 */
336 | }
337 |
338 | /**
339 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
340 | * `font-size` values of the `input`, it causes the cursor style of the
341 | * decrement button to change from `default` to `text`.
342 | */
343 |
344 | input[type="number"]::-webkit-inner-spin-button,
345 | input[type="number"]::-webkit-outer-spin-button {
346 | height: auto;
347 | }
348 |
349 | /**
350 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
351 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
352 | * (include `-moz` to future-proof).
353 | */
354 |
355 | input[type="search"] {
356 | -webkit-appearance: textfield; /* 1 */
357 | -moz-box-sizing: content-box;
358 | -webkit-box-sizing: content-box; /* 2 */
359 | box-sizing: content-box;
360 | }
361 |
362 | /**
363 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
364 | * Safari (but not Chrome) clips the cancel button when the search input has
365 | * padding (and `textfield` appearance).
366 | */
367 |
368 | input[type="search"]::-webkit-search-cancel-button,
369 | input[type="search"]::-webkit-search-decoration {
370 | -webkit-appearance: none;
371 | }
372 |
373 | /**
374 | * Define consistent border, margin, and padding.
375 | */
376 |
377 | fieldset {
378 | border: 1px solid #c0c0c0;
379 | margin: 0 2px;
380 | padding: 0.35em 0.625em 0.75em;
381 | }
382 |
383 | /**
384 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
385 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
386 | */
387 |
388 | legend {
389 | border: 0; /* 1 */
390 | padding: 0; /* 2 */
391 | }
392 |
393 | /**
394 | * Remove default vertical scrollbar in IE 8/9/10/11.
395 | */
396 |
397 | textarea {
398 | overflow: auto;
399 | }
400 |
401 | /**
402 | * Don't inherit the `font-weight` (applied by a rule above).
403 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
404 | */
405 |
406 | optgroup {
407 | font-weight: bold;
408 | }
409 |
410 | /* Tables
411 | ========================================================================== */
412 |
413 | /**
414 | * Remove most spacing between table cells.
415 | */
416 |
417 | table {
418 | border-collapse: collapse;
419 | border-spacing: 0;
420 | }
421 |
422 | td,
423 | th {
424 | padding: 0;
425 | }
--------------------------------------------------------------------------------
/less/overlay.less:
--------------------------------------------------------------------------------
1 | @zModal: 10;
2 |
3 | .overlay {
4 | position: absolute;
5 | top: 0;
6 | left: 0;
7 | width: 100%;
8 | height: 100%;
9 | z-index: @zModal;
10 | background-color: rgba(0,0,0,0.5); /*dim the background*/
11 | }
12 |
13 | .modal {
14 | width: 100%;
15 | height: 100%;
16 | top: 0;
17 | left: 0;
18 | position: absolute;
19 | z-index: @zModal + 1;
20 | }
21 |
--------------------------------------------------------------------------------
/less/util.less:
--------------------------------------------------------------------------------
1 | .BoxShadowHelper(@level: 1){
2 | & when (@level = 1) {
3 | box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
4 | }
5 | & when (@level = 2) {
6 | box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
7 | }
8 | & when (@level = 3) {
9 | box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
10 | }
11 | & when (@level = 4) {
12 | box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
13 | }
14 | & when (@level = 5) {
15 | box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22);
16 | }
17 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "asterion",
3 | "license": "Private",
4 | "private": true,
5 | "dependencies": {
6 | "fs-plus": "2.8.1",
7 | "madge": "0.5.2"
8 | },
9 | "devDependencies": {
10 | "asar": "0.8.0",
11 | "closurecompiler-externs": "1.0.4",
12 | "grunt": "0.4.5",
13 | "grunt-download-electron": "2.1.1",
14 | "moment": "2.10.6",
15 | "shelljs": "0.5.3",
16 | "winresourcer": "0.9.0"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject asterion "0.1.0-alpha1"
2 | :description "Make and explore dependency graphs for Clojure(Script) projects."
3 | :url "http://example.com/FIXME"
4 | :license {:name "Eclipse Public License"
5 | :url "http://www.eclipse.org/legal/epl-v10.html"}
6 |
7 | :source-paths ["src/clj" "src/cljs" "src/cljc"]
8 |
9 | :dependencies [[org.clojure/clojure "1.7.0"]
10 | [org.clojure/clojurescript "1.7.145"]
11 | [org.clojure/tools.namespace "0.3.19-SNAPSHOT"]
12 | [org.clojure/tools.reader "0.10.0-alpha1"]
13 | [org.clojure/data.json "0.2.6"]
14 | [commons-io/commons-io "2.4"]
15 | [environ "1.0.1"]
16 | [com.stuartsierra/component "0.2.3"]
17 | [leiningen-core "2.5.3"]
18 | [clj-jgit "0.8.8"]
19 | [ring "1.3.2"]
20 | [compojure "1.4.0"]
21 | [cljsjs/d3 "3.5.5-3"]
22 | [org.omcljs/om "0.9.0"]
23 | [cljs-ajax "0.5.0"]]
24 |
25 | :min-lein-version "2.5.1"
26 |
27 | :ring {:handler asterion.server/app-handler}
28 |
29 | :cljsbuild
30 | {:builds {:dev {:source-paths ["src/cljs" "env/dev/cljs"]
31 | :figwheel {:on-jsload "asterion.core/init!"}
32 | :compiler {:output-to "resources/public/js/p/app.js"
33 | :output-dir "resources/public/js/p/out"
34 | :asset-path "js/p/out"
35 | :source-map true
36 | :main "asterion.dev"
37 | :verbose true
38 | :optimizations :none
39 | :pretty-print true
40 | :cache-analysis true}}
41 | :production
42 | {:source-paths ["src/cljs" "env/prod/cljs"]
43 | :compiler {:output-to "resources/public/js/p/app.js"
44 | :optimizations :simple
45 | :main "asterion.prod"
46 | :closure-defines {:goog.DEBUG false}}}}}
47 |
48 | :clean-targets ^{:protect false} [:target-path "out" "resources/public/js/p"]
49 |
50 | :figwheel {:css-dirs ["resources/public/css"]}
51 |
52 | :profiles {:dev {:plugins [[lein-ancient "0.6.7"]
53 | [lein-cljsbuild "1.1.0"]
54 | [lein-environ "1.0.1"]
55 | [lein-kibit "0.1.2"]
56 | [lein-ring "0.9.7"]
57 | [lein-cljfmt "0.3.0"]
58 | [lein-beanstalk "0.2.7"]
59 | [lein-figwheel "0.4.1"]]}})
60 |
--------------------------------------------------------------------------------
/resources/public/app.js:
--------------------------------------------------------------------------------
1 | var app = require('app'),
2 | BrowserWindow = require('browser-window'),
3 | fs = require('fs-plus'),
4 | ipc = require('ipc'),
5 | Menu = require('menu'),
6 | path = require('path'),
7 | dialog = require('dialog'),
8 | shell = require('shell'),
9 | child_process = require('child_process'),
10 | packageJson = require(__dirname + '/package.json');
11 |
12 | // Report crashes to atom-shell.
13 | require('crash-reporter').start();
14 |
15 | const devConfigFile = __dirname + '/config.json';
16 | var devConfig = {};
17 | if (fs.existsSync(devConfigFile)) {
18 | devConfig = require(devConfigFile);
19 | }
20 |
21 |
22 | const isDev = (packageJson.version.indexOf("DEV") !== -1);
23 | const onMac = (process.platform === 'darwin');
24 | const acceleratorKey = onMac ? "Command" : "Control";
25 | const isInternal = (devConfig.hasOwnProperty('internal') && devConfig['internal'] === true);
26 |
27 |
28 |
29 | // Keep a global reference of the window object, if you don't, the window will
30 | // be closed automatically when the javascript object is GCed.
31 | var mainWindow = null;
32 |
33 | // make sure app.getDataPath() exists
34 | // https://github.com/oakmac/cuttle/issues/92
35 | if (!fs.isDirectorySync(app.getDataPath())) {
36 | fs.mkdirSync(app.getDataPath());
37 | }
38 |
39 |
40 | //------------------------------------------------------------------------------
41 | // Main
42 | //------------------------------------------------------------------------------
43 |
44 | const versionString = "Version " + packageJson.version + "\nDate " + packageJson["build-date"] + "\nCommit " + packageJson["build-commit"];
45 |
46 |
47 | function showVersion() {
48 | dialog.showMessageBox({type: "info", title: "Version", buttons: ["OK"], message: versionString});
49 | }
50 |
51 | var fileMenu = {
52 | label: 'File',
53 | submenu: [
54 | {
55 | label: 'Quit',
56 | accelerator: acceleratorKey + '+Q',
57 | click: function ()
58 | {
59 | app.quit();
60 | }
61 | }]
62 | };
63 |
64 | var helpMenu = {
65 | label: 'Help',
66 | submenu: [
67 | {
68 | label: 'Version',
69 | click: showVersion
70 | }]
71 | };
72 |
73 | var debugMenu = {
74 | label: 'Debug',
75 | submenu: [
76 | {
77 | label: 'Toggle DevTools',
78 | click: function ()
79 | {
80 | mainWindow.toggleDevTools();
81 | }
82 | }
83 | ]
84 | };
85 |
86 | var menuTemplate = [fileMenu, debugMenu, helpMenu];
87 |
88 |
89 | // NOTE: not all of the browserWindow options listed on the docs page work
90 | // on all operating systems
91 | const browserWindowOptions = {
92 | height: 850,
93 | title: 'asterion',
94 | width: 1400,
95 | icon: __dirname + '/img/logo_96x96.png'
96 | };
97 |
98 |
99 | //------------------------------------------------------------------------------
100 | // Register IPC Calls from the Renderers
101 | //------------------------------------------------------------------------------
102 |
103 | // Open files
104 | const projectDialogOpts = {
105 | title: 'Please select an existing project.clj file',
106 | properties: ['openFile'],
107 | filters: [
108 | {
109 | name: 'Leiningen project.clj',
110 | extensions: ['boot', 'xml', 'clj']
111 | }
112 | ]
113 | };
114 |
115 | function addProjectDialog(event, platform) {
116 | dialog.showOpenDialog(projectDialogOpts, function(filenames) {
117 | if (filenames) {
118 | var filename = filenames[0];
119 | mainWindow.webContents.send('add-project-success', filename, platform);
120 | }
121 | });
122 | }
123 |
124 | ipc.on('request-project-dialog', addProjectDialog);
125 |
126 | // grep files
127 |
128 | function grepWithFork(event, filenames, stringPattern) {
129 | var cmd = "egrep -Rl -m 100 '"
130 | + stringPattern
131 | + "' "
132 | + filenames.join(" ");
133 | child_process.exec(cmd, {maxBuffer: 200000000}, function(err, stdout) {
134 | if (err && (err.code == 2)) {
135 | console.log("There was an error:\n");
136 | process.stdout.write(stdout);
137 | mainWindow.webContents.send('search-error',err);
138 | } else if (err && (err.code == 1)) {
139 | mainWindow.webContents.send('search-not-found');
140 | } else {
141 | mainWindow.webContents.send('search-success',stdout.split(/\n/));
142 | }
143 | });
144 | }
145 |
146 | ipc.on('request-search', grepWithFork);
147 |
148 | //------------------------------------------------------------------------------
149 | // Ready
150 | //------------------------------------------------------------------------------
151 |
152 |
153 | // This method will be called when atom-shell has done everything
154 | // initialization and ready for creating browser windows.
155 | app.on('ready', function() {
156 | // Create the browser window.
157 | mainWindow = new BrowserWindow(browserWindowOptions);
158 |
159 | // and load the index.html of the app.
160 | mainWindow.loadUrl('file://' + __dirname + '/index.html');
161 |
162 | var menu = Menu.buildFromTemplate(menuTemplate);
163 |
164 | Menu.setApplicationMenu(menu);
165 |
166 | // Emitted when the window is closed.
167 | mainWindow.on('closed', function() {
168 | // Dereference the window object, usually you would store windows
169 | // in an array if your app supports multi windows, this is the time
170 | // when you should delete the corresponding element.
171 | mainWindow = null;
172 | app.quit();
173 | });
174 |
175 | if (devConfig.hasOwnProperty('dev-tools') && devConfig['dev-tools'] === true) {
176 | mainWindow.openDevTools();
177 | }
178 |
179 | });
180 |
--------------------------------------------------------------------------------
/resources/public/css/main.css:
--------------------------------------------------------------------------------
1 | circle {
2 | stroke-width: 1.5px;
3 | }
4 |
5 | line {
6 | stroke: #999;
7 | }
8 |
9 | .edgePath {
10 | stroke: black;
11 | }
12 |
--------------------------------------------------------------------------------
/resources/public/example.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "dev-tools": true
3 | }
4 |
--------------------------------------------------------------------------------
/resources/public/fonts/DroidSansMono.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bensu/asterion/847b1b152bdf368921268fe7aaf3da047f944a97/resources/public/fonts/DroidSansMono.woff2
--------------------------------------------------------------------------------
/resources/public/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bensu/asterion/847b1b152bdf368921268fe7aaf3da047f944a97/resources/public/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/resources/public/fonts/OpenSans-Light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bensu/asterion/847b1b152bdf368921268fe7aaf3da047f944a97/resources/public/fonts/OpenSans-Light.woff
--------------------------------------------------------------------------------
/resources/public/fonts/OpenSans-Semibold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bensu/asterion/847b1b152bdf368921268fe7aaf3da047f944a97/resources/public/fonts/OpenSans-Semibold.woff
--------------------------------------------------------------------------------
/resources/public/fonts/OpenSans.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bensu/asterion/847b1b152bdf368921268fe7aaf3da047f944a97/resources/public/fonts/OpenSans.woff
--------------------------------------------------------------------------------
/resources/public/fonts/RobotoSlab-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bensu/asterion/847b1b152bdf368921268fe7aaf3da047f944a97/resources/public/fonts/RobotoSlab-Regular.woff2
--------------------------------------------------------------------------------
/resources/public/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bensu/asterion/847b1b152bdf368921268fe7aaf3da047f944a97/resources/public/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/resources/public/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bensu/asterion/847b1b152bdf368921268fe7aaf3da047f944a97/resources/public/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/resources/public/img/clojure.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/resources/public/img/logo.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bensu/asterion/847b1b152bdf368921268fe7aaf3da047f944a97/resources/public/img/logo.icns
--------------------------------------------------------------------------------
/resources/public/img/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bensu/asterion/847b1b152bdf368921268fe7aaf3da047f944a97/resources/public/img/logo.ico
--------------------------------------------------------------------------------
/resources/public/img/logo_96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bensu/asterion/847b1b152bdf368921268fe7aaf3da047f944a97/resources/public/img/logo_96x96.png
--------------------------------------------------------------------------------
/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Asterion
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/resources/public/js/vendor/ga.js:
--------------------------------------------------------------------------------
1 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
2 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
3 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
4 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
5 |
6 | ga('create', 'UA-69327501-1', 'auto');
7 | ga('send', 'pageview');
8 |
9 |
--------------------------------------------------------------------------------
/resources/public/js/vendor/palette.js:
--------------------------------------------------------------------------------
1 | /** @license
2 | *
3 | * Colour Palette Generator script.
4 | * Copyright (c) 2014 Google Inc.
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may
7 | * not use this file except in compliance with the License. You may
8 | * obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15 | * implied. See the License for the specific language governing
16 | * permissions and limitations under the License.
17 | *
18 | * Furthermore, ColorBrewer colour schemes are covered by the following:
19 | *
20 | * Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and
21 | * The Pennsylvania State University.
22 | *
23 | * Licensed under the Apache License, Version 2.0 (the "License"); you may
24 | * not use this file except in compliance with the License. You may obtain
25 | * a copy of the License at
26 | *
27 | * http://www.apache.org/licenses/LICENSE-2.0
28 | *
29 | * Unless required by applicable law or agreed to in writing, software
30 | * distributed under the License is distributed on an "AS IS" BASIS,
31 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
32 | * implied. See the License for the specific language governing
33 | * permissions and limitations under the License.
34 | *
35 | * Redistribution and use in source and binary forms, with or without
36 | * modification, are permitted provided that the following conditions are
37 | * met:
38 | *
39 | * 1. Redistributions as source code must retain the above copyright
40 | * notice, this list of conditions and the following disclaimer.
41 | *
42 | * 2. The end-user documentation included with the redistribution, if any,
43 | * must include the following acknowledgment: "This product includes color
44 | * specifications and designs developed by Cynthia Brewer
45 | * (http://colorbrewer.org/)." Alternately, this acknowledgment may appear
46 | * in the software itself, if and wherever such third-party
47 | * acknowledgments normally appear.
48 | *
49 | * 4. The name "ColorBrewer" must not be used to endorse or promote
50 | * products derived from this software without prior written
51 | * permission. For written permission, please contact Cynthia Brewer at
52 | * cbrewer@psu.edu.
53 | *
54 | * 5. Products derived from this software may not be called "ColorBrewer",
55 | * nor may "ColorBrewer" appear in their name, without prior written
56 | * permission of Cynthia Brewer.
57 | *
58 | * Furthermore, Solarized colour schemes are covered by the following:
59 | *
60 | * Copyright (c) 2011 Ethan Schoonover
61 | *
62 | * Permission is hereby granted, free of charge, to any person obtaining
63 | * a copy of this software and associated documentation files (the
64 | * "Software"), to deal in the Software without restriction, including
65 | * without limitation the rights to use, copy, modify, merge, publish,
66 | * distribute, sublicense, and/or sell copies of the Software, and to
67 | * permit persons to whom the Software is furnished to do so, subject to
68 | * the following conditions:
69 | *
70 | * The above copyright notice and this permission notice shall be included
71 | * in all copies or substantial portions of the Software.
72 | *
73 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
74 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
75 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
76 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
77 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
78 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
79 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
80 | */
81 |
82 | 'use strict';
83 |
84 | var palette = (function() {
85 |
86 | var proto = Array.prototype;
87 | var slice = function(arr, opt_begin, opt_end) {
88 | return proto.slice.apply(arr, proto.slice.call(arguments, 1));
89 | };
90 |
91 | var extend = function(arr, arr2) {
92 | return proto.push.apply(arr, arr2);
93 | };
94 |
95 | var function_type = typeof function() {};
96 |
97 | var INF = 1000000000; // As far as we're concerned, that's infinity. ;)
98 |
99 |
100 | /**
101 | * Generate a colour palette from given scheme.
102 | *
103 | * If scheme argument is not a function it is passed to palettes.listSchemes
104 | * function (along with the number argument). This may result in an array
105 | * of more than one available scheme. If that is the case, scheme at
106 | * opt_index position is taken.
107 | *
108 | * This allows using different palettes for different data without having to
109 | * name the schemes specifically, for example:
110 | *
111 | * palette_for_foo = palette('sequential', 10, 0);
112 | * palette_for_bar = palette('sequential', 10, 1);
113 | * palette_for_baz = palette('sequential', 10, 2);
114 | *
115 | * @param {!palette.SchemeType|string|palette.Palette} scheme Scheme to
116 | * generate palette for. Either a function constructed with
117 | * palette.Scheme object, or anything that palette.listSchemes accepts
118 | * as name argument.
119 | * @param {number} number Number of colours to return. If negative,
120 | * absolute value is taken and colours will be returned in reverse
121 | * order.
122 | * @param {number=} opt_index If scheme is a name of a group or an array and
123 | * results in more than one scheme, index of the scheme to use. The
124 | * index wraps around.
125 | * @param {...*} varargs Additional arguments to pass to palette or colour
126 | * generator (if the chosen scheme uses those).
127 | * @return {Array.} Array of abs(number) 'RRGGBB' strings or null if
128 | * no matching scheme was found.
129 | */
130 | var palette = function(scheme, number, opt_index, varargs) {
131 | number |= 0;
132 | if (number == 0) {
133 | return [];
134 | }
135 |
136 | if (typeof scheme !== function_type) {
137 | var arr = palette.listSchemes(
138 | /** @type {string|palette.Palette} */ (scheme), number);
139 | if (!arr.length) {
140 | return null;
141 | }
142 | scheme = arr[(opt_index || 0) % arr.length];
143 | }
144 |
145 | var args = slice(arguments, 2);
146 | args[0] = number;
147 | return scheme.apply(scheme, args);
148 | };
149 |
150 |
151 | /**
152 | * Returns a callable colour scheme object.
153 | *
154 | * Just after being created, the scheme has no colour palettes and no way of
155 | * generating any, thus generate method will return null. To turn scheme
156 | * into a useful object, addPalette, addPalettes or setColorFunction methods
157 | * need to be used.
158 | *
159 | * To generate a colour palette with given number colours using function
160 | * returned by this method, just call it with desired number of colours.
161 | *
162 | * Since this function *returns* a callable object, it must *not* be used
163 | * with the new operator.
164 | *
165 | * @param {string} name Name of the scheme.
166 | * @param {string|!Array.=} opt_groups A group name or list of
167 | * groups the scheme should be categorised under. Three typical groups
168 | * to use are 'qualitative', 'sequential' and 'diverging', but any
169 | * groups may be created.
170 | * @return {!palette.SchemeType} A colour palette generator function, which
171 | * in addition has methods and properties like a regular object. Think
172 | * of it as a callable object.
173 | */
174 | palette.Scheme = function(name, opt_groups) {
175 | /**
176 | * A map from a number to a colour palettes with given number of colours.
177 | * @type {!Object.}
178 | */
179 | var palettes = {};
180 |
181 | /**
182 | * The biggest palette in palettes map.
183 | * @type {number}
184 | */
185 | var palettes_max = 0;
186 |
187 | /**
188 | * The smallest palette in palettes map.
189 | * @type {number}
190 | */
191 | var palettes_min = INF;
192 |
193 | var makeGenerator = function() {
194 | if (arguments.length <= 1) {
195 | return self.color_func.bind(self);
196 | } else {
197 | var args = slice(arguments);
198 | return function(x) {
199 | args[0] = x;
200 | return self.color_func.apply(self, args);
201 | };
202 | }
203 | };
204 |
205 | /**
206 | * Generate a colour palette from the scheme.
207 | *
208 | * If there was a palette added with addPalette (or addPalettes) with
209 | * enough colours, that palette will be used. Otherwise, if colour
210 | * function has been set using setColorFunction method, that function will
211 | * be used to generate the palette. Otherwise null is returned.
212 | *
213 | * @param {number} number Number of colours to return. If negative,
214 | * absolute value is taken and colours will be returned in reverse
215 | * order.
216 | * @param {...*} varargs Additional arguments to pass to palette or colour
217 | * generator (if the chosen scheme uses those).
218 | */
219 | var self = function(number, varargs) {
220 | number |= 0;
221 | if (!number) {
222 | return [];
223 | }
224 |
225 | var _number = number;
226 | number = Math.abs(number);
227 |
228 | if (number <= palettes_max) {
229 | for (var i = Math.max(number, palettes_min); !(i in palettes); ++i) {
230 | /* nop */
231 | }
232 | var colors = palettes[i];
233 | if (i > number) {
234 | var take_head =
235 | 'shrinking_takes_head' in colors ?
236 | colors.shrinking_takes_head : self.shrinking_takes_head;
237 | if (take_head) {
238 | colors = colors.slice(0, number);
239 | i = number;
240 | } else {
241 | return palette.generate(
242 | function(x) { return colors[Math.round(x)]; },
243 | _number, 0, colors.length - 1);
244 | }
245 | }
246 | colors = colors.slice();
247 | if (_number < 0) {
248 | colors.reverse();
249 | }
250 | return colors;
251 |
252 | } else if (self.color_func) {
253 | return palette.generate(makeGenerator.apply(self, arguments),
254 | _number, 0, 1, self.color_func_cyclic);
255 |
256 | } else {
257 | return null;
258 | }
259 | };
260 |
261 | /**
262 | * The name of the palette.
263 | * @type {string}
264 | */
265 | self.scheme_name = name;
266 |
267 | /**
268 | * A list of groups the palette belongs to.
269 | * @type {!Array.}
270 | */
271 | self.groups = opt_groups ?
272 | typeof opt_groups === 'string' ? [opt_groups] : opt_groups : [];
273 |
274 | /**
275 | * The biggest palette this scheme can generate.
276 | * @type {number}
277 | */
278 | self.max = 0;
279 |
280 | /**
281 | * The biggest palette this scheme can generate that is colour-blind
282 | * friendly.
283 | * @type {number}
284 | */
285 | self.cbf_max = INF;
286 |
287 |
288 | /**
289 | * Adds a colour palette to the colour scheme.
290 | *
291 | * @param {palette.Palette} palette An array of 'RRGGBB' strings
292 | * representing the palette to add.
293 | * @param {boolean=} opt_is_cbf Whether the palette is colourblind friendly.
294 | */
295 | self.addPalette = function(palette, opt_is_cbf) {
296 | var len = palette.length;
297 | if (len) {
298 | palettes[len] = palette;
299 | palettes_min = Math.min(palettes_min, len);
300 | palettes_max = Math.max(palettes_max, len);
301 | self.max = Math.max(self.max, len);
302 | if (!opt_is_cbf && len != 1) {
303 | self.cbf_max = Math.min(self.cbf_max, len - 1);
304 | }
305 | }
306 | };
307 |
308 | /**
309 | * Adds number of colour palettes to the colour scheme.
310 | *
311 | * @param {palette.PalettesList} palettes A map or an array of colour
312 | * palettes to add. If map, i.e. object, is used, properties should
313 | * use integer property names.
314 | * @param {number=} opt_max Size of the biggest palette in palettes set.
315 | * If not set, palettes must have a length property which will be used.
316 | * @param {number=} opt_cbf_max Size of the biggest palette which is still
317 | * colourblind friendly. 1 by default.
318 | */
319 | self.addPalettes = function(palettes, opt_max, opt_cbf_max) {
320 | opt_max = opt_max || palettes.length;
321 | for (var i = 0; i < opt_max; ++i) {
322 | if (i in palettes) {
323 | self.addPalette(palettes[i], true);
324 | }
325 | }
326 | self.cbf_max = Math.min(self.cbf_max, opt_cbf_max || 1);
327 | };
328 |
329 | /**
330 | * Enable shrinking palettes taking head of the list of colours.
331 | *
332 | * When user requests n-colour palette but the smallest palette added with
333 | * addPalette (or addPalettes) is m-colour one (where n < m), n colours
334 | * across the palette will be returned. For example:
335 | * var ex = palette.Scheme('ex');
336 | * ex.addPalette(['000000', 'bcbcbc', 'ffffff']);
337 | * var pal = ex(2);
338 | * // pal == ['000000', 'ffffff']
339 | *
340 | * This works for palettes where the distance between colours is
341 | * correlated to distance in the palette array, which is true in gradients
342 | * such as the one above.
343 | *
344 | * To turn this feature off shrinkByTakingHead can be set to true either
345 | * for all palettes in the scheme (if opt_idx is not given) or for palette
346 | * with given number of colours only. In general, setting the option for
347 | * given palette overwrites whatever has been set for the scheme. The
348 | * default, as described above, is false.
349 | *
350 | * Alternatively, the feature can be enabled by setting
351 | * shrinking_takes_head property for the palette Array or the scheme
352 | * object.
353 | *
354 | * For example, all of the below give equivalent results:
355 | * var pal = ['ff0000', '00ff00', '0000ff'];
356 | *
357 | * var ex = palette.Scheme('ex');
358 | * ex.addPalette(pal); // ex(2) == ['ff0000', '0000ff']
359 | * ex.shrinkByTakingHead(true); // ex(2) == ['ff0000', '00ff00']
360 | *
361 | * ex = palette.Scheme('ex');
362 | * ex.addPalette(pal); // ex(2) == ['ff0000', '0000ff']
363 | * ex.shrinkByTakingHead(true, 3); // ex(2) == ['ff0000', '00ff00']
364 | *
365 | * ex = palette.Scheme('ex');
366 | * ex.addPalette(pal);
367 | * ex.addPalette(pal); // ex(2) == ['ff0000', '0000ff']
368 | * pal.shrinking_takes_head = true; // ex(2) == ['ff0000', '00ff00']
369 | *
370 | * @param {boolean} enabled Whether to enable or disable the “shrinking
371 | * takes head” feature. It is disabled by default.
372 | * @param {number=} opt_idx If given, the “shrinking takes head” option
373 | * for palette with given number of colours is set. If such palette
374 | * does not exist, nothing happens.
375 | */
376 | self.shrinkByTakingHead = function(enabled, opt_idx) {
377 | if (opt_idx !== void(0)) {
378 | if (opt_idx in palettes) {
379 | palettes[opt_idx].shrinking_takes_head = !!enabled;
380 | }
381 | } else {
382 | self.shrinking_takes_head = !!enabled;
383 | }
384 | };
385 |
386 | /**
387 | * Sets a colour generation function of the colour scheme.
388 | *
389 | * The function must accept a singe number argument whose value can be
390 | * from 0.0 to 1.0, and return a colour as an 'RRGGBB' string. This
391 | * function will be used when generating palettes, i.e. if 11-colour
392 | * palette is requested, this function will be called with arguments 0.0,
393 | * 0.1, …, 1.0.
394 | *
395 | * If the palette generated by the function is colourblind friendly,
396 | * opt_is_cbf should be set to true.
397 | *
398 | * In some cases, it is not desirable to reach 1.0 when generating
399 | * a palette. This happens for hue-rainbows where the 0–1 range corresponds
400 | * to a 0°–360° range in hues, and since hue at 0° is the same as at 360°,
401 | * it's desired to stop short the end of the range when generating
402 | * a palette. To accomplish this, opt_cyclic should be set to true.
403 | *
404 | * @param {palette.ColorFunction} func A colour generator function.
405 | * @param {boolean=} opt_is_cbf Whether palette generate with the function
406 | * is colour-blind friendly.
407 | * @param {boolean=} opt_cyclic Whether colour at 0.0 is the same as the
408 | * one at 1.0.
409 | */
410 | self.setColorFunction = function(func, opt_is_cbf, opt_cyclic) {
411 | self.color_func = func;
412 | self.color_func_cyclic = !!opt_cyclic;
413 | self.max = INF;
414 | if (!opt_is_cbf && self.cbf_max === INF) {
415 | self.cbf_max = 1;
416 | }
417 | };
418 |
419 | self.color = function(x, varargs) {
420 | if (self.color_func) {
421 | return self.color_func.apply(this, arguments);
422 | } else {
423 | return null;
424 | }
425 | };
426 |
427 | return self;
428 | };
429 |
430 |
431 | /**
432 | * Creates a new palette.Scheme and initialises it by calling addPalettes
433 | * method with the rest of the arguments.
434 | *
435 | * @param {string} name Name of the scheme.
436 | * @param {string|!Array.} groups A group name or list of group
437 | * names the scheme belongs to.
438 | * @param {!Object.|!Array.}
439 | * palettes A map or an array of colour palettes to add. If map, i.e.
440 | * object, is used, properties should use integer property names.
441 | * @param {number=} opt_max Size of the biggest palette in palettes set.
442 | * If not set, palettes must have a length property which will be used.
443 | * @param {number=} opt_cbf_max Size of the biggest palette which is still
444 | * colourblind friendly. 1 by default.
445 | * @return {!palette.SchemeType} A colour palette generator function, which
446 | * in addition has methods and properties like a regular object. Think
447 | * of it as a callable object.
448 | */
449 | palette.Scheme.fromPalettes = function(name, groups,
450 | palettes, opt_max, opt_cbf_max) {
451 | var scheme = palette.Scheme(name, groups);
452 | scheme.addPalettes.apply(scheme, slice(arguments, 2));
453 | return scheme;
454 | };
455 |
456 |
457 | /**
458 | * Creates a new palette.Scheme and initialises it by calling
459 | * setColorFunction method with the rest of the arguments.
460 | *
461 | * @param {string} name Name of the scheme.
462 | * @param {string|!Array.} groups A group name or list of group
463 | * names the scheme belongs to.
464 | * @param {palette.ColorFunction} func A colour generator function.
465 | * @param {boolean=} opt_is_cbf Whether palette generate with the function
466 | * is colour-blind friendly.
467 | * @param {boolean=} opt_cyclic Whether colour at 0.0 is the same as the
468 | * one at 1.0.
469 | * @return {!palette.SchemeType} A colour palette generator function, which
470 | * in addition has methods and properties like a regular object. Think
471 | * of it as a callable object.
472 | */
473 | palette.Scheme.withColorFunction = function(name, groups,
474 | func, opt_is_cbf, opt_cyclic) {
475 | var scheme = palette.Scheme(name, groups);
476 | scheme.setColorFunction.apply(scheme, slice(arguments, 2));
477 | return scheme;
478 | };
479 |
480 |
481 | /**
482 | * A map of registered schemes. Maps a scheme or group name to a list of
483 | * scheme objects. Property name is either 'n-' for single scheme
484 | * names or 'g-' for scheme group names.
485 | *
486 | * @type {!Object.>}
487 | */
488 | var registered_schemes = {};
489 |
490 |
491 | /**
492 | * Registers a new colour scheme.
493 | *
494 | * @param {!palette.SchemeType} scheme The scheme to add.
495 | */
496 | palette.register = function(scheme) {
497 | registered_schemes['n-' + scheme.scheme_name] = [scheme];
498 | scheme.groups.forEach(function(g) {
499 | (registered_schemes['g-' + g] =
500 | registered_schemes['g-' + g] || []).push(scheme);
501 | });
502 | (registered_schemes['g-all'] =
503 | registered_schemes['g-all'] || []).push(scheme);
504 | };
505 |
506 |
507 | /**
508 | * List all schemes that match given name and number of colours.
509 | *
510 | * name argument can be either a string or an array of strings. In the
511 | * former case, the function acts as if the argument was an array with name
512 | * as a single argument (i.e. “palette.listSchemes('foo')” is exactly the same
513 | * as “palette.listSchemes(['foo'])”).
514 | *
515 | * Each name can be either name of a palette (e.g. 'tol-sq' for Paul Tol's
516 | * sequential palette), or a name of a group (e.g. 'sequential' for all
517 | * sequential palettes). Name can therefore map to a single scheme or
518 | * several schemes.
519 | *
520 | * Furthermore, name can be suffixed with '-cbf' to indicate that only
521 | * schemes that are colourblind friendly should be returned. For example,
522 | * 'rainbow' returns a HSV rainbow scheme, but because it is not colourblind
523 | * friendly, 'rainbow-cbf' returns no schemes.
524 | *
525 | * Some schemes may produce colourblind friendly palettes for some number of
526 | * colours. For example ColorBrewer's Dark2 scheme is colourblind friendly
527 | * if no more than 3 colours are generated. If opt_number is not specified,
528 | * 'qualitative-cbf' will include 'cb-Dark2' but if opt_number is given as,
529 | * say, 5 it won't.
530 | *
531 | * Name can also be 'all' which will return all registered schemes.
532 | * Naturally, 'all-cbf' will return all colourblind friendly schemes.
533 | *
534 | * Schemes are added to the library using palette.register. Schemes are
535 | * created using palette.Scheme function. By default, the following schemes
536 | * are available:
537 | *
538 | * Name Description
539 | * -------------- -----------------------------------------------------
540 | * tol Paul Tol's qualitative scheme, cbf, max 12 colours.
541 | * tol-dv Paul Tol's diverging scheme, cbf.
542 | * tol-seq Paul Tol's sequential scheme, cbf.
543 | * tol-rainbow Paul Tol's qualitative scheme, cbf.
544 | *
545 | * rainbow A rainbow palette.
546 | *
547 | * cb-YlGn ColorBrewer sequential schemes.
548 | * cb-YlGnBu
549 | * cb-GnBu
550 | * cb-BuGn
551 | * cb-PuBuGn
552 | * cb-PuBu
553 | * cb-BuPu
554 | * cb-RdPu
555 | * cb-PuRd
556 | * cb-OrRd
557 | * cb-YlOrRd
558 | * cb-YlOrBr
559 | * cb-Purples
560 | * cb-Blues
561 | * cb-Greens
562 | * cb-Oranges
563 | * cb-Reds
564 | * cb-Greys
565 | *
566 | * cb-PuOr ColorBrewer diverging schemes.
567 | * cb-BrBG
568 | * cb-PRGn
569 | * cb-PiYG
570 | * cb-RdBu
571 | * cb-RdGy
572 | * cb-RdYlBu
573 | * cb-Spectral
574 | * cb-RdYlGn
575 | *
576 | * cb-Accent ColorBrewer qualitative schemes.
577 | * cb-Dark2
578 | * cb-Paired
579 | * cb-Pastel1
580 | * cb-Pastel2
581 | * cb-Set1
582 | * cb-Set2
583 | * cb-Set3
584 | *
585 | * sol-base Solarized base colours.
586 | * sol-accent Solarized accent colours.
587 | *
588 | * The following groups are also available by default:
589 | *
590 | * Name Description
591 | * -------------- -----------------------------------------------------
592 | * all All registered schemes.
593 | * sequential All sequential schemes.
594 | * diverging All diverging schemes.
595 | * qualitative All qualitative schemes.
596 | * cb-sequential All ColorBrewer sequential schemes.
597 | * cb-diverging All ColorBrewer diverging schemes.
598 | * cb-qualitative All ColorBrewer qualitative schemes.
599 | *
600 | * You can read more about Paul Tol's palettes at http://www.sron.nl/~pault/.
601 | * You can read more about ColorBrewer at http://colorbrewer2.org.
602 | *
603 | * @param {string|!Array.} name A name of a colour scheme, of
604 | * a group of colour schemes, or an array of any of those.
605 | * @param {number=} opt_number When requesting only colourblind friendly
606 | * schemes, number of colours the scheme must provide generating such
607 | * that the palette is still colourblind friendly. 2 by default.
608 | * @return {!Array.} An array of colour scheme objects
609 | * matching the criteria. Sorted by scheme name.
610 | */
611 | palette.listSchemes = function(name, opt_number) {
612 | if (!opt_number) {
613 | opt_number = 2;
614 | } else if (opt_number < 0) {
615 | opt_number = -opt_number;
616 | }
617 |
618 | var ret = [];
619 | (typeof name === 'string' ? [name] : name).forEach(function(n) {
620 | var cbf = n.substring(n.length - 4) === '-cbf';
621 | if (cbf) {
622 | n = n.substring(0, n.length - 4);
623 | }
624 | var schemes =
625 | registered_schemes['g-' + n] ||
626 | registered_schemes['n-' + n] ||
627 | [];
628 | for (var i = 0, scheme; (scheme = schemes[i]); ++i) {
629 | if ((cbf ? scheme.cbf : scheme.max) >= opt_number) {
630 | ret.push(scheme);
631 | }
632 | }
633 | });
634 |
635 | ret.sort(function(a, b) {
636 | return a.scheme_name >= b.scheme_name ?
637 | a.scheme_name > b.scheme_name ? 1 : 0 : -1;
638 | });
639 | return ret;
640 | };
641 |
642 |
643 | /**
644 | * Generates a palette using given colour generating function.
645 | *
646 | * The color_func callback must accept a singe number argument whose value
647 | * can vary from 0.0 to 1.0 (or in general from opt_start to opt_end), and
648 | * return a colour as an 'RRGGBB' string. This function will be used when
649 | * generating palettes, i.e. if 11-colour palette is requested, this
650 | * function will be called with arguments 0.0, 0.1, …, 1.0.
651 | *
652 | * In some cases, it is not desirable to reach 1.0 when generating
653 | * a palette. This happens for hue-rainbows where the 0–1 range corresponds
654 | * to a 0°–360° range in hues, and since hue at 0° is the same as at 360°,
655 | * it's desired to stop short the end of the range when generating
656 | * a palette. To accomplish this, opt_cyclic should be set to true.
657 | *
658 | * opt_start and opt_end may be used to change the range the colour
659 | * generation function is called with. opt_end may be less than opt_start
660 | * which will case to traverse the range in reverse. Another way to reverse
661 | * the palette is requesting negative number of colours. The two methods do
662 | * not always lead to the same results (especially if opt_cyclic is set).
663 | *
664 | * @param {palette.ColorFunction} color_func A colours generating callback
665 | * function.
666 | * @param {number} number Number of colours to generate in the palette. If
667 | * number is negative, colours in the palette will be reversed. If only
668 | * one colour is requested, colour at opt_start will be returned.
669 | * @param {number=} opt_start Optional starting point for the palette
670 | * generation function. Zero by default.
671 | * @param {number=} opt_end Optional ending point for the palette generation
672 | * function. One by default.
673 | * @param {boolean=} opt_cyclic If true, function will assume colour at
674 | * point opt_start is the same as one at opt_end.
675 | * @return {palette.Palette} An array of 'RRGGBB' colours.
676 | */
677 | palette.generate = function(color_func, number, opt_start, opt_end,
678 | opt_cyclic) {
679 | if (Math.abs(number) < 1) {
680 | return [];
681 | }
682 |
683 | opt_start = opt_start === void(0) ? 0 : opt_start;
684 | opt_end = opt_end === void(0) ? 1 : opt_end;
685 |
686 | if (Math.abs(number) < 2) {
687 | return [color_func(opt_start)];
688 | }
689 |
690 | var i = Math.abs(number);
691 | var x = opt_start;
692 | var ret = [];
693 | var step = (opt_end - opt_start) / (opt_cyclic ? i : (i - 1));
694 |
695 | for (; --i >= 0; x += step) {
696 | ret.push(color_func(x));
697 | }
698 | if (number < 0) {
699 | ret.reverse();
700 | }
701 | return ret;
702 | };
703 |
704 |
705 | /**
706 | * Clamps value to [0, 1] range.
707 | * @param {number} v Number to limit value of.
708 | * @return {number} If v is inside of [0, 1] range returns v, otherwise
709 | * returns 0 or 1 depending which side of the range v is closer to.
710 | */
711 | var clamp = function(v) {
712 | return v > 0 ? (v < 1 ? v : 1) : 0;
713 | };
714 |
715 | /**
716 | * Converts r, g, b triple into RRGGBB hex representation.
717 | * @param {number} r Red value of the colour in the range [0, 1].
718 | * @param {number} g Green value of the colour in the range [0, 1].
719 | * @param {number} b Blue value of the colour in the range [0, 1].
720 | * @return {string} A lower-case RRGGBB representation of the colour.
721 | */
722 | palette.rgbColor = function(r, g, b) {
723 | return [r, g, b].map(function(v) {
724 | v = Number(Math.round(clamp(v) * 255)).toString(16);
725 | return v.length == 1 ? '0' + v : v;
726 | }).join('');
727 | };
728 |
729 | /**
730 | * Converts a linear r, g, b triple into RRGGBB hex representation.
731 | * @param {number} r Linear red value of the colour in the range [0, 1].
732 | * @param {number} g Linear green value of the colour in the range [0, 1].
733 | * @param {number} b Linear blue value of the colour in the range [0, 1].
734 | * @return {string} A lower-case RRGGBB representation of the colour.
735 | */
736 | palette.linearRgbColor = function(r, g, b) {
737 | // http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
738 | return [r, g, b].map(function(v) {
739 | v = clamp(v);
740 | if (v <= 0.0031308) {
741 | v = 12.92 * v;
742 | } else {
743 | v = 1.055 * Math.pow(v, 1 / 2.4) - 0.055;
744 | }
745 | v = Number(Math.round(v * 255)).toString(16);
746 | return v.length == 1 ? '0' + v : v;
747 | }).join('');
748 | };
749 |
750 | /**
751 | * Converts an HSV colours to RRGGBB hex representation.
752 | * @param {number} h Hue in the range [0, 1].
753 | * @param {number=} opt_s Saturation in the range [0, 1]. One by default.
754 | * @param {number=} opt_v Value in the range [0, 1]. One by default.
755 | * @return {string} An RRGGBB representation of the colour.
756 | */
757 | palette.hsvColor = function(h, opt_s, opt_v) {
758 | h *= 6;
759 | var s = opt_s === void(0) ? 1 : clamp(opt_s);
760 | var v = opt_v === void(0) ? 1 : clamp(opt_v);
761 | var x = v * (1 - s * Math.abs(h % 2 - 1));
762 | var m = v * (1 - s);
763 | switch (Math.floor(h) % 6) {
764 | case 0: return palette.rgbColor(v, x, m);
765 | case 1: return palette.rgbColor(x, v, m);
766 | case 2: return palette.rgbColor(m, v, x);
767 | case 3: return palette.rgbColor(m, x, v);
768 | case 4: return palette.rgbColor(x, m, v);
769 | default: return palette.rgbColor(v, m, x);
770 | }
771 | };
772 |
773 | palette.register(palette.Scheme.withColorFunction(
774 | 'rainbow', 'qualitative', palette.hsvColor, false, true));
775 |
776 | return palette;
777 | })();
778 |
779 |
780 | /** @typedef {function(number): string} */
781 | palette.ColorFunction;
782 |
783 | /** @typedef {!Array.} */
784 | palette.Palette;
785 |
786 | /** @typedef {!Object.|!Array.} */
787 | palette.PalettesList;
788 |
789 | /**
790 | * @typedef {
791 | * function(number, ...[?]): Array.|
792 | * {
793 | * scheme_name: string,
794 | * groups: !Array.,
795 | * max: number,
796 | * cbf_max: number,
797 | * addPalette: function(!Array., boolean=),
798 | * addPalettes: function(palette.PalettesList, number=, number=),
799 | * shrinkByTakingHead: function(boolean, number=),
800 | * setColorFunction: function(palette.ColorFunction, boolean=, boolean=),
801 | * color: function(number, ...[?]): ?string}}
802 | */
803 | palette.SchemeType;
804 |
805 |
806 | /* Paul Tol's schemes start here. *******************************************/
807 | /* See http://www.sron.nl/~pault/ */
808 |
809 | (function() {
810 | var rgb = palette.rgbColor;
811 |
812 | /**
813 | * Calculates value of a polynomial at given point.
814 | * For example, poly(x, 1, 2, 3) calculates value of “1 + 2*x + 2*X^2”.
815 | * @param {number} x Value to calculate polynomial for.
816 | * @param {...number} varargs Coefficients of the polynomial specified in
817 | * the order of rising powers of x including constant as the first
818 | * variable argument.
819 | */
820 | var poly = function(x, varargs) {
821 | var i = arguments.length - 1, n = arguments[i];
822 | while (i > 1) {
823 | n = n * x + arguments[--i];
824 | }
825 | return n;
826 | };
827 |
828 | /**
829 | * Calculate approximate value of error function with maximum error of 0.0005.
830 | * See .
831 | * @param {number} x Argument of the error function.
832 | * @return {number} Value of error function for x.
833 | */
834 | var erf = function(x) {
835 | // https://en.wikipedia.org/wiki/Error_function#Approximation_with_elementary_functions
836 | // This produces a maximum error of 0.0005 which is more then we need. In
837 | // the worst case, that error is multiplied by four and then divided by
838 | // two before being multiplied by 255, so in the end, the error is
839 | // multiplied by 510 which produces 0.255 which is less than a single
840 | // colour step.
841 | var y = poly(Math.abs(x), 1, 0.278393, 0.230389, 0.000972, 0.078108);
842 | y *= y; // y^2
843 | y *= y; // y^4
844 | y = 1 - 1 / y;
845 | return x < 0 ? -y : y;
846 | };
847 |
848 | palette.register(palette.Scheme.fromPalettes('tol', 'qualitative', [
849 | ['4477aa'],
850 | ['4477aa', 'cc6677'],
851 | ['4477aa', 'ddcc77', 'cc6677'],
852 | ['4477aa', '117733', 'ddcc77', 'cc6677'],
853 | ['332288', '88ccee', '117733', 'ddcc77', 'cc6677'],
854 | ['332288', '88ccee', '117733', 'ddcc77', 'cc6677', 'aa4499'],
855 | ['332288', '88ccee', '44aa99', '117733', 'ddcc77', 'cc6677', 'aa4499'],
856 | ['332288', '88ccee', '44aa99', '117733', '999933', 'ddcc77', 'cc6677',
857 | 'aa4499'],
858 | ['332288', '88ccee', '44aa99', '117733', '999933', 'ddcc77', 'cc6677',
859 | '882255', 'aa4499'],
860 | ['332288', '88ccee', '44aa99', '117733', '999933', 'ddcc77', '661100',
861 | 'cc6677', '882255', 'aa4499'],
862 | ['332288', '6699cc', '88ccee', '44aa99', '117733', '999933', 'ddcc77',
863 | '661100', 'cc6677', '882255', 'aa4499'],
864 | ['332288', '6699cc', '88ccee', '44aa99', '117733', '999933', 'ddcc77',
865 | '661100', 'cc6677', 'aa4466', '882255', 'aa4499']
866 | ], 12, 12));
867 |
868 | /**
869 | * Calculates a colour along Paul Tol's sequential colours axis.
870 | * See figure 7 and equation 1.
871 | * @param {number} x Position of the colour on the axis in the [0, 1] range.
872 | * @return {string} An RRGGBB representation of the colour.
873 | */
874 | palette.tolSequentialColor = function(x) {
875 | return rgb(1 - 0.392 * (1 + erf((x - 0.869) / 0.255)),
876 | 1.021 - 0.456 * (1 + erf((x - 0.527) / 0.376)),
877 | 1 - 0.493 * (1 + erf((x - 0.272) / 0.309)));
878 | };
879 |
880 | palette.register(palette.Scheme.withColorFunction(
881 | 'tol-sq', 'sequential', palette.tolSequentialColor, true));
882 |
883 | /**
884 | * Calculates a colour along Paul Tol's diverging colours axis.
885 | * See figure 8 and equation 2.
886 | * @param {number} x Position of the colour on the axis in the [0, 1] range.
887 | * @return {string} An RRGGBB representation of the colour.
888 | */
889 | palette.tolDivergingColor = function(x) {
890 | var g = poly(x, 0.572, 1.524, -1.811) / poly(x, 1, -0.291, 0.1574);
891 | return rgb(poly(x, 0.235, -2.13, 26.92, -65.5, 63.5, -22.36),
892 | g * g,
893 | 1 / poly(x, 1.579, -4.03, 12.92, -31.4, 48.6, -23.36));
894 | };
895 |
896 | palette.register(palette.Scheme.withColorFunction(
897 | 'tol-dv', 'diverging', palette.tolDivergingColor, true));
898 |
899 | /**
900 | * Calculates a colour along Paul Tol's rainbow colours axis.
901 | * See figure 13 and equation 3.
902 | * @param {number} x Position of the colour on the axis in the [0, 1] range.
903 | * @return {string} An RRGGBB representation of the colour.
904 | */
905 | palette.tolRainbowColor = function(x) {
906 | return rgb(poly(x, 0.472, -0.567, 4.05) / poly(x, 1, 8.72, -19.17, 14.1),
907 | poly(x, 0.108932, -1.22635, 27.284, -98.577, 163.3, -131.395,
908 | 40.634),
909 | 1 / poly(x, 1.97, 3.54, -68.5, 243, -297, 125));
910 | };
911 |
912 | palette.register(palette.Scheme.withColorFunction(
913 | 'tol-rainbow', 'qualitative', palette.tolRainbowColor, true));
914 | })();
915 |
916 |
917 | /* Solarized colour schemes start here. *************************************/
918 | /* See http://ethanschoonover.com/solarized */
919 |
920 | (function() {
921 | /*
922 | * Those are not really designed to be used in graphs, but we're keeping
923 | * them here in case someone cares.
924 | */
925 | palette.register(palette.Scheme.fromPalettes('sol-base', 'sequential', [
926 | ['002b36', '073642', '586e75', '657b83', '839496', '93a1a1', 'eee8d5',
927 | 'fdf6e3']
928 | ], 1, 8));
929 | palette.register(palette.Scheme.fromPalettes('sol-accent', 'qualitative', [
930 | ['b58900', 'cb4b16', 'dc322f', 'd33682', '6c71c4', '268bd2', '2aa198',
931 | '859900']
932 | ]));
933 | })();
934 |
935 |
936 | /* ColorBrewer colour schemes start here. ***********************************/
937 | /* See http://colorbrewer2.org/ */
938 |
939 | (function() {
940 | var schemes = {
941 | YlGn: {
942 | type: 'sequential',
943 | cbf: 42,
944 | 3: ['f7fcb9', 'addd8e', '31a354'],
945 | 4: ['ffffcc', 'c2e699', '78c679', '238443'],
946 | 5: ['ffffcc', 'c2e699', '78c679', '31a354', '006837'],
947 | 6: ['ffffcc', 'd9f0a3', 'addd8e', '78c679', '31a354', '006837'],
948 | 7: ['ffffcc', 'd9f0a3', 'addd8e', '78c679', '41ab5d', '238443',
949 | '005a32'],
950 | 8: ['ffffe5', 'f7fcb9', 'd9f0a3', 'addd8e', '78c679', '41ab5d',
951 | '238443', '005a32'],
952 | 9: ['ffffe5', 'f7fcb9', 'd9f0a3', 'addd8e', '78c679', '41ab5d',
953 | '238443', '006837', '004529']
954 | },
955 | YlGnBu: {
956 | type: 'sequential',
957 | cbf: 42,
958 | 3: ['edf8b1', '7fcdbb', '2c7fb8'],
959 | 4: ['ffffcc', 'a1dab4', '41b6c4', '225ea8'],
960 | 5: ['ffffcc', 'a1dab4', '41b6c4', '2c7fb8', '253494'],
961 | 6: ['ffffcc', 'c7e9b4', '7fcdbb', '41b6c4', '2c7fb8', '253494'],
962 | 7: ['ffffcc', 'c7e9b4', '7fcdbb', '41b6c4', '1d91c0', '225ea8',
963 | '0c2c84'],
964 | 8: ['ffffd9', 'edf8b1', 'c7e9b4', '7fcdbb', '41b6c4', '1d91c0',
965 | '225ea8', '0c2c84'],
966 | 9: ['ffffd9', 'edf8b1', 'c7e9b4', '7fcdbb', '41b6c4', '1d91c0',
967 | '225ea8', '253494', '081d58']
968 | },
969 | GnBu: {
970 | type: 'sequential',
971 | cbf: 42,
972 | 3: ['e0f3db', 'a8ddb5', '43a2ca'],
973 | 4: ['f0f9e8', 'bae4bc', '7bccc4', '2b8cbe'],
974 | 5: ['f0f9e8', 'bae4bc', '7bccc4', '43a2ca', '0868ac'],
975 | 6: ['f0f9e8', 'ccebc5', 'a8ddb5', '7bccc4', '43a2ca', '0868ac'],
976 | 7: ['f0f9e8', 'ccebc5', 'a8ddb5', '7bccc4', '4eb3d3', '2b8cbe',
977 | '08589e'],
978 | 8: ['f7fcf0', 'e0f3db', 'ccebc5', 'a8ddb5', '7bccc4', '4eb3d3',
979 | '2b8cbe', '08589e'],
980 | 9: ['f7fcf0', 'e0f3db', 'ccebc5', 'a8ddb5', '7bccc4', '4eb3d3',
981 | '2b8cbe', '0868ac', '084081']
982 | },
983 | BuGn: {
984 | type: 'sequential',
985 | cbf: 42,
986 | 3: ['e5f5f9', '99d8c9', '2ca25f'],
987 | 4: ['edf8fb', 'b2e2e2', '66c2a4', '238b45'],
988 | 5: ['edf8fb', 'b2e2e2', '66c2a4', '2ca25f', '006d2c'],
989 | 6: ['edf8fb', 'ccece6', '99d8c9', '66c2a4', '2ca25f', '006d2c'],
990 | 7: ['edf8fb', 'ccece6', '99d8c9', '66c2a4', '41ae76', '238b45',
991 | '005824'],
992 | 8: ['f7fcfd', 'e5f5f9', 'ccece6', '99d8c9', '66c2a4', '41ae76',
993 | '238b45', '005824'],
994 | 9: ['f7fcfd', 'e5f5f9', 'ccece6', '99d8c9', '66c2a4', '41ae76',
995 | '238b45', '006d2c', '00441b']
996 | },
997 | PuBuGn: {
998 | type: 'sequential',
999 | cbf: 42,
1000 | 3: ['ece2f0', 'a6bddb', '1c9099'],
1001 | 4: ['f6eff7', 'bdc9e1', '67a9cf', '02818a'],
1002 | 5: ['f6eff7', 'bdc9e1', '67a9cf', '1c9099', '016c59'],
1003 | 6: ['f6eff7', 'd0d1e6', 'a6bddb', '67a9cf', '1c9099', '016c59'],
1004 | 7: ['f6eff7', 'd0d1e6', 'a6bddb', '67a9cf', '3690c0', '02818a',
1005 | '016450'],
1006 | 8: ['fff7fb', 'ece2f0', 'd0d1e6', 'a6bddb', '67a9cf', '3690c0',
1007 | '02818a', '016450'],
1008 | 9: ['fff7fb', 'ece2f0', 'd0d1e6', 'a6bddb', '67a9cf', '3690c0',
1009 | '02818a', '016c59', '014636']
1010 | },
1011 | PuBu: {
1012 | type: 'sequential',
1013 | cbf: 42,
1014 | 3: ['ece7f2', 'a6bddb', '2b8cbe'],
1015 | 4: ['f1eef6', 'bdc9e1', '74a9cf', '0570b0'],
1016 | 5: ['f1eef6', 'bdc9e1', '74a9cf', '2b8cbe', '045a8d'],
1017 | 6: ['f1eef6', 'd0d1e6', 'a6bddb', '74a9cf', '2b8cbe', '045a8d'],
1018 | 7: ['f1eef6', 'd0d1e6', 'a6bddb', '74a9cf', '3690c0', '0570b0',
1019 | '034e7b'],
1020 | 8: ['fff7fb', 'ece7f2', 'd0d1e6', 'a6bddb', '74a9cf', '3690c0',
1021 | '0570b0', '034e7b'],
1022 | 9: ['fff7fb', 'ece7f2', 'd0d1e6', 'a6bddb', '74a9cf', '3690c0',
1023 | '0570b0', '045a8d', '023858']
1024 | },
1025 | BuPu: {
1026 | type: 'sequential',
1027 | cbf: 42,
1028 | 3: ['e0ecf4', '9ebcda', '8856a7'],
1029 | 4: ['edf8fb', 'b3cde3', '8c96c6', '88419d'],
1030 | 5: ['edf8fb', 'b3cde3', '8c96c6', '8856a7', '810f7c'],
1031 | 6: ['edf8fb', 'bfd3e6', '9ebcda', '8c96c6', '8856a7', '810f7c'],
1032 | 7: ['edf8fb', 'bfd3e6', '9ebcda', '8c96c6', '8c6bb1', '88419d',
1033 | '6e016b'],
1034 | 8: ['f7fcfd', 'e0ecf4', 'bfd3e6', '9ebcda', '8c96c6', '8c6bb1',
1035 | '88419d', '6e016b'],
1036 | 9: ['f7fcfd', 'e0ecf4', 'bfd3e6', '9ebcda', '8c96c6', '8c6bb1',
1037 | '88419d', '810f7c', '4d004b']
1038 | },
1039 | RdPu: {
1040 | type: 'sequential',
1041 | cbf: 42,
1042 | 3: ['fde0dd', 'fa9fb5', 'c51b8a'],
1043 | 4: ['feebe2', 'fbb4b9', 'f768a1', 'ae017e'],
1044 | 5: ['feebe2', 'fbb4b9', 'f768a1', 'c51b8a', '7a0177'],
1045 | 6: ['feebe2', 'fcc5c0', 'fa9fb5', 'f768a1', 'c51b8a', '7a0177'],
1046 | 7: ['feebe2', 'fcc5c0', 'fa9fb5', 'f768a1', 'dd3497', 'ae017e',
1047 | '7a0177'],
1048 | 8: ['fff7f3', 'fde0dd', 'fcc5c0', 'fa9fb5', 'f768a1', 'dd3497',
1049 | 'ae017e', '7a0177'],
1050 | 9: ['fff7f3', 'fde0dd', 'fcc5c0', 'fa9fb5', 'f768a1', 'dd3497',
1051 | 'ae017e', '7a0177', '49006a']
1052 | },
1053 | PuRd: {
1054 | type: 'sequential',
1055 | cbf: 42,
1056 | 3: ['e7e1ef', 'c994c7', 'dd1c77'],
1057 | 4: ['f1eef6', 'd7b5d8', 'df65b0', 'ce1256'],
1058 | 5: ['f1eef6', 'd7b5d8', 'df65b0', 'dd1c77', '980043'],
1059 | 6: ['f1eef6', 'd4b9da', 'c994c7', 'df65b0', 'dd1c77', '980043'],
1060 | 7: ['f1eef6', 'd4b9da', 'c994c7', 'df65b0', 'e7298a', 'ce1256',
1061 | '91003f'],
1062 | 8: ['f7f4f9', 'e7e1ef', 'd4b9da', 'c994c7', 'df65b0', 'e7298a',
1063 | 'ce1256', '91003f'],
1064 | 9: ['f7f4f9', 'e7e1ef', 'd4b9da', 'c994c7', 'df65b0', 'e7298a',
1065 | 'ce1256', '980043', '67001f']
1066 | },
1067 | OrRd: {
1068 | type: 'sequential',
1069 | cbf: 42,
1070 | 3: ['fee8c8', 'fdbb84', 'e34a33'],
1071 | 4: ['fef0d9', 'fdcc8a', 'fc8d59', 'd7301f'],
1072 | 5: ['fef0d9', 'fdcc8a', 'fc8d59', 'e34a33', 'b30000'],
1073 | 6: ['fef0d9', 'fdd49e', 'fdbb84', 'fc8d59', 'e34a33', 'b30000'],
1074 | 7: ['fef0d9', 'fdd49e', 'fdbb84', 'fc8d59', 'ef6548', 'd7301f',
1075 | '990000'],
1076 | 8: ['fff7ec', 'fee8c8', 'fdd49e', 'fdbb84', 'fc8d59', 'ef6548',
1077 | 'd7301f', '990000'],
1078 | 9: ['fff7ec', 'fee8c8', 'fdd49e', 'fdbb84', 'fc8d59', 'ef6548',
1079 | 'd7301f', 'b30000', '7f0000']
1080 | },
1081 | YlOrRd: {
1082 | type: 'sequential',
1083 | cbf: 42,
1084 | 3: ['ffeda0', 'feb24c', 'f03b20'],
1085 | 4: ['ffffb2', 'fecc5c', 'fd8d3c', 'e31a1c'],
1086 | 5: ['ffffb2', 'fecc5c', 'fd8d3c', 'f03b20', 'bd0026'],
1087 | 6: ['ffffb2', 'fed976', 'feb24c', 'fd8d3c', 'f03b20', 'bd0026'],
1088 | 7: ['ffffb2', 'fed976', 'feb24c', 'fd8d3c', 'fc4e2a', 'e31a1c',
1089 | 'b10026'],
1090 | 8: ['ffffcc', 'ffeda0', 'fed976', 'feb24c', 'fd8d3c', 'fc4e2a',
1091 | 'e31a1c', 'b10026'],
1092 | 9: ['ffffcc', 'ffeda0', 'fed976', 'feb24c', 'fd8d3c', 'fc4e2a',
1093 | 'e31a1c', 'bd0026', '800026']
1094 | },
1095 | YlOrBr: {
1096 | type: 'sequential',
1097 | cbf: 42,
1098 | 3: ['fff7bc', 'fec44f', 'd95f0e'],
1099 | 4: ['ffffd4', 'fed98e', 'fe9929', 'cc4c02'],
1100 | 5: ['ffffd4', 'fed98e', 'fe9929', 'd95f0e', '993404'],
1101 | 6: ['ffffd4', 'fee391', 'fec44f', 'fe9929', 'd95f0e', '993404'],
1102 | 7: ['ffffd4', 'fee391', 'fec44f', 'fe9929', 'ec7014', 'cc4c02',
1103 | '8c2d04'],
1104 | 8: ['ffffe5', 'fff7bc', 'fee391', 'fec44f', 'fe9929', 'ec7014',
1105 | 'cc4c02', '8c2d04'],
1106 | 9: ['ffffe5', 'fff7bc', 'fee391', 'fec44f', 'fe9929', 'ec7014',
1107 | 'cc4c02', '993404', '662506']
1108 | },
1109 | Purples: {
1110 | type: 'sequential',
1111 | cbf: 42,
1112 | 3: ['efedf5', 'bcbddc', '756bb1'],
1113 | 4: ['f2f0f7', 'cbc9e2', '9e9ac8', '6a51a3'],
1114 | 5: ['f2f0f7', 'cbc9e2', '9e9ac8', '756bb1', '54278f'],
1115 | 6: ['f2f0f7', 'dadaeb', 'bcbddc', '9e9ac8', '756bb1', '54278f'],
1116 | 7: ['f2f0f7', 'dadaeb', 'bcbddc', '9e9ac8', '807dba', '6a51a3',
1117 | '4a1486'],
1118 | 8: ['fcfbfd', 'efedf5', 'dadaeb', 'bcbddc', '9e9ac8', '807dba',
1119 | '6a51a3', '4a1486'],
1120 | 9: ['fcfbfd', 'efedf5', 'dadaeb', 'bcbddc', '9e9ac8', '807dba',
1121 | '6a51a3', '54278f', '3f007d']
1122 | },
1123 | Blues: {
1124 | type: 'sequential',
1125 | cbf: 42,
1126 | 3: ['deebf7', '9ecae1', '3182bd'],
1127 | 4: ['eff3ff', 'bdd7e7', '6baed6', '2171b5'],
1128 | 5: ['eff3ff', 'bdd7e7', '6baed6', '3182bd', '08519c'],
1129 | 6: ['eff3ff', 'c6dbef', '9ecae1', '6baed6', '3182bd', '08519c'],
1130 | 7: ['eff3ff', 'c6dbef', '9ecae1', '6baed6', '4292c6', '2171b5',
1131 | '084594'],
1132 | 8: ['f7fbff', 'deebf7', 'c6dbef', '9ecae1', '6baed6', '4292c6',
1133 | '2171b5', '084594'],
1134 | 9: ['f7fbff', 'deebf7', 'c6dbef', '9ecae1', '6baed6', '4292c6',
1135 | '2171b5', '08519c', '08306b']
1136 | },
1137 | Greens: {
1138 | type: 'sequential',
1139 | cbf: 42,
1140 | 3: ['e5f5e0', 'a1d99b', '31a354'],
1141 | 4: ['edf8e9', 'bae4b3', '74c476', '238b45'],
1142 | 5: ['edf8e9', 'bae4b3', '74c476', '31a354', '006d2c'],
1143 | 6: ['edf8e9', 'c7e9c0', 'a1d99b', '74c476', '31a354', '006d2c'],
1144 | 7: ['edf8e9', 'c7e9c0', 'a1d99b', '74c476', '41ab5d', '238b45',
1145 | '005a32'],
1146 | 8: ['f7fcf5', 'e5f5e0', 'c7e9c0', 'a1d99b', '74c476', '41ab5d',
1147 | '238b45', '005a32'],
1148 | 9: ['f7fcf5', 'e5f5e0', 'c7e9c0', 'a1d99b', '74c476', '41ab5d',
1149 | '238b45', '006d2c', '00441b']
1150 | },
1151 | Oranges: {
1152 | type: 'sequential',
1153 | cbf: 42,
1154 | 3: ['fee6ce', 'fdae6b', 'e6550d'],
1155 | 4: ['feedde', 'fdbe85', 'fd8d3c', 'd94701'],
1156 | 5: ['feedde', 'fdbe85', 'fd8d3c', 'e6550d', 'a63603'],
1157 | 6: ['feedde', 'fdd0a2', 'fdae6b', 'fd8d3c', 'e6550d', 'a63603'],
1158 | 7: ['feedde', 'fdd0a2', 'fdae6b', 'fd8d3c', 'f16913', 'd94801',
1159 | '8c2d04'],
1160 | 8: ['fff5eb', 'fee6ce', 'fdd0a2', 'fdae6b', 'fd8d3c', 'f16913',
1161 | 'd94801', '8c2d04'],
1162 | 9: ['fff5eb', 'fee6ce', 'fdd0a2', 'fdae6b', 'fd8d3c', 'f16913',
1163 | 'd94801', 'a63603', '7f2704']
1164 | },
1165 | Reds: {
1166 | type: 'sequential',
1167 | cbf: 42,
1168 | 3: ['fee0d2', 'fc9272', 'de2d26'],
1169 | 4: ['fee5d9', 'fcae91', 'fb6a4a', 'cb181d'],
1170 | 5: ['fee5d9', 'fcae91', 'fb6a4a', 'de2d26', 'a50f15'],
1171 | 6: ['fee5d9', 'fcbba1', 'fc9272', 'fb6a4a', 'de2d26', 'a50f15'],
1172 | 7: ['fee5d9', 'fcbba1', 'fc9272', 'fb6a4a', 'ef3b2c', 'cb181d',
1173 | '99000d'],
1174 | 8: ['fff5f0', 'fee0d2', 'fcbba1', 'fc9272', 'fb6a4a', 'ef3b2c',
1175 | 'cb181d', '99000d'],
1176 | 9: ['fff5f0', 'fee0d2', 'fcbba1', 'fc9272', 'fb6a4a', 'ef3b2c',
1177 | 'cb181d', 'a50f15', '67000d']
1178 | },
1179 | Greys: {
1180 | type: 'sequential',
1181 | cbf: 42,
1182 | 3: ['f0f0f0', 'bdbdbd', '636363'],
1183 | 4: ['f7f7f7', 'cccccc', '969696', '525252'],
1184 | 5: ['f7f7f7', 'cccccc', '969696', '636363', '252525'],
1185 | 6: ['f7f7f7', 'd9d9d9', 'bdbdbd', '969696', '636363', '252525'],
1186 | 7: ['f7f7f7', 'd9d9d9', 'bdbdbd', '969696', '737373', '525252',
1187 | '252525'],
1188 | 8: ['ffffff', 'f0f0f0', 'd9d9d9', 'bdbdbd', '969696', '737373',
1189 | '525252', '252525'],
1190 | 9: ['ffffff', 'f0f0f0', 'd9d9d9', 'bdbdbd', '969696', '737373',
1191 | '525252', '252525', '000000']
1192 | },
1193 | PuOr: {
1194 | type: 'diverging',
1195 | cbf: 42,
1196 | 3: ['f1a340', 'f7f7f7', '998ec3'],
1197 | 4: ['e66101', 'fdb863', 'b2abd2', '5e3c99'],
1198 | 5: ['e66101', 'fdb863', 'f7f7f7', 'b2abd2', '5e3c99'],
1199 | 6: ['b35806', 'f1a340', 'fee0b6', 'd8daeb', '998ec3', '542788'],
1200 | 7: ['b35806', 'f1a340', 'fee0b6', 'f7f7f7', 'd8daeb', '998ec3',
1201 | '542788'],
1202 | 8: ['b35806', 'e08214', 'fdb863', 'fee0b6', 'd8daeb', 'b2abd2',
1203 | '8073ac', '542788'],
1204 | 9: ['b35806', 'e08214', 'fdb863', 'fee0b6', 'f7f7f7', 'd8daeb',
1205 | 'b2abd2', '8073ac', '542788'],
1206 | 10: ['7f3b08', 'b35806', 'e08214', 'fdb863', 'fee0b6', 'd8daeb',
1207 | 'b2abd2', '8073ac', '542788', '2d004b'],
1208 | 11: ['7f3b08', 'b35806', 'e08214', 'fdb863', 'fee0b6', 'f7f7f7',
1209 | 'd8daeb', 'b2abd2', '8073ac', '542788', '2d004b']
1210 | },
1211 | BrBG: {
1212 | type: 'diverging',
1213 | cbf: 42,
1214 | 3: ['d8b365', 'f5f5f5', '5ab4ac'],
1215 | 4: ['a6611a', 'dfc27d', '80cdc1', '018571'],
1216 | 5: ['a6611a', 'dfc27d', 'f5f5f5', '80cdc1', '018571'],
1217 | 6: ['8c510a', 'd8b365', 'f6e8c3', 'c7eae5', '5ab4ac', '01665e'],
1218 | 7: ['8c510a', 'd8b365', 'f6e8c3', 'f5f5f5', 'c7eae5', '5ab4ac',
1219 | '01665e'],
1220 | 8: ['8c510a', 'bf812d', 'dfc27d', 'f6e8c3', 'c7eae5', '80cdc1',
1221 | '35978f', '01665e'],
1222 | 9: ['8c510a', 'bf812d', 'dfc27d', 'f6e8c3', 'f5f5f5', 'c7eae5',
1223 | '80cdc1', '35978f', '01665e'],
1224 | 10: ['543005', '8c510a', 'bf812d', 'dfc27d', 'f6e8c3', 'c7eae5',
1225 | '80cdc1', '35978f', '01665e', '003c30'],
1226 | 11: ['543005', '8c510a', 'bf812d', 'dfc27d', 'f6e8c3', 'f5f5f5',
1227 | 'c7eae5', '80cdc1', '35978f', '01665e', '003c30']
1228 | },
1229 | PRGn: {
1230 | type: 'diverging',
1231 | cbf: 42,
1232 | 3: ['af8dc3', 'f7f7f7', '7fbf7b'],
1233 | 4: ['7b3294', 'c2a5cf', 'a6dba0', '008837'],
1234 | 5: ['7b3294', 'c2a5cf', 'f7f7f7', 'a6dba0', '008837'],
1235 | 6: ['762a83', 'af8dc3', 'e7d4e8', 'd9f0d3', '7fbf7b', '1b7837'],
1236 | 7: ['762a83', 'af8dc3', 'e7d4e8', 'f7f7f7', 'd9f0d3', '7fbf7b',
1237 | '1b7837'],
1238 | 8: ['762a83', '9970ab', 'c2a5cf', 'e7d4e8', 'd9f0d3', 'a6dba0',
1239 | '5aae61', '1b7837'],
1240 | 9: ['762a83', '9970ab', 'c2a5cf', 'e7d4e8', 'f7f7f7', 'd9f0d3',
1241 | 'a6dba0', '5aae61', '1b7837'],
1242 | 10: ['40004b', '762a83', '9970ab', 'c2a5cf', 'e7d4e8', 'd9f0d3',
1243 | 'a6dba0', '5aae61', '1b7837', '00441b'],
1244 | 11: ['40004b', '762a83', '9970ab', 'c2a5cf', 'e7d4e8', 'f7f7f7',
1245 | 'd9f0d3', 'a6dba0', '5aae61', '1b7837', '00441b']
1246 | },
1247 | PiYG: {
1248 | type: 'diverging',
1249 | cbf: 42,
1250 | 3: ['e9a3c9', 'f7f7f7', 'a1d76a'],
1251 | 4: ['d01c8b', 'f1b6da', 'b8e186', '4dac26'],
1252 | 5: ['d01c8b', 'f1b6da', 'f7f7f7', 'b8e186', '4dac26'],
1253 | 6: ['c51b7d', 'e9a3c9', 'fde0ef', 'e6f5d0', 'a1d76a', '4d9221'],
1254 | 7: ['c51b7d', 'e9a3c9', 'fde0ef', 'f7f7f7', 'e6f5d0', 'a1d76a',
1255 | '4d9221'],
1256 | 8: ['c51b7d', 'de77ae', 'f1b6da', 'fde0ef', 'e6f5d0', 'b8e186',
1257 | '7fbc41', '4d9221'],
1258 | 9: ['c51b7d', 'de77ae', 'f1b6da', 'fde0ef', 'f7f7f7', 'e6f5d0',
1259 | 'b8e186', '7fbc41', '4d9221'],
1260 | 10: ['8e0152', 'c51b7d', 'de77ae', 'f1b6da', 'fde0ef', 'e6f5d0',
1261 | 'b8e186', '7fbc41', '4d9221', '276419'],
1262 | 11: ['8e0152', 'c51b7d', 'de77ae', 'f1b6da', 'fde0ef', 'f7f7f7',
1263 | 'e6f5d0', 'b8e186', '7fbc41', '4d9221', '276419']
1264 | },
1265 | RdBu: {
1266 | type: 'diverging',
1267 | cbf: 42,
1268 | 3: ['ef8a62', 'f7f7f7', '67a9cf'],
1269 | 4: ['ca0020', 'f4a582', '92c5de', '0571b0'],
1270 | 5: ['ca0020', 'f4a582', 'f7f7f7', '92c5de', '0571b0'],
1271 | 6: ['b2182b', 'ef8a62', 'fddbc7', 'd1e5f0', '67a9cf', '2166ac'],
1272 | 7: ['b2182b', 'ef8a62', 'fddbc7', 'f7f7f7', 'd1e5f0', '67a9cf',
1273 | '2166ac'],
1274 | 8: ['b2182b', 'd6604d', 'f4a582', 'fddbc7', 'd1e5f0', '92c5de',
1275 | '4393c3', '2166ac'],
1276 | 9: ['b2182b', 'd6604d', 'f4a582', 'fddbc7', 'f7f7f7', 'd1e5f0',
1277 | '92c5de', '4393c3', '2166ac'],
1278 | 10: ['67001f', 'b2182b', 'd6604d', 'f4a582', 'fddbc7', 'd1e5f0',
1279 | '92c5de', '4393c3', '2166ac', '053061'],
1280 | 11: ['67001f', 'b2182b', 'd6604d', 'f4a582', 'fddbc7', 'f7f7f7',
1281 | 'd1e5f0', '92c5de', '4393c3', '2166ac', '053061']
1282 | },
1283 | RdGy: {
1284 | type: 'diverging',
1285 | cbf: 42,
1286 | 3: ['ef8a62', 'ffffff', '999999'],
1287 | 4: ['ca0020', 'f4a582', 'bababa', '404040'],
1288 | 5: ['ca0020', 'f4a582', 'ffffff', 'bababa', '404040'],
1289 | 6: ['b2182b', 'ef8a62', 'fddbc7', 'e0e0e0', '999999', '4d4d4d'],
1290 | 7: ['b2182b', 'ef8a62', 'fddbc7', 'ffffff', 'e0e0e0', '999999',
1291 | '4d4d4d'],
1292 | 8: ['b2182b', 'd6604d', 'f4a582', 'fddbc7', 'e0e0e0', 'bababa',
1293 | '878787', '4d4d4d'],
1294 | 9: ['b2182b', 'd6604d', 'f4a582', 'fddbc7', 'ffffff', 'e0e0e0',
1295 | 'bababa', '878787', '4d4d4d'],
1296 | 10: ['67001f', 'b2182b', 'd6604d', 'f4a582', 'fddbc7', 'e0e0e0',
1297 | 'bababa', '878787', '4d4d4d', '1a1a1a'],
1298 | 11: ['67001f', 'b2182b', 'd6604d', 'f4a582', 'fddbc7', 'ffffff',
1299 | 'e0e0e0', 'bababa', '878787', '4d4d4d', '1a1a1a']
1300 | },
1301 | RdYlBu: {
1302 | type: 'diverging',
1303 | cbf: 42,
1304 | 3: ['fc8d59', 'ffffbf', '91bfdb'],
1305 | 4: ['d7191c', 'fdae61', 'abd9e9', '2c7bb6'],
1306 | 5: ['d7191c', 'fdae61', 'ffffbf', 'abd9e9', '2c7bb6'],
1307 | 6: ['d73027', 'fc8d59', 'fee090', 'e0f3f8', '91bfdb', '4575b4'],
1308 | 7: ['d73027', 'fc8d59', 'fee090', 'ffffbf', 'e0f3f8', '91bfdb',
1309 | '4575b4'],
1310 | 8: ['d73027', 'f46d43', 'fdae61', 'fee090', 'e0f3f8', 'abd9e9',
1311 | '74add1', '4575b4'],
1312 | 9: ['d73027', 'f46d43', 'fdae61', 'fee090', 'ffffbf', 'e0f3f8',
1313 | 'abd9e9', '74add1', '4575b4'],
1314 | 10: ['a50026', 'd73027', 'f46d43', 'fdae61', 'fee090', 'e0f3f8',
1315 | 'abd9e9', '74add1', '4575b4', '313695'],
1316 | 11: ['a50026', 'd73027', 'f46d43', 'fdae61', 'fee090', 'ffffbf',
1317 | 'e0f3f8', 'abd9e9', '74add1', '4575b4', '313695']
1318 | },
1319 | Spectral: {
1320 | type: 'diverging',
1321 | cbf: 0,
1322 | 3: ['fc8d59', 'ffffbf', '99d594'],
1323 | 4: ['d7191c', 'fdae61', 'abdda4', '2b83ba'],
1324 | 5: ['d7191c', 'fdae61', 'ffffbf', 'abdda4', '2b83ba'],
1325 | 6: ['d53e4f', 'fc8d59', 'fee08b', 'e6f598', '99d594', '3288bd'],
1326 | 7: ['d53e4f', 'fc8d59', 'fee08b', 'ffffbf', 'e6f598', '99d594',
1327 | '3288bd'],
1328 | 8: ['d53e4f', 'f46d43', 'fdae61', 'fee08b', 'e6f598', 'abdda4',
1329 | '66c2a5', '3288bd'],
1330 | 9: ['d53e4f', 'f46d43', 'fdae61', 'fee08b', 'ffffbf', 'e6f598',
1331 | 'abdda4', '66c2a5', '3288bd'],
1332 | 10: ['9e0142', 'd53e4f', 'f46d43', 'fdae61', 'fee08b', 'e6f598',
1333 | 'abdda4', '66c2a5', '3288bd', '5e4fa2'],
1334 | 11: ['9e0142', 'd53e4f', 'f46d43', 'fdae61', 'fee08b', 'ffffbf',
1335 | 'e6f598', 'abdda4', '66c2a5', '3288bd', '5e4fa2']
1336 | },
1337 | RdYlGn: {
1338 | type: 'diverging',
1339 | cbf: 0,
1340 | 3: ['fc8d59', 'ffffbf', '91cf60'],
1341 | 4: ['d7191c', 'fdae61', 'a6d96a', '1a9641'],
1342 | 5: ['d7191c', 'fdae61', 'ffffbf', 'a6d96a', '1a9641'],
1343 | 6: ['d73027', 'fc8d59', 'fee08b', 'd9ef8b', '91cf60', '1a9850'],
1344 | 7: ['d73027', 'fc8d59', 'fee08b', 'ffffbf', 'd9ef8b', '91cf60',
1345 | '1a9850'],
1346 | 8: ['d73027', 'f46d43', 'fdae61', 'fee08b', 'd9ef8b', 'a6d96a',
1347 | '66bd63', '1a9850'],
1348 | 9: ['d73027', 'f46d43', 'fdae61', 'fee08b', 'ffffbf', 'd9ef8b',
1349 | 'a6d96a', '66bd63', '1a9850'],
1350 | 10: ['a50026', 'd73027', 'f46d43', 'fdae61', 'fee08b', 'd9ef8b',
1351 | 'a6d96a', '66bd63', '1a9850', '006837'],
1352 | 11: ['a50026', 'd73027', 'f46d43', 'fdae61', 'fee08b', 'ffffbf',
1353 | 'd9ef8b', 'a6d96a', '66bd63', '1a9850', '006837']
1354 | },
1355 | Accent: {
1356 | type: 'qualitative',
1357 | cbf: 0,
1358 | 3: ['7fc97f', 'beaed4', 'fdc086'],
1359 | 4: ['7fc97f', 'beaed4', 'fdc086', 'ffff99'],
1360 | 5: ['7fc97f', 'beaed4', 'fdc086', 'ffff99', '386cb0'],
1361 | 6: ['7fc97f', 'beaed4', 'fdc086', 'ffff99', '386cb0', 'f0027f'],
1362 | 7: ['7fc97f', 'beaed4', 'fdc086', 'ffff99', '386cb0', 'f0027f',
1363 | 'bf5b17'],
1364 | 8: ['7fc97f', 'beaed4', 'fdc086', 'ffff99', '386cb0', 'f0027f',
1365 | 'bf5b17', '666666']
1366 | },
1367 | Dark2: {
1368 | type: 'qualitative',
1369 | cbf: 3,
1370 | 3: ['1b9e77', 'd95f02', '7570b3'],
1371 | 4: ['1b9e77', 'd95f02', '7570b3', 'e7298a'],
1372 | 5: ['1b9e77', 'd95f02', '7570b3', 'e7298a', '66a61e'],
1373 | 6: ['1b9e77', 'd95f02', '7570b3', 'e7298a', '66a61e', 'e6ab02'],
1374 | 7: ['1b9e77', 'd95f02', '7570b3', 'e7298a', '66a61e', 'e6ab02',
1375 | 'a6761d'],
1376 | 8: ['1b9e77', 'd95f02', '7570b3', 'e7298a', '66a61e', 'e6ab02',
1377 | 'a6761d', '666666']
1378 | },
1379 | Paired: {
1380 | type: 'qualitative',
1381 | cbf: 4,
1382 | 3: ['a6cee3', '1f78b4', 'b2df8a'],
1383 | 4: ['a6cee3', '1f78b4', 'b2df8a', '33a02c'],
1384 | 5: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99'],
1385 | 6: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c'],
1386 | 7: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c',
1387 | 'fdbf6f'],
1388 | 8: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c',
1389 | 'fdbf6f', 'ff7f00'],
1390 | 9: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c',
1391 | 'fdbf6f', 'ff7f00', 'cab2d6'],
1392 | 10: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c',
1393 | 'fdbf6f', 'ff7f00', 'cab2d6', '6a3d9a'],
1394 | 11: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c',
1395 | 'fdbf6f', 'ff7f00', 'cab2d6', '6a3d9a', 'ffff99'],
1396 | 12: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c',
1397 | 'fdbf6f', 'ff7f00', 'cab2d6', '6a3d9a', 'ffff99', 'b15928']
1398 | },
1399 | Pastel1: {
1400 | type: 'qualitative',
1401 | cbf: 0,
1402 | 3: ['fbb4ae', 'b3cde3', 'ccebc5'],
1403 | 4: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4'],
1404 | 5: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4', 'fed9a6'],
1405 | 6: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4', 'fed9a6', 'ffffcc'],
1406 | 7: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4', 'fed9a6', 'ffffcc',
1407 | 'e5d8bd'],
1408 | 8: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4', 'fed9a6', 'ffffcc',
1409 | 'e5d8bd', 'fddaec'],
1410 | 9: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4', 'fed9a6', 'ffffcc',
1411 | 'e5d8bd', 'fddaec', 'f2f2f2']
1412 | },
1413 | Pastel2: {
1414 | type: 'qualitative',
1415 | cbf: 0,
1416 | 3: ['b3e2cd', 'fdcdac', 'cbd5e8'],
1417 | 4: ['b3e2cd', 'fdcdac', 'cbd5e8', 'f4cae4'],
1418 | 5: ['b3e2cd', 'fdcdac', 'cbd5e8', 'f4cae4', 'e6f5c9'],
1419 | 6: ['b3e2cd', 'fdcdac', 'cbd5e8', 'f4cae4', 'e6f5c9', 'fff2ae'],
1420 | 7: ['b3e2cd', 'fdcdac', 'cbd5e8', 'f4cae4', 'e6f5c9', 'fff2ae',
1421 | 'f1e2cc'],
1422 | 8: ['b3e2cd', 'fdcdac', 'cbd5e8', 'f4cae4', 'e6f5c9', 'fff2ae',
1423 | 'f1e2cc', 'cccccc']
1424 | },
1425 | Set1: {
1426 | type: 'qualitative',
1427 | cbf: 0,
1428 | 3: ['e41a1c', '377eb8', '4daf4a'],
1429 | 4: ['e41a1c', '377eb8', '4daf4a', '984ea3'],
1430 | 5: ['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00'],
1431 | 6: ['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00', 'ffff33'],
1432 | 7: ['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00', 'ffff33',
1433 | 'a65628'],
1434 | 8: ['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00', 'ffff33',
1435 | 'a65628', 'f781bf'],
1436 | 9: ['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00', 'ffff33',
1437 | 'a65628', 'f781bf', '999999']
1438 | },
1439 | Set2: {
1440 | type: 'qualitative',
1441 | cbf: 3,
1442 | 3: ['66c2a5', 'fc8d62', '8da0cb'],
1443 | 4: ['66c2a5', 'fc8d62', '8da0cb', 'e78ac3'],
1444 | 5: ['66c2a5', 'fc8d62', '8da0cb', 'e78ac3', 'a6d854'],
1445 | 6: ['66c2a5', 'fc8d62', '8da0cb', 'e78ac3', 'a6d854', 'ffd92f'],
1446 | 7: ['66c2a5', 'fc8d62', '8da0cb', 'e78ac3', 'a6d854', 'ffd92f',
1447 | 'e5c494'],
1448 | 8: ['66c2a5', 'fc8d62', '8da0cb', 'e78ac3', 'a6d854', 'ffd92f',
1449 | 'e5c494', 'b3b3b3']
1450 | },
1451 | Set3: {
1452 | type: 'qualitative',
1453 | cbf: 0,
1454 | 3: ['8dd3c7', 'ffffb3', 'bebada'],
1455 | 4: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072'],
1456 | 5: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3'],
1457 | 6: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462'],
1458 | 7: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462',
1459 | 'b3de69'],
1460 | 8: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462',
1461 | 'b3de69', 'fccde5'],
1462 | 9: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462',
1463 | 'b3de69', 'fccde5', 'd9d9d9'],
1464 | 10: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462',
1465 | 'b3de69', 'fccde5', 'd9d9d9', 'bc80bd'],
1466 | 11: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462',
1467 | 'b3de69', 'fccde5', 'd9d9d9', 'bc80bd', 'ccebc5'],
1468 | 12: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462',
1469 | 'b3de69', 'fccde5', 'd9d9d9', 'bc80bd', 'ccebc5', 'ffed6f']
1470 | }
1471 | };
1472 |
1473 | for (var name in schemes) {
1474 | var scheme = schemes[name];
1475 | scheme = palette.Scheme.fromPalettes(
1476 | 'cb-' + name, [scheme.type, 'cb-' + scheme.type], scheme, 12, scheme.cbf);
1477 | palette.register(scheme);
1478 | }
1479 | })();
1480 |
--------------------------------------------------------------------------------
/resources/public/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "asterion",
3 | "version" : "0.0-DEV",
4 | "build-commit" : "0000",
5 | "build-date" : "0000-00-00",
6 | "main" : "app.js",
7 | "private": true
8 | }
9 |
--------------------------------------------------------------------------------
/resources/public/prod.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "dev-tools": false
3 | }
4 |
--------------------------------------------------------------------------------
/scripts/build-windows-exe.nsi:
--------------------------------------------------------------------------------
1 | ;; This script generates the windows .exe file for distribution.
2 | ;; NSIS - http://nsis.sourceforge.net/Main_Page
3 |
4 | ;; HM NIS Edit Wizard helper defines
5 | !define PRODUCT_NAME "draft"
6 | ;; !define PRODUCT_VERSION "0.0" ;; This is auto-defined by the caller in the release script
7 | !define PRODUCT_WEB_SITE "https://github.com/"
8 | !define PRODUCT_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\draft.exe"
9 | !define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
10 | !define PRODUCT_UNINST_ROOT_KEY "HKLM"
11 | !define PRODUCT_STARTMENU_REGVAL "NSIS:StartMenuDir"
12 |
13 | ;; MUI 1.67 compatible ------
14 | !include "MUI.nsh"
15 |
16 | ;; MUI Settings
17 | !define MUI_ABORTWARNING
18 | !define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
19 | !define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
20 |
21 | ;; Welcome page
22 | !insertmacro MUI_PAGE_WELCOME
23 |
24 | ;; Directory page
25 | !insertmacro MUI_PAGE_DIRECTORY
26 |
27 | ;; Start menu page
28 | var ICONS_GROUP
29 | !define MUI_STARTMENUPAGE_NODISABLE
30 | !define MUI_STARTMENUPAGE_DEFAULTFOLDER "Example"
31 | !define MUI_STARTMENUPAGE_REGISTRY_ROOT "${PRODUCT_UNINST_ROOT_KEY}"
32 | !define MUI_STARTMENUPAGE_REGISTRY_KEY "${PRODUCT_UNINST_KEY}"
33 | !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "${PRODUCT_STARTMENU_REGVAL}"
34 | !insertmacro MUI_PAGE_STARTMENU Application $ICONS_GROUP
35 |
36 | ;; Instfiles page
37 | !insertmacro MUI_PAGE_INSTFILES
38 |
39 | ;; Finish page
40 | !define MUI_FINISHPAGE_RUN "$INSTDIR\draft.exe"
41 | !insertmacro MUI_PAGE_FINISH
42 |
43 | ; Uninstaller pages
44 | !insertmacro MUI_UNPAGE_INSTFILES
45 |
46 | ; Language files
47 | !insertmacro MUI_LANGUAGE "English"
48 |
49 | ; Source directory
50 | ; (auto-defined by the caller in the release script)
51 | ;; !define OUTFILE
52 | ;; !define RELEASE_DIR
53 |
54 | ; MUI end ------
55 |
56 | Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
57 | OutFile "${OUTFILE}"
58 | InstallDir "$PROGRAMFILES\Example"
59 | InstallDirRegKey HKLM "${PRODUCT_DIR_REGKEY}" ""
60 | ShowInstDetails show
61 | ShowUnInstDetails show
62 |
63 | Section "MainSection" SEC01
64 | SetOutPath "$INSTDIR"
65 | SetOverwrite ifnewer
66 | File /r "${RELEASE_DIR}\*"
67 |
68 | ;; Shortcuts
69 | !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
70 | CreateDirectory "$SMPROGRAMS\$ICONS_GROUP"
71 | CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\draft.lnk" "$INSTDIR\draft.exe"
72 | CreateShortCut "$DESKTOP\draft.lnk" "$INSTDIR\draft.exe"
73 | !insertmacro MUI_STARTMENU_WRITE_END
74 | SectionEnd
75 |
76 | Section -AdditionalIcons
77 | SetOutPath $INSTDIR
78 | !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
79 | WriteIniStr "$INSTDIR\${PRODUCT_NAME}.url" "InternetShortcut" "URL" "${PRODUCT_WEB_SITE}"
80 | CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\Website.lnk" "$INSTDIR\${PRODUCT_NAME}.url"
81 | CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\Uninstall.lnk" "$INSTDIR\uninst.exe"
82 | !insertmacro MUI_STARTMENU_WRITE_END
83 | SectionEnd
84 |
85 | Section -Post
86 | WriteUninstaller "$INSTDIR\uninst.exe"
87 | WriteRegStr HKLM "${PRODUCT_DIR_REGKEY}" "" "$INSTDIR\draft.exe"
88 | WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
89 | WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe"
90 | WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\draft.exe"
91 | WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
92 | WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
93 | SectionEnd
94 |
95 |
96 | Function un.onUninstSuccess
97 | HideWindow
98 | MessageBox MB_ICONINFORMATION|MB_OK "$(^Name) was successfully removed from your computer."
99 | FunctionEnd
100 |
101 | Function un.onInit
102 | MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Are you sure you want to completely remove $(^Name) and all of its components?" IDYES +2
103 | Abort
104 | FunctionEnd
105 |
106 | Section Uninstall
107 | !insertmacro MUI_STARTMENU_GETFOLDER "Application" $ICONS_GROUP
108 |
109 | Delete "$SMPROGRAMS\$ICONS_GROUP\Uninstall.lnk"
110 | Delete "$SMPROGRAMS\$ICONS_GROUP\Website.lnk"
111 | Delete "$DESKTOP\draft.lnk"
112 | Delete "$SMPROGRAMS\$ICONS_GROUP\draft.lnk"
113 |
114 | RMDir "$SMPROGRAMS\$ICONS_GROUP"
115 |
116 | RMDir /r "$INSTDIR"
117 |
118 | DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
119 | DeleteRegKey HKLM "${PRODUCT_DIR_REGKEY}"
120 | SetAutoClose true
121 | SectionEnd
122 |
--------------------------------------------------------------------------------
/scripts/dmg/TestBkg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bensu/asterion/847b1b152bdf368921268fe7aaf3da047f944a97/scripts/dmg/TestBkg.png
--------------------------------------------------------------------------------
/scripts/dmg/TestBkg@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bensu/asterion/847b1b152bdf368921268fe7aaf3da047f944a97/scripts/dmg/TestBkg@2x.png
--------------------------------------------------------------------------------
/scripts/setup.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | pushd "%~dp0\.."
4 |
5 | echo. & echo Installing node dependencies...
6 | call npm install --no-optional
7 |
8 | echo. & echo Installing grunt & bower...
9 | call npm install -g grunt-cli
10 | call npm install -g bower
11 |
12 | call grunt setup
13 |
14 | echo. & echo setup complete.
15 |
16 | popd
17 |
--------------------------------------------------------------------------------
/scripts/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # exit on errors
4 | set -e
5 |
6 | cd "`dirname $0`/.."
7 |
8 | echo; echo "Installing node dependencies..."
9 | if [[ "$OSTYPE" == "darwin"* ]]; then
10 | npm install
11 | else
12 | # "grunt-appdmg" is a mac-only dependency that will fail to build on linux.
13 | # So we are including it as an optionalDependency in package.json
14 | # and preventing its installation with npm's --no-optional flag.
15 | npm install --no-optional
16 | fi
17 |
18 |
19 | echo; echo "Installing grunt and bower..."
20 |
21 | npm install -g grunt-cli bower
22 |
23 | grunt setup
24 |
25 | echo; echo "Setup complete."
26 |
--------------------------------------------------------------------------------
/src/clj/asterion/deps.clj:
--------------------------------------------------------------------------------
1 | (ns asterion.deps
2 | (:import [java.util UUID]
3 | [org.apache.commons.io FileUtils]
4 | [org.eclipse.jgit.api.errors InvalidRemoteException]
5 | [org.eclipse.jgit.errors TransportException])
6 | (:require [clojure.java.io :as io]
7 | [clj-jgit.porcelain :as git]
8 | [clj-jgit.util :as git-util]
9 | [asterion.deps.clojure :as clj]
10 | [asterion.deps.javascript :as js]))
11 |
12 | ;; ======================================================================
13 | ;; Utils
14 |
15 | (defn uuid []
16 | (str (UUID/randomUUID)))
17 |
18 | (defn project-type [dir subpath]
19 | (letfn [(file-exists? [f]
20 | (.exists (io/file dir subpath f)))]
21 | (cond
22 | (file-exists? "package.json") :js
23 | (file-exists? "project.clj") :clj
24 | :else nil)))
25 |
26 | (defmulti parse-repo project-type)
27 |
28 | (defmethod parse-repo :default [_ _] {:error :project/cant-find-sources})
29 |
30 | (defmethod parse-repo :clj
31 | [dir subpath]
32 | (clj/parse-repo dir subpath))
33 |
34 | (defmethod parse-repo :js
35 | [dir subpath]
36 | (js/parse-repo dir subpath))
37 |
38 | (defn parse-url
39 | "Given a git url (string) it will fetch the repository and try to return
40 | a dependency graph for it. It may also return the following errors:
41 |
42 | - {:error :no-project-file}
43 | - {:error :project-not-found}
44 | - {:error :project-protected}
45 | - {:error #} ;; unknown exception"
46 | ([url] (parse-url url ""))
47 | ([url subpath]
48 | {:pre [(string? url)]}
49 | (let [repo-name (git-util/name-from-uri url)
50 | dir (io/file "tmp" (str (uuid) repo-name))]
51 | (try
52 | (git/git-clone-full url (.getPath dir))
53 | (parse-repo dir subpath)
54 | (catch InvalidRemoteException _
55 | {:error :project/not-found})
56 | (catch TransportException _
57 | {:error :project/protected})
58 | (catch Exception e
59 | {:error e})
60 | (finally (future (FileUtils/deleteDirectory dir)))))))
61 |
--------------------------------------------------------------------------------
/src/clj/asterion/deps/clojure.clj:
--------------------------------------------------------------------------------
1 | (ns asterion.deps.clojure
2 | (:import [java.io File])
3 | (:require [clojure.java.io :as io]
4 | [clojure.set :as set]
5 | [leiningen.core.project :as project]
6 | [clojure.tools.namespace.file :as file]
7 | [clojure.tools.namespace.find :as find]
8 | [clojure.tools.namespace.track :as track]
9 | [clojure.tools.namespace.dependency :as dep]))
10 |
11 | ;; ======================================================================
12 | ;; Helpers
13 |
14 | (defn file->ns-name [f]
15 | (-> f file/read-file-ns-decl rest first))
16 |
17 | ;; ======================================================================
18 | ;; Dependency graph
19 |
20 | (defn project->builds
21 | "Takes a project map and returns the parsed :cljsbuild config,
22 | returns nil if there are none"
23 | [project]
24 | {:post [(every? string? (keys %)) (every? coll? (vals %))]}
25 | (when-let [builds (:builds (:cljsbuild project))]
26 | (->> (cond
27 | (map? builds) builds
28 | (vector? builds) (map (fn [build] [(:id build) build]) builds)
29 | :else (throw (Exception. "Bad cljsbuild options")))
30 | (map (fn [[k v]]
31 | [(name k) (:source-paths v)]))
32 | (into {}))))
33 |
34 | (defn parse-project
35 | "Takes a directory and returns the Clojure Sources for it"
36 | [^File project-dir]
37 | (let [f (io/file project-dir "project.clj")
38 | _ (assert (.exists f) "project.clj not found")
39 | project (project/read-raw (.getPath f)) ]
40 | (->> {"clj" (:source-paths project)}
41 | (merge (project->builds project))
42 | (map (fn [[k v]]
43 | [k (mapv (partial io/file project-dir) v)]))
44 | (into {}))))
45 |
46 | (defn platform->ext [platform]
47 | (get {:clj find/clj
48 | :cljs find/cljs
49 | :cljc (update find/clj :extensions (partial cons ".cljs"))}
50 | platform
51 | find/clj))
52 |
53 | (defn depgraph
54 | "Constructs a graph - {:nodes [{:name foo.bar}]
55 | :edges [{:source foo.bar :target foo.baz}]}
56 | from a set of source-paths."
57 | ([srcs] (depgraph nil srcs))
58 | ([platform srcs]
59 | (let [exts (platform->ext platform)
60 | source-fs (apply set/union
61 | (map #(find/find-sources-in-dir % exts) srcs))
62 | dep-graph ((file/add-files {} source-fs) ::track/deps)
63 | ns-names (set (map (comp second file/read-file-ns-decl) source-fs))
64 | part-of-project? (partial contains? ns-names)
65 | nodes (filter part-of-project? (reverse (dep/topo-sort dep-graph)))
66 | edges (->> nodes
67 | (mapcat #(->> (filter part-of-project?
68 | (dep/immediate-dependencies dep-graph %))
69 | (map (partial vector %)))))]
70 | {:edges (mapv (fn [[from to]] {:source from :target to}) edges)
71 | :nodes (->> nodes
72 | (map-indexed (fn [i n] [n i]))
73 | (into {})
74 | (sort-by second)
75 | (mapv (fn [[n i]] {:name (str n)})))})))
76 |
77 | (defn parse-repo [dir subpath]
78 | (try
79 | (->>
80 | (parse-project (io/file dir subpath))
81 | (map (fn [[id srcs]]
82 | [id (depgraph (if (= "clj" id) :clj :cljs) srcs)]))
83 | (into {}))
84 | (catch java.lang.AssertionError _
85 | {:error :project/no-project-file})
86 | (catch clojure.lang.ExceptionInfo e
87 | {:error :project/circular-dependency})))
88 |
--------------------------------------------------------------------------------
/src/clj/asterion/deps/javascript.clj:
--------------------------------------------------------------------------------
1 | (ns asterion.deps.javascript
2 | (:require [clojure.set :as set]
3 | [clojure.string :as str]
4 | [clojure.java.io :as io]
5 | [clojure.java.shell :refer [sh]]
6 | [clojure.data.json :as json]
7 | [graph.util :as graph]))
8 |
9 | (defn maybe-conj [coll v]
10 | (cond-> coll
11 | (some? v) (conj v)))
12 |
13 | (defn rename-file [f]
14 | (str/replace f #"/" "."))
15 |
16 | (defn module-name [n]
17 | (last (str/split n #"/")))
18 |
19 | (defn normalize-graph
20 | "Transforms all the module names into full paths
21 | and removes the outside dependencies"
22 | [g]
23 | (let [node-names (set (keys g))
24 | module-names (zipmap (map module-name node-names)
25 | node-names)
26 | in-project #(or (node-names %) (get module-names %))]
27 | (zipmap node-names
28 | (map #(remove nil? (mapv in-project %)) (vals g)))))
29 |
30 |
31 | (defn parse-repo
32 | [dir subpath]
33 | (with-open [rdr (io/reader (io/file dir subpath "package.json"))]
34 | (let [package-json (json/read rdr)
35 | srcs (-> #{}
36 | (set/union (set (get package-json "files")) #{"src" "lib"})
37 | (maybe-conj (get-in package-json ["directories" "lib"])))
38 | srcs-with-root (->> srcs
39 | (map (partial io/file dir subpath))
40 | (filter #(.exists %))
41 | (map #(.getPath %)))]
42 | (assert (every? some? srcs-with-root) "Paths can't be nil")
43 | (let [g (-> (apply sh "madge" "-j" srcs-with-root)
44 | :out
45 | json/read-str
46 | normalize-graph
47 | graph/clear-disj-nodes)
48 | node-names (keys g)]
49 | {"js" {:nodes (mapv (comp (partial hash-map :name) rename-file)
50 | node-names)
51 | :edges (->> node-names
52 | (mapcat (fn [n]
53 | (->> (get g n)
54 | (map (fn [e]
55 | {:source (rename-file n)
56 | :target (rename-file e)}))
57 | (remove nil?))))
58 | vec)}}))))
59 |
--------------------------------------------------------------------------------
/src/clj/asterion/server.clj:
--------------------------------------------------------------------------------
1 | (ns asterion.server
2 | (:require [clojure.string :as str]
3 | [clojure.java.io :as io]
4 | [com.stuartsierra.component :as component]
5 | [ring.middleware.params :as params]
6 | [ring.util.response :as response]
7 | [ring.adapter.jetty :as jetty]
8 | [compojure.core :refer :all]
9 | [compojure.route :as route]
10 | [compojure.handler :as handler]
11 | [asterion.deps :as deps]))
12 |
13 | ;; ======================================================================
14 | ;; Utils
15 |
16 | (defn ->url [user repo]
17 | (str "https://github.com/" user "/" repo ".git"))
18 |
19 | (defn error-response [error]
20 | {:status 500
21 | :headers {"Content-Type" "application/edn"}
22 | :body (pr-str error)})
23 |
24 | (defn ok-response [body]
25 | {:status 200
26 | :headers {"Content-Type" "application/edn"}
27 | :body (pr-str body)})
28 |
29 | ;; ======================================================================
30 | ;; Cache
31 |
32 | (def cache-root "cached")
33 |
34 | (defn cache-path [user repo]
35 | (str (str/join "/" [cache-root user repo]) ".edn") )
36 |
37 | (defn spit-graph
38 | "Writes to disk the results"
39 | [{:keys [user repo graph] :as data}]
40 | {:pre [(every? some? (vals data))]}
41 | (let [p (str "resources/public/" (cache-path user repo))
42 | parent (.getParentFile (io/file p))]
43 | ;; ensure directory
44 | (when-not (.exists parent)
45 | (.mkdirs parent))
46 | (spit p data)
47 | p))
48 |
49 | (defn cache!
50 | ([user repo]
51 | (cache! user repo ""))
52 | ([user repo subpath]
53 | (spit-graph {:user user
54 | :repo repo
55 | :graphs (deps/parse-url (->url user repo) subpath)})))
56 |
57 | (defn cached
58 | "Returns a path if the graph for the repo is in cache, otwherwise nil"
59 | [user repo]
60 | (when-let [c (some-> (cache-path user repo) io/resource io/file)]
61 | (when (.exists c)
62 | (.getPath c))))
63 |
64 | (defn repo-handler [user repo]
65 | (try
66 | (if-let [cache-path (cached user repo)]
67 | (response/file-response cache-path)
68 | (let [graphs (deps/parse-url (->url user repo))]
69 | (if (contains? graphs :error)
70 | (error-response graphs)
71 | (do
72 | (future (spit-graph {:user user :repo repo :graphs graphs}))
73 | (ok-response {:graphs graphs})))))
74 | (catch Exception e
75 | (error-response {:error e}))))
76 |
77 | (defroutes app-routes
78 | (HEAD "/" [] "")
79 | (GET "/" [] (response/redirect "index.html"))
80 | (GET "/repo/:user/:repo" [user repo]
81 | (if (and (string? user) (string? repo))
82 | (repo-handler user repo)
83 | (error-response (str "Bad params: " user repo))))
84 | (route/resources "/")
85 | (route/files "/")
86 | (route/not-found "404 - Page not found
"))
87 |
88 | (def app-handler
89 | (-> app-routes
90 | handler/site))
91 |
92 | (defn start-jetty [handler port]
93 | (jetty/run-jetty handler {:port (Integer. port) :join? false}))
94 |
95 | (defrecord Server [port jetty]
96 | component/Lifecycle
97 | (start [component]
98 | (println "Start server at port " port)
99 | (assoc component :jetty (start-jetty app-handler port)))
100 | (stop [component]
101 | (println "Stop server")
102 | (when jetty
103 | (.stop jetty))
104 | component))
105 |
106 | (def new-system (Server. 3000 nil))
107 |
--------------------------------------------------------------------------------
/src/cljc/graph/util.cljc:
--------------------------------------------------------------------------------
1 | (ns graph.util
2 | (:require [clojure.set :as set]))
3 |
4 | (defn useful-nodes [g]
5 | (loop [useful-nodes #{}
6 | nodes (keys g)]
7 | (if-let [node (first nodes)]
8 | (let [edges (set (get g node))]
9 | (recur (cond-> (set/union useful-nodes edges)
10 | (not (empty? edges)) (conj node))
11 | (rest nodes)))
12 | useful-nodes)))
13 |
14 | (defn clear-disj-nodes [g]
15 | (let [disj-nodes (set/difference (set (keys g))
16 | (useful-nodes g))]
17 | (apply dissoc g disj-nodes)))
18 |
--------------------------------------------------------------------------------
/src/cljs/asterion/analytics.cljs:
--------------------------------------------------------------------------------
1 | (ns asterion.analytics)
2 |
3 | (defn repo-event! [event data]
4 | (js/ga "send" "event" "Repositories" event data))
5 |
6 | (defn nav-event! [event data]
7 | (js/ga "send" "event" "Nav" event data))
8 |
--------------------------------------------------------------------------------
/src/cljs/asterion/click.cljs:
--------------------------------------------------------------------------------
1 | (ns asterion.click
2 | "Provides a listener for click elements, click-outside"
3 | (:import [goog.events EventType]
4 | [goog.events Event])
5 | (:require [goog.dom :as gdom]
6 | [goog.events :as events]))
7 |
8 | (defn- click-handler [in? sel f e]
9 | {:pre [(coll? sel) (fn? f)]}
10 | (let [t (.-target e)
11 | ff (if in? identity not)]
12 | (when (ff (some (partial gdom/getAncestorByClass t) sel))
13 | ;; We want to prevent the "in" element from handling it
14 | (when (ff true)
15 | (Event.stopPropagation e)
16 | (Event.preventDefault e))
17 | (f e)
18 | nil)))
19 |
20 | (defn install! [in? sel f]
21 | (events/listen js/window EventType.CLICK
22 | (partial click-handler in? sel f) true))
23 |
24 | (defn install-out!
25 | "Calls f with e whenever there is a click event e outside of the
26 | elements matched by the selectors in sel. Currently only classes are
27 | supported as selectors.
28 | Ex: sel = [\"some-class\" \"some-other-class\"]
29 |
30 | would match all clicks outside elements with those classes."
31 | [sel f]
32 | (install! false sel f))
33 |
34 | (defn install-in!
35 | "Calls f with e whenever there is a click event e inside of the
36 | elements matched by the selectors in sel. Currently only classes are
37 | supported as selectors.
38 | Ex: sel = [\"some-class\" \"some-other-class\"]
39 |
40 | would match all clicks outside elements with those classes."
41 | [sel f]
42 | (install! true sel f))
43 |
44 | (defn uninstall! [key]
45 | (events/unlistenByKey key))
46 |
--------------------------------------------------------------------------------
/src/cljs/asterion/components.cljs:
--------------------------------------------------------------------------------
1 | (ns asterion.components
2 | (:require [om.core :as om :include-macros true]
3 | [om.dom :as dom :include-macros true]
4 | [asterion.analytics :refer [nav-event!]]))
5 |
6 | (defn icon-button [{:keys [icon-class title]} owner {:keys [click-fn]}]
7 | (om/component
8 | (dom/i #js {:className (str icon-class " fa clickable")
9 | :title title
10 | :onClick (if (fn? click-fn)
11 | click-fn
12 | identity)})))
13 |
14 | (defn link
15 | ([href] (link href href))
16 | ([href label] (link label href ""))
17 | ([href label class]
18 | (dom/a #js {:className (str "file-item__text " class)
19 | :onClick (fn [_]
20 | (nav-event! "link-out" href))
21 | :target "_blank"
22 | :href href}
23 | label)))
24 |
25 | (def form-url
26 | "https://docs.google.com/forms/d/19FW2TpxN3FmtJ6drU5N6Ia8g3v_yhr1VuVq5sa74dZ4/viewform")
27 |
28 | (defn forms-link [label]
29 | (dom/a #js {:className (str "file-item__text file--activate")
30 | :onClick (fn [_]
31 | (nav-event! "link-out" "to-form"))
32 | :target "_blank"
33 | :href form-url}
34 | label))
35 |
--------------------------------------------------------------------------------
/src/cljs/asterion/core.cljs:
--------------------------------------------------------------------------------
1 | (ns asterion.core
2 | (:import [goog.dom ViewportSizeMonitor])
3 | (:require [goog.string.linkify :as links]
4 | [goog.events :as events]
5 | [clojure.string :as str]
6 | [cljs.reader :as reader]
7 | [ajax.core :refer [GET POST]]
8 | [om.core :as om :include-macros true]
9 | [om.dom :as dom :include-macros true]
10 | [asterion.d3]
11 | [asterion.deps :as deps]
12 | [asterion.tree :as tree]
13 | [asterion.project :as project]
14 | [asterion.components :as components]
15 | [asterion.analytics :refer [repo-event! nav-event!]]
16 | [asterion.click :as click]))
17 |
18 | ;; ======================================================================
19 | ;; Model
20 |
21 | (def init-state {:nav {:ns ""
22 | :highlight "" ;; can be local state
23 | :open? false
24 | :build nil
25 | :highlighted #{}}
26 | :buffer {}
27 | :graphs []
28 | :errors #{}
29 | :project {:url ""
30 | :user nil ;; string
31 | :repo nil ;; string
32 | :srcs #{}
33 | :platform nil}})
34 |
35 | (defonce app-state (atom init-state))
36 |
37 | ;; ======================================================================
38 | ;; Update
39 |
40 | (def ->form
41 | [(dom/p nil "We couldn't find the repository. ")
42 | (dom/p nil "Do you want to graph a "
43 | (components/forms-link "private repository?"))])
44 |
45 | (def error->msg*
46 | {:graph/empty-nodes "We found nothing to graph after filtering"
47 | :project/parse-error "We couldn't read your project.clj"
48 | :project/not-found "We couldn't find the repository"
49 | :project/protected ->form
50 | :project/cant-find-sources "We couldn't find your package.json/project.clj"
51 | :project/timeout "It took too long to talk to the server. We don't know what happened!"
52 | :project/no-project-file "We couldn't find a project.clj in the repository's top level directory."
53 | :project/invalid-url "The given url is invalid. Try copy-pasting from the url bar, ex: https://github.com/juxt/yada"
54 | :project/circular-dependency "The project contains a circular dependency, probably a cljs file requiring a clj macro file with the same name. This is a bug, we'll fix it soon!"
55 | :nav/search-error "There was a problem while searching"
56 | :nav/search-not-found "No matches found"
57 | :unknown-error "We are truly sorry but we don't know what happened."})
58 |
59 | (defn error->msg [error]
60 | (if (string? error)
61 | error
62 | (error->msg* error)))
63 |
64 | (defn update-state [data [tag msg]]
65 | (case tag
66 | :graph/add (let [build-id (or (first (keys msg)) "clj")]
67 | (-> data
68 | (assoc :graphs msg)
69 | (assoc-in [:nav :build] build-id)
70 | (update-state [:nav/graph->buffer (get msg build-id)])
71 | (update-state [:nav/draw! nil])))
72 |
73 | :project/data (update data :project #(merge % msg))
74 |
75 | :project/url (assoc-in data [:project :url] msg)
76 |
77 | :project/wait (assoc data :waiting? true)
78 |
79 | :project/done (assoc data :waiting? false)
80 |
81 | :project/invalid-url (update data :errors #(conj % :project/invalid-url))
82 |
83 | :project/clear init-state
84 |
85 | :nav/new-build (-> data
86 | (assoc-in [:nav :build] msg)
87 | (update-state [:nav/graph->buffer (get (:graphs data) msg)])
88 | (update-state [:nav/draw! nil]))
89 |
90 | :nav/toggle-builds (update-in data [:nav :open?] not)
91 |
92 | :nav/open-help (do (nav-event! "open-help" nil)
93 | (assoc data :overlay? true))
94 |
95 | :nav/close-help (do (nav-event! "close-help" nil)
96 | (assoc data :overlay? false))
97 |
98 | :nav/graph->buffer (assoc data :buffer msg)
99 |
100 | :nav/ns (assoc-in data [:nav :ns] msg)
101 |
102 | :nav/highlight (assoc-in data [:nav :highlight] msg)
103 |
104 | :nav/clear-highlighted (-> data
105 | (assoc-in [:nav :highlighted] #{})
106 | (update-state [:nav/draw! nil]))
107 |
108 | :nav/add-error (update data :errors #(conj % msg))
109 |
110 | :nav/clear-errors (assoc data :errors #{})
111 |
112 | :nav/draw! (let [g (-> (get (:graphs data) (:build (:nav data)))
113 | (deps/filter-graph (str/split (:ns (:nav data)) " "))
114 | (deps/highlight-graph (:highlighted (:nav data))))]
115 | (if (deps/valid-graph? g)
116 | (update-state data [:nav/graph->buffer g])
117 | (update-state data [:nav/add-error :graph/empty-nodes])))
118 |
119 | ;; If action is unknonw, return data unchanged
120 | data))
121 |
122 | ;; TODO: support raising several events sequentially
123 | (defn raise! [data tag msg]
124 | {:pre [(keyword? tag) (om/cursor? data)]}
125 | (om/transact! data #(update-state % [tag msg])))
126 |
127 | ;; ======================================================================
128 | ;; Sources Screen
129 |
130 | (defn help-button [data owner]
131 | (om/component
132 | (om/build components/icon-button
133 | {:title "Help & About"
134 | :icon-class "fa-question float-bottom-corner help-btn"}
135 | {:opts {:click-fn (fn [_]
136 | (raise! data :nav/open-help nil))}})))
137 |
138 | (defn close-button [data owner]
139 | (om/component
140 | (om/build components/icon-button
141 | {:title "Close Help"
142 | :icon-class "fa-times float-right-corner clear-btn"}
143 | {:opts {:click-fn (fn [_]
144 | (raise! data :nav/close-help nil))}})))
145 |
146 | (defn clear-button [data owner]
147 | (om/component
148 | (om/build components/icon-button
149 | {:title "Clear Project"
150 | :icon-class "fa-reply float-right-corner clear-btn"}
151 | {:opts {:click-fn (fn [_]
152 | (nav-event! "clear-repo" (:url (:project data)))
153 | (raise! data :project/clear nil))}})))
154 |
155 | (defn error-card [{:keys [error] :as data} owner {:keys [close-fn class]}]
156 | (om/component
157 | (dom/div #js {:className class}
158 | (om/build components/icon-button
159 | {:icon-class "fa-times float-right-corner clear-btn"
160 | :title "Dismiss"}
161 | {:opts {:click-fn close-fn}})
162 | (dom/h3 #js {:className "blue-box__subtitle"
163 | :title "A veces me equivoco y nos reimos buenamente los dos"}
164 | (:title error))
165 | (cond
166 | (string? (:msg error)) (dom/p nil (:msg error))
167 | :else (apply dom/div nil (:msg error))))))
168 |
169 |
170 | ;; ======================================================================
171 | ;; Project Screen
172 |
173 | (def examples-with-path
174 | [["https://github.com/bhauman/lein-figwheel" "sidecar"]
175 | ["https://github.com/aphyr/jepsen" "jepsen"]
176 | ["https://github.com/emezeske/lein-cljsbuild" "support"]])
177 |
178 | (def examples
179 | ["https://github.com/clojure/clojurescript"
180 | "https://github.com/circleci/frontend"
181 | "https://github.com/tonsky/datascript"
182 | "https://github.com/ztellman/manifold"
183 | "https://github.com/aphyr/riemann"
184 | "https://github.com/onyx-platform/onyx"
185 | "https://github.com/metabase/metabase"
186 | "https://github.com/Engelberg/instaparse"
187 | "https://github.com/overtone/overtone"
188 | "https://github.com/juxt/yada"])
189 |
190 | (defn error-handler [data err]
191 | (raise! data :project/done nil)
192 | (let [e (try
193 | (if (= :timeout (:failure err))
194 | :project/timeout
195 | (let [res (reader/read-string (:response err))]
196 | (if (keyword? (:error res))
197 | (:error res)
198 | :unknown-error)))
199 | (catch :default e
200 | :unknown-error))]
201 | (repo-event! "receive-error" (clj->js e))
202 | (raise! data :nav/add-error e)))
203 |
204 | (defn handler [data res]
205 | (repo-event! "receive-cached" (:url (:project data)))
206 | (raise! data :project/done nil)
207 | (raise! data :graph/add (:graphs (reader/read-string res))))
208 |
209 | ;; TODO: this function does the unexpected when the repo *does*
210 | ;; contain ".git" in the name!
211 | (defn remove-git-ext [^string url]
212 | (str/replace url #"\.git$" ""))
213 |
214 | (defn parse-url [url]
215 | (let [url' (links/findFirstUrl url)]
216 | (when-not (empty? url')
217 | (let [tokens (take-last 2 (str/split (remove-git-ext url') "/"))]
218 | (when (and (every? (comp not empty?) tokens)
219 | (not (re-find #"github" (first tokens))))
220 | tokens)))))
221 |
222 | (defn start! [data e]
223 | (raise! data :nav/clear-errors nil)
224 | (let [url (:url (:project data))]
225 | (repo-event! "start" url)
226 | (if-let [[user repo] (parse-url url)]
227 | (do
228 | (raise! data :project/wait nil)
229 | (raise! data :project/data {:user user :repo repo})
230 | (GET (str "/cached/" user "/" repo ".edn")
231 | {:handler (partial handler data)
232 | :error-handler
233 | (fn [err]
234 | (if (= 404 (:status err))
235 | (GET (str "/repo/" user "/" repo)
236 | {:handler (partial handler data)
237 | :error-handler (partial error-handler data)})
238 | (error-handler data err)))}))
239 | (do
240 | (repo-event! "invalid" url)
241 | (raise! data :project/invalid-url url)))))
242 |
243 | (defn button [label f]
244 | (dom/div #js {:className "btn--green"
245 | :onClick f}
246 | label))
247 |
248 | (defn project-name [data]
249 | (let [project (:project data)]
250 | (when-let [repo (:repo project)]
251 | (when-let [user (:user project)]
252 | (str user "/" repo)))))
253 |
254 | (defn example-link [link owner]
255 | (om/component
256 | (dom/li #js {:className "file-item"}
257 | (components/link link))))
258 |
259 | (defn overlay [data owner]
260 | (om/component
261 | (dom/div #js {:className "overlay"})))
262 |
263 | (defn modal [data owner]
264 | (reify
265 | om/IDidMount
266 | (did-mount [_]
267 | (om/set-state! owner :listen-click-in
268 | (click/install-out! ["about-modal"]
269 | #(raise! data :nav/close-help nil))))
270 | om/IWillUnmount
271 | (will-unmount [_]
272 | (click/uninstall! (om/get-state owner :listen-click-in)))
273 | om/IRender
274 | (render [_]
275 | (dom/div #js {:className "modal"}
276 | (dom/div #js {:className "page center-container"}
277 | (dom/div #js {:className "about-modal float-box blue-box"}
278 | (om/build close-button data)
279 | (dom/h1 #js {:className "blue-box__title"} "About")
280 | (dom/p nil "Dependency graphs can help you comunicate
281 | with your teammates, introduce people to the codebase,
282 | explore changes to the architecture, and help you
283 | enforce it during reviews.")
284 | (dom/p nil "Code analysis can be "
285 | (components/link "https://www.youtube.com/watch?v=hWhBmJJZoNM"
286 | "very useful" "file--activate")
287 | "but it is inconvienient to setup and generally ignored.
288 | Asterion aims to make code analysis more approachable.
289 | Are you interested in graphing "
290 | (components/forms-link "private repositories?"))
291 | (dom/p nil "To use the tool, make sure you are pasting a link to a
292 | Github repository that contains a project.clj file in
293 | it's top directory. For example:")
294 | (components/link "https://github.com/juxt/yada"
295 | "https://github.com/juxt/yada" "file--activate")
296 | (dom/p nil "will work, but "
297 | (components/link "https://github.com/clojure/clojure"
298 | "https://github.com/clojure/clojure")
299 | "won't because it doesn't have a project.clj. Boot projects are
300 | not yet supported.")
301 | (dom/p nil "If you have any feedback, you can find me at "
302 | (components/link "mailto:sbensu@gmail.com" "sbensu@gmail.com" "file--activate"))))))))
303 |
304 | (defn select-project [data owner]
305 | (om/component
306 | (dom/div #js {:className "page center-container whole-page"}
307 | (when (:overlay? data)
308 | (om/build overlay data))
309 | (when (:overlay? data)
310 | (om/build modal data))
311 | (om/build help-button data)
312 | (when-not (empty? (:errors data))
313 | (om/build error-card
314 | (assoc data
315 | :error {:title "Error"
316 | :msg (error->msg (first (:errors data)))})
317 | {:opts {:class "float-box error-card center"
318 | :close-fn (fn [_]
319 | (raise! data :nav/clear-errors nil))}}))
320 | (dom/div #js {:className "float-box blue-box center"}
321 | (dom/h1 #js {:className "blue-box__title"} "Asterion")
322 | (if (:waiting? data)
323 | (dom/div nil
324 | (dom/p nil (str "Processing " (or (project-name data) "...")))
325 | (dom/div #js {:className "loader"})
326 | (dom/p nil "Try other cool projects:")
327 | (apply dom/ul #js {:className "folder-list"}
328 | (->> examples
329 | (remove (partial = (:url (:project data))))
330 | shuffle
331 | (take 3)
332 | (map (partial om/build example-link))))
333 | (dom/p nil
334 | (dom/span nil "but whatever you do, don't try "
335 | (components/link "https://github.com/clojure/core.typed"
336 | "core.typed"))))
337 | (dom/div nil
338 | (dom/p nil "Make dependency graphs for Clojure projects.")
339 | (dom/p nil "Paste a link for a github repo:")
340 | (dom/input #js {:type "url"
341 | :className "blue-input"
342 | :placeholder (str "Ex: " (rand-nth examples))
343 | :value (:url (:project data))
344 | :onKeyDown (fn [e]
345 | (when (= "Enter" (.-key e))
346 | (start! data e)))
347 | :onChange (fn [e]
348 | (let [url (.. e -target -value)]
349 | (raise! data :project/url url)))})
350 | (dom/div #js {:className "center-container"}
351 | (button "Graph" (partial start! data)))))))))
352 |
353 | ;; ======================================================================
354 | ;; Graph Screen
355 |
356 | (defn nav-input [data owner {:keys [value-key placeholder title
357 | on-change on-enter]}]
358 | (om/component
359 | (dom/input #js {:className "top-break blue-input"
360 | :type "text"
361 | :title title
362 | :placeholder placeholder
363 | :value (get data value-key)
364 | :onKeyDown (fn [e]
365 | (when (= "Enter" (.-key e))
366 | (on-enter e)))
367 | :onChange (fn [e]
368 | (on-change (.. e -target -value)))})))
369 |
370 | (defn build-item [{:keys [data label]} owner]
371 | (om/component
372 | (dom/li #js {:onClick (fn [_]
373 | (raise! data :nav/new-build label)
374 | (raise! data :nav/toggle-builds nil))
375 | :className "build-item"}
376 | (dom/span #js {:className "build-name"} label))))
377 |
378 | (defn nav [data owner]
379 | (reify
380 | om/IRender
381 | (render [_]
382 | (dom/div #js {:className "float-box--side blue-box nav"}
383 | (if (empty? (:repo (:project data)))
384 | (dom/h3 #js {:className "blue-box__title"} "Asterion")
385 |
386 | (dom/div nil
387 | (dom/h3 #js {:className "project-name"} (:repo (:project data)))
388 | (when (< 1 (count (:graphs data)))
389 | (when-let [build (:build (:nav data))]
390 | (dom/div nil
391 | (dom/span #js {:className "build-title build-name"
392 | :onClick (fn [e]
393 | (raise! data :nav/toggle-builds nil))}
394 | (if (:open? (:nav data))
395 | (dom/i #js {:className "menu-icon fa fa-caret-down"})
396 | (dom/i #js {:className "menu-icon fa fa-caret-right"}))
397 | build)
398 | (when (:open? (:nav data))
399 | (apply dom/ul #js {:className "builds"}
400 | (->> (keys (:graphs data))
401 | (remove (partial = build))
402 | (map #(om/build build-item {:data data
403 | :label %}))))))))))
404 | (om/build clear-button data)
405 | (om/build nav-input (:nav data)
406 | {:opts {:on-change (partial raise! data :nav/ns)
407 | :on-enter (fn [e]
408 | (nav-event! "filter" (.. e -target -value))
409 | (raise! data :nav/draw! nil))
410 | :title "When you press Enter, I'll remove the ns with names that match these words."
411 | :value-key :ns
412 | :placeholder "filter out namespaces"}})))))
413 |
414 | (defn graph [buffer owner]
415 | (letfn [(draw! []
416 | (when-not (empty? @buffer)
417 | (tree/drawTree "#graph" (clj->js @buffer))))]
418 | (reify
419 | om/IRender
420 | (render [_]
421 | (dom/svg #js {:id "graph"}
422 | (dom/g #js {})))
423 | om/IDidUpdate
424 | (did-update [_ _ _]
425 | (draw!))
426 | om/IDidMount
427 | (did-mount [_]
428 | (let [vsm (ViewportSizeMonitor.)]
429 | (om/set-state! owner :vsm vsm)
430 | (events/listen vsm events/EventType.RESIZE (fn [e] (draw!))))
431 | (draw!))
432 | om/IWillUnmount
433 | (will-unmount [_]
434 | (.unlisten (om/get-state owner :vsm) events/EventType.RESIZE)))))
435 |
436 | ;; TODO: should show a loader
437 | (defn graph-explorer [data owner]
438 | (reify
439 | om/IRender
440 | (render [_]
441 | (dom/div #js {:className "page"}
442 | (om/build nav data)
443 | (when-not (empty? (:errors data))
444 | (om/build error-card
445 | (assoc data
446 | :error {:title "Error"
447 | :msg (error->msg (first (:errors data)))})
448 | {:opts {:class "notification float-box error-card"
449 | :close-fn (fn [_]
450 | (raise! data :nav/clear-errors nil))}}))
451 | (om/build graph (:buffer data))))))
452 |
453 | ;; ======================================================================
454 | ;; Component Dispatcher
455 |
456 | ;; TODO: should be a multimethod
457 | (defn main [data owner]
458 | (om/component
459 | (if (empty? (:graphs data))
460 | (om/build select-project data)
461 | (om/build graph-explorer data))))
462 |
463 | (defn init! []
464 | (om/root main app-state {:target (.getElementById js/document "app")}))
465 |
--------------------------------------------------------------------------------
/src/cljs/asterion/d3.cljs:
--------------------------------------------------------------------------------
1 | (ns asterion.d3
2 | (:require [cljsjs.d3]))
3 |
4 | (def d3 js/d3)
5 |
--------------------------------------------------------------------------------
/src/cljs/asterion/deps.cljs:
--------------------------------------------------------------------------------
1 | (ns asterion.deps
2 | (:require [clojure.set :as set]))
3 |
4 | ;; ======================================================================
5 | ;; Helpers
6 |
7 | (defn valid-graph? [graph]
8 | (not (empty? (:nodes graph))))
9 |
10 | (defn filter-graph
11 | "Removes from the graph the nodes and edges that match the namespaces in ns"
12 | [graph ns]
13 | (letfn [(starts-with-any? [s]
14 | (->> ns
15 | (map #(re-find (js/RegExp. %) s))
16 | (some some?)))
17 | (node-matches? [node]
18 | (starts-with-any? (name (:name node))))
19 | (edge-matches? [edge]
20 | (->> (vals (select-keys edge [:target :source]))
21 | (map (comp starts-with-any? name))
22 | (some true?)))]
23 | (-> graph
24 | (update :nodes (comp vec (partial remove node-matches?)))
25 | (update :edges (comp vec (partial remove edge-matches?))))))
26 |
27 | (defn highlight-graph
28 | "Adds :highlight true to the nodes in the graph that are also in highlighted"
29 | [graph highlighted]
30 | {:pre [(map? graph) (set? highlighted)]}
31 | (let [highlighted (set (map name highlighted))]
32 | (update graph :nodes
33 | (partial mapv #(assoc % :highlight (contains? highlighted (:name %)))))))
34 |
--------------------------------------------------------------------------------
/src/cljs/asterion/dir.cljs:
--------------------------------------------------------------------------------
1 | (ns asterion.dir
2 | (:require [cljs.node.io :as io]
3 | [om.core :as om :include-macros true]
4 | [om.dom :as dom :include-macros true]
5 | [asterion.components :as components]))
6 |
7 | (defn src?
8 | "Tries to guess if the dir-name is a source directory"
9 | [dir-name]
10 | (some? (re-find #"src" dir-name)))
11 |
12 | (defn ls->srcs [ls]
13 | (->> ls
14 | (filter :selected?)
15 | (map :name)
16 | set))
17 |
18 | (defn root->list-dir [root]
19 | (->> (io/list-dirs root)
20 | (mapv (fn [f] {:name f}))))
21 |
22 | (declare list-dir)
23 |
24 | (defn dir-item [{:keys [srcs dir]} owner {:keys [click-fn] :as opts}]
25 | (reify
26 | om/IInitState
27 | (init-state [_]
28 | {:expand? false
29 | :ls (root->list-dir (:name dir))})
30 | om/IRenderState
31 | (render-state [_ {:keys [ls expand?]}]
32 | (let [selected? (contains? srcs (:name dir))]
33 | (dom/li #js {:className "file-item"}
34 | (dom/span #js {:className (str "clickable "
35 | (if selected?
36 | "file-item__text--activated"
37 | "file-item__text"))
38 | :onClick (partial click-fn (:name dir))
39 | :title (if selected?
40 | "Unselect directory"
41 | "Select directory")}
42 | (io/file-name (:name dir)))
43 | (when-not (empty? ls)
44 | (om/build components/icon-button
45 | {:title "Expand directory"
46 | :icon-class (str "fa-chevron-down "
47 | (if expand?
48 | "expand-icon--active"
49 | "expand-icon"))}
50 | {:opts {:click-fn (fn [_]
51 | (om/update-state! owner :expand? not))}}))
52 | (dom/div #js {:className "divider"} nil)
53 | (when expand?
54 | (om/build list-dir {:srcs srcs :ls ls} {:opts opts})))))))
55 |
56 | (defn list-dir [{:keys [srcs ls]} owner opts]
57 | (om/component
58 | (apply dom/ul #js {:className "folder-list"}
59 | (map-indexed
60 | (fn [i dir]
61 | (om/build dir-item {:dir dir :srcs srcs} {:opts opts}))
62 | ls))))
63 |
--------------------------------------------------------------------------------
/src/cljs/asterion/project.cljs:
--------------------------------------------------------------------------------
1 | (ns asterion.project
2 | (:require [clojure.string :as str]
3 | [clojure.set :as set]
4 | [cljs.tools.reader.edn :as edn]))
5 |
6 | ;; if (typeof window.DOMParser !== 'undefined') {
7 | ;; return function(str) {
8 | ;; var parser = new window.DOMParser()
9 | ;; return parser.parseFromString(str, 'application/xml')
10 | ;; }
11 | ;; }
12 |
13 | (defn parse-xml [s]
14 | (let [constructor (.-DOMParser js/window)
15 | parser (constructor.)]
16 | (.parseFromString parser s "application/xml")))
17 |
18 | (enable-console-print!)
19 |
20 | (defn re-pos [re s]
21 | {:pre [(not (string? re)) (string? s)]}
22 | (let [re (js/RegExp. (.-source re) "g")]
23 | (loop [res {}]
24 | (if-let [m (.exec re s)]
25 | (recur (assoc res (.-index m) (first m)))
26 | res))))
27 |
28 | (defn extract-positions [project-string re]
29 | (->> project-string
30 | (re-pos re)
31 | keys))
32 |
33 | (defn extract-exp-at [project-string re pos]
34 | (-> project-string
35 | (subs pos)
36 | (str/replace re "")
37 | edn/read-string))
38 |
39 | (defn normalize-builds [builds]
40 | (cond
41 | (map? builds) (mapv (fn [[k v]] (assoc v :id (name k))) builds)
42 | (vector? builds) builds
43 | :else (throw (js/Error. "Builds should be maps or vectors"))))
44 |
45 | (defn parse-pom-name [pom-string]
46 | (-> (parse-xml pom-string)
47 | (.getElementsByTagName "artifactId")
48 | (aget 0)
49 | .-textContent))
50 |
51 | (defn parse-project-name [project-string]
52 | (let [re #"defproject"]
53 | (->> re
54 | (extract-positions project-string)
55 | first
56 | (extract-exp-at project-string re)
57 | name)))
58 |
59 | (comment
60 | (parse-pom-name "tools.namespace"))
61 |
62 | ;; TODO: move to test
63 | (comment
64 | (.log js/console (parse-name "(defproject komunike \"0.9.0-SNAPSHOT)"))
65 | (.log js/console (parse-name "(defproject org.omcljs/om \"0.9.0-SNAPSHOT)")))
66 |
67 | (defn parse-main-srcs [project-string]
68 | {:pre [(string? project-string)]
69 | :post [(set? %) (every? string? %)]}
70 | (let [re #":source-paths"]
71 | (->> (extract-positions project-string re)
72 | first
73 | (extract-exp-at project-string re)
74 | set)))
75 |
76 | (defn parse-builds-srcs [project-string]
77 | (let [re #":cljsbuild"]
78 | (->> (extract-positions project-string re)
79 | (map (partial extract-exp-at project-string re))
80 | ;; First :cljsbuild occurrence (mulitple
81 | ;; profiles,multiple builds)
82 | first
83 | :builds
84 | normalize-builds
85 | ;; ;; First build out all the possible builds
86 | first
87 | :source-paths
88 | set)))
89 |
90 | (defn parse [project-string]
91 | (set/union (parse-main-srcs project-string)
92 | (parse-builds-srcs project-string)))
93 |
--------------------------------------------------------------------------------
/src/cljs/asterion/ring.js:
--------------------------------------------------------------------------------
1 | goog.provide('asterion.ring');
2 |
3 | /**
4 | * Class for Ring
5 | * @param initArray
6 | * @constructor
7 | * @template T
8 | */
9 | asterion.ring.Ring = function(initArray) {
10 | /**
11 | * Internal index
12 | * @private {number}
13 | */
14 | this.index = 0;
15 | /**
16 | * Underlying array
17 | * @private {!Array.}
18 | */
19 | this.array = initArray.slice();
20 | };
21 |
22 | /**
23 | * Gets the next value in the ring
24 | **/
25 | asterion.ring.Ring.prototype.next = function() {
26 | this.index = (this.index < this.array.length) ? this.index : 0;
27 | var out = this.array[this.index];
28 | this.index = this.index + 1;
29 | return out;
30 | };
31 |
32 | /**
33 | * @param {Ring} initRing
34 | * @return {Function}
35 | */
36 | asterion.ring.memoize = function(initRing) {
37 | var dict = {};
38 | return function(val) {
39 | if (typeof dict[val] === "undefined") {
40 | dict[val] = initRing.next();
41 | };
42 | return dict[val];
43 | };
44 | };
45 |
46 |
--------------------------------------------------------------------------------
/src/cljs/asterion/tree.js:
--------------------------------------------------------------------------------
1 | goog.provide('asterion.tree');
2 |
3 | goog.require('goog.array');
4 | goog.require('asterion.d3');
5 | goog.require('asterion.ring');
6 |
7 | asterion.tree.config = function() {
8 | return {
9 | w: window.innerWidth,
10 | h: window.outerHeight,
11 | rx: 60,
12 | ry: 30,
13 | fill: d3.scale.category20()
14 | };
15 | };
16 |
17 | asterion.tree.svg = function(nodeId) {
18 | var config = asterion.tree.config();
19 | return d3.select(nodeId).attr("width", config.w).attr("height", config.h);
20 | };
21 |
22 | asterion.tree.formatNs = function(s) {
23 | return s.split(".").join("\n");
24 | };
25 |
26 | asterion.tree.colors = new asterion.ring.Ring(palette('tol-rainbow',12).reverse());
27 |
28 | asterion.tree.groupToColor = asterion.ring.memoize(asterion.tree.colors);
29 |
30 | asterion.tree.nodeToGroup = function(name) {
31 | // TODO: looking at the second token works for libraries, not for apps
32 | var parts = name.split("\.");
33 | if (parts.length > 1) {
34 | return parts[1].replace("-","");
35 | } else {
36 | return name;
37 | }
38 | };
39 |
40 | asterion.tree.nodeToSubGroup = function(name) {
41 | var tokens = name.split("\.");
42 | return (tokens.length > 2) ? tokens[1] : null;
43 | };
44 |
45 | asterion.tree.Graph = function(json) {
46 | var config = asterion.tree.config();
47 | var g = new dagreD3.graphlib.Graph().setGraph({});
48 |
49 | json.nodes.forEach(function(node) {
50 | var group = asterion.tree.nodeToGroup(node.name);
51 | var color = asterion.tree.groupToColor(group);
52 | var hgStyle = "";
53 | var labelStyle = "";
54 | if (node.highlight) {
55 | labelStyle = "fill:#f0f1eb";
56 | hgStyle = "fill:black;";
57 | }
58 | g.setNode(node.name, {
59 | label: node.name,
60 | labelStyle: labelStyle,
61 | style: "fill:#" + color + ";stroke:black;" + hgStyle});
62 | });
63 |
64 | json.edges.forEach(function(edge) {
65 | g.setEdge(edge.source, edge.target,{});
66 | });
67 | g.nodes().forEach(function(v) {
68 | var node = g.node(v);
69 | if (typeof node !== 'undefined') {
70 | node.rx = config.rx;
71 | node.ry = config.ry;
72 | }
73 | });
74 | return g;
75 | };
76 |
77 | asterion.tree.drawTree = function(nodeId, nsOpts, json) {
78 | var g = asterion.tree.Graph(nsOpts, json);
79 | var root = asterion.tree.svg(nodeId);
80 | var inner = root.select("g");
81 | var zoom = d3.behavior.zoom().on("zoom", function() {
82 | inner.attr("transform", "translate(" + d3.event.translate + ")" +
83 | "scale(" + d3.event.scale + ")");
84 | });
85 | root.call(zoom);
86 | var render = new dagreD3.render();
87 | render(inner, g);
88 |
89 | var initialScale = 0.75;
90 | zoom.translate([(root.attr("width") - g.graph().width * initialScale) / 2, 20])
91 | .scale(initialScale)
92 | .event(root);
93 | // root.attr("height", g.graph().height * initialScale + 40);
94 | };
95 |
--------------------------------------------------------------------------------
/src/cljs/cljs/node/io.cljs:
--------------------------------------------------------------------------------
1 | (ns cljs.node.io
2 | "Provides file utilities"
3 | (:require [cljs.nodejs]))
4 |
5 | (def path (js/require "path"))
6 |
7 | (def fs (js/require "fs"))
8 |
9 | (defn file->folder [p]
10 | (path.dirname p))
11 |
12 | (defn extension [p]
13 | (path.extname p))
14 |
15 | (defn join-paths [& args]
16 | (apply path.join args))
17 |
18 | (defn list-files [dir]
19 | (mapv (partial join-paths dir) (into-array (fs.readdirSync dir))))
20 |
21 | (defn dir? [d]
22 | (try
23 | (.isDirectory (fs.lstatSync d))
24 | (catch js/Object _ false)))
25 |
26 | (defn file-name [p]
27 | (.-name (path.parse p)))
28 |
29 | (defn list-dirs [dir]
30 | (vec (filter dir? (list-files dir))))
31 |
32 | (defn read-file [file]
33 | (fs.readFileSync file "utf8"))
34 |
--------------------------------------------------------------------------------