├── .gitignore ├── DataStore.js ├── Makefile ├── Utils.js ├── components ├── BarChart.js ├── ChartGlobalConfig.js ├── ChartSelector.js ├── Editor.js ├── EditorContainer.js ├── ExcelEditor.js ├── LineChart.js ├── MainContainer.js └── SettingsContainer.js ├── dist ├── CNAME ├── app-c0a285bc3f25568b99.css ├── browser-bundle-fedfac1b3f4ee5e7d388.js └── index.html ├── icons ├── bar-chart-icon.svg ├── line-chart-icon.svg ├── settings-icon.svg └── svg-icons-all.svg ├── index.html.tpl ├── index.js ├── package.json ├── sampleData.json ├── styles └── app.scss └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | node_modules 3 | .sass-cache 4 | styles/*.map 5 | styles/*.css 6 | *.bkp.json -------------------------------------------------------------------------------- /DataStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var lz = require('lz-string'); 5 | var Utils = require('./Utils'); 6 | var sampleJson = require('./sampleData.json'); 7 | 8 | 9 | function DataStore (key) { 10 | this.key = key; 11 | // initialize with sample data 12 | this.initialize(); 13 | this._events = {}; 14 | } 15 | 16 | DataStore.prototype.initialize = function () { 17 | if (this._initFromUrl()) return; 18 | if (localStore(this.key)) { 19 | this.data = localStore(this.key); 20 | } 21 | else { 22 | this._setSampleData(); 23 | } 24 | }; 25 | 26 | DataStore.prototype._setSampleData = function () { 27 | this.data = _.cloneDeep(sampleJson); 28 | }; 29 | 30 | DataStore.prototype._initFromUrl = function () { 31 | // if share url present, init data from url and return true 32 | // if not present, return false 33 | if (!window.location.hash) return false; 34 | var m = /#\/share\/(.+)/.exec(decodeURIComponent(window.location.hash)); 35 | if (m[1]) { 36 | try { 37 | var rawData = lz.decompressFromEncodedURIComponent(m[1]); 38 | this.data = JSON.parse(rawData); 39 | } 40 | catch (e) { return false; } 41 | return true; 42 | } 43 | else return false; 44 | }; 45 | 46 | DataStore.prototype.subscribe = function (eventType, cb) { 47 | var events = this._events[eventType] || (this._events[eventType] = []); 48 | events.push(cb); 49 | }; 50 | 51 | DataStore.prototype.inform = function (eventType) { 52 | var events; 53 | switch (eventType) { 54 | case 'reset': 55 | events = _.reduce(this._events, function (res, val) { 56 | return res.concat(val) 57 | }); 58 | break; 59 | 60 | case 'change': 61 | default: 62 | events = this._events[eventType]; 63 | break; 64 | } 65 | 66 | events.forEach(function (cb) { 67 | cb(); 68 | }); 69 | }; 70 | 71 | DataStore.prototype.getData = function () { 72 | return this.data; 73 | }, 74 | 75 | DataStore.prototype.update = function (newData) { 76 | this.data = newData; 77 | localStore(this.key, this.data); 78 | this.inform('change'); 79 | }; 80 | 81 | DataStore.prototype.resetData = function () { 82 | window.localStorage.removeItem(this.key); 83 | this._setSampleData(); 84 | this.inform('reset'); 85 | }; 86 | 87 | module.exports = DataStore; 88 | 89 | 90 | // Helper functions 91 | 92 | // save data to localstorage – localStore('my-data') 93 | // get data from localstorage – localStore('my-data', 'foobar') 94 | function localStore (namespace, data) { 95 | if (data) { 96 | return window.localStorage.setItem(namespace, JSON.stringify(data)); 97 | } 98 | 99 | var store = window.localStorage.getItem(namespace); 100 | return (store && JSON.parse(store)) || ""; 101 | } 102 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | css=$(shell ls dist/*.css) 2 | js=$(shell ls dist/*.js) 3 | css_file=$(notdir $(css)) 4 | js_file=$(notdir $(js)) 5 | html_file=dist/index.html 6 | 7 | DOMAIN=tinychart.co 8 | 9 | all: 10 | @echo "use 'make build' or 'make deploy'" 11 | 12 | prepare-dist: 13 | @mkdir -p dist 14 | @touch dist/CNAME 15 | @echo $(DOMAIN) > dist/CNAME 16 | 17 | dev-html: 18 | @cp index.html.tpl index.html 19 | @sed -i "" "s/{{MAINCSS}}/styles\/app.css/g" index.html 20 | @sed -i "" "s/{{MAINJS}}/browser-bundle.js/g" index.html 21 | 22 | build: prepare-dist 23 | @echo "building dist version..." 24 | @cp index.html.tpl $(html_file) 25 | @echo "inserting compiled css and js into index.html..." 26 | @sed -i "" "s/{{MAINCSS}}/$(subst /,\\/,${css_file})/g" $(html_file) 27 | @sed -i "" "s/{{MAINJS}}/$(subst /,\\/,${js_file})/g" $(html_file) 28 | @say build done 29 | 30 | deploy: build 31 | @echo "deploying dist/ to gh-pages remote branch..." 32 | git subtree push --prefix dist origin gh-pages 33 | @say deploy done 34 | 35 | .PHONY: prepare-dist build deploy 36 | -------------------------------------------------------------------------------- /Utils.js: -------------------------------------------------------------------------------- 1 | var lz = require('lz-string'); 2 | 3 | module.exports = { 4 | 5 | // SVG helper to generate icon code 6 | // usage: 7 | svgHelper: function (id) { 8 | return ""; 9 | }, 10 | 11 | // Chart colors for datasets 12 | colorschemes: [ 13 | { 14 | fillColor: "rgba(87,198,185,0.1)", 15 | strokeColor: "rgba(87,198,185,1)", 16 | pointColor: "rgba(87,198,185,1)", 17 | pointStrokeColor: "#fff", 18 | pointHighlightFill: "#fff", 19 | pointHighlightStroke: "rgba(122,178,234,1)", 20 | }, 21 | { 22 | fillColor: "rgba(163,142,243,0.1)", 23 | strokeColor: "rgba(163,142,243,1)", 24 | pointColor: "rgba(163,142,243,1)", 25 | pointStrokeColor: "#fff", 26 | pointHighlightFill: "#fff", 27 | pointHighlightStroke: "rgba(163,142,243,1)", 28 | }, 29 | { 30 | fillColor: "rgba(233,102,132,0.1)", 31 | strokeColor: "rgba(233,102,132,1)", 32 | pointColor: "rgba(233,102,132,1)", 33 | pointStrokeColor: "#fff", 34 | pointHighlightFill: "#fff", 35 | pointHighlightStroke: "rgba(122,178,234,1)", 36 | }, 37 | { 38 | fillColor: "rgba(122,178,234,0.1)", 39 | strokeColor: "rgba(122,178,234,1)", 40 | pointColor: "rgba(122,178,234,1)", 41 | pointStrokeColor: "#fff", 42 | pointHighlightFill: "#fff", 43 | pointHighlightStroke: "rgba(122,178,234,1)", 44 | }, 45 | { 46 | fillColor: "rgba(230,187,114,0.1)", 47 | strokeColor: "rgba(230,187,114,1)", 48 | pointColor: "rgba(230,187,114,1)", 49 | pointStrokeColor: "#fff", 50 | pointHighlightFill: "#fff", 51 | pointHighlightStroke: "rgba(230,187,114,1)", 52 | } 53 | ], 54 | 55 | // assign colorschemes to datasets 56 | assignColors: function (input, colors) { 57 | var output = _.cloneDeep(input); 58 | for (var i=0; i < output.datasets.length; i++) { 59 | for (var prop in colors[i]) { 60 | output.datasets[i][prop] = colors[i][prop]; 61 | } 62 | } 63 | return output; 64 | }, 65 | 66 | // set url from encoded data 67 | setUrl: function (data) { 68 | if (!data) { 69 | return window.history.replaceState({}, 'clear', '/'); 70 | } 71 | var compressed = lz.compressToEncodedURIComponent(JSON.stringify(data)); 72 | var url = '#/share/' + compressed; 73 | window.history.replaceState({}, 'share', url); 74 | return url; 75 | } 76 | 77 | }; -------------------------------------------------------------------------------- /components/BarChart.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | var Chart = require('chart.js/Chart'); 4 | var ChartConfig = require('./ChartGlobalConfig'); 5 | var Utils = require('../Utils'); 6 | 7 | 8 | var BarChart = React.createClass({ 9 | 10 | propTypes: { 11 | jsonData: React.PropTypes.object.isRequired, 12 | }, 13 | 14 | initializeChart: function (props) { 15 | var el = this.getDOMNode(); 16 | var ctx = el.getContext('2d'); 17 | Chart.defaults.global = ChartConfig; 18 | var data = Utils.assignColors(props.jsonData, Utils.colorschemes); 19 | el.width = 600; 20 | el.height = 480; 21 | this.chart = new Chart(ctx).Bar(data, {}); 22 | }, 23 | 24 | componentDidMount: function () { 25 | this.initializeChart(this.props); 26 | }, 27 | 28 | componentWillReceiveProps: function (props) { 29 | var chart = this.chart; 30 | chart.destroy(); 31 | this.initializeChart(props); 32 | }, 33 | 34 | componentWillUnmount: function () { 35 | var chart = this.chart; 36 | chart.destroy(); 37 | }, 38 | 39 | render: function () { 40 | var style = { 41 | marginTop: window.devicePixelRatio > 1 ? 100 : 50 42 | }; 43 | return ( 44 | 45 | ); 46 | } 47 | 48 | }); 49 | 50 | module.exports = BarChart; 51 | -------------------------------------------------------------------------------- /components/ChartGlobalConfig.js: -------------------------------------------------------------------------------- 1 | // Global configuration option for Chart.js 2 | // assign to Chart.defaults.global before initializing chart 3 | 4 | module.exports = { 5 | // Boolean - Whether to animate the chart 6 | animation: true, 7 | 8 | // Number - Number of animation steps 9 | animationSteps: 60, 10 | 11 | // String - Animation easing effect 12 | animationEasing: "easeOutQuart", 13 | 14 | // Boolean - If we should show the scale at all 15 | showScale: true, 16 | 17 | // Boolean - If we want to override with a hard coded scale 18 | scaleOverride: false, 19 | 20 | // ** Required if scaleOverride is true ** 21 | // Number - The number of steps in a hard coded scale 22 | scaleSteps: null, 23 | // Number - The value jump in the hard coded scale 24 | scaleStepWidth: null, 25 | // Number - The scale starting value 26 | scaleStartValue: null, 27 | 28 | // String - Colour of the scale line 29 | scaleLineColor: "rgba(0,0,0,.1)", 30 | 31 | // Number - Pixel width of the scale line 32 | scaleLineWidth: 1, 33 | 34 | // Boolean - Whether to show labels on the scale 35 | scaleShowLabels: true, 36 | 37 | // Interpolated JS string - can access value 38 | scaleLabel: "<%=value%>", 39 | 40 | // Boolean - Whether the scale should stick to integers, not floats even if drawing space is there 41 | scaleIntegersOnly: true, 42 | 43 | // Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value 44 | scaleBeginAtZero: false, 45 | 46 | // String - Scale label font declaration for the scale label 47 | scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", 48 | 49 | // Number - Scale label font size in pixels 50 | scaleFontSize: 12, 51 | 52 | // String - Scale label font weight style 53 | scaleFontStyle: "normal", 54 | 55 | // String - Scale label font colour 56 | scaleFontColor: "#666", 57 | 58 | // Boolean - whether or not the chart should be responsive and resize when the browser does. 59 | responsive: false, 60 | 61 | // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container 62 | maintainAspectRatio: true, 63 | 64 | // Boolean - Determines whether to draw tooltips on the canvas or not 65 | showTooltips: true, 66 | 67 | // Array - Array of string names to attach tooltip events 68 | tooltipEvents: ["mousemove", "touchstart", "touchmove"], 69 | 70 | // String - Tooltip background colour 71 | tooltipFillColor: "rgba(84,94,107,0.8)", 72 | 73 | // String - Tooltip label font declaration for the scale label 74 | tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", 75 | 76 | // Number - Tooltip label font size in pixels 77 | tooltipFontSize: 14, 78 | 79 | // String - Tooltip font weight style 80 | tooltipFontStyle: "normal", 81 | 82 | // String - Tooltip label font colour 83 | tooltipFontColor: "#fff", 84 | 85 | // String - Tooltip title font declaration for the scale label 86 | tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", 87 | 88 | // Number - Tooltip title font size in pixels 89 | tooltipTitleFontSize: 14, 90 | 91 | // String - Tooltip title font weight style 92 | tooltipTitleFontStyle: "bold", 93 | 94 | // String - Tooltip title font colour 95 | tooltipTitleFontColor: "#fff", 96 | 97 | // Number - pixel width of padding around tooltip text 98 | tooltipYPadding: 6, 99 | 100 | // Number - pixel width of padding around tooltip text 101 | tooltipXPadding: 6, 102 | 103 | // Number - Size of the caret on the tooltip 104 | tooltipCaretSize: 8, 105 | 106 | // Number - Pixel radius of the tooltip border 107 | tooltipCornerRadius: 6, 108 | 109 | // Number - Pixel offset from point x to tooltip edge 110 | tooltipXOffset: 10, 111 | 112 | // String - Template string for single tooltips 113 | tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>", 114 | 115 | // String - Template string for single tooltips 116 | multiTooltipTemplate: "<%= value %>", 117 | 118 | // Function - Will fire on animation progression. 119 | onAnimationProgress: function(){}, 120 | 121 | // Function - Will fire on animation completion. 122 | onAnimationComplete: function(){} 123 | }; -------------------------------------------------------------------------------- /components/ChartSelector.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | var Utils= require('../Utils'); 4 | 5 | var ChartSelector = React.createClass({ 6 | 7 | propTypes: { 8 | switchChartType: React.PropTypes.func.isRequired, 9 | types: React.PropTypes.array.isRequired 10 | }, 11 | 12 | onSelect: function (newVal) { 13 | this.props.switchChartType(newVal); 14 | }, 15 | 16 | render: function () { 17 | var cx = React.addons.classSet; 18 | var FlashMessageClasses = cx({ 19 | 'ChartSelector-button': true, 20 | 'is-selected': true, 21 | }); 22 | 23 | var options = this.props.types.map(function (type) { 24 | return ( 25 | 52 | 59 | 60 | Download Chart 61 | 62 | 63 | ); 64 | } 65 | 66 | }); 67 | 68 | module.exports = SettingsContainer; -------------------------------------------------------------------------------- /dist/CNAME: -------------------------------------------------------------------------------- 1 | tinychart.co 2 | -------------------------------------------------------------------------------- /dist/app-c0a285bc3f25568b99.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes rotate-wheel{100%{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes rotate-wheel{100%{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotate-wheel{100%{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes saved-inout{from{-webkit-transform:translateY(-3em);-moz-transform:translateY(-3em);-ms-transform:translateY(-3em);-o-transform:translateY(-3em);transform:translateY(-3em)}50%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-ms-transform:translateY(0);-o-transform:translateY(0);transform:translateY(0)}to{-webkit-transform:translateY(-3em);-moz-transform:translateY(-3em);-ms-transform:translateY(-3em);-o-transform:translateY(-3em);transform:translateY(-3em)}}@-moz-keyframes saved-inout{from{-webkit-transform:translateY(-3em);-moz-transform:translateY(-3em);-ms-transform:translateY(-3em);-o-transform:translateY(-3em);transform:translateY(-3em)}50%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-ms-transform:translateY(0);-o-transform:translateY(0);transform:translateY(0)}to{-webkit-transform:translateY(-3em);-moz-transform:translateY(-3em);-ms-transform:translateY(-3em);-o-transform:translateY(-3em);transform:translateY(-3em)}}@keyframes saved-inout{from{-webkit-transform:translateY(-3em);-moz-transform:translateY(-3em);-ms-transform:translateY(-3em);-o-transform:translateY(-3em);transform:translateY(-3em)}50%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-ms-transform:translateY(0);-o-transform:translateY(0);transform:translateY(0)}to{-webkit-transform:translateY(-3em);-moz-transform:translateY(-3em);-ms-transform:translateY(-3em);-o-transform:translateY(-3em);transform:translateY(-3em)}}@-webkit-keyframes editor-in{to{opacity:1;-webkit-transform:translateX(0);-moz-transform:translateX(0);-ms-transform:translateX(0);-o-transform:translateX(0);transform:translateX(0)}}@-moz-keyframes editor-in{to{opacity:1;-webkit-transform:translateX(0);-moz-transform:translateX(0);-ms-transform:translateX(0);-o-transform:translateX(0);transform:translateX(0)}}@keyframes editor-in{to{opacity:1;-webkit-transform:translateX(0);-moz-transform:translateX(0);-ms-transform:translateX(0);-o-transform:translateX(0);transform:translateX(0)}}html,body{width:100%;height:100%;margin:0;padding:0;font-family:"Avenir","Arial",sans-serif}h1,h2,h3,h4,h5{font-weight:300}.u-svgIcon{width:20px;height:20px;cursor:pointer;fill:#75808F;stroke:#75808F}.OuterContainer{height:100%}.App{display:-webkit-box;display:-moz-box;display:box;display:-webkit-flex;display:-moz-flex;display:-ms-flexbox;display:flex;height:100%;overflow:hidden}.App:before{content:'';position:fixed;background:black;top:0;left:0;width:100%;height:100%;z-index:3;visibility:hidden;opacity:0;-webkit-transition:all 300ms ease;-moz-transition:all 300ms ease;transition:all 300ms ease}.App.is-dimmed:before{visibility:visible;opacity:0.3}.MainContainer{-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;-webkit-flex:1;-moz-flex:1;-ms-flex:1;flex:1;z-index:2;background-color:#E0E6EC;color:#E0E6EC}.MainContainer-header{position:relative;padding:1em 1em 1em 2em;overflow:hidden}.MainContainer-footer{position:absolute;bottom:1em;margin-left:2em;color:#3B424A;cursor:pointer}.MainContainer-brand{font-size:1em;font-weight:800;margin-right:1em}.MainContainer-contact{color:#3B424A;text-decoration:none;font-size:0.9em;font-weight:100;opacity:0.7;-webkit-transition:200ms opacity ease;-moz-transition:200ms opacity ease;transition:200ms opacity ease}.MainContainer-contact:hover{opacity:1}.SettingsButton{float:right;cursor:pointer}.SettingsButton>svg{width:25px;height:25px;stroke:#75808F;fill:transparent;-webkit-transition:200ms stroke ease;-moz-transition:200ms stroke ease;transition:200ms stroke ease;pointer-events:none;-webkit-animation:rotate-wheel 3s linear infinite;-moz-animation:rotate-wheel 3s linear infinite;animation:rotate-wheel 3s linear infinite;-webkit-animation-play-state:paused;-moz-animation-play-state:paused;animation-play-state:paused}.SettingsButton:hover>svg{stroke:#545E6B;-webkit-animation-play-state:running;-moz-animation-play-state:running;animation-play-state:running}.SettingsButton:active>svg{stroke:#7AB2EA}.ChartSelector{overflow:hidden;width:100%}.ChartSelector-button{width:50%;background-image:-webkit-linear-gradient(-298deg, #D9DEE5 0%,#E2E7EE 100%);background-image:linear-gradient(28deg, #D9DEE5 0%,#E2E7EE 100%);border:1px solid #B8C6D3;box-shadow:inset 0px 1px 0px 0px rgba(255,255,255,0.5);padding:1em;outline:none;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ChartSelector-button:first-child{border-radius:4px 0px 0px 4px}.ChartSelector-button:last-child{border-radius:0px 4px 4px 0px}.ChartSelector-button:hover{background-image:-webkit-linear-gradient(-298deg, #ECEEF1 0%,#DDE3EA 100%);background-image:linear-gradient(28deg, #ECEEF1 0%,#DDE3EA 100%);box-shadow:inset 0px 1px 0px 0px rgba(255,255,255,0.3)}.ChartSelector-button:hover>svg{fill:#545E6B;stroke:#545E6B}.ChartSelector-button:active{background-image:-webkit-linear-gradient(-298deg, #E4E9EE 0%,#EAEEF2 100%);background-image:linear-gradient(28deg, #E4E9EE 0%,#EAEEF2 100%);box-shadow:inset 0px 1px 0px 0px rgba(255,255,255,0.3)}.ChartSelector-button:active>svg{fill:#4996E3;stroke:#4996E3}.EditorContainer{height:100%;width:30%;min-width:500px;background-color:#262a2e;color:#d9dfe2}.EditorNav{height:10%;min-height:60px;display:-webkit-box;display:-moz-box;display:box;display:-webkit-flex;display:-moz-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-moz-box-orient:horizontal;box-orient:horizontal;-webkit-flex-direction:row;-moz-flex-direction:row;flex-direction:row;-ms-flex-direction:row;-webkit-box-pack:center;-moz-box-pack:center;box-pack:center;-webkit-justify-content:center;-moz-justify-content:center;-ms-justify-content:center;-o-justify-content:center;justify-content:center;-ms-flex-pack:center}.EditorNav-list{list-style:none;cursor:pointer;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.EditorNav-elem{display:inline-block;padding:4px 8px;font-size:0.8em;color:#96A6AD;background:rgba(59,66,74,0.75);background-image:-webkit-linear-gradient(-90deg, rgba(255,255,255,0.05) 0%,rgba(255,255,255,0) 100%);background-image:linear-gradient(-180deg, rgba(255,255,255,0.05) 0%,rgba(255,255,255,0) 100%);border:1px solid rgba(26,29,33,0.75);box-shadow:inset 0px 1px 0px 0px rgba(255,255,255,0.02)}.EditorNav-elem:first-child{border-radius:4px 0 0 4px}.EditorNav-elem:last-child{border-radius:0 4px 4px 0}.EditorNav-elem:hover{color:#E2E7EB;background:#3B424A;background-image:-webkit-linear-gradient(-90deg, rgba(255,255,255,0.05) 0%,rgba(255,255,255,0) 100%);background-image:linear-gradient(-180deg, rgba(255,255,255,0.05) 0%,rgba(255,255,255,0) 100%);border:1px solid rgba(26,29,33,0.75);box-shadow:inset 0px 1px 0px 0px rgba(255,255,255,0.02)}.EditorNav-elem:active{color:#6BB6C4;background:rgba(58,66,73,0.75);background-image:-webkit-linear-gradient(-90deg, rgba(0,0,0,0.1) 0%,rgba(255,255,255,0) 100%);background-image:linear-gradient(-180deg, rgba(0,0,0,0.1) 0%,rgba(255,255,255,0) 100%);border:1px solid rgba(26,29,33,0.75);box-shadow:inset 0px -1px 0px 0px rgba(255,255,255,0.02)}.EditorNav-elem.is-selected{color:#191C1F;background-image:-webkit-linear-gradient(-115deg, #6BB6C4 0%,#6B9AD3 100%);background-image:linear-gradient(-155deg, #6BB6C4 0%,#6B9AD3 100%);border:1px solid rgba(26,29,33,0.75);box-shadow:inset 0px 1px 0px 0px rgba(255,255,255,0.12)}.JsonEditor{width:100%;height:90%;overflow:auto;opacity:0;z-index:1;-webkit-transform:translateX(-1em);-moz-transform:translateX(-1em);-ms-transform:translateX(-1em);-o-transform:translateX(-1em);transform:translateX(-1em);-webkit-animation:editor-in 600ms cubic-bezier(0.215, 0.61, 0.355, 1) forwards;-moz-animation:editor-in 600ms cubic-bezier(0.215, 0.61, 0.355, 1) forwards;animation:editor-in 600ms cubic-bezier(0.215, 0.61, 0.355, 1) forwards}#editor.ace_editor{background-color:#262a2e}#editor .ace_gutter{background-color:#262a2e}#editor .ace_variable{color:#6BB6C4}#editor .ace_string{color:#8091C6}#editor .ace_constant.ace_numeric{color:#E2E7EB}#editor .ace_gutter{color:#3B424A}.Chart{display:block;margin:0 auto;margin-top:50px}.SettingsContainer{position:fixed;display:block;width:250px;height:306px;top:2.9em;right:1.7em;border-radius:4px;background:#E0E6EC;opacity:0;z-index:3;color:#75808F;box-shadow:0 8px 13px rgba(0,0,0,0.36),0 0 0 1px rgba(0,0,0,0.06);-webkit-transition:300ms all cubic-bezier(0.34, 1.61, 0.7, 1);-moz-transition:300ms all cubic-bezier(0.34, 1.61, 0.7, 1);transition:300ms all cubic-bezier(0.34, 1.61, 0.7, 1);-webkit-transform:scale(0.7) translateY(-25%) translateX(25%);-moz-transform:scale(0.7) translateY(-25%) translateX(25%);-ms-transform:scale(0.7) translateY(-25%) translateX(25%);-o-transform:scale(0.7) translateY(-25%) translateX(25%);transform:scale(0.7) translateY(-25%) translateX(25%)}.SettingsContainer.is-open{opacity:1;-webkit-transform:scale(1) translateY(0);-moz-transform:scale(1) translateY(0);-ms-transform:scale(1) translateY(0);-o-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}.SettingsContainer.is-sharing{height:346px}.SettingsContainer-content{display:-webkit-box;display:-moz-box;display:box;display:-webkit-flex;display:-moz-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-moz-box-orient:vertical;box-orient:vertical;-webkit-flex-direction:column;-moz-flex-direction:column;flex-direction:column;-ms-flex-direction:column;-webkit-box-pack:justify;-moz-box-pack:justify;box-pack:justify;-webkit-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between;-ms-flex-pack:justify;-webkit-box-align:center;-moz-box-align:center;box-align:center;-webkit-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;-ms-flex-align:center;position:relative;padding:2em 2em}.SettingsContainer-button{display:block;position:relative;width:100%;height:46px;line-height:46px;margin-top:1.2em;padding:0;outline:none;cursor:pointer;border-radius:4px;font-size:1em;font-family:"Avenir","Arial",sans-serif;text-decoration:none;text-align:center;box-sizing:border-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.SettingsContainer-button--default{background-image:-webkit-linear-gradient(-298deg, #D9DEE5 0%,#E2E7EE 100%);background-image:linear-gradient(28deg, #D9DEE5 0%,#E2E7EE 100%);border:1px solid #B8C6D3;box-shadow:inset 0px 1px 0px 0px rgba(255,255,255,0.5);color:#75808F}.SettingsContainer-button--default:hover{background-image:-webkit-linear-gradient(-298deg, #ECEEF1 0%,#DDE3EA 100%);background-image:linear-gradient(28deg, #ECEEF1 0%,#DDE3EA 100%);box-shadow:inset 0px 1px 0px 0px rgba(255,255,255,0.3);color:#545E6B}.SettingsContainer-button--default:active{background-image:-webkit-linear-gradient(-298deg, #E4E9EE 0%,#EAEEF2 100%);background-image:linear-gradient(28deg, #E4E9EE 0%,#EAEEF2 100%);box-shadow:inset 0px 1px 0px 0px rgba(255,255,255,0.3);color:#4996E3}.SettingsContainer-button--blue{background-image:-webkit-linear-gradient(-115deg, #58ACD9 0%,#7BA8FF 100%);background-image:linear-gradient(-155deg, #58ACD9 0%,#7BA8FF 100%);border:1px solid #719BEC;box-shadow:inset 0px 1px 0px 0px rgba(255,255,255,0.15);color:#FFFFFF}.SettingsContainer-button--blue:hover{background-image:-webkit-linear-gradient(-115deg, #58ACD9 0%,#7BA8FF 50%);background-image:linear-gradient(-155deg, #58ACD9 0%,#7BA8FF 50%);box-shadow:inset 0px 1px 0px 0px rgba(255,255,255,0.12)}.SettingsContainer-button--blue:active{background-image:-webkit-linear-gradient(-294deg, #58ACD9 0%,#7BA8FF 100%);background-image:linear-gradient(24deg, #58ACD9 0%,#7BA8FF 100%);box-shadow:inset 0px -1px 0px 0px rgba(255,255,255,0.12)}.SettingsContainer-input{box-sizing:border-box;position:relative;width:100%;height:0;opacity:0;margin-top:-3px;padding:3px 0.4em 0 0.4em;outline:none;cursor:text;font-size:0.8em;color:#75808F;font-family:"Avenir","Arial",sans-serif;background:#FFF;border:1px solid #B8C6D3;border-radius:0 0 4px 4px;-webkit-transition:400ms all cubic-bezier(0.34, 1.61, 0.7, 1);-moz-transition:400ms all cubic-bezier(0.34, 1.61, 0.7, 1);transition:400ms all cubic-bezier(0.34, 1.61, 0.7, 1)}.SettingsContainer-input.is-sharing{height:40px;opacity:1}#share>span,#share::after{display:block;-webkit-transition:-webkit-transform 0.3s,opacity 0.3s;-moz-transition:-moz-transform 0.3s,opacity 0.3s;transition:transform 0.3s,opacity 0.3s;-webkit-transition-timing-function:cubic-bezier(0.2, 1, 0.3, 1);-moz-transition-timing-function:cubic-bezier(0.2, 1, 0.3, 1);transition-timing-function:cubic-bezier(0.2, 1, 0.3, 1)}#share::after{content:attr(data-text);color:#57C6B9;position:absolute;top:0;width:100%;opacity:0;-webkit-transform:translateY(25%);-moz-transform:translateY(25%);-ms-transform:translateY(25%);-o-transform:translateY(25%);transform:translateY(25%)}#share.is-sharing>span{opacity:0;-webkit-transform:translateY(-25%);-moz-transform:translateY(-25%);-ms-transform:translateY(-25%);-o-transform:translateY(-25%);transform:translateY(-25%)}#share.is-sharing::after{opacity:1;-webkit-transform:translateY(0);-moz-transform:translateY(0);-ms-transform:translateY(0);-o-transform:translateY(0);transform:translateY(0)}.FlashMessage{color:#545E6B;float:left;-webkit-transform:translateY(-3em);-moz-transform:translateY(-3em);-ms-transform:translateY(-3em);-o-transform:translateY(-3em);transform:translateY(-3em)}.FlashMessage.is-visible{-webkit-animation:saved-inout 4s cubic-bezier(0.86, 0, 0.07, 1) 0.2s forwards;-moz-animation:saved-inout 4s cubic-bezier(0.86, 0, 0.07, 1) 0.2s forwards;animation:saved-inout 4s cubic-bezier(0.86, 0, 0.07, 1) 0.2s forwards}.ExcelEditor{width:100%;height:90%;display:block;overflow:auto;z-index:1;-webkit-transform:translateX(1em);-moz-transform:translateX(1em);-ms-transform:translateX(1em);-o-transform:translateX(1em);transform:translateX(1em);opacity:0;-webkit-animation:editor-in 600ms cubic-bezier(0.215, 0.61, 0.355, 1) forwards;-moz-animation:editor-in 600ms cubic-bezier(0.215, 0.61, 0.355, 1) forwards;animation:editor-in 600ms cubic-bezier(0.215, 0.61, 0.355, 1) forwards}.ExcelEditor-controls{text-align:center;margin-top:3em;margin-bottom:2em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}.ExcelEditor-controls:hover .ExcelEditor-control{color:#E2E7EB}.ExcelEditor-control{display:inline-block;color:#96A6AD;opacity:0.6;margin:0 1em;-webkit-transition:300ms all ease;-moz-transition:300ms all ease;transition:300ms all ease}.ExcelEditor-control a{font-size:1.1em;font-weight:600;cursor:pointer}.ExcelEditor-control a:first-child{margin-right:0.4em}.ExcelEditor-control a:last-child{margin-left:0.4em}.ExcelEditor-control a:active{color:#6BB6C4}.ExcelEditor-control span{font-size:0.8em}.ExcelEditor-wrapper{display:block;text-align:center}.ExcelEditor-table{margin:0 auto;border-spacing:0.5em;color:#96A6AD}.ExcelEditor-table thead th{font-weight:300;font-size:0.7em}.ExcelEditor-header{background:#1A1D21;min-width:3.5em;border:2px solid transparent;border-radius:4px;outline:none;padding:0.2em 0.7em;font-weight:300;line-height:1.4em;-webkit-transition:200ms all ease;-moz-transition:200ms all ease;transition:200ms all ease}.ExcelEditor-header:hover{color:#E2E7EB;border:2px solid #444f58}.ExcelEditor-header:focus{color:#6BB6C4;border:2px solid #6BB6C4}.ExcelEditor-cell{min-width:3em;text-align:center;background:#1A1D21;border:2px solid transparent;border-radius:4px;outline:none;padding:0.2em 0.7em;font-weight:300;line-height:1.4em;-webkit-transition:200ms all ease;-moz-transition:200ms all ease;transition:200ms all ease}.ExcelEditor-cell:hover{color:#E2E7EB;border:2px solid #444f58}.ExcelEditor-cell:focus{color:#6BB6C4;border:2px solid #6BB6C4} 2 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tinychart – simple, ready-to-go charts 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 24 | 25 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /icons/bar-chart-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | bar-chart-icon 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /icons/line-chart-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Untitled 2 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /icons/settings-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Oval 1 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /icons/svg-icons-all.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /index.html.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tinychart – simple, ready-to-go charts 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 24 | 25 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react/addons'); 3 | var EditorContainer = require('./components/EditorContainer'); 4 | var MainContainer = require('./components/MainContainer'); 5 | var SettingsContainer = require('./components/SettingsContainer'); 6 | var DataStore = require('./DataStore'); 7 | var Utils = require('./Utils'); 8 | var CHART_TYPES = ['line', 'bar']; 9 | 10 | var icons = require('!!raw!./icons/svg-icons-all.svg'); 11 | 12 | var App = React.createClass({ 13 | 14 | getInitialState: function () { 15 | return { 16 | settingsIsOpen: false, 17 | settingsIsSharing: false, 18 | currentChartType: 'line', 19 | url: '' 20 | }; 21 | }, 22 | 23 | newData: function (data) { 24 | this.props.store.update(data); 25 | this.setState({ settingsIsSharing: false, url: ''}); 26 | Utils.setUrl(''); 27 | ga('send', 'event', 'user', 'update'); 28 | }, 29 | 30 | resetData: function () { 31 | this.props.store.resetData(); 32 | this.setState({ settingsIsSharing: false, url: ''}); 33 | Utils.setUrl(''); 34 | ga('send', 'event', 'user', 'reset'); 35 | }, 36 | 37 | shareUrl: function () { 38 | var url = Utils.setUrl(this.props.store.getData()); 39 | var fullUrl = window.location.origin + '/' + url; 40 | this.setState({ settingsIsSharing: true, url: fullUrl }); 41 | ga('send', 'event', 'user', 'share', url); 42 | }, 43 | 44 | openSettings: function () { 45 | this.setState({ settingsIsOpen: true }); 46 | }, 47 | 48 | switchChartType: function (toType) { 49 | this.setState({ currentChartType: toType }); 50 | }, 51 | 52 | closeSettings: function (evt) { 53 | if (this.state.settingsIsOpen) this.setState({ settingsIsOpen: false }); 54 | }, 55 | 56 | render: function () { 57 | var cx = React.addons.classSet; 58 | var AppClasses = cx({ 59 | 'App': true, 60 | 'is-dimmed': this.state.settingsIsOpen 61 | }); 62 | 63 | return ( 64 |
65 | 66 |
67 | 68 | 69 |
70 | 77 |
78 | ); 79 | } 80 | 81 | }); 82 | 83 | var store = new DataStore('chartpad'); 84 | 85 | React.render(, document.body); 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tinychart", 3 | "version": "0.6.0", 4 | "description": "A simple tool to create quick exportable charts", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "npm run watch-js & npm run watch-css", 8 | "clean": "rm -f browser-bundle.js index.html", 9 | "prewatch": "make dev-html", 10 | "watch": "npm run watch-js & npm run watch-css", 11 | "watch-js": "webpack --progress --colors --watch", 12 | "watch-css": "sass --watch styles/", 13 | "prebuild": "rm -rf dist/ && mkdir -p dist/", 14 | "build": "npm run build-css & npm run build-js", 15 | "build-css": "sass --style compressed --sourcemap=none styles/app.scss | hashmark -l 18 dist/app-{hash}.css --silent", 16 | "build-js": "NODE_ENV=production webpack -p --config webpack.config.js", 17 | "postbuild": "make build" 18 | }, 19 | "author": "Victor Delgado", 20 | "license": "MIT", 21 | "dependencies": { 22 | "brace": "^0.4.0", 23 | "chart.js": "^1.0.1-beta.2", 24 | "lodash": "^2.4.1", 25 | "lz-string": "^1.4.1", 26 | "react": "^0.12.1" 27 | }, 28 | "devDependencies": { 29 | "hashmark": "^2.0.1", 30 | "json-loader": "^0.5.1", 31 | "jsx-loader": "^0.12.2", 32 | "node-bourbon": "^1.2.3", 33 | "raw-loader": "^0.5.1", 34 | "webpack": "^1.4.13", 35 | "webpack-dev-server": "^1.7.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sampleData.json: -------------------------------------------------------------------------------- 1 | { 2 | "labels": ["January", "February", "March", "April", "May", "June", "July"], 3 | "datasets": [ 4 | { 5 | "data": [65, 59, 80, 81, 56, 55, 40] 6 | }, 7 | { 8 | "data": [28, 48, 40, 19, 86, 27, 90] 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /styles/app.scss: -------------------------------------------------------------------------------- 1 | @import '../node_modules/node-bourbon/assets/stylesheets/bourbon'; 2 | 3 | /* variables */ 4 | $font: 'Avenir', 'Arial', sans-serif; 5 | $radius: 4px; 6 | 7 | /* colors - light side */ 8 | $light-bg-grey: #E0E6EC; 9 | $dark-bg-grey: #262a2e; 10 | $purple: #8091C6; 11 | $green: #6BB6C4; 12 | $white: #FFF; 13 | $light-grey4: #E2E7EB; 14 | $light-grey2: #3B424A; 15 | $darkest-grey: #545E6B; 16 | $icon-dark-grey: #75808F; 17 | $icon-hover: #545E6B; 18 | $icon-active: #7AB2EA; 19 | $positive-green: #57C6B9; 20 | 21 | /* colors - dark side*/ 22 | $black: #1A1D21; 23 | $table-text: #96A6AD; 24 | $table-text-hover: #E2E7EB; 25 | $table-text-active: #6BB6C4; 26 | 27 | /* animations */ 28 | @include keyframes(rotate-wheel) { 100% { @include transform(rotate(360deg)); } } 29 | @include keyframes(saved-inout) { 30 | from { @include transform(translateY(-3em)); } 31 | 50% { @include transform(translateY(0)); } 32 | to { @include transform(translateY(-3em)); } 33 | } 34 | @include keyframes(editor-in) { 35 | to { opacity: 1; @include transform(translateX(0)); } 36 | } 37 | 38 | html, body { 39 | width: 100%; 40 | height: 100%; 41 | 42 | margin: 0; 43 | padding: 0; 44 | font-family: $font; 45 | } 46 | h1, h2, h3, h4, h5 { 47 | font-weight: 300; 48 | } 49 | 50 | .u-svgIcon { 51 | width: 20px; 52 | height: 20px; 53 | cursor: pointer; 54 | fill: #75808F; 55 | stroke: #75808F; 56 | } 57 | .OuterContainer { 58 | height: 100%; 59 | } 60 | .App { 61 | @include display(flex); 62 | height: 100%; 63 | overflow: hidden; 64 | } 65 | .App:before { 66 | content: ''; 67 | position: fixed; 68 | background: black; 69 | top:0; 70 | left:0; 71 | width:100%; 72 | height:100%; 73 | z-index: 3; 74 | visibility: hidden; 75 | opacity: 0; 76 | @include transition(all 300ms ease); 77 | } 78 | .App.is-dimmed:before { 79 | visibility: visible; 80 | opacity: 0.3; 81 | } 82 | 83 | .MainContainer { 84 | @include flex(1); 85 | z-index: 2; 86 | background-color: $light-bg-grey; 87 | color: #E0E6EC; 88 | } 89 | .MainContainer-header { 90 | position: relative; 91 | padding: 1em 1em 1em 2em; 92 | overflow: hidden; 93 | } 94 | .MainContainer-footer { 95 | position: absolute; 96 | bottom: 1em; 97 | margin-left: 2em; 98 | color: $light-grey2; 99 | cursor: pointer; 100 | } 101 | .MainContainer-brand { 102 | font-size: 1em; 103 | font-weight: 800; 104 | margin-right: 1em; 105 | } 106 | .MainContainer-contact { 107 | color: $light-grey2; 108 | text-decoration: none; 109 | font-size: 0.9em; 110 | font-weight: 100; 111 | opacity: 0.7; 112 | @include transition(200ms opacity ease); 113 | 114 | &:hover { 115 | opacity: 1; 116 | } 117 | } 118 | 119 | .SettingsButton { 120 | float: right; 121 | cursor: pointer; 122 | 123 | & > svg { 124 | width: 25px; 125 | height: 25px; 126 | stroke: $icon-dark-grey; 127 | fill: transparent; 128 | @include transition(200ms stroke ease); 129 | pointer-events: none; 130 | 131 | @include animation(rotate-wheel 3s linear infinite); 132 | @include animation-play-state(paused); 133 | 134 | } 135 | &:hover > svg { 136 | stroke: $icon-hover; 137 | @include animation-play-state(running); 138 | } 139 | 140 | &:active > svg { 141 | stroke: $icon-active; 142 | } 143 | } 144 | .ChartSelector { 145 | overflow: hidden; 146 | width: 100%; 147 | } 148 | .ChartSelector-button { 149 | width: 50%; 150 | @include background-image(linear-gradient(28deg, #D9DEE5 0%, #E2E7EE 100%)); 151 | border: 1px solid #B8C6D3; 152 | box-shadow: inset 0px 1px 0px 0px rgba(255,255,255,0.50); 153 | padding: 1em; 154 | outline: none; 155 | cursor: pointer; 156 | @include user-select(none); 157 | 158 | &:first-child { 159 | border-radius: $radius 0px 0px $radius; 160 | } 161 | &:last-child { 162 | border-radius: 0px $radius $radius 0px; 163 | } 164 | &:hover { 165 | @include background-image(linear-gradient(28deg, #ECEEF1 0%, #DDE3EA 100%)); 166 | box-shadow: inset 0px 1px 0px 0px rgba(255,255,255,0.30); 167 | & > svg{ 168 | fill: #545E6B; 169 | stroke: #545E6B; 170 | } 171 | } 172 | &:active { 173 | @include background-image(linear-gradient(28deg, #E4E9EE 0%, #EAEEF2 100%)); 174 | box-shadow: inset 0px 1px 0px 0px rgba(255,255,255,0.30); 175 | & > svg{ 176 | fill: #4996E3; 177 | stroke: #4996E3; 178 | } 179 | } 180 | } 181 | 182 | .EditorContainer { 183 | height: 100%; 184 | width: 30%; 185 | min-width: 500px; 186 | 187 | background-color: $dark-bg-grey; 188 | color: #d9dfe2; 189 | } 190 | 191 | .EditorNav { 192 | height: 10%; 193 | min-height: 60px; 194 | @include display(flex); 195 | @include flex-direction(row); 196 | @include justify-content(center); 197 | } 198 | 199 | .EditorNav-list { 200 | list-style: none; 201 | cursor: pointer; 202 | padding: 0; 203 | @include user-select(none); 204 | } 205 | 206 | .EditorNav-elem { 207 | display: inline-block; 208 | padding: 4px 8px; 209 | font-size: 0.8em; 210 | color: $table-text; 211 | background: rgba(59,66,74,0.75); 212 | @include background-image(linear-gradient(-180deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0.00) 100%)); 213 | border: 1px solid rgba(26,29,33,0.75); 214 | box-shadow: inset 0px 1px 0px 0px rgba(255,255,255,0.02); 215 | 216 | &:first-child { 217 | border-radius: $radius 0 0 $radius; 218 | } 219 | &:last-child { 220 | border-radius: 0 $radius $radius 0; 221 | } 222 | 223 | &:hover { 224 | color: #E2E7EB; 225 | background: #3B424A; 226 | @include background-image(linear-gradient(-180deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0.00) 100%)); 227 | border: 1px solid rgba(26,29,33,0.75); 228 | box-shadow: inset 0px 1px 0px 0px rgba(255,255,255,0.02); 229 | } 230 | &:active { 231 | color: $green; 232 | background: rgba(58,66,73,0.75); 233 | @include background-image(linear-gradient(-180deg, rgba(0,0,0,0.10) 0%, rgba(255,255,255,0.00) 100%)); 234 | border: 1px solid rgba(26,29,33,0.75); 235 | box-shadow: inset 0px -1px 0px 0px rgba(255,255,255,0.02); 236 | } 237 | } 238 | 239 | .EditorNav-elem.is-selected { 240 | color: #191C1F; 241 | @include background-image(linear-gradient(-155deg, #6BB6C4 0%, #6B9AD3 100%)); 242 | border: 1px solid rgba(26,29,33,0.75); 243 | box-shadow: inset 0px 1px 0px 0px rgba(255,255,255,0.12); 244 | } 245 | 246 | .JsonEditor { 247 | width: 100%; 248 | height: 90%; 249 | overflow: auto; 250 | opacity: 0; 251 | z-index: 1; 252 | @include transform(translateX(-1em)); 253 | @include animation(editor-in 600ms cubic-bezier(0.215, 0.61, 0.355, 1) forwards); 254 | } 255 | 256 | /* custom theme for ace editor */ 257 | #editor.ace_editor { 258 | background-color: $dark-bg-grey; 259 | } 260 | #editor .ace_gutter { 261 | background-color: $dark-bg-grey; 262 | } 263 | #editor .ace_variable { 264 | color: $green; 265 | } 266 | #editor .ace_string { 267 | color: $purple; 268 | } 269 | #editor .ace_constant.ace_numeric { 270 | color: $light-grey4; 271 | } 272 | #editor .ace_gutter { 273 | color: $light-grey2; 274 | } 275 | 276 | .Chart { 277 | display: block; 278 | margin: 0 auto; 279 | margin-top: 50px; 280 | } 281 | 282 | .SettingsContainer { 283 | position: fixed; 284 | display: block; 285 | width: 250px; 286 | height: 306px; 287 | top: 2.9em; 288 | right: 1.7em; 289 | border-radius: $radius; 290 | background: $light-bg-grey; 291 | opacity: 0; 292 | z-index: 3; 293 | color: $icon-dark-grey; 294 | box-shadow: 0 8px 13px rgba(0,0,0,0.36),0 0 0 1px rgba(0,0,0,0.06); 295 | @include transition(300ms all cubic-bezier(0.34,1.61,0.7,1)); 296 | @include transform(scale(0.7) translateY(-25%) translateX(25%)); 297 | } 298 | .SettingsContainer.is-open { 299 | opacity: 1; 300 | @include transform(scale(1) translateY(0)); 301 | } 302 | .SettingsContainer.is-sharing { 303 | height: 346px; 304 | } 305 | .SettingsContainer-content { 306 | @include display(flex); 307 | @include flex-direction(column); 308 | @include justify-content(space-between); 309 | @include align-items(center); 310 | position: relative; 311 | padding: 2em 2em; 312 | } 313 | .SettingsContainer-button { 314 | display: block; 315 | position: relative; 316 | width: 100%; 317 | height: 46px; 318 | line-height: 46px; 319 | margin-top: 1.2em; 320 | padding: 0; 321 | outline: none; 322 | cursor: pointer; 323 | border-radius: $radius; 324 | font-size: 1em; 325 | font-family: $font; 326 | text-decoration: none; 327 | text-align: center; 328 | box-sizing: border-box; 329 | @include user-select(none); 330 | } 331 | .SettingsContainer-button--default { 332 | @include background-image(linear-gradient(28deg, #D9DEE5 0%, #E2E7EE 100%)); 333 | border: 1px solid #B8C6D3; 334 | box-shadow: inset 0px 1px 0px 0px rgba(255,255,255,0.50); 335 | color: $icon-dark-grey; 336 | &:hover { 337 | @include background-image(linear-gradient(28deg, #ECEEF1 0%, #DDE3EA 100%)); 338 | box-shadow: inset 0px 1px 0px 0px rgba(255,255,255,0.30); 339 | color: #545E6B; 340 | } 341 | &:active { 342 | @include background-image(linear-gradient(28deg, #E4E9EE 0%, #EAEEF2 100%)); 343 | box-shadow: inset 0px 1px 0px 0px rgba(255,255,255,0.30); 344 | color: #4996E3; 345 | } 346 | } 347 | .SettingsContainer-button--blue { 348 | @include background-image(linear-gradient(-155deg, #58ACD9 0%, #7BA8FF 100%)); 349 | border: 1px solid #719BEC; 350 | box-shadow: inset 0px 1px 0px 0px rgba(255,255,255,0.15); 351 | color: #FFFFFF; 352 | &:hover { 353 | @include background-image(linear-gradient(-155deg, #58ACD9 0%, #7BA8FF 50%)); 354 | box-shadow: inset 0px 1px 0px 0px rgba(255,255,255,0.12); 355 | } 356 | &:active { 357 | @include background-image(linear-gradient(24deg, #58ACD9 0%, #7BA8FF 100%)); 358 | box-shadow: inset 0px -1px 0px 0px rgba(255,255,255,0.12); 359 | } 360 | } 361 | .SettingsContainer-input { 362 | box-sizing: border-box; 363 | position: relative; 364 | width: 100%; 365 | height: 0; 366 | opacity: 0; 367 | margin-top: -3px; 368 | padding: 3px 0.4em 0 0.4em; 369 | outline: none; 370 | cursor: text; 371 | font-size: 0.8em; 372 | color: $icon-dark-grey; 373 | font-family: $font; 374 | background: $white; 375 | border: 1px solid #B8C6D3; 376 | border-radius: 0 0 $radius $radius; 377 | @include transition(400ms all cubic-bezier(0.34,1.61,0.7,1)); 378 | 379 | &.is-sharing { 380 | height: 40px; 381 | opacity: 1; 382 | } 383 | } 384 | 385 | #share > span, 386 | #share::after { 387 | display: block; 388 | @include transition(transform 0.3s, opacity 0.3s); 389 | @include transition-timing-function(cubic-bezier(0.2, 1, 0.3, 1)); 390 | } 391 | #share::after { 392 | content: attr(data-text); 393 | color: $positive-green; 394 | position: absolute; 395 | top: 0; 396 | width: 100%; 397 | opacity: 0; 398 | @include transform(translateY(25%)); 399 | } 400 | #share.is-sharing { 401 | > span { 402 | opacity: 0; 403 | @include transform(translateY(-25%)); 404 | } 405 | &::after { 406 | opacity: 1; 407 | @include transform(translateY(0)); 408 | } 409 | } 410 | 411 | .FlashMessage { 412 | color: $darkest-grey; 413 | float: left; 414 | @include transform(translateY(-3em)); 415 | 416 | &.is-visible { 417 | @include animation(saved-inout 4s cubic-bezier(0.86, 0, 0.07, 1) 0.2s forwards); 418 | } 419 | } 420 | 421 | .ExcelEditor { 422 | width: 100%; 423 | height: 90%; 424 | display: block; 425 | overflow: auto; 426 | z-index: 1; 427 | @include transform(translateX(1em)); 428 | opacity: 0; 429 | @include animation(editor-in 600ms cubic-bezier(0.215, 0.61, 0.355, 1) forwards); 430 | } 431 | .ExcelEditor-controls { 432 | text-align: center; 433 | margin-top: 3em; 434 | margin-bottom: 2em; 435 | @include user-select(none); 436 | cursor: default; 437 | 438 | &:hover .ExcelEditor-control { 439 | color: $table-text-hover; 440 | } 441 | } 442 | .ExcelEditor-control { 443 | display: inline-block; 444 | color: $table-text; 445 | opacity: 0.6; 446 | margin: 0 1em; 447 | @include transition(300ms all ease); 448 | 449 | a { 450 | font-size: 1.1em; 451 | font-weight: 600; 452 | cursor: pointer; 453 | } 454 | a:first-child { 455 | margin-right: 0.4em; 456 | } 457 | a:last-child { 458 | margin-left: 0.4em; 459 | } 460 | a:active { 461 | color: $table-text-active; 462 | } 463 | span { 464 | font-size: 0.8em; 465 | } 466 | } 467 | .ExcelEditor-wrapper { 468 | display: block; 469 | text-align: center; 470 | } 471 | .ExcelEditor-table { 472 | margin: 0 auto; 473 | border-spacing: 0.5em; 474 | color: $table-text; 475 | 476 | thead th { 477 | font-weight: 300; 478 | font-size: 0.7em; 479 | } 480 | } 481 | .ExcelEditor-header { 482 | background: $black; 483 | min-width: 3.5em; 484 | border: 2px solid transparent; 485 | border-radius: $radius; 486 | outline: none; 487 | padding: 0.2em 0.7em; 488 | font-weight: 300; 489 | line-height: 1.4em; 490 | @include transition(200ms all ease); 491 | &:hover { 492 | color: $table-text-hover; 493 | border: 2px solid rgba(68, 79, 88, 50); 494 | } 495 | &:focus { 496 | color: $table-text-active; 497 | border: 2px solid $green; 498 | } 499 | } 500 | .ExcelEditor-cell { 501 | min-width: 3em; 502 | text-align: center; 503 | background: $black; 504 | border: 2px solid transparent; 505 | border-radius: $radius; 506 | outline: none; 507 | padding: 0.2em 0.7em; 508 | font-weight: 300; 509 | line-height: 1.4em; 510 | @include transition(200ms all ease); 511 | &:hover { 512 | color: $table-text-hover; 513 | border: 2px solid rgba(68, 79, 88, 50); 514 | } 515 | &:focus { 516 | color: $table-text-active; 517 | border: 2px solid $green; 518 | } 519 | } 520 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | var plugins = []; 4 | var release = false; 5 | if (process.env.NODE_ENV === 'production') { 6 | release = true; 7 | plugins.push( 8 | new webpack.DefinePlugin({ 9 | 'process.env': { 10 | NODE_ENV: JSON.stringify('production') 11 | } 12 | }), 13 | new webpack.optimize.UglifyJsPlugin(), 14 | new webpack.optimize.DedupePlugin() 15 | ); 16 | } 17 | 18 | module.exports = { 19 | cache: true, 20 | entry: './index', 21 | output: { 22 | path: release ? './dist' : '.', 23 | filename: release ? 'browser-bundle-[hash].js' : 'browser-bundle.js' 24 | }, 25 | module: { 26 | loaders: [ 27 | {test: /\.(js|jsx)$/, loader: 'jsx-loader'}, 28 | {test: /\.json$/, loader: 'json-loader'}, 29 | {test: /\.svg$/, loader: 'raw-loader'} 30 | ] 31 | }, 32 | resolve: { 33 | extensions: ['', '.js', '.jsx', '.json', '.svg'] 34 | }, 35 | plugins: plugins 36 | }; 37 | --------------------------------------------------------------------------------