Compare commits
181 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5dead4791 | ||
|
|
d933a21216 | ||
|
|
59169bfc96 | ||
|
|
bcca840f01 | ||
|
|
90a3b9f2c4 | ||
|
|
a37e431150 | ||
|
|
bf38c714a5 | ||
|
|
6b0b03e009 | ||
|
|
8556b098f9 | ||
|
|
d122b37f5d | ||
|
|
1756c27930 | ||
|
|
7867267608 | ||
|
|
92d588694b | ||
|
|
d10eaeccc9 | ||
|
|
c2bb49fec5 | ||
|
|
062f2b81cf | ||
|
|
65e72d0d07 | ||
|
|
345727635e | ||
|
|
c2baa53b06 | ||
|
|
31758827ae | ||
|
|
81de51bf6f | ||
|
|
0d1f27dbc1 | ||
|
|
c7282882d5 | ||
|
|
f9d04e8a72 | ||
|
|
bf2e804331 | ||
|
|
c349b0a1c1 | ||
|
|
4d640b5a3d | ||
|
|
380c3f0c75 | ||
|
|
e3e8202c98 | ||
|
|
889844407f | ||
|
|
f1830c36cf | ||
|
|
92f73b67ca | ||
|
|
9c1af66ba4 | ||
|
|
2b31ab498b | ||
|
|
034fd077e1 | ||
|
|
ca4443247e | ||
|
|
6f96252e45 | ||
|
|
0b93fd373d | ||
|
|
c3789d53b4 | ||
|
|
aec3c0b358 | ||
|
|
ef45c20558 | ||
|
|
10ab678fc6 | ||
|
|
87fb40ae26 | ||
|
|
d245fb91b5 | ||
|
|
c60032acce | ||
|
|
d2f51900f1 | ||
|
|
93405dc23a | ||
|
|
dafdb51f35 | ||
|
|
2d0fdf7e59 | ||
|
|
718de6cd50 | ||
|
|
38062f160a | ||
|
|
9a5a27a101 | ||
|
|
481d821721 | ||
|
|
7b111b790e | ||
|
|
ab92e7a94d | ||
|
|
22d2b7fe0f | ||
|
|
23319eed10 | ||
|
|
12cc064059 | ||
|
|
ffa29b3909 | ||
|
|
ee4e3c1b98 | ||
|
|
ebc16bb3fa | ||
|
|
0e2c0ce858 | ||
|
|
807f4dd0e5 | ||
|
|
b87d8a0fbf | ||
|
|
0b3e2e00cc | ||
|
|
167fb64d0c | ||
|
|
eeba80c487 | ||
|
|
a3f92f687d | ||
|
|
a4b61e97db | ||
|
|
9595dcff4d | ||
|
|
58ab4e2415 | ||
|
|
2b71b72065 | ||
|
|
bd9051a168 | ||
|
|
6f63b3033f | ||
|
|
f659caa06b | ||
|
|
5b7fe2b643 | ||
|
|
e5553ab45e | ||
|
|
4a77b70046 | ||
|
|
f67c6b5f46 | ||
|
|
fd407424ad | ||
|
|
e1b871982e | ||
|
|
b164244cd8 | ||
|
|
00226cce7f | ||
|
|
3a3d5cd7fb | ||
|
|
c6b77206ea | ||
|
|
d885dd34f9 | ||
|
|
54fe2348fc | ||
|
|
6e1413d2dd | ||
|
|
0296b839ec | ||
|
|
1c44f34490 | ||
|
|
10f3991e1f | ||
|
|
cda1bd59f9 | ||
|
|
8e27099866 | ||
|
|
60bce9ed59 | ||
|
|
1b4e750b2a | ||
|
|
619d35878f | ||
|
|
d48796f00e | ||
|
|
5561a4932a | ||
|
|
1aee5b7801 | ||
|
|
74c72b3ce4 | ||
|
|
09021aacad | ||
|
|
26c725171b | ||
|
|
4a0ea5fff7 | ||
|
|
301dce2dd1 | ||
|
|
6dce6df6b2 | ||
|
|
2102e04fec | ||
|
|
2397645ed4 | ||
|
|
0a8a1eda94 | ||
|
|
4d2492d8fd | ||
|
|
48210d5275 | ||
|
|
5804991b19 | ||
|
|
5be7b03ba4 | ||
|
|
a898c27403 | ||
|
|
49747150ae | ||
|
|
fdce2aac0d | ||
|
|
1c19f7fc77 | ||
|
|
12e20ca440 | ||
|
|
0ccc19ff9b | ||
|
|
e7bed92519 | ||
|
|
8c0870e6ea | ||
|
|
c9203554e7 | ||
|
|
2378fdf9ce | ||
|
|
23b6bca89e | ||
|
|
7a7eb33348 | ||
|
|
fee6b3fafa | ||
|
|
0f637bdd2e | ||
|
|
10a1eddaa7 | ||
|
|
21d1c0a1b5 | ||
|
|
3118d1b302 | ||
|
|
2362b5a157 | ||
|
|
7ede732892 | ||
|
|
5b10b19ed7 | ||
|
|
14f298b385 | ||
|
|
89da51f8d7 | ||
|
|
d5487c6b25 | ||
|
|
a244f3aafb | ||
|
|
9d3bf7763c | ||
|
|
b0f10a90ce | ||
|
|
417749f880 | ||
|
|
fded04a51d | ||
|
|
d8192eca0a | ||
|
|
2d3edf3a2e | ||
|
|
0890d22899 | ||
|
|
bad3128df9 | ||
|
|
48c8a90247 | ||
|
|
a9ba47fe12 | ||
|
|
f09f3d7c4b | ||
|
|
e17d78e196 | ||
|
|
9450c12552 | ||
|
|
f01b41827e | ||
|
|
b5f4d3b515 | ||
|
|
28e5a87cd0 | ||
|
|
10c48f04cd | ||
|
|
f79aca1796 | ||
|
|
be6b2fe556 | ||
|
|
8f4f5b126a | ||
|
|
2b1dcf0d2e | ||
|
|
987a1afbb7 | ||
|
|
f453932589 | ||
|
|
3dc9996e96 | ||
|
|
3197c4b3f9 | ||
|
|
f0b2f985b4 | ||
|
|
1c9c154f0f | ||
|
|
ebf55bf20d | ||
|
|
3461538fed | ||
|
|
7cdb23f585 | ||
|
|
73daf2f8d1 | ||
|
|
ae1e66f09b | ||
|
|
06080945b6 | ||
|
|
61ab06d640 | ||
|
|
0c045a9e52 | ||
|
|
27fb810dd7 | ||
|
|
2aa0e0dce0 | ||
|
|
417b5a5e09 | ||
|
|
7c5e660bd1 | ||
|
|
1a58b6d441 | ||
|
|
1ab89631b9 | ||
|
|
e1eb236cf4 | ||
|
|
95b7b9779b | ||
|
|
b0fa4bc924 | ||
|
|
49590e28e3 |
@@ -1 +1 @@
|
||||
repo_token: EMkVRVEKYgUESKaNN9QyOhPnFnKNqyDcJ
|
||||
repo_token: eESbYiv4An6KEvjpmguDs4L7YkubXbqn1
|
||||
|
||||
10
.gitignore
vendored
@@ -1,17 +1,21 @@
|
||||
*.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
|
||||
|
||||
|
||||
23
.landscape.yml
Normal 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(/|$)
|
||||
14
.travis.yml
@@ -5,16 +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
|
||||
- cd panoramix/assets
|
||||
- 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
|
||||
- cd panoramix/assets
|
||||
- npm run lint
|
||||
|
||||
185
CHANGELOG.md
Normal 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)
|
||||
@@ -30,8 +30,8 @@ Look through the GitHub issues for features. Anything tagged with
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -49,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 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
|
||||
@@ -66,24 +66,24 @@ 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
|
||||
|
||||
|
||||
## Setting up the node / npm javascript environment
|
||||
|
||||
`panoramix/assets` contains all npm-managed, front end assets.
|
||||
`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.
|
||||
@@ -116,7 +116,7 @@ new `node_modules/` folder within `assets/`.
|
||||
npm install
|
||||
```
|
||||
|
||||
To parse and generate bundled files for panoramix, run either of the
|
||||
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.
|
||||
|
||||
@@ -132,7 +132,7 @@ For every development session you will have to start a flask dev server
|
||||
as well as an npm watcher
|
||||
|
||||
```
|
||||
panoramix runserver -d -p 8081
|
||||
caravel runserver -d -p 8081
|
||||
npm run dev
|
||||
```
|
||||
|
||||
@@ -144,8 +144,12 @@ 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:
|
||||
@@ -153,12 +157,12 @@ Generate the documentation with:
|
||||
cd docs && ./build.sh
|
||||
|
||||
## CSS Themes
|
||||
As part of the npm build process, CSS for Panoramix is compiled from ```Less```, a dynamic stylesheet language.
|
||||
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 Panoramix, either by overriding CSS rules or preferably
|
||||
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 Panoramix are derived from
|
||||
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]
|
||||
|
||||
|
||||
11
INTHEWILD.md
Normal 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
|
||||
14
MANIFEST.in
@@ -1,8 +1,8 @@
|
||||
recursive-include panoramix/templates *
|
||||
recursive-include panoramix/static *
|
||||
recursive-exclude panoramix/static/assets/node_modules *
|
||||
recursive-include panoramix/static/assets/node_modules/font-awesome *
|
||||
recursive-exclude panoramix/static/docs *
|
||||
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 panoramix/data *
|
||||
recursive-include panoramix/migrations *
|
||||
recursive-include caravel/data *
|
||||
recursive-include caravel/migrations *
|
||||
|
||||
179
README.md
@@ -1,183 +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"/>
|
||||
|
||||
[](https://gitter.im/mistercrunch/panoramix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||

|
||||
[](https://coveralls.io/github/mistercrunch/panoramix?branch=master)
|
||||
[](https://landscape.io/github/mistercrunch/panoramix/master)
|
||||
[](https://travis-ci.org/airbnb/caravel)
|
||||
[](https://badge.fury.io/py/caravel)
|
||||
[](https://coveralls.io/github/airbnb/caravel?branch=master)
|
||||
[](https://landscape.io/github/airbnb/caravel/master)
|
||||
[](https://requires.io/github/airbnb/caravel/requirements/?branch=master)
|
||||
[](https://gitter.im/airbnb/caravel?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](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.
|
||||
|
||||
[this project used to be named **Panoramix**]
|
||||
|
||||
Video - Introduction to Panoramix
|
||||
|
||||
Video - Introduction to Caravel
|
||||
---------------------------------
|
||||
[](http://www.youtube.com/watch?v=3Txm_nj_R7M)
|
||||
[](http://www.youtube.com/watch?v=3Txm_nj_R7M)
|
||||
|
||||
Screenshots
|
||||
------------
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
Panoramix
|
||||
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
|
||||
----------------
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
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/mistercrunch/panoramix/blob/master/CONTRIBUTING.md)
|
||||
Interested in contributing? Casual hacking? Check out [Contributing.MD](https://github.com/airbnb/caravel/blob/master/CONTRIBUTING.md)
|
||||
|
||||
12
TODO.md
@@ -1,5 +1,5 @@
|
||||
# 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
|
||||
@@ -7,13 +7,9 @@ List of TODO items for Panoramix
|
||||
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
|
||||
Panoramix would discover and expose the plugins
|
||||
Caravel would discover and expose the plugins
|
||||
|
||||
## Features
|
||||
* **Stars:** set dashboards, slices and datasets as favorites
|
||||
* **Homepage:** a page that has links to your Slices and Dashes, favorited
|
||||
content, feed of recent actions (people viewing your objects)
|
||||
* **Comments:** allow for people to comment on slices and dashes
|
||||
* **Dashboard URL filters:** `{dash_url}#fltin__fieldname__value1,value2`
|
||||
* **Default slice:** choose a default slice for the dataset instead of
|
||||
default endpoint
|
||||
@@ -33,9 +29,13 @@ List of TODO items for Panoramix
|
||||
* **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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[python: **.py]
|
||||
[jinja2: **/templates/**.html]
|
||||
encoding = utf-8
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -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, redirect
|
||||
from flask.ext.appbuilder import SQLA, AppBuilder, IndexView
|
||||
from flask.ext.appbuilder.baseviews import expose
|
||||
from flask.ext.migrate import Migrate
|
||||
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,21 +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):
|
||||
@expose('/')
|
||||
def index(self):
|
||||
return redirect('/panoramix/featured')
|
||||
return redirect('/caravel/welcome')
|
||||
|
||||
appbuilder = AppBuilder(
|
||||
app, db.session,
|
||||
base_template='panoramix/base.html',
|
||||
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 config, views
|
||||
from caravel import config, views # noqa
|
||||
@@ -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"+
|
||||
@@ -57,13 +57,13 @@
|
||||
}],
|
||||
"max-depth": [2, 5],
|
||||
"max-len": [0, 80, 4],
|
||||
"max-nested-callbacks": [1, 2],
|
||||
"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": [2],
|
||||
"no-console": [1, { allow: ["warn", "error"] }],
|
||||
"no-const-assign": [2],
|
||||
"no-constant-condition": [2],
|
||||
"no-control-regex": [2],
|
||||
|
Before Width: | Height: | Size: 459 KiB After Width: | Height: | Size: 459 KiB |
BIN
caravel/assets/images/caravel.jpg
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
caravel/assets/images/caravel_logo.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 552 KiB After Width: | Height: | Size: 552 KiB |
|
Before Width: | Height: | Size: 236 KiB After Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 702 KiB After Width: | Height: | Size: 702 KiB |
|
Before Width: | Height: | Size: 328 KiB After Width: | Height: | Size: 328 KiB |
BIN
caravel/assets/images/favicon.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 147 KiB |
BIN
caravel/assets/images/tutorial/add_db.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
caravel/assets/images/tutorial/created.png
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
caravel/assets/images/tutorial/db_added.png
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
caravel/assets/images/tutorial/db_menu.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
caravel/assets/images/tutorial/db_plus.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
caravel/assets/images/tutorial/explore.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
caravel/assets/images/tutorial/in_new_dash.png
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
caravel/assets/images/tutorial/matrix.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
caravel/assets/images/tutorial/new_dash.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
caravel/assets/images/tutorial/pen.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
caravel/assets/images/tutorial/search.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
caravel/assets/images/viz_thumbnails/bar.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
caravel/assets/images/viz_thumbnails/big_number.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
caravel/assets/images/viz_thumbnails/bubble.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
caravel/assets/images/viz_thumbnails/filter.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
caravel/assets/images/viz_thumbnails/force_directed.png
Normal file
|
After Width: | Height: | Size: 242 KiB |
BIN
caravel/assets/images/viz_thumbnails/line.png
Normal file
|
After Width: | Height: | Size: 314 KiB |
BIN
caravel/assets/images/viz_thumbnails/percent_change.png
Normal file
|
After Width: | Height: | Size: 253 KiB |
BIN
caravel/assets/images/viz_thumbnails/pie.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
caravel/assets/images/viz_thumbnails/pivot_table.png
Normal file
|
After Width: | Height: | Size: 270 KiB |
BIN
caravel/assets/images/viz_thumbnails/sankey.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
caravel/assets/images/viz_thumbnails/stacked.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
caravel/assets/images/viz_thumbnails/sunburst.png
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
caravel/assets/images/viz_thumbnails/table.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
caravel/assets/images/viz_thumbnails/word_cloud.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
@@ -1,5 +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('../node_modules/jquery-ui/themes/base/jquery-ui.css');
|
||||
require('select2');
|
||||
require('../vendor/select2.sortable.js');
|
||||
@@ -1,14 +1,15 @@
|
||||
var $ = window.$ = require('jquery');
|
||||
var jQuery = window.jQuery = $;
|
||||
var px = require('./modules/panoramix.js');
|
||||
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('./panoramix-select2.js');
|
||||
require('./caravel-select2.js');
|
||||
require('../node_modules/gridster/dist/jquery.gridster.min.css');
|
||||
require('../node_modules/gridster/dist/jquery.gridster.min.js');
|
||||
|
||||
@@ -17,15 +18,21 @@ var Dashboard = function (dashboardData) {
|
||||
filters: {},
|
||||
init: function () {
|
||||
this.initDashboardView();
|
||||
px.initFavStars();
|
||||
var sliceObjects = [],
|
||||
dash = this;
|
||||
dashboard.slices.forEach(function (data) {
|
||||
var slice = px.Slice(data, dash);
|
||||
$("#slice_" + data.slice_id).find('a.refresh').click(function () {
|
||||
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();
|
||||
});
|
||||
sliceObjects.push(slice);
|
||||
slice.render();
|
||||
}
|
||||
});
|
||||
this.slices = sliceObjects;
|
||||
},
|
||||
@@ -51,7 +58,7 @@ var Dashboard = function (dashboardData) {
|
||||
return JSON.stringify(this.filters, null, 4);
|
||||
},
|
||||
refreshExcept: function (slice_id) {
|
||||
var immune = this.metadata.filter_immune_slices;
|
||||
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();
|
||||
@@ -77,25 +84,26 @@ var Dashboard = function (dashboardData) {
|
||||
this.refreshExcept(slice_id);
|
||||
},
|
||||
getSlice: function (slice_id) {
|
||||
this.slices.forEach(function (slice, i) {
|
||||
if (slice.slice_id === slice_id) {
|
||||
return slice;
|
||||
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: [100, 100],
|
||||
widget_base_dimensions: [95, 95],
|
||||
draggable: {
|
||||
handle: '.drag'
|
||||
},
|
||||
resize: {
|
||||
enabled: true,
|
||||
stop: function (e, ui, element) {
|
||||
var slice_data = $(element).data('slice');
|
||||
dashboard.getSlice(slice_data.slice_id).resize();
|
||||
dashboard.getSlice($(element).attr('slice_id')).resize();
|
||||
}
|
||||
},
|
||||
serialize_params: function (_w, wgd) {
|
||||
@@ -108,6 +116,16 @@ var Dashboard = function (dashboardData) {
|
||||
};
|
||||
}
|
||||
}).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 = {};
|
||||
@@ -125,15 +143,22 @@ var Dashboard = function (dashboardData) {
|
||||
};
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: '/panoramix/save_dash/' + dashboard.id + '/',
|
||||
url: '/caravel/save_dash/' + dashboard.id + '/',
|
||||
data: {
|
||||
data: JSON.stringify(data)
|
||||
},
|
||||
success: function () {
|
||||
alert("Saved!");
|
||||
showModal({
|
||||
title: "Success",
|
||||
body: "This dashboard was saved successfully."
|
||||
});
|
||||
},
|
||||
error: function () {
|
||||
alert("Error :(");
|
||||
error: function (error) {
|
||||
showModal({
|
||||
title: "Error",
|
||||
body: "Sorry, there was an error saving this dashboard:<br />" + error
|
||||
});
|
||||
console.warn("Save dashboard error", error);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -144,7 +169,8 @@ var Dashboard = function (dashboardData) {
|
||||
editor.setTheme("ace/theme/crimson_editor");
|
||||
editor.setOptions({
|
||||
minLines: 16,
|
||||
maxLines: Infinity
|
||||
maxLines: Infinity,
|
||||
useWorker: false
|
||||
});
|
||||
editor.getSession().setMode("ace/mode/css");
|
||||
|
||||
@@ -154,34 +180,72 @@ var Dashboard = function (dashboardData) {
|
||||
$("#css_template").on("change", function () {
|
||||
var css = $(this).find('option:selected').data('css');
|
||||
editor.setValue(css);
|
||||
|
||||
$('#dash_css').val(css);
|
||||
$("#user_style").html(css);
|
||||
injectCss("dashboard-template", css);
|
||||
|
||||
});
|
||||
$('#filters').click(function () {
|
||||
alert(dashboard.readFilters());
|
||||
showModal({
|
||||
title: "<span class='fa fa-info-circle'></span> Current Global Filters",
|
||||
body: "The following global filters are currently applied:<br/>" + dashboard.readFilters()
|
||||
});
|
||||
});
|
||||
$("a.closeslice").click(function () {
|
||||
$('#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);
|
||||
});
|
||||
$(".slice_info").click(function () {
|
||||
var widget = $(this).parents('.widget');
|
||||
var slice_description = widget.find('.slice_description');
|
||||
slice_description.slideToggle(500, function () {
|
||||
widget.find('.refresh').click();
|
||||
});
|
||||
});
|
||||
$("table.slice_header").mouseover(function () {
|
||||
$(this).find("td.icons nobr").show();
|
||||
});
|
||||
$("table.slice_header").mouseout(function () {
|
||||
$(this).find("td.icons nobr").hide();
|
||||
|
||||
$("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);
|
||||
$("#user_style").html(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();
|
||||
@@ -190,4 +254,5 @@ var Dashboard = function (dashboardData) {
|
||||
|
||||
$(document).ready(function () {
|
||||
Dashboard($('.dashboard').data('dashboard'));
|
||||
$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
|
||||
});
|
||||
@@ -5,13 +5,14 @@
|
||||
// js
|
||||
var $ = window.$ = require('jquery');
|
||||
var jQuery = window.jQuery = $;
|
||||
var px = require('./modules/panoramix.js');
|
||||
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('./panoramix-select2.js');
|
||||
require('./caravel-select2.js');
|
||||
|
||||
require('../node_modules/bootstrap-toggle/js/bootstrap-toggle.min.js');
|
||||
|
||||
@@ -53,19 +54,23 @@ function prepForm() {
|
||||
});
|
||||
}
|
||||
|
||||
function renderSlice() {
|
||||
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();
|
||||
slice.render(force);
|
||||
}
|
||||
|
||||
function initExploreView() {
|
||||
|
||||
function druidify() {
|
||||
$('div.alert').remove();
|
||||
history.pushState({}, document.title, slice.querystring());
|
||||
renderSlice();
|
||||
}
|
||||
|
||||
function get_collapsed_fieldsets() {
|
||||
var collapsed_fieldsets = $("#collapsed_fieldsets").val();
|
||||
|
||||
@@ -85,9 +90,9 @@ function initExploreView() {
|
||||
|
||||
if (parent.hasClass("collapsed")) {
|
||||
if (animation) {
|
||||
parent.find(".fieldset_content").slideDown();
|
||||
parent.find(".panel-body").slideDown();
|
||||
} else {
|
||||
parent.find(".fieldset_content").show();
|
||||
parent.find(".panel-body").show();
|
||||
}
|
||||
parent.removeClass("collapsed");
|
||||
parent.find("span.collapser").text("[-]");
|
||||
@@ -99,9 +104,9 @@ function initExploreView() {
|
||||
}
|
||||
} else { // not collapsed
|
||||
if (animation) {
|
||||
parent.find(".fieldset_content").slideUp();
|
||||
parent.find(".panel-body").slideUp();
|
||||
} else {
|
||||
parent.find(".fieldset_content").hide();
|
||||
parent.find(".panel-body").hide();
|
||||
}
|
||||
|
||||
parent.addClass("collapsed");
|
||||
@@ -115,8 +120,11 @@ function initExploreView() {
|
||||
$("#collapsed_fieldsets").val(collapsed_fieldsets.join("||"));
|
||||
}
|
||||
|
||||
$('legend').click(function () {
|
||||
px.initFavStars();
|
||||
|
||||
$('form .panel-heading').click(function () {
|
||||
toggle_fieldset($(this), true);
|
||||
$(this).css('cursor', 'pointer');
|
||||
});
|
||||
|
||||
function copyURLToClipboard(url) {
|
||||
@@ -175,15 +183,25 @@ function initExploreView() {
|
||||
$shortner.popover('destroy');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
alert("Error :(");
|
||||
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);
|
||||
@@ -199,9 +217,7 @@ function initExploreView() {
|
||||
bindOrder: 'sortableStop'
|
||||
});
|
||||
$("form").show();
|
||||
$('[data-toggle="tooltip"]').tooltip({
|
||||
container: 'body'
|
||||
});
|
||||
$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
|
||||
$(".ui-helper-hidden-accessible").remove(); // jQuery-ui 1.11+ creates a div for every tooltip
|
||||
|
||||
function set_filters() {
|
||||
@@ -256,7 +272,9 @@ function initExploreView() {
|
||||
}
|
||||
});
|
||||
|
||||
$(".druidify").click(druidify);
|
||||
$(".query").click(function () {
|
||||
query(true);
|
||||
});
|
||||
|
||||
function create_choices(term, data) {
|
||||
var filtered = $(data).filter(function () {
|
||||
@@ -319,7 +337,7 @@ $(document).ready(function () {
|
||||
$('.slice').data('slice', slice);
|
||||
|
||||
// call vis render method, which issues ajax
|
||||
renderSlice();
|
||||
query(false, false);
|
||||
|
||||
// make checkbox inputs display as toggles
|
||||
$(':checkbox')
|
||||
@@ -8,7 +8,7 @@ class App extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<Jumbotron>
|
||||
<h1>Panoramix</h1>
|
||||
<h1>Caravel</h1>
|
||||
<p>Extensible visualization tool for exploring data from any database.</p>
|
||||
</Jumbotron>
|
||||
);
|
||||
@@ -2,14 +2,13 @@ var $ = require('jquery');
|
||||
var jQuery = $;
|
||||
var d3 = require('d3');
|
||||
|
||||
require('../../stylesheets/panoramix.css');
|
||||
|
||||
// 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',
|
||||
@@ -46,7 +45,7 @@ var color = function () {
|
||||
// Color factory
|
||||
var seen = {};
|
||||
return function (s) {
|
||||
// next line is for dashed series that should have the same color
|
||||
// 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;
|
||||
@@ -132,6 +131,46 @@ var px = (function () {
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -169,16 +208,51 @@ var px = (function () {
|
||||
}
|
||||
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() + "&json=true";
|
||||
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);
|
||||
}
|
||||
@@ -194,7 +268,8 @@ var px = (function () {
|
||||
$('#csv').click(function () {
|
||||
window.location = data.csv_endpoint;
|
||||
});
|
||||
$('.btn-group.results span').removeAttr('disabled');
|
||||
$('.btn-group.results span,a').removeAttr('disabled');
|
||||
$('.query-and-save button').removeAttr('disabled');
|
||||
always(data);
|
||||
},
|
||||
error: function (msg) {
|
||||
@@ -204,6 +279,8 @@ var px = (function () {
|
||||
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 () {
|
||||
@@ -216,8 +293,8 @@ var px = (function () {
|
||||
if (slice_description.is(":visible")) {
|
||||
others += widget.find('.slice_description').height() + 25;
|
||||
}
|
||||
others += widget.find('.slice_header').height();
|
||||
return widget.height() - others;
|
||||
others += widget.find('.chart-header').height();
|
||||
return widget.height() - others - 10;
|
||||
},
|
||||
bindResizeToWindowResize: function () {
|
||||
var resizeTimer;
|
||||
@@ -228,8 +305,11 @@ var px = (function () {
|
||||
}, 500);
|
||||
});
|
||||
},
|
||||
render: function () {
|
||||
$('.btn-group.results span').attr('disabled', 'disabled');
|
||||
render: function (force) {
|
||||
if (force === undefined) {
|
||||
force = false;
|
||||
}
|
||||
this.force = force;
|
||||
token.find("img.loading").show();
|
||||
container.hide();
|
||||
container.html('');
|
||||
@@ -293,7 +373,8 @@ var px = (function () {
|
||||
formatDate: formatDate,
|
||||
timeFormatFactory: timeFormatFactory,
|
||||
color: color(),
|
||||
getParam: getParam
|
||||
getParam: getParam,
|
||||
initFavStars: initFavStars
|
||||
};
|
||||
})();
|
||||
|
||||
80
caravel/assets/javascripts/modules/utils.js
Normal 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
|
||||
};
|
||||
@@ -1,7 +1,10 @@
|
||||
var $ = window.$ = require('jquery');
|
||||
var jQuery = window.jQuery = $;
|
||||
var showModal = require('./modules/utils.js').showModal;
|
||||
|
||||
require('select2');
|
||||
require('datatables');
|
||||
require('datatables.net-bs');
|
||||
require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
|
||||
require('bootstrap');
|
||||
|
||||
var ace = require('brace');
|
||||
@@ -36,17 +39,20 @@ $(document).ready(function () {
|
||||
|
||||
function showTableMetadata() {
|
||||
$(".metadata").load(
|
||||
'/panoramix/table/' + database_id + '/' + $("#dbtable").val() + '/');
|
||||
'/caravel/table/' + database_id + '/' + $("#dbtable").val() + '/');
|
||||
}
|
||||
$("#dbtable").on("change", showTableMetadata);
|
||||
showTableMetadata();
|
||||
$("#create_view").click(function () {
|
||||
alert("Not implemented");
|
||||
showModal({
|
||||
title: "Error",
|
||||
body: "Sorry, this feature is not yet implemented"
|
||||
});
|
||||
});
|
||||
$(".sqlcontent").show();
|
||||
|
||||
function selectStarOnClick() {
|
||||
$.ajax('/panoramix/select_star/' + database_id + '/' + $("#dbtable").val() + '/')
|
||||
$.ajax('/caravel/select_star/' + database_id + '/' + $("#dbtable").val() + '/')
|
||||
.done(function (msg) {
|
||||
editor.setValue(msg);
|
||||
});
|
||||
@@ -67,7 +73,7 @@ $(document).ready(function () {
|
||||
history.pushState({}, document.title, '?sql=' + encodeURIComponent(editor.getValue()));
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: '/panoramix/runsql/',
|
||||
url: '/caravel/runsql/',
|
||||
data: {
|
||||
data: JSON.stringify({
|
||||
database_id: $('#database_id').val(),
|
||||
@@ -1,6 +1,6 @@
|
||||
var $ = window.$ = require('jquery');
|
||||
var jQuery = window.jQuery = $;
|
||||
var px = require('./modules/panoramix.js');
|
||||
var px = require('./modules/caravel.js');
|
||||
|
||||
require('bootstrap');
|
||||
|
||||
67
caravel/assets/javascripts/welcome.js
Normal 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');
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "panoramix",
|
||||
"name": "caravel",
|
||||
"version": "0.1.0",
|
||||
"description": "Any database to any visualization",
|
||||
"directories": {
|
||||
@@ -11,11 +11,11 @@
|
||||
"dev": "webpack -d --watch --colors",
|
||||
"prod": "webpack -p --colors",
|
||||
"lint": "npm run --silent lint:js",
|
||||
"lint:js": "eslint --ignore-path=.eslintignore --ext .js .; exit 0;"
|
||||
"lint:js": "eslint --ignore-path=.eslintignore --ext .js ."
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/mistercrunch/panoramix.git"
|
||||
"url": "git+https://github.com/airbnb/caravel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"big",
|
||||
@@ -31,9 +31,9 @@
|
||||
],
|
||||
"author": "Airbnb",
|
||||
"bugs": {
|
||||
"url": "https://github.com/mistercrunch/panoramix/issues"
|
||||
"url": "https://github.com/airbnb/caravel/issues"
|
||||
},
|
||||
"homepage": "https://github.com/mistercrunch/panoramix#readme",
|
||||
"homepage": "https://github.com/airbnb/caravel#readme",
|
||||
"dependencies": {
|
||||
"babel-loader": "^6.2.1",
|
||||
"babel-polyfill": "^6.3.14",
|
||||
@@ -43,21 +43,22 @@
|
||||
"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",
|
||||
"d3.layout.cloud": "^1.2.0",
|
||||
"datamaps": "^0.4.4",
|
||||
"datatables": "^1.10.9",
|
||||
"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",
|
||||
@@ -2,10 +2,25 @@ 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 {
|
||||
width:350px;
|
||||
position: absolute;
|
||||
@@ -15,15 +30,10 @@ input.form-control {
|
||||
margin-left: 365px;
|
||||
}
|
||||
|
||||
.modal-content.css {
|
||||
transition: opacity 0.5s ease;
|
||||
.favstar {
|
||||
margin-right: 10px;
|
||||
opacity: 0.5;
|
||||
border-color: black;
|
||||
}
|
||||
|
||||
.modal-content.css:hover {
|
||||
transition: opacity 0.5s ease;
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slice_description{
|
||||
@@ -39,7 +49,7 @@ input.form-control {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.padded{
|
||||
.padded {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
@@ -48,9 +58,6 @@ input.form-control {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.slice_container {
|
||||
//height: 100%;
|
||||
}
|
||||
.container-fluid {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -65,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 {
|
||||
@@ -78,7 +99,10 @@ form div {
|
||||
|
||||
.notbtn {
|
||||
cursor: default;
|
||||
box-shadow: none;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
@@ -92,25 +116,6 @@ 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;
|
||||
z-index: 888;
|
||||
@@ -118,10 +123,6 @@ legend.legend-style {
|
||||
.nvtooltip table td{
|
||||
font-size: 11px !important;
|
||||
}
|
||||
legend {
|
||||
width: auto;
|
||||
border-bottom: 0px;
|
||||
}
|
||||
.navbar {
|
||||
-webkit-box-shadow: 0px 3px 3px #AAA;
|
||||
-moz-box-shadow: 0px 3px 3px #AAA;
|
||||
@@ -131,42 +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 form div.form-control {
|
||||
margin-bottom: 5px !important;
|
||||
}
|
||||
@@ -179,7 +144,6 @@ legend {
|
||||
img.loading {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.dashboard a i {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -196,11 +160,9 @@ img.loading {
|
||||
.gridster li.widget{
|
||||
list-style-type: none;
|
||||
border-radius: 0;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 2px 1px 5px -2px #aaa;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
}
|
||||
.dashboard .gridster .dragging,
|
||||
@@ -224,22 +186,41 @@ img.loading {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.dashboard table.slice_header {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
21
caravel/assets/stylesheets/welcome.css
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -4,7 +4,7 @@ var d3 = window.d3 || require('d3');
|
||||
// CSS
|
||||
require('./big_number.css');
|
||||
|
||||
var px = require('../javascripts/modules/panoramix.js');
|
||||
var px = require('../javascripts/modules/caravel.js');
|
||||
|
||||
function bigNumberVis(slice) {
|
||||
var div = d3.select(slice.selector);
|
||||
@@ -24,13 +24,19 @@ function bigNumberVis(slice) {
|
||||
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 = data[data.length - 1][1];
|
||||
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) {
|
||||
@@ -96,6 +102,19 @@ function bigNumberVis(slice) {
|
||||
.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 %
|
||||
8
caravel/assets/visualizations/filter_box.css
Normal 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;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ var d3 = window.d3 || require('d3');
|
||||
|
||||
// CSS
|
||||
require('./filter_box.css');
|
||||
require('../javascripts/panoramix-select2.js');
|
||||
require('../javascripts/caravel-select2.js');
|
||||
|
||||
function filterBox(slice) {
|
||||
var filtersObj = {};
|
||||
@@ -56,7 +56,7 @@ function filterBox(slice) {
|
||||
})
|
||||
.on('change', fltChanged);
|
||||
}
|
||||
slice.done();
|
||||
slice.done(payload);
|
||||
|
||||
function select2Formatter(result, container /*, query, escapeMarkup*/) {
|
||||
var perc = Math.round((result.metric / maxes[result.filter]) * 100);
|
||||
@@ -1,6 +1,6 @@
|
||||
// JS
|
||||
var $ = window.$ || require('jquery');
|
||||
var px = window.px || require('../javascripts/modules/panoramix.js');
|
||||
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
|
||||
8
caravel/assets/visualizations/nvd3_vis.css
Normal file
@@ -0,0 +1,8 @@
|
||||
g.caravel path {
|
||||
stroke-dasharray: 5, 5;
|
||||
}
|
||||
|
||||
.nvtooltip tr.highlight td {
|
||||
font-weight: bold;
|
||||
font-size: 15px !important;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// JS
|
||||
var $ = window.$ || require('jquery');
|
||||
var d3 = window.d3 || require('d3');
|
||||
var px = window.px || require('../javascripts/modules/panoramix.js');
|
||||
var px = window.px || require('../javascripts/modules/caravel.js');
|
||||
var nv = require('nvd3');
|
||||
|
||||
// CSS
|
||||
@@ -27,7 +27,7 @@ function nvd3Vis(slice) {
|
||||
chart.lines2.xScale(d3.time.scale.utc());
|
||||
chart.x2Axis
|
||||
.showMaxMin(fd.x_axis_showminmax)
|
||||
.staggerLabels(true);
|
||||
.staggerLabels(false);
|
||||
} else {
|
||||
chart = nv.models.lineChart();
|
||||
}
|
||||
@@ -37,7 +37,7 @@ function nvd3Vis(slice) {
|
||||
chart.interpolate(fd.line_interpolation);
|
||||
chart.xAxis
|
||||
.showMaxMin(fd.x_axis_showminmax)
|
||||
.staggerLabels(true);
|
||||
.staggerLabels(false);
|
||||
break;
|
||||
|
||||
case 'bar':
|
||||
@@ -131,9 +131,6 @@ function nvd3Vis(slice) {
|
||||
var height = slice.height();
|
||||
height -= 15; // accounting for the staggered xAxis
|
||||
|
||||
if (chart.hasOwnProperty("x2Axis")) {
|
||||
height += 30;
|
||||
}
|
||||
chart.height(height);
|
||||
slice.container.css('height', height + 'px');
|
||||
|
||||
@@ -148,6 +145,22 @@ function nvd3Vis(slice) {
|
||||
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') {
|
||||
@@ -1,7 +1,7 @@
|
||||
var $ = window.$ = require('jquery');
|
||||
var jQuery = window.jQuery = $;
|
||||
|
||||
require('datatables');
|
||||
require('datatables.net-bs');
|
||||
require('./pivot_table.css');
|
||||
require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// CSS
|
||||
require('./sankey.css');
|
||||
// JS
|
||||
var px = window.px || require('../javascripts/modules/panoramix.js');
|
||||
var px = window.px || require('../javascripts/modules/caravel.js');
|
||||
var d3 = window.d3 || require('d3');
|
||||
d3.sankey = require('d3-sankey').sankey;
|
||||
|
||||
@@ -18,11 +18,9 @@ function sankeyVis(slice) {
|
||||
var width = slice.width() - margin.left - margin.right;
|
||||
var height = slice.height() - margin.top - margin.bottom;
|
||||
|
||||
var formatNumber = d3.format(",.0f"),
|
||||
format = function (d) {
|
||||
return formatNumber(d) + " TWh";
|
||||
};
|
||||
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)
|
||||
@@ -70,7 +68,7 @@ function sankeyVis(slice) {
|
||||
|
||||
link.append("title")
|
||||
.text(function (d) {
|
||||
return d.source.name + " → " + d.target.name + "\n" + format(d.value);
|
||||
return d.source.name + " → " + d.target.name + "\n" + formatNumber(d.value);
|
||||
});
|
||||
|
||||
var node = svg.append("g").selectAll(".node")
|
||||
@@ -103,7 +101,7 @@ function sankeyVis(slice) {
|
||||
})
|
||||
.append("title")
|
||||
.text(function (d) {
|
||||
return d.name + "\n" + format(d.value);
|
||||
return d.name + "\n" + formatNumber(d.value);
|
||||
});
|
||||
|
||||
node.append("text")
|
||||
49
caravel/assets/visualizations/sunburst.css
Normal 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;
|
||||
}
|
||||
375
caravel/assets/visualizations/sunburst.js
Normal 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;
|
||||