diff --git a/README.md b/README.md index d2459a2..3d5c66b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,77 @@ # CAT + +## Overview This Charting Application Tester (CAT) lets users make and adjust web graphics on the fly. + +### Controls +allow the choice and configuration of charts, as well as the data file with which to initialize the charts + +#### 1. Choose a Charting Library +controls which charting application library/version and charting library/version will be loaded + +##### Render Chart +button that generates the selected chart + +1. Destroys the currently displayed chart if one has been rendered. +2. Loads the selected data file. +3. Initializes the selected charting application library. + +##### Library: +dropdown with a list of charting application libraries + +1. Removes the previous library's .js, .css, and/or stylesheet from the DOM. +2. Updates the status section. +3. Loads the master branch of the selected library. + 1. Loads the library's package.json file to know where the main .js file lives. + 2. Optionally loads the settings-schema.json file to populate the settings text/form if the library has a settings schema file. + 3. Loads the main .js file. + 4. Optionally loads the main .css file if the library has a .css file. +4. Loads the branches and releases of the library. +5. Updates the settings text/form. + +##### Version: +dropdown with a list of branches and releases for the selected charting application library + +1. Removes the previous library's .js, .css, and/or stylesheet from the DOM. +2. Loads the selected version of the selected library. + 1. Loads the library's package.json file to know where the main .js file lives. + 2. Optionally loads the settings-schema.json file to populate the settings text/form if the library has a settings schema file. + 3. Loads the main .js file. + 4. Optionally loads the main .css file if the library has a .css file. +3. Updates the settings text/form. + +##### Init: +input that allows the specification of the namespace of the selected charting application library + +##### . +optional input that allows the specification of a method of the selected charting application library that generates the chart + +##### Webcharts Version: +dropdown with a list of branches and releases of the charting library, which is a dependency of the charting application library; Webcharts is currently the only charting library supported + +1. Removes the previous library's .js, .css, and/or stylesheet from the DOM. +2. Loads the selected library. + 1. Loads the library's package.json file to know where the main .js file lives. + 2. Loads the main .js file. + 3. Loads the main .css file if the library has a .css file. + +##### Schema: +input that accepts the name of the settings schema of the selected charting application library + +#### 2. Choose a Dataset +dropdown that allows the selection of data file with which to initialize the selected charting application + +##### :magnifying glass: +button that toggles the display from the chart to the loaded dataset + +#### 3. Customize the Chart +allows editing of the chart settings, either with a text input or with a settings form generated with the charting application library's settings schema + +##### Settings:-text +a simple text input that allows the specification of a settings object in JSON or JavaScript + +##### Settings:-form +a list of inputs generated with the settings schema of the loaded charting application library + +#### 4. Environment +a list of the loaded .css, .js, and stylesheets diff --git a/build/cat.js b/build/cat.js index f31cdb9..ea14fc9 100644 --- a/build/cat.js +++ b/build/cat.js @@ -1,1799 +1,1048 @@ -(function(global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' - ? (module.exports = factory()) - : typeof define === 'function' && define.amd ? define(factory) : (global.cat = factory()); -})(this, function() { - 'use strict'; - - /** - * @this {Promise} - */ - function finallyConstructor(callback) { - var constructor = this.constructor; - return this.then( - function(value) { - return constructor.resolve(callback()).then(function() { - return value; - }); - }, - function(reason) { - return constructor.resolve(callback()).then(function() { - return constructor.reject(reason); - }); - } - ); - } - - // Store setTimeout reference so promise-polyfill will be unaffected by - // other code modifying setTimeout (like sinon.useFakeTimers()) - var setTimeoutFunc = setTimeout; - - function noop() {} - - // Polyfill for Function.prototype.bind - function bind(fn, thisArg) { - return function() { - fn.apply(thisArg, arguments); - }; - } - - /** - * @constructor - * @param {Function} fn - */ - function Promise$1(fn) { - if (!(this instanceof Promise$1)) - throw new TypeError('Promises must be constructed via new'); - if (typeof fn !== 'function') throw new TypeError('not a function'); - /** @type {!number} */ - this._state = 0; - /** @type {!boolean} */ - this._handled = false; - /** @type {Promise|undefined} */ - this._value = undefined; - /** @type {!Array} */ - this._deferreds = []; - - doResolve(fn, this); - } - - function handle(self, deferred) { - while (self._state === 3) { - self = self._value; - } - if (self._state === 0) { - self._deferreds.push(deferred); - return; - } - self._handled = true; - Promise$1._immediateFn(function() { - var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; - if (cb === null) { - (self._state === 1 ? resolve : reject)(deferred.promise, self._value); - return; - } - var ret; - try { - ret = cb(self._value); - } catch (e) { - reject(deferred.promise, e); - return; - } - resolve(deferred.promise, ret); +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.cat = factory()); +}(this, (function () { 'use strict'; + + /** + * @this {Promise} + */ + function finallyConstructor(callback) { + var constructor = this.constructor; + return this.then( + function(value) { + return constructor.resolve(callback()).then(function() { + return value; }); - } - - function resolve(self, newValue) { - try { - // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure - if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.'); - if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { - var then = newValue.then; - if (newValue instanceof Promise$1) { - self._state = 3; - self._value = newValue; - finale(self); - return; - } else if (typeof then === 'function') { - doResolve(bind(then, newValue), self); - return; - } - } - self._state = 1; - self._value = newValue; - finale(self); - } catch (e) { - reject(self, e); - } - } - - function reject(self, newValue) { - self._state = 2; - self._value = newValue; - finale(self); - } - - function finale(self) { - if (self._state === 2 && self._deferreds.length === 0) { - Promise$1._immediateFn(function() { - if (!self._handled) { - Promise$1._unhandledRejectionFn(self._value); - } - }); - } - - for (var i = 0, len = self._deferreds.length; i < len; i++) { - handle(self, self._deferreds[i]); - } - self._deferreds = null; - } - - /** - * @constructor - */ - function Handler(onFulfilled, onRejected, promise) { - this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; - this.onRejected = typeof onRejected === 'function' ? onRejected : null; - this.promise = promise; - } - - /** - * Take a potentially misbehaving resolver function and make sure - * onFulfilled and onRejected are only called once. - * - * Makes no guarantees about asynchrony. - */ - function doResolve(fn, self) { - var done = false; - try { - fn( - function(value) { - if (done) return; - done = true; - resolve(self, value); - }, - function(reason) { - if (done) return; - done = true; - reject(self, reason); - } - ); - } catch (ex) { - if (done) return; - done = true; - reject(self, ex); - } - } - - Promise$1.prototype['catch'] = function(onRejected) { - return this.then(null, onRejected); - }; - - Promise$1.prototype.then = function(onFulfilled, onRejected) { - // @ts-ignore - var prom = new this.constructor(noop); - - handle(this, new Handler(onFulfilled, onRejected, prom)); - return prom; - }; - - Promise$1.prototype['finally'] = finallyConstructor; - - Promise$1.all = function(arr) { - return new Promise$1(function(resolve, reject) { - if (!arr || typeof arr.length === 'undefined') - throw new TypeError('Promise.all accepts an array'); - var args = Array.prototype.slice.call(arr); - if (args.length === 0) return resolve([]); - var remaining = args.length; - - function res(i, val) { - try { - if (val && (typeof val === 'object' || typeof val === 'function')) { - var then = val.then; - if (typeof then === 'function') { - then.call( - val, - function(val) { - res(i, val); - }, - reject - ); - return; - } - } - args[i] = val; - if (--remaining === 0) { - resolve(args); - } - } catch (ex) { - reject(ex); - } - } - - for (var i = 0; i < args.length; i++) { - res(i, args[i]); - } + }, + function(reason) { + return constructor.resolve(callback()).then(function() { + return constructor.reject(reason); }); - }; + } + ); + } - Promise$1.resolve = function(value) { - if (value && typeof value === 'object' && value.constructor === Promise$1) { - return value; - } + // Store setTimeout reference so promise-polyfill will be unaffected by + // other code modifying setTimeout (like sinon.useFakeTimers()) + var setTimeoutFunc = setTimeout; - return new Promise$1(function(resolve) { - resolve(value); - }); - }; + function noop() {} - Promise$1.reject = function(value) { - return new Promise$1(function(resolve, reject) { - reject(value); - }); - }; - - Promise$1.race = function(values) { - return new Promise$1(function(resolve, reject) { - for (var i = 0, len = values.length; i < len; i++) { - values[i].then(resolve, reject); - } - }); - }; - - // Use polyfill for setImmediate for performance gains - Promise$1._immediateFn = - (typeof setImmediate === 'function' && - function(fn) { - setImmediate(fn); - }) || - function(fn) { - setTimeoutFunc(fn, 0); - }; - - Promise$1._unhandledRejectionFn = function _unhandledRejectionFn(err) { - if (typeof console !== 'undefined' && console) { - console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console - } + // Polyfill for Function.prototype.bind + function bind(fn, thisArg) { + return function() { + fn.apply(thisArg, arguments); }; - - /** @suppress {undefinedVars} */ - var globalNS = (function() { - // the only reliable means to get the global object is - // `Function('return this')()` - // However, this causes CSP violations in Chrome apps. - if (typeof self !== 'undefined') { - return self; - } - if (typeof window !== 'undefined') { - return window; - } - if (typeof global !== 'undefined') { - return global; - } - throw new Error('unable to locate global object'); - })(); - - if (!('Promise' in globalNS)) { - globalNS['Promise'] = Promise$1; - } else if (!globalNS.Promise.prototype['finally']) { - globalNS.Promise.prototype['finally'] = finallyConstructor; - } - - if (typeof Object.assign != 'function') { - Object.defineProperty(Object, 'assign', { - value: function assign(target, varArgs) { - if (target == null) { - // TypeError if undefined or null - throw new TypeError('Cannot convert undefined or null to object'); - } - - var to = Object(target); - - for (var index = 1; index < arguments.length; index++) { - var nextSource = arguments[index]; - - if (nextSource != null) { - // Skip over if undefined or null - for (var nextKey in nextSource) { - // Avoid bugs when hasOwnProperty is shadowed - if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { - to[nextKey] = nextSource[nextKey]; - } - } - } - } - - return to; - }, - writable: true, - configurable: true - }); + } + + /** + * @constructor + * @param {Function} fn + */ + function Promise$1(fn) { + if (!(this instanceof Promise$1)) + throw new TypeError('Promises must be constructed via new'); + if (typeof fn !== 'function') throw new TypeError('not a function'); + /** @type {!number} */ + this._state = 0; + /** @type {!boolean} */ + this._handled = false; + /** @type {Promise|undefined} */ + this._value = undefined; + /** @type {!Array} */ + this._deferreds = []; + + doResolve(fn, this); + } + + function handle(self, deferred) { + while (self._state === 3) { + self = self._value; } - - if (!Array.prototype.find) { - Object.defineProperty(Array.prototype, 'find', { - value: function value(predicate) { - // 1. Let O be ? ToObject(this value). - if (this == null) { - throw new TypeError('"this" is null or not defined'); - } - - var o = Object(this); - - // 2. Let len be ? ToLength(? Get(O, 'length')). - var len = o.length >>> 0; - - // 3. If IsCallable(predicate) is false, throw a TypeError exception. - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } - - // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. - var thisArg = arguments[1]; - - // 5. Let k be 0. - var k = 0; - - // 6. Repeat, while k < len - while (k < len) { - // a. Let Pk be ! ToString(k). - // b. Let kValue be ? Get(O, Pk). - // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)). - // d. If testResult is true, return kValue. - var kValue = o[k]; - if (predicate.call(thisArg, kValue, k, o)) { - return kValue; - } - // e. Increase k by 1. - k++; - } - - // 7. Return undefined. - return undefined; - } - }); + if (self._state === 0) { + self._deferreds.push(deferred); + return; } - - if (!Array.prototype.findIndex) { - Object.defineProperty(Array.prototype, 'findIndex', { - value: function value(predicate) { - // 1. Let O be ? ToObject(this value). - if (this == null) { - throw new TypeError('"this" is null or not defined'); - } - - var o = Object(this); - - // 2. Let len be ? ToLength(? Get(O, "length")). - var len = o.length >>> 0; - - // 3. If IsCallable(predicate) is false, throw a TypeError exception. - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } - - // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. - var thisArg = arguments[1]; - - // 5. Let k be 0. - var k = 0; - - // 6. Repeat, while k < len - while (k < len) { - // a. Let Pk be ! ToString(k). - // b. Let kValue be ? Get(O, Pk). - // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)). - // d. If testResult is true, return k. - var kValue = o[k]; - if (predicate.call(thisArg, kValue, k, o)) { - return k; - } - // e. Increase k by 1. - k++; - } - - // 7. Return -1. - return -1; - } - }); - } - - function init() { - //layout the cat - this.wrap = d3 - .select(this.element) - .append('div') - .attr('class', 'cat-wrap'); - this.layout(this); - - //initialize the settings - this.setDefaults(this); - - //add others here! - - //create the controls - this.controls.init(this); - } - - function layout(cat) { - /* Layout primary sections */ - cat.controls.wrap = cat.wrap.append('div').classed('cat-controls section', true); - cat.chartWrap = cat.wrap.append('div').classed('cat-chart section', true); - cat.dataWrap = cat.wrap - .append('div') - .classed('cat-data section', true) - .classed('hidden', true); - - /* Layout CAT Controls Divs */ - cat.controls.wrap - .append('h2') - .classed('cat-controls-header', true) - .text('Charting Application Tester 😼'); - - cat.controls.submitWrap = cat.controls.wrap - .append('div') - .classed('control-section submit-section', true); - - cat.controls.rendererWrap = cat.controls.wrap - .append('div') - .classed('control-section renderer-section', true); - - cat.controls.dataWrap = cat.controls.wrap - .append('div') - .classed('control-section data-section', true); - - cat.controls.settingsWrap = cat.controls.wrap - .append('div') - .classed('control-section settings-section', true); - - cat.controls.environmentWrap = cat.controls.wrap - .append('div') - .classed('control-section environment-section', true); - } - - function addControlsToggle() { - var _this = this; - - var cat = this; - - this.controls.minimize = this.controls.submitWrap - .append('div') - .classed('cat-button cat-button--minimize hidden', true) - .attr('title', 'Hide controls') - .text('<<') - .on('click', function() { - _this.controls.wrap.classed('hidden', true); - _this.chartWrap.style('margin-left', 0); - _this.chartWrap.selectAll('.wc-chart').each(function(d) { - try { - d.draw(); - } catch (error) {} - }); - _this.dataWrap.style('margin-left', 0); - _this.controls.maximize = _this.wrap - .insert('div', ':first-child') - .classed('cat-button cat-button--maximize', true) - .text('>>') - .attr('title', 'Show controls') - .on('click', function() { - cat.controls.wrap.classed('hidden', false); - cat.chartWrap.style('margin-left', '20%'); - cat.chartWrap.selectAll('.wc-chart').each(function(d) { - try { - d.draw(); - } catch (error) {} - }); - cat.dataWrap.style('margin-left', '20%'); - d3.select(this).remove(); - }); - }); - } - - var _typeof = - typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' - ? function(obj) { - return typeof obj; - } - : function(obj) { - return obj && - typeof Symbol === 'function' && - obj.constructor === Symbol && - obj !== Symbol.prototype - ? 'symbol' - : typeof obj; - }; - - // Nice script loader from here: https://stackoverflow.com/questions/538745/how-to-tell-if-a-script-tag-failed-to-load - - function scriptLoader() {} - - scriptLoader.prototype = { - timer: function timer( - times, // number of times to try - delay, // delay per try - delayMore, // extra delay per try (additional to delay) - test, // called each try, timer stops if this returns true - failure, // called on failure - result // used internally, shouldn't be passed - ) { - var me = this; - if (times == -1 || times > 0) { - setTimeout(function() { - result = test() ? 1 : 0; - me.timer( - result ? 0 : times > 0 ? --times : times, - delay + (delayMore ? delayMore : 0), - delayMore, - test, - failure, - result - ); - }, result || delay < 0 ? 0.1 : delay); - } else if (typeof failure == 'function') { - setTimeout(failure, 1); - } - }, - - addEvent: function addEvent(el, eventName, eventFunc) { - if ((typeof el === 'undefined' ? 'undefined' : _typeof(el)) != 'object') { - return false; - } - - if (el.addEventListener) { - el.addEventListener(eventName, eventFunc, false); - return true; - } - - if (el.attachEvent) { - el.attachEvent('on' + eventName, eventFunc); - return true; - } - - return false; - }, - - // add script to dom - require: function require(url, args) { - var me = this; - args = args || {}; - - var scriptTag = document.createElement('script'); - var headTag = document.getElementsByTagName('head')[0]; - if (!headTag) { - return false; - } - - setTimeout(function() { - var f = typeof args.success == 'function' ? args.success : function() {}; - args.failure = typeof args.failure == 'function' ? args.failure : function() {}; - var fail = function fail() { - if (!scriptTag.__es) { - scriptTag.__es = true; - scriptTag.id = 'failed'; - args.failure(scriptTag); - } - }; - scriptTag.onload = function() { - scriptTag.id = 'loaded'; - f(scriptTag); - }; - scriptTag.type = 'text/javascript'; - scriptTag.async = typeof args.async == 'boolean' ? args.async : false; - scriptTag.charset = 'utf-8'; - me.__es = false; - me.addEvent(scriptTag, 'error', fail); // when supported - // when error event is not supported fall back to timer - me.timer( - 15, - 1000, - 0, - function() { - return scriptTag.id == 'loaded'; - }, - function() { - if (scriptTag.id != 'loaded') { - fail(); - } - } - ); - scriptTag.src = url; - setTimeout(function() { - try { - headTag.appendChild(scriptTag); - } catch (e) { - fail(); - } - }, 1); - }, typeof args.delay == 'number' ? args.delay : 1); - return true; + self._handled = true; + Promise$1._immediateFn(function() { + var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; + if (cb === null) { + (self._state === 1 ? resolve : reject)(deferred.promise, self._value); + return; + } + var ret; + try { + ret = cb(self._value); + } catch (e) { + reject(deferred.promise, e); + return; + } + resolve(deferred.promise, ret); + }); + } + + function resolve(self, newValue) { + try { + // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure + if (newValue === self) + throw new TypeError('A promise cannot be resolved with itself.'); + if ( + newValue && + (typeof newValue === 'object' || typeof newValue === 'function') + ) { + var then = newValue.then; + if (newValue instanceof Promise$1) { + self._state = 3; + self._value = newValue; + finale(self); + return; + } else if (typeof then === 'function') { + doResolve(bind(then, newValue), self); + return; } - }; - - function loadPackageJson(cat) { - return new Promise(function(resolve, reject) { - cat.current.url = - cat.current.version === 'master' - ? (cat.current.rootURL || cat.config.rootURL) + '/' + cat.current.name - : (cat.current.rootURL || cat.config.rootURL) + - '/' + - cat.current.name + - '@' + - cat.current.version; - var xhr = new XMLHttpRequest(); - xhr.open('GET', cat.current.url + '/package.json'); - xhr.onload = function() { - if (this.status === 200) { - resolve(xhr.response); - } else { - reject({ - status: this.status, - statusTxt: xhr.statusText - }); - } - }; - xhr.onerror = function() { - reject({ - status: this.status, - statusText: xhr.statusText - }); - }; - xhr.send(); - }); - } - - function getCSS() { - var current_css = []; - d3.selectAll('link').each(function() { - var obj = {}; - obj.sel = this; - obj.link = d3.select(this).property('href'); - obj.disabled = d3.select(this).property('disabled'); - obj.filename = obj.link.substring(obj.link.lastIndexOf('/') + 1); - obj.wrap = d3.select(this); - current_css.push(obj); - }); - return current_css; + } + self._state = 1; + self._value = newValue; + finale(self); + } catch (e) { + reject(self, e); } - - function getJS() { - var current_js = []; - d3.selectAll('script').each(function() { - var obj = {}; - obj.link = d3.select(this).property('src'); - obj.filename = obj.link.substring(obj.link.lastIndexOf('/') + 1); - if (obj.link) { - current_js.push(obj); - } - }); - return current_js; - } - - function createChartExport(cat) { - /* Get settings from current controls */ - var webcharts_version = cat.controls.libraryVersion.node().value; - var renderer_version = cat.controls.versionSelect.node().value; - var data_file = cat.controls.dataFileSelect.node().value; - var data_file_path = cat.config.dataURL + data_file; - var init_string = cat.current.sub - ? cat.current.main + '.' + cat.current.sub - : cat.current.main; - - var chart_config = JSON.stringify(cat.current.config, null, ' '); - var renderer_css = ''; - if (cat.current.css) { - var css_path = - cat.config.rootURL + - '/' + - cat.current.name + - '/' + - renderer_version + - '/' + - cat.current.css; - renderer_css = ""; + } + + function reject(self, newValue) { + self._state = 2; + self._value = newValue; + finale(self); + } + + function finale(self) { + if (self._state === 2 && self._deferreds.length === 0) { + Promise$1._immediateFn(function() { + if (!self._handled) { + Promise$1._unhandledRejectionFn(self._value); } - - /* Return a html for a working chart */ - var exampleTemplate = - '\n\n\n \n\n \n ' + - cat.current.name + - "\n\n \n\n \n \n \n\n \n " + - renderer_css + - "\n \n\n \n

" + - cat.current.name + - ' created for ' + - cat.current.defaultData + - "

\n
\n
\n \n\n \n\n"; - return exampleTemplate; + }); } - function showEnv(cat) { - /*build list of loaded CSS */ - var current_css = getCSS(); - var cssItems = cat.controls.cssList.selectAll('li').data(current_css); - var newItems = cssItems.enter().append('li'); - var itemContents = newItems.append('span').property('title', function(d) { - return d.link; - }); - - itemContents - .append('a') - .text(function(d) { - return d.filename; - }) - .attr('href', function(d) { - return d.link; - }) - .property('target', '_blank'); - - var switchWrap = itemContents - .append('label') - .attr('class', 'switch') - .classed('hidden', function(d) { - return d.filename == 'cat.css'; - }); - - var switchCheck = switchWrap - .append('input') - .property('type', 'checkbox') - .property('checked', function(d) { - return !d.disabled; - }); - switchWrap.append('span').attr('class', 'slider round'); - - switchCheck.on('click', function(d) { - //load or unload css - d.disabled = !d.disabled; - d.wrap.property('disabled', d.disabled); - - //update toggle mark - this.checked = !d.disabled; - }); - - cssItems.exit().remove(); - - /*build list of loaded JS */ - var current_js = getJS(); - var jsItems = cat.controls.jsList.selectAll('li').data(current_js); - - jsItems - .enter() - .append('li') - .append('a') - .text(function(d) { - return d.filename; - }) - .property('title', function(d) { - return d.link; - }) - .attr('href', function(d) { - return d.link; - }) - .property('target', '_blank'); - - jsItems.exit().remove(); + for (var i = 0, len = self._deferreds.length; i < len; i++) { + handle(self, self._deferreds[i]); } - - function renderChart(cat) { - var rendererObj = cat.controls.rendererSelect.selectAll('option:checked').data()[0]; - cat.settings.sync(cat); - //render the new chart with the current settings - var dataFile = cat.controls.dataFileSelect.node().value; - var dataObject = cat.config.dataFiles.find(function(f) { - return f.label == dataFile; - }); - var version = cat.controls.versionSelect.node().value; - cat.current.main = cat.controls.mainFunction.node().value; - cat.current.sub = cat.controls.subFunction.node().value; - - function render(error, data) { - if (error) { - cat.status.loadStatus(cat.statusDiv, false, dataFilePath); - } else { - cat.status.loadStatus(cat.statusDiv, true, dataFilePath); - if (cat.current.sub) { - var myChart = window[cat.current.main][cat.current.sub]( - '.cat-chart', - cat.current.config - ); - cat.status.chartCreateStatus(cat.statusDiv, cat.current.main, cat.current.sub); - } else { - var myChart = window[cat.current.main]('.cat-chart .chart', cat.current.config); - cat.status.chartCreateStatus(cat.statusDiv, cat.current.main); - } - - cat.current.htmlExport = createChartExport(cat); // save the source code before init - - try { - myChart.init(data); - } catch (err) { - cat.status.chartInitStatus(cat.statusDiv, false, err); - } finally { - cat.status.chartInitStatus(cat.statusDiv, true, null, cat.current.htmlExport); - - // save to server button - if (cat.config.useServer) { - cat.status.saveToServer(cat); - } - showEnv(cat); - - //don't print any new statuses until a new chart is rendered - cat.printStatus = false; - } - } - } - - if (dataObject.user_loaded) { - dataObject.json = d3.csv.parse(dataObject.csv_raw); - render(false, dataObject.json); - } else { - var dataFilePath = dataObject.path + dataFile; - d3.csv(dataFilePath, function(error, data) { - render(error, data); - }); + self._deferreds = null; + } + + /** + * @constructor + */ + function Handler(onFulfilled, onRejected, promise) { + this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; + this.onRejected = typeof onRejected === 'function' ? onRejected : null; + this.promise = promise; + } + + /** + * Take a potentially misbehaving resolver function and make sure + * onFulfilled and onRejected are only called once. + * + * Makes no guarantees about asynchrony. + */ + function doResolve(fn, self) { + var done = false; + try { + fn( + function(value) { + if (done) return; + done = true; + resolve(self, value); + }, + function(reason) { + if (done) return; + done = true; + reject(self, reason); } + ); + } catch (ex) { + if (done) return; + done = true; + reject(self, ex); } + } - function loadRenderer(cat) { - var promisedPackage = loadPackageJson(cat); - promisedPackage.then(function(response) { - cat.current.package = JSON.parse(response); - cat.current.js_url = - cat.current.url + '/' + cat.current.package.main.replace(/^\.?\/?/, ''); - cat.current.css_url = cat.current.css ? cat.current.url + '/' + cat.current.css : null; - - if (cat.current.css) { - var current_css = getCSS().filter(function(f) { - return f.link == cat.current.css_url; - }); - var css_loaded = current_css.length > 0; - if (!css_loaded) { - var link = document.createElement('link'); - link.href = cat.current.css_url; - - link.type = 'text/css'; - link.rel = 'stylesheet'; - document.getElementsByTagName('head')[0].appendChild(link); - } else if (current_css[0].disabled) { - //enable the css if it's disabled - d3.select(current_css[0].sel).property('disabled', false); - cat.controls.cssList - .selectAll('li') - .filter(function(d) { - return d.link == cat.current.css_url; - }) - .select('input') - .property('checked', true); - } - } - - var current_js = getJS().filter(function(f) { - return f.link == cat.current.js_url; - }); - var js_loaded = current_js.length > 0; - - if (!js_loaded) { - var loader = new scriptLoader(); - loader.require(cat.current.js_url, { - async: true, - success: function success() { - cat.status.loadStatus( - cat.statusDiv, - true, - cat.current.js_url, - cat.current.name, - cat.current.version - ); - renderChart(cat); - }, - failure: function failure() { - cat.status.loadStatus( - cat.statusDiv, - false, - cat.current.js_url, - cat.current.name, - cat.current.version - ); - } - }); - } else { - cat.status.loadStatus( - cat.statusDiv, - true, - cat.current.js_url, - cat.current.name, - cat.current.version - ); - renderChart(cat); - } - }); - } + Promise$1.prototype['catch'] = function(onRejected) { + return this.then(null, onRejected); + }; - function loadLibrary(cat) { - var version = cat.controls.libraryVersion.node().value; - var library = 'webcharts'; //hardcode to webcharts for now - could generalize later + Promise$1.prototype.then = function(onFulfilled, onRejected) { + // @ts-ignore + var prom = new this.constructor(noop); - // --- load css --- // - var cssPath = - version !== 'master' - ? cat.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' - : cat.config.rootURL + '/Webcharts/css/webcharts.css'; + handle(this, new Handler(onFulfilled, onRejected, prom)); + return prom; + }; - var current_css = getCSS().filter(function(f) { - return f.link == cssPath; - }); - var css_loaded = current_css.length > 0; - if (!css_loaded) { - //load the css if it isn't already loaded - var link = document.createElement('link'); - link.href = cssPath; - link.type = 'text/css'; - link.rel = 'stylesheet'; - document.getElementsByTagName('head')[0].appendChild(link); - } else if (current_css[0].disabled) { - //enable the css if it's disabled - d3.select(current_css[0].sel).property('disabled', false); - cat.controls.cssList - .selectAll('li') - .filter(function(d) { - return d.link == cssPath; - }) - .select('input') - .property('checked', true); - } + Promise$1.prototype['finally'] = finallyConstructor; - // --- load js --- // - var rendererPath = - version !== 'master' - ? cat.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' - : cat.config.rootURL + '/Webcharts/build/webcharts.js'; + Promise$1.all = function(arr) { + return new Promise$1(function(resolve, reject) { + if (!arr || typeof arr.length === 'undefined') + throw new TypeError('Promise.all accepts an array'); + var args = Array.prototype.slice.call(arr); + if (args.length === 0) return resolve([]); + var remaining = args.length; - var current_js = getJS().filter(function(f) { - return f.link == rendererPath; - }); - var js_loaded = current_js.length > 0; - - if (!js_loaded) { - var loader = new scriptLoader(); - loader.require(rendererPath, { - async: true, - success: function success() { - cat.status.loadStatus(cat.statusDiv, true, rendererPath, library, version); - loadRenderer(cat); + function res(i, val) { + try { + if (val && (typeof val === 'object' || typeof val === 'function')) { + var then = val.then; + if (typeof then === 'function') { + then.call( + val, + function(val) { + res(i, val); }, - failure: function failure() { - cat.status.loadStatus(cat.statusDiv, false, rendererPath, library, version); - } - }); - } else { - cat.status.loadStatus(cat.statusDiv, true, rendererPath, library, version); - loadRenderer(cat); + reject + ); + return; + } + } + args[i] = val; + if (--remaining === 0) { + resolve(args); + } + } catch (ex) { + reject(ex); } - } + } - function addSubmitButton() { - var _this = this; - - this.controls.submitButton = this.controls.submitWrap - .append('button') - .attr('class', 'submit') - .text('Render Chart') - .on('click', function() { - _this.controls.minimize.classed('hidden', false); - _this.dataWrap.classed('hidden', true); - _this.chartWrap.classed('hidden', false); - - //Disable and/or remove previously loaded stylesheets. - d3 - .selectAll('link') - .filter(function() { - return !this.href.indexOf('css/cat.css'); - }) - .property('disabled', true) - .remove(); - - d3 - .selectAll('style') - .property('disabled', true) - .remove(); - - _this.chartWrap.selectAll('*').remove(); - _this.printStatus = true; - _this.statusDiv = _this.chartWrap.append('div').attr('class', 'status'); - _this.statusDiv - .append('div') - .text('Starting to render the chart ... ') - .classed('info', true); - - _this.chartWrap.append('div').attr('class', 'chart'); - loadLibrary(_this); - }); - } + for (var i = 0; i < args.length; i++) { + res(i, args[i]); + } + }); + }; - function initSubmit(cat) { - addControlsToggle.call(cat); - addSubmitButton.call(cat); + Promise$1.resolve = function(value) { + if (value && typeof value === 'object' && value.constructor === Promise$1) { + return value; } - function updateRenderer(select) { - var _this = this; - - this.current = d3 - .select(select) - .select('option:checked') - .data()[0]; - this.current.version = 'master'; - - //update the chart type configuration to the defaults for the selected renderer - this.controls.mainFunction.node().value = this.current.main; - this.controls.versionSelect.node().value = 'master'; - this.controls.subFunction.node().value = this.current.sub; - this.controls.schema.node().value = this.current.schema; - - //update the selected data set to the default for the new rendererSection - this.controls.dataFileSelect.selectAll('option').property('selected', function(d) { - return _this.current.defaultData === d.label; - }); - - //Re-initialize the chart config section - this.settings.set(this); - } - - function initRendererSelect(cat) { - cat.controls.rendererWrap.append('h3').text('1. Choose a Charting Library'); - cat.controls.rendererWrap.append('span').text('Library: '); - - cat.controls.rendererSelect = cat.controls.rendererWrap.append('select'); - cat.controls.rendererSelect - .selectAll('option') - .data(cat.config.renderers) - .enter() - .append('option') - .text(function(d) { - return d.name; - }); - - cat.controls.rendererSelect.on('change', function() { - updateRenderer.call(cat, this); - }); - cat.controls.rendererWrap.append('br'); - cat.controls.rendererWrap.append('span').text('Version: '); - cat.controls.versionSelect = cat.controls.rendererWrap.append('input'); - cat.controls.versionSelect.node().value = 'master'; - cat.controls.versionSelect.on('input', function() { - cat.current.version = this.value; - }); - cat.controls.versionSelect.on('change', function() { - cat.settings.set(cat); - }); - cat.controls.rendererWrap.append('br'); - - cat.controls.rendererWrap - .append('a') - .text('More Options') - .style('text-decoration', 'underline') - .style('color', 'blue') - .style('cursor', 'pointer') - .on('click', function() { - d3.select(this).remove(); - cat.controls.rendererWrap.selectAll('*').classed('hidden', false); - }); - - //specify the code to create the chart - cat.controls.rendererWrap - .append('span') - .text(' Init: ') - .classed('hidden', true); - cat.controls.mainFunction = cat.controls.rendererWrap - .append('input') - .classed('hidden', true); - cat.controls.mainFunction.node().value = cat.current.main; - cat.controls.rendererWrap - .append('span') - .text('.') - .classed('hidden', true); - cat.controls.subFunction = cat.controls.rendererWrap - .append('input') - .classed('hidden', true); - cat.controls.subFunction.node().value = cat.current.sub; - cat.controls.rendererWrap.append('br').classed('hidden', true); - //Webcharts versionSelect - cat.controls.rendererWrap - .append('span') - .text('Webcharts Version: ') - .classed('hidden', true); - cat.controls.libraryVersion = cat.controls.rendererWrap - .append('input') - .classed('hidden', true); - cat.controls.libraryVersion.node().value = 'master'; - cat.controls.rendererWrap.append('br').classed('hidden', true); - - cat.controls.rendererWrap - .append('span') - .text('Schema: ') - .classed('hidden', true); - cat.controls.schema = cat.controls.rendererWrap.append('input').classed('hidden', true); - cat.controls.schema.node().value = cat.current.schema; - cat.controls.rendererWrap.append('br').classed('hidden', true); - - //add enter listener - cat.controls.addEnterEventListener(cat.controls.rendererWrap, cat); - } - - function showDataPreview(cat) { - cat.dataWrap.classed('hidden', false); - cat.chartWrap.classed('hidden', true); - cat.dataWrap.selectAll('*').remove(); - - if (cat.dataPreview) { - cat.dataPreview.destroy(); - } - - var dataFile = cat.controls.dataFileSelect.node().value; - var dataObject = cat.config.dataFiles.find(function(f) { - return f.label == dataFile; - }); - var path = dataObject.path + dataObject.label; - - cat.dataWrap - .append('button') - .text('<< Close Data Preview') - .on('click', function() { - cat.dataWrap.classed('hidden', true); - cat.chartWrap.classed('hidden', false); - }); - - cat.dataWrap.append('h3').text('Data Preview for ' + dataFile); - - cat.dataWrap - .append('div') - .attr('class', 'dataPreview') - .style('overflow-x', 'overlay'); - cat.dataPreview = webCharts.createTable('.dataPreview'); - if (dataObject.user_loaded) { - cat.dataPreview.init(d3.csv.parse(dataObject.csv_raw)); - } else { - d3.csv(path, function(raw) { - cat.dataPreview.init(raw); - }); - } - } - - function initDataSelect(cat) { - cat.controls.dataWrap.append('h3').text('2. Choose a data Set'); - cat.controls.dataFileSelect = cat.controls.dataWrap.append('select'); - - cat.controls.dataWrap - .append('span') - .html('🔍') - .style('cursor', 'pointer') - .on('click', function() { - showDataPreview(cat); - }); - - cat.controls.dataFileSelect - .selectAll('option') - .data(cat.config.dataFiles) - .enter() - .append('option') - .text(function(d) { - return d.label; - }) - .property('selected', function(d) { - return cat.current.defaultData == d.label ? true : null; - }); - } + return new Promise$1(function(resolve) { + resolve(value); + }); + }; + + Promise$1.reject = function(value) { + return new Promise$1(function(resolve, reject) { + reject(value); + }); + }; + + Promise$1.race = function(values) { + return new Promise$1(function(resolve, reject) { + for (var i = 0, len = values.length; i < len; i++) { + values[i].then(resolve, reject); + } + }); + }; + + // Use polyfill for setImmediate for performance gains + Promise$1._immediateFn = + (typeof setImmediate === 'function' && + function(fn) { + setImmediate(fn); + }) || + function(fn) { + setTimeoutFunc(fn, 0); + }; - function initFileLoad() { - var cat = this; - //draw the control - var loadLabel = cat.controls.dataWrap.append('p').style('margin', 0); - - loadLabel - .append('small') - .text('Use local .csv file:') - .append('sup') - .html('ⓘ') - .property( - 'title', - 'Render a chart using a local file. File is added to the data set list, and is only available for a single session and is not saved.' - ) - .style('cursor', 'help'); - - var loadStatus = loadLabel - .append('small') - .attr('class', 'loadStatus') - .style('float', 'right') - .text('Select a csv to load'); - - cat.controls.dataFileLoad = cat.controls.dataWrap - .append('input') - .attr('type', 'file') - .attr('class', 'file-load-input') - .on('change', function() { - if (this.value.slice(-4).toLowerCase() == '.csv') { - loadStatus.text(this.files[0].name + ' ready to load').style('color', 'green'); - cat.controls.dataFileLoadButton.attr('disabled', null); - } else { - loadStatus.text(this.files[0].name + ' is not a csv').style('color', 'red'); - cat.controls.dataFileLoadButton.attr('disabled', true); - } - }); - - cat.controls.dataFileLoadButton = cat.controls.dataWrap - .append('button') - .text('Load') - .attr('class', 'file-load-button') - .attr('disabled', true) - .on('click', function(d) { - //credit to https://jsfiddle.net/Ln37kqc0/ - var files = cat.controls.dataFileLoad.node().files; - - if (files.length <= 0) { - //shouldn't happen since button is disabled when no file is present, but ... - console.log('No file selected ...'); - return false; - } - - var fr = new FileReader(); - fr.onload = function(e) { - // get the current date/time - var d = new Date(); - var n = d3.time.format('%X')(d); - - //make an object for the file - var dataObject = { - label: files[0].name + ' (added at ' + n + ')', - user_loaded: true, - csv_raw: e.target.result - }; - cat.config.dataFiles.push(dataObject); - - //add it to the select dropdown - cat.controls.dataFileSelect - .append('option') - .datum(dataObject) - .text(function(d) { - return d.label; - }) - .attr('selected', true); - - //clear the file input & disable the load button - loadStatus.text(files[0].name + ' loaded').style('color', 'green'); - - cat.controls.dataFileLoadButton.attr('disabled', true); - cat.controls.dataFileLoad.property('value', ''); - }; - - fr.readAsText(files.item(0)); - }); + Promise$1._unhandledRejectionFn = function _unhandledRejectionFn(err) { + if (typeof console !== 'undefined' && console) { + console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console } - - function initChartConfig(cat) { - var settingsHeading = cat.controls.settingsWrap - .append('h3') - .html('3. Customize the Chart '); - - cat.controls.settingsWrap.append('span').text('Settings: '); - - /* - ////////////////////////////////////// - //initialize the config status icon - ////////////////////////////////////// - cat.controls.settingsStatus = settingsSection - .append("div") - .style("font-size", "1.5em") - .style("float", "right") - .style("cursor", "pointer"); - settingsSection.append("br"); - */ - - ////////////////////////////////////////////////////////////////////// - //radio buttons to toggle between "text" and "form" based settings - ///////////////////////////////////////////////////////////////////// - cat.controls.settingsTypeText = cat.controls.settingsWrap - .append('input') - .attr('class', 'radio') - .property('type', 'radio') - .property('name', 'settingsType') - .property('value', 'text'); - cat.controls.settingsWrap.append('span').text('text'); - cat.controls.settingsTypeForm = cat.controls.settingsWrap - .append('input') - .attr('class', 'radio') - .property('type', 'radio') - .property('name', 'settingsType') - .property('value', 'form'); - cat.controls.settingsWrap.append('span').text('form'); - cat.controls.settingsType = cat.controls.settingsWrap.selectAll('input[type="radio"]'); - - cat.controls.settingsType.on('change', function(d) { - cat.settings.sync(cat); //first sync the current settings to both views - - //then update to the new view, and update controls. - cat.current.settingsView = this.value; // - if (cat.current.settingsView == 'text') { - cat.controls.settingsInput.classed('hidden', false); - cat.controls.settingsForm.classed('hidden', true); - } else if (cat.current.settingsView == 'form') { - cat.controls.settingsInput.classed('hidden', true); - cat.controls.settingsForm.classed('hidden', false); - } - }); - cat.controls.settingsWrap.append('br'); - - ////////////////////////////////////////////////////////////////////// - //text input section - ///////////////////////////////////////////////////////////////////// - cat.controls.settingsInput = cat.controls.settingsWrap - .append('textarea') - .attr('rows', 10) - .style('width', '90%') - .text('{}'); - - ////////////////////////////////////////////////////////////////////// - //wrapper for the form - ///////////////////////////////////////////////////////////////////// - cat.controls.settingsForm = cat.controls.settingsWrap - .append('div') - .attr('class', 'settingsForm') - .append('form'); - - //set the text/form settings for the first renderer - cat.settings.set(cat); + }; + + /** @suppress {undefinedVars} */ + var globalNS = (function() { + // the only reliable means to get the global object is + // `Function('return this')()` + // However, this causes CSP violations in Chrome apps. + if (typeof self !== 'undefined') { + return self; } - - function initEnvConfig(cat) { - var settingsHeading = cat.controls.environmentWrap.append('h3').html('4. Environment '); - - cat.controls.cssList = cat.controls.environmentWrap.append('ul').attr('class', 'cssList'); - cat.controls.cssList.append('h5').text('Loaded Stylesheets'); - - cat.controls.jsList = cat.controls.environmentWrap.append('ul').attr('class', 'jsList'); - cat.controls.jsList.append('h5').text('Loaded javascript'); - - showEnv(cat); + if (typeof window !== 'undefined') { + return window; } - - function init$1(cat) { - cat.current = cat.config.renderers[0]; - cat.current.version = 'master'; - initSubmit(cat); - initRendererSelect(cat); - initDataSelect(cat); - initFileLoad.call(cat); - initChartConfig(cat); - initEnvConfig(cat); + if (typeof global !== 'undefined') { + return global; } + throw new Error('unable to locate global object'); + })(); + + if (!('Promise' in globalNS)) { + globalNS['Promise'] = Promise$1; + } else if (!globalNS.Promise.prototype['finally']) { + globalNS.Promise.prototype['finally'] = finallyConstructor; + } + + if (typeof Object.assign != 'function') { + Object.defineProperty(Object, 'assign', { + value: function assign(target, varArgs) { + + if (target == null) { + // TypeError if undefined or null + throw new TypeError('Cannot convert undefined or null to object'); + } - function addEnterEventListener(selection, cat) { - //Add Enter event listener to all controls. - selection.selectAll('select,input').each(function() { - this.addEventListener('keypress', function(e) { - var key = e.which || e.keyCode; - - //13 is Enter - if (key === 13) cat.controls.submitButton.node().click(); - }); - }); - } - - /*------------------------------------------------------------------------------------------------\ - Define controls object. - \------------------------------------------------------------------------------------------------*/ - - var controls = { - init: init$1, - addEnterEventListener: addEnterEventListener - }; - - var defaultSettings = { - useServer: false, - rootURL: null, - dataURL: null, - dataFiles: [], - renderers: [] - }; + var to = Object(target); - function setDefaults(cat) { - cat.config.useServer = cat.config.useServer || defaultSettings.useServer; - cat.config.rootURL = cat.config.rootURL || defaultSettings.rootURL; - cat.config.dataURL = cat.config.dataURL || defaultSettings.dataURL; - cat.config.dataFiles = cat.config.dataFiles || defaultSettings.dataFiles; - cat.config.renderers = cat.config.renderers || defaultSettings.renderers; - - cat.config.dataFiles = cat.config.dataFiles.map(function(df) { - return typeof df == 'string' - ? { label: df, path: cat.config.dataURL, user_loaded: false } - : df; - }); - } + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; - function makeForm(cat, obj) { - d3 - .select('.settingsForm form') - .selectAll('*') - .remove(); - - //define form from settings schema - cat.current.form = brutusin['json-forms'].create(cat.current.schemaObj); - - if (!obj) { - //Render form with default schema settings. - cat.current.form.render(d3.select('.settingsForm form').node()); - - //Define renderer settings. - cat.current.config = cat.current.form.getData(); - - //Update text settings with default schema settings. - //cat.controls.settingsInput.node().value = JSON.stringify(cat.current.config, null, 4); - var json = JSON.stringify(cat.current.config, null, 4); - cat.controls.settingsInput.attr('rows', json.split('\n').length); - cat.controls.settingsInput.html(json); - } else - //Render form with updated text settings. - cat.current.form.render(d3.select('.settingsForm form').node(), cat.current.config); - - d3 - .select('.settingsForm form') - .selectAll('.glyphicon-remove') - .text('X'); - - //handle submission with the "render chart" button - d3.select('.settingsForm form .form-actions input').remove(); - //format the form a little bit so that we can dodge bootstrap - d3.selectAll('i.icon-plus-sign').text('+'); - d3.selectAll('i.icon-minus-sign').text('-'); - - //add enter listener - cat.controls.addEnterEventListener(cat.controls.wrap.select('.settingsForm'), cat); - } + if (nextSource != null) { + // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } - function setStatus(cat, statusVal) { - var statusOptions = [ - { - key: 'valid', - symbol: '✔', - color: 'green', - details: - "Settings match the current schema. Click 'Render Chart' to draw the chart." - }, - { - key: 'invalid', - symbol: '✘', - color: 'red', - details: - "Settings do not match the current schema. You can still click 'Render Chart' to try to draw the chart, but it might not work as expected." - }, - { - key: 'unknown', - symbol: '?', - color: 'blue', - details: - "You've loaded a schema, but the setting have changed. Click 'Validate Settings' to see if they're valid or you can click 'Render Chart' and see what happens." - }, - { - key: 'no schema', - symbol: 'NA', - color: '#999', - details: - "No Schema loaded. Cannot validate the current settings. You can click 'Render Chart' and see what happens." - } - ]; + return to; + }, + writable: true, + configurable: true + }); + } + + if (!Array.prototype.find) { + Object.defineProperty(Array.prototype, 'find', { + value: function value(predicate) { + // 1. Let O be ? ToObject(this value). + if (this == null) { + throw new TypeError('"this" is null or not defined'); + } - var myStatus = statusOptions.filter(function(d) { - return d.key == statusVal; - })[0]; + var o = Object(this); - cat.controls.settingsStatus - .html(myStatus.symbol) - .style('color', myStatus.color) - .attr('title', myStatus.details); - } + // 2. Let len be ? ToLength(? Get(O, 'length')). + var len = o.length >>> 0; - function validateSchema(cat) { - // consider: http://epoberezkin.github.io/ajv/#getting-started - // var Ajv = require('ajv'); - // var ajv = new Ajv(); // options can be passed, e.g. {allErrors: true} - // var validate = ajv.compile(cat.); - return true; - } + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } - function set$1(cat) { - // load the schema (if any) and see if it is validate - cat.current.schemaPath = [ - cat.current.rootURL || cat.config.rootURL, - cat.current.version !== 'master' - ? cat.current.name + '@' + cat.current.version - : cat.current.name, - cat.current.schema - ].join('/'); - - cat.current.settingsView = 'text'; - cat.controls.settingsInput.value = '{}'; - cat.current.config = {}; - - d3.json(cat.current.schemaPath, function(error, schemaObj) { - if (error) { - console.log('No schema loaded.'); - cat.current.hasValidSchema = false; - cat.current.schemaObj = null; - } else { - // attempt to validate the schema - console.log('Schema found ...'); - cat.current.hasValidSchema = validateSchema(schemaObj); - cat.current.settingsView = cat.current.hasValidSchema ? 'form' : 'text'; - cat.current.schemaObj = cat.current.hasValidSchema ? schemaObj : null; - } - //set the radio buttons - cat.controls.settingsTypeText.property('checked', cat.current.settingsView == 'text'); + // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. + var thisArg = arguments[1]; + + // 5. Let k be 0. + var k = 0; + + // 6. Repeat, while k < len + while (k < len) { + // a. Let Pk be ! ToString(k). + // b. Let kValue be ? Get(O, Pk). + // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)). + // d. If testResult is true, return kValue. + var kValue = o[k]; + if (predicate.call(thisArg, kValue, k, o)) { + return kValue; + } + // e. Increase k by 1. + k++; + } - cat.controls.settingsTypeForm - .property('checked', cat.current.settingsView == 'form') - .property('disabled', !cat.current.hasValidSchema); + // 7. Return undefined. + return undefined; + } + }); + } + + if (!Array.prototype.findIndex) { + Object.defineProperty(Array.prototype, 'findIndex', { + value: function value(predicate) { + // 1. Let O be ? ToObject(this value). + if (this == null) { + throw new TypeError('"this" is null or not defined'); + } - // Show/Hide sections - cat.controls.settingsInput.classed('hidden', cat.current.settingsView != 'text'); - cat.controls.settingsForm.classed('hidden', cat.current.settingsView != 'form'); + var o = Object(this); - //update the text or make the schema - cat.controls.settingsInput.node().value = JSON5.stringify(cat.current.config, null, 4); + // 2. Let len be ? ToLength(? Get(O, "length")). + var len = o.length >>> 0; - if (cat.current.hasValidSchema) { - console.log('... and it is valid. Making a nice form.'); - makeForm(cat); - } - }); - } - - function sync(cat, printStatus) { - function IsJsonString(str) { - try { - JSON5.parse(str); - } catch (e) { - return false; - } - return true; - } + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } - // set current config - if (cat.current.settingsView == 'text') { - var text = cat.controls.settingsInput.node().value; - - if (IsJsonString(text)) { - var settings = JSON5.parse(text); - var json = JSON.stringify(settings, null, 4); - - if (cat.printStatus) { - cat.statusDiv - .append('div') - .html('Successfully loaded settings from text input.') - .classed('success', true); - } - - cat.controls.settingsInput.node().value = json; - cat.current.config = settings; - } else { - if (cat.printStatus) { - cat.statusDiv - .append('div') - .html( - "Couldn't load settings from text. Check to see if you have valid JSON." - ) - .classed('error', true); - } - } + // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. + var thisArg = arguments[1]; + + // 5. Let k be 0. + var k = 0; + + // 6. Repeat, while k < len + while (k < len) { + // a. Let Pk be ! ToString(k). + // b. Let kValue be ? Get(O, Pk). + // c. Let testResult be ToBoolean(? Call(predicate, T, � kValue, k, O �)). + // d. If testResult is true, return k. + var kValue = o[k]; + if (predicate.call(thisArg, kValue, k, o)) { + return k; + } + // e. Increase k by 1. + k++; + } - if (cat.current.hasValidSchema) { - makeForm(cat, cat.current.config); - } - } else if (cat.current.settingsView == 'form') { - //this submits the form which: - //- saves the current object - //- updates the hidden text view - //$(".settingsForm form").trigger("submit"); - //get settings object from form - cat.current.config = cat.current.form.getData(); - //update settings text field to match form - cat.controls.settingsInput.node().value = JSON.stringify(cat.current.config, null, 4); + // 7. Return -1. + return -1; + } + }); + } + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + + var slicedToArray = function () { + function sliceIterator(arr, i) { + var _arr = []; + var _n = true; + var _d = false; + var _e = undefined; + + try { + for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { + _arr.push(_s.value); + + if (i && _arr.length === i) break; } - } - - /*------------------------------------------------------------------------------------------------\ - Define controls object. - \------------------------------------------------------------------------------------------------*/ - - var settings = { - set: set$1, - sync: sync, - setStatus: setStatus - }; - - function chartCreateStatus(statusDiv, main, sub) { - var message = sub - ? 'Created the chart by calling ' + main + '.' + sub + '().' - : 'Created the chart by calling ' + main + '().'; - - statusDiv - .append('div') - .html(message) - .classed('info', true); - } - - function chartInitStatus(statusDiv, success, err, htmlExport) { - if (success) { - //hide all non-error statuses - statusDiv.selectAll('div:not(.error)').classed('hidden', true); - - // Print basic success message - statusDiv - .append('div') - .attr('class', 'initSuccess') - .html( - "All Done. Your chart should be below. Show full log" - ) - .classed('info', true); - - //Click to show all statuses - statusDiv - .select('div.initSuccess') - .select('span.showLog') - .style('cursor', 'pointer') - .style('text-decoration', 'underline') - .style('float', 'right') - .on('click', function() { - d3.select(this).remove(); - statusDiv.selectAll('div').classed('hidden', false); - }); - - //generic caution (hidden by default) - statusDiv - .append('div') - .classed('hidden', true) - .classed('info', true) - .html( - "ⓘ Just because there are no errors doesn't mean there can't be problems. If things look strange, it might be a problem with the settings/data combo or with the renderer itself." - ); - - //export source code (via copy/paste) - statusDiv - .append('div') - .classed('hidden', true) - .classed('export', true) - .classed('minimized', true) - .html("Click to see chart's full source code"); - - statusDiv.select('div.export.minimized').on('click', function() { - d3.select(this).classed('minimized', false); - d3.select(this).html('Source code for chart:'); - d3 - .select(this) - .append('code') - .html( - htmlExport - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/\n/g, '
') - .replace(/ /g, ' ') - ); - }); - } else { - //if init fails (success == false) - statusDiv - .append('div') - .html( - "There might've been some problems initializing the chart. Errors include:
" + - err + - '' - ) - .classed('error', true); + } catch (err) { + _d = true; + _e = err; + } finally { + try { + if (!_n && _i["return"]) _i["return"](); + } finally { + if (_d) throw _e; } - } + } - function saveToServer(cat) { - var serverDiv = cat.statusDiv - .append('div') - .attr('class', 'info') - .text('Enter your name and click save for a reusable URL. '); - var nameInput = serverDiv.append('input').property('placeholder', 'Name'); - var saveButton = serverDiv - .append('button') - .text('Save') - .property('disabled', true); - - nameInput.on('input', function() { - saveButton.property('disabled', nameInput.node().value.length == 0); - }); - - saveButton.on('click', function() { - //remove the form - d3.select(this).remove(); - nameInput.remove(); - - //format an object for the post - var dataFile = cat.controls.dataFileSelect.node().value; - var dataFilePath = cat.config.dataURL + dataFile; - var chartObj = { - name: nameInput.node().value, - renderer: cat.current.name, - version: cat.controls.versionSelect.node().value, - dataFile: dataFilePath, - chart: btoa(cat.current.htmlExport) - }; - - //post the object, get a URL back - $.post('./export/', chartObj, function(data) { - serverDiv.html("Chart saved as " + data.url + ''); - }).fail(function() { - serverDiv.text("Sorry. Couldn't save the chart.").classed('error', true); - console.warn('Error :( Something went wrong saving the chart.'); - }); - }); + return _arr; } - function loadStatus(statusDiv, passed, path, library, version) { - var message = passed ? 'Successfully loaded ' + path : 'Failed to load ' + path; - - if ((library != undefined) & (version != undefined)) - message = message + ' (Library: ' + library + ', Version: ' + version + ')'; - - statusDiv - .append('div') - .html(message) - .classed('error', !passed); - } - - /*------------------------------------------------------------------------------------------------\ - Define controls object. - \------------------------------------------------------------------------------------------------*/ - - var status = { - chartCreateStatus: chartCreateStatus, - chartInitStatus: chartInitStatus, - saveToServer: saveToServer, - loadStatus: loadStatus + return function (arr, i) { + if (Array.isArray(arr)) { + return arr; + } else if (Symbol.iterator in Object(arr)) { + return sliceIterator(arr, i); + } else { + throw new TypeError("Invalid attempt to destructure non-iterable instance"); + } }; - - function createCat() { - var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'body'; - var config = arguments[1]; - - var cat = { - element: element, - config: config, - init: init, - layout: layout, - controls: controls, - setDefaults: setDefaults, - settings: settings, - status: status - }; - - return cat; - } - - var index = { - createCat: createCat - }; - - return index; -}); + }(); + + function getVersions(repo) { + var apiURL = this.config.apiURL + '/' + repo; + var branches = fetch(apiURL + '/branches').then(function (response) { + return response.json(); + }); + var releases = fetch(apiURL + '/releases').then(function (response) { + return response.json(); + }); + + return Promise.all([branches, releases]).then(function (values) { + var _values = slicedToArray(values, 2), + branches = _values[0], + releases = _values[1]; + + branches.sort(function (a, b) { + return a.name === 'master' ? -1 : b.name === 'master' ? 1 : a.name < b.name ? -1 : 1; + }); + return d3.merge(values); + }).catch(function (err) { + console.log(err); + }); + } + + function updateSelect(select, data) { + select.selectAll('option').data(data).enter().append('option').text(function (d) { + return d.label; + }); + } + + function loadPackageJSON(repo, version) { + var cdnURL = this.config.cdnURL + '/' + repo; + var pkgURL = version === 'master' ? cdnURL + '/package.json' : cdnURL + '@' + version + '/package.json'; + + return new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', pkgURL); + xhr.onload = function () { + if (this.status === 200) { + resolve(xhr.response); + } else { + reject({ + status: this.status, + statusTxt: xhr.statusText + }); + } + }; + xhr.onerror = function () { + reject({ + status: this.status, + statusText: xhr.statusText + }); + }; + xhr.send(); + }); + } + + // Nice script loader from here: https://stackoverflow.com/questions/538745/how-to-tell-if-a-script-tag-failed-to-load + + function scriptLoader() {} + + scriptLoader.prototype = { + timer: function timer(times, // number of times to try + delay, // delay per try + delayMore, // extra delay per try (additional to delay) + test, // called each try, timer stops if this returns true + failure, // called on failure + result // used internally, shouldn't be passed + ) { + var me = this; + if (times == -1 || times > 0) { + setTimeout(function () { + result = test() ? 1 : 0; + me.timer(result ? 0 : times > 0 ? --times : times, delay + (delayMore ? delayMore : 0), delayMore, test, failure, result); + }, result || delay < 0 ? 0.1 : delay); + } else if (typeof failure == 'function') { + setTimeout(failure, 1); + } + }, + + addEvent: function addEvent(el, eventName, eventFunc) { + if ((typeof el === 'undefined' ? 'undefined' : _typeof(el)) != 'object') { + return false; + } + + if (el.addEventListener) { + el.addEventListener(eventName, eventFunc, false); + return true; + } + + if (el.attachEvent) { + el.attachEvent('on' + eventName, eventFunc); + return true; + } + + return false; + }, + + // add script to dom + require: function require(url, args) { + var me = this; + args = args || {}; + + var scriptTag = document.createElement('script'); + var headTag = document.getElementsByTagName('head')[0]; + if (!headTag) { + return false; + } + + setTimeout(function () { + var f = typeof args.success == 'function' ? args.success : function () {}; + args.failure = typeof args.failure == 'function' ? args.failure : function () {}; + var fail = function fail() { + if (!scriptTag.__es) { + scriptTag.__es = true; + scriptTag.id = 'failed'; + args.failure(scriptTag); + } + }; + scriptTag.onload = function () { + scriptTag.id = 'loaded'; + f(scriptTag); + }; + scriptTag.type = 'text/javascript'; + scriptTag.async = typeof args.async == 'boolean' ? args.async : false; + scriptTag.charset = 'utf-8'; + me.__es = false; + me.addEvent(scriptTag, 'error', fail); // when supported + // when error event is not supported fall back to timer + me.timer(15, 1000, 0, function () { + return scriptTag.id == 'loaded'; + }, function () { + if (scriptTag.id != 'loaded') { + fail(); + } + }); + scriptTag.src = url; + setTimeout(function () { + try { + headTag.appendChild(scriptTag); + } catch (e) { + fail(); + } + }, 1); + }, typeof args.delay == 'number' ? args.delay : 1); + return scriptTag; + } + }; + + function loadFiles(repo, pkg, branch, css) { + var version = branch || pkg.version; + var cdnURL = this.config.cdnURL + '/' + repo; + + //Load .css file + if (css) { + var cssURL = version === 'master' ? cdnURL + '/' + css : cdnURL + '@' + version + '/' + css; + var link = document.createElement('link'); + link.href = cssURL; + link.type = 'text/css'; + link.rel = 'stylesheet'; + document.getElementsByTagName('head')[0].appendChild(link); + } + + //Load .js file + var jsURL = version === 'master' ? cdnURL + '/' + pkg.main.replace(/^\.?\/?/, '') : cdnURL + '@' + version + '/' + pkg.main.replace(/^\.?\/?/, ''); + + var loader = new scriptLoader(); + var script = loader.require(jsURL, { + async: true, + success: function success() { + console.log('Loaded ' + jsURL + '.'); + }, + failure: function failure() { + console.warn('Failed to load ' + jsURL + '.'); + } + }); + } + + function updateFields(version) { + var _this = this; + + this.controls.mainFunction.node().value = this.chartingApplication.main; + this.controls.subFunction.node().value = this.chartingApplication.sub; + this.controls.schema.node().value = this.chartingApplication.schema; + this.controls.dataFileSelect.selectAll('option').property('selected', function (d) { + return _this.chartingApplication.defaultData === d.label; + }); + } + + var utilities = { + getVersions: getVersions, + updateSelect: updateSelect, + loadPackageJSON: loadPackageJSON, + loadFiles: loadFiles, + scriptLoader: scriptLoader, + updateFields: updateFields + }; + + var defaultSettings = { + useServer: false, + rootURL: 'https://github.com/RhoInc', + dataURL: 'https://raw.githubusercontent.com/RhoInc/data-library/master/data/', + chartingLibrary: { + name: 'Webcharts', + css: 'css/webcharts.css', + versions: [] + }, + renderers: [], + dataFiles: [] + }; + + function setDefaults() { + var _this = this; + + this.config.useServer = this.config.useServer || defaultSettings.useServer; + this.config.rootURL = this.config.rootURL || defaultSettings.rootURL; + this.config.apiURL = this.config.rootURL.replace('github.com', 'api.github.com/repos'); + this.config.cdnURL = this.config.rootURL.replace('github.com', 'cdn.jsdelivr.net/gh'); + this.config.dataURL = this.config.dataURL || defaultSettings.dataURL; + this.config.chartingLibrary = this.config.chartingLibrary || defaultSettings.chartingLibrary; + this.config.renderers = this.config.renderers || defaultSettings.renderers; + this.config.dataFiles = this.config.dataFiles || defaultSettings.dataFiles; + + this.config.renderers.forEach(function (renderer) { + renderer.label = renderer.label || renderer.name; + }); + + this.config.dataFiles = this.config.dataFiles.map(function (df) { + return typeof df === 'string' ? { label: df, path: _this.config.dataURL, user_loaded: false } : df; + }); + } + + function toggleDisplayOfControls() { + var _this = this; + + var styleSheet = Array.from(document.styleSheets).find(function (styleSheet) { + return styleSheet.href.indexOf('cat.css') > -1; + }); + var controlsWidth = Array.from(styleSheet.cssRules).find(function (cssRule) { + return cssRule.selectorText === '.cat-wrap .cat-controls'; + }).style.width; + + //Hide controls. + this.hideControls.on('click', function () { + _this.controls.wrap.classed('hidden', true); + _this.chartWrap.style('margin-left', 0); + _this.chartWrap.selectAll('.wc-chart').each(function (d) { + try { + d.draw(); + } catch (error) {} + }); + _this.dataWrap.style('margin-left', 0); + _this.hideControls.classed('hidden', true); + _this.showControls.classed('hidden', false); + }); + + //Show controls. + this.showControls.on('click', function () { + _this.controls.wrap.classed('hidden', false); + _this.chartWrap.style('margin-left', controlsWidth); + _this.chartWrap.selectAll('.wc-chart').each(function (d) { + try { + d.draw(); + } catch (error) {} + }); + _this.dataWrap.style('margin-left', controlsWidth); + _this.hideControls.classed('hidden', false); + _this.showControls.classed('hidden', true); + }); + } + + function renderChart() { + this.controls.submitWrap = this.controls.wrap.append('div').classed('control-section submit-section', true); + this.controls.submitButton = this.controls.submitWrap.append('button').attr('class', 'submit').text('Render Chart'); + } + + function library() { + this.controls.rendererWrap.append('span').text('Library: '); + this.controls.rendererSelect = this.controls.rendererWrap.append('select'); + this.controls.rendererWrap.append('br'); + } + + function version() { + this.controls.rendererWrap.append('span').text('Version: '); + this.controls.versionSelect = this.controls.rendererWrap.append('select'); + this.controls.rendererWrap.append('br'); + } + + function init() { + this.controls.rendererWrap.append('span').text(' Init: '); + //.classed('hidden', true); + this.controls.mainFunction = this.controls.rendererWrap.append('input'); //.classed('hidden', true); + this.controls.rendererWrap.append('span').text('.'); + //.classed('hidden', true); + this.controls.subFunction = this.controls.rendererWrap.append('input'); //.classed('hidden', true); + this.controls.rendererWrap.append('br'); //.classed('hidden', true); + } + + function webchartsVersion() { + this.controls.rendererWrap.append('span').text('Webcharts Version: '); + //.classed('hidden', true); + this.controls.libraryVersion = this.controls.rendererWrap.append('select'); + //.classed('hidden', true); + this.controls.rendererWrap.append('br'); //.classed('hidden', true); + } + + function schema() { + this.controls.rendererWrap.append('span').text('Schema: '); + //.classed('hidden', true); + this.controls.schema = this.controls.rendererWrap.append('input'); //.classed('hidden', true); + this.controls.rendererWrap.append('br'); //.classed('hidden', true); + } + + function chooseAChartingLibrary() { + this.controls.rendererWrap = this.controls.wrap.append('div').classed('control-section renderer-section', true); + this.controls.rendererWrap.append('h3').text('1. Choose a Charting Library'); + library.call(this); + version.call(this); + //moreOptions.call(this); + init.call(this); + schema.call(this); + webchartsVersion.call(this); + } + + function dataFile() { + this.controls.dataFileSelect = this.controls.dataWrap.append('select'); + } + + function loadADataFile() { + var loadLabel = this.controls.dataWrap.append('p').style('margin', 0); + loadLabel.append('small').text('Use local .csv file:').append('sup').html('ⓘ').property('title', 'Render a chart using a local file. File is added to the data set list, and is only available for a single session and is not saved.').style('cursor', 'help'); + this.controls.loadStatus = loadLabel.append('small').attr('class', 'loadStatus').style('float', 'right').text('Select a csv to load'); + this.controls.dataFileLoad = this.controls.dataWrap.append('input').attr('type', 'file').attr('class', 'file-load-input'); + this.controls.dataFileLoadButton = this.controls.dataWrap.append('button').text('Load').attr('class', 'file-load-button').attr('disabled', true); + } + + function chooseADataset() { + this.controls.dataWrap = this.controls.wrap.append('div').classed('control-section data-section', true); + this.controls.dataWrap.append('h3').text('2. Choose a Dataset'); + dataFile.call(this); + this.controls.viewData = this.controls.dataWrap.append('span').html('🔍').style('cursor', 'pointer'); + loadADataFile.call(this); + } + + function customizeTheChart() { + this.controls.settingsWrap = this.controls.wrap.append('div').classed('control-section settings-section', true); + this.controls.settingsWrap.append('h3').html('3. Customize the Chart '); + this.controls.settingsWrap.append('span').text('Settings: '); + this.controls.settingsTypeText = this.controls.settingsWrap.append('input').attr('class', 'radio').property('type', 'radio').property('name', 'settingsType').property('value', 'text'); + this.controls.settingsWrap.append('span').text('text'); + this.controls.settingsTypeForm = this.controls.settingsWrap.append('input').attr('class', 'radio').property('type', 'radio').property('name', 'settingsType').property('value', 'form'); + this.controls.settingsWrap.append('span').text('form'); + this.controls.settingsType = this.controls.settingsWrap.selectAll('input[type="radio"]'); + this.controls.settingsWrap.append('br'); + this.controls.settingsInput = this.controls.settingsWrap.append('textarea').attr('rows', 10).style('width', '90%').text('{}'); + this.controls.settingsForm = this.controls.settingsWrap.append('div').attr('class', 'settingsForm').append('form'); + } + + function environment() { + this.controls.environmentWrap = this.controls.wrap.append('div').classed('control-section environment-section', true); + this.controls.environmentWrap.append('h3').html('4. Environment '); + this.controls.cssList = this.controls.environmentWrap.append('ul').attr('class', 'cssList'); + this.controls.cssList.append('h5').text('Loaded Stylesheets'); + this.controls.jsList = this.controls.environmentWrap.append('ul').attr('class', 'jsList'); + this.controls.jsList.append('h5').text('Loaded javascript'); + } + + function controls() { + this.controls.wrap.append('h2').classed('cat-controls-header', true).text('Charting Application Tester 😼'); + renderChart.call(this); + chooseAChartingLibrary.call(this); + chooseADataset.call(this); + customizeTheChart.call(this); + environment.call(this); + } + + function layout() { + this.wrap = d3.select(this.element).append('div').attr('class', 'cat-wrap'); + + //Controls display toggle + this.hideControls = this.wrap.append('div').classed('cat-button cat-button--hide-controls', true).attr('title', 'Hide controls').text('<<'); + this.showControls = this.wrap.append('div').classed('cat-button cat-button--show-controls hidden', true).attr('title', 'Show controls').text('>>'); + toggleDisplayOfControls.call(this); + + //Controls + this.controls.wrap = this.wrap.append('div').classed('cat-controls section', true); + controls.call(this); + + //Chart + this.chartWrap = this.wrap.append('div').classed('cat-chart section', true); + + //Table + this.dataWrap = this.wrap.append('div').classed('cat-data section', true).classed('hidden', true); + } + + function loadData() { + this.utilities.updateSelect.call(this, this.controls.rendererSelect, this.config.renderers); + this.utilities.updateSelect.call(this, this.controls.dataFileSelect, this.config.dataFiles); + } + + function loadChartingLibrary() { + var _this = this; + + this.utilities.getVersions.call(this, this.config.chartingLibrary.name).then(function (versions) { + _this.config.chartingLibrary.versions = versions.map(function (version) { + version.label = versions.tag_name ? version.tag_name : version.name; + return version; + }); + _this.utilities.updateSelect.call(_this, _this.controls.libraryVersion, _this.config.chartingLibrary.versions); + }); + this.utilities.loadPackageJSON.call(this, this.config.chartingLibrary.name, 'master').then(function (pkg) { + _this.config.chartingLibrary.pkg = JSON.parse(pkg); + _this.utilities.loadFiles.call(_this, _this.config.chartingLibrary.name, _this.config.chartingLibrary.pkg, 'master', _this.config.chartingLibrary.css); + }); + } + + function loadChartingApplication() { + var _this = this; + + this.chartingApplication = this.config.renderers[0]; + this.utilities.getVersions.call(this, this.chartingApplication.name).then(function (versions) { + _this.chartingApplication.versions = versions.map(function (version) { + version.label = versions.tag_name ? version.tag_name : version.name; + return version; + }); + _this.utilities.updateSelect.call(_this, _this.controls.versionSelect, _this.chartingApplication.versions); + }); + this.utilities.loadPackageJSON.call(this, this.chartingApplication.name, 'master').then(function (pkg) { + _this.chartingApplication.pkg = JSON.parse(pkg); + _this.utilities.loadFiles.call(_this, _this.chartingApplication.name, _this.chartingApplication.pkg, 'master', _this.chartingApplication.css); + _this.utilities.updateFields.call(_this, _this.chartingApplication.main, _this.chartingApplication.sub, _this.chartingApplication.schema); + }); + } + + function destroyChart() { + if (this.chartingApplicationInstance) { + if (this.chartingApplicationInstance && this.chartingApplicationInstance.destroy) { + if (this.chartingApplicationInstance && this.chartingApplicationInstance.destroy) this.chartingApplicationInstance.destroy(); + } else { + this.chartWrap.selectAll('.wc-chart').each(function (chart) { + if (chart.destroy) chart.destroy();else { + //remove resize event listener + select(window).on('resize.' + chart.element + chart.id, null); + + //destroy controls + if (chart.controls) { + chart.controls.destroy(); + } + + //unmount chart wrapper + chart.wrap.remove(); + } + }); + } + } + + this.chartWrap.selectAll('*').remove(); + } + + function loadData$1() { + var dataFile = this.controls.dataFileSelect.node().value; + this.dataObject = this.config.dataFiles.find(function (f) { + return f.label == dataFile; + }); + this.dataObject.dataFilePath = this.dataObject.path + dataFile; + return fetch(this.dataObject.dataFilePath).then(function (response) { + return response.text(); + }).then(function (text) { + return d3.csv.parse(text); + }); + } + + function initializeChart(data) { + this.chartWrap.append('div').attr('class', 'chart'); + + //Pass element and settings to charting application. + if (this.chartingApplication.sub) { + this.chartingApplicationInstance = window[this.chartingApplication.main][this.chartingApplication.sub]('.cat-chart .chart', this.chartingApplication.config || {}); + } else { + this.chartingApplicationInstance = window[this.chartingApplication.main]('.cat-chart .chart', this.chartingApplication.config || {}); + } + + //Pass data to charting application. + try { + this.chartingApplicationInstance.init(data); + } catch (err) { + console.warn(err); + } + } + + /* + 1. Destroys the currently displayed chart if one has been rendered. + 2. Loads the selected data file. + 3. Initializes the selected charting application library. + */ + + function renderChart$1() { + var _this = this; + + this.controls.submitButton.on('click', function () { + _this.dataWrap.classed('hidden', true); + _this.chartWrap.classed('hidden', false); + destroyChart.call(_this); + loadData$1.call(_this).then(function (json) { + initializeChart.call(_this, json); + }); + }); + } + + function changeLibrary() { + var cat = this; + + this.controls.rendererSelect.on('change', function (d) { + cat.chartingApplication = d3.select(this).selectAll('option:checked').datum(); + cat.chartingApplication.version = 'master'; + cat.controls.versionSelect.selectAll('option').property('selected', function (d) { + return d.label === cat.chartingApplication.version; + }); + cat.utilities.getVersions.call(cat, cat.chartingApplication.name).then(function (versions) { + cat.chartingApplication.versions = versions.map(function (version) { + version.label = versions.tag_name ? version.tag_name : version.name; + return version; + }); + cat.utilities.updateSelect.call(cat, cat.controls.versionSelect, cat.chartingApplication.versions); + }); + cat.utilities.loadPackageJSON.call(cat, cat.chartingApplication.name, 'master').then(function (pkg) { + cat.chartingApplication.pkg = JSON.parse(pkg); + cat.utilities.loadFiles.call(cat, cat.chartingApplication.name, cat.chartingApplication.pkg, 'master', cat.chartingApplication.css); + cat.utilities.updateFields.call(cat, cat.chartingApplication.main, cat.chartingApplication.sub, cat.chartingApplication.schema); + }); + }); + } + + function changeLibraryVersion() { + var cat = this; + + this.controls.versionSelect.on('change', function (d) { + cat.chartingApplication.version = d3.select(this).selectAll('option:checked').datum().label; + cat.utilities.loadPackageJSON.call(cat, cat.chartingApplication.name, cat.chartingApplication.version).then(function (pkg) { + cat.chartingApplication.pkg = JSON.parse(pkg); + cat.utilities.loadFiles.call(cat, cat.chartingApplication.name, cat.chartingApplication.pkg, cat.chartingApplication.version, cat.chartingApplication.css); + cat.utilities.updateFields.call(cat, cat.chartingApplication.main, cat.chartingApplication.sub, cat.chartingApplication.schema); + }); + }); + } + + function initializeControls() { + renderChart$1.call(this); + changeLibrary.call(this); + changeLibraryVersion.call(this); + } + + function init$1() { + //settings + setDefaults.call(this); + + //layout + layout.call(this); + + //renderers and data files + loadData.call(this); + + //load charting library + loadChartingLibrary.call(this); + + //load charting application + loadChartingApplication.call(this); + + //initialize controls + initializeControls.call(this); + } + + //import controls from './cat/controls'; + //import settings from './cat/settings'; + //import status from './cat/status'; + + function createCat() { + var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'body'; + var config = arguments[1]; + + var cat = { + element: element, + config: config, + utilities: utilities, + init: init$1, + controls: {} + //settings, + //status + }; + + return cat; + } + + var index = { + createCat: createCat + }; + + return index; + +}))); diff --git a/css/cat.css b/css/cat.css index 687ef60..b6ab4b9 100644 --- a/css/cat.css +++ b/css/cat.css @@ -1,44 +1,51 @@ /* general */ -.hidden{ - display:none +.hidden { + display: none } -.cat-wrap .cat-controls{ - position: relatvie; - width:20%; +.cat-wrap { + position: relative; +} + +.cat-wrap .cat-controls { + width: 25%; top: 0; - bottom:0; - position:fixed; - overflow-y:scroll; - overflow-x:scroll; - background:#99badd; - padding:0.5em; - margin:0; - border:1px solid #999; -} - -.cat-wrap .cat-chart, .cat-wrap .cat-data{ - margin-left:20%; + bottom: 0; + position: fixed; + overflow-y: scroll; + overflow-x: scroll; + background: #99badd; + padding: 0.5em; + margin: 0; + border: 1px solid #999; +} + +.cat-wrap .cat-chart, .cat-wrap .cat-data { + margin-left: 25%; padding: 0 3em; } -.cat-wrap .cat-controls.hidden{ - display:none; +.cat-wrap .cat-controls.hidden { + display: none; +} + +.cat-wrap .cat-controls .cat-controls-header { + text-align: center; } -.cat-wrap .cat-controls h2{ - margin-top:0.1em; - margin-bottom:0.2em; - font-size:1.3em; +.cat-wrap .cat-controls h2 { + margin-top: 0.1em; + margin-bottom: 0.2em; + font-size: 1.3em; } -.cat-wrap .cat-controls h3{ - margin-bottom:0; - margin-top:0.2em; +.cat-wrap .cat-controls h3 { + margin-bottom: 0; + margin-top: 0.2em; } -.cat-wrap .cat-controls div{ - margin-bottom:.5em; +.cat-wrap .cat-controls div { + margin-bottom: .5em; } .cat-wrap .submit-section { @@ -47,45 +54,49 @@ position: relative; } .cat-wrap .cat-button { - position: absolute; - left: 0; - top: 5px; + position: fixed; + top: 10px; + left: 12px; vertical-align: middle; background: white; cursor: pointer; padding: 0 5px; - font-size:1em; - border:2px solid #7BAFD4; + font-size: 1em; + border: 2px solid #7BAFD4; border-radius: 2px; } -.cat-wrap .cat-button--maximize { - left: .75em; - top: 10px; +.cat-wrap .cat-button:hover { + background: #7BAFD4; + border: 2px solid black; } -.cat-wrap .cat-controls button.submit{ +.cat-wrap .cat-controls button.submit { cursor: pointer; margin: 0 auto; - display:block; - color:black; - border:none; - border-radius:3px; + display: block; + color: black; + border: none; + border-radius: 3px; padding: 5px 10px; - font-size:1em; - background-color:white; - border:2px solid #7BAFD4; + font-size: 1em; + background-color: white; + border: 2px solid #7BAFD4; +} + +.cat-wrap .cat-controls button.submit:hover { + background-color: #7BAFD4; + color: white; } -.cat-wrap .cat-controls button.submit:hover{ - background-color:#7BAFD4; - color:white; +.cat-wrap .cat-controls .data-section select { + max-width: 100%; } -.cat-wrap .cat-controls .file-load-input{ - width:75% +.cat-wrap .cat-controls .data-section .file-load-input { + width: 75% } -.cat-wrap .cat-controls .file-load-button{ - width:25% +.cat-wrap .cat-controls .data-section .file-load-button { + width: 25% } @@ -96,16 +107,16 @@ rows: 10; width: 100%; } -.cat-wrap .cat-controls .settings-section .prop-value{ - vertical-align:top; - padding-bottom:0.1em; +.cat-wrap .cat-controls .settings-section .prop-value { + vertical-align: top; + padding-bottom: 0.1em; } /* status formatting */ -.cat-wrap .cat-chart .status div{ +.cat-wrap .cat-chart .status div { padding: 3px; margin-bottom: 4px; border: 1px solid transparent; @@ -114,48 +125,48 @@ background-color: #dff0d8; border-color: #d6e9c6; } -.cat-wrap .cat-chart .status{ - padding-bottom:.5em; - margin-bottom:.5em; - border-bottom:1px dotted #999; +.cat-wrap .cat-chart .status { + padding-bottom: .5em; + margin-bottom: .5em; + border-bottom: 1px dotted #999; } -.cat-wrap .cat-chart .status div.error{ +.cat-wrap .cat-chart .status div.error { color: #a94442; background-color: #f2dede; border-color: #ebccd1; } -.cat-wrap .cat-chart .status div.info{ +.cat-wrap .cat-chart .status div.info { color: black; background-color: white; border-color: black; } -.cat-wrap .cat-chart .status div.export{ - font-size:0.6em; +.cat-wrap .cat-chart .status div.export { + font-size: 0.6em; color: black; background-color: #ffffe5; border-color: black; } -.cat-wrap .cat-chart .status div.export.minimized{ - font-size:1.0em; +.cat-wrap .cat-chart .status div.export.minimized { + font-size: 1.0em; } -.cat-wrap .environment-section ul{ +.cat-wrap .environment-section ul { list-style: none; padding-left: 0.1em; } -.cat-wrap .environment-section ul h5{ - margin:0; +.cat-wrap .environment-section ul h5 { + margin: 0; } /********************************************************************** ** Sliders from https://www.w3schools.com/howto/howto_css_switch.asp ** **********************************************************************/ /**/ .switch { - margin-left:0.5em; + margin-left: 0.5em; position: relative; display: inline-block; width: 30px; @@ -166,7 +177,7 @@ } /* Hide default HTML checkbox */ -.switch input {display:none;} +.switch input {display: none;} /* The slider */ .slider { diff --git a/index.html b/index.html index 4b9e1f8..aada08e 100644 --- a/index.html +++ b/index.html @@ -11,6 +11,7 @@ + @@ -24,6 +25,7 @@ + diff --git a/index.js b/index.js index 573c7cd..692a193 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,6 @@ -var myCatConfig = { +const myCatConfig = { useServer: false, - rootURL: 'https://cdn.jsdelivr.net/gh/RhoInc', - dataURL: 'https://raw.githubusercontent.com/RhoInc/data-library/master/data/', + repoURL: '../graphics/data', renderers: [ { name: 'web-codebook', @@ -82,13 +81,13 @@ var myCatConfig = { defaultData: 'clinical-trials/renderer-specific/adbds.csv' }, { - name: 'safety-eDISH', + name: 'hep-explorer', main: 'safetyedish', sub: null, css: null, schema: 'settings-schema.json', defaultData: 'clinical-trials/renderer-specific/adbds.csv', - rootURL: 'https://cdn.jsdelivr.net/gh/ASA-DIA-InteractiveSafetyGraphics', + rootURL: 'https://cdn.jsdelivr.net/gh/SafetyGraphics', }, /**-------------------------------------------------------------------------------------------\ @@ -135,10 +134,38 @@ var myCatConfig = { ] }; -myCatConfig.dataFiles = dataFiles.map(function(m){ - return m.rel_path.slice(7) -}); +//Modify renderer objects. +//myCatConfig.renderers +// .forEach(function(renderer) { +// renderer.rootURL = renderer.rootURL || myCatConfig.rootURL; +// renderer.api_url = renderer.rootURL.replace('cdn.jsdelivr.net/gh', 'api.github.com/repos') + '/' + renderer.name; +// renderer.branches_api_url = renderer.api_url + '/branches'; +// renderer.releases_api_url = renderer.api_url + '/releases'; +// }); -var myCat = cat.createCat('body',myCatConfig) +//Map data file objects to a path relative to myCatConfig.dataURL. +myCatConfig.dataFiles = dataFiles + .map(function(dataFile) { + return dataFile.rel_path.slice(7); + }); -myCat.init() +//Instantiate CAT. +const myCat = cat.createCat('body',myCatConfig); + +//Read in repository data from graphics repo. +Promise + .all([ + fetch(`${myCat.config.repoURL}/repos.json`), + fetch(`${myCat.config.repoURL}/releases.json`), + fetch(`${myCat.config.repoURL}/branches.json`), + ]) + .then(responses => Promise.all(responses.map(response => response.json()))) + .then(json => { + const [repos,releases,branches] = json; + myCat.repos = repos; + myCat.releases = releases; + myCat.branches = branches; + + //Initialize CAT. + myCat.init(); + }); diff --git a/package-lock.json b/package-lock.json index 662c99c..c1b0f54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,23 @@ { "name": "cat", - "version": "0.9.0", + "version": "0.10.0", "lockfileVersion": 1, "requires": true, "dependencies": { + "@handsontable/formulajs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@handsontable/formulajs/-/formulajs-2.0.1.tgz", + "integrity": "sha512-jTdJO/6ZmuaHoiTdnraGbPkdnA7m0VMrZ54vWXi22WpwnsIKAWbqjWTwvDoSuEpcc7/YHVIVlSDtfXHKmaYhdQ==", + "requires": { + "@handsontable/jstat": "^1.0.0", + "bessel": "^0.2.0" + } + }, + "@handsontable/jstat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@handsontable/jstat/-/jstat-1.0.0.tgz", + "integrity": "sha512-5XxZ9xIk6iSjrc1p5N/yI2dofBXp0IzZVgrkETDC196SxoJCRNOeKgM9fTHMhoxa02wuaZPLp6stojlppNxP/A==" + }, "@types/estree": { "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", @@ -16,6 +30,14 @@ "integrity": "sha512-i1sl+WCX2OCHeUi9oi7PiCNUtYFrpWhpcx878vpeq/tlZTKzcFdHePlyFHVbWqeuKN0SRPl/9ZFDSTsfv9h7VQ==", "dev": true }, + "@types/pikaday": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@types/pikaday/-/pikaday-1.6.0.tgz", + "integrity": "sha512-cnKjF7i6oA1ADxQdSWHcEStLZeiH8qbf6l7B9O88PhLgnmbUMM62ali0/PaDtINm6ezpNcqtERWL6Y+pAgHKQQ==", + "requires": { + "moment": ">=2.14.0" + } + }, "abab": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", @@ -754,6 +776,14 @@ "tweetnacl": "^0.14.3" } }, + "bessel": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bessel/-/bessel-0.2.0.tgz", + "integrity": "sha1-E8s5zSkjMhnsLacl4LoMZvtGtvI=", + "requires": { + "voc": "^1.1.0" + } + }, "bignumber.js": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", @@ -1408,6 +1438,26 @@ "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", "dev": true }, + "handsontable": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/handsontable/-/handsontable-7.0.3.tgz", + "integrity": "sha512-CRwrI6VFcNhTSiTtIrLhdofuOBb9itNmbUsl4ntlu61qnEpyRBqT7Vv5wT+icRtBlq4czlYIWKMz8GGZe3e2ow==", + "requires": { + "@types/pikaday": "1.6.0", + "core-js": "^3.0.0", + "hot-formula-parser": "^3.0.1", + "moment": "2.20.1", + "numbro": "2.1.1", + "pikaday": "1.5.1" + }, + "dependencies": { + "core-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.1.2.tgz", + "integrity": "sha512-3poRGjbu56leCtZCZCzCgQ7GcKOflDFnjWIepaPFUsM0IXUBrne10sl3aa2Bkcz3+FjRdIxBe9dAMhIJmEnQNA==" + } + } + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -1443,6 +1493,15 @@ "os-tmpdir": "^1.0.1" } }, + "hot-formula-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hot-formula-parser/-/hot-formula-parser-3.0.1.tgz", + "integrity": "sha512-QhYPVlVh/GF/hHtBp+MwgDp5kpgrrjeJi3d3/GxTWtqwLBOOM4KlZT/YWcsfZj5JE68MNvFgj3ZzYpkGyvGtwA==", + "requires": { + "@handsontable/formulajs": "^2.0.1", + "tiny-emitter": "^2.0.1" + } + }, "http-errors": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", @@ -1590,9 +1649,9 @@ "dev": true }, "jquery": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", - "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", + "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" }, "js-tokens": { "version": "3.0.2", @@ -1852,6 +1911,11 @@ "minimist": "0.0.8" } }, + "moment": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", + "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1877,6 +1941,21 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, + "numbro": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/numbro/-/numbro-2.1.1.tgz", + "integrity": "sha512-H3VamlHyqYYomNngAbrl/CT92DnOSC2rJxx6hfZrgj0NVnqxAtOvGbwgpOYjv4ASgxodDWBSYHJ1ZxaEq2lfTg==", + "requires": { + "bignumber.js": "^4.0.4" + }, + "dependencies": { + "bignumber.js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.1.0.tgz", + "integrity": "sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA==" + } + } + }, "nwmatcher": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.3.tgz", @@ -2048,6 +2127,14 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "pikaday": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pikaday/-/pikaday-1.5.1.tgz", + "integrity": "sha1-CkhUm8GhTqHQjEQHTXYbwvK/z9M=", + "requires": { + "moment": "2.x" + } + }, "pixelmatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", @@ -2584,6 +2671,11 @@ "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", "dev": true }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "tinycolor2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", diff --git a/package.json b/package.json index c533dc1..8dada60 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cat", - "version": "0.9.1", + "version": "0.10.0", "description": "The Charting Application Tester (CAT) lets users make and adjust web graphics on the fly.", "module": "./src/index.js", "main": "./build/cat.js", @@ -12,17 +12,18 @@ "format-bundle": "prettier --print-width=100 --tab-width=4 --single-quote --write ./build/cat.js", "watch": "rollup -c -w", "start": "node server/app.js", - "initServer": "node server/initServer.js" + "init-server": "node server/initServer.js" }, "author": "Rho, Inc.", "license": "MIT", "dependencies": { + "body-parser": "~1", "d3": "^3.5.14", - "webcharts": "~1", - "jquery": "~3", - "json5": "^0.5.1", "express": "~4", - "body-parser": "~1" + "handsontable": "^7.0.3", + "jquery": "^3.4.1", + "json5": "^0.5.1", + "webcharts": "~1" }, "devDependencies": { "babel-plugin-external-helpers": "^6.22.0", diff --git a/src/cat/controls.js b/src/cat/controls.js index 27701fc..ff182aa 100644 --- a/src/cat/controls.js +++ b/src/cat/controls.js @@ -5,7 +5,7 @@ import { init } from './controls/init'; import addEnterEventListener from './addEnterEventListener'; -export const controls = { +export default { init, addEnterEventListener }; diff --git a/src/cat/controls/init.js b/src/cat/controls/init.js index 4774b73..c212131 100644 --- a/src/cat/controls/init.js +++ b/src/cat/controls/init.js @@ -5,13 +5,13 @@ import { initFileLoad } from './initFileLoad'; import { initChartConfig } from './initChartConfig'; import { initEnvConfig } from './initEnvConfig'; -export function init(cat) { - cat.current = cat.config.renderers[0]; - cat.current.version = 'master'; - initSubmit(cat); - initRendererSelect(cat); - initDataSelect(cat); - initFileLoad.call(cat); - initChartConfig(cat); - initEnvConfig(cat); +export function init() { + this.current = this.config.renderers[0]; + this.current.version = 'master'; + initSubmit.call(this); + initRendererSelect.call(this); + initDataSelect.call(this); + initFileLoad.call(this); + initChartConfig.call(this); + initEnvConfig.call(this); } diff --git a/src/cat/controls/initChartConfig.js b/src/cat/controls/initChartConfig.js index 0b3352f..fac82a1 100644 --- a/src/cat/controls/initChartConfig.js +++ b/src/cat/controls/initChartConfig.js @@ -1,40 +1,7 @@ -export function initChartConfig(cat) { - var settingsHeading = cat.controls.settingsWrap.append('h3').html('3. Customize the Chart '); +export function initChartConfig() { + const cat = this; - cat.controls.settingsWrap.append('span').text('Settings: '); - - /* - ////////////////////////////////////// - //initialize the config status icon - ////////////////////////////////////// - cat.controls.settingsStatus = settingsSection - .append("div") - .style("font-size", "1.5em") - .style("float", "right") - .style("cursor", "pointer"); - settingsSection.append("br"); -*/ - - ////////////////////////////////////////////////////////////////////// - //radio buttons to toggle between "text" and "form" based settings - ///////////////////////////////////////////////////////////////////// - cat.controls.settingsTypeText = cat.controls.settingsWrap - .append('input') - .attr('class', 'radio') - .property('type', 'radio') - .property('name', 'settingsType') - .property('value', 'text'); - cat.controls.settingsWrap.append('span').text('text'); - cat.controls.settingsTypeForm = cat.controls.settingsWrap - .append('input') - .attr('class', 'radio') - .property('type', 'radio') - .property('name', 'settingsType') - .property('value', 'form'); - cat.controls.settingsWrap.append('span').text('form'); - cat.controls.settingsType = cat.controls.settingsWrap.selectAll('input[type="radio"]'); - - cat.controls.settingsType.on('change', function(d) { + this.controls.settingsType.on('change', function(d) { cat.settings.sync(cat); //first sync the current settings to both views //then update to the new view, and update controls. @@ -47,25 +14,6 @@ export function initChartConfig(cat) { cat.controls.settingsForm.classed('hidden', false); } }); - cat.controls.settingsWrap.append('br'); - - ////////////////////////////////////////////////////////////////////// - //text input section - ///////////////////////////////////////////////////////////////////// - cat.controls.settingsInput = cat.controls.settingsWrap - .append('textarea') - .attr('rows', 10) - .style('width', '90%') - .text('{}'); - - ////////////////////////////////////////////////////////////////////// - //wrapper for the form - ///////////////////////////////////////////////////////////////////// - cat.controls.settingsForm = cat.controls.settingsWrap - .append('div') - .attr('class', 'settingsForm') - .append('form'); - //set the text/form settings for the first renderer - cat.settings.set(cat); + this.settings.set(this); } diff --git a/src/cat/controls/initDataSelect.js b/src/cat/controls/initDataSelect.js index f708266..53c88d3 100644 --- a/src/cat/controls/initDataSelect.js +++ b/src/cat/controls/initDataSelect.js @@ -1,24 +1,12 @@ import { showDataPreview } from '../datapreview/showDataPreview'; -export function initDataSelect(cat) { - cat.controls.dataWrap.append('h3').text('2. Choose a data Set'); - cat.controls.dataFileSelect = cat.controls.dataWrap.append('select'); - - cat.controls.dataWrap - .append('span') - .html('🔍') - .style('cursor', 'pointer') +export function initDataSelect() { + this.controls.viewData .on('click', function() { - showDataPreview(cat); + showDataPreview(this); }); - cat.controls.dataFileSelect + this.controls.dataFileSelect .selectAll('option') - .data(cat.config.dataFiles) - .enter() - .append('option') - .text(d => d.label) - .property('selected', function(d) { - return cat.current.defaultData == d.label ? true : null; - }); + .property('selected', d => this.current.defaultData === d); } diff --git a/src/cat/controls/initEnvConfig.js b/src/cat/controls/initEnvConfig.js index a3c9562..b07c841 100644 --- a/src/cat/controls/initEnvConfig.js +++ b/src/cat/controls/initEnvConfig.js @@ -1,13 +1,5 @@ import { showEnv } from '../env/showEnv'; -export function initEnvConfig(cat) { - var settingsHeading = cat.controls.environmentWrap.append('h3').html('4. Environment '); - - cat.controls.cssList = cat.controls.environmentWrap.append('ul').attr('class', 'cssList'); - cat.controls.cssList.append('h5').text('Loaded Stylesheets'); - - cat.controls.jsList = cat.controls.environmentWrap.append('ul').attr('class', 'jsList'); - cat.controls.jsList.append('h5').text('Loaded javascript'); - - showEnv(cat); +export function initEnvConfig() { + showEnv(this); } diff --git a/src/cat/controls/initFileLoad.js b/src/cat/controls/initFileLoad.js index 6bd9607..0a0f009 100644 --- a/src/cat/controls/initFileLoad.js +++ b/src/cat/controls/initFileLoad.js @@ -1,29 +1,7 @@ export function initFileLoad() { - var cat = this; - //draw the control - var loadLabel = cat.controls.dataWrap.append('p').style('margin', 0); + const cat = this; - loadLabel - .append('small') - .text('Use local .csv file:') - .append('sup') - .html('ⓘ') - .property( - 'title', - 'Render a chart using a local file. File is added to the data set list, and is only available for a single session and is not saved.' - ) - .style('cursor', 'help'); - - var loadStatus = loadLabel - .append('small') - .attr('class', 'loadStatus') - .style('float', 'right') - .text('Select a csv to load'); - - cat.controls.dataFileLoad = cat.controls.dataWrap - .append('input') - .attr('type', 'file') - .attr('class', 'file-load-input') + this.controls.dataFileLoad .on('change', function() { if (this.value.slice(-4).toLowerCase() == '.csv') { loadStatus.text(this.files[0].name + ' ready to load').style('color', 'green'); @@ -34,11 +12,7 @@ export function initFileLoad() { } }); - cat.controls.dataFileLoadButton = cat.controls.dataWrap - .append('button') - .text('Load') - .attr('class', 'file-load-button') - .attr('disabled', true) + this.controls.dataFileLoadButton .on('click', function(d) { //credit to https://jsfiddle.net/Ln37kqc0/ var files = cat.controls.dataFileLoad.node().files; diff --git a/src/cat/controls/initRendererSelect.js b/src/cat/controls/initRendererSelect.js index 3737446..0140424 100644 --- a/src/cat/controls/initRendererSelect.js +++ b/src/cat/controls/initRendererSelect.js @@ -1,74 +1,48 @@ -import updateRenderer from './initRendererSelect/updateRenderer'; +import unloadDOM from './initRendererSelect/unloadDOM'; +import loadPackageJSON from '../init/loadPackageJSON'; +import loadRenderer from '../init/loadRenderer'; +import getVersions from '../init/getVersions'; +import updateSettings from '../init/updateSettings'; -export function initRendererSelect(cat) { - cat.controls.rendererWrap.append('h3').text('1. Choose a Charting Library'); - cat.controls.rendererWrap.append('span').text('Library: '); +/* + 1. Removes the previous library's .js, .css, and/or stylesheet from the DOM. + 2. Updates the status section. + 3. Loads the master branch of the selected library. + 1. Loads the library's package.json file to know where the main .js file lives. + 2. Optionally loads the settings-schema.json file to populate the settings text/form if the library has a settings schema file. + 3. Loads the main .js file. + 4. Optionally loads the main .css file if the library has a .css file. + 4. Loads the branches and releases of the library. + 5. Updates the settings text/form. +*/ - cat.controls.rendererSelect = cat.controls.rendererWrap.append('select'); - cat.controls.rendererSelect +export function initRendererSelect() { + unloadDOM.call(this); + //updateStatus.call(this); + + const cat = this; + this.controls.rendererSelect .selectAll('option') - .data(cat.config.renderers) + .data(this.config.renderers) .enter() .append('option') .text(d => d.name); - - cat.controls.rendererSelect.on('change', function() { + this.controls.rendererSelect.on('change', function() { updateRenderer.call(cat, this); + getVersions(cat.controls.versionSelect, cat.current.api_url); }); - cat.controls.rendererWrap.append('br'); - cat.controls.rendererWrap.append('span').text('Version: '); - cat.controls.versionSelect = cat.controls.rendererWrap.append('input'); - cat.controls.versionSelect.node().value = 'master'; - cat.controls.versionSelect.on('input', function() { + this.controls.versionSelect.on('change', function() { + console.log(this.value); cat.current.version = this.value; - }); - cat.controls.versionSelect.on('change', function() { cat.settings.set(cat); }); - cat.controls.rendererWrap.append('br'); - - cat.controls.rendererWrap - .append('a') - .text('More Options') - .style('text-decoration', 'underline') - .style('color', 'blue') - .style('cursor', 'pointer') + this.controls.moreOptions .on('click', function() { d3.select(this).remove(); cat.controls.rendererWrap.selectAll('*').classed('hidden', false); }); - - //specify the code to create the chart - cat.controls.rendererWrap - .append('span') - .text(' Init: ') - .classed('hidden', true); - cat.controls.mainFunction = cat.controls.rendererWrap.append('input').classed('hidden', true); - cat.controls.mainFunction.node().value = cat.current.main; - cat.controls.rendererWrap - .append('span') - .text('.') - .classed('hidden', true); - cat.controls.subFunction = cat.controls.rendererWrap.append('input').classed('hidden', true); - cat.controls.subFunction.node().value = cat.current.sub; - cat.controls.rendererWrap.append('br').classed('hidden', true); - //Webcharts versionSelect - cat.controls.rendererWrap - .append('span') - .text('Webcharts Version: ') - .classed('hidden', true); - cat.controls.libraryVersion = cat.controls.rendererWrap.append('input').classed('hidden', true); - cat.controls.libraryVersion.node().value = 'master'; - cat.controls.rendererWrap.append('br').classed('hidden', true); - - cat.controls.rendererWrap - .append('span') - .text('Schema: ') - .classed('hidden', true); - cat.controls.schema = cat.controls.rendererWrap.append('input').classed('hidden', true); - cat.controls.schema.node().value = cat.current.schema; - cat.controls.rendererWrap.append('br').classed('hidden', true); - - //add enter listener - cat.controls.addEnterEventListener(cat.controls.rendererWrap, cat); + this.controls.mainFunction.node().value = this.current.main; + this.controls.subFunction.node().value = this.current.sub; + this.controls.schema.node().value = this.current.schema; + this.controls.addEnterEventListener(this.controls.rendererWrap, cat); } diff --git a/src/cat/controls/initRendererSelect/getVersions.js b/src/cat/controls/initRendererSelect/getVersions.js new file mode 100644 index 0000000..ced9b2b --- /dev/null +++ b/src/cat/controls/initRendererSelect/getVersions.js @@ -0,0 +1,27 @@ +export default function getVersions( + select, + repo = 'https://api.github.com/repos/RhoInc/Webcharts' +) { + const branches = fetch(`${repo}/branches`).then(response => response.json()); + const releases = fetch(`${repo}/releases`).then(response => response.json()); + + Promise.all([branches, releases]) + .then(values => { + const [branches, releases] = values; + branches.sort( + (a, b) => + a.name === 'master' ? -1 : b.name === 'master' ? 1 : a.name < b.name ? -1 : 1 + ); + select.selectAll('option').remove(); + select + .selectAll('option') + .data(d3.merge(values)) + .enter() + .append('option') + .text(d => d.tag_name || d.name) + .property('selected', d => d.name === 'master'); + }) + .catch(err => { + console.log(err); + }); +} diff --git a/src/cat/controls/initRendererSelect/loadDOM.js b/src/cat/controls/initRendererSelect/loadDOM.js new file mode 100644 index 0000000..da2dd4b --- /dev/null +++ b/src/cat/controls/initRendererSelect/loadDOM.js @@ -0,0 +1,2 @@ +export default function loadDOM() { +} diff --git a/src/cat/controls/initRendererSelect/unloadDOM.js b/src/cat/controls/initRendererSelect/unloadDOM.js new file mode 100644 index 0000000..93d8460 --- /dev/null +++ b/src/cat/controls/initRendererSelect/unloadDOM.js @@ -0,0 +1,14 @@ +export default function unloadDOM() { + d3 + .selectAll('link') + .filter(function() { + return !this.href.indexOf('css/cat.css'); + }) + .property('disabled', true) + .remove(); + + d3 + .selectAll('style') + .property('disabled', true) + .remove(); +} diff --git a/src/cat/controls/initRendererSelect/updateRenderer.js b/src/cat/controls/initRendererSelect/updateRenderer.js index d8fdc8e..127b716 100644 --- a/src/cat/controls/initRendererSelect/updateRenderer.js +++ b/src/cat/controls/initRendererSelect/updateRenderer.js @@ -1,4 +1,5 @@ export default function updateRenderer(select) { + this.previous = _.clone(this.current); this.current = d3 .select(select) .select('option:checked') diff --git a/src/cat/controls/initSubmit.js b/src/cat/controls/initSubmit.js index 535457c..e4c60e2 100644 --- a/src/cat/controls/initSubmit.js +++ b/src/cat/controls/initSubmit.js @@ -1,7 +1,24 @@ -import addControlsToggle from './initSubmit/addControlsToggle'; -import addSubmitButton from './initSubmit/addSubmitButton'; +import destroyChart from './initSubmit/destroyChart'; +import updateStatus from './initSubmit/updateStatus'; +import loadData from './initSubmit/loadData'; +import initializeChart from './initSubmit/initializeChart'; -export function initSubmit(cat) { - addControlsToggle.call(cat); - addSubmitButton.call(cat); +/* + 1. Destroys the currently displayed chart if one has been rendered. + 2. Updates the status section. + 3. Loads the selected data file. + 4. Initializes the selected charting application library. +*/ + +export function initSubmit() { + this.controls.submitButton.on('click', () => { + this.dataWrap.classed('hidden', true); + this.chartWrap.classed('hidden', false); + destroyChart.call(this); + updateStatus.call(this); + loadData.call(this) + .then(json => { + initializeChart.call(this, json); + }); + }); } diff --git a/src/cat/controls/initSubmit/addControlsToggle.js b/src/cat/controls/initSubmit/addControlsToggle.js deleted file mode 100644 index 205c369..0000000 --- a/src/cat/controls/initSubmit/addControlsToggle.js +++ /dev/null @@ -1,35 +0,0 @@ -export default function addControlsToggle() { - const cat = this; - - this.controls.minimize = this.controls.submitWrap - .append('div') - .classed('cat-button cat-button--minimize hidden', true) - .attr('title', 'Hide controls') - .text('<<') - .on('click', () => { - this.controls.wrap.classed('hidden', true); - this.chartWrap.style('margin-left', 0); - this.chartWrap.selectAll('.wc-chart').each(function(d) { - try { - d.draw(); - } catch (error) {} - }); - this.dataWrap.style('margin-left', 0); - this.controls.maximize = this.wrap - .insert('div', ':first-child') - .classed('cat-button cat-button--maximize', true) - .text('>>') - .attr('title', 'Show controls') - .on('click', function() { - cat.controls.wrap.classed('hidden', false); - cat.chartWrap.style('margin-left', '20%'); - cat.chartWrap.selectAll('.wc-chart').each(function(d) { - try { - d.draw(); - } catch (error) {} - }); - cat.dataWrap.style('margin-left', '20%'); - d3.select(this).remove(); - }); - }); -} diff --git a/src/cat/controls/initSubmit/addSubmitButton.js b/src/cat/controls/initSubmit/addSubmitButton.js deleted file mode 100644 index 9dd805b..0000000 --- a/src/cat/controls/initSubmit/addSubmitButton.js +++ /dev/null @@ -1,38 +0,0 @@ -import { loadLibrary } from '../../loadLibrary'; - -export default function addSubmitButton() { - this.controls.submitButton = this.controls.submitWrap - .append('button') - .attr('class', 'submit') - .text('Render Chart') - .on('click', () => { - this.controls.minimize.classed('hidden', false); - this.dataWrap.classed('hidden', true); - this.chartWrap.classed('hidden', false); - - //Disable and/or remove previously loaded stylesheets. - d3 - .selectAll('link') - .filter(function() { - return !this.href.indexOf('css/cat.css'); - }) - .property('disabled', true) - .remove(); - - d3 - .selectAll('style') - .property('disabled', true) - .remove(); - - this.chartWrap.selectAll('*').remove(); - this.printStatus = true; - this.statusDiv = this.chartWrap.append('div').attr('class', 'status'); - this.statusDiv - .append('div') - .text('Starting to render the chart ... ') - .classed('info', true); - - this.chartWrap.append('div').attr('class', 'chart'); - loadLibrary(this); - }); -} diff --git a/src/cat/controls/initSubmit/destroyChart.js b/src/cat/controls/initSubmit/destroyChart.js new file mode 100644 index 0000000..39e7ad2 --- /dev/null +++ b/src/cat/controls/initSubmit/destroyChart.js @@ -0,0 +1,30 @@ +export default function destroyChart() { + if (this.previous) { + if (this.previous.instance && this.previous.instance.destroy) { + console.log('destroy'); + console.log(this.previous); + if (this.previous.instance && this.previous.instance.destroy) + this.previous.instance.destroy(); + } else { + console.log('no destroy'); + console.log(this.previous); + this.chartWrap.selectAll('.wc-chart').each(function(chart) { + if (chart.destroy) chart.destroy(); + else { + //remove resize event listener + select(window).on('resize.' + chart.element + chart.id, null); + + //destroy controls + if (chart.controls) { + chart.controls.destroy(); + } + + //unmount chart wrapper + chart.wrap.remove(); + } + }); + } + } + + this.chartWrap.selectAll('*').remove(); +} diff --git a/src/cat/controls/initSubmit/initializeChart.js b/src/cat/controls/initSubmit/initializeChart.js new file mode 100644 index 0000000..b47c20a --- /dev/null +++ b/src/cat/controls/initSubmit/initializeChart.js @@ -0,0 +1,42 @@ +import { loadLibrary } from '../../loadLibrary'; + +export default function initializeChart(data) { + this.chartWrap.append('div').attr('class', 'chart'); + + this.status.loadStatus(this.statusDiv, true, this.dataObject.dataFilePath); + + if (this.current.sub) { + this.current.instance = window[this.current.main][this.current.sub]( + '.cat-chart', + this.current.config + ); + this.status.chartCreateStatus(this.statusDiv, this.current.main, this.current.sub); + } else { + this.current.instance = window[this.current.main]( + '.cat-chart .chart', + this.current.config + ); + this.status.chartCreateStatus(this.statusDiv, this.current.main); + } + + //this.current.htmlExport = createChartExport(this); // save the source code before init + + try { + this.current.instance.init(data); + } catch (err) { + this.status.chartInitStatus(this.statusDiv, false, err); + } finally { + this.status.chartInitStatus(this.statusDiv, true, null, this.current.htmlExport); + + // save to server button + if (this.config.useServer) { + this.status.saveToServer(this); + } + //showEnv(this); + + //don't print any new statuses until a new chart is rendered + this.printStatus = false; + } + + this.current.rendered = true; +} diff --git a/src/cat/controls/initSubmit/loadData.js b/src/cat/controls/initSubmit/loadData.js new file mode 100644 index 0000000..d8add0c --- /dev/null +++ b/src/cat/controls/initSubmit/loadData.js @@ -0,0 +1,8 @@ +export default function loadData() { + const dataFile = this.controls.dataFileSelect.node().value; + this.dataObject = this.config.dataFiles.find(f => f.label == dataFile); + this.dataObject.dataFilePath = this.dataObject.path + dataFile; + return fetch(this.dataObject.dataFilePath) + .then(response => response.text()) + .then(text => d3.csv.parse(text)); +} diff --git a/src/cat/controls/initSubmit/updateStatus.js b/src/cat/controls/initSubmit/updateStatus.js new file mode 100644 index 0000000..050543b --- /dev/null +++ b/src/cat/controls/initSubmit/updateStatus.js @@ -0,0 +1,8 @@ +export default function updateStatus() { + this.printStatus = true; + this.statusDiv = this.chartWrap.append('div').attr('class', 'status'); + this.statusDiv + .append('div') + .text('Starting to render the chart ... ') + .classed('info', true); +} diff --git a/src/cat/datapreview/showDataPreview.js b/src/cat/datapreview/showDataPreview.js index 428904d..48bfd5b 100644 --- a/src/cat/datapreview/showDataPreview.js +++ b/src/cat/datapreview/showDataPreview.js @@ -24,13 +24,57 @@ export function showDataPreview(cat) { cat.dataWrap .append('div') .attr('class', 'dataPreview') - .style('overflow-x', 'overlay'); - cat.dataPreview = webCharts.createTable('.dataPreview'); + // .style('overflow-x', 'overlay'); + //cat.dataPreview = webCharts.createTable('.dataPreview'); if (dataObject.user_loaded) { - cat.dataPreview.init(d3.csv.parse(dataObject.csv_raw)); + dataObject.data = d3.csv.parse(dataObject.csv_raw); + handsOnTable(dataObject.data); + //cat.dataPreview.init(d3.csv.parse(dataObject.csv_raw)); } else { d3.csv(path, function(raw) { - cat.dataPreview.init(raw); + dataObject.data = raw; + handsOnTable(dataObject.data); + //cat.dataPreview.init(raw); }); } + + function handsOnTable(data) { + const colHeaders = Object.keys(data[0]); + const table = new Handsontable( + cat.dataWrap.select('.dataPreview').node(), + { + data: data.map(d => Object.keys(d).map(key => d[key])), + colHeaders, + columns: colHeaders.map(_ => { return {type: 'text'}; }), + rowHeaders: true, + dropdownMenu: true, + filters: true, + afterChange: function(changes) { + dataObject.json = this.getData() + .map(d => { + return d.reduce( + (acc,cur,i) => { + acc[colHeaders[i]] = cur; + return acc; + }, + {} + ); + }); + }, + afterFilter: function(changes) { + dataObject.json = this.getData() + .map(d => { + return d.reduce( + (acc,cur,i) => { + acc[colHeaders[i]] = cur; + return acc; + }, + {} + ); + }); + }, + licenseKey: 'non-commercial-and-evaluation', + }, + ); + } } diff --git a/src/cat/defaultSettings.js b/src/cat/defaultSettings.js index 61f21b6..df92123 100644 --- a/src/cat/defaultSettings.js +++ b/src/cat/defaultSettings.js @@ -1,9 +1,12 @@ -const defaultSettings = { +export default { useServer: false, - rootURL: null, - dataURL: null, + rootURL: 'https://github.com/RhoInc', + dataURL: 'https://raw.githubusercontent.com/RhoInc/data-library/master/data/', + chartingLibrary: { + name: 'Webcharts', + css: 'css/webcharts.css', + versions: [], + }, + renderers: [], dataFiles: [], - renderers: [] }; - -export default defaultSettings; diff --git a/src/cat/init.js b/src/cat/init.js index 563002c..0e7bc77 100644 --- a/src/cat/init.js +++ b/src/cat/init.js @@ -1,16 +1,26 @@ -export function init() { - //layout the cat - this.wrap = d3 - .select(this.element) - .append('div') - .attr('class', 'cat-wrap'); - this.layout(this); +import setDefaults from './init/setDefaults'; +import layout from './init/layout'; +import loadData from './init/loadData'; +import loadChartingLibrary from './init/loadChartingLibrary'; +import loadChartingApplication from './init/loadChartingApplication'; +import initializeControls from './init/initializeControls'; - //initialize the settings - this.setDefaults(this); +export default function init() { + //settings + setDefaults.call(this); - //add others here! + //layout + layout.call(this); - //create the controls - this.controls.init(this); + //renderers and data files + loadData.call(this); + + //load charting library + loadChartingLibrary.call(this); + + //load charting application + loadChartingApplication.call(this); + + //initialize controls + initializeControls.call(this); } diff --git a/src/cat/init/initializeControls.js b/src/cat/init/initializeControls.js new file mode 100644 index 0000000..c9ca4f1 --- /dev/null +++ b/src/cat/init/initializeControls.js @@ -0,0 +1,9 @@ +import renderChart from './initializeControls/renderChart'; +import changeLibrary from './initializeControls/changeLibrary'; +import changeLibraryVersion from './initializeControls/changeLibraryVersion'; + +export default function initializeControls() { + renderChart.call(this); + changeLibrary.call(this); + changeLibraryVersion.call(this); +} diff --git a/src/cat/init/initializeControls/changeLibrary.js b/src/cat/init/initializeControls/changeLibrary.js new file mode 100644 index 0000000..fd44387 --- /dev/null +++ b/src/cat/init/initializeControls/changeLibrary.js @@ -0,0 +1,26 @@ +export default function changeLibrary() { + const cat = this; + + this.controls.rendererSelect.on('change', function(d) { + cat.chartingApplication = d3.select(this).selectAll('option:checked').datum(); + cat.chartingApplication.version = 'master'; + cat.controls.versionSelect.selectAll('option').property('selected', d => d.label === cat.chartingApplication.version); + cat.utilities.getVersions.call(cat, cat.chartingApplication.name) + .then(versions => { + cat.chartingApplication.versions = versions + .map(version => { + version.label = versions.tag_name + ? version.tag_name + : version.name; + return version; + }); + cat.utilities.updateSelect.call(cat, cat.controls.versionSelect, cat.chartingApplication.versions); + }); + cat.utilities.loadPackageJSON.call(cat, cat.chartingApplication.name, 'master') + .then(pkg => { + cat.chartingApplication.pkg = JSON.parse(pkg); + cat.utilities.loadFiles.call(cat, cat.chartingApplication.name, cat.chartingApplication.pkg, 'master', cat.chartingApplication.css); + cat.utilities.updateFields.call(cat, cat.chartingApplication.main, cat.chartingApplication.sub, cat.chartingApplication.schema); + }); + }); +} diff --git a/src/cat/init/initializeControls/changeLibraryVersion.js b/src/cat/init/initializeControls/changeLibraryVersion.js new file mode 100644 index 0000000..d678dc5 --- /dev/null +++ b/src/cat/init/initializeControls/changeLibraryVersion.js @@ -0,0 +1,13 @@ +export default function changeLibraryVersion() { + const cat = this; + + this.controls.versionSelect.on('change', function(d) { + cat.chartingApplication.version = d3.select(this).selectAll('option:checked').datum().label; + cat.utilities.loadPackageJSON.call(cat, cat.chartingApplication.name, cat.chartingApplication.version) + .then(pkg => { + cat.chartingApplication.pkg = JSON.parse(pkg); + cat.utilities.loadFiles.call(cat, cat.chartingApplication.name, cat.chartingApplication.pkg, cat.chartingApplication.version, cat.chartingApplication.css); + cat.utilities.updateFields.call(cat, cat.chartingApplication.main, cat.chartingApplication.sub, cat.chartingApplication.schema); + }); + }); +} diff --git a/src/cat/init/initializeControls/renderChart.js b/src/cat/init/initializeControls/renderChart.js new file mode 100644 index 0000000..59191dc --- /dev/null +++ b/src/cat/init/initializeControls/renderChart.js @@ -0,0 +1,21 @@ +import destroyChart from './renderChart/destroyChart'; +import loadData from './renderChart/loadData'; +import initializeChart from './renderChart/initializeChart'; + +/* + 1. Destroys the currently displayed chart if one has been rendered. + 2. Loads the selected data file. + 3. Initializes the selected charting application library. +*/ + +export default function renderChart() { + this.controls.submitButton.on('click', () => { + this.dataWrap.classed('hidden', true); + this.chartWrap.classed('hidden', false); + destroyChart.call(this); + loadData.call(this) + .then(json => { + initializeChart.call(this, json); + }); + }); +} diff --git a/src/cat/init/initializeControls/renderChart/destroyChart.js b/src/cat/init/initializeControls/renderChart/destroyChart.js new file mode 100644 index 0000000..18be5b3 --- /dev/null +++ b/src/cat/init/initializeControls/renderChart/destroyChart.js @@ -0,0 +1,26 @@ +export default function destroyChart() { + if (this.chartingApplicationInstance) { + if (this.chartingApplicationInstance && this.chartingApplicationInstance.destroy) { + if (this.chartingApplicationInstance && this.chartingApplicationInstance.destroy) + this.chartingApplicationInstance.destroy(); + } else { + this.chartWrap.selectAll('.wc-chart').each(function(chart) { + if (chart.destroy) chart.destroy(); + else { + //remove resize event listener + select(window).on('resize.' + chart.element + chart.id, null); + + //destroy controls + if (chart.controls) { + chart.controls.destroy(); + } + + //unmount chart wrapper + chart.wrap.remove(); + } + }); + } + } + + this.chartWrap.selectAll('*').remove(); +} diff --git a/src/cat/init/initializeControls/renderChart/initializeChart.js b/src/cat/init/initializeControls/renderChart/initializeChart.js new file mode 100644 index 0000000..92214f0 --- /dev/null +++ b/src/cat/init/initializeControls/renderChart/initializeChart.js @@ -0,0 +1,23 @@ +export default function initializeChart(data) { + this.chartWrap.append('div').attr('class', 'chart'); + + //Pass element and settings to charting application. + if (this.chartingApplication.sub) { + this.chartingApplicationInstance = window[this.chartingApplication.main][this.chartingApplication.sub]( + '.cat-chart .chart', + this.chartingApplication.config || {} + ); + } else { + this.chartingApplicationInstance = window[this.chartingApplication.main]( + '.cat-chart .chart', + this.chartingApplication.config || {} + ); + } + + //Pass data to charting application. + try { + this.chartingApplicationInstance.init(data); + } catch (err) { + console.warn(err); + } +} diff --git a/src/cat/init/initializeControls/renderChart/loadData.js b/src/cat/init/initializeControls/renderChart/loadData.js new file mode 100644 index 0000000..d8add0c --- /dev/null +++ b/src/cat/init/initializeControls/renderChart/loadData.js @@ -0,0 +1,8 @@ +export default function loadData() { + const dataFile = this.controls.dataFileSelect.node().value; + this.dataObject = this.config.dataFiles.find(f => f.label == dataFile); + this.dataObject.dataFilePath = this.dataObject.path + dataFile; + return fetch(this.dataObject.dataFilePath) + .then(response => response.text()) + .then(text => d3.csv.parse(text)); +} diff --git a/src/cat/init/layout.js b/src/cat/init/layout.js new file mode 100644 index 0000000..248d463 --- /dev/null +++ b/src/cat/init/layout.js @@ -0,0 +1,35 @@ +import toggleDisplayOfControls from './layout/toggleDisplayOfControls'; +import controls from './layout/controls'; + +export default function layout() { + this.wrap = d3 + .select(this.element) + .append('div') + .attr('class', 'cat-wrap'); + + //Controls display toggle + this.hideControls = this.wrap + .append('div') + .classed('cat-button cat-button--hide-controls', true) + .attr('title', 'Hide controls') + .text('<<'); + this.showControls = this.wrap + .append('div') + .classed('cat-button cat-button--show-controls hidden', true) + .attr('title', 'Show controls') + .text('>>'); + toggleDisplayOfControls.call(this); + + //Controls + this.controls.wrap = this.wrap.append('div').classed('cat-controls section', true); + controls.call(this); + + //Chart + this.chartWrap = this.wrap.append('div').classed('cat-chart section', true); + + //Table + this.dataWrap = this.wrap + .append('div') + .classed('cat-data section', true) + .classed('hidden', true); +} diff --git a/src/cat/init/layout/chart.js b/src/cat/init/layout/chart.js new file mode 100644 index 0000000..e69de29 diff --git a/src/cat/init/layout/controls.js b/src/cat/init/layout/controls.js new file mode 100644 index 0000000..05fbd9a --- /dev/null +++ b/src/cat/init/layout/controls.js @@ -0,0 +1,17 @@ +import renderChart from './controls/renderChart'; +import chooseAChartingLibrary from './controls/chooseAChartingLibrary'; +import chooseADataset from './controls/chooseADataset'; +import customizeTheChart from './controls/customizeTheChart'; +import environment from './controls/environment'; + +export default function controls() { + this.controls.wrap + .append('h2') + .classed('cat-controls-header', true) + .text('Charting Application Tester 😼'); + renderChart.call(this); + chooseAChartingLibrary.call(this); + chooseADataset.call(this); + customizeTheChart.call(this); + environment.call(this); +} diff --git a/src/cat/init/layout/controls/chooseAChartingLibrary.js b/src/cat/init/layout/controls/chooseAChartingLibrary.js new file mode 100644 index 0000000..0f42d11 --- /dev/null +++ b/src/cat/init/layout/controls/chooseAChartingLibrary.js @@ -0,0 +1,19 @@ +import library from './chooseAChartingLibrary/library'; +import version from './chooseAChartingLibrary/version'; +import moreOptions from './chooseAChartingLibrary/moreOptions'; +import init from './chooseAChartingLibrary/init'; +import webchartsVersion from './chooseAChartingLibrary/webchartsVersion'; +import schema from './chooseAChartingLibrary/schema'; + +export default function chooseAChartingLibrary() { + this.controls.rendererWrap = this.controls.wrap + .append('div') + .classed('control-section renderer-section', true); + this.controls.rendererWrap.append('h3').text('1. Choose a Charting Library'); + library.call(this); + version.call(this); + //moreOptions.call(this); + init.call(this); + schema.call(this); + webchartsVersion.call(this); +} diff --git a/src/cat/init/layout/controls/chooseAChartingLibrary/init.js b/src/cat/init/layout/controls/chooseAChartingLibrary/init.js new file mode 100644 index 0000000..b595f6e --- /dev/null +++ b/src/cat/init/layout/controls/chooseAChartingLibrary/init.js @@ -0,0 +1,13 @@ +export default function init() { + this.controls.rendererWrap + .append('span') + .text(' Init: ') + //.classed('hidden', true); + this.controls.mainFunction = this.controls.rendererWrap.append('input')//.classed('hidden', true); + this.controls.rendererWrap + .append('span') + .text('.') + //.classed('hidden', true); + this.controls.subFunction = this.controls.rendererWrap.append('input')//.classed('hidden', true); + this.controls.rendererWrap.append('br')//.classed('hidden', true); +} diff --git a/src/cat/init/layout/controls/chooseAChartingLibrary/library.js b/src/cat/init/layout/controls/chooseAChartingLibrary/library.js new file mode 100644 index 0000000..f1b8f7e --- /dev/null +++ b/src/cat/init/layout/controls/chooseAChartingLibrary/library.js @@ -0,0 +1,5 @@ +export default function library() { + this.controls.rendererWrap.append('span').text('Library: '); + this.controls.rendererSelect = this.controls.rendererWrap.append('select'); + this.controls.rendererWrap.append('br'); +} diff --git a/src/cat/init/layout/controls/chooseAChartingLibrary/moreOptions.js b/src/cat/init/layout/controls/chooseAChartingLibrary/moreOptions.js new file mode 100644 index 0000000..d27627b --- /dev/null +++ b/src/cat/init/layout/controls/chooseAChartingLibrary/moreOptions.js @@ -0,0 +1,8 @@ +export default function moreOptions() { + this.controls.moreOptions = this.controls.rendererWrap + .append('a') + .text('More Options') + .style('text-decoration', 'underline') + .style('color', 'blue') + .style('cursor', 'pointer'); +} diff --git a/src/cat/init/layout/controls/chooseAChartingLibrary/schema.js b/src/cat/init/layout/controls/chooseAChartingLibrary/schema.js new file mode 100644 index 0000000..90852ae --- /dev/null +++ b/src/cat/init/layout/controls/chooseAChartingLibrary/schema.js @@ -0,0 +1,8 @@ +export default function schema() { + this.controls.rendererWrap + .append('span') + .text('Schema: ') + //.classed('hidden', true); + this.controls.schema = this.controls.rendererWrap.append('input')//.classed('hidden', true); + this.controls.rendererWrap.append('br')//.classed('hidden', true); +} diff --git a/src/cat/init/layout/controls/chooseAChartingLibrary/version.js b/src/cat/init/layout/controls/chooseAChartingLibrary/version.js new file mode 100644 index 0000000..ae58a9e --- /dev/null +++ b/src/cat/init/layout/controls/chooseAChartingLibrary/version.js @@ -0,0 +1,5 @@ +export default function version() { + this.controls.rendererWrap.append('span').text('Version: '); + this.controls.versionSelect = this.controls.rendererWrap.append('select'); + this.controls.rendererWrap.append('br'); +} diff --git a/src/cat/init/layout/controls/chooseAChartingLibrary/webchartsVersion.js b/src/cat/init/layout/controls/chooseAChartingLibrary/webchartsVersion.js new file mode 100644 index 0000000..b24f218 --- /dev/null +++ b/src/cat/init/layout/controls/chooseAChartingLibrary/webchartsVersion.js @@ -0,0 +1,10 @@ +export default function webchartsVersion() { + this.controls.rendererWrap + .append('span') + .text('Webcharts Version: ') + //.classed('hidden', true); + this.controls.libraryVersion = this.controls.rendererWrap + .append('select') + //.classed('hidden', true); + this.controls.rendererWrap.append('br')//.classed('hidden', true); +} diff --git a/src/cat/init/layout/controls/chooseADataset.js b/src/cat/init/layout/controls/chooseADataset.js new file mode 100644 index 0000000..414829f --- /dev/null +++ b/src/cat/init/layout/controls/chooseADataset.js @@ -0,0 +1,15 @@ +import dataFile from './chooseADataset/dataFile'; +import loadADataFile from './chooseADataset/loadADataFile'; + +export default function chooseADataset() { + this.controls.dataWrap = this.controls.wrap + .append('div') + .classed('control-section data-section', true); + this.controls.dataWrap.append('h3').text('2. Choose a Dataset'); + dataFile.call(this); + this.controls.viewData = this.controls.dataWrap + .append('span') + .html('🔍') + .style('cursor', 'pointer'); + loadADataFile.call(this); +} diff --git a/src/cat/init/layout/controls/chooseADataset/dataFile.js b/src/cat/init/layout/controls/chooseADataset/dataFile.js new file mode 100644 index 0000000..6e5469f --- /dev/null +++ b/src/cat/init/layout/controls/chooseADataset/dataFile.js @@ -0,0 +1,3 @@ +export default function dataFile() { + this.controls.dataFileSelect = this.controls.dataWrap.append('select'); +} diff --git a/src/cat/init/layout/controls/chooseADataset/loadADataFile.js b/src/cat/init/layout/controls/chooseADataset/loadADataFile.js new file mode 100644 index 0000000..7c2c141 --- /dev/null +++ b/src/cat/init/layout/controls/chooseADataset/loadADataFile.js @@ -0,0 +1,27 @@ +export default function loadADataFile() { + const loadLabel = this.controls.dataWrap.append('p').style('margin', 0); + loadLabel + .append('small') + .text('Use local .csv file:') + .append('sup') + .html('ⓘ') + .property( + 'title', + 'Render a chart using a local file. File is added to the data set list, and is only available for a single session and is not saved.' + ) + .style('cursor', 'help'); + this.controls.loadStatus = loadLabel + .append('small') + .attr('class', 'loadStatus') + .style('float', 'right') + .text('Select a csv to load'); + this.controls.dataFileLoad = this.controls.dataWrap + .append('input') + .attr('type', 'file') + .attr('class', 'file-load-input'); + this.controls.dataFileLoadButton = this.controls.dataWrap + .append('button') + .text('Load') + .attr('class', 'file-load-button') + .attr('disabled', true); +} diff --git a/src/cat/init/layout/controls/customizeTheChart.js b/src/cat/init/layout/controls/customizeTheChart.js new file mode 100644 index 0000000..d9daf4b --- /dev/null +++ b/src/cat/init/layout/controls/customizeTheChart.js @@ -0,0 +1,32 @@ +export default function customizeTheChart() { + this.controls.settingsWrap = this.controls.wrap + .append('div') + .classed('control-section settings-section', true); + this.controls.settingsWrap.append('h3').html('3. Customize the Chart '); + this.controls.settingsWrap.append('span').text('Settings: '); + this.controls.settingsTypeText = this.controls.settingsWrap + .append('input') + .attr('class', 'radio') + .property('type', 'radio') + .property('name', 'settingsType') + .property('value', 'text'); + this.controls.settingsWrap.append('span').text('text'); + this.controls.settingsTypeForm = this.controls.settingsWrap + .append('input') + .attr('class', 'radio') + .property('type', 'radio') + .property('name', 'settingsType') + .property('value', 'form'); + this.controls.settingsWrap.append('span').text('form'); + this.controls.settingsType = this.controls.settingsWrap.selectAll('input[type="radio"]'); + this.controls.settingsWrap.append('br'); + this.controls.settingsInput = this.controls.settingsWrap + .append('textarea') + .attr('rows', 10) + .style('width', '90%') + .text('{}'); + this.controls.settingsForm = this.controls.settingsWrap + .append('div') + .attr('class', 'settingsForm') + .append('form'); +} diff --git a/src/cat/init/layout/controls/environment.js b/src/cat/init/layout/controls/environment.js new file mode 100644 index 0000000..36b0a4d --- /dev/null +++ b/src/cat/init/layout/controls/environment.js @@ -0,0 +1,10 @@ +export default function environment() { + this.controls.environmentWrap = this.controls.wrap + .append('div') + .classed('control-section environment-section', true); + this.controls.environmentWrap.append('h3').html('4. Environment '); + this.controls.cssList = this.controls.environmentWrap.append('ul').attr('class', 'cssList'); + this.controls.cssList.append('h5').text('Loaded Stylesheets'); + this.controls.jsList = this.controls.environmentWrap.append('ul').attr('class', 'jsList'); + this.controls.jsList.append('h5').text('Loaded javascript'); +} diff --git a/src/cat/init/layout/controls/renderChart.js b/src/cat/init/layout/controls/renderChart.js new file mode 100644 index 0000000..c15a295 --- /dev/null +++ b/src/cat/init/layout/controls/renderChart.js @@ -0,0 +1,9 @@ +export default function renderChart() { + this.controls.submitWrap = this.controls.wrap + .append('div') + .classed('control-section submit-section', true); + this.controls.submitButton = this.controls.submitWrap + .append('button') + .attr('class', 'submit') + .text('Render Chart') +} diff --git a/src/cat/init/layout/table.js b/src/cat/init/layout/table.js new file mode 100644 index 0000000..e69de29 diff --git a/src/cat/init/layout/toggleDisplayOfControls.js b/src/cat/init/layout/toggleDisplayOfControls.js new file mode 100644 index 0000000..17355d7 --- /dev/null +++ b/src/cat/init/layout/toggleDisplayOfControls.js @@ -0,0 +1,36 @@ +export default function toggleDisplayOfControls() { + const styleSheet = Array.from(document.styleSheets).find( + styleSheet => styleSheet.href.indexOf('cat.css') > -1 + ); + const controlsWidth = Array.from(styleSheet.cssRules).find( + cssRule => cssRule.selectorText === '.cat-wrap .cat-controls' + ).style.width; + + //Hide controls. + this.hideControls.on('click', () => { + this.controls.wrap.classed('hidden', true); + this.chartWrap.style('margin-left', 0); + this.chartWrap.selectAll('.wc-chart').each(function(d) { + try { + d.draw(); + } catch (error) {} + }); + this.dataWrap.style('margin-left', 0); + this.hideControls.classed('hidden', true); + this.showControls.classed('hidden', false); + }); + + //Show controls. + this.showControls.on('click', () => { + this.controls.wrap.classed('hidden', false); + this.chartWrap.style('margin-left', controlsWidth); + this.chartWrap.selectAll('.wc-chart').each(function(d) { + try { + d.draw(); + } catch (error) {} + }); + this.dataWrap.style('margin-left', controlsWidth); + this.hideControls.classed('hidden', false); + this.showControls.classed('hidden', true); + }); +} diff --git a/src/cat/init/loadChartingApplication.js b/src/cat/init/loadChartingApplication.js new file mode 100644 index 0000000..f9f3bfa --- /dev/null +++ b/src/cat/init/loadChartingApplication.js @@ -0,0 +1,20 @@ +export default function loadChartingApplication() { + this.chartingApplication = this.config.renderers[0]; + this.utilities.getVersions.call(this, this.chartingApplication.name) + .then(versions => { + this.chartingApplication.versions = versions + .map(version => { + version.label = versions.tag_name + ? version.tag_name + : version.name; + return version; + }); + this.utilities.updateSelect.call(this, this.controls.versionSelect, this.chartingApplication.versions); + }); + this.utilities.loadPackageJSON.call(this, this.chartingApplication.name, 'master') + .then(pkg => { + this.chartingApplication.pkg = JSON.parse(pkg); + this.utilities.loadFiles.call(this, this.chartingApplication.name, this.chartingApplication.pkg, 'master', this.chartingApplication.css); + this.utilities.updateFields.call(this, this.chartingApplication.main, this.chartingApplication.sub, this.chartingApplication.schema); + }); +} diff --git a/src/cat/init/loadChartingLibrary.js b/src/cat/init/loadChartingLibrary.js new file mode 100644 index 0000000..5cebafa --- /dev/null +++ b/src/cat/init/loadChartingLibrary.js @@ -0,0 +1,18 @@ +export default function loadChartingLibrary() { + this.utilities.getVersions.call(this, this.config.chartingLibrary.name) + .then(versions => { + this.config.chartingLibrary.versions = versions + .map(version => { + version.label = versions.tag_name + ? version.tag_name + : version.name; + return version; + }); + this.utilities.updateSelect.call(this, this.controls.libraryVersion, this.config.chartingLibrary.versions); + }); + this.utilities.loadPackageJSON.call(this, this.config.chartingLibrary.name, 'master') + .then(pkg => { + this.config.chartingLibrary.pkg = JSON.parse(pkg); + this.utilities.loadFiles.call(this, this.config.chartingLibrary.name, this.config.chartingLibrary.pkg, 'master', this.config.chartingLibrary.css); + }); +} diff --git a/src/cat/init/loadData.js b/src/cat/init/loadData.js new file mode 100644 index 0000000..85d7461 --- /dev/null +++ b/src/cat/init/loadData.js @@ -0,0 +1,4 @@ +export default function loadData() { + this.utilities.updateSelect.call(this, this.controls.rendererSelect, this.config.renderers); + this.utilities.updateSelect.call(this, this.controls.dataFileSelect, this.config.dataFiles); +} diff --git a/src/cat/init/setDefaults.js b/src/cat/init/setDefaults.js new file mode 100644 index 0000000..3d750f7 --- /dev/null +++ b/src/cat/init/setDefaults.js @@ -0,0 +1,22 @@ +import defaultSettings from '../defaultSettings'; + +export default function setDefaults() { + this.config.useServer = this.config.useServer || defaultSettings.useServer; + this.config.rootURL = this.config.rootURL || defaultSettings.rootURL; + this.config.apiURL = this.config.rootURL.replace('github.com', 'api.github.com/repos'); + this.config.cdnURL = this.config.rootURL.replace('github.com', 'cdn.jsdelivr.net/gh'); + this.config.dataURL = this.config.dataURL || defaultSettings.dataURL; + this.config.chartingLibrary = this.config.chartingLibrary || defaultSettings.chartingLibrary; + this.config.renderers = this.config.renderers || defaultSettings.renderers; + this.config.dataFiles = this.config.dataFiles || defaultSettings.dataFiles; + + this.config.renderers.forEach(renderer => { + renderer.label = renderer.label || renderer.name; + }); + + this.config.dataFiles = this.config.dataFiles.map(df => { + return typeof df === 'string' + ? { label: df, path: this.config.dataURL, user_loaded: false } + : df; + }); +} diff --git a/src/cat/layout.js b/src/cat/layout.js deleted file mode 100644 index 9df959a..0000000 --- a/src/cat/layout.js +++ /dev/null @@ -1,35 +0,0 @@ -export function layout(cat) { - /* Layout primary sections */ - cat.controls.wrap = cat.wrap.append('div').classed('cat-controls section', true); - cat.chartWrap = cat.wrap.append('div').classed('cat-chart section', true); - cat.dataWrap = cat.wrap - .append('div') - .classed('cat-data section', true) - .classed('hidden', true); - - /* Layout CAT Controls Divs */ - cat.controls.wrap - .append('h2') - .classed('cat-controls-header', true) - .text('Charting Application Tester 😼'); - - cat.controls.submitWrap = cat.controls.wrap - .append('div') - .classed('control-section submit-section', true); - - cat.controls.rendererWrap = cat.controls.wrap - .append('div') - .classed('control-section renderer-section', true); - - cat.controls.dataWrap = cat.controls.wrap - .append('div') - .classed('control-section data-section', true); - - cat.controls.settingsWrap = cat.controls.wrap - .append('div') - .classed('control-section settings-section', true); - - cat.controls.environmentWrap = cat.controls.wrap - .append('div') - .classed('control-section environment-section', true); -} diff --git a/src/cat/loadLibrary.js b/src/cat/loadLibrary.js index 2e3b1cd..48a5d42 100644 --- a/src/cat/loadLibrary.js +++ b/src/cat/loadLibrary.js @@ -1,4 +1,4 @@ -import { scriptLoader } from '../util/scriptLoader'; +import scriptLoader from '../util/scriptLoader'; import { loadRenderer } from './loadRenderer'; import { getCSS } from './env/getCSS'; import { getJS } from './env/getJS'; diff --git a/src/cat/loadRenderer.js b/src/cat/loadRenderer.js index 144691d..efc2ff2 100644 --- a/src/cat/loadRenderer.js +++ b/src/cat/loadRenderer.js @@ -1,76 +1,16 @@ -import loadPackageJson from './loadRenderer/loadPackageJson'; -import { getCSS } from './env/getCSS'; -import { getJS } from './env/getJS'; -import { scriptLoader } from '../util/scriptLoader'; -import { renderChart } from './renderChart'; +import loadWebcharts from './loadRenderer/loadWebcharts'; +import getVersions from './loadRenderer/getVersions'; +import loadPackageJSON from './loadRenderer/loadPackageJSON'; +import loadRenderer from './loadRenderer/loadRenderer'; -export function loadRenderer(cat) { - const promisedPackage = loadPackageJson(cat); - promisedPackage.then(function(response) { - cat.current.package = JSON.parse(response); - cat.current.js_url = `${cat.current.url}/${cat.current.package.main.replace( - /^\.?\/?/, - '' - )}`; - cat.current.css_url = cat.current.css ? `${cat.current.url}/${cat.current.css}` : null; - - if (cat.current.css) { - var current_css = getCSS().filter(f => f.link == cat.current.css_url); - var css_loaded = current_css.length > 0; - if (!css_loaded) { - var link = document.createElement('link'); - link.href = cat.current.css_url; - - link.type = 'text/css'; - link.rel = 'stylesheet'; - document.getElementsByTagName('head')[0].appendChild(link); - } else if (current_css[0].disabled) { - //enable the css if it's disabled - d3.select(current_css[0].sel).property('disabled', false); - cat.controls.cssList - .selectAll('li') - .filter(d => d.link == cat.current.css_url) - .select('input') - .property('checked', true); - } - } - - var current_js = getJS().filter(f => f.link == cat.current.js_url); - var js_loaded = current_js.length > 0; - - if (!js_loaded) { - var loader = new scriptLoader(); - loader.require(cat.current.js_url, { - async: true, - success: function() { - cat.status.loadStatus( - cat.statusDiv, - true, - cat.current.js_url, - cat.current.name, - cat.current.version - ); - renderChart(cat); - }, - failure: function() { - cat.status.loadStatus( - cat.statusDiv, - false, - cat.current.js_url, - cat.current.name, - cat.current.version - ); - } - }); - } else { - cat.status.loadStatus( - cat.statusDiv, - true, - cat.current.js_url, - cat.current.name, - cat.current.version - ); - renderChart(cat); - } - }); +export default function loadRenderer() { + loadWebcharts.call(this, 'master'); + getVersions.call(this, this.controls.libraryVersion); + this.current = this.config.renderers[0]; + this.current.version = 'master'; + loadPackageJSON.call(this) + .then(response => { + loadRenderer.call(this, 'master', response) + getVersions.call(this, this.controls.versionSelect, this.current.api_url); + }); } diff --git a/src/cat/loadRenderer/getVersions.js b/src/cat/loadRenderer/getVersions.js new file mode 100644 index 0000000..ced9b2b --- /dev/null +++ b/src/cat/loadRenderer/getVersions.js @@ -0,0 +1,27 @@ +export default function getVersions( + select, + repo = 'https://api.github.com/repos/RhoInc/Webcharts' +) { + const branches = fetch(`${repo}/branches`).then(response => response.json()); + const releases = fetch(`${repo}/releases`).then(response => response.json()); + + Promise.all([branches, releases]) + .then(values => { + const [branches, releases] = values; + branches.sort( + (a, b) => + a.name === 'master' ? -1 : b.name === 'master' ? 1 : a.name < b.name ? -1 : 1 + ); + select.selectAll('option').remove(); + select + .selectAll('option') + .data(d3.merge(values)) + .enter() + .append('option') + .text(d => d.tag_name || d.name) + .property('selected', d => d.name === 'master'); + }) + .catch(err => { + console.log(err); + }); +} diff --git a/src/cat/loadRenderer/loadData.js b/src/cat/loadRenderer/loadData.js new file mode 100644 index 0000000..e69de29 diff --git a/src/cat/loadRenderer/loadLibrary.js b/src/cat/loadRenderer/loadLibrary.js new file mode 100644 index 0000000..a41fd27 --- /dev/null +++ b/src/cat/loadRenderer/loadLibrary.js @@ -0,0 +1,32 @@ +import scriptLoader from '../../util/scriptLoader'; + +export default function loadWebcharts(library, version, response) { + // --- load css --- // + const cssPath = + version !== 'master' + ? this.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' + : this.config.rootURL + '/Webcharts/css/webcharts.css'; + + const link = document.createElement('link'); + link.href = cssPath; + link.type = 'text/css'; + link.rel = 'stylesheet'; + document.getElementsByTagName('head')[0].appendChild(link); + + // --- load js --- // + const rendererPath = + version !== 'master' + ? this.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' + : this.config.rootURL + '/Webcharts/build/webcharts.js'; + + const loader = new scriptLoader(); + loader.require(rendererPath, { + async: true, + success: () => { + //this.status.loadStatus(this.statusDiv, true, rendererPath, library, version); + }, + failure: () => { + //this.status.loadStatus(this.statusDiv, false, rendererPath, library, version); + } + }); +} diff --git a/src/cat/loadRenderer/loadPackageJson.js b/src/cat/loadRenderer/loadPackageJson.js index e4eedfc..829e322 100644 --- a/src/cat/loadRenderer/loadPackageJson.js +++ b/src/cat/loadRenderer/loadPackageJson.js @@ -1,13 +1,13 @@ -export default function loadPackageJson(cat) { - return new Promise(function(resolve, reject) { - cat.current.url = - cat.current.version === 'master' - ? `${cat.current.rootURL || cat.config.rootURL}/${cat.current.name}` - : `${cat.current.rootURL || cat.config.rootURL}/${cat.current.name}@${ - cat.current.version +export default function loadPackageJson() { + return new Promise((resolve, reject) => { + this.current.url = + this.current.version === 'master' + ? `${this.current.rootURL || this.config.rootURL}/${this.current.name}` + : `${this.current.rootURL || this.config.rootURL}/${this.current.name}@${ + this.current.version }`; - var xhr = new XMLHttpRequest(); - xhr.open('GET', `${cat.current.url}/package.json`); + const xhr = new XMLHttpRequest(); + xhr.open('GET', `${this.current.url}/package.json`); xhr.onload = function() { if (this.status === 200) { resolve(xhr.response); diff --git a/src/cat/loadRenderer/loadRenderer.js b/src/cat/loadRenderer/loadRenderer.js new file mode 100644 index 0000000..14d0d25 --- /dev/null +++ b/src/cat/loadRenderer/loadRenderer.js @@ -0,0 +1,60 @@ +import scriptLoader from '../../util/scriptLoader'; + +export default function loadRenderer(version, response) { + //Attach package.json. + this.current.package = JSON.parse(response); + + //Update version. + this.controls.versionSelect.node().value = version || 'master'; + + //Update namespace and method. + this.controls.mainFunction.node().value = this.current.main; + this.controls.subFunction.node().value = this.current.sub; + + //Update schema. + this.controls.schema.node().value = this.current.schema; + + //Update data file. + this.controls.dataFileSelect + .selectAll('option') + .property('selected', d => this.current.defaultData === d.label); + + //Load .css file + this.current.css_url = this.current.css ? `${this.current.url}/${this.current.css}` : null; + if (this.current.css) { + this.current.link = document.createElement('link'); + this.current.link.href = this.current.css_url; + + this.current.link.type = 'text/css'; + this.current.link.rel = 'stylesheet'; + document.getElementsByTagName('head')[0].appendChild(this.current.link); + } + + //Load .js file + this.current.js_url = `${this.current.url}/${this.current.package.main.replace( + /^\.?\/?/, + '' + )}`; + const loader = new scriptLoader(); + this.current.script = loader.require(this.current.js_url, { + async: true, + success: () => { + //this.status.loadStatus( + // this.statusDiv, + // true, + // this.current.js_url, + // this.current.name, + // version || this.current.version + //); + }, + failure: () => { + //this.status.loadStatus( + // this.statusDiv, + // false, + // this.current.js_url, + // this.current.name, + // version || this.current.version + //); + } + }); +} diff --git a/src/cat/loadRenderer/loadWebcharts.js b/src/cat/loadRenderer/loadWebcharts.js new file mode 100644 index 0000000..afeb66a --- /dev/null +++ b/src/cat/loadRenderer/loadWebcharts.js @@ -0,0 +1,35 @@ +import scriptLoader from '../../util/scriptLoader'; + +export default function loadWebcharts(version) { + version = version || this.controls.libraryVersion.node().value; + const library = 'webcharts'; //hardcode to webcharts for now - could generalize later + + // --- load css --- // + const cssPath = + version !== 'master' + ? this.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' + : this.config.rootURL + '/Webcharts/css/webcharts.css'; + + const link = document.createElement('link'); + link.href = cssPath; + link.type = 'text/css'; + link.rel = 'stylesheet'; + document.getElementsByTagName('head')[0].appendChild(link); + + // --- load js --- // + const rendererPath = + version !== 'master' + ? this.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' + : this.config.rootURL + '/Webcharts/build/webcharts.js'; + + const loader = new scriptLoader(); + loader.require(rendererPath, { + async: true, + success: () => { + //this.status.loadStatus(this.statusDiv, true, rendererPath, library, version); + }, + failure: () => { + //this.status.loadStatus(this.statusDiv, false, rendererPath, library, version); + } + }); +} diff --git a/src/cat/loadRenderer/updateSettings.js b/src/cat/loadRenderer/updateSettings.js new file mode 100644 index 0000000..3b8cb13 --- /dev/null +++ b/src/cat/loadRenderer/updateSettings.js @@ -0,0 +1,2 @@ +export default function updateSettings() { +} diff --git a/src/cat/renderChart.js b/src/cat/renderChart.js index f1b0cf2..a9b8547 100644 --- a/src/cat/renderChart.js +++ b/src/cat/renderChart.js @@ -17,20 +17,23 @@ export function renderChart(cat) { } else { cat.status.loadStatus(cat.statusDiv, true, dataFilePath); if (cat.current.sub) { - var myChart = window[cat.current.main][cat.current.sub]( + cat.current.instance = window[cat.current.main][cat.current.sub]( '.cat-chart', cat.current.config ); cat.status.chartCreateStatus(cat.statusDiv, cat.current.main, cat.current.sub); } else { - var myChart = window[cat.current.main]('.cat-chart .chart', cat.current.config); + cat.current.instance = window[cat.current.main]( + '.cat-chart .chart', + cat.current.config + ); cat.status.chartCreateStatus(cat.statusDiv, cat.current.main); } cat.current.htmlExport = createChartExport(cat); // save the source code before init try { - myChart.init(data); + cat.current.instance.init(data); } catch (err) { cat.status.chartInitStatus(cat.statusDiv, false, err); } finally { @@ -46,15 +49,19 @@ export function renderChart(cat) { cat.printStatus = false; } } + cat.current.rendered = true; } - if (dataObject.user_loaded) { + if (dataObject.json) + render(false, dataObject.json); + else if (dataObject.user_loaded) { dataObject.json = d3.csv.parse(dataObject.csv_raw); render(false, dataObject.json); } else { var dataFilePath = dataObject.path + dataFile; d3.csv(dataFilePath, function(error, data) { - render(error, data); + dataObject.json = data; + render(error, dataObject.json); }); } } diff --git a/src/cat/setDefaults.js b/src/cat/setDefaults.js deleted file mode 100644 index a23fce0..0000000 --- a/src/cat/setDefaults.js +++ /dev/null @@ -1,15 +0,0 @@ -import defaultSettings from './defaultSettings'; - -export function setDefaults(cat) { - cat.config.useServer = cat.config.useServer || defaultSettings.useServer; - cat.config.rootURL = cat.config.rootURL || defaultSettings.rootURL; - cat.config.dataURL = cat.config.dataURL || defaultSettings.dataURL; - cat.config.dataFiles = cat.config.dataFiles || defaultSettings.dataFiles; - cat.config.renderers = cat.config.renderers || defaultSettings.renderers; - - cat.config.dataFiles = cat.config.dataFiles.map(function(df) { - return typeof df == 'string' - ? { label: df, path: cat.config.dataURL, user_loaded: false } - : df; - }); -} diff --git a/src/cat/settings.js b/src/cat/settings.js index 0f530af..f13013d 100644 --- a/src/cat/settings.js +++ b/src/cat/settings.js @@ -6,7 +6,7 @@ import { set } from './settings/set'; import { sync } from './settings/sync'; import { setStatus } from './settings/setStatus'; -export const settings = { +export default { set: set, sync: sync, setStatus: setStatus diff --git a/src/cat/status.js b/src/cat/status.js index e434bf4..1fd15e3 100644 --- a/src/cat/status.js +++ b/src/cat/status.js @@ -7,7 +7,7 @@ import { chartInitStatus } from './status/chartInitStatus'; import { saveToServer } from './status/saveToServer'; import { loadStatus } from './status/loadStatus'; -export const status = { +export default { chartCreateStatus: chartCreateStatus, chartInitStatus: chartInitStatus, saveToServer: saveToServer, diff --git a/src/createCat.js b/src/createCat.js index 7ee0e95..883c229 100644 --- a/src/createCat.js +++ b/src/createCat.js @@ -1,20 +1,18 @@ -import { init } from './cat/init'; -import { layout } from './cat/layout'; -import { controls } from './cat/controls'; -import { setDefaults } from './cat/setDefaults'; -import { settings } from './cat/settings'; -import { status } from './cat/status'; +import utilities from './util/utilities'; +import init from './cat/init'; +//import controls from './cat/controls'; +//import settings from './cat/settings'; +//import status from './cat/status'; export function createCat(element = 'body', config) { - let cat = { - element: element, - config: config, - init: init, - layout: layout, - controls: controls, - setDefaults: setDefaults, - settings: settings, - status: status + const cat = { + element, + config, + utilities, + init, + controls: {}, + //settings, + //status }; return cat; diff --git a/src/util/defaultSettings.js b/src/util/defaultSettings.js new file mode 100644 index 0000000..e69de29 diff --git a/src/util/getVersions.js b/src/util/getVersions.js new file mode 100644 index 0000000..39098f4 --- /dev/null +++ b/src/util/getVersions.js @@ -0,0 +1,18 @@ +export default function getVersions(repo) { + const apiURL = this.config.apiURL + '/' + repo; + const branches = fetch(`${apiURL}/branches`).then(response => response.json()); + const releases = fetch(`${apiURL}/releases`).then(response => response.json()); + + return Promise.all([branches, releases]) + .then(values => { + const [branches, releases] = values; + branches.sort( + (a, b) => + a.name === 'master' ? -1 : b.name === 'master' ? 1 : a.name < b.name ? -1 : 1 + ); + return d3.merge(values); + }) + .catch(err => { + console.log(err); + }); +} diff --git a/src/util/loadFiles.js b/src/util/loadFiles.js new file mode 100644 index 0000000..57d9437 --- /dev/null +++ b/src/util/loadFiles.js @@ -0,0 +1,34 @@ +import scriptLoader from './scriptLoader'; + +export default function loadFiles(repo, pkg, branch, css) { + const version = branch || pkg.version; + const cdnURL = this.config.cdnURL + '/' + repo; + + //Load .css file + if (css) { + const cssURL = version === 'master' + ? cdnURL + '/' + css + : cdnURL + '@' + version + '/' + css; + const link = document.createElement('link'); + link.href = cssURL; + link.type = 'text/css'; + link.rel = 'stylesheet'; + document.getElementsByTagName('head')[0].appendChild(link); + } + + //Load .js file + const jsURL = version === 'master' + ? cdnURL + '/' + pkg.main.replace(/^\.?\/?/, '') + : cdnURL + '@' + version + '/' + pkg.main.replace(/^\.?\/?/, ''); + + const loader = new scriptLoader(); + const script = loader.require(jsURL, { + async: true, + success: () => { + console.log(`Loaded ${jsURL}.`); + }, + failure: () => { + console.warn(`Failed to load ${jsURL}.`); + } + }); +} diff --git a/src/util/loadPackageJSON.js b/src/util/loadPackageJSON.js new file mode 100644 index 0000000..444771c --- /dev/null +++ b/src/util/loadPackageJSON.js @@ -0,0 +1,28 @@ +export default function loadPackageJSON(repo, version) { + const cdnURL = this.config.cdnURL + '/' + repo; + const pkgURL = version === 'master' + ? cdnURL + '/package.json' + : cdnURL + '@' + version + '/package.json'; + + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', pkgURL); + xhr.onload = function() { + if (this.status === 200) { + resolve(xhr.response); + } else { + reject({ + status: this.status, + statusTxt: xhr.statusText + }); + } + }; + xhr.onerror = function() { + reject({ + status: this.status, + statusText: xhr.statusText + }); + }; + xhr.send(); + }); +} diff --git a/src/util/scriptLoader.js b/src/util/scriptLoader.js index f7a9c1c..0dc0345 100644 --- a/src/util/scriptLoader.js +++ b/src/util/scriptLoader.js @@ -1,6 +1,6 @@ // Nice script loader from here: https://stackoverflow.com/questions/538745/how-to-tell-if-a-script-tag-failed-to-load -export function scriptLoader() {} +export default function scriptLoader() {} scriptLoader.prototype = { timer: function( @@ -100,6 +100,6 @@ scriptLoader.prototype = { } }, 1); }, typeof args.delay == 'number' ? args.delay : 1); - return true; + return scriptTag; } }; diff --git a/src/util/updateFields.js b/src/util/updateFields.js new file mode 100644 index 0000000..4e46ea9 --- /dev/null +++ b/src/util/updateFields.js @@ -0,0 +1,8 @@ +export default function updateFields(version) { + this.controls.mainFunction.node().value = this.chartingApplication.main; + this.controls.subFunction.node().value = this.chartingApplication.sub; + this.controls.schema.node().value = this.chartingApplication.schema; + this.controls.dataFileSelect + .selectAll('option') + .property('selected', d => this.chartingApplication.defaultData === d.label); +} diff --git a/src/util/updateSelect.js b/src/util/updateSelect.js new file mode 100644 index 0000000..4c09e05 --- /dev/null +++ b/src/util/updateSelect.js @@ -0,0 +1,8 @@ +export default function updateSelect(select, data) { + select + .selectAll('option') + .data(data) + .enter() + .append('option') + .text(d => d.label); +} diff --git a/src/util/utilities.js b/src/util/utilities.js new file mode 100644 index 0000000..9e25491 --- /dev/null +++ b/src/util/utilities.js @@ -0,0 +1,15 @@ +import getVersions from './getVersions'; +import updateSelect from './updateSelect'; +import loadPackageJSON from './loadPackageJSON'; +import loadFiles from './loadFiles'; +import scriptLoader from './scriptLoader'; +import updateFields from './updateFields'; + +export default { + getVersions, + updateSelect, + loadPackageJSON, + loadFiles, + scriptLoader, + updateFields, +} diff --git a/test-page/handsontable/index.css b/test-page/handsontable/index.css new file mode 100644 index 0000000..7819db5 --- /dev/null +++ b/test-page/handsontable/index.css @@ -0,0 +1,33 @@ +@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,300); +* { + padding: 0; + margin: 0; + font-family: 'Open Sans'; +} +#title { + width: 96%; + padding: 0 0 12px 0; + border-bottom: 2px solid lightgray; + margin: 24px 2% 12px 2%; + font-size: 32px; + font-weight: normal; +} +#subtitle { + width: 96%; + margin: 0 2% 12px 2%; + font-size: 24px; + font-weight: lighter; +} +#container { + width: 96%; + margin: 12px 2%; + display: inline-block; +} +#chart { + width: 50%; + float: left; +} +#table { + width: 50%; + float: right; +} diff --git a/test-page/handsontable/index.html b/test-page/handsontable/index.html new file mode 100644 index 0000000..26e79f0 --- /dev/null +++ b/test-page/handsontable/index.html @@ -0,0 +1,27 @@ + + + + Hands-on Table: Test Page + + + + + + + + + + + + + +
Hands-on Table
+
Test Page
+
+
+
+
+ + + + diff --git a/test-page/handsontable/index.js b/test-page/handsontable/index.js new file mode 100644 index 0000000..55b08b5 --- /dev/null +++ b/test-page/handsontable/index.js @@ -0,0 +1,92 @@ +fetch('https://raw.githubusercontent.com/RhoInc/data-library/master/data/miscellaneous/climate-data.csv') + .then(response => response.text()) + .then(text => { + //const chartSettings = { + // x: { + // type: 'linear', + // column: 'sepal width', + // label: 'Sepal Width' + // }, + // y: { + // type: 'linear', + // column: 'sepal length', + // label: 'Sepal Length' + // }, + // marks: [ + // { + // type: 'circle', + // per: ['species', 'sepal width', 'sepal length'], + // tooltip: '[species]: $x/$y', + // attributes: { + // stroke: 'black' + // } + // } + // ], + // color_by: 'species', + // color_dom: null, + // legend: { + // label: 'Species', + // location: 'top' + // }, + // resizable: false + //}; + //const controls = new webCharts.createControls( + // document.getElementById('chart'), + // { + // inputs: [ + // { + // type: 'subsetter', + // value_col: 'species', + // label: 'Species' + // } + // ] + // } + //); + //const chart = new webCharts.createChart( + // document.getElementById('chart'), + // chartSettings, + // controls + //); + const data = d3.csv.parse(text); + console.log(data); + //chart.init(data); + const colHeaders = Object.keys(data[0]); + const table = new Handsontable( + document.getElementById('table'), + { + data: data.map(d => Object.keys(d).map(key => d[key])), + colHeaders, + columns: colHeaders.map(_ => { return {name: _, type: 'text'}; }), + rowHeaders: true, + dropdownMenu: true, + filters: true, + //afterChange: function(changes) { + // const updatedData = this.getData() + // .map(d => { + // return d.reduce( + // (acc,cur,i) => { + // acc[colHeaders[i]] = cur; + // return acc; + // }, + // {} + // ); + // }); + // chart.draw(updatedData); + //}, + //afterFilter: function(changes) { + // const updatedData = this.getData() + // .map(d => { + // return d.reduce( + // (acc,cur,i) => { + // acc[colHeaders[i]] = cur; + // return acc; + // }, + // {} + // ); + // }); + // chart.draw(updatedData); + //}, + licenseKey: 'non-commercial-and-evaluation', + }, + ); + });