Compare commits

..

330 Commits
0.7.0 ... 0.8.6

Author SHA1 Message Date
Maxime Beauchemin
c5dead4791 v0.8.6 2016-04-07 14:26:28 -07:00
Maxime Beauchemin
d933a21216 Prettyfying an image in the docs 2016-04-07 14:26:28 -07:00
michellethomas
59169bfc96 Merge pull request #212 from airbnb/big_number_total
Adding a big number total viz type that is not a timeseries metric
2016-04-07 14:22:12 -07:00
Maxime Beauchemin
bcca840f01 Adding from __future__ imports (#288)
* Adding from __future__ imports

* fixes

* Fixing doctests

* Removing unused ColorFactory (in js now)

* linting
2016-04-07 08:39:08 -07:00
mherr
90a3b9f2c4 Update INTHEWILD.md
added #GfKDataLab as a Caravel-User
2016-04-06 23:56:19 -07:00
Maxime Beauchemin
a37e431150 Adding _images to .gitignore 2016-04-06 23:32:35 -07:00
Maxime Beauchemin
bf38c714a5 Adding missing images 2016-04-06 21:10:41 -07:00
Julian Nadeau
6b0b03e009 Fix localhost link in installation docs 2016-04-06 20:13:04 -07:00
Maxime Beauchemin
8556b098f9 Enable Time Grain Option for Redshift 2016-04-06 20:12:24 -07:00
Julian Nadeau
d122b37f5d Add python-pip to the install docs 2016-04-06 18:11:24 -07:00
Maxime Beauchemin
1756c27930 Adding upgrade instructions to docs 2016-04-06 18:02:40 -07:00
Aaron Critchley
7867267608 SqlAlchemy -> SQLAlchemy in README.md
* SqlAlchemy -> SQLAlchemy in README.md

Source: http://www.sqlalchemy.org/

* SqlAlchemy -> SQLAlchemy in tutorial.rst
2016-04-06 17:54:41 -07:00
Maxime Beauchemin
92d588694b Improving the Installation docs 2016-04-06 08:46:32 -07:00
Maxime Beauchemin
d10eaeccc9 Adding a Gallery to the docs 2016-04-06 08:46:21 -07:00
greens231
c2bb49fec5 Fix 4e6a06bad7a8_init.py migration script to work with Postgres
* Update 4e6a06bad7a8_init.py

* removed comments
2016-04-06 08:24:52 -07:00
Maxime Beauchemin
062f2b81cf Datasource dropdown in Explore view 2016-04-06 08:23:27 -07:00
andrewhn
65e72d0d07 Csv download improvements
* name + extension for generated csv and json files

* write csv index where data is meaningful
2016-04-06 08:22:49 -07:00
Maxime Beauchemin
345727635e Adding y_axis_format to DistributionBarViz 2016-04-06 08:20:24 -07:00
Maxime Beauchemin
c2baa53b06 bugfix datatables move to new package 2016-04-05 21:40:24 -07:00
Maxime Beauchemin
31758827ae + button on Slice list view redirects to Table view with alert 2016-04-05 13:33:02 -07:00
Maxime Beauchemin
81de51bf6f Minor tweaks 2016-04-04 21:39:33 -07:00
andrewhn
0d1f27dbc1 add postgres grains 2016-04-04 20:56:10 -07:00
Maxime Beauchemin
c7282882d5 Fixing the pagination display on welcome 2016-04-04 20:47:12 -07:00
Maxime Beauchemin
f9d04e8a72 Fixed refresh_datasource redirect 2016-04-04 20:43:06 -07:00
Maxime Beauchemin
bf2e804331 Removed trailing coma in Database.extra default 2016-04-04 17:42:31 -07:00
Maxime Beauchemin
c349b0a1c1 Fixed link url in docs 2016-04-04 17:14:08 -07:00
Maxime Beauchemin
4d640b5a3d [fix] panel overflowing on welcome page 2016-04-04 16:56:10 -07:00
Maxime Beauchemin
380c3f0c75 Using boostrap panels for form fieldsets in explore view 2016-04-04 16:14:55 -07:00
andrewhn
e3e8202c98 clear element before redrawing sankey 2016-04-04 16:13:54 -07:00
Maxime Beauchemin
889844407f Adding extra options to deeper configure sqlalchemy 2016-04-04 16:13:08 -07:00
Maxime Beauchemin
f1830c36cf A better welcome page 2016-04-04 16:12:28 -07:00
Chris Williams
92f73b67ca Move window.alert() calls to bootstrap modals. Also log errors to console.
* Move window.alert() calls to bootstrap modals. Also log errors that occur to console.

* move misc modal to basic template so it's available on all pages.
2016-04-04 16:11:23 -07:00
skje
9c1af66ba4 Fix ignored SQL where clauses 2016-04-04 16:03:21 -07:00
Maxime Beauchemin
2b31ab498b [hotfix] fixing json endpoint 2016-04-04 15:20:10 -07:00
Maxime Beauchemin
034fd077e1 Doc formating fix 2016-04-04 10:36:51 -07:00
Maxime Beauchemin
ca4443247e Prettyfying the Caravel on README 2016-04-03 20:08:36 -07:00
Maxime Beauchemin
6f96252e45 A logo on the navbar 2016-04-03 20:03:27 -07:00
Maxime Beauchemin
0b93fd373d [hotfix] hashing unicode in py3 2016-04-03 14:04:53 -07:00
Jiayu Liu
c3789d53b4 Removing duplicate get_table in fetch_metadata 2016-04-03 07:40:58 -07:00
Maxime Beauchemin
aec3c0b358 Fixing bug when datasource has been deleted 2016-04-03 07:37:18 -07:00
Karel Vervaeke
ef45c20558 Hash cache keys to avoid too keys being too long. Resolves #240 2016-04-03 07:33:43 -07:00
Maxime Beauchemin
10ab678fc6 Finishing up the tutorial 2016-04-02 23:34:47 -07:00
Maxime Beauchemin
87fb40ae26 Bumping version of npm up 2016-04-02 23:24:36 -07:00
Maxime Beauchemin
d245fb91b5 travis tweaks 2016-04-02 23:20:05 -07:00
Maxime Beauchemin
c60032acce Pillow with a capital P 2016-04-02 23:17:27 -07:00
Maxime Beauchemin
d2f51900f1 Adding a tutorial 2016-04-02 23:11:52 -07:00
Maxime Beauchemin
93405dc23a Clarify SQLALCHEMY_DATABASE_URI in the docs 2016-04-01 16:41:02 -07:00
Maxime Beauchemin
dafdb51f35 Adding Caravel Docker link to README 2016-04-01 16:16:21 -07:00
Maxime Beauchemin
2d0fdf7e59 Adding a CHANGELOG.md 2016-04-01 13:55:04 -07:00
Maxime Beauchemin
718de6cd50 v0.8.5 2016-04-01 13:30:12 -07:00
Maxime Beauchemin
38062f160a Adding an INTHEWILD file 2016-04-01 13:23:51 -07:00
Maxime Beauchemin
9a5a27a101 Merge pull request #234 from airbnb/pinpandas
Pin pandas, remove numpy
2016-04-01 11:52:41 -07:00
Maxime Beauchemin
481d821721 Tweaking docs 2016-04-01 08:37:19 -07:00
Maxime Beauchemin
7b111b790e Adding an OS dependencies section to the install docs 2016-04-01 08:33:28 -07:00
Maxime Beauchemin
ab92e7a94d Pin pandas, remove numpy 2016-04-01 08:04:44 -07:00
Maxime Beauchemin
22d2b7fe0f Merge pull request #229 from jmcomets/patch-1
Remove "requirements.txt" mention from README
2016-04-01 00:23:51 -07:00
Jean-Marie Comets
23319eed10 Remove "requirements.txt" mention from README
I noticed that there isn't the file `requirements.txt` was removed when moving to PyPi. Plus there's no such thing as an open-source project without an "update README" PR. ;)
2016-04-01 09:05:12 +02:00
Maxime Beauchemin
12cc064059 Merge pull request #225 from airbnb/chris/remove-random-power-units
remove power units from sankey diagram
2016-03-31 23:23:42 -07:00
Maxime Beauchemin
ffa29b3909 Merge pull request #219 from airbnb/chris/sunburst-conditional-percent
Add 'Percent of previous' to sunburst vis. Appease npm warnings for data tables and d3.layout.cloud
2016-03-31 23:23:27 -07:00
Maxime Beauchemin
ee4e3c1b98 Merge pull request #224 from cyrusstoller/typo_fix
Fixing minor typos in the readme
2016-03-31 23:21:12 -07:00
Maxime Beauchemin
ebc16bb3fa Merge pull request #214 from airbnb/kim/fix_druid_datasource_bug
Fix an installation bug.
2016-03-31 23:19:44 -07:00
Chris Williams
0e2c0ce858 remove power units from sankey diagram 2016-03-31 16:59:39 -07:00
Cyrus Stoller
807f4dd0e5 Fixing minor typos in the readme 2016-03-31 15:38:34 -04:00
Chris Williams
b87d8a0fbf change 'of previous' to 'of parent' 2016-03-31 12:26:49 -07:00
Chris Williams
0b3e2e00cc Add 'Percent of previous' to sunburst vis. Appease npm warnings for data tables and d3-sankey. 2016-03-31 12:26:49 -07:00
Maxime Beauchemin
167fb64d0c Merge pull request #218 from airbnb/redirects
Redirecting URL from previous names to caravel
2016-03-31 12:24:16 -07:00
Maxime Beauchemin
eeba80c487 Merge branch 'master' into redirects 2016-03-31 12:07:35 -07:00
Maxime Beauchemin
a3f92f687d Merge pull request #223 from thebucknerlife/master
Fixed typo in README
2016-03-31 12:05:32 -07:00
Maxime Beauchemin
a4b61e97db Merge pull request #222 from brchristian/patch-1
remove duplicate Druid.io section in README.md
2016-03-31 12:04:59 -07:00
Greg Buckner
9595dcff4d Fixed typo in README 2016-03-31 12:02:26 -07:00
brchristian
58ab4e2415 remove duplicate Druid.io section in README.md 2016-03-31 11:47:33 -07:00
Maxime Beauchemin
2b71b72065 Redirecting URL from previous names to caravel 2016-03-31 09:31:07 -07:00
Maxime Beauchemin
bd9051a168 Fixing README link 2016-03-31 08:50:47 -07:00
Maxime Beauchemin
6f63b3033f Dedupping info in README by pointing to docs 2016-03-31 08:48:34 -07:00
Kim Pham
f659caa06b Fix an installation bug. 2016-03-30 19:08:38 -07:00
Maxime Beauchemin
5b7fe2b643 Merge pull request #213 from airbnb/kim/fix_druid_datasource_bug
Fix a bug when loading DruidDatasource.
2016-03-30 17:02:16 -07:00
Kim Pham
e5553ab45e Fix lint warning. 2016-03-30 16:38:02 -07:00
Kim Pham
4a77b70046 A better fix. 2016-03-30 16:28:08 -07:00
Kim Pham
f67c6b5f46 Fix a bug when loading DruidDatasource. 2016-03-30 16:11:14 -07:00
Maxime Beauchemin
fd407424ad A few replacements related to the rename
mostly just s/mistercrunch/airbnb/g
2016-03-30 14:29:07 -07:00
Maxime Beauchemin
e1b871982e Adding docs link badge 2016-03-29 21:28:43 -07:00
Maxime Beauchemin
b164244cd8 Renaming forgotten doc file 2016-03-29 21:24:01 -07:00
Maxime Beauchemin
00226cce7f Changing image size, second try 2016-03-29 21:18:17 -07:00
Maxime Beauchemin
3a3d5cd7fb Changing image size 2016-03-29 21:16:53 -07:00
Maxime Beauchemin
c6b77206ea Adding build status badge and image to README 2016-03-29 21:14:44 -07:00
Maxime Beauchemin
d885dd34f9 Getting coveralls to work 2016-03-29 12:24:00 -07:00
Maxime Beauchemin
54fe2348fc Dummy commit to trigger travis 2016-03-29 12:00:37 -07:00
Maxime Beauchemin
6e1413d2dd Adding dev-reqs.txt to build 2016-03-29 11:59:25 -07:00
Maxime Beauchemin
0296b839ec Changing repo token for coveralls 2016-03-29 11:28:06 -07:00
Maxime Beauchemin
1c44f34490 Merge pull request #204 from airbnb/fixes
Fixing the order and coverage report for the unit tests
2016-03-29 11:24:37 -07:00
Maxime Beauchemin
10f3991e1f Merge pull request #209 from airbnb/screenshots
Fresh screenshots
2016-03-29 11:12:10 -07:00
Maxime Beauchemin
cda1bd59f9 Fresh screenshots 2016-03-29 11:11:13 -07:00
Maxime Beauchemin
8e27099866 Fixing the tests for py3 2016-03-29 10:02:15 -07:00
Maxime Beauchemin
60bce9ed59 Fixing the order and coverage report for the unit tests 2016-03-28 23:50:11 -07:00
Maxime Beauchemin
1b4e750b2a Merge pull request #206 from airbnb/caravel
Caravel
2016-03-28 23:45:38 -07:00
Maxime Beauchemin
619d35878f [dashed->caravel] Replace in files 2016-03-28 22:01:21 -07:00
Maxime Beauchemin
d48796f00e Caravel - renaming files 2016-03-28 21:53:24 -07:00
Chris Williams
5561a4932a Merge pull request #205 from airbnb/chris/sunburst-fix
fix sunburst error. add `less` to package.json
2016-03-28 17:07:51 -07:00
Chris Williams
1aee5b7801 fix sunburst error. add less to package.json because less-loader complains if it's not there. 2016-03-28 15:20:08 -07:00
Maxime Beauchemin
74c72b3ce4 Merge pull request #203 from airbnb/fixes
Fixing mysql install
2016-03-27 23:03:19 -07:00
Maxime Beauchemin
09021aacad Adding LONG to list of numeric types, updating TODO 2016-03-27 22:51:45 -07:00
Maxime Beauchemin
26c725171b Fixing mysql install 2016-03-27 22:37:04 -07:00
Maxime Beauchemin
4a0ea5fff7 Merge pull request #202 from airbnb/better_tests
Using setup.py nosetests to run tests
2016-03-27 19:55:45 -07:00
Maxime Beauchemin
301dce2dd1 Using setup.py nosetests to run tests 2016-03-27 19:15:21 -07:00
Maxime Beauchemin
6dce6df6b2 JS Lint breaks the build 2016-03-27 15:36:32 -07:00
Maxime Beauchemin
2102e04fec Adding tests for ping and health enpoint 2016-03-27 14:23:49 -07:00
Maxime Beauchemin
2397645ed4 Fix intermitent bug with npm locks 2016-03-27 14:23:49 -07:00
Maxime Beauchemin
0a8a1eda94 Merge pull request #199 from airbnb/touchups
Fix a few minor bugs
2016-03-27 08:09:08 -07:00
Maxime Beauchemin
4d2492d8fd Merge pull request #200 from airbnb/sankey
Add a sankey example
2016-03-27 08:08:36 -07:00
Maxime Beauchemin
48210d5275 Add a sankey example 2016-03-27 00:27:48 -07:00
Maxime Beauchemin
5804991b19 Fix a few minor bugs 2016-03-27 00:18:26 -07:00
Maxime Beauchemin
5be7b03ba4 [hotfix] Fix tooltip overflow to be visible outside widget 2016-03-26 21:10:41 -07:00
Maxime Beauchemin
a898c27403 Minor CSS for navbar brand to not decorate the link 2016-03-26 18:10:40 -07:00
Maxime Beauchemin
49747150ae [hotfix] merging 2 migration scripts 2016-03-26 15:15:21 -07:00
Maxime Beauchemin
fdce2aac0d Merge pull request #192 from airbnb/kim/misc_hack
Fix Druid metadata refresh.
2016-03-25 22:16:54 -07:00
Maxime Beauchemin
1c19f7fc77 Merge pull request #198 from airbnb/welcome
A welcome page
2016-03-25 22:15:28 -07:00
Maxime Beauchemin
12e20ca440 Merge pull request #197 from NiharikaRay/master
Adding a DRUID_IS_ACTIVE flag and changing nav bar
2016-03-25 22:14:46 -07:00
Maxime Beauchemin
0ccc19ff9b Merge pull request #196 from airbnb/fix_audit
Fixing issues around fk nullable=False on audit fields
2016-03-25 22:13:45 -07:00
Maxime Beauchemin
e7bed92519 [hotfix] can't back out of history after entering explore 2016-03-25 22:02:50 -07:00
Maxime Beauchemin
8c0870e6ea Linting to perfection 2016-03-25 21:55:28 -07:00
Maxime Beauchemin
c9203554e7 a welcome page 2016-03-25 18:08:33 -07:00
Kim Pham
2378fdf9ce Disable polymorphism in DruidMetric as well 2016-03-25 16:45:35 -07:00
Kim Pham
23b6bca89e Fix style warning 2016-03-25 13:49:45 -07:00
Kim Pham
7a7eb33348 Disable Datasource sql polymorphism & Fix refresh Druid metadata. 2016-03-25 13:44:35 -07:00
Niharika Ray
fee6b3fafa Adding a DRUID_IS_ACTIVE flag and changing nav bar 2016-03-25 11:26:59 -07:00
Maxime Beauchemin
0f637bdd2e Fixing issues around fk nullable=False on audit fields 2016-03-25 08:13:15 -07:00
Maxime Beauchemin
10a1eddaa7 Improving the docs build script 2016-03-24 08:08:05 -07:00
Maxime Beauchemin
21d1c0a1b5 0.8.4 2016-03-23 22:26:16 -07:00
Maxime Beauchemin
3118d1b302 Merge pull request #193 from airbnb/favstar
Adding favorites for Slices and Dashboards
2016-03-23 22:25:12 -07:00
Maxime Beauchemin
2362b5a157 Tunning tooltips 2016-03-23 22:23:29 -07:00
Maxime Beauchemin
7ede732892 Adding favorites for Slices and Dashboards 2016-03-23 22:15:23 -07:00
Maxime Beauchemin
5b10b19ed7 Fixing the docs 2016-03-23 21:41:37 -07:00
Maxime Beauchemin
14f298b385 dashboard bug fixes and better page titles 2016-03-23 21:27:45 -07:00
Maxime Beauchemin
89da51f8d7 Pointing landscape badge to the right branch 2016-03-23 16:16:20 -07:00
Maxime Beauchemin
d5487c6b25 0.8.3 2016-03-23 14:11:37 -07:00
Maxime Beauchemin
a244f3aafb Fixing build issues with preventive rm /Users/maxime_beauchemin/.npm/*.lock 2016-03-23 14:11:37 -07:00
Maxime Beauchemin
9d3bf7763c Fixing build issues with preventive rm /Users/maxime_beauchemin/.npm/*.lock 2016-03-23 11:14:29 -07:00
Maxime Beauchemin
b0f10a90ce Merge pull request #188 from airbnb/caching
Introducing a caching layer!
2016-03-22 22:55:35 -07:00
Maxime Beauchemin
417749f880 Explore mode should only expose cache on first load 2016-03-22 22:09:02 -07:00
Maxime Beauchemin
fded04a51d Hack to get the force refresh in the explore view 2016-03-22 18:11:10 -07:00
Maxime Beauchemin
d8192eca0a Introducing a caching layer 2016-03-22 18:11:10 -07:00
Maxime Beauchemin
2d3edf3a2e js lint breaks the build 2016-03-22 08:16:23 -07:00
Maxime Beauchemin
0890d22899 Merge pull request #191 from airbnb/week-grain
Add week ending and week start to grain
2016-03-21 15:56:36 -07:00
Siddharth
bad3128df9 Update grain label name 2016-03-21 15:41:49 -07:00
Siddharth
48c8a90247 Fix multi-line statement 2016-03-21 14:27:03 -07:00
Siddharth
a9ba47fe12 Add week ending and week start to grain 2016-03-21 13:45:14 -07:00
Maxime Beauchemin
f09f3d7c4b Merge pull request #190 from airbnb/vnum
Cranking up version numbers
2016-03-19 21:46:03 -07:00
Maxime Beauchemin
e17d78e196 Cranking up version numbers 2016-03-19 21:33:39 -07:00
Maxime Beauchemin
9450c12552 note about the previous name in README 2016-03-19 16:09:08 -07:00
Maxime Beauchemin
f01b41827e Adding reqs badge 2016-03-19 16:03:15 -07:00
Maxime Beauchemin
b5f4d3b515 Adding reqs badge 2016-03-19 10:30:54 -07:00
Maxime Beauchemin
28e5a87cd0 pypi badges 2016-03-19 10:16:59 -07:00
Maxime Beauchemin
10c48f04cd [renaming] 2016-03-19 10:07:18 -07:00
Maxime Beauchemin
f79aca1796 rename - 2nd phase 2016-03-18 08:52:50 -07:00
Maxime Beauchemin
be6b2fe556 [panoramix] -> [dashed] 2016-03-17 23:44:58 -07:00
Chris Williams
8f4f5b126a Merge pull request #184 from mistercrunch/chris/sunburst-fixes
sunburst improvements
2016-03-17 17:54:10 -07:00
Maxime Beauchemin
2b1dcf0d2e [hotfix] granularity fix 2016-03-17 14:54:26 -07:00
Maxime Beauchemin
987a1afbb7 [hotfix] granularity fix 2016-03-17 14:45:21 -07:00
Maxime Beauchemin
f453932589 [hotfix] druid granularity missing 2016-03-17 14:37:14 -07:00
Chris Williams
3dc9996e96 hide breadcrumbs on mouseout 2016-03-17 14:10:15 -07:00
Chris Williams
3197c4b3f9 add utility function for wrapping svg text, apply to sunburst breadcrumbs, and make colors more readable for sunburst. 2016-03-17 14:02:54 -07:00
Maxime Beauchemin
f0b2f985b4 Merge pull request #186 from mistercrunch/docstrings
Adding docstrings !
2016-03-16 22:40:50 -07:00
Maxime Beauchemin
1c9c154f0f More docstrings 2016-03-16 22:26:17 -07:00
Maxime Beauchemin
ebf55bf20d Adding docstrings ! 2016-03-16 20:40:50 -07:00
Maxime Beauchemin
3461538fed [hotfix] appbuilder modal z-index fix 2016-03-16 17:27:56 -07:00
Maxime Beauchemin
7cdb23f585 [hotfix] grouping by grain, filtering on raw expression 2016-03-16 17:04:36 -07:00
Maxime Beauchemin
73daf2f8d1 [hotfix] casting dates for presto grain functions 2016-03-16 16:42:35 -07:00
Maxime Beauchemin
ae1e66f09b Merge pull request #181 from mistercrunch/time_grain
Dynamic time granularity on any datetime column
2016-03-16 16:11:42 -07:00
Chris Williams
06080945b6 fix misc sunburst bugs from refactor. 2016-03-16 15:52:52 -07:00
Maxime Beauchemin
61ab06d640 Removing unicode from country data 2016-03-16 15:49:01 -07:00
Chris Williams
0c045a9e52 trim '0' groups from sunburst paths to show null groups. add ability to map color to sunburst groupby groups. add fancier breadcrumbs to sunburst. 2016-03-16 15:24:30 -07:00
Maxime Beauchemin
27fb810dd7 Dynamic time granularity on any datetime column 2016-03-16 15:12:16 -07:00
Chris Williams
2aa0e0dce0 Merge pull request #182 from mistercrunch/chris/modal-and-dashboard-css-fixes
more css fixes
2016-03-16 09:11:47 -07:00
Maxime Beauchemin
417b5a5e09 Adding linting note to CONTRIBUTING.md 2016-03-16 08:23:59 -07:00
Maxime Beauchemin
7c5e660bd1 Merge pull request #178 from mistercrunch/fix_audit
Allowing all extra fields in AuditMixin to be nullable
2016-03-16 08:15:12 -07:00
Chris Williams
1a58b6d441 fix overflow scroll bars on all dashboard charts. make dashboard chart control toggle interaction nicer. make sure user-stylesheets are applied last. remove ace css editor error/warning parsing. make filters look nicer in dashboards. fix some linting. 2016-03-15 11:52:26 -07:00
Maxime Beauchemin
1ab89631b9 Adding caching to TODO 2016-03-13 23:04:43 -07:00
Chris Williams
e1eb236cf4 Merge pull request #175 from mistercrunch/chris/css-fixes
refactor dashboard chart html, make several css improvements.
2016-03-13 23:03:35 -07:00
Maxime Beauchemin
95b7b9779b Allowing all extra fields in AuditMixin to be nullable 2016-03-13 21:36:40 -07:00
Chris Williams
b0fa4bc924 remove unused css 2016-03-11 12:29:41 -08:00
Chris Williams
49590e28e3 refactor dashboard chart html, make several css improvements. 2016-03-11 12:22:41 -08:00
Maxime Beauchemin
fb6a9977f7 Merge pull request #172 from mistercrunch/packaging
Fixing the python and js packaging
2016-03-10 22:33:09 -05:00
Maxime Beauchemin
9e4b38b5e6 Adjusting css for display not to flicker on dashboard load 2016-03-10 08:46:20 -05:00
Maxime Beauchemin
e2cd14d320 Fixing the python and js packaging 2016-03-10 01:35:20 -05:00
Maxime Beauchemin
370c5af425 [hotfix] fix utils.markdown function chokes on None 2016-03-09 07:34:00 -05:00
Maxime Beauchemin
e53a1cc75b Adding a TODO entry for viz selector 2016-03-08 16:53:54 -05:00
Maxime Beauchemin
91833ce16f Merge pull request #171 from mistercrunch/fix_filter
Fixing multiple refresh bug in filter_box
2016-03-08 07:29:14 -05:00
Maxime Beauchemin
829271417e Fixing bug in filter_box 2016-03-07 23:03:30 -05:00
Maxime Beauchemin
55d4ac45fa Merge pull request #169 from mistercrunch/select2-style
Fixing the look of select2 components
2016-03-07 13:46:38 -05:00
Maxime Beauchemin
d6db3a9cf1 semicolon 2016-03-07 13:46:19 -05:00
Maxime Beauchemin
c0e4aca7e4 Using dash - instead of _ on filename, making input form-control white 2016-03-06 17:48:50 -05:00
Maxime Beauchemin
9855a60013 Fixing the look of select2 components 2016-03-06 14:48:05 -05:00
Maxime Beauchemin
a53dbfcfaa Merge pull request #168 from mistercrunch/travis_npm
Getting travis to build the npm related stuff
2016-03-06 08:07:37 -05:00
Maxime Beauchemin
b2d72fbfe1 Tweaks 2016-03-05 23:43:17 -05:00
Maxime Beauchemin
78e01ab0a1 Caching node_modules 2016-03-05 23:01:39 -05:00
Maxime Beauchemin
454bb10f5a Getting travis to build the npm related stuff 2016-03-05 22:22:36 -05:00
Maxime Beauchemin
ee025b3331 Fixing REST api and unsortable columns 2016-03-05 21:50:23 -05:00
Chris Williams
1e27f03b4d Merge pull request #166 from mistercrunch/chris/css
make css theme customization easier by using less for bootstrap themes
2016-03-05 12:45:59 -08:00
Chris Williams
9095cd3bca update contributing.md to explain updated LESS/CSS customization 2016-03-05 12:41:22 -08:00
Maxime Beauchemin
bfa6d13b49 Merge pull request #163 from mistercrunch/css
Shipping with CSS templates out of the box
2016-03-04 23:38:42 -05:00
Chris Williams
d3f7bbd3f1 remove pre-compiled bootstrap themes, use npm/wepack to compile from less variables instead. also fix a few css style bugs. 2016-03-04 18:20:49 -08:00
Maxime Beauchemin
570b9f09f6 Merge pull request #164 from mistercrunch/docs
Improving the docs
2016-03-04 18:58:12 -05:00
Maxime Beauchemin
e39f7db0a3 Merge pull request #165 from mistercrunch/window_resize
Fixing window resize for explore and standalone
2016-03-04 18:57:44 -05:00
Maxime Beauchemin
9de19b169f setResizeOnWindowResize -> bindResizeToWindowResize 2016-03-04 18:56:06 -05:00
Maxime Beauchemin
722c16a6e5 Fixing window resize for explore and standalone 2016-03-04 14:47:50 -05:00
Maxime Beauchemin
5486e5cac8 barol -> tirol 2016-03-04 13:47:04 -05:00
Chris Williams
1782d8f278 Merge pull request #161 from mistercrunch/chris/eslint
Add linting to package.json, do all of the linting.
2016-03-04 10:24:06 -08:00
Maxime Beauchemin
0a8ab6c499 Shipping with a CSS template out of the box 2016-03-04 00:44:55 -05:00
Maxime Beauchemin
a7e75a2bc5 [hotfix] cosmetic fitting for nvd3 line chart overflowing 2016-03-03 22:30:22 -05:00
Maxime Beauchemin
486fb8bfb4 [hotfix] fixing the examples 2016-03-03 22:30:22 -05:00
Maxime Beauchemin
bd296720b8 Improving the docs 2016-03-02 12:19:11 -08:00
Chris Williams
68f8937a2e lint standalone.js 2016-03-02 12:03:32 -08:00
Maxime Beauchemin
8703fa9460 Merge pull request #160 from mistercrunch/fix_dashed
Fixing the dashed line when using time compare
2016-03-02 11:47:50 -08:00
Chris Williams
673f741aa6 Add linting to package.json, run 'npm run lint' to run (will try to add commit hook later). Do all of the linting... 2016-03-02 11:32:27 -08:00
Maxime Beauchemin
b61ddd0009 semicolon 2016-03-02 11:12:55 -08:00
Maxime Beauchemin
e1e1746967 Fixing the dashed line when using time compare 2016-03-02 11:12:55 -08:00
Maxime Beauchemin
b6a2521722 hotfix - forgot to add standalone.js 2016-03-02 10:57:36 -08:00
Maxime Beauchemin
309a6dcb1d Merge pull request #159 from mistercrunch/standalone
Fixing the standalone mode
2016-03-02 00:28:50 -08:00
Maxime Beauchemin
51797ca9a5 Fixing the standalone mode 2016-03-01 18:22:41 -08:00
Maxime Beauchemin
2872c6262d Merge pull request #158 from mistercrunch/refactor
Refactor
2016-03-01 17:09:52 -08:00
Maxime Beauchemin
b44d48c724 Using vim-jsbeautify 2016-03-01 15:56:31 -08:00
Maxime Beauchemin
a68d7428c3 Javascript refactor 2016-03-01 15:51:06 -08:00
Maxime Beauchemin
e434cbe268 Better filtering 2016-03-01 15:50:09 -08:00
Maxime Beauchemin
367ca336cc Merge pull request #154 from mistercrunch/fix_dates
Digging into leap year bug and improvming tests
2016-03-01 14:35:10 -08:00
Maxime Beauchemin
4fe89a3811 Pinning parsedatetime to 2.0 2016-03-01 14:23:54 -08:00
Maxime Beauchemin
8c525b7b8f Hotfix nvd3's secondary xaxis uses the wrong formating 2016-03-01 13:44:06 -08:00
Chris Williams
b36b1ef05a Merge pull request #157 from mistercrunch/chris/copy-short-url
add button to auto-copy short URLs in /explore page
2016-03-01 12:10:11 -08:00
Chris Williams
88f9442297 add button to auto-copy short URLs in /explore page 2016-03-01 11:40:13 -08:00
Maxime Beauchemin
32cd9f5e73 Updating TODO.md 2016-03-01 07:47:13 -08:00
Maxime Beauchemin
eccf9c2ac7 [hotfix] saving as new bugfix 2016-02-29 22:36:44 -08:00
Maxime Beauchemin
8b0f2afc0a Merge pull request #149 from mistercrunch/immune_to_filter
Allowing to make certain widgets immune to filter
2016-02-29 20:06:39 -08:00
Maxime Beauchemin
be4a7a4077 Linting 2016-02-29 18:10:33 -08:00
Maxime Beauchemin
69cfcabb72 Making Dashboard look better 2016-02-29 16:55:59 -08:00
Maxime Beauchemin
3a38c601e0 Fixing tooltips 2016-02-29 15:55:54 -08:00
Maxime Beauchemin
22327e204f Transfering all the json in one point 2016-02-29 15:55:54 -08:00
Maxime Beauchemin
bd06f995ee Allowing to make certain widgets immune to filter 2016-02-29 15:55:54 -08:00
Maxime Beauchemin
9f809e9f8b hotfix, readme pointing to the right landscape branch 2016-02-29 14:59:49 -08:00
Maxime Beauchemin
34709c8846 Digging into leap year bug and improvming tests 2016-02-29 14:55:21 -08:00
Maxime Beauchemin
bb46887668 Merge pull request #151 from mistercrunch/lint
Linting
2016-02-29 14:35:56 -08:00
Maxime Beauchemin
74b32ac993 Merge pull request #153 from tay/patch-1
Improve README
2016-02-29 12:30:18 -08:00
Fiona Tay
ca124f271b Improve README
Fix typo
Link to the actual Contributing file
2016-02-29 12:04:39 -08:00
Maxime Beauchemin
0a842598df More linting 2016-02-29 00:32:52 -08:00
Maxime Beauchemin
cb46a61728 Linting 2016-02-29 00:04:37 -08:00
Maxime Beauchemin
8bf629c0b2 [hotfix] fixing explore view 2016-02-28 15:32:25 -08:00
Maxime Beauchemin
63b4f56c6a Merge pull request #139 from mistercrunch/chris/npm-ify2
NPMification & Reactification
2016-02-28 15:19:04 -08:00
Maxime Beauchemin
d40664bca7 Merge pull request #147 from mistercrunch/npm
Tackling Featured Datasets
2016-02-28 15:14:43 -08:00
Maxime Beauchemin
bc4182060a Rolling back to previous theme until we polish bootswarch.paper 2016-02-28 08:12:00 -08:00
Maxime Beauchemin
58c7b8c436 Removing js_files and css_files entries from viz.py 2016-02-27 09:02:39 -08:00
Maxime Beauchemin
c304cc493a Moving content of assets/readme.md to contributing.md 2016-02-27 07:47:56 -08:00
Maxime Beauchemin
4f4c67ce15 Moving html assets back to flask template folder 2016-02-27 07:33:06 -08:00
Maxime Beauchemin
c929b2a9c9 npmify datamaps dependency 2016-02-27 07:27:51 -08:00
Maxime Beauchemin
fd0e680cf0 Cleaning up assets/vendors 2016-02-26 18:35:59 -08:00
Maxime Beauchemin
70e1aa37e3 Adding missing fonts 2016-02-25 22:38:48 -08:00
Maxime Beauchemin
e4e7b911c5 Fixing dashboard.html 2016-02-25 21:15:03 -08:00
Maxime Beauchemin
d8409c1e0a Fixing table and pivot_table 2016-02-25 17:44:23 -08:00
Maxime Beauchemin
8dcd5e0628 Tackling Featured Datasets 2016-02-24 17:44:22 -08:00
Maxime Beauchemin
d2bb9aaf96 Merge pull request #148 from tay/patch-1
Fix typo
2016-02-24 17:38:52 -08:00
Fiona Tay
7c96a2eddf Fix typo 2016-02-24 17:19:44 -08:00
Chris Williams
71b11117d7 Merge pull request #145 from mistercrunch/npm
Moving files around ... yay!
2016-02-24 16:35:40 -08:00
Maxime Beauchemin
e70542d743 NPMify sql.html 2016-02-24 16:22:32 -08:00
Maxime Beauchemin
171301b6cf Moving files around 2016-02-24 15:12:51 -08:00
Chris Williams
3e834603fc Remove base.entry.js, declare all JS dependencies in explore using npm. Pin older select2 version to enable select2Sortable. 2016-02-24 13:30:42 -08:00
Maxime Beauchemin
71073d8f83 Merge pull request #142 from mistercrunch/npm
A few cosmetic fixes (nvd3 tooltips, buttons, tables)
2016-02-24 11:47:21 -08:00
Maxime Beauchemin
df8ea5287b Adjustments based on comment 2016-02-24 11:43:21 -08:00
Maxime Beauchemin
9552d09b85 A few cosmetic fixes (nvd3 tooltips, buttons, tables) 2016-02-24 11:30:01 -08:00
Maxime Beauchemin
dbb9f66202 Merge pull request #141 from mistercrunch/npm
A simple base template for npm
2016-02-24 09:48:24 -08:00
Maxime Beauchemin
d582efe76b Fixing 2 links 2016-02-24 09:45:30 -08:00
Maxime Beauchemin
f5e0ed7239 A simple base template for npm 2016-02-23 17:48:13 -08:00
Chris Williams
79e6fc986d huge npm refactor of all visualizations. table views are broken due to broken shimming for jquery/jquery-ui, dashboards are broken due to refactor of explore views. 2016-02-23 17:00:08 -08:00
Maxime Beauchemin
523b6d815a Merge pull request #140 from dayzzz/segment_selection
use the latest segment to extract metadata
2016-02-22 22:27:07 -08:00
Hongbo Zeng
5c38fc731a use the latest segment to extract metadata 2016-02-22 16:44:29 -08:00
Chris Williams
af34549377 partial refactor of explore page, visualizations not managed with webpack yet. 2016-02-19 15:12:25 -08:00
Chris Williams
0eb3dcd81f Update readme.md 2016-02-19 12:07:19 -08:00
Chris Williams
f40c024fda Add npm package.json and setup webpack to transpile ES6/JSX and compile JS files for frontend refactor. See readme.md in assets/ for npm setup instructions and visit the panoramix home page for a React sandbox. 2016-02-19 11:39:50 -08:00
Maxime Beauchemin
20bd4ca0eb Merge pull request #136 from mistercrunch/barbar
Improved the bar char to allow for dimensional breakdowns
2016-02-15 20:26:12 -08:00
Maxime Beauchemin
7e60b14448 Trying to fix a unicode bug with presto 2016-02-15 20:25:23 -08:00
Maxime Beauchemin
e42d1a1fc5 Improved the bar char to allow for dimensional breakdowns 2016-02-15 20:10:33 -08:00
Maxime Beauchemin
6f4397c7d7 Fixing druid metadata import 2016-02-12 15:47:23 -08:00
Maxime Beauchemin
1f41ce3a7d CLI adding refresh_druid command 2016-02-12 15:25:42 -08:00
Maxime Beauchemin
d970e896ad bugfix - Cluster -> DruidCluster 2016-02-12 00:49:43 -08:00
Maxime Beauchemin
5686dc0865 Bugfix Column -> DruidColumn 2016-02-12 00:45:09 -08:00
Maxime Beauchemin
80651a5dc2 More logging 2016-02-12 00:32:15 -08:00
Maxime Beauchemin
2c4396882e More verbose druid metadata refresh 2016-02-12 00:01:25 -08:00
Maxime Beauchemin
ba28fbbd6b Fixing link to cluster view 2016-02-11 17:58:12 -08:00
Maxime Beauchemin
4acf1865f8 Merge pull request #134 from mistercrunch/fix_init
Fixing the roles auto maintenance
2016-02-11 16:10:39 -08:00
Maxime Beauchemin
53a0f81985 Fixing the roles auto maintenance 2016-02-11 15:54:51 -08:00
Maxime Beauchemin
baac8c44a5 Desperatly changing view name to avoid proxy odd bug 2016-02-11 13:23:46 -08:00
Maxime Beauchemin
88b8f73489 Not specifying flask-login version, letting flask-appbuilder do it 2016-02-11 12:54:08 -08:00
Maxime Beauchemin
e02702d320 Pinning appbuilder to 1.6 2016-02-11 10:59:49 -08:00
Maxime Beauchemin
e39d6dbc3d Hack to log dashboard_ids when slugs are used 2016-02-10 14:12:38 -08:00
Maxime Beauchemin
07d52eda48 Merge pull request #132 from mistercrunch/fix_legend
[nvd3] fixing the legend toggle bug
2016-02-10 12:27:46 -08:00
Maxime Beauchemin
c7ecb331e4 [nvd3] fixing the legend toggle bug 2016-02-10 12:22:05 -08:00
Maxime Beauchemin
ec0566860a Fixing legend toggle in nvd3 charts 2016-02-10 11:34:46 -08:00
Maxime Beauchemin
e759dfaf0a Merge pull request #131 from mistercrunch/moretests
More tests using doctests!
2016-02-10 11:26:17 -08:00
Maxime Beauchemin
e039547762 More tests using doctests 2016-02-10 11:20:13 -08:00
Maxime Beauchemin
7b0c045a42 Merge pull request #130 from mistercrunch/log_more
Logging more
2016-02-10 11:13:35 -08:00
Maxime Beauchemin
5e9096f275 Merge pull request #129 from mistercrunch/rename_druid_classes
Renaming Classes related to Druid
2016-02-10 08:54:20 -08:00
Maxime Beauchemin
b1f88a53ad Logging more 2016-02-10 08:52:49 -08:00
Maxime Beauchemin
32442aa6b7 Adding Metric class as well 2016-02-10 07:18:59 -08:00
Maxime Beauchemin
b18d117852 Renaming Classes related to Druid 2016-02-10 06:56:35 -08:00
Maxime Beauchemin
460f6cbed5 Updated TODO and color bugfix 2016-02-04 17:01:40 -08:00
Maxime Beauchemin
61b3a85d7a improving the sql view 2016-02-04 00:26:20 -08:00
Maxime Beauchemin
98ba32399e Polish 2016-02-03 23:11:06 -08:00
Maxime Beauchemin
b10487cf39 Fixing the overflow for tables 2016-02-03 21:51:10 -08:00
Maxime Beauchemin
72cf50a8c3 Merge pull request #127 from mistercrunch/sql
SQL editor, eventually will be tied to a flow to create views
2016-02-03 21:36:48 -08:00
Maxime Beauchemin
3b9b81f93e Getting the back button to work 2016-02-03 21:36:21 -08:00
Maxime Beauchemin
9c47415d62 Error handling and table links 2016-02-03 21:36:21 -08:00
Maxime Beauchemin
50d7d0fb5b Making progress 2016-02-03 21:36:21 -08:00
Maxime Beauchemin
e8ae49d181 An airpal like interface 2016-02-03 21:35:57 -08:00
Maxime Beauchemin
354ef9b4bb Polishing the CSS template feature 2016-02-03 21:13:43 -08:00
Maxime Beauchemin
d276be8d50 Moving CSS templates link to Sources 2016-02-03 20:19:54 -08:00
Maxime Beauchemin
cf5d290a12 Merge pull request #128 from mistercrunch/css_templates
Allowing definition of css templates
2016-02-03 18:09:21 -08:00
Maxime Beauchemin
dbbedc3ba4 Allowing definition of css templates 2016-02-03 18:04:28 -08:00
Maxime Beauchemin
9838fbb107 Fixing bad merge 2016-01-29 10:32:23 -08:00
Maxime Beauchemin
95aa6e00fa [fix] can't back out with back button 2016-01-29 10:30:43 -08:00
Maxime Beauchemin
5156d9f2dd Merge pull request #126 from mistercrunch/heatmap
New viz: Heatmap!
2016-01-28 16:45:32 -08:00
Maxime Beauchemin
4e6e20daa4 Allowing for normalizing across x or y 2016-01-28 13:11:45 -08:00
Maxime Beauchemin
44b43a352a Adding vid to README.md 2016-01-27 14:04:27 -08:00
Maxime Beauchemin
0714dc62d0 Added smoothening option 2016-01-27 09:45:02 -08:00
Maxime Beauchemin
40b28d0afa Allowing different color schemes 2016-01-27 08:50:39 -08:00
Maxime Beauchemin
09c77242fc Working without zoom 2016-01-26 23:17:58 -08:00
Maxime Beauchemin
0f58c609d0 Heatmap 2016-01-26 12:57:12 -08:00
Maxime Beauchemin
a2f14b3787 getting something to show 2016-01-24 22:52:27 -08:00
Maxime Beauchemin
b91460db4f [bugfix] sunburst text shows behind arcs 2016-01-24 17:44:44 -08:00
Maxime Beauchemin
1b661a1ee8 Merge pull request #125 from mistercrunch/colors_perfect
Consistent colors rendered client side
2016-01-24 14:35:00 -08:00
Maxime Beauchemin
a3c2ee0e1d Consistent colors rendered client side 2016-01-24 13:56:20 -08:00
Maxime Beauchemin
e6920f8066 CRUD views improvements 2016-01-24 10:09:26 -08:00
Maxime Beauchemin
0ca59bcc21 Curating list of fields 2016-01-23 10:15:56 -08:00
Maxime Beauchemin
9fb708a74a Merge pull request #124 from mistercrunch/colors
A more cohesive color strategy
2016-01-23 10:03:16 -08:00
Maxime Beauchemin
32e4b9f2bc A more cohesive color strategy 2016-01-23 09:59:16 -08:00
267 changed files with 12122 additions and 57588 deletions

View File

@@ -1 +1 @@
repo_token: EMkVRVEKYgUESKaNN9QyOhPnFnKNqyDcJ
repo_token: eESbYiv4An6KEvjpmguDs4L7YkubXbqn1

16
.gitignore vendored
View File

@@ -1,16 +1,26 @@
*.pyc
babel
.DS_Store
.coverage
_build
_static
panoramix/bin/panoramixc
_images
caravel/bin/caravelc
env_py3
.eggs
build
*.db
tmp
panoramix_config.py
caravel_config.py
local_config.py
env
dist
panoramix.egg-info/
caravel.egg-info/
app.db
*.bak
# Node.js, webpack artifacts
*.entry.js
*.js.map
node_modules
npm-debug.log

23
.landscape.yml Normal file
View File

@@ -0,0 +1,23 @@
doc-warnings: yes
test-warnings: no
strictness: medium
max-line-length: 90
uses:
- flask
autodetect: yes
pylint:
disable:
- cyclic-import
- invalid-name
- logging-format-interpolation
options:
docstring-min-length: 10
pep8:
full: true
ignore-paths:
- docs
- caravel/migrations/env.py
- caravel/ascii_art.py
ignore-patterns:
- ^example/doc_.*\.py$
- (^|/)docs(/|$)

View File

@@ -5,11 +5,18 @@ python:
cache:
directories:
- $HOME/.wheelhouse/
before_install:
- npm install -g npm@'>=2.7.1'
install:
- pip wheel -w $HOME/.wheelhouse -f $HOME/.wheelhouse -r requirements.txt
- pip install --find-links=$HOME/.wheelhouse --no-index -rrequirements.txt
- python setup.py install
# command to run tests
- pip wheel -w $HOME/.wheelhouse -f $HOME/.wheelhouse .
- pip install --find-links=$HOME/.wheelhouse --no-index .
- pip install --find-links=$HOME/.wheelhouse --no-index -r dev-reqs.txt
- cd caravel/assets
- npm --version
- npm install
- npm run lint
- npm run prod
- cd $TRAVIS_BUILD_DIR
script: bash run_tests.sh
after_success: coveralls
after_success:
- coveralls

185
CHANGELOG.md Normal file
View File

@@ -0,0 +1,185 @@
## Change Log
### 0.8.5 (2016/04/01 20:30 +00:00)
- [#234](https://github.com/airbnb/caravel/pull/234) Pin pandas, remove numpy (@mistercrunch)
- [#229](https://github.com/airbnb/caravel/pull/229) Remove "requirements.txt" mention from README (@jmcomets)
- [#225](https://github.com/airbnb/caravel/pull/225) remove power units from sankey diagram (@williaster)
- [#219](https://github.com/airbnb/caravel/pull/219) Add 'Percent of previous' to sunburst vis. Appease npm warnings for data tables and d3.layout.cloud (@williaster)
- [#224](https://github.com/airbnb/caravel/pull/224) Fixing minor typos in the readme (@cyrusstoller)
- [#214](https://github.com/airbnb/caravel/pull/214) Fix an installation bug. (@kim-pham-airbnb)
- [#218](https://github.com/airbnb/caravel/pull/218) Redirecting URL from previous names to caravel (@mistercrunch)
- [#223](https://github.com/airbnb/caravel/pull/223) Fixed typo in README (@thebucknerlife)
- [#222](https://github.com/airbnb/caravel/pull/222) remove duplicate Druid.io section in README.md (@brchristian)
- [#213](https://github.com/airbnb/caravel/pull/213) Fix a bug when loading DruidDatasource. (@kim-pham-airbnb)
- [#204](https://github.com/airbnb/caravel/pull/204) Fixing the order and coverage report for the unit tests (@mistercrunch)
- [#209](https://github.com/airbnb/caravel/pull/209) Fresh screenshots (@mistercrunch)
- [#206](https://github.com/airbnb/caravel/pull/206) Caravel (@mistercrunch)
- [#205](https://github.com/airbnb/caravel/pull/205) fix sunburst error. add `less` to package.json (@williaster)
- [#203](https://github.com/airbnb/caravel/pull/203) Fixing mysql install (@mistercrunch)
- [#202](https://github.com/airbnb/caravel/pull/202) Using setup.py nosetests to run tests (@mistercrunch)
- [#199](https://github.com/airbnb/caravel/pull/199) Fix a few minor bugs (@mistercrunch)
- [#200](https://github.com/airbnb/caravel/pull/200) Add a sankey example (@mistercrunch)
- [#192](https://github.com/airbnb/caravel/pull/192) Fix Druid metadata refresh. (@kim-pham-airbnb)
- [#198](https://github.com/airbnb/caravel/pull/198) A welcome page (@mistercrunch)
- [#197](https://github.com/airbnb/caravel/pull/197) Adding a DRUID_IS_ACTIVE flag and changing nav bar (@NiharikaRay)
- [#196](https://github.com/airbnb/caravel/pull/196) Fixing issues around fk nullable=False on audit fields (@mistercrunch)
### 0.8.4 (2016/03/24 05:26 +00:00)
- [#193](https://github.com/airbnb/caravel/pull/193) Adding favorites for Slices and Dashboards (@mistercrunch)
### 0.8.2 (2016/03/23 20:43 +00:00)
- [#188](https://github.com/airbnb/caravel/pull/188) Introducing a caching layer! (@mistercrunch)
### 0.8.1 (2016/03/21 23:41 +00:00)
- [#191](https://github.com/airbnb/caravel/pull/191) Add week ending and week start to grain (@airbnb)
- [#190](https://github.com/airbnb/caravel/pull/190) Cranking up version numbers (@mistercrunch)
- [#184](https://github.com/airbnb/caravel/pull/184) sunburst improvements (@williaster)
- [#186](https://github.com/airbnb/caravel/pull/186) Adding docstrings ! (@mistercrunch)
- [#181](https://github.com/airbnb/caravel/pull/181) Dynamic time granularity on any datetime column (@mistercrunch)
- [#182](https://github.com/airbnb/caravel/pull/182) more css fixes (@williaster)
- [#178](https://github.com/airbnb/caravel/pull/178) Allowing all extra fields in AuditMixin to be nullable (@mistercrunch)
- [#175](https://github.com/airbnb/caravel/pull/175) refactor dashboard chart html, make several css improvements. (@williaster)
### 0.8.0 (2016/03/11 03:33 +00:00)
- [#172](https://github.com/airbnb/caravel/pull/172) Fixing the python and js packaging (@mistercrunch)
- [#171](https://github.com/airbnb/caravel/pull/171) Fixing multiple refresh bug in filter_box (@mistercrunch)
- [#169](https://github.com/airbnb/caravel/pull/169) Fixing the look of select2 components (@mistercrunch)
- [#168](https://github.com/airbnb/caravel/pull/168) Getting travis to build the npm related stuff (@mistercrunch)
- [#166](https://github.com/airbnb/caravel/pull/166) make css theme customization easier by using less for bootstrap themes (@williaster)
- [#163](https://github.com/airbnb/caravel/pull/163) Shipping with CSS templates out of the box (@mistercrunch)
- [#164](https://github.com/airbnb/caravel/pull/164) Improving the docs (@mistercrunch)
- [#165](https://github.com/airbnb/caravel/pull/165) Fixing window resize for explore and standalone (@mistercrunch)
- [#161](https://github.com/airbnb/caravel/pull/161) Add linting to package.json, do all of the linting. (@williaster)
- [#160](https://github.com/airbnb/caravel/pull/160) Fixing the dashed line when using time compare (@mistercrunch)
- [#159](https://github.com/airbnb/caravel/pull/159) Fixing the standalone mode (@mistercrunch)
- [#158](https://github.com/airbnb/caravel/pull/158) Refactor (@mistercrunch)
- [#154](https://github.com/airbnb/caravel/pull/154) Digging into leap year bug and improvming tests (@mistercrunch)
- [#157](https://github.com/airbnb/caravel/pull/157) add button to auto-copy short URLs in /explore page (@williaster)
- [#149](https://github.com/airbnb/caravel/pull/149) Allowing to make certain widgets immune to filter (@mistercrunch)
- [#151](https://github.com/airbnb/caravel/pull/151) Linting (@mistercrunch)
- [#153](https://github.com/airbnb/caravel/pull/153) Improve README (@tay)
- [#139](https://github.com/airbnb/caravel/pull/139) NPMification & Reactification (@williaster, @mistercrunch)
- [#147](https://github.com/airbnb/caravel/pull/147) Tackling Featured Datasets (@mistercrunch)
- [#148](https://github.com/airbnb/caravel/pull/148) Fix typo (@tay)
- [#145](https://github.com/airbnb/caravel/pull/145) Moving files around ... yay! (@mistercrunch)
- [#142](https://github.com/airbnb/caravel/pull/142) A few cosmetic fixes (nvd3 tooltips, buttons, tables) (@mistercrunch)
- [#141](https://github.com/airbnb/caravel/pull/141) A simple base template for npm (@mistercrunch)
- [#140](https://github.com/airbnb/caravel/pull/140) use the latest segment to extract metadata (@dayzzz)
- [#136](https://github.com/airbnb/caravel/pull/136) Improved the bar char to allow for dimensional breakdowns (@mistercrunch)
- [#134](https://github.com/airbnb/caravel/pull/134) Fixing the roles auto maintenance (@mistercrunch)
- [#132](https://github.com/airbnb/caravel/pull/132) [nvd3] fixing the legend toggle bug (@mistercrunch)
- [#131](https://github.com/airbnb/caravel/pull/131) More tests using doctests! (@mistercrunch)
- [#130](https://github.com/airbnb/caravel/pull/130) Logging more (@mistercrunch)
- [#129](https://github.com/airbnb/caravel/pull/129) Renaming Classes related to Druid (@mistercrunch)
- [#127](https://github.com/airbnb/caravel/pull/127) SQL editor, eventually will be tied to a flow to create views (@mistercrunch)
- [#128](https://github.com/airbnb/caravel/pull/128) Allowing definition of css templates (@mistercrunch)
- [#126](https://github.com/airbnb/caravel/pull/126) New viz: Heatmap! (@mistercrunch)
- [#125](https://github.com/airbnb/caravel/pull/125) Consistent colors rendered client side (@mistercrunch)
- [#124](https://github.com/airbnb/caravel/pull/124) A more cohesive color strategy (@mistercrunch)
### 0.7.0 (2016/01/23 15:16 +00:00)
- [#123](https://github.com/airbnb/caravel/pull/123) Adding a color factory (@mistercrunch)
- [#122](https://github.com/airbnb/caravel/pull/122) Adding Parallel coordinates viz (@mistercrunch)
- [#121](https://github.com/airbnb/caravel/pull/121) Iframe (@mistercrunch)
- [#120](https://github.com/airbnb/caravel/pull/120) Slice information can be displayed in dashboard (@mistercrunch)
- [#117](https://github.com/airbnb/caravel/pull/117) Doing some refactoring (@mistercrunch)
- [#115](https://github.com/airbnb/caravel/pull/115) Providing options for Y axis number formating (@mistercrunch)
- [#112](https://github.com/airbnb/caravel/pull/112) Adding an URL shortner (@mistercrunch)
- [#113](https://github.com/airbnb/caravel/pull/113) Prettier checkboxes (@mistercrunch)
- [#111](https://github.com/airbnb/caravel/pull/111) Loading another example amazing dash (@mistercrunch)
- [#109](https://github.com/airbnb/caravel/pull/109) Getting browser history to work on the explore view (@mistercrunch)
- [#108](https://github.com/airbnb/caravel/pull/108) pulling to the front on hover (@BradBaker)
- [#104](https://github.com/airbnb/caravel/pull/104) simplifying tooltip code (@BradBaker)
- [#105](https://github.com/airbnb/caravel/pull/105) adding stagger for all charts that have a date axis (@BradBaker)
- [#102](https://github.com/airbnb/caravel/pull/102) Fix for 2-axis charts where it shrinks them a little bit (@bradmbaker, @BradBaker)
- [#101](https://github.com/airbnb/caravel/pull/101) Add a Gitter chat badge to README.md (@gitter-badger)
- [#100](https://github.com/airbnb/caravel/pull/100) Update tooltips with new classes (@bradmbaker)
- [#99](https://github.com/airbnb/caravel/pull/99) Time resampling as in Pandas (@mistercrunch)
- [#98](https://github.com/airbnb/caravel/pull/98) Change Scaling to Operate on SVG instead of Div (@bradmbaker)
- [#96](https://github.com/airbnb/caravel/pull/96) Adding a filter box widget (@mistercrunch)
- [#95](https://github.com/airbnb/caravel/pull/95) Working on docs (@mistercrunch)
- [#94](https://github.com/airbnb/caravel/pull/94) Massive js refactor + Dashboard filters (@mistercrunch)
- [#93](https://github.com/airbnb/caravel/pull/93) Controller (@mistercrunch)
- [#92](https://github.com/airbnb/caravel/pull/92) Allowing not to group by on table view (@mistercrunch)
- [#91](https://github.com/airbnb/caravel/pull/91) Exports (@mistercrunch)
- [#90](https://github.com/airbnb/caravel/pull/90) A basic squeleton for the docs (@mistercrunch)
- [#89](https://github.com/airbnb/caravel/pull/89) Featured datasets (@michellethomas)
- [#87](https://github.com/airbnb/caravel/pull/87) fixing a few bugs with tool tip overflow (@BradBaker)
- [#88](https://github.com/airbnb/caravel/pull/88) World Map viz with bubbles (@mistercrunch)
- [#86](https://github.com/airbnb/caravel/pull/86) adjusting date formats (@BradBaker)
- [#85](https://github.com/airbnb/caravel/pull/85) Adding sankey diagrams (@mistercrunch)
- [#84](https://github.com/airbnb/caravel/pull/84) Adding directed force layout viz (@mistercrunch)
- [#83](https://github.com/airbnb/caravel/pull/83) Big JS refactor (@mistercrunch)
- [#82](https://github.com/airbnb/caravel/pull/82) letting tooltips in the dashboard overflow (@BradBaker)
- [#81](https://github.com/airbnb/caravel/pull/81) Slightly better layout for explore page (@mistercrunch)
- [#80](https://github.com/airbnb/caravel/pull/80) Checkboxes everywhere (@mistercrunch)
- [#79](https://github.com/airbnb/caravel/pull/79) Cleanup around multiple select fields (@mistercrunch)
### 0.6.0 (2015/12/11 01:17 +00:00)
- [#77](https://github.com/airbnb/caravel/pull/77) Better tooltips and more ways to integrate them easily (@mistercrunch)
- [#76](https://github.com/airbnb/caravel/pull/76) Introducing form overrides for label and tooltips (@mistercrunch)
- [#75](https://github.com/airbnb/caravel/pull/75) New viz: sunbursts (@mistercrunch)
- [#74](https://github.com/airbnb/caravel/pull/74) Introducing fieldsets (@mistercrunch)
- [#73](https://github.com/airbnb/caravel/pull/73) Airflowlike theme (@mistercrunch)
- [#72](https://github.com/airbnb/caravel/pull/72) Logging slice and dash views (@mistercrunch)
- [#70](https://github.com/airbnb/caravel/pull/70) Adding url slug support for dashboard model (@mistercrunch)
- [#71](https://github.com/airbnb/caravel/pull/71) Add option to show minmax on x axis (@mistercrunch)
- [#69](https://github.com/airbnb/caravel/pull/69) Allowing for [Save AS] and [Overwrite] (@mistercrunch)
- [#68](https://github.com/airbnb/caravel/pull/68) Adding cumsum to rolling functions (@mistercrunch)
- [#67](https://github.com/airbnb/caravel/pull/67) Fix debug mode calls get_json twice (@mistercrunch)
- [#66](https://github.com/airbnb/caravel/pull/66) Adding a PivotTableViz (@mistercrunch)
- [#65](https://github.com/airbnb/caravel/pull/65) Adding custom HAVING clause (@mistercrunch)
- [#64](https://github.com/airbnb/caravel/pull/64) Preserving the ordering in selectmultiple (@mistercrunch)
- [#63](https://github.com/airbnb/caravel/pull/63) Encrypting the passwords out of connection strings (@mistercrunch)
- [#61](https://github.com/airbnb/caravel/pull/61) BetterBooleanField to fix html omitting non-checked <input> (@patrickleotardif)
- [#60](https://github.com/airbnb/caravel/pull/60) Fix Markup Widget bug (@NiharikaRay)
- [#59](https://github.com/airbnb/caravel/pull/59) Adding y-axis format option (@patrickleotardif)
- [#58](https://github.com/airbnb/caravel/pull/58) Setting min_periods to 1 for rolling windows (@mistercrunch)
- [#56](https://github.com/airbnb/caravel/pull/56) adding sort order of the slices on changed_on field (@mistercrunch)
### 0.5.2 (2015/10/24 01:06 +00:00)
- [#53](https://github.com/airbnb/caravel/pull/53) Py3 (@mistercrunch)
- [#51](https://github.com/airbnb/caravel/pull/51) Adding timezone offset as a datasource param (@mistercrunch)
- [#52](https://github.com/airbnb/caravel/pull/52) Speed up travis builds with wheels (@mistercrunch)
- [#48](https://github.com/airbnb/caravel/pull/48) Allowing to specify the gunicorn timeout in CLI and config (@mistercrunch)
### 0.5.0 (2015/10/13 01:09 +00:00)
- [#46](https://github.com/airbnb/caravel/pull/46) Allowing to change the "Time Column" on SqlA (@mistercrunch)
- [#45](https://github.com/airbnb/caravel/pull/45) Bootstrapping widgets from javascript initializer. (@akuhn)
- [#43](https://github.com/airbnb/caravel/pull/43) Supporting arbitrary expressions (@mistercrunch)
- [#42](https://github.com/airbnb/caravel/pull/42) Adding ability to style a dashboard with CSS (@mistercrunch)
- [#41](https://github.com/airbnb/caravel/pull/41) Cleaning up the static folder (@mistercrunch)
- [#35](https://github.com/airbnb/caravel/pull/35) A first draft on default security roles (@mistercrunch)
- [#40](https://github.com/airbnb/caravel/pull/40) Introducing time comparison (@mistercrunch)
- [#39](https://github.com/airbnb/caravel/pull/39) Adding interpolation choice for line charts (@mistercrunch)
- [#38](https://github.com/airbnb/caravel/pull/38) Extract css rules and scripts into separate files. (@akuhn)
- [#37](https://github.com/airbnb/caravel/pull/37) Viz type (@mistercrunch)
- [#36](https://github.com/airbnb/caravel/pull/36) Extract widget javascript to separate files. (@akuhn)
- [#34](https://github.com/airbnb/caravel/pull/34) Ripping out Highcharts. (@mistercrunch)
### 0.4.0 (2015/09/27 04:39 +00:00)
- [#33](https://github.com/airbnb/caravel/pull/33) Adding nvd3 support (@mistercrunch)
- [#32](https://github.com/airbnb/caravel/pull/32) Adding a foundation for unit tests (@mistercrunch)
- [#31](https://github.com/airbnb/caravel/pull/31) Adding a button to test connections (@mistercrunch)
- [#30](https://github.com/airbnb/caravel/pull/30) Word cloud widget! (@mistercrunch)
- [#29](https://github.com/airbnb/caravel/pull/29) Adding support for markup (html/markdown) widgets (@mistercrunch)
- [#28](https://github.com/airbnb/caravel/pull/28) Fix default Sqlite path. (@noddi)
- [#27](https://github.com/airbnb/caravel/pull/27) More refactor and bugfixes (@mistercrunch)
- [#26](https://github.com/airbnb/caravel/pull/26) Bugfix (@mistercrunch)
- [#25](https://github.com/airbnb/caravel/pull/25) Adding basic dashboarding support! (@mistercrunch)
- [#23](https://github.com/airbnb/caravel/pull/23) Custom WHERE clause for tables (not druid) + error handling refactor (@mistercrunch)
- [#22](https://github.com/airbnb/caravel/pull/22) Form factory refactor (@mistercrunch)
- [#20](https://github.com/airbnb/caravel/pull/20) add tzinfo config, useful when start druid without utc timezone (@wbchn)
### 0.2.1 (2015/09/05 22:08 +00:00)
- [#19](https://github.com/airbnb/caravel/pull/19) Preparing pypi package (@mistercrunch)
### 0.2.0 (2015/09/05 20:43 +00:00)
- [#16](https://github.com/airbnb/caravel/pull/16) Adding Bubble charts (@mistercrunch)
- [#13](https://github.com/airbnb/caravel/pull/13) Now supporting SQL Multiple database (@mistercrunch)
- [#12](https://github.com/airbnb/caravel/pull/12) Cosmetricks (@mistercrunch)
- [#11](https://github.com/airbnb/caravel/pull/11) Fixing the ways metrics are autogenerated (@mistercrunch)
- [#10](https://github.com/airbnb/caravel/pull/10) Now enabling multi-cluster, connection info managed in UI (@mistercrunch)
- [#9](https://github.com/airbnb/caravel/pull/9) Multi delete action on datasources (@mistercrunch)
- [#8](https://github.com/airbnb/caravel/pull/8) Preventing bad json from creating problems (@mistercrunch)
- [#3](https://github.com/airbnb/caravel/pull/3) Implementing my own highcharts wrapper (@mistercrunch)

View File

@@ -28,14 +28,10 @@ open to whoever wants to implement it.
Look through the GitHub issues for features. Anything tagged with
"feature" is open to whoever wants to implement it.
We've created the operators, hooks, macros and executors we needed, but we
made sure that this part of Panoramix is extensible. New operators,
hooks and operators are very welcomed!
### Documentation
Panoramix could always use better documentation,
whether as part of the official Panoramix docs,
Caravel could always use better documentation,
whether as part of the official Caravel docs,
in docstrings, `docs/*.rst` or even on the web as blog posts or
articles.
@@ -53,14 +49,14 @@ If you are proposing a feature:
## Latest Documentation
[API Documentation](http://pythonhosted.com/panoramix)
[API Documentation](http://pythonhosted.com/caravel)
## Setting up a development environment
## Setting up a Python development environment
# fork the repo on github and then clone it
# alternatively you may want to clone the main repo but that won't work
# so well if you are planning on sending PRs
# git clone git@github.com:mistercrunch/panoramix.git
# git clone git@github.com:airbnb/caravel.git
# [optional] setup a virtual env and activate it
virtualenv env
@@ -70,21 +66,75 @@ If you are proposing a feature:
python setup.py develop
# Create an admin user
fabmanager create-admin --app panoramix
fabmanager create-admin --app caravel
# Initialize the database
panoramix db upgrade
caravel db upgrade
# Create default roles and permissions
panoramix init
caravel init
# Load some data to play with
panoramix load_examples
caravel load_examples
# start a dev web server
panoramix runserver -d
caravel runserver -d
For every development session you may have to
## Setting up the node / npm javascript environment
`caravel/assets` contains all npm-managed, front end assets.
Flask-Appbuilder itself comes bundled with jQuery and bootstrap.
While these may be phased out over time, these packages are currently not
managed with npm.
### Using npm to generate bundled files
#### npm
First, npm must be available in your environment. If it is not you can run the following commands
(taken from [this source](https://gist.github.com/DanHerbert/9520689))
```
brew install node --without-npm
echo prefix=~/.npm-packages >> ~/.npmrc
curl -L https://www.npmjs.com/install.sh | sh
```
The final step is to add
`~/.node/bin` to your `PATH` so commands you install globally are usable.
Add something like this to your `.bashrc` file.
```
export PATH="$HOME/.node/bin:$PATH"
```
#### npm packages
To install third party libraries defined in `package.json`, run the
following within this directory which will install them in a
new `node_modules/` folder within `assets/`.
```
npm install
```
To parse and generate bundled files for caravel, run either of the
following commands. The `dev` flag will keep the npm script running and
re-run it upon any changes within the assets directory.
```
# Compiles the production / optimized js & css
npm run prod
# Start a web server that manages and updates your assets as you modify them
npm run dev
```
For every development session you will have to start a flask dev server
as well as an npm watcher
```
caravel runserver -d -p 8081
npm run dev
```
## Testing
@@ -94,24 +144,37 @@ Tests can then be run with:
Lint the project with:
# for python changes
flake8 changes tests
# for javascript
npm run lint
## API documentation
Generate the documentation with:
cd docs && ./build.sh
## CSS Themes
As part of the npm build process, CSS for Caravel is compiled from ```Less```, a dynamic stylesheet language.
It's possible to customize or add your own theme to Caravel, either by overriding CSS rules or preferably
by modifying the Less variables or files in ```assets/stylesheets/less/```.
The ```variables.less``` and ```bootswatch.less``` files that ship with Caravel are derived from
[Bootswatch](https://bootswatch.com) and thus extend Bootstrap. Modify variables in these files directly, or
swap them out entirely with the equivalent files from other Bootswatch (themes)[https://github.com/thomaspark/bootswatch.git]
## Pull Request Guidelines
Before you submit a pull request from your forked repo, check that it
Before you submit a pull request from your forked repo, check that it
meets these guidelines:
1. The pull request should include tests, either as doctests,
unit tests, or both.
2. If the pull request adds functionality, the docs should be updated
as part of the same PR. Doc string are often sufficient, make
as part of the same PR. Doc string are often sufficient, make
sure to follow the sphinx compatible standards.
3. The pull request should work for Python 2.6, 2.7, and ideally python 3.3.
`from __future__ import ` will be required in every `.py` file soon.

11
INTHEWILD.md Normal file
View File

@@ -0,0 +1,11 @@
Please use [pull requests](https://github.com/airbnb/caravel/pull/new/master)
to add your organization and/or project to this document!
Organizations
----------
- [Airbnb](https://github.com/airbnb)
- [GfK Data Lab] (http://datalab.gfk.com)
Projects
----------
- None we know of yet

View File

@@ -1,4 +1,8 @@
recursive-include panoramix/templates *
recursive-include panoramix/static *
recursive-include panoramix/data *
recursive-include panoramix/migrations *
recursive-include caravel/templates *
recursive-include caravel/static *
recursive-exclude caravel/static/assets/node_modules *
recursive-include caravel/static/assets/node_modules/font-awesome *
recursive-exclude caravel/static/docs *
recursive-exclude tests *
recursive-include caravel/data *
recursive-include caravel/migrations *

185
README.md
View File

@@ -1,169 +1,110 @@
Panoramix
Caravel
=========
<img src="http://i.imgur.com/H0Kyvyi.jpg" style="border-radius: 20px; box-shadow:5px 5px 5px gray;" alt="Caravel" width="500"/>
[![Join the chat at https://gitter.im/mistercrunch/panoramix](https://badges.gitter.im/mistercrunch/panoramix.svg)](https://gitter.im/mistercrunch/panoramix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
![img](https://travis-ci.org/mistercrunch/panoramix.svg?branch=master)
[![Coverage Status](https://coveralls.io/repos/mistercrunch/panoramix/badge.svg?branch=master&service=github)](https://coveralls.io/github/mistercrunch/panoramix?branch=master)
[![Build Status](https://travis-ci.org/airbnb/caravel.svg?branch=master)](https://travis-ci.org/airbnb/caravel)
[![PyPI version](https://badge.fury.io/py/caravel.svg)](https://badge.fury.io/py/caravel)
[![Coverage Status](https://coveralls.io/repos/airbnb/caravel/badge.svg?branch=master&service=github)](https://coveralls.io/github/airbnb/caravel?branch=master)
[![Code Health](https://landscape.io/github/airbnb/caravel/master/landscape.svg?style=flat)](https://landscape.io/github/airbnb/caravel/master)
[![Requirements Status](https://requires.io/github/airbnb/caravel/requirements.svg?branch=master)](https://requires.io/github/airbnb/caravel/requirements/?branch=master)
[![Join the chat at https://gitter.im/airbnb/caravel](https://badges.gitter.im/airbnb/caravel.svg)](https://gitter.im/airbnb/caravel?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Documentation](https://img.shields.io/badge/docs-airbnb.io-blue.svg)](http://airbnb.io/caravel/)
Panoramix is a data exploration platform designed to be visual, intuitive
Caravel is a data exploration platform designed to be visual, intuitive
and interactive.
![img](http://i.imgur.com/bi09J9X.png)
![img](http://i.imgur.com/aOaH0ty.png)
[this project used to be named **Panoramix**]
Panoramix
Video - Introduction to Caravel
---------------------------------
[![Caravel - ](http://img.youtube.com/vi/3Txm_nj_R7M/0.jpg)](http://www.youtube.com/watch?v=3Txm_nj_R7M)
Screenshots
------------
![img](http://i.imgur.com/JRbTnTx.png)
![img](http://i.imgur.com/4wRtxwb.png)
Caravel
---------
Panoramix's main goal is to make it easy to slice, dice and visualize data.
It empowers its user to perform **analytics at the speed of thought**.
Caravel's main goal is to make it easy to slice, dice and visualize data.
It empowers users to perform **analytics at the speed of thought**.
Panoramix provides:
* A quick way to intuitively visualize datasets
* Create and share interactive dashboards
Caravel provides:
* A quick way to intuitively visualize datasets by allowing users to create
and share interactive dashboards
* A rich set of visualizations to analyze your data, as well as a flexible
way to extend the capabilities
* An extensible, high granularity security model allowing intricate rules
on who can access which features, and integration with major
authentication providers (database, OpenID, LDAP, OAuth & REMOTE_USER
through Flask AppBuiler)
* A simple semantic layer, allowing to control how data sources are
displayed in the UI,
by defining which fields should show up in which dropdown and which
aggregation and function (metrics) are made available to the user
* Deep integration with Druid allows for Panoramix to stay blazing fast while
* A simple semantic layer, allowing to control how data sources are
displayed in the UI, by defining which fields should show up in
which dropdown and which aggregation and function (metrics) are
made available to the user
* Deep integration with Druid allows for Caravel to stay blazing fast while
slicing and dicing large, realtime datasets
Buzz Phrases
------------
* Analytics at the speed of thought!
* Instantaneous learning curve
* Realtime analytics when querying [Druid.io](http://druid.io)
* Extentsible to infinity
Database Support
----------------
Panoramix was originally designed on to of Druid.io, but quickly broadened
its scope to support other databases through the use of SqlAlchemy, a Python
Caravel was originally designed on top of Druid.io, but quickly broadened
its scope to support other databases through the use of SQLAlchemy, a Python
ORM that is compatible with
[most common databases](http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html).
[most common databases](http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html).
What is Druid?
-------------
From their website at http://druid.io
*Druid is an open-source analytics data store designed for
business intelligence (OLAP) queries on event data. Druid provides low
latency (real-time) data ingestion, flexible data exploration,
and fast data aggregation. Existing Druid deployments have scaled to
trillions of events and petabytes of data. Druid is best used to
*Druid is an open-source analytics data store designed for
business intelligence (OLAP) queries on event data. Druid provides low
latency (real-time) data ingestion, flexible data exploration,
and fast data aggregation. Existing Druid deployments have scaled to
trillions of events and petabytes of data. Druid is best used to
power analytic dashboards and applications.*
Installation
------------
Installation & Configuration
----------------------------
Panoramix is currently only tested using Python 2.7.*. Python 3 support is
on the roadmap, Python 2.6 won't be supported.
[See in the documentation](http://airbnb.io/caravel/installation.html)
Follow these few simple steps to install Panoramix.
```
# Install panoramix
pip install panoramix
# Create an admin user
fabmanager create-admin --app panoramix
# Initialize the database
panoramix db upgrade
# Create default roles and permissions
panoramix init
# Load some data to play with
panoramix load_examples
# Start the development web server
panoramix runserver -d
```
After installation, you should be able to point your browser to the right
hostname:port [http://localhost:8088](http://localhost:8088), login using
the credential you entered while creating the admin account, and navigate to
`Menu -> Admin -> Refresh Metadata`. This action should bring in all of
your datasources for Panoramix to be aware of, and they should show up in
`Menu -> Datasources`, from where you can start playing with your data!
Configuration
-------------
To configure your application, you need to create a file (module)
`panoramix_config.py` and make sure it is in your PYTHONPATH. Here are some
of the parameters you can copy / paste in that configuration module:
```
#---------------------------------------------------------
# Panoramix specifix config
#---------------------------------------------------------
ROW_LIMIT = 5000
WEBSERVER_THREADS = 8
PANORAMIX_WEBSERVER_PORT = 8088
#---------------------------------------------------------
#---------------------------------------------------------
# Flask App Builder configuration
#---------------------------------------------------------
# Your App secret key
SECRET_KEY = '\2\1thisismyscretkey\1\2\e\y\y\h'
# The SQLAlchemy connection string.
SQLALCHEMY_DATABASE_URI = 'sqlite:////tmp/panoramix.db'
# Flask-WTF flag for CSRF
CSRF_ENABLED = True
# Whether to run the web server in debug mode or not
DEBUG = True
```
This file also allows you to define configuration parameters used by
Flask App Builder, the web framework used by Panoramix. Please consult
the [Flask App Builder Documentation](http://flask-appbuilder.readthedocs.org/en/latest/config.html) for more information on how to configure Panoramix.
* From the UI, enter the information about your clusters in the
``Admin->Clusters`` menu by hitting the + sign.
* Once the Druid cluster connection information is entered, hit the
``Admin->Refresh Metadata`` menu item to populate
* Navigate to your datasources
More screenshots
----------------
![img](http://i.imgur.com/Rt6gNQ9.png)
![img](http://i.imgur.com/t7VOtqQ.png)
![img](http://i.imgur.com/PaiFQnH.png)
![img](http://i.imgur.com/CdcGHuC.png)
![img](http://i.imgur.com/MAFZTtU.png)
![img](http://i.imgur.com/xcy1QjN.png)
![img](http://i.imgur.com/RWqA8ly.png)
![img](http://i.imgur.com/D2kZL7q.png)
![img](http://i.imgur.com/0UPTK61.png)
![img](http://i.imgur.com/ahHoCuS.png)
Related Links
Resources
-------------
* [Panoramix Google Group] (https://groups.google.com/forum/#!forum/airbnb_panoramix)
* [Gitter (live chat) Channel](https://gitter.im/mistercrunch/panoramix)
* [Caravel Google Group](https://groups.google.com/forum/#!forum/airbnb_caravel)
* [Gitter (live chat) Channel](https://gitter.im/airbnb/caravel)
* [Docker image](https://hub.docker.com/r/kochalex/caravel/) (community contributed)
Tip of the Hat
--------------
Panoramix would not be possible without these great frameworks / libs
Caravel would not be possible without these great frameworks / libs
* Flask App Builder - Allowing us to focus on building the app quickly while
getting the foundation for free
* The Flask ecosystem - Simply amazing. So much Plug, easy play.
* NVD3 - One of the best charting library out there
* Much more, check out the requirements.txt file!
* NVD3 - One of the best charting libraries out there
* Much more, check out the `install_requires` section in the [setup.py](https://github.com/airbnb/caravel/blob/master/setup.py) file!
Contributing
------------
Interested in contributing? Casual hacking? Check out [Contributing.MD](https://github.com/airbnb/caravel/blob/master/CONTRIBUTING.md)

62
TODO.md
View File

@@ -1,40 +1,56 @@
# TODO
List of TODO items for Panoramix
List of TODO items for Caravel
## Important
* **Getting proper JS testing:** unit tests on the Python side are pretty
solid, but now we need a test suite for the JS part of the site,
testing all the ajax-type calls
* **Viz Plugins:** Allow people to define and share visualization plugins.
ideally one would only need to drop in a set of files in a folder and
Caravel would discover and expose the plugins
## Features
* Slider form element
* **Dashboard URL filters:** `{dash_url}#fltin__fieldname__value1,value2`
* **Default slice:** choose a default slice for the dataset instead of default endpoint
* **refresh freq**: specifying the refresh frequency of a dashboard and specific slices within it, some randomization would be nice
* **Color hash in JS:** it'd be nice to use the same hash function for color attribution of series
on the js side as on the python side (`panoramix.utils.color`)
* **Widget sets / chart grids:** a way to have all charts support making a series of charts and putting them in a grid.
the same way that you can groupby for series, you could chart by. The form fieldset would be common and use
a single field to "grid by", a limit number of chart as an N * N grid size.
* **Free form SQL editor:** Having an Airpal-like easy SQL editor
* **Advanced dashboard configuration:** define which slices are immune to which filters, how often widgets should refresh,
maybe this should start as a json blob...
* **Getting proper JS testing:** unit tests on the Python side are pretty solid, but now we need a test
suite for the JS part of the site, testing all the ajax-type calls
* **Annotations layers:** allow for people to maintain data annotations,
attached to a layer and time range. These layers can be added on top of some visualizations as annotations.
An example of a layer might be "holidays" or "site outages", ...
* **Worth doing? User defined groups:** People could define mappings in the UI of say "Countries I follow" and apply it to different datasets. For now, this is done by writing CASE-WHEN-type expression which is probably good enough.
* **Default slice:** choose a default slice for the dataset instead of
default endpoint
* **refresh freq**: specifying the refresh frequency of a dashboard and
specific slices within it, some randomization would be nice
* **Widget sets / chart grids:** a way to have all charts support making
a series of charts and putting them in a grid. The same way that you
can groupby for series, you could chart by. The form field set would be
common and use a single field to "grid by", a limit number of chart as
an N * N grid size.
* **Advanced dashboard configuration:** currently you can define which
slices in a dashboard are immune to filtering.
* **Annotations layers:** allow for people to maintain data annotations,
attached to a layer and time range. These layers can be added on top of
some visualizations as annotations. An example of a layer might be
"holidays" or "site outages", ...
* **Slack integration** - TBD
* **Sexy Viz Selector:** the visualization selector should be a nice large
modal with nice thumbnails for each one of the viz
* **Comments:** allow for people to comment on slices and dashes
## Easy-ish fix
* Build matrix to include mysql using tox
* Figure out why coverage isn't working
* Kill switch for Druid in docs
* CREATE VIEW button from SQL editor
* Test button for when editing SQL expression
* Slider form element
* datasource in explore mode could be a dropdown
* Create a set of slices and dashboard on top of the World Bank dataset that ship with load_examples
* [sql] make "Test Connection" test further, run an actual dummy query
* [druid] Allow for post aggregations (ratios!)
* in/notin filters autocomplete
* in/notin filters autocomplete (druid)
## New viz
* Animated scatter plots
* Maps that use geocodes
* Time animated scatter plots
* Horizon charts
* Calendar heatmap
* Chord diagram
* ...
## Community
* Creat a proper user documentation (started using Sphinx and boostrap...)
* Create a proper user documentation (started using Sphinx and boostrap...)
* Usage vid

View File

@@ -29,7 +29,7 @@ script_location = migrations
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = scheme://localhost/panoramix
sqlalchemy.url = scheme://localhost/caravel
# Logging configuration
[loggers]

View File

@@ -1,3 +0,0 @@
[python: **.py]
[jinja2: **/templates/**.html]
encoding = utf-8

View File

@@ -1 +0,0 @@

View File

@@ -1,13 +1,20 @@
"""Package's main module!"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import logging
import os
from flask import Flask
from flask import Flask, redirect
from flask.ext.appbuilder import SQLA, AppBuilder, IndexView
from flask.ext.appbuilder.baseviews import expose
from flask.ext.migrate import Migrate
from panoramix import config
from flask.ext.cache import Cache
APP_DIR = os.path.dirname(__file__)
CONFIG_MODULE = os.environ.get('PANORAMIX_CONFIG', 'panoramix.config')
CONFIG_MODULE = os.environ.get('CARAVEL_CONFIG', 'caravel.config')
# Logging configuration
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(name)s:%(message)s')
@@ -16,18 +23,24 @@ logging.getLogger().setLevel(logging.DEBUG)
app = Flask(__name__)
app.config.from_object(CONFIG_MODULE)
db = SQLA(app)
cache = Cache(app, config=app.config.get('CACHE_CONFIG'))
migrate = Migrate(app, db, directory=APP_DIR + "/migrations")
class MyIndexView(IndexView):
index_template = 'index.html'
@expose('/')
def index(self):
return redirect('/caravel/welcome')
appbuilder = AppBuilder(
app, db.session, base_template='panoramix/base.html',
app, db.session,
base_template='caravel/base.html',
indexview=MyIndexView,
security_manager_class=app.config.get("CUSTOM_SECURITY_MANAGER"))
sm = appbuilder.sm
get_session = appbuilder.get_session
from panoramix import views
from caravel import config, views # noqa

View File

@@ -1,3 +1,8 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
error = (
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+

3
caravel/assets/.babelrc Normal file
View File

@@ -0,0 +1,3 @@
{
"presets" : ["es2015", "react"]
}

View File

@@ -0,0 +1,3 @@
node_modules/*
vendor/*
javascripts/dist/*

234
caravel/assets/.eslintrc Normal file
View File

@@ -0,0 +1,234 @@
{
"root": true,
"globals": {
"Symbol": false,
"Map": false,
"Set": false,
"Reflect": false,
},
"env": {
"es6": false,
"browser": true,
"node": true,
},
"parserOptions": {
"ecmaVersion": 5,
"sourceType": "module"
},
"rules": {
"array-bracket-spacing": [2, "never", {
"singleValue": false,
"objectsInArrays": false,
"arraysInArrays": false
}],
"array-callback-return": [2],
"block-spacing": [2, "always"],
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"callback-return": [2, ["callback"]],
"camelcase": [0],
"comma-dangle": [2, "never"],
"comma-spacing": [2],
"comma-style": [2, "last"],
"curly": [2, "all"],
"eqeqeq": 2,
"func-names": [0],
"id-length": [2, { "min": 1, "max": 25, "properties": "never" }],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"keyword-spacing": [2, {
"before": true,
"after": true,
"overrides": {
"return": { "after": true },
"throw": { "after": true },
"case": { "after": true }
}
}],
"linebreak-style": [2, "unix"],
"lines-around-comment": [2, {
"beforeBlockComment": false,
"afterBlockComment": false,
"beforeLineComment": false,
"allowBlockStart": true,
"allowBlockEnd": true
}],
"max-depth": [2, 5],
"max-len": [0, 80, 4],
"max-nested-callbacks": [1, 3],
"max-params": [1, 4],
"new-parens": [2],
"newline-after-var": [0],
"no-bitwise": [0],
"no-cond-assign": [2],
"no-console": [1, { allow: ["warn", "error"] }],
"no-const-assign": [2],
"no-constant-condition": [2],
"no-control-regex": [2],
"no-debugger": [2],
"no-delete-var": [2],
"no-dupe-args": [2],
"no-dupe-class-members": [2],
"no-dupe-keys": [2],
"no-duplicate-case": [2],
"no-else-return": [0],
"no-empty": [2],
"no-eq-null": [0],
"no-eval": [2],
"no-ex-assign": [2],
"no-extend-native": [2],
"no-extra-bind": [2],
"no-extra-boolean-cast": [2],
"no-extra-label": [2],
"no-extra-parens": [0], // needed for clearer #math eg (a - b) / c
"no-extra-semi": [2],
"no-fallthrough": [2],
"no-floating-decimal": [2],
"no-func-assign": [2],
"no-implied-eval": [2],
"no-implicit-coercion": [2, {
"boolean": false,
"number": true,
"string": true
}],
"no-implicit-globals": [2],
"no-inline-comments": [0],
"no-invalid-regexp": [2],
"no-irregular-whitespace": [2],
"no-iterator": [2],
"no-label-var": [2],
"no-labels": [2, { "allowLoop": false, "allowSwitch": false }],
"no-lone-blocks": [2],
"no-lonely-if": [2],
"no-loop-func": [2],
"no-magic-numbers": [0], // doesn't work well with vis cosmetic constant
"no-mixed-requires": [1, false],
"no-mixed-spaces-and-tabs": [2, false],
"no-multi-spaces": [2, {
"exceptions": {
"ImportDeclaration": true,
"Property": true,
"VariableDeclarator": true
}
}],
"no-multi-str": [2],
"no-multiple-empty-lines": [2, { "max": 1, "maxEOF": 1 }],
"no-native-reassign": [2],
"no-negated-condition": [2],
"no-negated-in-lhs": [2],
"no-nested-ternary": [0],
"no-new": [2],
"no-new-func": [2],
"no-new-object": [2],
"no-new-require": [0],
"no-new-symbol": [2],
"no-new-wrappers": [2],
"no-obj-calls": [2],
"no-octal": [2],
"no-octal-escape": [2],
"no-path-concat": [0],
"no-process-env": [0],
"no-process-exit": [2],
"no-proto": [2],
"no-redeclare": [2],
"no-regex-spaces": [2],
"no-restricted-modules": [0],
"no-restricted-imports": [0],
"no-restricted-syntax": [2,
"DebuggerStatement",
"LabeledStatement",
"WithStatement"
],
"no-return-assign": [2, "always"],
"no-script-url": [2],
"no-self-assign": [2],
"no-self-compare": [0],
"no-sequences": [2],
"no-shadow-restricted-names": [2],
"no-spaced-func": [2],
"no-sparse-arrays": [2],
"no-sync": [0],
"no-ternary": [0],
"no-this-before-super": [2],
"no-throw-literal": [2],
"no-trailing-spaces": [2, { "skipBlankLines": false }],
"no-undef": [2, { "typeof": true }],
"no-undef-init": [2],
"no-undefined": [0],
"no-underscore-dangle": [0], // __data__ sometimes
"no-unexpected-multiline": [2],
"no-unmodified-loop-condition": [2],
"no-unneeded-ternary": [2],
"no-unreachable": [2],
"no-unused-expressions": [2],
"no-unused-labels": [2],
"no-unused-vars": [2, {
"vars": "all",
"args": "none", // (d, i) pattern d3 func makes difficult to enforce
"varsIgnorePattern": "jQuery"
}],
"no-use-before-define": [0],
"no-useless-call": [2],
"no-useless-concat": [2],
"no-useless-constructor": [2],
"no-void": [0],
"no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }],
"no-with": [2],
"no-whitespace-before-property": [2],
"object-curly-spacing": [2, "always"],
"object-shorthand": [2, "never"],
"one-var": [0],
"one-var-declaration-per-line": [2, "initializations"],
"operator-assignment": [0, "always"],
"padded-blocks": [0],
"prefer-arrow-callback": [0],
"prefer-const": [0],
"prefer-reflect": [0],
"prefer-rest-params": [0],
"prefer-spread": [0],
"prefer-template": [0],
"quote-props": [2, "as-needed", { "keywords": true }],
"radix": [2],
"require-yield": [2],
"semi": [2],
"semi-spacing": [2, { "before": false, "after": true }],
"sort-vars": [0],
"sort-imports": [0],
"space-before-function-paren": [2, { "anonymous": "always", "named": "never" }],
"space-before-blocks": [2, { "functions": "always", "keywords": "always" }],
"space-in-brackets": [0, "never", {
"singleValue": true,
"arraysInArrays": false,
"arraysInObjects": false,
"objectsInArrays": true,
"objectsInObjects": true,
"propertyName": false
}],
},
// Temporarily not enforced
"new-cap": [2], // @TODO more tricky for the moment
"newline-per-chained-call": [2, { "ignoreChainWithDepth": 6 }],
"no-param-reassign": [0], // turn on once default args supported
"no-shadow": [2, { // @TODO more tricky for the moment with eg 'data'
"builtinGlobals": false,
"hoist": "functions",
"allow": ["i", "d"]
}],
"space-in-parens": [2, "never"],
"space-infix-ops": [2],
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [2, "always", { "markers": ["!"] }],
"spaced-line-comment": [0, "always"],
"strict": [2, "global"],
"template-curly-spacing": [2, "never"],
"use-isnan": [2],
"valid-jsdoc": [0],
"valid-typeof": [2],
"vars-on-top": [0],
"wrap-iife": [2],
"wrap-regex": [2],
"yield-star-spacing": [2, { "before": false, "after": true }],
"yoda": [2, "never", { "exceptRange": true, "onlyEquality": false }]
}

View File

Before

Width:  |  Height:  |  Size: 459 KiB

After

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 552 KiB

After

Width:  |  Height:  |  Size: 552 KiB

View File

Before

Width:  |  Height:  |  Size: 236 KiB

After

Width:  |  Height:  |  Size: 236 KiB

View File

Before

Width:  |  Height:  |  Size: 702 KiB

After

Width:  |  Height:  |  Size: 702 KiB

View File

Before

Width:  |  Height:  |  Size: 328 KiB

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

View File

@@ -0,0 +1,5 @@
require('../node_modules/select2/select2.css');
require('../node_modules/select2-bootstrap-css/select2-bootstrap.min.css');
require('../node_modules/jquery-ui/themes/base/jquery-ui.css');
require('select2');
require('../vendor/select2.sortable.js');

View File

@@ -0,0 +1 @@
require('../stylesheets/less/index.less');

View File

@@ -0,0 +1,258 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
var px = require('./modules/caravel.js');
var d3 = require('d3');
var showModal = require('./modules/utils.js').showModal;
require('bootstrap');
var ace = require('brace');
require('brace/mode/css');
require('brace/theme/crimson_editor');
require('./caravel-select2.js');
require('../node_modules/gridster/dist/jquery.gridster.min.css');
require('../node_modules/gridster/dist/jquery.gridster.min.js');
var Dashboard = function (dashboardData) {
var dashboard = $.extend(dashboardData, {
filters: {},
init: function () {
this.initDashboardView();
px.initFavStars();
var sliceObjects = [],
dash = this;
dashboard.slices.forEach(function (data) {
if (data.error) {
var html = '<div class="alert alert-danger">' + data.error + '</div>';
$("#slice_" + data.slice_id).find('.token').html(html);
} else {
var slice = px.Slice(data, dash);
$("#slice_" + data.slice_id).find('a.refresh').click(function () {
slice.render(true);
});
sliceObjects.push(slice);
slice.render();
}
});
this.slices = sliceObjects;
},
setFilter: function (slice_id, col, vals) {
this.addFilter(slice_id, col, vals, false);
},
addFilter: function (slice_id, col, vals, merge) {
if (merge === undefined) {
merge = true;
}
if (!(slice_id in this.filters)) {
this.filters[slice_id] = {};
}
if (!(col in this.filters[slice_id]) || !merge) {
this.filters[slice_id][col] = vals;
} else {
this.filters[slice_id][col] = d3.merge([this.filters[slice_id][col], vals]);
}
this.refreshExcept(slice_id);
},
readFilters: function () {
// Returns a list of human readable active filters
return JSON.stringify(this.filters, null, 4);
},
refreshExcept: function (slice_id) {
var immune = this.metadata.filter_immune_slice || [];
this.slices.forEach(function (slice) {
if (slice.data.slice_id !== slice_id && immune.indexOf(slice.data.slice_id) === -1) {
slice.render();
}
});
},
clearFilters: function (slice_id) {
delete this.filters[slice_id];
this.refreshExcept(slice_id);
},
removeFilter: function (slice_id, col, vals) {
if (slice_id in this.filters) {
if (col in this.filters[slice_id]) {
var a = [];
this.filters[slice_id][col].forEach(function (v) {
if (vals.indexOf(v) < 0) {
a.push(v);
}
});
this.filters[slice_id][col] = a;
}
}
this.refreshExcept(slice_id);
},
getSlice: function (slice_id) {
slice_id = parseInt(slice_id, 10);
for (var i=0; i < this.slices.length; i++) {
if (this.slices[i].data.slice_id === slice_id) {
return this.slices[i];
}
}
},
initDashboardView: function () {
dashboard = this;
var gridster = $(".gridster ul").gridster({
autogrow_cols: true,
widget_margins: [10, 10],
widget_base_dimensions: [95, 95],
draggable: {
handle: '.drag'
},
resize: {
enabled: true,
stop: function (e, ui, element) {
dashboard.getSlice($(element).attr('slice_id')).resize();
}
},
serialize_params: function (_w, wgd) {
return {
slice_id: $(_w).attr('slice_id'),
col: wgd.col,
row: wgd.row,
size_x: wgd.size_x,
size_y: wgd.size_y
};
}
}).data('gridster');
// Displaying widget controls on hover
$('.chart-header').hover(
function () {
$(this).find('.chart-controls').fadeIn(300);
},
function () {
$(this).find('.chart-controls').fadeOut(300);
}
);
$("div.gridster").css('visibility', 'visible');
$("#savedash").click(function () {
var expanded_slices = {};
$.each($(".slice_info"), function (i, d) {
var widget = $(this).parents('.widget');
var slice_description = widget.find('.slice_description');
if (slice_description.is(":visible")) {
expanded_slices[$(d).attr('slice_id')] = true;
}
});
var data = {
positions: gridster.serialize(),
css: editor.getValue(),
expanded_slices: expanded_slices
};
$.ajax({
type: "POST",
url: '/caravel/save_dash/' + dashboard.id + '/',
data: {
data: JSON.stringify(data)
},
success: function () {
showModal({
title: "Success",
body: "This dashboard was saved successfully."
});
},
error: function (error) {
showModal({
title: "Error",
body: "Sorry, there was an error saving this dashboard:<br />" + error
});
console.warn("Save dashboard error", error);
}
});
});
var editor = ace.edit("dash_css");
editor.$blockScrolling = Infinity;
editor.setTheme("ace/theme/crimson_editor");
editor.setOptions({
minLines: 16,
maxLines: Infinity,
useWorker: false
});
editor.getSession().setMode("ace/mode/css");
$(".select2").select2({
dropdownAutoWidth: true
});
$("#css_template").on("change", function () {
var css = $(this).find('option:selected').data('css');
editor.setValue(css);
$('#dash_css').val(css);
injectCss("dashboard-template", css);
});
$('#filters').click(function () {
showModal({
title: "<span class='fa fa-info-circle'></span> Current Global Filters",
body: "The following global filters are currently applied:<br/>" + dashboard.readFilters()
});
});
$('#refresh_dash').click(function () {
dashboard.slices.forEach(function (slice) {
slice.render(true);
});
});
$("a.remove-chart").click(function () {
var li = $(this).parents("li");
gridster.remove_widget(li);
});
$("li.widget").click(function (e) {
var $this = $(this);
var $target = $(e.target);
if ($target.hasClass("slice_info")) {
$this.find(".slice_description").slideToggle(0, function () {
$this.find('.refresh').click();
});
} else if ($target.hasClass("controls-toggle")) {
$this.find(".chart-controls").toggle();
}
});
editor.on("change", function () {
var css = editor.getValue();
$('#dash_css').val(css);
injectCss("dashboard-template", css);
});
var css = $('.dashboard').data('css');
injectCss("dashboard-template", css);
// Injects the passed css string into a style sheet with the specified className
// If a stylesheet doesn't exist with the passed className, one will be injected into <head>
function injectCss(className, css) {
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.querySelector('.' + className);
if (!style) {
if (className.split(' ').length > 1) {
throw new Error("This method only supports selections with a single class name.");
}
style = document.createElement('style');
style.className = className;
style.type = 'text/css';
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.innerHTML = css;
}
}
}
});
dashboard.init();
return dashboard;
};
$(document).ready(function () {
Dashboard($('.dashboard').data('dashboard'));
$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
});

View File

@@ -0,0 +1,352 @@
// Javascript for the explorer page
// Init explorer view -> load vis dependencies -> read data (from dynamic html) -> render slice
// nb: to add a new vis, you must also add a Python fn in viz.py
//
// js
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
var px = require('./modules/caravel.js');
var showModal = require('./modules/utils.js').showModal;
require('jquery-ui');
$.widget.bridge('uitooltip', $.ui.tooltip); // Shutting down jq-ui tooltips
require('bootstrap');
require('./caravel-select2.js');
require('../node_modules/bootstrap-toggle/js/bootstrap-toggle.min.js');
// css
require('../vendor/pygments.css');
require('../node_modules/bootstrap-toggle/css/bootstrap-toggle.min.css');
var slice;
function prepForm() {
var i = 1;
// Assigning the right id to form elements in filters
$("#filters > div").each(function () {
$(this).attr("id", function () {
return "flt_" + i;
});
$(this).find("#flt_col_0")
.attr("id", function () {
return "flt_col_" + i;
})
.attr("name", function () {
return "flt_col_" + i;
});
$(this).find("#flt_op_0")
.attr("id", function () {
return "flt_op_" + i;
})
.attr("name", function () {
return "flt_op_" + i;
});
$(this).find("#flt_eq_0")
.attr("id", function () {
return "flt_eq_" + i;
})
.attr("name", function () {
return "flt_eq_" + i;
});
i++;
});
}
function query(force, pushState) {
if (force === undefined) {
force = false;
}
if (pushState !== false) {
history.pushState({}, document.title, slice.querystring());
}
$('.query-and-save button').attr('disabled', 'disabled');
$('.btn-group.results span,a').attr('disabled', 'disabled');
$('div.alert').remove();
$('#is_cached').hide();
prepForm();
slice.render(force);
}
function initExploreView() {
function get_collapsed_fieldsets() {
var collapsed_fieldsets = $("#collapsed_fieldsets").val();
if (collapsed_fieldsets !== undefined && collapsed_fieldsets !== "") {
collapsed_fieldsets = collapsed_fieldsets.split('||');
} else {
collapsed_fieldsets = [];
}
return collapsed_fieldsets;
}
function toggle_fieldset(legend, animation) {
var parent = legend.parent();
var fieldset = parent.find(".legend_label").text();
var collapsed_fieldsets = get_collapsed_fieldsets();
var index;
if (parent.hasClass("collapsed")) {
if (animation) {
parent.find(".panel-body").slideDown();
} else {
parent.find(".panel-body").show();
}
parent.removeClass("collapsed");
parent.find("span.collapser").text("[-]");
// removing from array, js is overcomplicated
index = collapsed_fieldsets.indexOf(fieldset);
if (index !== -1) {
collapsed_fieldsets.splice(index, 1);
}
} else { // not collapsed
if (animation) {
parent.find(".panel-body").slideUp();
} else {
parent.find(".panel-body").hide();
}
parent.addClass("collapsed");
parent.find("span.collapser").text("[+]");
index = collapsed_fieldsets.indexOf(fieldset);
if (index === -1 && fieldset !== "" && fieldset !== undefined) {
collapsed_fieldsets.push(fieldset);
}
}
$("#collapsed_fieldsets").val(collapsed_fieldsets.join("||"));
}
px.initFavStars();
$('form .panel-heading').click(function () {
toggle_fieldset($(this), true);
$(this).css('cursor', 'pointer');
});
function copyURLToClipboard(url) {
var textArea = document.createElement("textarea");
textArea.style.position = 'fixed';
textArea.style.left = '-1000px';
textArea.value = url;
document.body.appendChild(textArea);
textArea.select();
try {
var successful = document.execCommand('copy');
if (!successful) {
throw new Error("Not successful");
}
} catch (err) {
window.alert("Sorry, your browser does not support copying. Use Ctrl / Cmd + C!");
}
document.body.removeChild(textArea);
return successful;
}
$('#shortner').click(function () {
$.ajax({
type: "POST",
url: '/r/shortner/',
data: {
data: '/' + window.location.pathname + slice.querystring()
},
success: function (data) {
var close = '<a style="cursor: pointer;"><i class="fa fa-close" id="close_shortner"></i></a>';
var copy = '<a style="cursor: pointer;"><i class="fa fa-clipboard" title="Copy to clipboard" id="copy_url"></i></a>';
var spaces = '&nbsp;&nbsp;&nbsp;';
var popover = data + spaces + copy + spaces + close;
var $shortner = $('#shortner')
.popover({
content: popover,
placement: 'left',
html: true,
trigger: 'manual'
})
.popover('show');
$('#copy_url').tooltip().click(function () {
var success = copyURLToClipboard(data);
if (success) {
$(this).attr("data-original-title", "Copied!").tooltip('fixTitle').tooltip('show');
window.setTimeout(destroyPopover, 1200);
}
});
$('#close_shortner').click(destroyPopover);
function destroyPopover() {
$shortner.popover('destroy');
}
},
error: function (error) {
showModal({
title: "Error",
body: "Sorry, an error occurred during this operation:<br/>" + error
});
console.warn("Short URL error", error);
}
});
});
$("#viz_type").change(function () {
$("#query").submit();
});
$("#datasource_id").change(function () {
var url = $(this).find('option:selected').attr('url');
window.location = url;
});
var collapsed_fieldsets = get_collapsed_fieldsets();
for (var i = 0; i < collapsed_fieldsets.length; i++) {
toggle_fieldset($('legend:contains("' + collapsed_fieldsets[i] + '")'), false);
}
$(".select2").select2({
dropdownAutoWidth: true
});
$(".select2Sortable").select2({
dropdownAutoWidth: true
});
$(".select2Sortable").select2Sortable({
bindOrder: 'sortableStop'
});
$("form").show();
$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
$(".ui-helper-hidden-accessible").remove(); // jQuery-ui 1.11+ creates a div for every tooltip
function set_filters() {
for (var i = 1; i < 10; i++) {
var eq = px.getParam("flt_eq_" + i);
if (eq !== '') {
add_filter(i);
}
}
}
set_filters();
function add_filter(i) {
var cp = $("#flt0").clone();
$(cp).appendTo("#filters");
$(cp).show();
if (i !== undefined) {
$(cp).find("#flt_eq_0").val(px.getParam("flt_eq_" + i));
$(cp).find("#flt_op_0").val(px.getParam("flt_op_" + i));
$(cp).find("#flt_col_0").val(px.getParam("flt_col_" + i));
}
$(cp).find('select').select2();
$(cp).find('.remove').click(function () {
$(this).parent().parent().remove();
});
}
$(window).bind("popstate", function (event) {
// Browser back button
var returnLocation = history.location || document.location;
// Could do something more lightweight here, but we're not optimizing
// for the use of the back button anyways
returnLocation.reload();
});
$("#plus").click(add_filter);
$("#btn_save").click(function () {
var slice_name = prompt("Name your slice!");
if (slice_name !== "" && slice_name !== null) {
$("#slice_name").val(slice_name);
prepForm();
$("#action").val("save");
$("#query").submit();
}
});
$("#btn_overwrite").click(function () {
var flag = confirm("Overwrite slice [" + $("#slice_name").val() + "] !?");
if (flag) {
$("#action").val("overwrite");
prepForm();
$("#query").submit();
}
});
$(".query").click(function () {
query(true);
});
function create_choices(term, data) {
var filtered = $(data).filter(function () {
return this.text.localeCompare(term) === 0;
});
if (filtered.length === 0) {
return {
id: term,
text: term
};
}
}
function initSelectionToValue(element, callback) {
callback({
id: element.val(),
text: element.val()
});
}
$(".select2_freeform").each(function () {
var parent = $(this).parent();
var name = $(this).attr('name');
var l = [];
var selected = '';
for (var i = 0; i < this.options.length; i++) {
l.push({
id: this.options[i].value,
text: this.options[i].text
});
if (this.options[i].selected) {
selected = this.options[i].value;
}
}
parent.append(
'<input class="' + $(this).attr('class') + '" name="' + name + '" type="text" value="' + selected + '">'
);
$("input[name='" + name + "']").select2({
createSearchChoice: create_choices,
initSelection: initSelectionToValue,
dropdownAutoWidth: true,
multiple: false,
data: l
});
$(this).remove();
});
}
$(document).ready(function () {
initExploreView();
// Dynamically register this visualization
var visType = window.viz_type.value;
px.registerViz(visType);
var data = $('.slice').data('slice');
slice = px.Slice(data);
//
$('.slice').data('slice', slice);
// call vis render method, which issues ajax
query(false, false);
// make checkbox inputs display as toggles
$(':checkbox')
.addClass('pull-right')
.attr("data-onstyle", "default")
.bootstrapToggle({
size: 'mini'
});
$('div.toggle').addClass('pull-right');
slice.bindResizeToWindowResize();
});

View File

@@ -0,0 +1,18 @@
var $ = require('jquery');
var jQuery = $;
import React from 'react';
import { render } from 'react-dom';
import { Jumbotron } from 'react-bootstrap';
class App extends React.Component {
render () {
return (
<Jumbotron>
<h1>Caravel</h1>
<p>Extensible visualization tool for exploring data from any database.</p>
</Jumbotron>
);
}
}
render(<App />, document.getElementById('app'));

View File

@@ -0,0 +1,381 @@
var $ = require('jquery');
var jQuery = $;
var d3 = require('d3');
// vis sources
var sourceMap = {
area: 'nvd3_vis.js',
bar: 'nvd3_vis.js',
bubble: 'nvd3_vis.js',
big_number: 'big_number.js',
big_number_total: 'big_number.js',
compare: 'nvd3_vis.js',
dist_bar: 'nvd3_vis.js',
directed_force: 'directed_force.js',
filter_box: 'filter_box.js',
heatmap: 'heatmap.js',
iframe: 'iframe.js',
line: 'nvd3_vis.js',
markup: 'markup.js',
para: 'parallel_coordinates.js',
pie: 'nvd3_vis.js',
pivot_table: 'pivot_table.js',
sankey: 'sankey.js',
sunburst: 'sunburst.js',
table: 'table.js',
word_cloud: 'word_cloud.js',
world_map: 'world_map.js'
};
var color = function () {
// Color related utility functions go in this object
var bnbColors = [
//rausch hackb kazan babu lima beach barol
'#ff5a5f', '#7b0051', '#007A87', '#00d1c1', '#8ce071', '#ffb400', '#b4a76c',
'#ff8083', '#cc0086', '#00a1b3', '#00ffeb', '#bbedab', '#ffd266', '#cbc29a',
'#ff3339', '#ff1ab1', '#005c66', '#00b3a5', '#55d12e', '#b37e00', '#988b4e'
];
var spectrums = {
blue_white_yellow: ['#00d1c1', 'white', '#ffb400'],
fire: ['white', 'yellow', 'red', 'black'],
white_black: ['white', 'black'],
black_white: ['black', 'white']
};
var colorBnb = function () {
// Color factory
var seen = {};
return function (s) {
// next line is for caravel series that should have the same color
s = s.replace('---', '');
if (seen[s] === undefined) {
seen[s] = Object.keys(seen).length;
}
return this.bnbColors[seen[s] % this.bnbColors.length];
};
};
var colorScalerFactory = function (colors, data, accessor) {
// Returns a linear scaler our of an array of color
if (!Array.isArray(colors)) {
colors = spectrums[colors];
}
var ext = [0, 1];
if (data !== undefined) {
ext = d3.extent(data, accessor);
}
var points = [];
var chunkSize = (ext[1] - ext[0]) / colors.length;
$.each(colors, function (i, c) {
points.push(i * chunkSize);
});
return d3.scale.linear().domain(points).range(colors);
};
return {
bnbColors: bnbColors,
category21: colorBnb(),
colorScalerFactory: colorScalerFactory
};
};
var px = (function () {
var visualizations = {};
var slice;
function getParam(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
function UTC(dttm) {
return new Date(dttm.getUTCFullYear(), dttm.getUTCMonth(), dttm.getUTCDate(), dttm.getUTCHours(), dttm.getUTCMinutes(), dttm.getUTCSeconds());
}
var tickMultiFormat = d3.time.format.multi([
[".%L", function (d) {
return d.getMilliseconds();
}], // If there are millisections, show only them
[":%S", function (d) {
return d.getSeconds();
}], // If there are seconds, show only them
["%a %b %d, %I:%M %p", function (d) {
return d.getMinutes() !== 0;
}], // If there are non-zero minutes, show Date, Hour:Minute [AM/PM]
["%a %b %d, %I %p", function (d) {
return d.getHours() !== 0;
}], // If there are hours that are multiples of 3, show date and AM/PM
["%a %b %d, %Y", function (d) {
return d.getDate() !== 1;
}], // If not the first of the month, do "month day, year."
["%B %Y", function (d) {
return d.getMonth() !== 0 && d.getDate() === 1;
}], // If the first of the month, do "month day, year."
["%Y", function (d) {
return true;
}] // fall back on month, year
]);
function formatDate(dttm) {
var d = UTC(new Date(dttm));
//d = new Date(d.getTime() - 1 * 60 * 60 * 1000);
return tickMultiFormat(d);
}
function timeFormatFactory(d3timeFormat) {
var f = d3.time.format(d3timeFormat);
return function (dttm) {
var d = UTC(new Date(dttm));
return f(d);
};
}
function initFavStars() {
var baseUrl = '/caravel/favstar/';
// Init star behavihor for favorite
function show() {
if ($(this).hasClass('selected')) {
$(this).html('<i class="fa fa-star"></i>');
} else {
$(this).html('<i class="fa fa-star-o"></i>');
}
}
$('.favstar')
.attr('title', 'Click to favorite/unfavorite')
.each(show)
.each(function () {
var url = baseUrl + $(this).attr("class_name");
var star = this;
url += '/' + $(this).attr("obj_id") + '/';
$.getJSON(url + 'count/', function (data) {
if (data.count > 0) {
$(star)
.addClass('selected')
.each(show);
}
});
})
.click(function () {
$(this).toggleClass('selected');
var url = baseUrl + $(this).attr("class_name");
url += '/' + $(this).attr("obj_id") + '/';
if ($(this).hasClass('selected')) {
url += 'select/';
} else {
url += 'unselect/';
}
$.get(url);
$(this).each(show);
})
.tooltip();
}
var Slice = function (data, dashboard) {
var timer;
var token = $('#' + data.token);
var container_id = data.token + '_con';
var selector = '#' + container_id;
var container = $(selector);
var slice_id = data.slice_id;
var dttm = 0;
var stopwatch = function () {
dttm += 10;
var num = dttm / 1000;
$('#timer').text(num.toFixed(2) + " sec");
};
var qrystr = '';
var always = function (data) {
//Private f, runs after done and error
clearInterval(timer);
$('#timer').removeClass('btn-warning');
};
slice = {
data: data,
container: container,
container_id: container_id,
selector: selector,
querystring: function () {
var parser = document.createElement('a');
parser.href = data.json_endpoint;
if (dashboard !== undefined) {
var flts = encodeURIComponent(JSON.stringify(dashboard.filters));
qrystr = parser.search + "&extra_filters=" + flts;
} else if ($('#query').length === 0) {
qrystr = parser.search;
} else {
qrystr = '?' + $('#query').serialize();
}
return qrystr;
},
getWidgetHeader: function () {
return this.container.parents("li.widget").find(".chart-header");
},
jsonEndpoint: function () {
var parser = document.createElement('a');
parser.href = data.json_endpoint;
var endpoint = parser.pathname + this.querystring();
endpoint += "&json=true";
endpoint += "&force=" + this.force;
return endpoint;
},
done: function (data) {
clearInterval(timer);
token.find("img.loading").hide();
container.show();
var cachedSelector = null;
if (dashboard === undefined) {
cachedSelector = $('#is_cached');
if (data !== undefined && data.is_cached) {
cachedSelector
.attr('title', 'Served from data cached at ' + data.cached_dttm + '. Click to force-refresh')
.show()
.tooltip('fixTitle');
} else {
cachedSelector.hide();
}
} else {
var refresh = this.getWidgetHeader().find('.refresh');
if (data !== undefined && data.is_cached) {
refresh
.addClass('danger')
.attr(
'title',
'Served from data cached at ' + data.cached_dttm + '. Click to force-refresh')
.tooltip('fixTitle');
} else {
refresh
.removeClass('danger')
.attr(
'title',
'Click to force-refresh')
.tooltip('fixTitle');
}
}
if (data !== undefined) {
$("#query_container").html(data.query);
}
$('#timer').removeClass('btn-warning');
$('#timer').addClass('btn-success');
$('span.query').removeClass('disabled');
$('#json').click(function () {
window.location = data.json_endpoint;
});
$('#standalone').click(function () {
window.location = data.standalone_endpoint;
});
$('#csv').click(function () {
window.location = data.csv_endpoint;
});
$('.btn-group.results span,a').removeAttr('disabled');
$('.query-and-save button').removeAttr('disabled');
always(data);
},
error: function (msg) {
token.find("img.loading").hide();
var err = '<div class="alert alert-danger">' + msg + '</div>';
container.html(err);
container.show();
$('span.query').removeClass('disabled');
$('#timer').addClass('btn-danger');
$('.btn-group.results span,a').removeAttr('disabled');
$('.query-and-save button').removeAttr('disabled');
always(data);
},
width: function () {
return token.width();
},
height: function () {
var others = 0;
var widget = container.parents('.widget');
var slice_description = widget.find('.slice_description');
if (slice_description.is(":visible")) {
others += widget.find('.slice_description').height() + 25;
}
others += widget.find('.chart-header').height();
return widget.height() - others - 10;
},
bindResizeToWindowResize: function () {
var resizeTimer;
$(window).on('resize', function (e) {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
slice.resize();
}, 500);
});
},
render: function (force) {
if (force === undefined) {
force = false;
}
this.force = force;
token.find("img.loading").show();
container.hide();
container.html('');
dttm = 0;
timer = setInterval(stopwatch, 10);
$('#timer').removeClass('btn-danger btn-success');
$('#timer').addClass('btn-warning');
this.viz.render();
},
resize: function () {
token.find("img.loading").show();
container.hide();
container.html('');
this.viz.render();
this.viz.resize();
},
addFilter: function (col, vals) {
if (dashboard !== undefined) {
dashboard.addFilter(slice_id, col, vals);
}
},
setFilter: function (col, vals) {
if (dashboard !== undefined) {
dashboard.setFilter(slice_id, col, vals);
}
},
clearFilter: function () {
if (dashboard !== undefined) {
delete dashboard.clearFilter(slice_id);
}
},
removeFilter: function (col, vals) {
if (dashboard !== undefined) {
delete dashboard.removeFilter(slice_id, col, vals);
}
}
};
var visType = data.form_data.viz_type;
px.registerViz(visType);
slice.viz = visualizations[data.form_data.viz_type](slice);
return slice;
};
function registerViz(name) {
var visSource = sourceMap[name];
if (visSource) {
var visFactory = require('../../visualizations/' + visSource);
if (typeof visFactory === 'function') {
visualizations[name] = visFactory;
}
} else {
throw new Error("require(" + name + ") failed.");
}
}
// Export public functions
return {
registerViz: registerViz,
Slice: Slice,
formatDate: formatDate,
timeFormatFactory: timeFormatFactory,
color: color(),
getParam: getParam,
initFavStars: initFavStars
};
})();
module.exports = px;

View File

@@ -0,0 +1,80 @@
var $ = require('jquery');
var d3 = require('d3');
/*
Utility function that takes a d3 svg:text selection and a max width, and splits the
text's text across multiple tspan lines such that any given line does not exceed max width
If text does not span multiple lines AND adjustedY is passed, will set the text to the passed val
*/
function wrapSvgText(text, width, adjustedY) {
var lineHeight = 1; // ems
text.each(function () {
var text = d3.select(this),
words = text.text().split(/\s+/),
word,
line = [],
lineNumber = 0,
x = text.attr("x"),
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
var didWrap = false;
for (var i = 0; i < words.length; i++) {
word = words[i];
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop(); // remove word that pushes over the limit
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
didWrap = true;
}
}
if (!didWrap && typeof adjustedY !== "undefined") {
tspan.attr("y", adjustedY);
}
});
}
/**
* Sets the body and title content of a modal, and shows it. Assumes HTML for modal exists and that
* it handles closing (i.e., works with bootstrap)
*
* @param {object} options object of the form
* {
* title: {string},
* body: {string},
* modalSelector: {string, default: '.misc-modal' },
* titleSelector: {string, default: '.misc-modal .modal-title' },
* bodySelector: {string, default: '.misc-modal .modal-body' },
* }
*/
function showModal(options) {
options.modalSelector = options.modalSelector || ".misc-modal";
options.titleSelector = options.titleSelector || ".misc-modal .modal-title";
options.bodySelector = options.bodySelector || ".misc-modal .modal-body";
$(options.titleSelector).html(options.title || "");
$(options.bodySelector).html(options.body || "");
$(options.modalSelector).modal("show");
}
module.exports = {
wrapSvgText: wrapSvgText,
showModal: showModal
};

View File

@@ -0,0 +1,103 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
var showModal = require('./modules/utils.js').showModal;
require('select2');
require('datatables.net-bs');
require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
require('bootstrap');
var ace = require('brace');
require('brace/mode/sql');
require('brace/theme/crimson_editor');
$(document).ready(function () {
function getParam(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
function initSqlEditorView() {
var database_id = $('#database_id').val();
var editor = ace.edit("sql");
editor.$blockScrolling = Infinity;
editor.getSession().setUseWrapMode(true);
$('#sql').hide();
editor.setTheme("ace/theme/crimson_editor");
editor.setOptions({
minLines: 16,
maxLines: Infinity
});
editor.getSession().setMode("ace/mode/sql");
editor.focus();
$("select").select2({
dropdownAutoWidth: true
});
function showTableMetadata() {
$(".metadata").load(
'/caravel/table/' + database_id + '/' + $("#dbtable").val() + '/');
}
$("#dbtable").on("change", showTableMetadata);
showTableMetadata();
$("#create_view").click(function () {
showModal({
title: "Error",
body: "Sorry, this feature is not yet implemented"
});
});
$(".sqlcontent").show();
function selectStarOnClick() {
$.ajax('/caravel/select_star/' + database_id + '/' + $("#dbtable").val() + '/')
.done(function (msg) {
editor.setValue(msg);
});
}
$("#select_star").click(selectStarOnClick);
editor.setValue(getParam('sql'));
$(window).bind("popstate", function (event) {
// Could do something more lightweight here, but we're not optimizing
// for the use of the back button anyways
editor.setValue(getParam('sql'));
$("#run").click();
});
$("#run").click(function () {
$('#results').hide(0);
$('#loading').show(0);
history.pushState({}, document.title, '?sql=' + encodeURIComponent(editor.getValue()));
$.ajax({
type: "POST",
url: '/caravel/runsql/',
data: {
data: JSON.stringify({
database_id: $('#database_id').val(),
sql: editor.getSession().getValue()
})
},
success: function (data) {
$('#loading').hide(0);
$('#results').show(0);
$('#results').html(data);
$('table.sql_results').DataTable({
paging: false,
searching: true,
aaSorting: []
});
},
error: function (err, err2) {
$('#loading').hide(0);
$('#results').show(0);
$('#results').html(err.responseText);
}
});
});
}
initSqlEditorView();
});

View File

@@ -0,0 +1,13 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
var px = require('./modules/caravel.js');
require('bootstrap');
$(document).ready(function () {
var slice;
var data = $('.slice').data('slice');
slice = px.Slice(data);
slice.render();
slice.bindResizeToWindowResize();
});

View File

@@ -0,0 +1,67 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
require('../stylesheets/welcome.css');
require('bootstrap');
require('datatables.net-bs');
require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
require('../node_modules/cal-heatmap/cal-heatmap.css');
var CalHeatMap = require('cal-heatmap');
function modelViewTable(selector, modelEndpoint) {
// Builds a dataTable from a flask appbuilder api endpoint
$.getJSON(modelEndpoint + '/api/read', function (data) {
var tableData = jQuery.map(data.result, function (el, i) {
var row = $.map(data.list_columns, function (col, i) {
return el[col];
});
return [row];
});
var cols = jQuery.map(data.list_columns, function (col, i) {
return { sTitle: data.label_columns[col] };
});
var panel = $(selector).parents('.panel');
panel.find("img.loading").remove();
$(selector).DataTable({
aaData: tableData,
aoColumns: cols,
bPaginate: true,
pageLength: 10,
bLengthChange: false,
aaSorting: [],
searching: true,
bInfo: false
});
// Hack to move the searchbox in the right spot
var search = panel.find(".dataTables_filter input");
search.addClass('form-control').detach();
search.appendTo(panel.find(".search"));
panel.find('.dataTables_filter').remove();
// Hack to display the page navigator properly
panel.find('.col-sm-5').remove();
var nav = panel.find('.col-sm-7');
nav.removeClass('col-sm-7');
nav.addClass('col-sm-12');
$(selector).slideDown();
$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
});
}
$(document).ready(function () {
var cal = new CalHeatMap();
cal.init({
start: new Date().setFullYear(new Date().getFullYear() - 1),
range: 13,
data: '/caravel/activity_per_day',
domain: "month",
subDomain: "day",
itemName: "action",
tooltip: true
});
modelViewTable('#dash_table', '/dashboardmodelviewasync');
modelViewTable('#slice_table', '/sliceasync');
});

View File

@@ -0,0 +1,78 @@
{
"name": "caravel",
"version": "0.1.0",
"description": "Any database to any visualization",
"directories": {
"doc": "docs",
"test": "tests"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack -d --watch --colors",
"prod": "webpack -p --colors",
"lint": "npm run --silent lint:js",
"lint:js": "eslint --ignore-path=.eslintignore --ext .js ."
},
"repository": {
"type": "git",
"url": "git+https://github.com/airbnb/caravel.git"
},
"keywords": [
"big",
"data",
"exploratory",
"analysis",
"react",
"d3",
"airbnb",
"nerds",
"database",
"flask"
],
"author": "Airbnb",
"bugs": {
"url": "https://github.com/airbnb/caravel/issues"
},
"homepage": "https://github.com/airbnb/caravel#readme",
"dependencies": {
"babel-loader": "^6.2.1",
"babel-polyfill": "^6.3.14",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"bootstrap": "^3.3.6",
"bootstrap-datepicker": "^1.6.0",
"bootstrap-toggle": "^2.2.1",
"brace": "^0.7.0",
"cal-heatmap": "3.5.4",
"css-loader": "^0.23.1",
"d3": "^3.5.14",
"d3-cloud": "^1.2.1",
"d3-sankey": "^0.2.1",
"d3-tip": "^0.6.7",
"datamaps": "^0.4.4",
"datatables-bootstrap3-plugin": "^0.4.0",
"datatables.net-bs": "^1.10.11",
"exports-loader": "^0.6.3",
"font-awesome": "^4.5.0",
"gridster": "^0.5.6",
"imports-loader": "^0.6.5",
"jquery": "^2.2.1",
"jquery-ui": "^1.10.5",
"less": "^2.6.1",
"less-loader": "^2.2.2",
"nvd3": "1.8.2",
"react": "^0.14.7",
"react-bootstrap": "^0.28.3",
"react-dom": "^0.14.7",
"select2": "3.5",
"select2-bootstrap-css": "^1.4.6",
"style-loader": "^0.13.0",
"topojson": "^1.6.22",
"webpack": "^1.12.12"
},
"devDependencies": {
"eslint": "^2.2.0",
"file-loader": "^0.8.5",
"url-loader": "^0.5.7"
}
}

View File

@@ -1,5 +1,24 @@
html>body{
margin: 0px; !important
body {
margin: 0px !important;
}
.modal-dialog {
z-index: 1100;
}
.label {
font-size: 100%;
}
.no-wrap {
white-space: nowrap;
}
input.form-control {
background-color: white;
}
.chart-header a.danger {
color: red;
}
.col-left-fixed {
@@ -11,6 +30,12 @@ html>body{
margin-left: 365px;
}
.favstar {
margin-right: 10px;
opacity: 0.5;
cursor: pointer;
}
.slice_description{
padding: 8px;
margin: 5px;
@@ -24,7 +49,7 @@ html>body{
cursor: pointer;
}
.padded{
.padded {
padding: 10px;
}
@@ -33,9 +58,6 @@ html>body{
overflow: auto;
}
.slice_container {
//height: 100%;
}
.container-fluid {
text-align: left;
}
@@ -50,10 +72,24 @@ form div {
}
.navbar-brand a {
color: white;
text-decoration: none;
}
.navbar-brand a:hover {
color: white;
text-decoration: none;
}
.header span{
margin-left: 3px;
.header span {
margin-left: 5px;
}
.widget-is-cached {
display: none;
}
.header span.label {
margin-left: 5px;
margin-right: 5px;
}
#timer {
@@ -61,13 +97,12 @@ form div {
text-align: right;
}
.select2-results .select2-highlighted {
background-color: #005c66;
}
.notbtn {
cursor: default;
box-shadow: none;
border: 1px solid #ccc;
}
hr {
margin-top: 15px;
margin-bottom: 15px;
@@ -81,33 +116,12 @@ span.title-block {
font-size: 20px;
}
fieldset.fs-style {
font-family: Verdana, Arial, sans-serif;
font-size: small;
font-weight: normal;
border: 1px solid #CCC;
background-color: #F4F4F4;
border-radius: 6px;
padding: 10px;
margin: 0px 0px 10px 0px;
}
legend.legend-style {
font-size: 14px;
padding: 0px 6px;
cursor: pointer;
margin: 0px;
color: #444;
background-color: transparent;
font-weight: bold;
}
.nvtooltip {
position: relative; !important
//position: relative !important;
z-index: 888;
}
legend {
width: auto;
border-bottom: 0px;
.nvtooltip table td{
font-size: 11px !important;
}
.navbar {
-webkit-box-shadow: 0px 3px 3px #AAA;
@@ -118,46 +132,6 @@ legend {
.panel.panel-primary {
margin: 10px;
}
.index .carousel img {
max-height: 500px;
}
.index .carousel {
overflow: hidden;
height: 500px;
}
.index .carousel-caption h1 {
font-size: 80px;
}
.index .carousel-caption p {
font-size: 20px;
}
.index div.carousel-caption{
background: rgba(0,0,0,0.5);
border-radius: 20px;
top: 150px;
bottom: auto !important;
}
.index .carousel-inner > .item > img {
margin: 0 auto;
}
.index {
margin: -20px;
}
.index .carousel-indicators li {
background-color: #AAA;
border: 1px solid black;
}
.index .carousel-indicators .active {
background-color: #000;
border: 5px solid black;
}
.datasource .select2-container-multi .select2-choices {
height: 70px;
overflow: auto;
}
.datasource form div.form-control {
margin-bottom: 5px !important;
}
@@ -170,12 +144,11 @@ legend {
img.loading {
width: 40px;
}
.dashboard a i {
cursor: pointer;
}
.dashboard i.drag {
cursor: move; !important
cursor: move !important;
}
.dashboard .gridster .preview-holder {
z-index: 1;
@@ -186,11 +159,11 @@ img.loading {
}
.gridster li.widget{
list-style-type: none;
border: 1px solid gray;
overflow: hidden;
box-shadow: 2px 2px 2px #AAA;
border-radius: 5px;
background-color: white;
border-radius: 0;
margin: 5px;
border: 1px solid #ccc;
box-shadow: 2px 1px 5px -2px #aaa;
background-color: #fff;
}
.dashboard .gridster .dragging,
.dashboard .gridster .resizing {
@@ -209,38 +182,45 @@ img.loading {
font-size: 14px;
padding: 5px;
}
.dashboard div.gridster {
visibility: hidden
}
.dashboard div.slice_content {
width: 100%;
height: 100%;
}
.dashboard table.slice_header {
width: 100%;
height: 20px;
}
.dashboard li.widget.line,
.dashboard li.widget.bar,
.dashboard li.widget.compare,
.dashboard li.widget.area,
.dashboard li.widget.pie,
.dashboard li.widget.dist_bar,
.dashboard li.widget.sunburst {
overflow: visible; /* This allows elements within these widget typesin a dashboard to overflow */
}
.dashboard div.nvtooltip {
z-index: 888; /* this lets tool tips go on top of other slices */
}
.dashboard td.icons {
width: 50px;
}
.dashboard td.icons nobr {
display: none;
}
div.header {
font-weight: bold;
}
li.widget:hover {
z-index: 1000;
}
li.widget .chart-header {
padding: 5px;
background-color: #f1f1f1;
}
li.widget .chart-header a {
margin-left: 5px;
}
#is_cached {
display: none;
}
li.widget .chart-controls {
background-color: #f1f1f1;
position: absolute;
right: 0;
left: 0;
padding: 0px 5px;
opacity: 0.75;
display: none;
}
li.widget .slice_container {
overflow: auto;
}

View File

@@ -0,0 +1,616 @@
// Paper 3.3.5
// Bootswatch
// -----------------------------------------------------
@web-font-path: "https://fonts.googleapis.com/css?family=Roboto:300,400,500,700";
.web-font(@path) {
@import url("@{path}");
}
.web-font(@web-font-path);
// Navbar =====================================================================
.navbar {
border: none;
.box-shadow(0 1px 2px rgba(0,0,0,.3));
&-brand {
font-size: 24px;
}
&-inverse {
.navbar-form {
input[type=text],
input[type=password] {
color: #fff;
.box-shadow(inset 0 -1px 0 @navbar-inverse-link-color);
.placeholder(@navbar-inverse-link-color);
&:focus {
.box-shadow(inset 0 -2px 0 #fff);
}
}
}
}
}
// Buttons ====================================================================
#btn(@class,@bg) {
.btn-@{class} {
background-size: 200%;
background-position: 50%;
&:focus {
background-color: @bg;
}
&:hover,
&:active:hover {
background-color: darken(@bg, 6%);
}
&:active {
background-color: darken(@bg, 12%);
#gradient > .radial(darken(@bg, 12%) 10%, @bg 11%);
background-size: 1000%;
.box-shadow(2px 2px 4px rgba(0,0,0,.4));
}
}
}
#btn(default,@btn-default-bg);
#btn(primary,@btn-primary-bg);
#btn(success,@btn-success-bg);
#btn(info,@btn-info-bg);
#btn(warning,@btn-warning-bg);
#btn(danger,@btn-danger-bg);
#btn(link,#fff);
.btn {
text-transform: uppercase;
border: none;
.box-shadow(1px 1px 4px rgba(0,0,0,.4));
.transition(all 0.4s);
&-link {
border-radius: @btn-border-radius-base;
.box-shadow(none);
color: @btn-default-color;
&:hover,
&:focus {
.box-shadow(none);
color: @btn-default-color;
text-decoration: none;
}
}
&-default {
&.disabled {
background-color: rgba(0, 0, 0, 0.1);
color: rgba(0, 0, 0, 0.4);
opacity: 1;
}
}
}
.btn-group {
.btn + .btn,
.btn + .btn-group,
.btn-group + .btn,
.btn-group + .btn-group {
margin-left: 0;
}
&-vertical {
> .btn + .btn,
> .btn + .btn-group,
> .btn-group + .btn,
> .btn-group + .btn-group {
margin-top: 0;
}
}
}
// Typography =================================================================
body {
-webkit-font-smoothing: antialiased;
letter-spacing: .1px;
}
p {
margin: 0 0 1em;
}
input,
button {
-webkit-font-smoothing: antialiased;
letter-spacing: .1px;
}
a {
.transition(all 0.2s);
}
// Tables =====================================================================
.table-hover {
> tbody > tr,
> tbody > tr > th,
> tbody > tr > td {
.transition(all 0.2s);
}
}
// Forms ======================================================================
label {
font-weight: normal;
}
textarea,
textarea.form-control,
input.form-control,
input[type=text],
input[type=password],
input[type=email],
input[type=number],
[type=text].form-control,
[type=password].form-control,
[type=email].form-control,
[type=tel].form-control,
[contenteditable].form-control {
padding: 0;
border: none;
border-radius: 0;
-webkit-appearance: none;
.box-shadow(inset 0 -1px 0 #ddd);
font-size: 16px;
&:focus {
.box-shadow(inset 0 -2px 0 @brand-primary);
}
&[disabled],
&[readonly] {
.box-shadow(none);
border-bottom: 1px dotted #ddd;
}
&.input {
&-sm {
font-size: @font-size-small;
}
&-lg {
font-size: @font-size-large;
}
}
}
select,
select.form-control {
border: 0;
border-radius: 0;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
padding-left: 0;
padding-right: 0\9; // remove padding for < ie9 since default arrow can't be removed
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAMAAACelLz8AAAAJ1BMVEVmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmaP/QSjAAAADHRSTlMAAgMJC0uWpKa6wMxMdjkoAAAANUlEQVR4AeXJyQEAERAAsNl7Hf3X6xt0QL6JpZWq30pdvdadme+0PMdzvHm8YThHcT1H7K0BtOMDniZhWOgAAAAASUVORK5CYII=);
background-size: 13px;
background-repeat: no-repeat;
background-position: right center;
.box-shadow(inset 0 -1px 0 #ddd);
font-size: 16px;
line-height: 1.5;
&::-ms-expand {
display: none;
}
&.input {
&-sm {
font-size: @font-size-small;
}
&-lg {
font-size: @font-size-large;
}
}
&:focus {
.box-shadow(inset 0 -2px 0 @brand-primary);
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAMAAACelLz8AAAAJ1BMVEUhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISF8S9ewAAAADHRSTlMAAgMJC0uWpKa6wMxMdjkoAAAANUlEQVR4AeXJyQEAERAAsNl7Hf3X6xt0QL6JpZWq30pdvdadme+0PMdzvHm8YThHcT1H7K0BtOMDniZhWOgAAAAASUVORK5CYII=);
}
&[multiple] {
background: none;
}
}
.radio,
.radio-inline,
.checkbox,
.checkbox-inline {
label {
padding-left: 25px;
}
input[type="radio"],
input[type="checkbox"] {
margin-left: -25px;
}
}
input[type="radio"],
.radio input[type="radio"],
.radio-inline input[type="radio"] {
position: relative;
margin-top: 6px;
margin-right: 4px;
vertical-align: top;
border: none;
background-color: transparent;
-webkit-appearance: none;
appearance: none;
cursor: pointer;
&:focus {
outline: none;
}
&:before,
&:after {
content: "";
display: block;
width: 18px;
height: 18px;
border-radius: 50%;
.transition(240ms);
}
&:before {
position: absolute;
left: 0;
top: -3px;
background-color: @brand-primary;
.scale(0);
}
&:after {
position: relative;
top: -3px;
border: 2px solid @gray;
}
&:checked:before {
.scale(0.5);
}
&:disabled:checked:before {
background-color: @gray-light;
}
&:checked:after {
border-color: @brand-primary;
}
&:disabled:after,
&:disabled:checked:after {
border-color: @gray-light;
}
}
input[type="checkbox"],
.checkbox input[type="checkbox"],
.checkbox-inline input[type="checkbox"] {
position: relative;
border: none;
margin-bottom: -4px;
-webkit-appearance: none;
appearance: none;
cursor: pointer;
&:focus {
outline: none;
}
&:focus:after {
border-color: @brand-primary;
}
&:after {
content: "";
display: block;
width: 18px;
height: 18px;
margin-top: -2px;
margin-right: 5px;
border: 2px solid @gray;
border-radius: 2px;
.transition(240ms);
}
&:checked:before {
content: "";
position: absolute;
top: 0;
left: 6px;
display: table;
width: 6px;
height: 12px;
border: 2px solid #fff;
border-top-width: 0;
border-left-width: 0;
.rotate(45deg);
}
&:checked:after {
background-color: @brand-primary;
border-color: @brand-primary;
}
&:disabled:after {
border-color: @gray-light;
}
&:disabled:checked:after {
background-color: @gray-light;
border-color: transparent;
}
}
.has-warning {
input:not([type=checkbox]),
.form-control,
input.form-control[readonly],
input[type=text][readonly],
[type=text].form-control[readonly],
input:not([type=checkbox]):focus,
.form-control:focus {
border-bottom: none;
.box-shadow(inset 0 -2px 0 @brand-warning);
}
}
.has-error {
input:not([type=checkbox]),
.form-control,
input.form-control[readonly],
input[type=text][readonly],
[type=text].form-control[readonly],
input:not([type=checkbox]):focus,
.form-control:focus {
border-bottom: none;
.box-shadow(inset 0 -2px 0 @brand-danger);
}
}
.has-success {
input:not([type=checkbox]),
.form-control,
input.form-control[readonly],
input[type=text][readonly],
[type=text].form-control[readonly],
input:not([type=checkbox]):focus,
.form-control:focus {
border-bottom: none;
.box-shadow(inset 0 -2px 0 @brand-success);
}
}
// Remove the Bootstrap feedback styles for input addons
.input-group-addon {
.has-warning &, .has-error &, .has-success & {
color: @input-color;
border-color: @input-group-addon-border-color;
background-color: @input-group-addon-bg;
}
}
// Navs =======================================================================
.nav-tabs {
> li > a,
> li > a:focus {
margin-right: 0;
background-color: transparent;
border: none;
color: @navbar-default-link-color;
.box-shadow(inset 0 -1px 0 #ddd);
.transition(all 0.2s);
&:hover {
background-color: transparent;
.box-shadow(inset 0 -2px 0 @brand-primary);
color: @brand-primary;
}
}
& > li.active > a,
& > li.active > a:focus {
border: none;
.box-shadow(inset 0 -2px 0 @brand-primary);
color: @brand-primary;
&:hover {
border: none;
color: @brand-primary;
}
}
& > li.disabled > a {
.box-shadow(inset 0 -1px 0 #ddd);
}
&.nav-justified {
& > li > a,
& > li > a:hover,
& > li > a:focus,
& > .active > a,
& > .active > a:hover,
& > .active > a:focus {
border: none;
}
}
.dropdown-menu {
margin-top: 0;
}
}
.dropdown-menu {
margin-top: 0;
border: none;
.box-shadow(0 1px 4px rgba(0,0,0,.3));
}
// Indicators =================================================================
.alert {
border: none;
color: #fff;
&-success {
background-color: @brand-success;
}
&-info {
background-color: @brand-info;
}
&-warning {
background-color: @brand-warning;
}
&-danger {
background-color: @brand-danger;
}
a:not(.close),
.alert-link {
color: #fff;
font-weight: bold;
}
.close {
color: #fff;
}
}
.badge {
padding: 4px 6px 4px;
}
.progress {
position: relative;
z-index: 1;
height: 6px;
border-radius: 0;
.box-shadow(none);
&-bar {
.box-shadow(none);
&:last-child {
border-radius: 0 3px 3px 0;
}
&:last-child {
&:before {
display: block;
content: "";
position: absolute;
width: 100%;
height: 100%;
left: 0;
right: 0;
z-index: -1;
background-color: lighten(@progress-bar-bg, 35%);
}
}
&-success:last-child.progress-bar:before {
background-color: lighten(@brand-success, 35%);
}
&-info:last-child.progress-bar:before {
background-color: lighten(@brand-info, 45%);
}
&-warning:last-child.progress-bar:before {
background-color: lighten(@brand-warning, 35%);
}
&-danger:last-child.progress-bar:before {
background-color: lighten(@brand-danger, 25%);
}
}
}
// Progress bars ==============================================================
// Containers =================================================================
.close {
font-size: 34px;
font-weight: 300;
line-height: 24px;
opacity: 0.6;
.transition(all 0.2s);
&:hover {
opacity: 1;
}
}
.list-group {
&-item {
padding: 15px;
}
&-item-text {
color: @gray-light;
}
}
.well {
border-radius: 0;
.box-shadow(none);
}
.panel {
border: none;
border-radius: 2px;
.box-shadow(0 1px 4px rgba(0,0,0,.3));
&-heading {
border-bottom: none;
}
&-footer {
border-top: none;
}
}
.popover {
border: none;
.box-shadow(0 1px 4px rgba(0,0,0,.3));
}
.carousel {
&-caption {
h1, h2, h3, h4, h5, h6 {
color: inherit;
}
}
}

View File

@@ -0,0 +1,5 @@
// Index .less, any imports here will be included in the final css build
@import "~bootstrap/less/bootstrap.less";
@import "./variables.less";
@import "./bootswatch.less";

View File

@@ -0,0 +1,881 @@
// Modified from Bootswatch Paper 3.3.6
// Variables
// --------------------------------------------------
//== Colors
//
//## Airbnb colors
@rausch: #ff5a5f; // coral
@kazan: #007a87; // dark teal
@hackberry: #7b0051; // purple
@babu: #00d1c1; // light teal
@lima: #8ce071; // bright green
@beach: #ffb400; // yellow
@ebisu: #ffaa91; // peach
@tirol: #b4a76c; // khaki
@foggy: #9CA299; // dark grey
@hof: #565A5C; // light grey
//## Gray and brand colors for use across Bootstrap.
@gray-base: #000;
@gray-darker: lighten(@gray-base, 13.5%); // #222
@gray-dark: #212121;
@gray: #666;
@gray-light: #bbb;
@gray-lighter: lighten(@gray-base, 93.5%); // #eee
@brand-primary: darken(@babu, 5%);
@brand-success: darken(@lima, 15%);
@brand-info: @beach;
@brand-warning: @hackberry;
@brand-danger: darken(@rausch, 5%);
//== Scaffolding
//
//## Settings for some of the most global styles.
//** Background color for `<body>`.
@body-bg: #fff;
//** Global text color on `<body>`.
@text-color: @gray;
//** Global textual link color.
@link-color: @brand-primary;
//** Link hover color set via `darken()` function.
@link-hover-color: darken(@link-color, 15%);
//** Link hover decoration.
@link-hover-decoration: underline;
//== Typography
//
//## Font, line-height, and color for body text, headings, and more.
@font-family-sans-serif: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif;
@font-family-serif: Georgia, "Times New Roman", Times, serif;
//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
@font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
@font-family-base: @font-family-sans-serif;
@font-size-base: 13px;
@font-size-large: ceil((@font-size-base * 1.25)); // ~18px
@font-size-small: ceil((@font-size-base * 0.85)); // ~12px
@font-size-h1: 56px;
@font-size-h2: 45px;
@font-size-h3: 34px;
@font-size-h4: 24px;
@font-size-h5: 20px;
@font-size-h6: 14px;
//** Unit-less `line-height` for use in components like buttons.
@line-height-base: 1.846; // 20/14
//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
@line-height-computed: floor((@font-size-base * @line-height-base)); // ~20px
//** By default, this inherits from the `<body>`.
@headings-font-family: inherit;
@headings-font-weight: 400;
@headings-line-height: 1.1;
@headings-color: #444;
//== Iconography
//
//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
//** Load fonts from this directory.
@icon-font-path: "../fonts/";
//** File name for all font files.
@icon-font-name: "glyphicons-halflings-regular";
//** Element ID within SVG icon file.
@icon-font-svg-id: "glyphicons_halflingsregular";
//== Components
//
//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
@padding-base-vertical: 6px;
@padding-base-horizontal: 16px;
@padding-large-vertical: 10px;
@padding-large-horizontal: 16px;
@padding-small-vertical: 5px;
@padding-small-horizontal: 10px;
@padding-xs-vertical: 1px;
@padding-xs-horizontal: 5px;
@line-height-large: 1.3333333; // extra decimals for Win 8.1 Chrome
@line-height-small: 1.5;
@border-radius-base: 3px;
@border-radius-large: 3px;
@border-radius-small: 3px;
//** Global color for active items (e.g., navs or dropdowns).
@component-active-color: #fff;
//** Global background color for active items (e.g., navs or dropdowns).
@component-active-bg: @brand-primary;
//** Width of the `border` for generating carets that indicator dropdowns.
@caret-width-base: 4px;
//** Carets increase slightly in size for larger components.
@caret-width-large: 5px;
//== Tables
//
//## Customizes the `.table` component with basic values, each used across all table variations.
//** Padding for `<th>`s and `<td>`s.
@table-cell-padding: 8px;
//** Padding for cells in `.table-condensed`.
@table-condensed-cell-padding: 5px;
//** Default background color used for all tables.
@table-bg: transparent;
//** Background color used for `.table-striped`.
@table-bg-accent: #f9f9f9;
//** Background color used for `.table-hover`.
@table-bg-hover: @gray-lighter;
@table-bg-active: @table-bg-hover;
//** Border color for table and cell borders.
@table-border-color: #ddd;
//== Buttons
//
//## For each of Bootstrap's buttons, define text, background and border color.
@btn-font-weight: normal;
@btn-default-color: #444;
@btn-default-bg: #fff;
@btn-default-border: transparent;
@btn-primary-color: #fff;
@btn-primary-bg: @brand-primary;
@btn-primary-border: transparent;
@btn-success-color: #fff;
@btn-success-bg: @brand-success;
@btn-success-border: transparent;
@btn-info-color: #fff;
@btn-info-bg: @brand-info;
@btn-info-border: transparent;
@btn-warning-color: #fff;
@btn-warning-bg: @brand-warning;
@btn-warning-border: transparent;
@btn-danger-color: #fff;
@btn-danger-bg: @brand-danger;
@btn-danger-border: transparent;
@btn-link-disabled-color: @gray-light;
// Allows for customizing button radius independently from global border radius
@btn-border-radius-base: @border-radius-base;
@btn-border-radius-large: @border-radius-large;
@btn-border-radius-small: @border-radius-small;
//== Forms
//
//##
//** `<input>` background color
@input-bg: transparent;
//** `<input disabled>` background color
@input-bg-disabled: transparent;
//** Text color for `<input>`s
@input-color: @gray;
//** `<input>` border color
@input-border: transparent;
// TODO: Rename `@input-border-radius` to `@input-border-radius-base` in v4
//** Default `.form-control` border radius
// This has no effect on `<select>`s in some browsers, due to the limited stylability of `<select>`s in CSS.
@input-border-radius: @border-radius-base;
//** Large `.form-control` border radius
@input-border-radius-large: @border-radius-large;
//** Small `.form-control` border radius
@input-border-radius-small: @border-radius-small;
//** Border color for inputs on focus
@input-border-focus: #66afe9;
//** Placeholder text color
@input-color-placeholder: @gray-light;
//** Default `.form-control` height
@input-height-base: (@line-height-computed + (@padding-base-vertical * 2) + 2);
//** Large `.form-control` height
@input-height-large: (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
//** Small `.form-control` height
@input-height-small: (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
//** `.form-group` margin
@form-group-margin-bottom: 15px;
@legend-color: @gray-dark;
@legend-border-color: #e5e5e5;
//** Background color for textual input addons
@input-group-addon-bg: transparent;
//** Border color for textual input addons
@input-group-addon-border-color: @input-border;
//** Disabled cursor for form controls and buttons.
@cursor-disabled: not-allowed;
//== Dropdowns
//
//## Dropdown menu container and contents.
//** Background for the dropdown menu.
@dropdown-bg: #fff;
//** Dropdown menu `border-color`.
@dropdown-border: rgba(0,0,0,.15);
//** Dropdown menu `border-color` **for IE8**.
@dropdown-fallback-border: #ccc;
//** Divider color for between dropdown items.
@dropdown-divider-bg: #e5e5e5;
//** Dropdown link text color.
@dropdown-link-color: @text-color;
//** Hover color for dropdown links.
@dropdown-link-hover-color: darken(@gray-dark, 5%);
//** Hover background for dropdown links.
@dropdown-link-hover-bg: @gray-lighter;
//** Active dropdown menu item text color.
@dropdown-link-active-color: @component-active-color;
//** Active dropdown menu item background color.
@dropdown-link-active-bg: @component-active-bg;
//** Disabled dropdown menu item background color.
@dropdown-link-disabled-color: @gray-light;
//** Text color for headers within dropdown menus.
@dropdown-header-color: @gray-light;
//** Deprecated `@dropdown-caret-color` as of v3.1.0
@dropdown-caret-color: @gray-light;
//-- Z-index master list
//
// Warning: Avoid customizing these values. They're used for a bird's eye view
// of components dependent on the z-axis and are designed to all work together.
//
// Note: These variables are not generated into the Customizer.
@zindex-navbar: 1000;
@zindex-dropdown: 1000;
@zindex-popover: 1060;
@zindex-tooltip: 1070;
@zindex-navbar-fixed: 1030;
@zindex-modal-background: 1040;
@zindex-modal: 1050;
//== Media queries breakpoints
//
//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
// Extra small screen / phone
//** Deprecated `@screen-xs` as of v3.0.1
@screen-xs: 480px;
//** Deprecated `@screen-xs-min` as of v3.2.0
@screen-xs-min: @screen-xs;
//** Deprecated `@screen-phone` as of v3.0.1
@screen-phone: @screen-xs-min;
// Small screen / tablet
//** Deprecated `@screen-sm` as of v3.0.1
@screen-sm: 768px;
@screen-sm-min: @screen-sm;
//** Deprecated `@screen-tablet` as of v3.0.1
@screen-tablet: @screen-sm-min;
// Medium screen / desktop
//** Deprecated `@screen-md` as of v3.0.1
@screen-md: 992px;
@screen-md-min: @screen-md;
//** Deprecated `@screen-desktop` as of v3.0.1
@screen-desktop: @screen-md-min;
// Large screen / wide desktop
//** Deprecated `@screen-lg` as of v3.0.1
@screen-lg: 1200px;
@screen-lg-min: @screen-lg;
//** Deprecated `@screen-lg-desktop` as of v3.0.1
@screen-lg-desktop: @screen-lg-min;
// So media queries don't overlap when required, provide a maximum
@screen-xs-max: (@screen-sm-min - 1);
@screen-sm-max: (@screen-md-min - 1);
@screen-md-max: (@screen-lg-min - 1);
//== Grid system
//
//## Define your custom responsive grid.
//** Number of columns in the grid.
@grid-columns: 12;
//** Padding between columns. Gets divided in half for the left and right.
@grid-gutter-width: 30px;
// Navbar collapse
//** Point at which the navbar becomes uncollapsed.
@grid-float-breakpoint: @screen-sm-min;
//** Point at which the navbar begins collapsing.
@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
//== Container sizes
//
//## Define the maximum width of `.container` for different screen sizes.
// Small screen / tablet
@container-tablet: (720px + @grid-gutter-width);
//** For `@screen-sm-min` and up.
@container-sm: @container-tablet;
// Medium screen / desktop
@container-desktop: (940px + @grid-gutter-width);
//** For `@screen-md-min` and up.
@container-md: @container-desktop;
// Large screen / wide desktop
@container-large-desktop: (1140px + @grid-gutter-width);
//** For `@screen-lg-min` and up.
@container-lg: @container-large-desktop;
//== Navbar
//
//##
// Basics of a navbar
@navbar-height: 64px;
@navbar-margin-bottom: @line-height-computed;
@navbar-border-radius: @border-radius-base;
@navbar-padding-horizontal: floor((@grid-gutter-width / 2));
@navbar-padding-vertical: ((@navbar-height - @line-height-computed) / 2);
@navbar-collapse-max-height: 340px;
@navbar-default-color: @gray-light;
@navbar-default-bg: #fff;
@navbar-default-border: transparent;
// Navbar links
@navbar-default-link-color: @gray;
@navbar-default-link-hover-color: @gray-dark;
@navbar-default-link-hover-bg: transparent;
@navbar-default-link-active-color: @gray-dark;
@navbar-default-link-active-bg: darken(@navbar-default-bg, 6.5%);
@navbar-default-link-disabled-color: #ccc;
@navbar-default-link-disabled-bg: transparent;
// Navbar brand label
@navbar-default-brand-color: @navbar-default-link-color;
@navbar-default-brand-hover-color: @navbar-default-link-hover-color;
@navbar-default-brand-hover-bg: transparent;
// Navbar toggle
@navbar-default-toggle-hover-bg: transparent;
@navbar-default-toggle-icon-bar-bg: rgba(0,0,0,0.5);
@navbar-default-toggle-border-color: transparent;
//=== Inverted navbar
// Reset inverted navbar basics
@navbar-inverse-color: @gray-light;
@navbar-inverse-bg: @brand-primary;
@navbar-inverse-border: transparent;
// Inverted navbar links
@navbar-inverse-link-color: lighten(@brand-primary, 30%);
@navbar-inverse-link-hover-color: #fff;
@navbar-inverse-link-hover-bg: transparent;
@navbar-inverse-link-active-color: @navbar-inverse-link-hover-color;
@navbar-inverse-link-active-bg: darken(@navbar-inverse-bg, 10%);
@navbar-inverse-link-disabled-color: #444;
@navbar-inverse-link-disabled-bg: transparent;
// Inverted navbar brand label
@navbar-inverse-brand-color: @navbar-inverse-link-color;
@navbar-inverse-brand-hover-color: #fff;
@navbar-inverse-brand-hover-bg: transparent;
// Inverted navbar toggle\
@navbar-inverse-toggle-hover-bg: transparent;
@navbar-inverse-toggle-icon-bar-bg: rgba(0,0,0,0.5);
@navbar-inverse-toggle-border-color: transparent;
//== Navs
//
//##
//=== Shared nav styles
@nav-link-padding: 10px 15px;
@nav-link-hover-bg: @gray-lighter;
@nav-disabled-link-color: @gray-light;
@nav-disabled-link-hover-color: @gray-light;
//== Tabs
@nav-tabs-border-color: transparent;
@nav-tabs-link-hover-border-color: @gray-lighter;
@nav-tabs-active-link-hover-bg: transparent;
@nav-tabs-active-link-hover-color: @gray;
@nav-tabs-active-link-hover-border-color: transparent;
@nav-tabs-justified-link-border-color: @nav-tabs-border-color;
@nav-tabs-justified-active-link-border-color: @body-bg;
//== Pills
@nav-pills-border-radius: @border-radius-base;
@nav-pills-active-link-hover-bg: @component-active-bg;
@nav-pills-active-link-hover-color: @component-active-color;
//== Pagination
//
//##
@pagination-color: @link-color;
@pagination-bg: #fff;
@pagination-border: #ddd;
@pagination-hover-color: @link-hover-color;
@pagination-hover-bg: @gray-lighter;
@pagination-hover-border: #ddd;
@pagination-active-color: #fff;
@pagination-active-bg: @brand-primary;
@pagination-active-border: @brand-primary;
@pagination-disabled-color: @gray-light;
@pagination-disabled-bg: #fff;
@pagination-disabled-border: #ddd;
//== Pager
//
//##
@pager-bg: @pagination-bg;
@pager-border: @pagination-border;
@pager-border-radius: 15px;
@pager-hover-bg: @pagination-hover-bg;
@pager-active-bg: @pagination-active-bg;
@pager-active-color: @pagination-active-color;
@pager-disabled-color: @pagination-disabled-color;
//== Jumbotron
//
//##
@jumbotron-padding: 30px;
@jumbotron-color: inherit;
@jumbotron-bg: #f9f9f9;
@jumbotron-heading-color: @headings-color;
@jumbotron-font-size: ceil((@font-size-base * 1.5));
@jumbotron-heading-font-size: ceil((@font-size-base * 4.5));
//== Form states and alerts
//
//## Define colors for form feedback states and, by default, alerts.
@state-success-text: @brand-success;
@state-success-bg: #dff0d8;
@state-success-border: darken(spin(@state-success-bg, -10), 5%);
@state-info-text: @brand-info;
@state-info-bg: #e1bee7;
@state-info-border: darken(spin(@state-info-bg, -10), 7%);
@state-warning-text: @brand-warning;
@state-warning-bg: #ffe0b2;
@state-warning-border: darken(spin(@state-warning-bg, -10), 5%);
@state-danger-text: @brand-danger;
@state-danger-bg: #f9bdbb;
@state-danger-border: darken(spin(@state-danger-bg, -10), 5%);
//== Tooltips
//
//##
//** Tooltip max width
@tooltip-max-width: 200px;
//** Tooltip text color
@tooltip-color: #fff;
//** Tooltip background color
@tooltip-bg: #727272;
@tooltip-opacity: .9;
//** Tooltip arrow width
@tooltip-arrow-width: 5px;
//** Tooltip arrow color
@tooltip-arrow-color: @tooltip-bg;
//== Popovers
//
//##
//** Popover body background color
@popover-bg: #fff;
//** Popover maximum width
@popover-max-width: 276px;
//** Popover border color
@popover-border-color: transparent;
//** Popover fallback border color
@popover-fallback-border-color: transparent;
//** Popover title background color
@popover-title-bg: darken(@popover-bg, 3%);
//** Popover arrow width
@popover-arrow-width: 10px;
//** Popover arrow color
@popover-arrow-color: @popover-bg;
//** Popover outer arrow width
@popover-arrow-outer-width: (@popover-arrow-width + 1);
//** Popover outer arrow color
@popover-arrow-outer-color: fadein(@popover-border-color, 7.5%);
//** Popover outer arrow fallback color
@popover-arrow-outer-fallback-color: darken(@popover-fallback-border-color, 20%);
//== Labels
//
//##
//** Default label background color
@label-default-bg: @gray-light;
//** Primary label background color
@label-primary-bg: @brand-primary;
//** Success label background color
@label-success-bg: @brand-success;
//** Info label background color
@label-info-bg: @brand-info;
//** Warning label background color
@label-warning-bg: @brand-warning;
//** Danger label background color
@label-danger-bg: @brand-danger;
//** Default label text color
@label-color: #fff;
//** Default text color of a linked label
@label-link-hover-color: #fff;
//== Modals
//
//##
//** Padding applied to the modal body
@modal-inner-padding: 15px;
//** Padding applied to the modal title
@modal-title-padding: 15px;
//** Modal title line-height
@modal-title-line-height: @line-height-base;
//** Background color of modal content area
@modal-content-bg: #fff;
//** Modal content border color
@modal-content-border-color: transparent;
//** Modal content border color **for IE8**
@modal-content-fallback-border-color: #999;
//** Modal backdrop background color
@modal-backdrop-bg: #000;
//** Modal backdrop opacity
@modal-backdrop-opacity: .5;
//** Modal header border color
@modal-header-border-color: transparent;
//** Modal footer border color
@modal-footer-border-color: @modal-header-border-color;
@modal-lg: 900px;
@modal-md: 600px;
@modal-sm: 300px;
//== Alerts
//
//## Define alert colors, border radius, and padding.
@alert-padding: 15px;
@alert-border-radius: @border-radius-base;
@alert-link-font-weight: bold;
@alert-success-bg: @state-success-bg;
@alert-success-text: @state-success-text;
@alert-success-border: @state-success-border;
@alert-info-bg: @state-info-bg;
@alert-info-text: @state-info-text;
@alert-info-border: @state-info-border;
@alert-warning-bg: @state-warning-bg;
@alert-warning-text: @state-warning-text;
@alert-warning-border: @state-warning-border;
@alert-danger-bg: @state-danger-bg;
@alert-danger-text: @state-danger-text;
@alert-danger-border: @state-danger-border;
//== Progress bars
//
//##
//** Background color of the whole progress component
@progress-bg: #f5f5f5;
//** Progress bar text color
@progress-bar-color: #fff;
//** Variable for setting rounded corners on progress bar.
@progress-border-radius: @border-radius-base;
//** Default progress bar color
@progress-bar-bg: @brand-primary;
//** Success progress bar color
@progress-bar-success-bg: @brand-success;
//** Warning progress bar color
@progress-bar-warning-bg: @brand-warning;
//** Danger progress bar color
@progress-bar-danger-bg: @brand-danger;
//** Info progress bar color
@progress-bar-info-bg: @brand-info;
//== List group
//
//##
//** Background color on `.list-group-item`
@list-group-bg: #fff;
//** `.list-group-item` border color
@list-group-border: #ddd;
//** List group border radius
@list-group-border-radius: @border-radius-base;
//** Background color of single list items on hover
@list-group-hover-bg: #f5f5f5;
//** Text color of active list items
@list-group-active-color: @component-active-color;
//** Background color of active list items
@list-group-active-bg: @component-active-bg;
//** Border color of active list elements
@list-group-active-border: @list-group-active-bg;
//** Text color for content within active list items
@list-group-active-text-color: lighten(@list-group-active-bg, 40%);
//** Text color of disabled list items
@list-group-disabled-color: @gray-light;
//** Background color of disabled list items
@list-group-disabled-bg: @gray-lighter;
//** Text color for content within disabled list items
@list-group-disabled-text-color: @list-group-disabled-color;
@list-group-link-color: #555;
@list-group-link-hover-color: @list-group-link-color;
@list-group-link-heading-color: #333;
//== Panels
//
//##
@panel-bg: #fff;
@panel-body-padding: 15px;
@panel-heading-padding: 10px 15px;
@panel-footer-padding: @panel-heading-padding;
@panel-border-radius: @border-radius-base;
//** Border color for elements within panels
@panel-inner-border: #ddd;
@panel-footer-bg: #f5f5f5;
@panel-default-text: @gray-dark;
@panel-default-border: #ddd;
@panel-default-heading-bg: #f5f5f5;
@panel-primary-text: #fff;
@panel-primary-border: @brand-primary;
@panel-primary-heading-bg: @brand-primary;
@panel-success-text: #fff;
@panel-success-border: @state-success-border;
@panel-success-heading-bg: @brand-success;
@panel-info-text: #fff;
@panel-info-border: @state-info-border;
@panel-info-heading-bg: @brand-info;
@panel-warning-text: #fff;
@panel-warning-border: @state-warning-border;
@panel-warning-heading-bg: @brand-warning;
@panel-danger-text: #fff;
@panel-danger-border: @state-danger-border;
@panel-danger-heading-bg: @brand-danger;
//== Thumbnails
//
//##
//** Padding around the thumbnail image
@thumbnail-padding: 4px;
//** Thumbnail background color
@thumbnail-bg: @body-bg;
//** Thumbnail border color
@thumbnail-border: #ddd;
//** Thumbnail border radius
@thumbnail-border-radius: @border-radius-base;
//** Custom text color for thumbnail captions
@thumbnail-caption-color: @text-color;
//** Padding around the thumbnail caption
@thumbnail-caption-padding: 9px;
//== Wells
//
//##
@well-bg: #f9f9f9;
@well-border: transparent;
//== Badges
//
//##
@badge-color: #fff;
//** Linked badge text color on hover
@badge-link-hover-color: #fff;
@badge-bg: @gray-light;
//** Badge text color in active nav link
@badge-active-color: @link-color;
//** Badge background color in active nav link
@badge-active-bg: #fff;
@badge-font-weight: normal;
@badge-line-height: 1;
@badge-border-radius: 10px;
//== Breadcrumbs
//
//##
@breadcrumb-padding-vertical: 8px;
@breadcrumb-padding-horizontal: 15px;
//** Breadcrumb background color
@breadcrumb-bg: #f5f5f5;
//** Breadcrumb text color
@breadcrumb-color: #ccc;
//** Text color of current page in the breadcrumb
@breadcrumb-active-color: @gray-light;
//** Textual separator for between breadcrumb elements
@breadcrumb-separator: "/";
//== Carousel
//
//##
@carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6);
@carousel-control-color: #fff;
@carousel-control-width: 15%;
@carousel-control-opacity: .5;
@carousel-control-font-size: 20px;
@carousel-indicator-active-bg: #fff;
@carousel-indicator-border-color: #fff;
@carousel-caption-color: #fff;
//== Close
//
//##
@close-font-weight: normal;
@close-color: #000;
@close-text-shadow: none;
//== Code
//
//##
@code-color: #c7254e;
@code-bg: #f9f2f4;
@kbd-color: #fff;
@kbd-bg: #333;
@pre-bg: #f5f5f5;
@pre-color: @gray-dark;
@pre-border-color: #ccc;
@pre-scrollable-max-height: 340px;
//== Type
//
//##
//** Horizontal offset for forms and lists.
@component-offset-horizontal: 180px;
//** Text muted color
@text-muted: @gray-light;
//** Abbreviations and acronyms border color
@abbr-border-color: @gray-light;
//** Headings small color
@headings-small-color: @gray-light;
//** Blockquote small color
@blockquote-small-color: @gray-light;
//** Blockquote font size
@blockquote-font-size: (@font-size-base * 1.25);
//** Blockquote border color
@blockquote-border-color: @gray-lighter;
//** Page header border color
@page-header-border-color: @gray-lighter;
//** Width of horizontal description list titles
@dl-horizontal-offset: @component-offset-horizontal;
//** Point at which .dl-horizontal becomes horizontal
@dl-horizontal-breakpoint: @grid-float-breakpoint;
//** Horizontal line color.
@hr-border: @gray-lighter;

View File

@@ -0,0 +1,21 @@
.table i {
padding-top: 6px;
}
img.loading {
width: 25px;
}
.welcome table {
display: none;
width: 100%;
}
input {
margin-left: 5px;
margin-top: 8px;
}
.panel-body {
overflow: auto;
}

View File

@@ -54,7 +54,18 @@
clear: left; font-size: 12px; line-height: 18px; height: 18px;
margin: 0px;
}
.parcoords .row:nth-child(odd) { background: rgba(0,0,0,0.05); }
.parcoords .header { font-weight: bold; }
.parcoords .cell { float: left; overflow: hidden; white-space: nowrap; width: 100px; height: 18px; }
.parcoords .col-0 { width: 180px; }
.parcoords .row:nth-child(odd) {
background: rgba(0,0,0,0.05);
}
.parcoords .header {
font-weight: bold;
}
.parcoords .cell {
float: left;
overflow: hidden;
white-space: nowrap;
width: 100px; height: 18px;
}
.parcoords .col-0 {
width: 180px;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
// http://bl.ocks.org/3687826
d3.divgrid = function(config) {
// from http://bl.ocks.org/3687826
module.exports = function(config) {
var columns = [];
var dg = function(selection) {

View File

@@ -1,7 +1,7 @@
/**
* jQuery Select2 Sortable
* - enable select2 to be sortable via normal select element
*
*
* author : Vafour
* modified : Kevin Provance (kprovance)
* inspired by : jQuery Chosen Sortable (https://github.com/mrhenry/jquery-chosen-sortable)
@@ -26,44 +26,44 @@
// Opt group names
var optArr = [];
$select.find('optgroup').each(function(idx, val) {
optArr.push (val);
});
$select.find('option').each(function(idx, val) {
var groupName = $(this).parent('optgroup').prop('label');
var optVal = this;
if (groupName === undefined) {
if (this.value !== '' && !this.selected) {
optArr.push (optVal);
}
}
});
sorted = $($select2.find('.select2-choices li[class!="select2-search-field"]').map(function () {
if (!this) {
return undefined;
}
var id = $(this).data('select2Data').id;
return $select.find('option[value="' + id + '"]')[0];
}));
sorted.push.apply(sorted, optArr);
$select.children().remove();
$select.append(sorted);
});
return $this;
},
select2Sortable: function () {
var args = Array.prototype.slice.call(arguments, 0);
$this = this.filter('[multiple]'),
var $this = this.filter('[multiple]'),
validMethods = ['destroy'];
if (args.length === 0 || typeof (args[0]) === 'object') {
@@ -75,7 +75,7 @@
tolerance: 'pointer'
}
};
var options = $.extend(defaultOptions, args[0]);
// Init select2 only if not already initialized to prevent select2 configuration loss
@@ -96,12 +96,12 @@
$select2choices.on("sortstop.select2sortable", function (event, ui) {
$select.select2SortableOrder();
});
$select.on('change', function (e) {
$(this).select2SortableOrder();
});
break;
default:
// apply options ordering in form submit
$select.closest('form').unbind('submit.select2sortable').on('submit.select2sortable', function () {
@@ -115,15 +115,15 @@
if ($.inArray(args[0], validMethods) == -1) {
throw "Unknown method: " + args[0];
}
if (args[0] === 'destroy') {
$this.select2SortableDestroy();
}
}
return $this;
},
select2SortableDestroy: function () {
var $this = this.filter('[multiple]');
$this.each(function () {
@@ -139,7 +139,7 @@
// destroy select2Sortable
$select2choices.sortable('destroy');
});
return $this;
}
});

View File

@@ -1,4 +1,5 @@
.big_number g.axis text {
.big_number g.axis text,
.big_number_total g.axis text {
font-size: 10px;
font-weight: normal;
color: gray;
@@ -8,18 +9,21 @@
font-weight: none;
}
.big_number text.big {
.big_number text.big,
.big_number_total text.big{
stroke: black;
text-anchor: middle;
fill: black;
}
.big_number g.tick line {
.big_number g.tick line,
.big_number_total g.tick line{
stroke-width: 1px;
stroke: grey;
}
.big_number .domain {
.big_number .domain,
.big_number_total .domain{
fill: none;
stroke: black;
stroke-width: 1;

View File

@@ -0,0 +1,181 @@
// JS
var d3 = window.d3 || require('d3');
// CSS
require('./big_number.css');
var px = require('../javascripts/modules/caravel.js');
function bigNumberVis(slice) {
var div = d3.select(slice.selector);
function render() {
d3.json(slice.jsonEndpoint(), function (error, payload) {
//Define the percentage bounds that define color from red to green
if (error !== null) {
slice.error(error.responseText);
return '';
}
var fd = payload.form_data;
var json = payload.data;
var color_range = [-1, 1];
var f = d3.format(fd.y_axis_format);
var fp = d3.format('+.1%');
var width = slice.width();
var height = slice.height();
div.selectAll("*").remove();
var svg = div.append('svg');
svg.attr("width", width);
svg.attr("height", height);
var data = json.data;
var compare_suffix = ' ' + json.compare_suffix;
var v_compare = null;
var v = null;
if (data.length > 1) {
v = data[data.length - 1][1];
} else {
v = data[data.length - 1][0];
}
if (json.compare_lag > 0) {
var pos = data.length - (json.compare_lag + 1);
if (pos >= 0) {
v_compare = (v / data[pos][1]) - 1;
}
}
var date_ext = d3.extent(data, function (d) {
return d[0];
});
var value_ext = d3.extent(data, function (d) {
return d[1];
});
var margin = 20;
var scale_x = d3.time.scale.utc().domain(date_ext).range([margin, width - margin]);
var scale_y = d3.scale.linear().domain(value_ext).range([height - (margin), margin]);
var colorRange = [d3.hsl(0, 1, 0.3), d3.hsl(120, 1, 0.3)];
var scale_color = d3.scale
.linear().domain(color_range)
.interpolate(d3.interpolateHsl)
.range(colorRange).clamp(true);
var line = d3.svg.line()
.x(function (d) {
return scale_x(d[0]);
})
.y(function (d) {
return scale_y(d[1]);
})
.interpolate("basis");
//Drawing trend line
var g = svg.append('g');
g.append('path')
.attr('d', function (d) {
return line(data);
})
.attr('stroke-width', 5)
.attr('opacity', 0.5)
.attr('fill', "none")
.attr('stroke-linecap', "round")
.attr('stroke', "grey");
g = svg.append('g')
.attr('class', 'digits')
.attr('opacity', 1);
var y = height / 2;
if (v_compare !== null) {
y = (height / 8) * 3;
}
//Printing big number
g.append('text')
.attr('x', width / 2)
.attr('y', y)
.attr('class', 'big')
.attr('alignment-baseline', 'middle')
.attr('id', 'bigNumber')
.style('font-weight', 'bold')
.style('cursor', 'pointer')
.text(f(v))
.style('font-size', d3.min([height, width]) / 3.5)
.attr('fill', 'white');
//Printing big number subheader text
if (json.subheader !== null) {
g.append('text')
.attr('x', width / 2)
.attr('y', y + d3.min([height, width]) / 4.5)
.text(json.subheader)
.attr('id', 'subheader_text')
.style('font-size', d3.min([height, width]) / 16)
.style('text-anchor', 'middle')
.attr('fill', c)
.attr('stroke', c);
}
var c = scale_color(v_compare);
//Printing compare %
if (v_compare !== null) {
g.append('text')
.attr('x', width / 2)
.attr('y', (height / 16) * 12)
.text(fp(v_compare) + compare_suffix)
.style('font-size', d3.min([height, width]) / 8)
.style('text-anchor', 'middle')
.attr('fill', c)
.attr('stroke', c);
}
var g_axis = svg.append('g').attr('class', 'axis').attr('opacity', 0);
g = g_axis.append('g');
var x_axis = d3.svg.axis()
.scale(scale_x)
.orient('bottom')
.ticks(4)
.tickFormat(px.formatDate);
g.call(x_axis);
g.attr('transform', 'translate(0,' + (height - margin) + ')');
g = g_axis.append('g').attr('transform', 'translate(' + (width - margin) + ',0)');
var y_axis = d3.svg.axis()
.scale(scale_y)
.orient('left')
.tickFormat(d3.format(fd.y_axis_format))
.tickValues(value_ext);
g.call(y_axis);
g.selectAll('text')
.style('text-anchor', 'end')
.attr('y', '-7')
.attr('x', '-4');
g.selectAll("text")
.style('font-size', '10px');
div.on('mouseover', function (d) {
var div = d3.select(this);
div.select('path').transition().duration(500).attr('opacity', 1)
.style('stroke-width', '2px');
div.select('g.digits').transition().duration(500).attr('opacity', 0.1);
div.select('g.axis').transition().duration(500).attr('opacity', 1);
})
.on('mouseout', function (d) {
var div = d3.select(this);
div.select('path').transition().duration(500).attr('opacity', 0.5)
.style('stroke-width', '5px');
div.select('g.digits').transition().duration(500).attr('opacity', 1);
div.select('g.axis').transition().duration(500).attr('opacity', 0);
});
slice.done(payload);
});
}
return {
render: render,
resize: render
};
}
module.exports = bigNumberVis;

View File

@@ -0,0 +1,175 @@
// JS
var d3 = window.d3 || require('d3');
// CSS
require('./directed_force.css');
/* Modified from http://bl.ocks.org/d3noob/5141278 */
function directedForceVis(slice) {
var div = d3.select(slice.selector);
var link_length = slice.data.form_data.link_length || 200;
var charge = slice.data.form_data.charge || -500;
var render = function () {
var width = slice.width();
var height = slice.height() - 25;
d3.json(slice.jsonEndpoint(), function (error, json) {
if (error !== null) {
slice.error(error.responseText);
return '';
}
var links = json.data;
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function (link) {
link.source = nodes[link.source] || (nodes[link.source] = {
name: link.source
});
link.target = nodes[link.target] || (nodes[link.target] = {
name: link.target
});
link.value = Number(link.value);
var target_name = link.target.name;
var source_name = link.source.name;
if (nodes[target_name].total === undefined) {
nodes[target_name].total = link.value;
}
if (nodes[source_name].total === undefined) {
nodes[source_name].total = 0;
}
if (nodes[target_name].max === undefined) {
nodes[target_name].max = 0;
}
if (link.value > nodes[target_name].max) {
nodes[target_name].max = link.value;
}
if (nodes[target_name].min === undefined) {
nodes[target_name].min = 0;
}
if (link.value > nodes[target_name].min) {
nodes[target_name].min = link.value;
}
nodes[target_name].total += link.value;
});
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(link_length)
.charge(charge)
.on("tick", tick)
.start();
var svg = div.append("svg")
.attr("width", width)
.attr("height", height);
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
var edgeScale = d3.scale.linear()
.range([0.1, 0.5]);
// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", "link")
.style("opacity", function (d) {
return edgeScale(d.value / d.target.max);
})
.attr("marker-end", "url(#end)");
// define the nodes
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("mouseenter", function (d) {
d3.select(this)
.select("circle")
.transition()
.style('stroke-width', 5);
d3.select(this)
.select("text")
.transition()
.style('font-size', 25);
})
.on("mouseleave", function (d) {
d3.select(this)
.select("circle")
.transition()
.style('stroke-width', 1.5);
d3.select(this)
.select("text")
.transition()
.style('font-size', 12);
})
.call(force.drag);
// add the nodes
var ext = d3.extent(d3.values(nodes), function (d) {
return Math.sqrt(d.total);
});
var circleScale = d3.scale.linear()
.domain(ext)
.range([3, 30]);
node.append("circle")
.attr("r", function (d) {
return circleScale(Math.sqrt(d.total));
});
// add the text
node.append("text")
.attr("x", 6)
.attr("dy", ".35em")
.text(function (d) {
return d.name;
});
// add the curvy lines
function tick() {
path.attr("d", function (d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" +
d.source.x + "," +
d.source.y + "A" +
dr + "," + dr + " 0 0,1 " +
d.target.x + "," +
d.target.y;
});
node.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
slice.done(json);
});
};
return {
render: render,
resize: render
};
}
module.exports = directedForceVis;

View File

@@ -0,0 +1,8 @@
.select2-highlighted > .filter_box {
background-color: transparent;
border: 1px caravel black;
}
.dashboard .filter_box .slice_container > div {
padding-top: 0;
}

View File

@@ -0,0 +1,82 @@
// JS
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
var d3 = window.d3 || require('d3');
// CSS
require('./filter_box.css');
require('../javascripts/caravel-select2.js');
function filterBox(slice) {
var filtersObj = {};
var d3token = d3.select(slice.selector);
var fltChanged = function () {
var val = $(this).val();
var vals = [];
if (val !== '') {
vals = val.split(',');
}
slice.setFilter($(this).attr('name'), vals);
};
var refresh = function () {
d3token.selectAll("*").remove();
var container = d3token
.append('div')
.classed('padded', true);
$.getJSON(slice.jsonEndpoint(), function (payload) {
var maxes = {};
for (var filter in payload.data) {
var data = payload.data[filter];
maxes[filter] = d3.max(data, function (d) {
return d.metric;
});
var id = 'fltbox__' + filter;
var div = container.append('div');
div.append("label").text(filter);
div.append('div')
.attr('name', filter)
.classed('form-control', true)
.attr('multiple', '')
.attr('id', id);
filtersObj[filter] = $('#' + id).select2({
placeholder: "Select [" + filter + ']',
containment: 'parent',
dropdownAutoWidth: true,
data: data,
multiple: true,
formatResult: select2Formatter
})
.on('change', fltChanged);
}
slice.done(payload);
function select2Formatter(result, container /*, query, escapeMarkup*/) {
var perc = Math.round((result.metric / maxes[result.filter]) * 100);
var style = 'padding: 2px 5px;';
style += "background-image: ";
style += "linear-gradient(to right, lightgrey, lightgrey " + perc + "%, rgba(0,0,0,0) " + perc + "%";
$(container).attr('style', 'padding: 0px; background: white;');
$(container).addClass('filter_box');
return '<div style="' + style + '"><span>' + result.text + '</span></div>';
}
})
.fail(function (xhr) {
slice.error(xhr.responseText);
});
};
return {
render: refresh,
resize: refresh
};
}
module.exports = filterBox;

View File

@@ -0,0 +1,79 @@
.heatmap .axis text {
font: 10px sans-serif;
}
.heatmap .axis path,
.heatmap .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.heatmap svg {
}
.heatmap canvas, .heatmap img {
image-rendering: optimizeSpeed; /* Older versions of FF */
image-rendering: -moz-crisp-edges; /* FF 6.0+ */
image-rendering: -webkit-optimize-contrast; /* Safari */
image-rendering: -o-crisp-edges; /* OS X & Windows Opera (12.02+) */
image-rendering: pixelated; /* Awesome future-browsers */
-ms-interpolation-mode: nearest-neighbor; /* IE */
}
/* from d3-tip */
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
pointer-events: none;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
position: absolute;
pointer-events: none;
}
/* Northward tooltips */
.d3-tip.n:after {
content: "\25BC";
margin: -1px 0 0 0;
top: 100%;
left: 0;
text-align: center;
}
/* Eastward tooltips */
.d3-tip.e:after {
content: "\25C0";
margin: -4px 0 0 0;
top: 50%;
left: -8px;
}
/* Southward tooltips */
.d3-tip.s:after {
content: "\25B2";
margin: 0 0 1px 0;
top: -8px;
left: 0;
text-align: center;
}
/* Westward tooltips */
.d3-tip.w:after {
content: "\25B6";
margin: -4px 0 0 -1px;
top: 50%;
left: 100%;
}

View File

@@ -0,0 +1,209 @@
// JS
var $ = window.$ || require('jquery');
var px = window.px || require('../javascripts/modules/caravel.js');
var d3 = require('d3');
d3.tip = require('d3-tip'); //using window.d3 doesn't capture events properly bc of multiple instances
// CSS
require('./heatmap.css');
// Inspired from http://bl.ocks.org/mbostock/3074470
// https://jsfiddle.net/cyril123/h0reyumq/
function heatmapVis(slice) {
var margins = {
t: 10,
r: 10,
b: 50,
l: 60
};
function refresh() {
var width = slice.width();
var height = slice.height();
var hmWidth = width - (margins.l + margins.r);
var hmHeight = height - (margins.b + margins.t);
var fp = d3.format('.3p');
d3.json(slice.jsonEndpoint(), function (error, payload) {
var matrix = {};
if (error) {
slice.error(error.responseText);
return '';
}
var fd = payload.form_data;
var data = payload.data;
function ordScale(k, rangeBands, reverse) {
if (reverse === undefined) {
reverse = false;
}
var domain = {};
$.each(data, function (i, d) {
domain[d[k]] = true;
});
domain = Object.keys(domain).sort(function (a, b) {
return b - a;
});
if (reverse) {
domain.reverse();
}
if (rangeBands === undefined) {
return d3.scale.ordinal().domain(domain).range(d3.range(domain.length));
} else {
return d3.scale.ordinal().domain(domain).rangeBands(rangeBands);
}
}
var xScale = ordScale('x');
var yScale = ordScale('y', undefined, true);
var xRbScale = ordScale('x', [0, hmWidth]);
var yRbScale = ordScale('y', [hmHeight, 0]);
var X = 0,
Y = 1;
var heatmapDim = [xRbScale.domain().length, yRbScale.domain().length];
var color = px.color.colorScalerFactory(fd.linear_color_scheme);
var scale = [
d3.scale.linear()
.domain([0, heatmapDim[X]])
.range([0, hmWidth]),
d3.scale.linear()
.domain([0, heatmapDim[Y]])
.range([0, hmHeight])
];
var container = d3.select(slice.selector)
.style("left", "0px")
.style("position", "relative")
.style("top", "0px");
var canvas = container.append("canvas")
.attr("width", heatmapDim[X])
.attr("height", heatmapDim[Y])
.style("width", hmWidth + "px")
.style("height", hmHeight + "px")
.style("image-rendering", fd.canvas_image_rendering)
.style("left", margins.l + "px")
.style("top", margins.t + "px")
.style("position", "absolute");
var svg = container.append("svg")
.attr("width", width)
.attr("height", height)
.style("left", "0px")
.style("top", "0px")
.style("position", "absolute");
var rect = svg.append('g')
.attr("transform", "translate(" + margins.l + "," + margins.t + ")")
.append('rect')
.style('fill-opacity', 0)
.attr('stroke', 'black')
.attr("width", hmWidth)
.attr("height", hmHeight);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset(function () {
var k = d3.mouse(this);
var x = k[0] - (hmWidth / 2);
return [k[1] - 20, x];
})
.html(function (d) {
var k = d3.mouse(this);
var m = Math.floor(scale[0].invert(k[0]));
var n = Math.floor(scale[1].invert(k[1]));
if (m in matrix && n in matrix[m]) {
var obj = matrix[m][n];
var s = "";
s += "<div><b>" + fd.all_columns_x + ": </b>" + obj.x + "<div>";
s += "<div><b>" + fd.all_columns_y + ": </b>" + obj.y + "<div>";
s += "<div><b>" + fd.metric + ": </b>" + obj.v + "<div>";
s += "<div><b>%: </b>" + fp(obj.perc) + "<div>";
return s;
}
});
rect.call(tip);
var xAxis = d3.svg.axis()
.scale(xRbScale)
.tickValues(xRbScale.domain().filter(
function (d, i) {
return !(i % (parseInt(fd.xscale_interval, 10)));
}))
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yRbScale)
.tickValues(yRbScale.domain().filter(
function (d, i) {
return !(i % (parseInt(fd.yscale_interval, 10)));
}))
.orient("left");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + margins.l + "," + (margins.t + hmHeight) + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("transform", "rotate(-45)")
.style("font-weight", "bold");
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margins.l + ", 0)")
.call(yAxis);
rect.on('mousemove', tip.show);
rect.on('mouseout', tip.hide);
var context = canvas.node().getContext("2d");
context.imageSmoothingEnabled = false;
createImageObj();
// Compute the pixel colors; scaled by CSS.
function createImageObj() {
var imageObj = new Image();
var image = context.createImageData(heatmapDim[0], heatmapDim[1]);
var pixs = {};
$.each(data, function (i, d) {
var c = d3.rgb(color(d.perc));
var x = xScale(d.x);
var y = yScale(d.y);
pixs[x + (y * xScale.domain().length)] = c;
if (matrix[x] === undefined) {
matrix[x] = {};
}
if (matrix[x][y] === undefined) {
matrix[x][y] = d;
}
});
var p = -1;
for (var i = 0; i < heatmapDim[0] * heatmapDim[1]; i++) {
var c = pixs[i];
var alpha = 255;
if (c === undefined) {
c = d3.rgb('#F00');
alpha = 0;
}
image.data[++p] = c.r;
image.data[++p] = c.g;
image.data[++p] = c.b;
image.data[++p] = alpha;
}
context.putImageData(image, 0, 0);
imageObj.src = canvas.node().toDataURL();
}
slice.done();
});
}
return {
render: refresh,
resize: refresh
};
}
module.exports = heatmapVis;

View File

@@ -0,0 +1,25 @@
var $ = window.$ || require('jquery');
function iframeWidget(slice) {
function refresh() {
$('#code').attr('rows', '15');
$.getJSON(slice.jsonEndpoint(), function (payload) {
slice.container.html('<iframe style="width:100%;"></iframe>');
var iframe = slice.container.find('iframe');
iframe.css('height', slice.height());
iframe.attr('src', payload.form_data.url);
slice.done();
})
.fail(function (xhr) {
slice.error(xhr.responseText);
});
}
return {
render: refresh,
resize: refresh
};
}
module.exports = iframeWidget;

View File

@@ -0,0 +1,23 @@
var $ = window.$ || require('jquery');
function markupWidget(slice) {
function refresh() {
$('#code').attr('rows', '15');
$.getJSON(slice.jsonEndpoint(), function (payload) {
slice.container.html(payload.data.html);
slice.done();
})
.fail(function (xhr) {
slice.error(xhr.responseText);
});
}
return {
render: refresh,
resize: refresh
};
}
module.exports = markupWidget;

View File

@@ -0,0 +1,8 @@
g.caravel path {
stroke-dasharray: 5, 5;
}
.nvtooltip tr.highlight td {
font-weight: bold;
font-size: 15px !important;
}

View File

@@ -0,0 +1,221 @@
// JS
var $ = window.$ || require('jquery');
var d3 = window.d3 || require('d3');
var px = window.px || require('../javascripts/modules/caravel.js');
var nv = require('nvd3');
// CSS
require('../node_modules/nvd3/build/nv.d3.min.css');
require('./nvd3_vis.css');
function nvd3Vis(slice) {
var chart;
var render = function () {
$.getJSON(slice.jsonEndpoint(), function (payload) {
var fd = payload.form_data;
var viz_type = fd.viz_type;
var f = d3.format('.3s');
var colorKey = 'key';
nv.addGraph(function () {
switch (viz_type) {
case 'line':
if (fd.show_brush) {
chart = nv.models.lineWithFocusChart();
chart.lines2.xScale(d3.time.scale.utc());
chart.x2Axis
.showMaxMin(fd.x_axis_showminmax)
.staggerLabels(false);
} else {
chart = nv.models.lineChart();
}
// To alter the tooltip header
// chart.interactiveLayer.tooltip.headerFormatter(function(){return '';});
chart.xScale(d3.time.scale.utc());
chart.interpolate(fd.line_interpolation);
chart.xAxis
.showMaxMin(fd.x_axis_showminmax)
.staggerLabels(false);
break;
case 'bar':
chart = nv.models.multiBarChart()
.showControls(true)
.groupSpacing(0.1);
chart.xAxis
.showMaxMin(false)
.staggerLabels(true);
chart.stacked(fd.bar_stacked);
break;
case 'dist_bar':
chart = nv.models.multiBarChart()
.showControls(true) //Allow user to switch between 'Grouped' and 'Stacked' mode.
.reduceXTicks(false)
.rotateLabels(45)
.groupSpacing(0.1); //Distance between each group of bars.
chart.xAxis
.showMaxMin(false);
chart.stacked(fd.bar_stacked);
break;
case 'pie':
chart = nv.models.pieChart();
colorKey = 'x';
chart.valueFormat(f);
if (fd.donut) {
chart.donut(true);
chart.labelsOutside(true);
}
chart.labelsOutside(true);
chart.cornerRadius(true);
break;
case 'column':
chart = nv.models.multiBarChart()
.reduceXTicks(false)
.rotateLabels(45);
break;
case 'compare':
chart = nv.models.cumulativeLineChart();
chart.xScale(d3.time.scale.utc());
chart.xAxis
.showMaxMin(false)
.staggerLabels(true);
break;
case 'bubble':
var row = function (col1, col2) {
return "<tr><td>" + col1 + "</td><td>" + col2 + "</td></tr>";
};
chart = nv.models.scatterChart();
chart.showDistX(true);
chart.showDistY(true);
chart.tooltip.contentGenerator(function (obj) {
var p = obj.point;
var s = "<table>";
s += '<tr><td style="color:' + p.color + ';"><strong>' + p[fd.entity] + '</strong> (' + p.group + ')</td></tr>';
s += row(fd.x, f(p.x));
s += row(fd.y, f(p.y));
s += row(fd.size, f(p.size));
s += "</table>";
return s;
});
chart.pointRange([5, fd.max_bubble_size * fd.max_bubble_size]);
break;
case 'area':
chart = nv.models.stackedAreaChart();
chart.style(fd.stacked_style);
chart.xScale(d3.time.scale.utc());
chart.xAxis
.showMaxMin(false)
.staggerLabels(true);
break;
default:
throw new Error("Unrecognized visualization for nvd3" + viz_type);
}
if ("showLegend" in chart && typeof fd.show_legend !== 'undefined') {
chart.showLegend(fd.show_legend);
}
var height = slice.height();
height -= 15; // accounting for the staggered xAxis
chart.height(height);
slice.container.css('height', height + 'px');
if ((viz_type === "line" || viz_type === "area") && fd.rich_tooltip) {
chart.useInteractiveGuideline(true);
}
if (fd.y_axis_zero) {
chart.forceY([0, 1]);
} else if (fd.y_log_scale) {
chart.yScale(d3.scale.log());
}
if (fd.x_log_scale) {
chart.xScale(d3.scale.log());
}
var xAxisFormatter = null;
if (viz_type === 'bubble') {
xAxisFormatter = d3.format('.3s');
} else if (fd.x_axis_format === 'smart_date') {
xAxisFormatter = px.formatDate;
chart.xAxis.tickFormat(xAxisFormatter);
} else if (fd.x_axis_format !== undefined) {
xAxisFormatter = px.timeFormatFactory(fd.x_axis_format);
chart.xAxis.tickFormat(xAxisFormatter);
}
if (chart.hasOwnProperty("x2Axis")) {
chart.x2Axis.tickFormat(xAxisFormatter);
height += 30;
}
if (viz_type === 'bubble') {
chart.xAxis.tickFormat(d3.format('.3s'));
} else if (fd.x_axis_format === 'smart_date') {
chart.xAxis.tickFormat(px.formatDate);
} else if (fd.x_axis_format !== undefined) {
chart.xAxis.tickFormat(px.timeFormatFactory(fd.x_axis_format));
}
if (chart.yAxis !== undefined) {
chart.yAxis.tickFormat(d3.format('.3s'));
}
if (fd.contribution || fd.num_period_compare || viz_type === 'compare') {
chart.yAxis.tickFormat(d3.format('.3p'));
if (chart.y2Axis !== undefined) {
chart.y2Axis.tickFormat(d3.format('.3p'));
}
} else if (fd.y_axis_format) {
chart.yAxis.tickFormat(d3.format(fd.y_axis_format));
if (chart.y2Axis !== undefined) {
chart.y2Axis.tickFormat(d3.format(fd.y_axis_format));
}
}
chart.color(function (d, i) {
return px.color.category21(d[colorKey]);
});
d3.select(slice.selector).html('');
d3.select(slice.selector).append("svg")
.datum(payload.data)
.transition().duration(500)
.attr('height', height)
.call(chart);
return chart;
});
slice.done(payload);
})
.fail(function (xhr) {
slice.error(xhr.responseText);
});
};
var update = function () {
if (chart && chart.update) {
chart.update();
}
};
return {
render: render,
resize: update
};
}
module.exports = nvd3Vis;

View File

@@ -1,29 +1,39 @@
px.registerViz('para', function(slice) {
// JS
var $ = window.$ || require('jquery');
var d3 = window.d3 || require('d3');
d3.parcoords = require('../vendor/parallel_coordinates/d3.parcoords.js');
d3.divgrid = require('../vendor/parallel_coordinates/divgrid.js');
// CSS
require('../vendor/parallel_coordinates/d3.parcoords.css');
function parallelCoordVis(slice) {
function refresh() {
$('#code').attr('rows', '15')
$.getJSON(slice.jsonEndpoint(), function(payload) {
$('#code').attr('rows', '15');
$.getJSON(slice.jsonEndpoint(), function (payload) {
var data = payload.data;
var fd = payload.form_data;
ext = d3.extent(data, function(d){
var ext = d3.extent(data, function (d) {
return d[fd.secondary_metric];
});
ext = [ext[0], (ext[1]-ext[0])/2,ext[1]];
ext = [ext[0], (ext[1] - ext[0]) / 2, ext[1]];
var cScale = d3.scale.linear()
.domain(ext)
.range(['red', 'grey', 'blue'])
.interpolate(d3.interpolateLab);
var color = function(d){return cScale(d[fd.secondary_metric])};
var container = d3.select(slice.selector);
if (fd.show_datatable)
var eff_height = slice.height() / 2;
else
var eff_height = slice.height();
var div = container.append('div')
var color = function (d) {
return cScale(d[fd.secondary_metric]);
};
var container = d3.select(slice.selector);
var eff_height = fd.show_datatable ? (slice.height() / 2) : slice.height();
container.append('div')
.attr('id', 'parcoords_' + slice.container_id)
.style('height', eff_height + 'px')
.classed("parcoords", true);
var parcoords = d3.parcoords()('#parcoords_' + slice.container_id)
.width(slice.width())
.color(color)
@@ -41,35 +51,42 @@ px.registerViz('para', function(slice) {
// create data table, row hover highlighting
var grid = d3.divgrid();
container.append("div")
.datum(data.slice(0,10))
.datum(data.slice(0, 10))
.attr('id', "grid")
.call(grid)
.classed("parcoords", true)
.selectAll(".row")
.on({
"mouseover": function(d) { parcoords.highlight([d]) },
"mouseout": parcoords.unhighlight
mouseover: function (d) {
parcoords.highlight([d]);
},
mouseout: parcoords.unhighlight
});
// update data table on brush event
parcoords.on("brush", function(d) {
parcoords.on("brush", function (d) {
d3.select("#grid")
.datum(d.slice(0,10))
.datum(d.slice(0, 10))
.call(grid)
.selectAll(".row")
.on({
"mouseover": function(d) { parcoords.highlight([d]) },
"mouseout": parcoords.unhighlight
mouseover: function (d) {
parcoords.highlight([d]);
},
mouseout: parcoords.unhighlight
});
});
}
slice.done();
})
.fail(function(xhr) {
slice.error(xhr.responseText);
});
};
.fail(function (xhr) {
slice.error(xhr.responseText);
});
}
return {
render: refresh,
resize: refresh,
resize: refresh
};
});
}
module.exports = parallelCoordVis;

View File

@@ -0,0 +1,13 @@
.gridster .widget.pivot_table {
overflow: auto !important;
}
.table tr>th {
padding: 1px 5px !important;
font-size: small !important;
}
.table tr>td {
padding: 1px 5px !important;
font-size: small !important;
}

View File

@@ -0,0 +1,31 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
require('datatables.net-bs');
require('./pivot_table.css');
require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
module.exports = function (slice) {
var container = slice.container;
var form_data = slice.data.form_data;
function refresh() {
$.getJSON(slice.jsonEndpoint(), function (json) {
container.html(json.data);
if (form_data.groupby.length === 1) {
var table = container.find('table').DataTable({
paging: false,
searching: false
});
table.column('-1').order('desc').draw();
}
slice.done(json);
}).fail(function (xhr) {
slice.error(xhr.responseText);
});
}
return {
render: refresh,
resize: refresh
};
};

View File

@@ -18,5 +18,3 @@
.sankey .link:hover {
stroke-opacity: .5;
}

View File

@@ -0,0 +1,138 @@
// CSS
require('./sankey.css');
// JS
var px = window.px || require('../javascripts/modules/caravel.js');
var d3 = window.d3 || require('d3');
d3.sankey = require('d3-sankey').sankey;
function sankeyVis(slice) {
var div = d3.select(slice.selector);
var render = function () {
var margin = {
top: 5,
right: 5,
bottom: 5,
left: 5
};
var width = slice.width() - margin.left - margin.right;
var height = slice.height() - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f");
div.selectAll("*").remove();
var svg = div.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
d3.json(slice.jsonEndpoint(), function (error, json) {
if (error !== null) {
slice.error(error.responseText);
return '';
}
var links = json.data;
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function (link) {
link.source = nodes[link.source] || (nodes[link.source] = { name: link.source });
link.target = nodes[link.target] || (nodes[link.target] = { name: link.target });
link.value = Number(link.value);
});
nodes = d3.values(nodes);
sankey
.nodes(nodes)
.links(links)
.layout(32);
var link = svg.append("g").selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function (d) {
return Math.max(1, d.dy);
})
.sort(function (a, b) {
return b.dy - a.dy;
});
link.append("title")
.text(function (d) {
return d.source.name + " → " + d.target.name + "\n" + formatNumber(d.value);
});
var node = svg.append("g").selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
})
.call(d3.behavior.drag()
.origin(function (d) {
return d;
})
.on("dragstart", function () {
this.parentNode.appendChild(this);
})
.on("drag", dragmove));
node.append("rect")
.attr("height", function (d) {
return d.dy;
})
.attr("width", sankey.nodeWidth())
.style("fill", function (d) {
d.color = px.color.category21(d.name.replace(/ .*/, ""));
return d.color;
})
.style("stroke", function (d) {
return d3.rgb(d.color).darker(2);
})
.append("title")
.text(function (d) {
return d.name + "\n" + formatNumber(d.value);
});
node.append("text")
.attr("x", -6)
.attr("y", function (d) {
return d.dy / 2;
})
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function (d) {
return d.name;
})
.filter(function (d) {
return d.x < width / 2;
})
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
function dragmove(d) {
d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
sankey.relayout();
link.attr("d", path);
}
slice.done(json);
});
};
return {
render: render,
resize: render
};
}
module.exports = sankeyVis;

View File

@@ -0,0 +1,49 @@
.sunburst text {
shape-rendering: crispEdges;
}
.sunburst path {
stroke: #333;
stroke-width: 0.5px;
}
.sunburst .center-label {
text-anchor: middle;
fill: #000;
pointer-events: none;
}
.sunburst .path-abs-percent {
font-size: 3.5em;
font-weight: 400;
}
.sunburst .path-cond-percent {
font-size: 2em;
}
.sunburst .path-metrics {
font-size: 1.5em;
}
.sunburst .path-ratio {
font-size: 1.2em;
}
.sunburst .breadcrumbs text {
font-weight: 600;
font-size: 1.2em;
text-anchor: middle;
fill: #000;
}
/* dashboard specific */
.dashboard .sunburst text {
font-size: 1em;
}
.dashboard .sunburst .path-abs-percent {
font-size: 2.5em;
}
.dashboard .sunburst .path-cond-percent {
font-size: 1.75em;
}
.dashboard .sunburst .path-metrics {
font-size: 1em;
}
.dashboard .sunburst .path-ratio {
font-size: 1em;
}

View File

@@ -0,0 +1,375 @@
var d3 = window.d3 || require('d3');
var px = require('../javascripts/modules/caravel.js');
var wrapSvgText = require('../javascripts/modules/utils.js').wrapSvgText;
require('./sunburst.css');
// Modified from http://bl.ocks.org/kerryrodden/7090426
function sunburstVis(slice) {
var container = d3.select(slice.selector);
var render = function () {
// vars with shared scope within this function
var margin = { top: 10, right: 5, bottom: 10, left: 5 };
var containerWidth = slice.width();
var containerHeight = slice.height();
var breadcrumbHeight = containerHeight * 0.085;
var visWidth = containerWidth - margin.left - margin.right;
var visHeight = containerHeight - margin.top - margin.bottom - breadcrumbHeight;
var radius = Math.min(visWidth, visHeight) / 2;
var colorByCategory = true; // color by category if primary/secondary metrics match
var maxBreadcrumbs, breadcrumbDims, // set based on data
totalSize, // total size of all segments; set after loading the data.
colorScale,
breadcrumbs, vis, arcs, gMiddleText; // dom handles
// Helper + path gen functions
var partition = d3.layout.partition()
.size([2 * Math.PI, radius * radius])
.value(function (d) { return d.m1; });
var arc = d3.svg.arc()
.startAngle(function (d) {
return d.x;
})
.endAngle(function (d) {
return d.x + d.dx;
})
.innerRadius(function (d) {
return Math.sqrt(d.y);
})
.outerRadius(function (d) {
return Math.sqrt(d.y + d.dy);
});
var formatNum = d3.format(".3s");
var formatPerc = d3.format(".3p");
container.select("svg").remove();
var svg = container.append("svg:svg")
.attr("width", containerWidth)
.attr("height", containerHeight);
d3.json(slice.jsonEndpoint(), function (error, rawData) {
if (error !== null) {
slice.error(error.responseText);
return '';
}
createBreadcrumbs(rawData);
createVisualization(rawData);
slice.done(rawData);
});
function createBreadcrumbs(rawData) {
var firstRowData = rawData.data[0];
maxBreadcrumbs = (firstRowData.length - 2) + 1; // -2 bc row contains 2x metrics, +extra for %label and buffer
breadcrumbDims = {
width: visWidth / maxBreadcrumbs,
height: breadcrumbHeight *0.8, // more margin
spacing: 3,
tipTailWidth: 10
};
breadcrumbs = svg.append("svg:g")
.attr("class", "breadcrumbs")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
breadcrumbs.append("svg:text")
.attr("class", "end-label");
}
// Main function to draw and set up the visualization, once we have the data.
function createVisualization(rawData) {
var tree = buildHierarchy(rawData.data);
vis = svg.append("svg:g")
.attr("class", "sunburst-vis")
.attr("transform", "translate(" + (margin.left + (visWidth / 2)) + "," + (margin.top + breadcrumbHeight + (visHeight / 2)) + ")")
.on("mouseleave", mouseleave);
arcs = vis.append("svg:g")
.attr("id", "arcs");
gMiddleText = vis.append("svg:g")
.attr("class", "center-label");
// Bounding circle underneath the sunburst, to make it easier to detect
// when the mouse leaves the parent g.
arcs.append("svg:circle")
.attr("r", radius)
.style("opacity", 0);
// For efficiency, filter nodes to keep only those large enough to see.
var nodes = partition.nodes(tree)
.filter(function (d) {
return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
});
var ext;
if (rawData.form_data.metric !== rawData.form_data.secondary_metric) {
colorByCategory = false;
ext = d3.extent(nodes, function (d) {
return d.m2 / d.m1;
});
colorScale = d3.scale.linear()
.domain([ext[0], ext[0] + ((ext[1] - ext[0]) / 2), ext[1]])
.range(["#00D1C1", "white", "#FFB400"]);
}
var path = arcs.data([tree]).selectAll("path")
.data(nodes)
.enter().append("svg:path")
.attr("display", function (d) {
return d.depth ? null : "none";
})
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", function (d) {
return colorByCategory ? px.color.category21(d.name) : colorScale(d.m2 / d.m1);
})
.style("opacity", 1)
.on("mouseenter", mouseenter);
// Get total size of the tree = value of root node from partition.
totalSize = path.node().__data__.value;
}
// Fade all but the current sequence, and show it in the breadcrumb trail.
function mouseenter(d) {
var sequenceArray = getAncestors(d);
var parentOfD = sequenceArray[sequenceArray.length - 2] || null;
var absolutePercentage = (d.m1 / totalSize).toPrecision(3);
var conditionalPercentage = parentOfD ? (d.m1 / parentOfD.m1).toPrecision(3) : null;
var absolutePercString = formatPerc(absolutePercentage);
var conditionalPercString = parentOfD ? formatPerc(conditionalPercentage) : "";
var yOffsets = ["-25", "7", "35", "60"]; // 3 levels of text if inner-most level, 4 otherwise
var offsetIndex = 0;
// If metrics match, assume we are coloring by category
var metricsMatch = Math.abs(d.m1 - d.m2) < 0.00001;
gMiddleText.selectAll("*").remove();
gMiddleText.append("text")
.attr("class", "path-abs-percent")
.attr("y", yOffsets[offsetIndex++])
.text(absolutePercString + " of total");
if (conditionalPercString) {
gMiddleText.append("text")
.attr("class", "path-cond-percent")
.attr("y", yOffsets[offsetIndex++])
.text(conditionalPercString + " of parent");
}
gMiddleText.append("text")
.attr("class", "path-metrics")
.attr("y", yOffsets[offsetIndex++])
.text("m1: " + formatNum(d.m1) + (metricsMatch ? "" : ", m2: " + formatNum(d.m2)));
gMiddleText.append("text")
.attr("class", "path-ratio")
.attr("y", yOffsets[offsetIndex++])
.text((metricsMatch ? "" : ("m2/m1: " + formatPerc(d.m2 / d.m1))) );
// Reset and fade all the segments.
arcs.selectAll("path")
.style("stroke-width", null)
.style("stroke", null)
.style("opacity", 0.7);
// Then highlight only those that are an ancestor of the current segment.
arcs.selectAll("path")
.filter(function (node) {
return (sequenceArray.indexOf(node) >= 0);
})
.style("opacity", 1)
.style("stroke-width", "2px")
.style("stroke", "#000");
updateBreadcrumbs(sequenceArray, absolutePercString);
}
// Restore everything to full opacity when moving off the visualization.
function mouseleave(d) {
// Hide the breadcrumb trail
breadcrumbs.style("visibility", "hidden");
gMiddleText.selectAll("*").remove();
// Deactivate all segments during transition.
arcs.selectAll("path").on("mouseenter", null);
//gMiddleText.selectAll("*").remove();
// Transition each segment to full opacity and then reactivate it.
arcs.selectAll("path")
.transition()
.duration(200)
.style("opacity", 1)
.style("stroke", null)
.style("stroke-width", null)
.each("end", function () {
d3.select(this).on("mouseenter", mouseenter);
});
}
// Given a node in a partition layout, return an array of all of its ancestor
// nodes, highest first, but excluding the root.
function getAncestors(node) {
var path = [];
var current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
// Generate a string that describes the points of a breadcrumb polygon.
function breadcrumbPoints(d, i) {
var points = [];
points.push("0,0");
points.push(breadcrumbDims.width + ",0");
points.push(breadcrumbDims.width + breadcrumbDims.tipTailWidth + "," + (breadcrumbDims.height / 2));
points.push(breadcrumbDims.width+ "," + breadcrumbDims.height);
points.push("0," + breadcrumbDims.height);
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
points.push(breadcrumbDims.tipTailWidth + "," + (breadcrumbDims.height / 2));
}
return points.join(" ");
}
function updateBreadcrumbs(sequenceArray, percentageString) {
var g = breadcrumbs.selectAll("g")
.data(sequenceArray, function (d) {
return d.name + d.depth;
});
// Add breadcrumb and label for entering nodes.
var entering = g.enter().append("svg:g");
entering.append("svg:polygon")
.attr("points", breadcrumbPoints)
.style("fill", function (d) {
return colorByCategory ? px.color.category21(d.name) : colorScale(d.m2 / d.m1);
});
entering.append("svg:text")
.attr("x", (breadcrumbDims.width + breadcrumbDims.tipTailWidth) / 2)
.attr("y", breadcrumbDims.height / 4)
.attr("dy", "0.35em")
.attr("class", "step-label")
.text(function (d) { return d.name; })
.call(wrapSvgText, breadcrumbDims.width, breadcrumbDims.height / 2);
// Set position for entering and updating nodes.
g.attr("transform", function (d, i) {
return "translate(" + i * (breadcrumbDims.width + breadcrumbDims.spacing) + ", 0)";
});
// Remove exiting nodes.
g.exit().remove();
// Now move and update the percentage at the end.
breadcrumbs.select(".end-label")
.attr("x", (sequenceArray.length + 0.5) * (breadcrumbDims.width + breadcrumbDims.spacing))
.attr("y", breadcrumbDims.height / 2)
.attr("dy", "0.35em")
.text(percentageString);
// Make the breadcrumb trail visible, if it's hidden.
breadcrumbs.style("visibility", null);
}
function buildHierarchy(rows) {
var root = {
name: "root",
children: []
};
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var m1 = Number(row[row.length - 2]);
var m2 = Number(row[row.length - 1]);
var levels = row.slice(0, row.length - 2);
if (isNaN(m1)) { // e.g. if this is a header row
continue;
}
var currentNode = root;
for (var j = 0; j < levels.length; j++) {
var children = currentNode.children || [];
var nodeName = levels[j];
// If the next node has the name "0", it will
var isLeafNode = (j >= levels.length - 1) || levels[j+1] === 0;
var childNode;
if (!isLeafNode) {
// Not yet at the end of the sequence; move down the tree.
var foundChild = false;
for (var k = 0; k < children.length; k++) {
if (children[k].name === nodeName) {
childNode = children[k];
foundChild = true;
break;
}
}
// If we don't already have a child node for this branch, create it.
if (!foundChild) {
childNode = {
name: nodeName,
children: []
};
children.push(childNode);
}
currentNode = childNode;
} else if (nodeName !== 0) {
// Reached the end of the sequence; create a leaf node.
childNode = {
name: nodeName,
m1: m1,
m2: m2
};
children.push(childNode);
}
}
}
function recurse(node) {
if (node.children) {
var sums;
var m1 = 0;
var m2 = 0;
for (var i = 0; i < node.children.length; i++) {
sums = recurse(node.children[i]);
m1 += sums[0];
m2 += sums[1];
}
node.m1 = m1;
node.m2 = m2;
}
return [node.m1, node.m2];
}
recurse(root);
return root;
}
};
return {
render: render,
resize: render
};
}
module.exports = sunburstVis;

Some files were not shown because too many files have changed in this diff Show More